| @@ -0,0 +1,230 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| { | |||
| BlocksVersion::BlocksVersion (const String& versionString) | |||
| { | |||
| evaluate (versionString); | |||
| } | |||
| String BlocksVersion::toString (bool extended) const | |||
| { | |||
| String output = String (major) + "." + String (minor) + "." + String (patch); | |||
| if (extended) | |||
| { | |||
| if (releaseType.isNotEmpty()) | |||
| output += "-" + releaseType + "-" + String (releaseCount); | |||
| if (commit.isNotEmpty()) | |||
| output += "-" + commit; | |||
| if (forced) | |||
| output += "-f"; | |||
| } | |||
| return output; | |||
| } | |||
| static std::regex getRegEx() | |||
| { | |||
| static const std::string majorMinorPatchRegex { "([0-9]+)\\.([0-9]+)\\.([0-9]+)" }; | |||
| static const std::string releaseAndCommitDetailsRegex { "(?:-(alpha|beta|rc))?(?:-([0-9]+))?(?:-g([A-Za-z0-9]+))?" }; | |||
| static const std::string forcedUpdateRegex { "(-f)?" }; | |||
| static const std::regex regEx ("(?:.+)?" + majorMinorPatchRegex + releaseAndCommitDetailsRegex + forcedUpdateRegex + "(?:.+)?"); | |||
| return regEx; | |||
| } | |||
| bool BlocksVersion::isValidVersion (const String& versionString) | |||
| { | |||
| return std::regex_match (versionString.toRawUTF8(), getRegEx()); | |||
| } | |||
| bool BlocksVersion::evaluate (const String& versionString) | |||
| { | |||
| std::cmatch groups; | |||
| const bool result = std::regex_match (versionString.toRawUTF8(), groups, getRegEx()); | |||
| jassert (result); | |||
| auto toInt = [](const std::sub_match<const char*> match) | |||
| { | |||
| return std::atoi (match.str().c_str()); | |||
| }; | |||
| enum tags { FULL, MAJOR, MINOR, PATCH, RELEASE, COUNT, COMMIT, FORCED}; | |||
| major = toInt (groups[MAJOR]); | |||
| minor = toInt (groups[MINOR]); | |||
| patch = toInt (groups[PATCH]); | |||
| releaseType = String (groups[RELEASE]); | |||
| releaseCount = toInt (groups[COUNT]); | |||
| commit = String (groups[COMMIT]); | |||
| forced = groups[FORCED].matched; | |||
| return result; | |||
| } | |||
| bool BlocksVersion::isEqualTo (const BlocksVersion& other) const | |||
| { | |||
| return major == other.major && | |||
| minor == other.minor && | |||
| patch == other.patch && | |||
| releaseType == other.releaseType && | |||
| releaseCount == other.releaseCount; | |||
| } | |||
| bool BlocksVersion::isGreaterThan (const BlocksVersion& other) const | |||
| { | |||
| if (major != other.major) return (major > other.major); | |||
| if (minor != other.minor) return (minor > other.minor); | |||
| if (patch != other.patch) return (patch > other.patch); | |||
| return releaseTypeGreaterThan (other); | |||
| } | |||
| bool BlocksVersion::releaseTypeGreaterThan (const BlocksVersion& other) const | |||
| { | |||
| auto getReleaseTypePriority = [](const juce::BlocksVersion& version) | |||
| { | |||
| String releaseTypes[4] = { "alpha", "beta", "rc", {} }; | |||
| for (int i = 0; i < 4; ++i) | |||
| if (version.releaseType == releaseTypes[i]) | |||
| return i; | |||
| return -1; | |||
| }; | |||
| if (releaseType != other.releaseType) | |||
| return getReleaseTypePriority (*this) > getReleaseTypePriority (other); | |||
| return releaseCount > other.releaseCount; | |||
| } | |||
| bool BlocksVersion::operator== (const BlocksVersion& other) const | |||
| { | |||
| return isEqualTo (other); | |||
| } | |||
| bool BlocksVersion::operator!=(const BlocksVersion& other) const | |||
| { | |||
| return ! (*this == other); | |||
| } | |||
| bool BlocksVersion::operator> (const BlocksVersion& other) const | |||
| { | |||
| return isGreaterThan (other); | |||
| } | |||
| bool BlocksVersion::operator< (const BlocksVersion& other) const | |||
| { | |||
| return ! (*this > other) && (*this != other); | |||
| } | |||
| bool BlocksVersion::operator<= (const BlocksVersion& other) const | |||
| { | |||
| return (*this < other) || (*this == other); | |||
| } | |||
| bool BlocksVersion::operator>= (const BlocksVersion& other) const | |||
| { | |||
| return (*this > other) || (*this == other); | |||
| } | |||
| #if JUCE_UNIT_TESTS | |||
| class BlocksVersionUnitTests : public UnitTest | |||
| { | |||
| public: | |||
| BlocksVersionUnitTests() : UnitTest ("BlocksVersionUnitTests", "Blocks") {} | |||
| void runTest() override | |||
| { | |||
| beginTest ("Compare patch number"); | |||
| expect (BlocksVersion ("4.6.7") < BlocksVersion ("4.6.11")); | |||
| expect (BlocksVersion ("4.6.6") > BlocksVersion ("4.6.2")); | |||
| expect (BlocksVersion ("4.6.5") <= BlocksVersion ("4.6.8")); | |||
| expect (BlocksVersion ("4.6.4") >= BlocksVersion ("4.6.3")); | |||
| beginTest ("Compare minor number"); | |||
| expect (BlocksVersion ("4.5.9") < BlocksVersion ("4.6.7")); | |||
| expect (BlocksVersion ("4.15.2") > BlocksVersion ("4.6.6")); | |||
| expect (BlocksVersion ("4.4.8") <= BlocksVersion ("4.6.5")); | |||
| expect (BlocksVersion ("4.7.4") >= BlocksVersion ("4.6.3")); | |||
| beginTest ("Compare major number"); | |||
| expect (BlocksVersion ("4.6.9") < BlocksVersion ("8.5.7")); | |||
| expect (BlocksVersion ("15.6.2") > BlocksVersion ("4.9.6")); | |||
| expect (BlocksVersion ("4.6.8") <= BlocksVersion ("7.4.5")); | |||
| expect (BlocksVersion ("5.6.4") >= BlocksVersion ("4.7.3")); | |||
| beginTest ("Compare build number"); | |||
| expect (BlocksVersion ("0.3.2-alpha-3-gjduh") < BlocksVersion ("0.3.2-alpha-12-gjduh")); | |||
| expect (BlocksVersion ("0.3.2-alpha-4-gjduh") > BlocksVersion ("0.3.2-alpha-1-gjduh")); | |||
| expect (BlocksVersion ("0.3.2-beta-5-gjduh") <= BlocksVersion ("0.3.2-beta-6-gjduh")); | |||
| expect (BlocksVersion ("0.3.2-beta-6-gjduh") >= BlocksVersion ("0.3.2-beta-3-gjduh")); | |||
| beginTest ("Compare build type"); | |||
| expect (BlocksVersion ("0.3.2-alpha-3-gjduhenf") < BlocksVersion ("0.3.2-beta-1-gjduhenf")); | |||
| expect (BlocksVersion ("0.3.2-beta-3-gjduhenf") < BlocksVersion ("0.3.2")); | |||
| expect (BlocksVersion ("0.3.2") > BlocksVersion ("0.3.2-alpha-3-gjduhenf")); | |||
| beginTest ("Compare equal numbers"); | |||
| expect (BlocksVersion ("4.6.7") == BlocksVersion ("4.6.7")); | |||
| expect (BlocksVersion ("4.6.7-alpha-3-gsdfsf") == BlocksVersion ("4.6.7-alpha-3-gsdfsf")); | |||
| beginTest ("Identify forced version"); | |||
| expect (BlocksVersion("0.2.2-2-g25eaec8a-f").forced); | |||
| expect (BlocksVersion("0.2.2-2-f").forced); | |||
| expect (! BlocksVersion("0.2.2-2-g25eaec8-d7").forced); | |||
| beginTest ("Valid Strings"); | |||
| expect (BlocksVersion::isValidVersion ("Rainbow 0.4.5-beta-1-g4c36e")); | |||
| expect (! BlocksVersion::isValidVersion ("0.4-beta-1-g4c36e")); | |||
| expect (! BlocksVersion::isValidVersion ("a.0.4-beta-1-g4c36e")); | |||
| expect (BlocksVersion::isValidVersion ("BLOCKS control 0.2.2-2-g25eaec8a-f.syx")); | |||
| expect (BlocksVersion("BLOCKS control 0.2.2-2-g25eaec8a-f.syx") == BlocksVersion("0.2.2-2-g25eaec8a-f")); | |||
| beginTest ("Default constructors"); | |||
| { | |||
| BlocksVersion v1 ("4.5.9"); | |||
| BlocksVersion v2 (v1); | |||
| BlocksVersion v3; | |||
| v3 = v1; | |||
| expect (v2 == v1); | |||
| expect (v3 == v1); | |||
| BlocksVersion emptyVersion; | |||
| expect (emptyVersion == BlocksVersion ("0.0.0")); | |||
| } | |||
| } | |||
| }; | |||
| static BlocksVersionUnitTests BlocksVersionUnitTests; | |||
| #endif | |||
| } // namespace juce | |||
| @@ -0,0 +1,82 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| #pragma once | |||
| namespace juce | |||
| { | |||
| struct BlocksVersion | |||
| { | |||
| public: | |||
| /** The main value in a version number x.0.0 */ | |||
| int major = 0; | |||
| /** The secondary value in a version number 1.x.0 */ | |||
| int minor = 0; | |||
| /** The tertiary value in a version number 1.0.x */ | |||
| int patch = 0; | |||
| /** The release tag for this version, such as "beta", "alpha", "rc", etc */ | |||
| juce::String releaseType; | |||
| /** A numberical value assosiated with the release tag, such as "beta 4" */ | |||
| int releaseCount = 0; | |||
| /** The assosiated git commit that generated this firmware version */ | |||
| juce::String commit; | |||
| /** Identify "forced" firmware builds **/ | |||
| bool forced = false; | |||
| juce::String toString (bool extended = false) const; | |||
| /** Constructs a version number from an formatted juce::String */ | |||
| BlocksVersion (const juce::String&); | |||
| /** Constructs a version number from another BlocksVersion */ | |||
| BlocksVersion (const BlocksVersion& other) = default; | |||
| /** Creates an empty version number **/ | |||
| BlocksVersion() = default; | |||
| /** Returns true if string format is valid */ | |||
| static bool isValidVersion (const juce::String& versionString); | |||
| bool operator == (const BlocksVersion&) const; | |||
| bool operator != (const BlocksVersion&) const; | |||
| bool operator < (const BlocksVersion&) const; | |||
| bool operator > (const BlocksVersion&) const; | |||
| bool operator <= (const BlocksVersion&) const; | |||
| bool operator >= (const BlocksVersion&) const; | |||
| private: | |||
| /** @internal */ | |||
| bool evaluate (const juce::String& versionString); | |||
| bool releaseTypeGreaterThan (const BlocksVersion& otherReleaseType) const; | |||
| bool isGreaterThan (const BlocksVersion& other) const; | |||
| bool isEqualTo (const BlocksVersion& other) const; | |||
| }; | |||
| } // namespace juce | |||
| @@ -28,6 +28,8 @@ | |||
| #endif | |||
| #else | |||
| #include <regex> | |||
| namespace juce | |||
| { | |||
| #include "littlefoot/juce_LittleFootRemoteHeap.h" | |||
| @@ -41,6 +43,7 @@ namespace juce | |||
| #include "blocks/juce_BlockConfigManager.h" | |||
| #include "blocks/juce_Block.cpp" | |||
| #include "blocks/juce_BlocksVersion.cpp" | |||
| #include "topology/juce_PhysicalTopologySource.cpp" | |||
| #include "topology/juce_RuleBasedTopologySource.cpp" | |||
| #include "visualisers/juce_DrumPadLEDProgram.cpp" | |||
| @@ -73,6 +73,7 @@ namespace juce | |||
| #include "blocks/juce_ControlButton.h" | |||
| #include "blocks/juce_TouchList.h" | |||
| #include "blocks/juce_StatusLight.h" | |||
| #include "blocks/juce_BlocksVersion.h" | |||
| #include "topology/juce_Topology.h" | |||
| #include "topology/juce_TopologySource.h" | |||
| #include "topology/juce_PhysicalTopologySource.h" | |||
| @@ -154,41 +154,44 @@ struct BlockSerialNumber | |||
| }; | |||
| //============================================================================== | |||
| /** Structure for the version number | |||
| /** Structure for generic block data | |||
| @tags{Blocks} | |||
| */ | |||
| struct VersionNumber | |||
| */ | |||
| template <size_t MaxSize> | |||
| struct BlockStringData | |||
| { | |||
| uint8 version[21] = {}; | |||
| uint8 data[MaxSize] = {}; | |||
| uint8 length = 0; | |||
| juce::String asString() const | |||
| static const size_t maxLength { MaxSize }; | |||
| bool isNotEmpty() const | |||
| { | |||
| return juce::String (reinterpret_cast<const char*> (version), | |||
| std::min (sizeof (version), static_cast<size_t> (length))); | |||
| return length > 0; | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| /** Structure for the block name | |||
| bool operator== (const BlockStringData& other) const | |||
| { | |||
| if (length != other.length) | |||
| return false; | |||
| @tags{Blocks} | |||
| */ | |||
| struct BlockName | |||
| { | |||
| uint8 name[33] = {}; | |||
| uint8 length = 0; | |||
| for (int i = 0; i < length; ++i) | |||
| if (data[i] != other.data[i]) | |||
| return false; | |||
| bool isValid() const { return length > 0; } | |||
| return true; | |||
| } | |||
| juce::String asString() const | |||
| bool operator!= (const BlockStringData& other) const | |||
| { | |||
| return juce::String (reinterpret_cast<const char*> (name), | |||
| std::min (sizeof (name), static_cast<size_t> (length))); | |||
| return ! ( *this == other ); | |||
| } | |||
| }; | |||
| using VersionNumber = BlockStringData<21>; | |||
| using BlockName = BlockStringData<33>; | |||
| //============================================================================== | |||
| /** Structure for the device status | |||
| @@ -194,7 +194,7 @@ struct HostPacketDecoder | |||
| version.version.length = (uint8) reader.readBits (7); | |||
| for (uint32 i = 0; i < version.version.length; ++i) | |||
| version.version.version[i] = (uint8) reader.readBits (7); | |||
| version.version.data[i] = (uint8) reader.readBits (7); | |||
| handler.handleVersion (version); | |||
| return true; | |||
| @@ -208,7 +208,7 @@ struct HostPacketDecoder | |||
| name.name.length = (uint8) reader.readBits (7); | |||
| for (uint32 i = 0; i < name.name.length; ++i) | |||
| name.name.name[i] = (uint8) reader.readBits (7); | |||
| name.name.data[i] = (uint8) reader.readBits (7); | |||
| handler.handleName (name); | |||
| return true; | |||
| @@ -40,8 +40,8 @@ public: | |||
| BlocksProtocol::BlockName blockName, | |||
| bool isMasterBlock) | |||
| : Block (juce::String ((const char*) serial.serial, sizeof (serial.serial)), | |||
| juce::String ((const char*) version.version, version.length), | |||
| juce::String ((const char*) blockName.name, blockName.length)), | |||
| juce::String ((const char*) version.data, version.length), | |||
| juce::String ((const char*) blockName.data, blockName.length)), | |||
| modelData (serial), | |||
| remoteHeap (modelData.programAndHeapSize), | |||
| detector (&detectorToUse), | |||
| @@ -81,8 +81,8 @@ public: | |||
| void markReconnected (const DeviceInfo& deviceInfo) | |||
| { | |||
| versionNumber = deviceInfo.version.asString(); | |||
| name = deviceInfo.name.asString(); | |||
| versionNumber = asString (deviceInfo.version); | |||
| name = asString (deviceInfo.name); | |||
| isMaster = deviceInfo.isMaster; | |||
| setProgram (nullptr); | |||
| @@ -29,6 +29,55 @@ namespace | |||
| { | |||
| return static_cast<Block::Timestamp> (timestamp); | |||
| } | |||
| template <typename V> | |||
| static int removeUnusedBlocksFromMap (std::map<Block::UID, V>& mapToClean, const juce::Array<DeviceInfo>& currentDevices) | |||
| { | |||
| int numRemoved = 0; | |||
| for (auto iter = mapToClean.begin(); iter != mapToClean.end();) | |||
| { | |||
| bool found = false; | |||
| for (auto& info : currentDevices) | |||
| { | |||
| if (info.uid == iter->first) | |||
| { | |||
| found = true; | |||
| break; | |||
| } | |||
| } | |||
| if (found) | |||
| { | |||
| ++iter; | |||
| } | |||
| else | |||
| { | |||
| mapToClean.erase (iter++); | |||
| ++numRemoved; | |||
| } | |||
| } | |||
| return numRemoved; | |||
| } | |||
| /* Returns true new element added to map or value of existing key changed */ | |||
| template <typename V> | |||
| static bool insertOrAssign (std::map<Block::UID, V>& map, const Block::UID& key, const V& value) | |||
| { | |||
| const auto result = map.emplace (std::make_pair (key, value)); | |||
| if (! result.second) | |||
| { | |||
| if (result.first->second == value) | |||
| return false; | |||
| result.first->second = value; | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| template <typename Detector> | |||
| @@ -44,14 +93,16 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
| this->handleIncomingMessage (data, dataSize); | |||
| }; | |||
| if (auto midiDeviceConnection = static_cast<MIDIDeviceConnection*> (deviceConnection.get())) | |||
| depreciatedVersionReader = std::make_unique<DepreciatedVersionReader> (*midiDeviceConnection); | |||
| startTimer (200); | |||
| sendTopologyRequest(); | |||
| } | |||
| bool isStillConnected (const juce::StringArray& detectedDevices) const noexcept | |||
| { | |||
| return detectedDevices.contains (deviceName) | |||
| && ! failedToGetTopology(); | |||
| return detectedDevices.contains (deviceName) && ! failedToGetTopology(); | |||
| } | |||
| int getIndexFromDeviceID (Block::UID uid) const noexcept | |||
| @@ -63,15 +114,6 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
| 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) | |||
| @@ -114,37 +156,53 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
| void endTopology() | |||
| { | |||
| currentDeviceInfo = getArrayOfDeviceInfo (incomingTopologyDevices); | |||
| currentDeviceConnections = getArrayOfConnections (incomingTopologyConnections); | |||
| currentTopologyDevices = incomingTopologyDevices; | |||
| lastTopologyReceiveTime = juce::Time::getCurrentTime(); | |||
| const int numRemoved = blockPings.removeIf ([this] (auto& ping) | |||
| { | |||
| for (auto& info : currentDeviceInfo) | |||
| if (info.uid == ping.blockUID) | |||
| return false; | |||
| if (incomingTopologyDevices.isEmpty()) | |||
| { | |||
| jassertfalse; | |||
| return; | |||
| } | |||
| LOG_CONNECTIVITY ("API Disconnected by topology update " << ping.blockUID); | |||
| return true; | |||
| }); | |||
| currentTopologyDevices.swapWith (incomingTopologyDevices); | |||
| currentTopologyConnections.swapWith (incomingTopologyConnections); | |||
| if (numRemoved > 0) | |||
| detector.handleTopologyChange(); | |||
| incomingTopologyDevices.clearQuick(); | |||
| incomingTopologyConnections.clearQuick(); | |||
| refreshCurrentDeviceInfo(); | |||
| refreshCurrentDeviceConnections(); | |||
| removeUnusedBlocksFromMap (versionNumbers, currentDeviceInfo); | |||
| removeUnusedBlocksFromMap (blockNames, currentDeviceInfo); | |||
| removePingForDisconnectedBlocks(); | |||
| detector.handleTopologyChange(); | |||
| } | |||
| void handleVersion (BlocksProtocol::DeviceVersion version) | |||
| { | |||
| for (auto& d : currentDeviceInfo) | |||
| if (d.index == version.index && version.version.length > 1) | |||
| d.version = version.version; | |||
| const auto uid = getDeviceIDFromIndex (version.index); | |||
| if (uid == Block::UID() || version.version.length <= 1) | |||
| return; | |||
| setVersion (uid, version.version); | |||
| } | |||
| void handleName (BlocksProtocol::DeviceName name) | |||
| { | |||
| for (auto& d : currentDeviceInfo) | |||
| if (d.index == name.index && name.name.length > 1) | |||
| d.name = name.name; | |||
| const auto uid = getDeviceIDFromIndex (name.index); | |||
| if (uid == Block::UID() || name.name.length <= 1) | |||
| return; | |||
| if (insertOrAssign (blockNames, uid, name.name)) | |||
| { | |||
| refreshCurrentDeviceInfo(); | |||
| detector.handleTopologyChange(); | |||
| } | |||
| } | |||
| void handleControlButtonUpDown (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, | |||
| @@ -299,14 +357,19 @@ private: | |||
| std::unique_ptr<PhysicalTopologySource::DeviceConnection> deviceConnection; | |||
| juce::Array<BlocksProtocol::DeviceStatus> incomingTopologyDevices, currentTopologyDevices; | |||
| juce::Array<BlocksProtocol::DeviceConnection> incomingTopologyConnections; | |||
| juce::Array<BlocksProtocol::DeviceConnection> incomingTopologyConnections, currentTopologyConnections; | |||
| juce::CriticalSection incomingPacketLock; | |||
| juce::Array<juce::MemoryBlock> incomingPackets; | |||
| std::map<Block::UID, BlocksProtocol::VersionNumber> versionNumbers; | |||
| std::map<Block::UID, BlocksProtocol::BlockName> blockNames; | |||
| std::unique_ptr<DepreciatedVersionReader> depreciatedVersionReader; | |||
| struct TouchStart { float x, y; }; | |||
| TouchList<TouchStart> touchStartPositions; | |||
| TouchList<TouchStart> touchStartPositions; | |||
| Block::UID masterBlock = 0; | |||
| //============================================================================== | |||
| @@ -338,11 +401,12 @@ private: | |||
| checkApiTimeouts (now); | |||
| startApiModeOnConnectedBlocks(); | |||
| requestMasterBlockVersionIfNeeded(); | |||
| } | |||
| bool failedToGetTopology() const noexcept | |||
| { | |||
| return numTopologyRequestsSent > 4 && lastTopologyReceiveTime == juce::Time(); | |||
| return numTopologyRequestsSent >= 4 && lastTopologyReceiveTime == juce::Time(); | |||
| } | |||
| bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const | |||
| @@ -354,11 +418,36 @@ private: | |||
| return sendMessageToDevice (p); | |||
| } | |||
| //============================================================================== | |||
| void requestMasterBlockVersionIfNeeded() | |||
| { | |||
| if (depreciatedVersionReader == nullptr) | |||
| return; | |||
| const auto masterVersion = depreciatedVersionReader->getVersionNumber(); | |||
| if (masterVersion.isNotEmpty()) | |||
| setVersion (masterBlock, masterVersion); | |||
| } | |||
| void setVersion (const Block::UID uid, const BlocksProtocol::VersionNumber versionNumber) | |||
| { | |||
| if (uid == masterBlock) | |||
| depreciatedVersionReader.reset(); | |||
| if (insertOrAssign (versionNumbers, uid, versionNumber)) | |||
| { | |||
| refreshCurrentDeviceInfo(); | |||
| detector.handleTopologyChange(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| struct BlockPingTime | |||
| { | |||
| Block::UID blockUID; | |||
| juce::Time lastPing; | |||
| juce::Time connected; | |||
| }; | |||
| juce::Array<BlockPingTime> blockPings; | |||
| @@ -375,7 +464,7 @@ private: | |||
| else | |||
| { | |||
| LOG_CONNECTIVITY ("API Connected " << uid); | |||
| blockPings.add ({ uid, now }); | |||
| blockPings.add ({ uid, now, now }); | |||
| detector.handleTopologyChange(); | |||
| } | |||
| } | |||
| @@ -408,6 +497,8 @@ private: | |||
| LOG_CONNECTIVITY ("API Disconnected " << uid << ", re-probing topology"); | |||
| currentDeviceInfo.clearQuick(); | |||
| blockPings.clearQuick(); | |||
| blockNames.clear(); | |||
| versionNumbers.clear(); | |||
| detector.handleTopologyChange(); | |||
| scheduleNewTopologyRequest(); | |||
| } | |||
| @@ -432,6 +523,22 @@ private: | |||
| } | |||
| } | |||
| /* Returns true is ping was removed */ | |||
| void removePingForDisconnectedBlocks() | |||
| { | |||
| const auto removed = [this] (auto& ping) | |||
| { | |||
| for (auto& info : currentDeviceInfo) | |||
| if (info.uid == ping.blockUID) | |||
| return false; | |||
| LOG_CONNECTIVITY ("API Disconnected by topology update " << ping.blockUID); | |||
| return true; | |||
| }; | |||
| blockPings.removeIf (removed); | |||
| } | |||
| void startApiModeOnConnectedBlocks() | |||
| { | |||
| for (auto& info : currentDeviceInfo) | |||
| @@ -445,6 +552,21 @@ private: | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void checkVersionNumberTimeouts() | |||
| { | |||
| for (const auto& device : currentDeviceInfo) | |||
| { | |||
| const auto version = versionNumbers.find (device.uid); | |||
| if (version == versionNumbers.end()) | |||
| { | |||
| auto* ping = getPing (device.uid); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| Block::UID getDeviceIDFromIndex (BlocksProtocol::TopologyIndex index) const noexcept | |||
| { | |||
| @@ -463,38 +585,7 @@ private: | |||
| if (uid == Block::UID()) | |||
| scheduleNewTopologyRequest(); | |||
| 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); | |||
| if (dc.device1 <= 0 || dc.device2 <= 0) | |||
| continue; | |||
| 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 }; | |||
| return uid; | |||
| } | |||
| //============================================================================== | |||
| @@ -534,30 +625,79 @@ private: | |||
| } | |||
| //============================================================================== | |||
| juce::Array<DeviceInfo> getArrayOfDeviceInfo (const juce::Array<BlocksProtocol::DeviceStatus>& devices) | |||
| BlocksProtocol::VersionNumber getVersionNumber (Block::UID uid) | |||
| { | |||
| juce::Array<DeviceInfo> result; | |||
| const auto version = versionNumbers.find (uid); | |||
| return version == versionNumbers.end() ? BlocksProtocol::VersionNumber() : version->second; | |||
| } | |||
| BlocksProtocol::BlockName getName (Block::UID uid) | |||
| { | |||
| const auto name = blockNames.find (uid); | |||
| return name == blockNames.end() ? BlocksProtocol::BlockName() : name->second; | |||
| } | |||
| for (auto& device : devices) | |||
| //============================================================================== | |||
| const DeviceInfo* getDeviceInfoFromUID (Block::UID uid) const noexcept | |||
| { | |||
| for (auto& d : currentDeviceInfo) | |||
| if (d.uid == uid) | |||
| return &d; | |||
| return nullptr; | |||
| } | |||
| 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 refreshCurrentDeviceInfo() | |||
| { | |||
| currentDeviceInfo.clearQuick(); | |||
| for (auto& device : currentTopologyDevices) | |||
| { | |||
| BlocksProtocol::VersionNumber version; | |||
| BlocksProtocol::BlockName name; | |||
| const auto uid = getBlockUIDFromSerialNumber (device.serialNumber); | |||
| // For backwards compatibility we assume the first device we see in a group is the master and won't change | |||
| if (masterBlock == 0) | |||
| const auto uid = getBlockUIDFromSerialNumber (device.serialNumber); | |||
| const auto version = getVersionNumber (uid); | |||
| const auto name = getName (uid); | |||
| // For backwards compatibility we assume the first device we see in a group is the master and won't change | |||
| if (masterBlock == 0) | |||
| masterBlock = uid; | |||
| result.add ({ uid, | |||
| device.index, | |||
| device.serialNumber, | |||
| version, | |||
| name, | |||
| masterBlock == uid }); | |||
| currentDeviceInfo.add ({ uid, | |||
| device.index, | |||
| device.serialNumber, | |||
| version, | |||
| name, | |||
| masterBlock == uid }); | |||
| } | |||
| } | |||
| return result; | |||
| void refreshCurrentDeviceConnections() | |||
| { | |||
| currentDeviceConnections.clearQuick(); | |||
| for (auto&& c : currentTopologyConnections) | |||
| { | |||
| BlockDeviceConnection dc; | |||
| dc.device1 = getDeviceIDFromIndex (c.device1); | |||
| dc.device2 = getDeviceIDFromIndex (c.device2); | |||
| if (dc.device1 <= 0 || dc.device2 <= 0) | |||
| continue; | |||
| dc.connectionPortOnDevice1 = convertConnectionPort (dc.device1, c.port1); | |||
| dc.connectionPortOnDevice2 = convertConnectionPort (dc.device2, c.port2); | |||
| currentDeviceConnections.add (dc); | |||
| } | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectedDeviceGroup) | |||
| @@ -0,0 +1,128 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| { | |||
| /** | |||
| Firmware below 0.2.5 does not report its version over the Blocks API. | |||
| This class can make requests and process responses to retreive the master Block version. | |||
| */ | |||
| class DepreciatedVersionReader : private MIDIDeviceConnection::Listener, | |||
| private Timer | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| DepreciatedVersionReader (MIDIDeviceConnection& deviceConnectionToUse) | |||
| : deviceConnection (deviceConnectionToUse) | |||
| { | |||
| deviceConnection.addListener (this); | |||
| startTimer (10); | |||
| } | |||
| //============================================================================== | |||
| ~DepreciatedVersionReader() | |||
| { | |||
| deviceConnection.removeListener (this); | |||
| } | |||
| //============================================================================== | |||
| BlocksProtocol::VersionNumber getVersionNumber() | |||
| { | |||
| if (! allRequestsComplete()) | |||
| return {}; | |||
| auto highestVersion = result[0]; | |||
| for (size_t i = 1; i < numFirmwareApps; ++i) | |||
| { | |||
| const BlocksVersion highest { asString (highestVersion) }; | |||
| const BlocksVersion test { asString ( result[i]) }; | |||
| if (highest < test) | |||
| highestVersion = result[i]; | |||
| } | |||
| return highestVersion; | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| static constexpr size_t numFirmwareApps = 3; | |||
| BlocksProtocol::VersionNumber result[numFirmwareApps]; | |||
| MIDIDeviceConnection& deviceConnection; | |||
| size_t currentRequest = 0; | |||
| //============================================================================== | |||
| bool allRequestsComplete() const { return currentRequest >= numFirmwareApps; } | |||
| //============================================================================== | |||
| void makeNextRequest() | |||
| { | |||
| static constexpr size_t requestSize { 8 }; | |||
| static constexpr uint8 requests[][requestSize] = {{ 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x00, 0xf7 }, // Main App | |||
| { 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x01, 0xf7 }, // Stm32 | |||
| { 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x03, 0xf7 }}; // Bootloader | |||
| deviceConnection.sendMessageToDevice (&requests[currentRequest][0], requestSize); | |||
| } | |||
| //============================================================================== | |||
| void processVersionMessage (const uint8* data, const size_t size) | |||
| { | |||
| if (currentRequest >= numFirmwareApps || size < 1 || size - 1 > VersionNumber::maxLength) | |||
| return; | |||
| result[currentRequest].length = uint8 (size - 1); | |||
| memcpy (result[currentRequest].data, data, result[currentRequest].length); | |||
| ++currentRequest; | |||
| allRequestsComplete() ? stopTimer() : startTimer (10); | |||
| } | |||
| //============================================================================== | |||
| void handleIncomingMidiMessage (const MidiMessage& message) override | |||
| { | |||
| const uint8 roliVersionHeader[] = { 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03 }; | |||
| const auto headerSize = sizeof (roliVersionHeader); | |||
| const auto data = message.getRawData(); | |||
| const auto size = size_t (message.getRawDataSize()); | |||
| if (memcmp (data, roliVersionHeader, headerSize) == 0) | |||
| processVersionMessage (data + headerSize, size - headerSize); | |||
| } | |||
| void connectionBeingDeleted (const MIDIDeviceConnection&) override {} | |||
| //============================================================================== | |||
| void timerCallback() override | |||
| { | |||
| startTimer (200); | |||
| makeNextRequest(); | |||
| } | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DepreciatedVersionReader) | |||
| }; | |||
| } // namespace juce | |||
| @@ -36,20 +36,20 @@ namespace | |||
| static bool versionNumberChanged (const DeviceInfo& device, juce::String version) noexcept | |||
| { | |||
| auto deviceVersion = device.version.asString(); | |||
| auto deviceVersion = asString (device.version); | |||
| return deviceVersion != version && deviceVersion.isNotEmpty(); | |||
| } | |||
| static void setVersionNumberForBlock (const DeviceInfo& deviceInfo, Block& block) noexcept | |||
| { | |||
| jassert (deviceInfo.uid == block.uid); | |||
| block.versionNumber = deviceInfo.version.asString(); | |||
| block.versionNumber = asString (deviceInfo.version); | |||
| } | |||
| static void setNameForBlock (const DeviceInfo& deviceInfo, Block& block) | |||
| { | |||
| jassert (deviceInfo.uid == block.uid); | |||
| block.name = deviceInfo.name.asString(); | |||
| block.name = asString (deviceInfo.name); | |||
| } | |||
| } | |||
| @@ -465,7 +465,7 @@ private: | |||
| if (versionNumberChanged (updatedInfo, blockToUpdate->versionNumber)) | |||
| setVersionNumberForBlock (updatedInfo, *blockToUpdate); | |||
| if (updatedInfo.name.isValid()) | |||
| if (updatedInfo.name.isNotEmpty()) | |||
| setNameForBlock (updatedInfo, *blockToUpdate); | |||
| if (updatedInfo.isMaster != blockToUpdate->isMasterBlock()) | |||
| @@ -692,6 +692,7 @@ private: | |||
| } | |||
| } | |||
| //============================================================================== | |||
| JUCE_DECLARE_WEAK_REFERENCEABLE (Detector) | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Detector) | |||
| }; | |||
| @@ -76,7 +76,7 @@ struct MIDIDeviceConnection : public PhysicalTopologySource::DeviceConnection, | |||
| 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 (memcmp (data, BlocksProtocol::roliSysexHeader, sizeof (BlocksProtocol::roliSysexHeader) - 1) == 0); | |||
| jassert (static_cast<const uint8*> (data)[dataSize - 1] == 0xf7); | |||
| if (midiOutput != nullptr) | |||
| @@ -25,6 +25,7 @@ | |||
| #define LOG_BLOCKS_CONNECTIVITY 0 | |||
| #define LOG_BLOCKS_PINGS 0 | |||
| #define DUMP_BANDWIDTH_STATS 0 | |||
| #define DUMP_TOPOLOGY 0 | |||
| #define TOPOLOGY_LOG(text) \ | |||
| JUCE_BLOCK_WITH_FORCED_SEMICOLON (juce::String buf ("Topology Src: "); \ | |||
| @@ -46,9 +47,21 @@ | |||
| #include "internal/juce_BandwidthStatsLogger.cpp" | |||
| #endif | |||
| namespace | |||
| { | |||
| /** Helper function to create juce::String from BlockStringData */ | |||
| template <size_t MaxSize> | |||
| juce::String asString (juce::BlocksProtocol::BlockStringData<MaxSize> blockString) | |||
| { | |||
| return { reinterpret_cast<const char*> (blockString.data), | |||
| juce::jmin (sizeof (blockString.data), static_cast<size_t> (blockString.length))}; | |||
| } | |||
| } | |||
| #include "internal/juce_MidiDeviceConnection.cpp" | |||
| #include "internal/juce_MIDIDeviceDetector.cpp" | |||
| #include "internal/juce_DeviceInfo.cpp" | |||
| #include "internal/juce_DepreciatedVersionReader.cpp" | |||
| #include "internal/juce_ConnectedDeviceGroup.cpp" | |||
| #include "internal/juce_BlockImplementation.cpp" | |||
| #include "internal/juce_Detector.cpp" | |||