| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE library.
 -    Copyright (c) 2017 - ROLI Ltd.
 - 
 -    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
 - {
 - 
 - #define JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED \
 -     jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
 - 
 - #if DUMP_BANDWIDTH_STATS
 - namespace
 - {
 -     struct PortIOStats
 -     {
 -         PortIOStats (const char* nm) : name (nm) {}
 - 
 -         const char* const name;
 -         int byteCount           = 0;
 -         int messageCount        = 0;
 -         int bytesPerSec         = 0;
 -         int largestMessageBytes = 0;
 -         int lastMessageBytes    = 0;
 - 
 -         void update (double elapsedSec)
 -         {
 -             if (byteCount > 0)
 -             {
 -                 bytesPerSec = (int) (byteCount / elapsedSec);
 -                 byteCount = 0;
 -                 juce::Logger::writeToLog (getString());
 -             }
 -         }
 - 
 -         juce::String getString() const
 -         {
 -             return juce::String (name) + ": "
 -                     + "count="    + juce::String (messageCount).paddedRight (' ', 7)
 -                     + "rate="     + (juce::String (bytesPerSec / 1024.0f, 1) + " Kb/sec").paddedRight (' ', 11)
 -                     + "largest="  + (juce::String (largestMessageBytes) + " bytes").paddedRight (' ', 11)
 -                     + "last="     + (juce::String (lastMessageBytes) + " bytes").paddedRight (' ', 11);
 -         }
 - 
 -         void registerMessage (int numBytes) noexcept
 -         {
 -             byteCount += numBytes;
 -             ++messageCount;
 -             lastMessageBytes = numBytes;
 -             largestMessageBytes = juce::jmax (largestMessageBytes, numBytes);
 -         }
 -     };
 - 
 -     static PortIOStats inputStats { "Input" }, outputStats { "Output" };
 -     static uint32 startTime = 0;
 - 
 -     static inline void resetOnSecondBoundary()
 -     {
 -         auto now = juce::Time::getMillisecondCounter();
 -         double elapsedSec = (now - startTime) / 1000.0;
 - 
 -         if (elapsedSec >= 1.0)
 -         {
 -             inputStats.update (elapsedSec);
 -             outputStats.update (elapsedSec);
 -             startTime = now;
 -         }
 -     }
 - 
 -     static inline void registerBytesOut (int numBytes)
 -     {
 -         outputStats.registerMessage (numBytes);
 -         resetOnSecondBoundary();
 -     }
 - 
 -     static inline void registerBytesIn (int numBytes)
 -     {
 -         inputStats.registerMessage (numBytes);
 -         resetOnSecondBoundary();
 -     }
 - }
 - 
 - juce::String getMidiIOStats()
 - {
 -     return inputStats.getString() + "   " + outputStats.getString();
 - }
 - #endif
 - 
 - 
 - //==============================================================================
 - struct PhysicalTopologySource::Internal
 - {
 -     struct Detector;
 -     struct BlockImplementation;
 -     struct ControlButtonImplementation;
 -     struct RotaryDialImplementation;
 -     struct TouchSurfaceImplementation;
 -     struct LEDGridImplementation;
 -     struct LEDRowImplementation;
 - 
 -     //==============================================================================
 -     struct MIDIDeviceConnection  : public DeviceConnection,
 -                                    public juce::MidiInputCallback
 -     {
 -         MIDIDeviceConnection() {}
 - 
 -         ~MIDIDeviceConnection()
 -         {
 -             JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
 - 
 -             listeners.call ([this] (Listener& l) { l.connectionBeingDeleted (*this); });
 - 
 -             if (midiInput != nullptr)
 -                 midiInput->stop();
 - 
 -             if (interprocessLock != nullptr)
 -                 interprocessLock->exit();
 -         }
 - 
 -         bool lockAgainstOtherProcesses (const String& midiInName, const String& midiOutName)
 -         {
 -             interprocessLock.reset (new juce::InterProcessLock ("blocks_sdk_"
 -                                                                   + File::createLegalFileName (midiInName)
 -                                                                   + "_" + File::createLegalFileName (midiOutName)));
 -             if (interprocessLock->enter (500))
 -                 return true;
 - 
 -             interprocessLock = nullptr;
 -             return false;
 -         }
 - 
 -         struct Listener
 -         {
 -             virtual ~Listener() {}
 - 
 -             virtual void handleIncomingMidiMessage (const juce::MidiMessage& message) = 0;
 -             virtual void connectionBeingDeleted (const MIDIDeviceConnection&) = 0;
 -         };
 - 
 -         void addListener (Listener* l)
 -         {
 -             listeners.add (l);
 -         }
 - 
 -         void removeListener (Listener* l)
 -         {
 -             listeners.remove (l);
 -         }
 - 
 -         bool sendMessageToDevice (const void* data, size_t dataSize) override
 -         {
 -             JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED // This method must only be called from the message thread!
 - 
 -             jassert (dataSize > sizeof (BlocksProtocol::roliSysexHeader) + 2);
 -             jassert (memcmp (data, BlocksProtocol::roliSysexHeader, sizeof (BlocksProtocol::roliSysexHeader)) == 0);
 -             jassert (static_cast<const uint8*> (data)[dataSize - 1] == 0xf7);
 - 
 -             if (midiOutput != nullptr)
 -             {
 -                 midiOutput->sendMessageNow (juce::MidiMessage (data, (int) dataSize));
 -                 return true;
 -             }
 - 
 -             return false;
 -         }
 - 
 -         void handleIncomingMidiMessage (juce::MidiInput*, const juce::MidiMessage& message) override
 -         {
 -             const auto data = message.getRawData();
 -             const int dataSize = message.getRawDataSize();
 -             const int bodySize = dataSize - (int) (sizeof (BlocksProtocol::roliSysexHeader) + 1);
 - 
 -             if (bodySize > 0 && memcmp (data, BlocksProtocol::roliSysexHeader, sizeof (BlocksProtocol::roliSysexHeader)) == 0)
 -                 if (handleMessageFromDevice != nullptr)
 -                     handleMessageFromDevice (data + sizeof (BlocksProtocol::roliSysexHeader), (size_t) bodySize);
 - 
 -             listeners.call ([&] (Listener& l) { l.handleIncomingMidiMessage (message); });
 -         }
 - 
 -         std::unique_ptr<juce::MidiInput> midiInput;
 -         std::unique_ptr<juce::MidiOutput> midiOutput;
 - 
 -     private:
 -         juce::ListenerList<Listener> listeners;
 -         std::unique_ptr<juce::InterProcessLock> interprocessLock;
 - 
 -         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceConnection)
 -     };
 - 
 -     struct MIDIDeviceDetector  : public DeviceDetector
 -     {
 -         MIDIDeviceDetector() {}
 - 
 -         juce::StringArray scanForDevices() override
 -         {
 -             juce::StringArray result;
 - 
 -             for (auto& pair : findDevices())
 -                 result.add (pair.inputName + " & " + pair.outputName);
 - 
 -             return result;
 -         }
 - 
 -         DeviceConnection* openDevice (int index) override
 -         {
 -             auto pair = findDevices()[index];
 - 
 -             if (pair.inputIndex >= 0 && pair.outputIndex >= 0)
 -             {
 -                 std::unique_ptr<MIDIDeviceConnection> dev (new MIDIDeviceConnection());
 - 
 -                 if (dev->lockAgainstOtherProcesses (pair.inputName, pair.outputName))
 -                 {
 -                     dev->midiInput.reset (juce::MidiInput::openDevice (pair.inputIndex, dev.get()));
 -                     dev->midiOutput.reset (juce::MidiOutput::openDevice (pair.outputIndex));
 - 
 -                     if (dev->midiInput != nullptr)
 -                     {
 -                         dev->midiInput->start();
 -                         return dev.release();
 -                     }
 -                 }
 -             }
 - 
 -             return nullptr;
 -         }
 - 
 -         static bool isBlocksMidiDeviceName (const juce::String& name)
 -         {
 -             return name.indexOf (" BLOCK") > 0 || name.indexOf (" Block") > 0;
 -         }
 - 
 -         static String cleanBlocksDeviceName (juce::String name)
 -         {
 -             name = name.trim();
 - 
 -             if (name.endsWith (" IN)"))
 -                 return name.dropLastCharacters (4);
 - 
 -             if (name.endsWith (" OUT)"))
 -                 return name.dropLastCharacters (5);
 - 
 -             const int openBracketPosition = name.lastIndexOfChar ('[');
 -             if (openBracketPosition != -1 && name.endsWith ("]"))
 -                 return name.dropLastCharacters (name.length() - openBracketPosition);
 - 
 -             return name;
 -         }
 - 
 -         struct MidiInputOutputPair
 -         {
 -             juce::String outputName, inputName;
 -             int outputIndex = -1, inputIndex = -1;
 -         };
 - 
 -         static juce::Array<MidiInputOutputPair> findDevices()
 -         {
 -             juce::Array<MidiInputOutputPair> result;
 - 
 -             auto midiInputs  = juce::MidiInput::getDevices();
 -             auto midiOutputs = juce::MidiOutput::getDevices();
 - 
 -             for (int j = 0; j < midiInputs.size(); ++j)
 -             {
 -                 if (isBlocksMidiDeviceName (midiInputs[j]))
 -                 {
 -                     MidiInputOutputPair pair;
 -                     pair.inputName = midiInputs[j];
 -                     pair.inputIndex = j;
 - 
 -                     String cleanedInputName = cleanBlocksDeviceName (pair.inputName);
 -                     for (int i = 0; i < midiOutputs.size(); ++i)
 -                     {
 -                         if (cleanBlocksDeviceName (midiOutputs[i]) == cleanedInputName)
 -                         {
 -                             pair.outputName = midiOutputs[i];
 -                             pair.outputIndex = i;
 -                             break;
 -                         }
 -                     }
 - 
 -                     result.add (pair);
 -                 }
 -             }
 - 
 -             return result;
 -         }
 - 
 -         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceDetector)
 -     };
 - 
 -     //==============================================================================
 -     struct DeviceInfo
 -     {
 -         Block::UID uid;
 -         BlocksProtocol::TopologyIndex index;
 -         BlocksProtocol::BlockSerialNumber serial;
 -         BlocksProtocol::VersionNumber version;
 -         BlocksProtocol::BlockName name;
 -         bool isMaster;
 -     };
 - 
 -     static Block::Timestamp deviceTimestampToHost (uint32 timestamp) noexcept
 -     {
 -         return static_cast<Block::Timestamp> (timestamp);
 -     }
 - 
 -     static juce::Array<DeviceInfo> getArrayOfDeviceInfo (const juce::Array<BlocksProtocol::DeviceStatus>& devices)
 -     {
 -         juce::Array<DeviceInfo> result;
 -         bool isFirst = true;
 - 
 -         for (auto& device : devices)
 -         {
 -             BlocksProtocol::VersionNumber version;
 -             BlocksProtocol::BlockName name;
 - 
 -             result.add ({ getBlockUIDFromSerialNumber (device.serialNumber),
 -                           device.index,
 -                           device.serialNumber,
 -                           version,
 -                           name,
 -                           isFirst });
 - 
 -             isFirst = false;
 -         }
 - 
 -         return result;
 -     }
 - 
 -     static bool containsBlockWithUID (const juce::Array<DeviceInfo>& devices, Block::UID uid) noexcept
 -     {
 -         for (auto&& d : devices)
 -             if (d.uid == uid)
 -                 return true;
 - 
 -         return false;
 -     }
 - 
 -     static bool containsBlockWithUID (const Block::Array& blocks, Block::UID uid) noexcept
 -     {
 -         for (auto&& block : blocks)
 -             if (block->uid == uid)
 -                 return true;
 - 
 -         return false;
 -     }
 - 
 -     static bool versionNumberAddedToBlock (const juce::Array<DeviceInfo>& devices, Block::UID uid, juce::String version) noexcept
 -     {
 -         for (auto&& d : devices)
 -         {
 -             String deviceVersion (reinterpret_cast<const char*> (d.version.version),
 -                                   jmin (static_cast<size_t> (d.version.length), sizeof (d.version.version)));
 - 
 -             if (d.uid == uid && deviceVersion != version && deviceVersion.isNotEmpty())
 -                 return true;
 -         }
 - 
 -         return false;
 -     }
 - 
 -     static bool nameAddedToBlock (const juce::Array<DeviceInfo>& devices, Block::UID uid) noexcept
 -     {
 -         for (auto&& d : devices)
 -             if (d.uid == uid && d.name.length)
 -                 return true;
 - 
 -         return false;
 -     }
 - 
 -     static void setVersionNumberForBlock (const juce::Array<DeviceInfo>& devices, Block& block) noexcept
 -     {
 -         for (auto&& d : devices)
 -             if (d.uid == block.uid)
 -                 block.versionNumber = juce::String ((const char*) d.version.version, d.version.length);
 -     }
 - 
 -     static void setNameForBlock (const juce::Array<DeviceInfo>& devices, Block& block) noexcept
 -     {
 -         for (auto&& d : devices)
 -             if (d.uid == block.uid)
 -                 block.name = juce::String ((const char*) d.name.name, d.name.length);
 -     }
 - 
 -     //==============================================================================
 -     struct ConnectedDeviceGroup  : private juce::AsyncUpdater,
 -                                    private juce::Timer
 -     {
 -         ConnectedDeviceGroup (Detector& d, const juce::String& name, DeviceConnection* connection)
 -             : detector (d), deviceName (name), deviceConnection (connection)
 -         {
 -             lastGlobalPingTime = juce::Time::getCurrentTime();
 - 
 -             deviceConnection->handleMessageFromDevice = [this] (const void* data, size_t dataSize)
 -             {
 -                 this->handleIncomingMessage (data, dataSize);
 -             };
 - 
 -             startTimer (200);
 -             sendTopologyRequest();
 -         }
 - 
 -         bool isStillConnected (const juce::StringArray& detectedDevices) const noexcept
 -         {
 -             return detectedDevices.contains (deviceName)
 -                     && ! failedToGetTopology()
 -                     && lastGlobalPingTime > juce::Time::getCurrentTime() - juce::RelativeTime::seconds (pingTimeoutSeconds);
 -         }
 - 
 -         Block::UID getDeviceIDFromIndex (BlocksProtocol::TopologyIndex index) const noexcept
 -         {
 -             for (auto& d : currentDeviceInfo)
 -                 if (d.index == index)
 -                     return d.uid;
 - 
 -             return {};
 -         }
 - 
 -         int getIndexFromDeviceID (Block::UID uid) const noexcept
 -         {
 -             for (auto& d : currentDeviceInfo)
 -                 if (d.uid == uid)
 -                     return d.index;
 - 
 -             return -1;
 -         }
 - 
 -         const DeviceInfo* getDeviceInfoFromUID (Block::UID uid) const noexcept
 -         {
 -             for (auto& d : currentDeviceInfo)
 -                 if (d.uid == uid)
 -                     return &d;
 - 
 -             return nullptr;
 -         }
 - 
 -         const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept
 -         {
 -             for (auto&& status : currentTopologyDevices)
 -                 if (getBlockUIDFromSerialNumber (status.serialNumber) == deviceID)
 -                     return &status;
 - 
 -             return nullptr;
 -         }
 - 
 -         //==============================================================================
 -         juce::Time lastTopologyRequestTime, lastTopologyReceiveTime;
 -         int numTopologyRequestsSent = 0;
 - 
 -         void sendTopologyRequest()
 -         {
 -             ++numTopologyRequestsSent;
 -             lastTopologyRequestTime = juce::Time::getCurrentTime();
 -             sendCommandMessage (0, BlocksProtocol::requestTopologyMessage);
 -         }
 - 
 -         void scheduleNewTopologyRequest()
 -         {
 -             numTopologyRequestsSent = 0;
 -             lastTopologyReceiveTime = juce::Time();
 -         }
 - 
 -         bool failedToGetTopology() const noexcept
 -         {
 -             return numTopologyRequestsSent > 4 && lastTopologyReceiveTime == juce::Time();
 -         }
 - 
 -         bool hasAnyBlockStoppedPinging() const noexcept
 -         {
 -             auto now = juce::Time::getCurrentTime();
 - 
 -             for (auto& ping : blockPings)
 -                 if (ping.lastPing < now - juce::RelativeTime::seconds (pingTimeoutSeconds))
 -                     return true;
 - 
 -             return false;
 -         }
 - 
 -         void timerCallback() override
 -         {
 -             auto now = juce::Time::getCurrentTime();
 - 
 -             if ((now > lastTopologyReceiveTime + juce::RelativeTime::seconds (30.0) || hasAnyBlockStoppedPinging())
 -                   && now > lastTopologyRequestTime + juce::RelativeTime::seconds (1.0)
 -                   && numTopologyRequestsSent < 4)
 -                 sendTopologyRequest();
 -         }
 - 
 -         //==============================================================================
 -         // The following methods will be called by the HostPacketDecoder:
 -         void beginTopology (int numDevices, int numConnections)
 -         {
 -             incomingTopologyDevices.clearQuick();
 -             incomingTopologyDevices.ensureStorageAllocated (numDevices);
 -             incomingTopologyConnections.clearQuick();
 -             incomingTopologyConnections.ensureStorageAllocated (numConnections);
 -         }
 - 
 -         void extendTopology (int numDevices, int numConnections)
 -         {
 -             incomingTopologyDevices.ensureStorageAllocated (incomingTopologyDevices.size() + numDevices);
 -             incomingTopologyConnections.ensureStorageAllocated (incomingTopologyConnections.size() + numConnections);
 -         }
 - 
 -         void handleTopologyDevice (BlocksProtocol::DeviceStatus status)
 -         {
 -             incomingTopologyDevices.add (status);
 -         }
 - 
 -         void handleTopologyConnection (BlocksProtocol::DeviceConnection connection)
 -         {
 -             incomingTopologyConnections.add (connection);
 -         }
 - 
 -         void endTopology()
 -         {
 -             currentDeviceInfo = getArrayOfDeviceInfo (incomingTopologyDevices);
 -             currentDeviceConnections = getArrayOfConnections (incomingTopologyConnections);
 -             currentTopologyDevices = incomingTopologyDevices;
 -             currentTopologyConnections = incomingTopologyConnections;
 -             detector.handleTopologyChange();
 - 
 -             lastTopologyReceiveTime = juce::Time::getCurrentTime();
 -             blockPings.clear();
 -         }
 - 
 -         void handleVersion (BlocksProtocol::DeviceVersion version)
 -         {
 -             for (auto i = 0; i < currentDeviceInfo.size(); ++i)
 -             {
 -                 if (currentDeviceInfo[i].index == version.index && version.version.length > 1)
 -                 {
 -                     if (memcmp (currentDeviceInfo.getReference (i).version.version, version.version.version, sizeof (version.version)))
 -                     {
 -                         currentDeviceInfo.getReference(i).version = version.version;
 -                         detector.handleTopologyChange();
 -                     }
 -                 }
 -             }
 -         }
 - 
 -         void handleName (BlocksProtocol::DeviceName name)
 -         {
 -             for (auto i = 0; i < currentDeviceInfo.size(); ++i)
 -             {
 -                 if (currentDeviceInfo[i].index == name.index && name.name.length > 1)
 -                 {
 -                     if (memcmp (currentDeviceInfo.getReference (i).name.name, name.name.name, sizeof (name.name)))
 -                     {
 -                         currentDeviceInfo.getReference (i).name = name.name;
 -                         detector.handleTopologyChange();
 -                     }
 -                 }
 -             }
 -         }
 - 
 -         void handleControlButtonUpDown (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp,
 -                                         BlocksProtocol::ControlButtonID buttonID, bool isDown)
 -         {
 -             if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
 -                 detector.handleButtonChange (deviceID, deviceTimestampToHost (timestamp), buttonID.get(), isDown);
 -         }
 - 
 -         void handleCustomMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, const int32* data)
 -         {
 -             if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
 -                 detector.handleCustomMessage (deviceID, deviceTimestampToHost (timestamp), data);
 -         }
 - 
 -         void handleTouchChange (BlocksProtocol::TopologyIndex deviceIndex,
 -                                 uint32 timestamp,
 -                                 BlocksProtocol::TouchIndex touchIndex,
 -                                 BlocksProtocol::TouchPosition position,
 -                                 BlocksProtocol::TouchVelocity velocity,
 -                                 bool isStart, bool isEnd)
 -         {
 -             if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
 -             {
 -                 TouchSurface::Touch touch;
 - 
 -                 touch.index             = (int) touchIndex.get();
 -                 touch.x                 = position.x.toUnipolarFloat();
 -                 touch.y                 = position.y.toUnipolarFloat();
 -                 touch.z                 = position.z.toUnipolarFloat();
 -                 touch.xVelocity         = velocity.vx.toBipolarFloat();
 -                 touch.yVelocity         = velocity.vy.toBipolarFloat();
 -                 touch.zVelocity         = velocity.vz.toBipolarFloat();
 -                 touch.eventTimestamp    = deviceTimestampToHost (timestamp);
 -                 touch.isTouchStart      = isStart;
 -                 touch.isTouchEnd        = isEnd;
 -                 touch.blockUID          = deviceID;
 - 
 -                 setTouchStartPosition (touch);
 - 
 -                 detector.handleTouchChange (deviceID, touch);
 -             }
 -         }
 - 
 -         void setTouchStartPosition (TouchSurface::Touch& touch)
 -         {
 -             auto& startPos = touchStartPositions.getValue (touch);
 - 
 -             if (touch.isTouchStart)
 -                 startPos = { touch.x, touch.y };
 - 
 -             touch.startX = startPos.x;
 -             touch.startY = startPos.y;
 -         }
 - 
 -         void handlePacketACK (BlocksProtocol::TopologyIndex deviceIndex, BlocksProtocol::PacketCounter counter)
 -         {
 -             if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
 -                 detector.handleSharedDataACK (deviceID, counter);
 -         }
 - 
 -         void handleFirmwareUpdateACK (BlocksProtocol::TopologyIndex deviceIndex, BlocksProtocol::FirmwareUpdateACKCode resultCode, BlocksProtocol::FirmwareUpdateACKDetail resultDetail)
 -         {
 -             if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
 -                 detector.handleFirmwareUpdateACK (deviceID, (uint8) resultCode.get(), (uint32) resultDetail.get());
 -         }
 - 
 -         void handleConfigUpdateMessage (BlocksProtocol::TopologyIndex deviceIndex, int32 item, int32 value, int32 min, int32 max)
 -         {
 -             if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
 -                 detector.handleConfigUpdateMessage (deviceID, item, value, min, max);
 -         }
 - 
 -         void handleConfigSetMessage (BlocksProtocol::TopologyIndex deviceIndex, int32 item, int32 value)
 -         {
 -             if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
 -                 detector.handleConfigSetMessage (deviceID, item, value);
 -         }
 - 
 -         void handleConfigFactorySyncEndMessage (BlocksProtocol::TopologyIndex deviceIndex)
 -         {
 -             if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
 -                 detector.handleConfigFactorySyncEndMessage (deviceID);
 -         }
 - 
 -         void handleLogMessage (BlocksProtocol::TopologyIndex deviceIndex, const String& message)
 -         {
 -             if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
 -                 detector.handleLogMessage (deviceID, message);
 -         }
 - 
 -         //==============================================================================
 -         template <typename PacketBuilder>
 -         bool sendMessageToDevice (const PacketBuilder& builder) const
 -         {
 -             if (deviceConnection->sendMessageToDevice (builder.getData(), (size_t) builder.size()))
 -             {
 -                #if DUMP_BANDWIDTH_STATS
 -                 registerBytesOut (builder.size());
 -                #endif
 -                 return true;
 -             }
 - 
 -             return false;
 -         }
 - 
 -         bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const
 -         {
 -             BlocksProtocol::HostPacketBuilder<64> p;
 -             p.writePacketSysexHeaderBytes (deviceIndex);
 -             p.deviceControlMessage (commandID);
 -             p.writePacketSysexFooter();
 -             return sendMessageToDevice (p);
 -         }
 - 
 -         bool broadcastCommandMessage (uint32 commandID) const
 -         {
 -             return sendCommandMessage (BlocksProtocol::topologyIndexForBroadcast, commandID);
 -         }
 - 
 -         DeviceConnection* getDeviceConnection()
 -         {
 -             return deviceConnection.get();
 -         }
 - 
 -         Detector& detector;
 -         juce::String deviceName;
 - 
 -         juce::Array<DeviceInfo> currentDeviceInfo;
 -         juce::Array<BlockDeviceConnection> currentDeviceConnections;
 - 
 -         static constexpr double pingTimeoutSeconds = 6.0;
 - 
 -     private:
 -         //==============================================================================
 -         std::unique_ptr<DeviceConnection> deviceConnection;
 - 
 -         juce::Array<BlocksProtocol::DeviceStatus> incomingTopologyDevices, currentTopologyDevices;
 -         juce::Array<BlocksProtocol::DeviceConnection> incomingTopologyConnections, currentTopologyConnections;
 - 
 -         juce::CriticalSection incomingPacketLock;
 -         juce::Array<juce::MemoryBlock> incomingPackets;
 - 
 -         struct TouchStart
 -         {
 -             float x, y;
 -         };
 - 
 -         TouchList<TouchStart> touchStartPositions;
 - 
 -         juce::Time lastGlobalPingTime;
 - 
 -         struct BlockPingTime
 -         {
 -             Block::UID blockUID;
 -             juce::Time lastPing;
 -         };
 - 
 -         juce::Array<BlockPingTime> blockPings;
 - 
 -         Block::UID getDeviceIDFromMessageIndex (BlocksProtocol::TopologyIndex index) noexcept
 -         {
 -             auto uid = getDeviceIDFromIndex (index);
 - 
 -             if (uid == Block::UID())
 -             {
 -                 scheduleNewTopologyRequest(); // force a re-request of the topology when we
 -                                               // get an event from a block that we don't know about
 -             }
 -             else
 -             {
 -                 auto now = juce::Time::getCurrentTime();
 - 
 -                 for (auto& ping : blockPings)
 -                 {
 -                     if (ping.blockUID == uid)
 -                     {
 -                         ping.lastPing = now;
 -                         return uid;
 -                     }
 -                 }
 - 
 -                 blockPings.add ({ uid, now });
 -             }
 - 
 -             return uid;
 -         }
 - 
 -         juce::Array<BlockDeviceConnection> getArrayOfConnections (const juce::Array<BlocksProtocol::DeviceConnection>& connections)
 -         {
 -             juce::Array<BlockDeviceConnection> result;
 - 
 -             for (auto&& c : connections)
 -             {
 -                 BlockDeviceConnection dc;
 -                 dc.device1 = getDeviceIDFromIndex (c.device1);
 -                 dc.device2 = getDeviceIDFromIndex (c.device2);
 -                 dc.connectionPortOnDevice1 = convertConnectionPort (dc.device1, c.port1);
 -                 dc.connectionPortOnDevice2 = convertConnectionPort (dc.device2, c.port2);
 - 
 -                 result.add (dc);
 -             }
 - 
 -             return result;
 -         }
 - 
 -         Block::ConnectionPort convertConnectionPort (Block::UID uid, BlocksProtocol::ConnectorPort p) noexcept
 -         {
 -             if (auto* info = getDeviceInfoFromUID (uid))
 -                 return BlocksProtocol::BlockDataSheet (info->serial).convertPortIndexToConnectorPort (p);
 - 
 -             jassertfalse;
 -             return { Block::ConnectionPort::DeviceEdge::north, 0 };
 -         }
 - 
 -         //==============================================================================
 -         void handleIncomingMessage (const void* data, size_t dataSize)
 -         {
 -             juce::MemoryBlock mb (data, dataSize);
 - 
 -             {
 -                 const juce::ScopedLock sl (incomingPacketLock);
 -                 incomingPackets.add (std::move (mb));
 -             }
 - 
 -             triggerAsyncUpdate();
 - 
 -            #if DUMP_BANDWIDTH_STATS
 -             registerBytesIn ((int) dataSize);
 -            #endif
 -         }
 - 
 -         void handleAsyncUpdate() override
 -         {
 -             juce::Array<juce::MemoryBlock> packets;
 -             packets.ensureStorageAllocated (32);
 - 
 -             {
 -                 const juce::ScopedLock sl (incomingPacketLock);
 -                 incomingPackets.swapWith (packets);
 -             }
 - 
 -             for (auto& packet : packets)
 -             {
 -                 auto data = static_cast<const uint8*> (packet.getData());
 - 
 -                 BlocksProtocol::HostPacketDecoder<ConnectedDeviceGroup>
 -                     ::processNextPacket (*this, *data, data + 1, (int) packet.getSize() - 1);
 -             }
 - 
 -             lastGlobalPingTime = juce::Time::getCurrentTime();
 -         }
 - 
 -         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectedDeviceGroup)
 -     };
 - 
 -     //==============================================================================
 -     /** This is the main singleton object that keeps track of connected blocks */
 -     struct Detector   : public juce::ReferenceCountedObject,
 -                         private juce::Timer
 -     {
 -         Detector()  : defaultDetector (new MIDIDeviceDetector()), deviceDetector (*defaultDetector)
 -         {
 -             topologyBroadcastThrottle.detector = this;
 -             startTimer (10);
 -         }
 - 
 -         Detector (DeviceDetector& dd)  : deviceDetector (dd)
 -         {
 -             topologyBroadcastThrottle.detector = this;
 -             startTimer (10);
 -         }
 - 
 -         ~Detector()
 -         {
 -             jassert (activeTopologySources.isEmpty());
 -             jassert (activeControlButtons.isEmpty());
 -         }
 - 
 -         using Ptr = juce::ReferenceCountedObjectPtr<Detector>;
 - 
 -         static Detector::Ptr getDefaultDetector()
 -         {
 -             auto& d = getDefaultDetectorPointer();
 - 
 -             if (d == nullptr)
 -                 d = new Detector();
 - 
 -             return d;
 -         }
 - 
 -         static Detector::Ptr& getDefaultDetectorPointer()
 -         {
 -             static Detector::Ptr defaultDetector;
 -             return defaultDetector;
 -         }
 - 
 -         void detach (PhysicalTopologySource* pts)
 -         {
 -             activeTopologySources.removeAllInstancesOf (pts);
 - 
 -             if (activeTopologySources.isEmpty())
 -             {
 -                 for (auto& b : currentTopology.blocks)
 -                     if (auto bi = BlockImplementation::getFrom (*b))
 -                         bi->sendCommandMessage (BlocksProtocol::endAPIMode);
 - 
 -                 currentTopology = {};
 -                 topologyBroadcastThrottle.lastTopology = {};
 - 
 -                 auto& d = getDefaultDetectorPointer();
 - 
 -                 if (d != nullptr && d->getReferenceCount() == 2)
 -                     getDefaultDetectorPointer() = nullptr;
 -             }
 -         }
 - 
 -         bool isConnected (Block::UID deviceID) const noexcept
 -         {
 -             JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED // This method must only be called from the message thread!
 - 
 -             for (auto&& b : currentTopology.blocks)
 -                 if (b->uid == deviceID)
 -                     return true;
 - 
 -             return false;
 -         }
 - 
 -         const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept
 -         {
 -             for (auto d : connectedDeviceGroups)
 -                 if (auto status = d->getLastStatus (deviceID))
 -                     return status;
 - 
 -             return nullptr;
 -         }
 - 
 -         void handleTopologyChange()
 -         {
 -             JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
 - 
 -             {
 -                 juce::Array<DeviceInfo> newDeviceInfo;
 -                 juce::Array<BlockDeviceConnection> newDeviceConnections;
 - 
 -                 for (auto d : connectedDeviceGroups)
 -                 {
 -                     newDeviceInfo.addArray (d->currentDeviceInfo);
 -                     newDeviceConnections.addArray (d->currentDeviceConnections);
 -                 }
 - 
 -                 for (int i = currentTopology.blocks.size(); --i >= 0;)
 -                 {
 -                     auto block = currentTopology.blocks.getUnchecked (i);
 - 
 -                     if (! containsBlockWithUID (newDeviceInfo, block->uid))
 -                     {
 -                         if (auto bi = BlockImplementation::getFrom (*block))
 -                             bi->invalidate();
 - 
 -                         currentTopology.blocks.remove (i);
 -                     }
 -                     else
 -                     {
 -                         if (versionNumberAddedToBlock (newDeviceInfo, block->uid, block->versionNumber))
 -                             setVersionNumberForBlock (newDeviceInfo, *block);
 - 
 -                         if (nameAddedToBlock (newDeviceInfo, block->uid))
 -                             setNameForBlock (newDeviceInfo, *block);
 -                     }
 -                 }
 - 
 -                 for (auto& info : newDeviceInfo)
 -                     if (info.serial.isValid())
 -                         if (! containsBlockWithUID (currentTopology.blocks, getBlockUIDFromSerialNumber (info.serial)))
 -                             currentTopology.blocks.add (new BlockImplementation (info.serial, *this, info.version, info.name, info.isMaster));
 - 
 -                 currentTopology.connections.swapWith (newDeviceConnections);
 -             }
 - 
 -             topologyBroadcastThrottle.scheduleTopologyChangeCallback();
 -         }
 - 
 -         void handleSharedDataACK (Block::UID deviceID, uint32 packetCounter) const
 -         {
 -             JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
 - 
 -             for (auto&& b : currentTopology.blocks)
 -                 if (b->uid == deviceID)
 -                     if (auto bi = BlockImplementation::getFrom (*b))
 -                         bi->handleSharedDataACK (packetCounter);
 -         }
 - 
 -         void handleFirmwareUpdateACK (Block::UID deviceID, uint8 resultCode, uint32 resultDetail)
 -         {
 -             for (auto&& b : currentTopology.blocks)
 -                 if (b->uid == deviceID)
 -                     if (auto bi = BlockImplementation::getFrom (*b))
 -                         bi->handleFirmwareUpdateACK (resultCode, resultDetail);
 -         }
 - 
 -         void handleConfigUpdateMessage (Block::UID deviceID, int32 item, int32 value, int32 min, int32 max)
 -         {
 -             for (auto&& b : currentTopology.blocks)
 -                 if (b->uid == deviceID)
 -                     if (auto bi = BlockImplementation::getFrom (*b))
 -                         bi->handleConfigUpdateMessage (item, value, min, max);
 -         }
 - 
 -         void notifyBlockOfConfigChange (BlockImplementation& bi, uint32 item)
 -         {
 -             if (auto configChangedCallback = bi.configChangedCallback)
 -             {
 -                 if (item >= bi.getMaxConfigIndex())
 -                     configChangedCallback (bi, {}, item);
 -                 else
 -                     configChangedCallback (bi, bi.getLocalConfigMetaData (item), item);
 -             }
 -         }
 - 
 -         void handleConfigSetMessage (Block::UID deviceID, int32 item, int32 value)
 -         {
 -             for (auto&& b : currentTopology.blocks)
 -             {
 -                 if (b->uid == deviceID)
 -                 {
 -                     if (auto bi = BlockImplementation::getFrom (*b))
 -                     {
 -                         bi->handleConfigSetMessage (item, value);
 -                         notifyBlockOfConfigChange (*bi, uint32 (item));
 -                     }
 -                 }
 -             }
 -         }
 - 
 -         void handleConfigFactorySyncEndMessage (Block::UID deviceID)
 -         {
 -             for (auto&& b : currentTopology.blocks)
 -                 if (b->uid == deviceID)
 -                     if (auto bi = BlockImplementation::getFrom (*b))
 -                         notifyBlockOfConfigChange (*bi, bi->getMaxConfigIndex());
 -         }
 - 
 -         void handleLogMessage (Block::UID deviceID, const String& message) const
 -         {
 -             JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
 - 
 -             for (auto&& b : currentTopology.blocks)
 -                 if (b->uid == deviceID)
 -                     if (auto bi = BlockImplementation::getFrom (*b))
 -                         bi->handleLogMessage (message);
 -         }
 - 
 -         void handleButtonChange (Block::UID deviceID, Block::Timestamp timestamp, uint32 buttonIndex, bool isDown) const
 -         {
 -             JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
 - 
 -             for (auto b : activeControlButtons)
 -             {
 -                 if (b->block.uid == deviceID)
 -                 {
 -                     if (auto bi = BlockImplementation::getFrom (b->block))
 -                     {
 -                         bi->pingFromDevice();
 - 
 -                         if (buttonIndex < (uint32) bi->modelData.buttons.size())
 -                             b->broadcastButtonChange (timestamp, bi->modelData.buttons[(int) buttonIndex].type, isDown);
 -                     }
 -                 }
 -             }
 -         }
 - 
 -         void handleTouchChange (Block::UID deviceID, const TouchSurface::Touch& touchEvent)
 -         {
 -             JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
 - 
 -             for (auto t : activeTouchSurfaces)
 -             {
 -                 if (t->block.uid == deviceID)
 -                 {
 -                     TouchSurface::Touch scaledEvent (touchEvent);
 - 
 -                     scaledEvent.x      *= t->block.getWidth();
 -                     scaledEvent.y      *= t->block.getHeight();
 -                     scaledEvent.startX *= t->block.getWidth();
 -                     scaledEvent.startY *= t->block.getHeight();
 - 
 -                     t->broadcastTouchChange (scaledEvent);
 -                 }
 -             }
 -         }
 - 
 -         void cancelAllActiveTouches() noexcept
 -         {
 -             for (auto surface : activeTouchSurfaces)
 -                 surface->cancelAllActiveTouches();
 -         }
 - 
 -         void handleCustomMessage (Block::UID deviceID, Block::Timestamp timestamp, const int32* data)
 -         {
 -             for (auto&& b : currentTopology.blocks)
 -                 if (b->uid == deviceID)
 -                     if (auto bi = BlockImplementation::getFrom (*b))
 -                         bi->handleCustomMessage (timestamp, data);
 -         }
 - 
 -         //==============================================================================
 -         int getIndexFromDeviceID (Block::UID deviceID) const noexcept
 -         {
 -             for (auto* c : connectedDeviceGroups)
 -             {
 -                 auto index = c->getIndexFromDeviceID (deviceID);
 - 
 -                 if (index >= 0)
 -                     return index;
 -             }
 - 
 -             return -1;
 -         }
 - 
 -         template <typename PacketBuilder>
 -         bool sendMessageToDevice (Block::UID deviceID, const PacketBuilder& builder) const
 -         {
 -             for (auto* c : connectedDeviceGroups)
 -                 if (c->getIndexFromDeviceID (deviceID) >= 0)
 -                     return c->sendMessageToDevice (builder);
 - 
 -             return false;
 -         }
 - 
 -         static Detector* getFrom (Block& b) noexcept
 -         {
 -             if (auto* bi = BlockImplementation::getFrom (b))
 -                 return &(bi->detector);
 - 
 -             jassertfalse;
 -             return nullptr;
 -         }
 - 
 -         DeviceConnection* getDeviceConnectionFor (const Block& b)
 -         {
 -             for (const auto& d : connectedDeviceGroups)
 -             {
 -                 for (const auto& info : d->currentDeviceInfo)
 -                 {
 -                     if (info.uid == b.uid)
 -                         return d->getDeviceConnection();
 -                 }
 -             }
 - 
 -             return nullptr;
 -         }
 - 
 -         std::unique_ptr<MIDIDeviceDetector> defaultDetector;
 -         DeviceDetector& deviceDetector;
 - 
 -         juce::Array<PhysicalTopologySource*> activeTopologySources;
 -         juce::Array<ControlButtonImplementation*> activeControlButtons;
 -         juce::Array<TouchSurfaceImplementation*> activeTouchSurfaces;
 - 
 -         BlockTopology currentTopology;
 - 
 -     private:
 -         void timerCallback() override
 -         {
 -             startTimer (1500);
 - 
 -             auto detectedDevices = deviceDetector.scanForDevices();
 - 
 -             handleDevicesRemoved (detectedDevices);
 -             handleDevicesAdded (detectedDevices);
 -         }
 - 
 -         void handleDevicesRemoved (const juce::StringArray& detectedDevices)
 -         {
 -             bool anyDevicesRemoved = false;
 - 
 -             for (int i = connectedDeviceGroups.size(); --i >= 0;)
 -             {
 -                 if (! connectedDeviceGroups.getUnchecked(i)->isStillConnected (detectedDevices))
 -                 {
 -                     connectedDeviceGroups.remove (i);
 -                     anyDevicesRemoved = true;
 -                 }
 -             }
 - 
 -             if (anyDevicesRemoved)
 -                 handleTopologyChange();
 -         }
 - 
 -         void handleDevicesAdded (const juce::StringArray& detectedDevices)
 -         {
 -             bool anyDevicesAdded = false;
 - 
 -             for (const auto& devName : detectedDevices)
 -             {
 -                 if (! hasDeviceFor (devName))
 -                 {
 -                     if (auto d = deviceDetector.openDevice (detectedDevices.indexOf (devName)))
 -                     {
 -                         connectedDeviceGroups.add (new ConnectedDeviceGroup (*this, devName, d));
 -                         anyDevicesAdded = true;
 -                     }
 -                 }
 -             }
 - 
 -             if (anyDevicesAdded)
 -                 handleTopologyChange();
 -         }
 - 
 -         bool hasDeviceFor (const juce::String& devName) const
 -         {
 -             for (auto d : connectedDeviceGroups)
 -                 if (d->deviceName == devName)
 -                     return true;
 - 
 -             return false;
 -         }
 - 
 -         juce::OwnedArray<ConnectedDeviceGroup> connectedDeviceGroups;
 - 
 -         //==============================================================================
 -         /** Flurries of topology messages sometimes arrive due to loose connections.
 -             Avoid informing listeners until they've stabilised.
 -         */
 -         struct TopologyBroadcastThrottle  : private juce::Timer
 -         {
 -             TopologyBroadcastThrottle() = default;
 - 
 -             void scheduleTopologyChangeCallback()
 -             {
 -                #ifdef JUCE_BLOCKS_TOPOLOGY_BROADCAST_THROTTLE_TIME
 -                 startTimer (JUCE_BLOCKS_TOPOLOGY_BROADCAST_THROTTLE_TIME);
 -                #else
 -                 startTimer (750);
 -                #endif
 -             }
 - 
 -             void timerCallback() override
 -             {
 -                 if (detector->currentTopology != lastTopology)
 -                 {
 -                     lastTopology = detector->currentTopology;
 - 
 -                     for (auto* d : detector->activeTopologySources)
 -                         d->listeners.call ([] (TopologySource::Listener& l) { l.topologyChanged(); });
 - 
 -                    #if DUMP_TOPOLOGY
 -                     dumpTopology (lastTopology);
 -                    #endif
 -                 }
 - 
 -                 stopTimer();
 -             }
 - 
 -             Detector* detector = nullptr;
 -             BlockTopology lastTopology;
 -         };
 - 
 -         TopologyBroadcastThrottle topologyBroadcastThrottle;
 - 
 -         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Detector)
 -     };
 - 
 -     //==============================================================================
 -     struct BlockImplementation  : public Block,
 -                                   private MIDIDeviceConnection::Listener,
 -                                   private Timer
 -     {
 -         BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, Detector& detectorToUse, BlocksProtocol::VersionNumber version, BlocksProtocol::BlockName name, bool master)
 -             : Block (juce::String ((const char*) serial.serial, sizeof (serial.serial)),
 -                      juce::String ((const char*) version.version, version.length),
 -                      juce::String ((const char*) name.name, name.length)),
 -               modelData (serial),
 -               remoteHeap (modelData.programAndHeapSize),
 -               detector (detectorToUse),
 -               isMaster (master)
 -         {
 -             sendCommandMessage (BlocksProtocol::beginAPIMode);
 - 
 -             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));
 - 
 -             if (modelData.numLEDRowLEDs > 0)
 -                 ledRow.reset (new LEDRowImplementation (*this));
 - 
 -             listenerToMidiConnection = dynamic_cast<MIDIDeviceConnection*> (detector.getDeviceConnectionFor (*this));
 - 
 -             if (listenerToMidiConnection != nullptr)
 -                 listenerToMidiConnection->addListener (this);
 - 
 -             config.setDeviceComms (listenerToMidiConnection);
 -         }
 - 
 -         ~BlockImplementation()
 -         {
 -             if (listenerToMidiConnection != nullptr)
 -             {
 -                 config.setDeviceComms (nullptr);
 -                 listenerToMidiConnection->removeListener (this);
 -             }
 -         }
 - 
 -         void invalidate()
 -         {
 -             isStillConnected = false;
 -         }
 - 
 -         Type getType() const override                                   { return modelData.apiType; }
 -         juce::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; }
 -         bool isConnected() const override                               { return isStillConnected && detector.isConnected (uid); }
 -         bool isMasterBlock() const override                             { return isMaster; }
 - 
 -         TouchSurface* getTouchSurface() const override                  { return touchSurface.get(); }
 -         LEDGrid* getLEDGrid() const override                            { return ledGrid.get(); }
 -         LEDRow* getLEDRow() const override                              { 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
 -         {
 -             if (auto status = detector.getLastStatus (uid))
 -                 return status->batteryLevel.toUnipolarFloat();
 - 
 -             return 0.0f;
 -         }
 - 
 -         bool isBatteryCharging() const override
 -         {
 -             if (auto status = detector.getLastStatus (uid))
 -                 return status->batteryCharging.get() != 0;
 - 
 -             return false;
 -         }
 - 
 -         bool supportsGraphics() const override
 -         {
 -             return false;
 -         }
 - 
 -         int getDeviceIndex() const noexcept
 -         {
 -             return isConnected() ? detector.getIndexFromDeviceID (uid) : -1;
 -         }
 - 
 -         template <typename PacketBuilder>
 -         bool sendMessageToDevice (const PacketBuilder& builder)
 -         {
 -             lastMessageSendTime = juce::Time::getCurrentTime();
 -             return detector.sendMessageToDevice (uid, builder);
 -         }
 - 
 -         bool sendCommandMessage (uint32 commandID)
 -         {
 -             int index = getDeviceIndex();
 - 
 -             if (index < 0)
 -                 return false;
 - 
 -             BlocksProtocol::HostPacketBuilder<64> p;
 -             p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index);
 -             p.deviceControlMessage (commandID);
 -             p.writePacketSysexFooter();
 - 
 -             return sendMessageToDevice (p);
 -         }
 - 
 -         void handleCustomMessage (Block::Timestamp, const int32* data)
 -         {
 -             ProgramEventMessage m;
 - 
 -             for (uint32 i = 0; i < BlocksProtocol::numProgramMessageInts; ++i)
 -                 m.values[i] = data[i];
 - 
 -             programEventListeners.call ([&] (ProgramEventListener& l) { l.handleProgramEvent (*this, m); });
 -         }
 - 
 -         static BlockImplementation* getFrom (Block& b) noexcept
 -         {
 -             if (auto bi = dynamic_cast<BlockImplementation*> (&b))
 -                 return bi;
 - 
 -             jassertfalse;
 -             return nullptr;
 -         }
 - 
 -         bool isControlBlock() const
 -         {
 -             auto type = getType();
 - 
 -             return type == Block::Type::liveBlock
 -                 || type == Block::Type::loopBlock
 -                 || type == Block::Type::touchBlock
 -                 || type == Block::Type::developerControlBlock;
 -         }
 - 
 -         //==============================================================================
 -         std::function<void(const String&)> logger;
 - 
 -         void setLogger (std::function<void(const String&)> newLogger) override
 -         {
 -             logger = newLogger;
 -         }
 - 
 -         void handleLogMessage (const String& message) const
 -         {
 -             if (logger != nullptr)
 -                 logger (message);
 -         }
 - 
 -         //==============================================================================
 -         juce::Result setProgram (Program* newProgram) override
 -         {
 -             if (newProgram == nullptr || program.get() != newProgram)
 -             {
 -                 {
 -                     std::unique_ptr<Program> p (newProgram);
 - 
 -                     if (program != nullptr
 -                          && newProgram != nullptr
 -                          && program->getLittleFootProgram() == newProgram->getLittleFootProgram())
 -                         return juce::Result::ok();
 - 
 -                     stopTimer();
 -                     std::swap (program, p);
 -                 }
 - 
 -                 stopTimer();
 -                 programSize = 0;
 - 
 -                 if (program != nullptr)
 -                 {
 -                     littlefoot::Compiler compiler;
 -                     compiler.addNativeFunctions (PhysicalTopologySource::getStandardLittleFootFunctions());
 - 
 -                     auto err = compiler.compile (program->getLittleFootProgram(), 512);
 - 
 -                     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!");
 - 
 -                     auto size = (size_t) compiler.compiledObjectCode.size();
 -                     programSize = (uint32) size;
 - 
 -                     remoteHeap.resetDataRangeToUnknown (0, remoteHeap.blockSize);
 -                     remoteHeap.clear();
 -                     remoteHeap.sendChanges (*this, true);
 - 
 -                     remoteHeap.resetDataRangeToUnknown (0, (uint32) size);
 -                     remoteHeap.setBytes (0, compiler.compiledObjectCode.begin(), size);
 -                     remoteHeap.sendChanges (*this, true);
 - 
 -                     this->resetConfigListActiveStatus();
 - 
 -                     if (auto changeCallback = this->configChangedCallback)
 -                         changeCallback (*this, {}, this->getMaxConfigIndex());
 -                 }
 -                 else
 -                 {
 -                     remoteHeap.clear();
 -                 }
 -             }
 -             else
 -             {
 -                 jassertfalse;
 -             }
 - 
 -             return juce::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())
 -             {
 -                 auto index = getDeviceIndex();
 - 
 -                 if (index >= 0)
 -                 {
 -                     BlocksProtocol::HostPacketBuilder<128> p;
 -                     p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index);
 - 
 -                     if (p.addProgramEventMessage (message.values))
 -                     {
 -                         p.writePacketSysexFooter();
 -                         sendMessageToDevice (p);
 -                     }
 -                 }
 -                 else
 -                 {
 -                     jassertfalse;
 -                 }
 -             }
 -         }
 - 
 -         void timerCallback() override
 -         {
 -             if (remoteHeap.isFullySynced() && remoteHeap.isProgramLoaded())
 -             {
 -                 stopTimer();
 -                 sendCommandMessage (BlocksProtocol::saveProgramAsDefault);
 -             }
 -             else
 -             {
 -                 startTimer (100);
 -             }
 -         }
 - 
 -         void saveProgramAsDefault() override
 -         {
 -             startTimer (10);
 -         }
 - 
 -         uint32 getMemorySize() override
 -         {
 -             return modelData.programAndHeapSize;
 -         }
 - 
 -         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 = {};
 - 
 -             auto index = getDeviceIndex();
 - 
 -             if (index >= 0)
 -             {
 -                 BlocksProtocol::HostPacketBuilder<256> p;
 -                 p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index);
 - 
 -                 if (p.addFirmwareUpdatePacket (data, size))
 -                 {
 -                     p.writePacketSysexFooter();
 - 
 -                     if (sendMessageToDevice (p))
 -                     {
 -                         firmwarePacketAckCallback = callback;
 -                         return true;
 -                     }
 -                 }
 -             }
 -             else
 -             {
 -                 jassertfalse;
 -             }
 - 
 -             return false;
 -         }
 - 
 -         void handleFirmwareUpdateACK (uint8 resultCode, uint32 resultDetail)
 -         {
 -             if (firmwarePacketAckCallback != nullptr)
 -             {
 -                 firmwarePacketAckCallback (resultCode, resultDetail);
 -                 firmwarePacketAckCallback = {};
 -             }
 -         }
 - 
 -         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 = juce::Time::getCurrentTime();
 -         }
 - 
 -         void addDataInputPortListener (DataInputPortListener* listener) override
 -         {
 -             Block::addDataInputPortListener (listener);
 - 
 -             if (auto midiInput = getMidiInput())
 -                 midiInput->start();
 -         }
 - 
 -         void sendMessage (const void* message, size_t messageSize) override
 -         {
 -             if (auto midiOutput = getMidiOutput())
 -                 midiOutput->sendMessageNow ({ message, (int) messageSize });
 -         }
 - 
 -         void handleTimerTick()
 -         {
 -             if (++resetMessagesSent < 3)
 -             {
 -                 if (resetMessagesSent == 1)
 -                     sendCommandMessage (BlocksProtocol::endAPIMode);
 - 
 -                 sendCommandMessage (BlocksProtocol::beginAPIMode);
 -                 return;
 -             }
 - 
 -             if (ledGrid != nullptr)
 -                 if (auto renderer = ledGrid->getRenderer())
 -                     renderer->renderLEDGrid (*ledGrid);
 - 
 -             remoteHeap.sendChanges (*this, false);
 - 
 -             if (lastMessageSendTime < juce::Time::getCurrentTime() - juce::RelativeTime::milliseconds (pingIntervalMs))
 -                 sendCommandMessage (BlocksProtocol::ping);
 -         }
 - 
 -         //==============================================================================
 -         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();
 -         }
 - 
 -         void setConfigChangedCallback (std::function<void(Block&, const ConfigMetaData&, uint32)> configChanged) override
 -         {
 -             configChangedCallback = configChanged;
 -         }
 - 
 -         void factoryReset() override
 -         {
 -             auto index = getDeviceIndex();
 - 
 -             if (index >= 0)
 -             {
 -                 BlocksProtocol::HostPacketBuilder<32> p;
 -                 p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index);
 -                 p.addFactoryReset();
 -                 p.writePacketSysexFooter();
 -                 sendMessageToDevice (p);
 -             }
 -             else
 -             {
 -                 jassertfalse;
 -             }
 -         }
 - 
 -         void blockReset() override
 -         {
 -             auto index = getDeviceIndex();
 - 
 -             if (index >= 0)
 -             {
 -                 BlocksProtocol::HostPacketBuilder<32> p;
 -                 p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index);
 -                 p.addBlockReset();
 -                 p.writePacketSysexFooter();
 -                 sendMessageToDevice (p);
 -             }
 -             else
 -             {
 -                 jassertfalse;
 -             }
 -         }
 - 
 -         bool setName (const juce::String& newName) override
 -         {
 -             auto index = getDeviceIndex();
 - 
 -             if (index >= 0)
 -             {
 -                 BlocksProtocol::HostPacketBuilder<128> p;
 -                 p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index);
 - 
 -                 if (p.addSetBlockName (newName))
 -                 {
 -                     p.writePacketSysexFooter();
 - 
 -                     if (sendMessageToDevice (p))
 -                         return true;
 -                 }
 -             }
 -             else
 -             {
 -                 jassertfalse;
 -             }
 - 
 -             return false;
 -         }
 - 
 -         //==============================================================================
 -         std::unique_ptr<TouchSurface> touchSurface;
 -         juce::OwnedArray<ControlButton> controlButtons;
 -         std::unique_ptr<LEDGridImplementation> ledGrid;
 -         std::unique_ptr<LEDRowImplementation> ledRow;
 -         juce::OwnedArray<StatusLight> statusLights;
 - 
 -         BlocksProtocol::BlockDataSheet modelData;
 - 
 -         MIDIDeviceConnection* listenerToMidiConnection = nullptr;
 - 
 -         static constexpr int pingIntervalMs = 400;
 - 
 -         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;
 - 
 -         Detector& detector;
 -         juce::Time lastMessageSendTime, lastMessageReceiveTime;
 - 
 -         BlockConfigManager config;
 -         std::function<void(Block&, const ConfigMetaData&, uint32)> configChangedCallback;
 - 
 -     private:
 -         std::unique_ptr<Program> program;
 -         uint32 programSize = 0;
 - 
 -         std::function<void(uint8, uint32)> firmwarePacketAckCallback;
 - 
 -         uint32 resetMessagesSent = 0;
 -         bool isStillConnected = true;
 -         bool isMaster = false;
 - 
 -         void initialiseDeviceIndexAndConnection()
 -         {
 -             config.setDeviceIndex ((TopologyIndex) getDeviceIndex());
 -             config.setDeviceComms (listenerToMidiConnection);
 -         }
 - 
 -         const juce::MidiInput* getMidiInput() const
 -         {
 -             if (auto c = dynamic_cast<MIDIDeviceConnection*> (detector.getDeviceConnectionFor (*this)))
 -                 return c->midiInput.get();
 - 
 -             jassertfalse;
 -             return nullptr;
 -         }
 - 
 -         juce::MidiInput* getMidiInput()
 -         {
 -             return const_cast<juce::MidiInput*> (static_cast<const BlockImplementation&>(*this).getMidiInput());
 -         }
 - 
 -         const juce::MidiOutput* getMidiOutput() const
 -         {
 -             if (auto c = dynamic_cast<MIDIDeviceConnection*> (detector.getDeviceConnectionFor (*this)))
 -                 return c->midiOutput.get();
 - 
 -             jassertfalse;
 -             return nullptr;
 -         }
 - 
 -         juce::MidiOutput* getMidiOutput()
 -         {
 -             return const_cast<juce::MidiOutput*> (static_cast<const BlockImplementation&>(*this).getMidiOutput());
 -         }
 - 
 -         void handleIncomingMidiMessage (const juce::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);
 -             juce::ignoreUnused (c);
 -             listenerToMidiConnection->removeListener (this);
 -             listenerToMidiConnection = nullptr;
 -             config.setDeviceComms (nullptr);
 -         }
 - 
 -         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlockImplementation)
 -     };
 - 
 -     //==============================================================================
 -     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 (new 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, colour.getRed()   >> 3);
 -             block.setDataBits (bitIndex + 5,  6, colour.getGreen() >> 2);
 -             block.setDataBits (bitIndex + 11, 5, colour.getBlue()  >> 3);
 -         }
 - 
 -         struct DefaultLEDGridProgram  : public Block::Program
 -         {
 -             DefaultLEDGridProgram (Block& b) : Block::Program (b) {}
 - 
 -             juce::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)
 -     };
 - 
 -     //==============================================================================
 -     struct TouchSurfaceImplementation  : public TouchSurface,
 -                                          private juce::Timer
 -     {
 -         TouchSurfaceImplementation (BlockImplementation& b)  : TouchSurface (b), blockImpl (b)
 -         {
 -             if (auto det = Detector::getFrom (block))
 -                 det->activeTouchSurfaces.add (this);
 - 
 -             startTimer (500);
 -         }
 - 
 -         ~TouchSurfaceImplementation()
 -         {
 -             if (auto det = Detector::getFrom (block))
 -                 det->activeTouchSurfaces.removeFirstMatchingValue (this);
 -         }
 - 
 -         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, juce::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 = juce::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 = juce::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 = juce::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)
 -         {
 -             if (auto det = Detector::getFrom (block))
 -                 det->activeControlButtons.add (this);
 -         }
 - 
 -         ~ControlButtonImplementation()
 -         {
 -             if (auto det = Detector::getFrom (block))
 -                 det->activeControlButtons.removeFirstMatchingValue (this);
 -         }
 - 
 -         ButtonFunction getType() const override         { return buttonInfo.type; }
 -         juce::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)
 -         {
 -         }
 - 
 -         juce::String getName() const override               { return info.name; }
 - 
 -         bool setColour (LEDColour newColour) override
 -         {
 -             // XXX TODO!
 -             juce::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)
 -     };
 - 
 -     //==============================================================================
 -    #if DUMP_TOPOLOGY
 -     static juce::String idToSerialNum (const BlockTopology& topology, Block::UID uid)
 -     {
 -         for (auto* b : topology.blocks)
 -             if (b->uid == uid)
 -                 return b->serialNumber;
 - 
 -         return "???";
 -     }
 - 
 -     static juce::String portEdgeToString (Block::ConnectionPort port)
 -     {
 -         switch (port.edge)
 -         {
 -             case Block::ConnectionPort::DeviceEdge::north: return "north";
 -             case Block::ConnectionPort::DeviceEdge::south: return "south";
 -             case Block::ConnectionPort::DeviceEdge::east:  return "east";
 -             case Block::ConnectionPort::DeviceEdge::west:  return "west";
 -         }
 - 
 -         return {};
 -     }
 - 
 -     static juce::String portToString (Block::ConnectionPort port)
 -     {
 -         return portEdgeToString (port) + "_" + juce::String (port.index);
 -     }
 - 
 -     static void dumpTopology (const BlockTopology& topology)
 -     {
 -         MemoryOutputStream m;
 - 
 -         m << "=============================================================================" << newLine
 -           << "Topology:  " << topology.blocks.size() << " device(s)" << newLine
 -           << newLine;
 - 
 -         int index = 0;
 - 
 -         for (auto block : topology.blocks)
 -         {
 -             m << "Device " << index++ << (block->isMasterBlock() ? ":  (MASTER)" : ":") << newLine;
 - 
 -             m << "  Description: " << block->getDeviceDescription() << newLine
 -               << "  Serial: " << block->serialNumber << newLine;
 - 
 -             if (auto bi = BlockImplementation::getFrom (*block))
 -                 m << "  Short address: " << (int) bi->getDeviceIndex() << newLine;
 - 
 -             m << "  Battery level: " + juce::String (juce::roundToInt (100.0f * block->getBatteryLevel())) + "%" << newLine
 -               << "  Battery charging: " + juce::String (block->isBatteryCharging() ? "y" : "n") << newLine
 -               << "  Width: " << block->getWidth() << newLine
 -               << "  Height: " << block->getHeight() << newLine
 -               << "  Millimeters per unit: " << block->getMillimetersPerUnit() << newLine
 -               << newLine;
 -         }
 - 
 -         for (auto& connection : topology.connections)
 -         {
 -             m << idToSerialNum (topology, connection.device1)
 -               << ":" << portToString (connection.connectionPortOnDevice1)
 -               << "  <->  "
 -               << idToSerialNum (topology, connection.device2)
 -               << ":" << portToString (connection.connectionPortOnDevice2) << newLine;
 -         }
 - 
 -         m << "=============================================================================" << newLine;
 - 
 -         Logger::outputDebugString (m.toString());
 -     }
 -    #endif
 - };
 - 
 - //==============================================================================
 - struct PhysicalTopologySource::DetectorHolder  : private juce::Timer
 - {
 -     DetectorHolder (PhysicalTopologySource& pts)
 -         : topologySource (pts),
 -           detector (Internal::Detector::getDefaultDetector())
 -     {
 -         startTimerHz (30);
 -     }
 - 
 -     DetectorHolder (PhysicalTopologySource& pts, DeviceDetector& dd)
 -         : topologySource (pts),
 -           detector (new Internal::Detector (dd))
 -     {
 -         startTimerHz (30);
 -     }
 - 
 -     void timerCallback() override
 -     {
 -         if (! topologySource.hasOwnServiceTimer())
 -             handleTimerTick();
 -     }
 - 
 -     void handleTimerTick()
 -     {
 -         for (auto& b : detector->currentTopology.blocks)
 -             if (auto bi = Internal::BlockImplementation::getFrom (*b))
 -                 bi->handleTimerTick();
 -     }
 - 
 -     PhysicalTopologySource& topologySource;
 -     Internal::Detector::Ptr detector;
 - };
 - 
 - //==============================================================================
 - PhysicalTopologySource::PhysicalTopologySource()
 -     : detector (new DetectorHolder (*this))
 - {
 -     detector->detector->activeTopologySources.add (this);
 - }
 - 
 - PhysicalTopologySource::PhysicalTopologySource (DeviceDetector& detectorToUse)
 -     : detector (new DetectorHolder (*this, detectorToUse))
 - {
 -     detector->detector->activeTopologySources.add (this);
 - }
 - 
 - PhysicalTopologySource::~PhysicalTopologySource()
 - {
 -     detector->detector->detach (this);
 -     detector = nullptr;
 - }
 - 
 - BlockTopology PhysicalTopologySource::getCurrentTopology() const
 - {
 -     JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED // This method must only be called from the message thread!
 - 
 -     return detector->detector->currentTopology;
 - }
 - 
 - void PhysicalTopologySource::cancelAllActiveTouches() noexcept
 - {
 -     detector->detector->cancelAllActiveTouches();
 - }
 - 
 - bool PhysicalTopologySource::hasOwnServiceTimer() const     { return false; }
 - void PhysicalTopologySource::handleTimerTick()              { detector->handleTimerTick(); }
 - 
 - PhysicalTopologySource::DeviceConnection::DeviceConnection() {}
 - PhysicalTopologySource::DeviceConnection::~DeviceConnection() {}
 - 
 - PhysicalTopologySource::DeviceDetector::DeviceDetector() {}
 - PhysicalTopologySource::DeviceDetector::~DeviceDetector() {}
 - 
 - const char* const* PhysicalTopologySource::getStandardLittleFootFunctions() noexcept
 - {
 -     return BlocksProtocol::ledProgramLittleFootFunctions;
 - }
 - 
 - static bool blocksMatch (const Block::Array& list1, const Block::Array& list2) noexcept
 - {
 -     if (list1.size() != list2.size())
 -         return false;
 - 
 -     for (auto&& b : list1)
 -         if (! list2.contains (b))
 -             return false;
 - 
 -     return true;
 - }
 - 
 - bool BlockTopology::operator== (const BlockTopology& other) const noexcept
 - {
 -     return connections == other.connections && blocksMatch (blocks, other.blocks);
 - }
 - 
 - bool BlockTopology::operator!= (const BlockTopology& other) const noexcept
 - {
 -     return ! operator== (other);
 - }
 - 
 - bool BlockDeviceConnection::operator== (const BlockDeviceConnection& other) const noexcept
 - {
 -     return (device1 == other.device1 && device2 == other.device2
 -              && connectionPortOnDevice1 == other.connectionPortOnDevice1
 -              && connectionPortOnDevice2 == other.connectionPortOnDevice2)
 -         || (device1 == other.device2 && device2 == other.device1
 -              && connectionPortOnDevice1 == other.connectionPortOnDevice2
 -              && connectionPortOnDevice2 == other.connectionPortOnDevice1);
 - }
 - 
 - bool BlockDeviceConnection::operator!= (const BlockDeviceConnection& other) const noexcept
 - {
 -     return ! operator== (other);
 - }
 - 
 - } // namespace juce
 
 
  |