|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2020 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- The code included in this file is provided under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license. Permission
- To use, copy, modify, and/or distribute this software for any purpose with or
- without fee is hereby granted provided that the above copyright notice and
- this permission notice appear in all copies.
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- template <typename Detector>
- struct BlockImplementation : public Block,
- private MIDIDeviceConnection::Listener,
- private Timer
- {
- public:
- struct ControlButtonImplementation;
- struct TouchSurfaceImplementation;
- struct LEDGridImplementation;
- struct LEDRowImplementation;
-
- BlockImplementation (Detector& detectorToUse, const DeviceInfo& deviceInfo)
- : Block (deviceInfo.serial.asString(),
- deviceInfo.version.asString(),
- deviceInfo.name.asString()),
- modelData (deviceInfo.serial),
- remoteHeap (modelData.programAndHeapSize),
- detector (&detectorToUse),
- config (modelData.defaultConfig)
- {
- markReconnected (deviceInfo);
-
- if (modelData.hasTouchSurface)
- touchSurface.reset (new TouchSurfaceImplementation (*this));
-
- int i = 0;
-
- for (auto&& b : modelData.buttons)
- controlButtons.add (new ControlButtonImplementation (*this, i++, b));
-
- if (modelData.lightGridWidth > 0 && modelData.lightGridHeight > 0)
- ledGrid.reset (new LEDGridImplementation (*this));
-
- for (auto&& s : modelData.statusLEDs)
- statusLights.add (new StatusLightImplementation (*this, s));
-
- updateMidiConnectionListener();
- }
-
- ~BlockImplementation() override
- {
- markDisconnected();
- }
-
- void markDisconnected()
- {
- if (auto surface = dynamic_cast<TouchSurfaceImplementation*> (touchSurface.get()))
- surface->disableTouchSurface();
-
- disconnectMidiConnectionListener();
- connectionTime = Time();
- }
-
- void markReconnected (const DeviceInfo& deviceInfo)
- {
- if (wasPowerCycled())
- resetPowerCycleFlag();
-
- if (connectionTime == Time())
- connectionTime = Time::getCurrentTime();
-
- updateDeviceInfo (deviceInfo);
-
- remoteHeap.reset();
-
- setProgram (nullptr);
-
- if (auto surface = dynamic_cast<TouchSurfaceImplementation*> (touchSurface.get()))
- surface->activateTouchSurface();
-
- updateMidiConnectionListener();
- }
-
- void updateDeviceInfo (const DeviceInfo& deviceInfo)
- {
- versionNumber = deviceInfo.version.asString();
- name = deviceInfo.name.asString();
- isMaster = deviceInfo.isMaster;
- masterUID = deviceInfo.masterUid;
- batteryCharging = deviceInfo.batteryCharging;
- batteryLevel = deviceInfo.batteryLevel;
- topologyIndex = deviceInfo.index;
- }
-
- void setToMaster (bool shouldBeMaster)
- {
- isMaster = shouldBeMaster;
- }
-
- void updateMidiConnectionListener()
- {
- if (detector == nullptr)
- return;
-
- listenerToMidiConnection = dynamic_cast<MIDIDeviceConnection*> (detector->getDeviceConnectionFor (*this));
-
- if (listenerToMidiConnection != nullptr)
- listenerToMidiConnection->addListener (this);
-
- config.setDeviceComms (listenerToMidiConnection);
- }
-
- void disconnectMidiConnectionListener()
- {
- if (listenerToMidiConnection != nullptr)
- {
- config.setDeviceComms (nullptr);
- listenerToMidiConnection->removeListener (this);
- listenerToMidiConnection = nullptr;
- }
- }
-
- bool isConnected() const override
- {
- if (detector != nullptr)
- return detector->isConnected (uid);
-
- return false;
- }
-
- bool isConnectedViaBluetooth() const override
- {
- if (detector != nullptr)
- return detector->isConnectedViaBluetooth (*this);
-
- return false;
- }
-
- Type getType() const override { return modelData.apiType; }
- String getDeviceDescription() const override { return modelData.description; }
- int getWidth() const override { return modelData.widthUnits; }
- int getHeight() const override { return modelData.heightUnits; }
- float getMillimetersPerUnit() const override { return 47.0f; }
- bool isHardwareBlock() const override { return true; }
- juce::Array<Block::ConnectionPort> getPorts() const override { return modelData.ports; }
- Time getConnectionTime() const override { return connectionTime; }
- bool isMasterBlock() const override { return isMaster; }
- Block::UID getConnectedMasterUID() const override { return masterUID; }
- int getRotation() const override { return rotation; }
-
- BlockArea getBlockAreaWithinLayout() const override
- {
- if (rotation % 2 == 0)
- return { position.first, position.second, modelData.widthUnits, modelData.heightUnits };
-
- return { position.first, position.second, modelData.heightUnits, modelData.widthUnits };
- }
-
- TouchSurface* getTouchSurface() const override { return touchSurface.get(); }
- LEDGrid* getLEDGrid() const override { return ledGrid.get(); }
-
- LEDRow* getLEDRow() override
- {
- if (ledRow == nullptr && modelData.numLEDRowLEDs > 0)
- ledRow.reset (new LEDRowImplementation (*this));
-
- return ledRow.get();
- }
-
- juce::Array<ControlButton*> getButtons() const override
- {
- juce::Array<ControlButton*> result;
- result.addArray (controlButtons);
- return result;
- }
-
- juce::Array<StatusLight*> getStatusLights() const override
- {
- juce::Array<StatusLight*> result;
- result.addArray (statusLights);
- return result;
- }
-
- float getBatteryLevel() const override
- {
- return batteryLevel.toUnipolarFloat();
- }
-
- bool isBatteryCharging() const override
- {
- return batteryCharging.get() > 0;
- }
-
- bool supportsGraphics() const override
- {
- return false;
- }
-
- int getDeviceIndex() const noexcept
- {
- return isConnected() ? topologyIndex : -1;
- }
-
- template <typename PacketBuilder>
- bool sendMessageToDevice (const PacketBuilder& builder)
- {
- if (detector != nullptr)
- {
- lastMessageSendTime = Time::getCurrentTime();
- return detector->sendMessageToDevice (uid, builder);
- }
-
- return false;
- }
-
- bool sendCommandMessage (uint32 commandID)
- {
- return buildAndSendPacket<64> ([commandID] (BlocksProtocol::HostPacketBuilder<64>& p)
- { return p.deviceControlMessage (commandID); });
- }
-
- void handleProgramEvent (const ProgramEventMessage& message)
- {
- programEventListeners.call ([&] (ProgramEventListener& l) { l.handleProgramEvent(*this, message); });
- }
-
- void handleCustomMessage (Block::Timestamp, const int32* data)
- {
- ProgramEventMessage m;
-
- for (uint32 i = 0; i < BlocksProtocol::numProgramMessageInts; ++i)
- m.values[i] = data[i];
-
- handleProgramEvent (m);
- }
-
- static BlockImplementation* getFrom (Block* b) noexcept
- {
- jassert (dynamic_cast<BlockImplementation*> (b) != nullptr);
- return dynamic_cast<BlockImplementation*> (b);
- }
-
- static BlockImplementation* getFrom (Block& b) noexcept
- {
- return getFrom (&b);
- }
-
- //==============================================================================
- std::function<void (const Block& block, const String&)> logger;
-
- void setLogger (std::function<void (const Block& block, const String&)> newLogger) override
- {
- logger = std::move (newLogger);
- }
-
- void handleLogMessage (const String& message) const
- {
- if (logger != nullptr)
- logger (*this, message);
- }
-
- //==============================================================================
- Result setProgram (std::unique_ptr<Program> newProgram,
- ProgramPersistency persistency = ProgramPersistency::setAsTemp) override
- {
- auto doProgramsMatch = [&]
- {
- if (program == nullptr || newProgram == nullptr)
- return false;
-
- return program->getLittleFootProgram() == newProgram->getLittleFootProgram()
- && program->getSearchPaths() == newProgram->getSearchPaths();
- }();
-
- if (doProgramsMatch)
- {
- if (isProgramLoaded)
- {
- MessageManager::callAsync ([blockRef = Block::Ptr (this), this]
- {
- programLoadedListeners.call ([&] (ProgramLoadedListener& l) { l.handleProgramLoaded (*this); });
- });
- }
-
- return Result::ok();
- }
-
- program = std::move (newProgram);
- return loadProgram (persistency);
- }
-
- Result loadProgram (ProgramPersistency persistency)
- {
- stopTimer();
-
- programSize = 0;
- isProgramLoaded = shouldSaveProgramAsDefault = false;
-
- if (program == nullptr)
- {
- remoteHeap.clearTargetData();
- return Result::ok();
- }
-
- auto res = compileProgram();
-
- if (res.failed())
- return res;
-
- programSize = (uint32) compiler.compiledObjectCode.size();
-
- remoteHeap.resetDataRangeToUnknown (0, remoteHeap.blockSize);
- remoteHeap.clearTargetData();
- remoteHeap.sendChanges (*this, true);
-
- remoteHeap.resetDataRangeToUnknown (0, programSize);
- remoteHeap.setBytes (0, compiler.compiledObjectCode.begin(), programSize);
- remoteHeap.sendChanges (*this, true);
-
- this->resetConfigListActiveStatus();
-
- const auto legacyProgramChangeConfigIndex = getMaxConfigIndex();
- handleConfigItemChanged ({ legacyProgramChangeConfigIndex }, legacyProgramChangeConfigIndex);
-
- shouldSaveProgramAsDefault = persistency == ProgramPersistency::setAsDefault;
- startTimer (20);
-
- return Result::ok();
- }
-
- Result compileProgram()
- {
- compiler.addNativeFunctions (PhysicalTopologySource::getStandardLittleFootFunctions());
-
- const auto err = compiler.compile (program->getLittleFootProgram(), 512, program->getSearchPaths());
-
- if (err.failed())
- return err;
-
- DBG ("Compiled littlefoot program, space needed: "
- << (int) compiler.getCompiledProgram().getTotalSpaceNeeded() << " bytes");
-
- if (compiler.getCompiledProgram().getTotalSpaceNeeded() > getMemorySize())
- return Result::fail ("Program too large!");
-
- return Result::ok();
- }
-
- Program* getProgram() const override { return program.get(); }
-
- void sendProgramEvent (const ProgramEventMessage& message) override
- {
- static_assert (sizeof (ProgramEventMessage::values) == 4 * BlocksProtocol::numProgramMessageInts,
- "Need to keep the internal and external messages structures the same");
-
- if (remoteHeap.isProgramLoaded())
- {
- buildAndSendPacket<128> ([&message] (BlocksProtocol::HostPacketBuilder<128>& p)
- { return p.addProgramEventMessage (message.values); });
- }
- }
-
- void timerCallback() override
- {
- if (remoteHeap.isFullySynced() && remoteHeap.isProgramLoaded())
- {
- isProgramLoaded = true;
- stopTimer();
-
- if (shouldSaveProgramAsDefault)
- doSaveProgramAsDefault();
-
- programLoadedListeners.call([&] (ProgramLoadedListener& l) { l.handleProgramLoaded (*this); });
- }
- else
- {
- startTimer (100);
- }
- }
-
- void saveProgramAsDefault() override
- {
- shouldSaveProgramAsDefault = true;
-
- if (! isTimerRunning() && isProgramLoaded)
- doSaveProgramAsDefault();
- }
-
- void resetProgramToDefault() override
- {
- if (! shouldSaveProgramAsDefault)
- setProgram (nullptr);
-
- sendCommandMessage (BlocksProtocol::endAPIMode);
- sendCommandMessage (BlocksProtocol::beginAPIMode);
- }
-
- uint32 getMemorySize() override
- {
- return modelData.programAndHeapSize;
- }
-
- uint32 getHeapMemorySize() override
- {
- jassert (isPositiveAndNotGreaterThan (programSize, modelData.programAndHeapSize));
- return modelData.programAndHeapSize - programSize;
- }
-
- void setDataByte (size_t offset, uint8 value) override
- {
- remoteHeap.setByte (programSize + offset, value);
- }
-
- void setDataBytes (size_t offset, const void* newData, size_t num) override
- {
- remoteHeap.setBytes (programSize + offset, static_cast<const uint8*> (newData), num);
- }
-
- void setDataBits (uint32 startBit, uint32 numBits, uint32 value) override
- {
- remoteHeap.setBits (programSize * 8 + startBit, numBits, value);
- }
-
- uint8 getDataByte (size_t offset) override
- {
- return remoteHeap.getByte (programSize + offset);
- }
-
- void handleSharedDataACK (uint32 packetCounter) noexcept
- {
- pingFromDevice();
- remoteHeap.handleACKFromDevice (*this, packetCounter);
- }
-
- bool sendFirmwareUpdatePacket (const uint8* data, uint8 size, std::function<void (uint8, uint32)> callback) override
- {
- firmwarePacketAckCallback = nullptr;
-
- if (buildAndSendPacket<256> ([data, size] (BlocksProtocol::HostPacketBuilder<256>& p)
- { return p.addFirmwareUpdatePacket (data, size); }))
- {
- firmwarePacketAckCallback = callback;
- return true;
- }
-
- return false;
- }
-
- void handleFirmwareUpdateACK (uint8 resultCode, uint32 resultDetail)
- {
- if (firmwarePacketAckCallback != nullptr)
- {
- firmwarePacketAckCallback (resultCode, resultDetail);
- firmwarePacketAckCallback = nullptr;
- }
- }
-
- void handleConfigUpdateMessage (int32 item, int32 value, int32 min, int32 max)
- {
- config.handleConfigUpdateMessage (item, value, min, max);
- }
-
- void handleConfigSetMessage (int32 item, int32 value)
- {
- config.handleConfigSetMessage (item, value);
- }
-
- void pingFromDevice()
- {
- lastMessageReceiveTime = Time::getCurrentTime();
- }
-
- MIDIDeviceConnection* getDeviceConnection()
- {
- return dynamic_cast<MIDIDeviceConnection*> (detector->getDeviceConnectionFor (*this));
- }
-
- void addDataInputPortListener (DataInputPortListener* listener) override
- {
- if (auto deviceConnection = getDeviceConnection())
- {
- {
- ScopedLock scopedLock (deviceConnection->criticalSecton);
- Block::addDataInputPortListener (listener);
- }
-
- deviceConnection->midiInput->start();
- }
- else
- {
- Block::addDataInputPortListener (listener);
- }
- }
-
- void removeDataInputPortListener (DataInputPortListener* listener) override
- {
- if (auto deviceConnection = getDeviceConnection())
- {
- {
- ScopedLock scopedLock (deviceConnection->criticalSecton);
- Block::removeDataInputPortListener (listener);
- }
- }
- else
- {
- Block::removeDataInputPortListener (listener);
- }
- }
-
- void sendMessage (const void* message, size_t messageSize) override
- {
- if (auto midiOutput = getMidiOutput())
- midiOutput->sendMessageNow ({ message, (int) messageSize });
- }
-
- void handleTimerTick()
- {
- if (ledGrid != nullptr)
- if (auto renderer = ledGrid->getRenderer())
- renderer->renderLEDGrid (*ledGrid);
-
- remoteHeap.sendChanges (*this, false);
-
- if (lastMessageSendTime < Time::getCurrentTime() - getPingInterval())
- sendCommandMessage (BlocksProtocol::ping);
- }
-
- RelativeTime getPingInterval()
- {
- return RelativeTime::milliseconds (isMaster ? masterPingIntervalMs : dnaPingIntervalMs);
- }
-
- //==============================================================================
- void handleConfigItemChanged (const ConfigMetaData& data, uint32 index)
- {
- configItemListeners.call([&] (ConfigItemListener& l) { l.handleConfigItemChanged (*this, data, index); });
- }
-
- void handleConfigSyncEnded()
- {
- configItemListeners.call([&] (ConfigItemListener& l) { l.handleConfigSyncEnded (*this); });
- }
-
- int32 getLocalConfigValue (uint32 item) override
- {
- initialiseDeviceIndexAndConnection();
- return config.getItemValue ((BlocksProtocol::ConfigItemId) item);
- }
-
- void setLocalConfigValue (uint32 item, int32 value) override
- {
- initialiseDeviceIndexAndConnection();
- config.setItemValue ((BlocksProtocol::ConfigItemId) item, value);
- }
-
- void setLocalConfigRange (uint32 item, int32 min, int32 max) override
- {
- initialiseDeviceIndexAndConnection();
- config.setItemMin ((BlocksProtocol::ConfigItemId) item, min);
- config.setItemMax ((BlocksProtocol::ConfigItemId) item, max);
- }
-
- void setLocalConfigItemActive (uint32 item, bool isActive) override
- {
- initialiseDeviceIndexAndConnection();
- config.setItemActive ((BlocksProtocol::ConfigItemId) item, isActive);
- }
-
- bool isLocalConfigItemActive (uint32 item) override
- {
- initialiseDeviceIndexAndConnection();
- return config.getItemActive ((BlocksProtocol::ConfigItemId) item);
- }
-
- uint32 getMaxConfigIndex() override
- {
- return uint32 (BlocksProtocol::maxConfigIndex);
- }
-
- bool isValidUserConfigIndex (uint32 item) override
- {
- return item >= (uint32) BlocksProtocol::ConfigItemId::user0
- && item < (uint32) (BlocksProtocol::ConfigItemId::user0 + numberOfUserConfigs);
- }
-
- ConfigMetaData getLocalConfigMetaData (uint32 item) override
- {
- initialiseDeviceIndexAndConnection();
- return config.getMetaData ((BlocksProtocol::ConfigItemId) item);
- }
-
- void requestFactoryConfigSync() override
- {
- initialiseDeviceIndexAndConnection();
- config.requestFactoryConfigSync();
- }
-
- void resetConfigListActiveStatus() override
- {
- config.resetConfigListActiveStatus();
- }
-
- bool setName (const String& newName) override
- {
- return buildAndSendPacket<128> ([&newName] (BlocksProtocol::HostPacketBuilder<128>& p)
- { return p.addSetBlockName (newName); });
- }
-
- void factoryReset() override
- {
- buildAndSendPacket<32> ([] (BlocksProtocol::HostPacketBuilder<32>& p)
- { return p.addFactoryReset(); });
-
- juce::Timer::callAfterDelay (5, [ref = WeakReference<BlockImplementation>(this)]
- {
- if (ref != nullptr)
- ref->blockReset();
- });
- }
-
- void blockReset() override
- {
- bool messageSent = false;
-
- if (isMasterBlock())
- {
- sendMessage (BlocksProtocol::SpecialMessageFromHost::resetMaster,
- sizeof (BlocksProtocol::SpecialMessageFromHost::resetMaster));
- messageSent = true;
- }
- else
- {
- messageSent = buildAndSendPacket<32> ([] (BlocksProtocol::HostPacketBuilder<32>& p)
- { return p.addBlockReset(); });
- }
-
- if (messageSent)
- {
- hasBeenPowerCycled = true;
-
- if (detector != nullptr)
- detector->notifyBlockIsRestarting (uid);
- }
- }
-
- bool wasPowerCycled() const { return hasBeenPowerCycled; }
- void resetPowerCycleFlag() { hasBeenPowerCycled = false; }
-
- //==============================================================================
- std::unique_ptr<TouchSurface> touchSurface;
- OwnedArray<ControlButton> controlButtons;
- std::unique_ptr<LEDGridImplementation> ledGrid;
- std::unique_ptr<LEDRowImplementation> ledRow;
- OwnedArray<StatusLight> statusLights;
-
- BlocksProtocol::BlockDataSheet modelData;
-
- MIDIDeviceConnection* listenerToMidiConnection = nullptr;
-
- static constexpr int masterPingIntervalMs = 400;
- static constexpr int dnaPingIntervalMs = 1666;
-
- static constexpr uint32 maxBlockSize = BlocksProtocol::padBlockProgramAndHeapSize;
- static constexpr uint32 maxPacketCounter = BlocksProtocol::PacketCounter::maxValue;
- static constexpr uint32 maxPacketSize = 200;
-
- using PacketBuilder = BlocksProtocol::HostPacketBuilder<maxPacketSize>;
-
- using RemoteHeapType = littlefoot::LittleFootRemoteHeap<BlockImplementation>;
- RemoteHeapType remoteHeap;
-
- WeakReference<Detector> detector;
- Time lastMessageSendTime, lastMessageReceiveTime;
-
- BlockConfigManager config;
-
- private:
- littlefoot::Compiler compiler;
- std::unique_ptr<Program> program;
- uint32 programSize = 0;
-
- std::function<void (uint8, uint32)> firmwarePacketAckCallback;
-
- bool isMaster = false;
- Block::UID masterUID = {};
-
- BlocksProtocol::BatteryLevel batteryLevel {};
- BlocksProtocol::BatteryCharging batteryCharging {};
-
- BlocksProtocol::TopologyIndex topologyIndex {};
-
- Time connectionTime {};
-
- std::pair<int, int> position;
- int rotation = 0;
- friend Detector;
-
- bool isProgramLoaded = false;
- bool shouldSaveProgramAsDefault = false;
- bool hasBeenPowerCycled = false;
-
- void initialiseDeviceIndexAndConnection()
- {
- config.setDeviceIndex ((TopologyIndex) getDeviceIndex());
- config.setDeviceComms (listenerToMidiConnection);
- }
-
- const MidiInput* getMidiInput() const
- {
- if (detector != nullptr)
- if (auto c = dynamic_cast<const MIDIDeviceConnection*> (detector->getDeviceConnectionFor (*this)))
- return c->midiInput.get();
-
- jassertfalse;
- return nullptr;
- }
-
- MidiInput* getMidiInput()
- {
- return const_cast<MidiInput*> (static_cast<const BlockImplementation&>(*this).getMidiInput());
- }
-
- const MidiOutput* getMidiOutput() const
- {
- if (detector != nullptr)
- if (auto c = dynamic_cast<const MIDIDeviceConnection*> (detector->getDeviceConnectionFor (*this)))
- return c->midiOutput.get();
-
- jassertfalse;
- return nullptr;
- }
-
- MidiOutput* getMidiOutput()
- {
- return const_cast<MidiOutput*> (static_cast<const BlockImplementation&>(*this).getMidiOutput());
- }
-
- void handleIncomingMidiMessage (const MidiMessage& message) override
- {
- dataInputPortListeners.call ([&] (DataInputPortListener& l) { l.handleIncomingDataPortMessage (*this, message.getRawData(),
- (size_t) message.getRawDataSize()); });
- }
-
- void connectionBeingDeleted (const MIDIDeviceConnection& c) override
- {
- jassert (listenerToMidiConnection == &c);
- ignoreUnused (c);
- disconnectMidiConnectionListener();
- }
-
- void doSaveProgramAsDefault()
- {
- sendCommandMessage (BlocksProtocol::saveProgramAsDefault);
- }
-
- template <int packetBytes, typename PacketBuilderFn>
- bool buildAndSendPacket (PacketBuilderFn buildFn)
- {
- auto index = getDeviceIndex();
-
- if (index < 0)
- {
- jassertfalse;
- return false;
- }
-
- BlocksProtocol::HostPacketBuilder<packetBytes> p;
- p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index);
-
- if (! buildFn (p))
- return false;
-
- p.writePacketSysexFooter();
- return sendMessageToDevice (p);
- }
-
- public:
- //==============================================================================
- struct TouchSurfaceImplementation : public TouchSurface,
- private Timer
- {
- TouchSurfaceImplementation (BlockImplementation& b) : TouchSurface (b), blockImpl (b)
- {
- activateTouchSurface();
- }
-
- ~TouchSurfaceImplementation() override
- {
- disableTouchSurface();
- }
-
- void activateTouchSurface()
- {
- startTimer (500);
- }
-
- void disableTouchSurface()
- {
- stopTimer();
- }
-
- int getNumberOfKeywaves() const noexcept override
- {
- return blockImpl.modelData.numKeywaves;
- }
-
- void broadcastTouchChange (const TouchSurface::Touch& touchEvent)
- {
- auto& status = touches.getValue (touchEvent);
-
- // Fake a touch end if we receive a duplicate touch-start with no preceding touch-end (ie: comms error)
- if (touchEvent.isTouchStart && status.isActive)
- killTouch (touchEvent, status, Time::getMillisecondCounter());
-
- // Fake a touch start if we receive an unexpected event with no matching start event. (ie: comms error)
- if (! touchEvent.isTouchStart && ! status.isActive)
- {
- TouchSurface::Touch t (touchEvent);
- t.isTouchStart = true;
- t.isTouchEnd = false;
-
- if (t.zVelocity <= 0) t.zVelocity = status.lastStrikePressure;
- if (t.zVelocity <= 0) t.zVelocity = t.z;
- if (t.zVelocity <= 0) t.zVelocity = 0.9f;
-
- listeners.call ([&] (TouchSurface::Listener& l) { l.touchChanged (*this, t); });
- }
-
- // Normal handling:
- status.lastEventTime = Time::getMillisecondCounter();
- status.isActive = ! touchEvent.isTouchEnd;
-
- if (touchEvent.isTouchStart)
- status.lastStrikePressure = touchEvent.zVelocity;
-
- listeners.call ([&] (TouchSurface::Listener& l) { l.touchChanged (*this, touchEvent); });
- }
-
- void timerCallback() override
- {
- // Find touches that seem to have become stuck, and fake a touch-end for them..
- static const uint32 touchTimeOutMs = 500;
-
- for (auto& t : touches)
- {
- auto& status = t.value;
- auto now = Time::getMillisecondCounter();
-
- if (status.isActive && now > status.lastEventTime + touchTimeOutMs)
- killTouch (t.touch, status, now);
- }
- }
-
- struct TouchStatus
- {
- uint32 lastEventTime = 0;
- float lastStrikePressure = 0;
- bool isActive = false;
- };
-
- void killTouch (const TouchSurface::Touch& touch, TouchStatus& status, uint32 timeStamp) noexcept
- {
- jassert (status.isActive);
-
- TouchSurface::Touch killTouch (touch);
-
- killTouch.z = 0;
- killTouch.xVelocity = 0;
- killTouch.yVelocity = 0;
- killTouch.zVelocity = -1.0f;
- killTouch.eventTimestamp = timeStamp;
- killTouch.isTouchStart = false;
- killTouch.isTouchEnd = true;
-
- listeners.call ([&] (TouchSurface::Listener& l) { l.touchChanged (*this, killTouch); });
-
- status.isActive = false;
- }
-
- void cancelAllActiveTouches() noexcept override
- {
- const auto now = Time::getMillisecondCounter();
-
- for (auto& t : touches)
- if (t.value.isActive)
- killTouch (t.touch, t.value, now);
-
- touches.clear();
- }
-
- BlockImplementation& blockImpl;
- TouchList<TouchStatus> touches;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TouchSurfaceImplementation)
- };
-
- //==============================================================================
- struct ControlButtonImplementation : public ControlButton
- {
- ControlButtonImplementation (BlockImplementation& b, int index, BlocksProtocol::BlockDataSheet::ButtonInfo info)
- : ControlButton (b), blockImpl (b), buttonInfo (info), buttonIndex (index)
- {
- }
-
- ~ControlButtonImplementation() override
- {
- }
-
- ButtonFunction getType() const override { return buttonInfo.type; }
- String getName() const override { return BlocksProtocol::getButtonNameForFunction (buttonInfo.type); }
- float getPositionX() const override { return buttonInfo.x; }
- float getPositionY() const override { return buttonInfo.y; }
-
- bool hasLight() const override { return blockImpl.isControlBlock(); }
-
- bool setLightColour (LEDColour colour) override
- {
- if (hasLight())
- {
- if (auto row = blockImpl.ledRow.get())
- {
- row->setButtonColour ((uint32) buttonIndex, colour);
- return true;
- }
- }
-
- return false;
- }
-
- void broadcastButtonChange (Block::Timestamp timestamp, ControlButton::ButtonFunction button, bool isDown)
- {
- if (button == buttonInfo.type)
- {
- if (wasDown == isDown)
- sendButtonChangeToListeners (timestamp, ! isDown);
-
- sendButtonChangeToListeners (timestamp, isDown);
- wasDown = isDown;
- }
- }
-
- void sendButtonChangeToListeners (Block::Timestamp timestamp, bool isDown)
- {
- if (isDown)
- listeners.call ([&] (ControlButton::Listener& l) { l.buttonPressed (*this, timestamp); });
- else
- listeners.call ([&] (ControlButton::Listener& l) { l.buttonReleased (*this, timestamp); });
- }
-
- BlockImplementation& blockImpl;
- BlocksProtocol::BlockDataSheet::ButtonInfo buttonInfo;
- int buttonIndex;
- bool wasDown = false;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlButtonImplementation)
- };
-
-
- //==============================================================================
- struct StatusLightImplementation : public StatusLight
- {
- StatusLightImplementation (Block& b, BlocksProtocol::BlockDataSheet::StatusLEDInfo i) : StatusLight (b), info (i)
- {
- }
-
- String getName() const override { return info.name; }
-
- bool setColour (LEDColour newColour) override
- {
- // XXX TODO!
- ignoreUnused (newColour);
- return false;
- }
-
- BlocksProtocol::BlockDataSheet::StatusLEDInfo info;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StatusLightImplementation)
- };
-
- //==============================================================================
- struct LEDGridImplementation : public LEDGrid
- {
- LEDGridImplementation (BlockImplementation& b) : LEDGrid (b), blockImpl (b)
- {
- }
-
- int getNumColumns() const override { return blockImpl.modelData.lightGridWidth; }
- int getNumRows() const override { return blockImpl.modelData.lightGridHeight; }
-
- BlockImplementation& blockImpl;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGridImplementation)
- };
-
- //==============================================================================
- struct LEDRowImplementation : public LEDRow,
- private Timer
- {
- LEDRowImplementation (BlockImplementation& b) : LEDRow (b)
- {
- startTimer (300);
- }
-
- void setButtonColour (uint32 index, LEDColour colour)
- {
- if (index < 10)
- {
- colours[index] = colour;
- flush();
- }
- }
-
- int getNumLEDs() const override
- {
- return static_cast<const BlockImplementation&> (block).modelData.numLEDRowLEDs;
- }
-
- void setLEDColour (int index, LEDColour colour) override
- {
- if ((uint32) index < 15u)
- {
- colours[10 + index] = colour;
- flush();
- }
- }
-
- void setOverlayColour (LEDColour colour) override
- {
- colours[25] = colour;
- flush();
- }
-
- void resetOverlayColour() override
- {
- setOverlayColour ({});
- }
-
- private:
- LEDColour colours[26];
-
- void timerCallback() override
- {
- stopTimer();
- loadProgramOntoBlock();
- flush();
- }
-
- void loadProgramOntoBlock()
- {
- if (block.getProgram() == nullptr)
- {
- auto err = block.setProgram (std::make_unique <DefaultLEDGridProgram> (block));
-
- if (err.failed())
- {
- DBG (err.getErrorMessage());
- jassertfalse;
- }
- }
- }
-
- void flush()
- {
- if (block.getProgram() != nullptr)
- for (uint32 i = 0; i < (uint32) numElementsInArray (colours); ++i)
- write565Colour (16 * i, colours[i]);
- }
-
- void write565Colour (uint32 bitIndex, LEDColour colour)
- {
- block.setDataBits (bitIndex, 5, (uint32) (colour.getRed() >> 3));
- block.setDataBits (bitIndex + 5, 6, (uint32) (colour.getGreen() >> 2));
- block.setDataBits (bitIndex + 11, 5, (uint32) (colour.getBlue() >> 3));
- }
-
- struct DefaultLEDGridProgram : public Block::Program
- {
- DefaultLEDGridProgram (Block& b) : Block::Program (b) {}
-
- String getLittleFootProgram() override
- {
- /* Data format:
-
- 0: 10 x 5-6-5 bits for button LED RGBs
- 20: 15 x 5-6-5 bits for LED row colours
- 50: 1 x 5-6-5 bits for LED row overlay colour
- */
- return R"littlefoot(
-
- #heapsize: 128
-
- int getColour (int bitIndex)
- {
- return makeARGB (255,
- getHeapBits (bitIndex, 5) << 3,
- getHeapBits (bitIndex + 5, 6) << 2,
- getHeapBits (bitIndex + 11, 5) << 3);
- }
-
- int getButtonColour (int index)
- {
- return getColour (16 * index);
- }
-
- int getLEDColour (int index)
- {
- if (getHeapInt (50))
- return getColour (50 * 8);
-
- return getColour (20 * 8 + 16 * index);
- }
-
- void repaint()
- {
- for (int x = 0; x < 15; ++x)
- fillPixel (getLEDColour (x), x, 0);
-
- for (int i = 0; i < 10; ++i)
- fillPixel (getButtonColour (i), i, 1);
- }
-
- void handleMessage (int p1, int p2) {}
-
- )littlefoot";
- }
- };
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDRowImplementation)
- };
-
- private:
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlockImplementation)
- JUCE_DECLARE_WEAK_REFERENCEABLE (BlockImplementation)
- };
-
- } // namespace juce
|