| @@ -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 | #endif | ||||
| #else | #else | ||||
| #include <regex> | |||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| #include "littlefoot/juce_LittleFootRemoteHeap.h" | #include "littlefoot/juce_LittleFootRemoteHeap.h" | ||||
| @@ -41,6 +43,7 @@ namespace juce | |||||
| #include "blocks/juce_BlockConfigManager.h" | #include "blocks/juce_BlockConfigManager.h" | ||||
| #include "blocks/juce_Block.cpp" | #include "blocks/juce_Block.cpp" | ||||
| #include "blocks/juce_BlocksVersion.cpp" | |||||
| #include "topology/juce_PhysicalTopologySource.cpp" | #include "topology/juce_PhysicalTopologySource.cpp" | ||||
| #include "topology/juce_RuleBasedTopologySource.cpp" | #include "topology/juce_RuleBasedTopologySource.cpp" | ||||
| #include "visualisers/juce_DrumPadLEDProgram.cpp" | #include "visualisers/juce_DrumPadLEDProgram.cpp" | ||||
| @@ -73,6 +73,7 @@ namespace juce | |||||
| #include "blocks/juce_ControlButton.h" | #include "blocks/juce_ControlButton.h" | ||||
| #include "blocks/juce_TouchList.h" | #include "blocks/juce_TouchList.h" | ||||
| #include "blocks/juce_StatusLight.h" | #include "blocks/juce_StatusLight.h" | ||||
| #include "blocks/juce_BlocksVersion.h" | |||||
| #include "topology/juce_Topology.h" | #include "topology/juce_Topology.h" | ||||
| #include "topology/juce_TopologySource.h" | #include "topology/juce_TopologySource.h" | ||||
| #include "topology/juce_PhysicalTopologySource.h" | #include "topology/juce_PhysicalTopologySource.h" | ||||
| @@ -154,41 +154,44 @@ struct BlockSerialNumber | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| /** Structure for the version number | |||||
| /** Structure for generic block data | |||||
| @tags{Blocks} | @tags{Blocks} | ||||
| */ | |||||
| struct VersionNumber | |||||
| */ | |||||
| template <size_t MaxSize> | |||||
| struct BlockStringData | |||||
| { | { | ||||
| uint8 version[21] = {}; | |||||
| uint8 data[MaxSize] = {}; | |||||
| uint8 length = 0; | 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 | /** Structure for the device status | ||||
| @@ -194,7 +194,7 @@ struct HostPacketDecoder | |||||
| version.version.length = (uint8) reader.readBits (7); | version.version.length = (uint8) reader.readBits (7); | ||||
| for (uint32 i = 0; i < version.version.length; ++i) | 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); | handler.handleVersion (version); | ||||
| return true; | return true; | ||||
| @@ -208,7 +208,7 @@ struct HostPacketDecoder | |||||
| name.name.length = (uint8) reader.readBits (7); | name.name.length = (uint8) reader.readBits (7); | ||||
| for (uint32 i = 0; i < name.name.length; ++i) | 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); | handler.handleName (name); | ||||
| return true; | return true; | ||||
| @@ -40,8 +40,8 @@ public: | |||||
| BlocksProtocol::BlockName blockName, | BlocksProtocol::BlockName blockName, | ||||
| bool isMasterBlock) | bool isMasterBlock) | ||||
| : Block (juce::String ((const char*) serial.serial, sizeof (serial.serial)), | : 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), | modelData (serial), | ||||
| remoteHeap (modelData.programAndHeapSize), | remoteHeap (modelData.programAndHeapSize), | ||||
| detector (&detectorToUse), | detector (&detectorToUse), | ||||
| @@ -81,8 +81,8 @@ public: | |||||
| void markReconnected (const DeviceInfo& deviceInfo) | void markReconnected (const DeviceInfo& deviceInfo) | ||||
| { | { | ||||
| versionNumber = deviceInfo.version.asString(); | |||||
| name = deviceInfo.name.asString(); | |||||
| versionNumber = asString (deviceInfo.version); | |||||
| name = asString (deviceInfo.name); | |||||
| isMaster = deviceInfo.isMaster; | isMaster = deviceInfo.isMaster; | ||||
| setProgram (nullptr); | setProgram (nullptr); | ||||
| @@ -29,6 +29,55 @@ namespace | |||||
| { | { | ||||
| return static_cast<Block::Timestamp> (timestamp); | 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> | template <typename Detector> | ||||
| @@ -44,14 +93,16 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||||
| this->handleIncomingMessage (data, dataSize); | this->handleIncomingMessage (data, dataSize); | ||||
| }; | }; | ||||
| if (auto midiDeviceConnection = static_cast<MIDIDeviceConnection*> (deviceConnection.get())) | |||||
| depreciatedVersionReader = std::make_unique<DepreciatedVersionReader> (*midiDeviceConnection); | |||||
| startTimer (200); | startTimer (200); | ||||
| sendTopologyRequest(); | sendTopologyRequest(); | ||||
| } | } | ||||
| bool isStillConnected (const juce::StringArray& detectedDevices) const noexcept | 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 | int getIndexFromDeviceID (Block::UID uid) const noexcept | ||||
| @@ -63,15 +114,6 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||||
| return -1; | 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 | const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept | ||||
| { | { | ||||
| for (auto&& status : currentTopologyDevices) | for (auto&& status : currentTopologyDevices) | ||||
| @@ -114,37 +156,53 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||||
| void endTopology() | void endTopology() | ||||
| { | { | ||||
| currentDeviceInfo = getArrayOfDeviceInfo (incomingTopologyDevices); | |||||
| currentDeviceConnections = getArrayOfConnections (incomingTopologyConnections); | |||||
| currentTopologyDevices = incomingTopologyDevices; | |||||
| lastTopologyReceiveTime = juce::Time::getCurrentTime(); | 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) | 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) | 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, | void handleControlButtonUpDown (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, | ||||
| @@ -299,14 +357,19 @@ private: | |||||
| std::unique_ptr<PhysicalTopologySource::DeviceConnection> deviceConnection; | std::unique_ptr<PhysicalTopologySource::DeviceConnection> deviceConnection; | ||||
| juce::Array<BlocksProtocol::DeviceStatus> incomingTopologyDevices, currentTopologyDevices; | juce::Array<BlocksProtocol::DeviceStatus> incomingTopologyDevices, currentTopologyDevices; | ||||
| juce::Array<BlocksProtocol::DeviceConnection> incomingTopologyConnections; | |||||
| juce::Array<BlocksProtocol::DeviceConnection> incomingTopologyConnections, currentTopologyConnections; | |||||
| juce::CriticalSection incomingPacketLock; | juce::CriticalSection incomingPacketLock; | ||||
| juce::Array<juce::MemoryBlock> incomingPackets; | 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; }; | struct TouchStart { float x, y; }; | ||||
| TouchList<TouchStart> touchStartPositions; | |||||
| TouchList<TouchStart> touchStartPositions; | |||||
| Block::UID masterBlock = 0; | Block::UID masterBlock = 0; | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -338,11 +401,12 @@ private: | |||||
| checkApiTimeouts (now); | checkApiTimeouts (now); | ||||
| startApiModeOnConnectedBlocks(); | startApiModeOnConnectedBlocks(); | ||||
| requestMasterBlockVersionIfNeeded(); | |||||
| } | } | ||||
| bool failedToGetTopology() const noexcept | bool failedToGetTopology() const noexcept | ||||
| { | { | ||||
| return numTopologyRequestsSent > 4 && lastTopologyReceiveTime == juce::Time(); | |||||
| return numTopologyRequestsSent >= 4 && lastTopologyReceiveTime == juce::Time(); | |||||
| } | } | ||||
| bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const | bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const | ||||
| @@ -354,11 +418,36 @@ private: | |||||
| return sendMessageToDevice (p); | 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 | struct BlockPingTime | ||||
| { | { | ||||
| Block::UID blockUID; | Block::UID blockUID; | ||||
| juce::Time lastPing; | juce::Time lastPing; | ||||
| juce::Time connected; | |||||
| }; | }; | ||||
| juce::Array<BlockPingTime> blockPings; | juce::Array<BlockPingTime> blockPings; | ||||
| @@ -375,7 +464,7 @@ private: | |||||
| else | else | ||||
| { | { | ||||
| LOG_CONNECTIVITY ("API Connected " << uid); | LOG_CONNECTIVITY ("API Connected " << uid); | ||||
| blockPings.add ({ uid, now }); | |||||
| blockPings.add ({ uid, now, now }); | |||||
| detector.handleTopologyChange(); | detector.handleTopologyChange(); | ||||
| } | } | ||||
| } | } | ||||
| @@ -408,6 +497,8 @@ private: | |||||
| LOG_CONNECTIVITY ("API Disconnected " << uid << ", re-probing topology"); | LOG_CONNECTIVITY ("API Disconnected " << uid << ", re-probing topology"); | ||||
| currentDeviceInfo.clearQuick(); | currentDeviceInfo.clearQuick(); | ||||
| blockPings.clearQuick(); | blockPings.clearQuick(); | ||||
| blockNames.clear(); | |||||
| versionNumbers.clear(); | |||||
| detector.handleTopologyChange(); | detector.handleTopologyChange(); | ||||
| scheduleNewTopologyRequest(); | 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() | void startApiModeOnConnectedBlocks() | ||||
| { | { | ||||
| for (auto& info : currentDeviceInfo) | 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 | Block::UID getDeviceIDFromIndex (BlocksProtocol::TopologyIndex index) const noexcept | ||||
| { | { | ||||
| @@ -463,38 +585,7 @@ private: | |||||
| if (uid == Block::UID()) | if (uid == Block::UID()) | ||||
| scheduleNewTopologyRequest(); | 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; | 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) | 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 | 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(); | return deviceVersion != version && deviceVersion.isNotEmpty(); | ||||
| } | } | ||||
| static void setVersionNumberForBlock (const DeviceInfo& deviceInfo, Block& block) noexcept | static void setVersionNumberForBlock (const DeviceInfo& deviceInfo, Block& block) noexcept | ||||
| { | { | ||||
| jassert (deviceInfo.uid == block.uid); | jassert (deviceInfo.uid == block.uid); | ||||
| block.versionNumber = deviceInfo.version.asString(); | |||||
| block.versionNumber = asString (deviceInfo.version); | |||||
| } | } | ||||
| static void setNameForBlock (const DeviceInfo& deviceInfo, Block& block) | static void setNameForBlock (const DeviceInfo& deviceInfo, Block& block) | ||||
| { | { | ||||
| jassert (deviceInfo.uid == block.uid); | 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)) | if (versionNumberChanged (updatedInfo, blockToUpdate->versionNumber)) | ||||
| setVersionNumberForBlock (updatedInfo, *blockToUpdate); | setVersionNumberForBlock (updatedInfo, *blockToUpdate); | ||||
| if (updatedInfo.name.isValid()) | |||||
| if (updatedInfo.name.isNotEmpty()) | |||||
| setNameForBlock (updatedInfo, *blockToUpdate); | setNameForBlock (updatedInfo, *blockToUpdate); | ||||
| if (updatedInfo.isMaster != blockToUpdate->isMasterBlock()) | if (updatedInfo.isMaster != blockToUpdate->isMasterBlock()) | ||||
| @@ -692,6 +692,7 @@ private: | |||||
| } | } | ||||
| } | } | ||||
| //============================================================================== | |||||
| JUCE_DECLARE_WEAK_REFERENCEABLE (Detector) | JUCE_DECLARE_WEAK_REFERENCEABLE (Detector) | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (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! | JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED // This method must only be called from the message thread! | ||||
| jassert (dataSize > sizeof (BlocksProtocol::roliSysexHeader) + 2); | 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); | jassert (static_cast<const uint8*> (data)[dataSize - 1] == 0xf7); | ||||
| if (midiOutput != nullptr) | if (midiOutput != nullptr) | ||||
| @@ -25,6 +25,7 @@ | |||||
| #define LOG_BLOCKS_CONNECTIVITY 0 | #define LOG_BLOCKS_CONNECTIVITY 0 | ||||
| #define LOG_BLOCKS_PINGS 0 | #define LOG_BLOCKS_PINGS 0 | ||||
| #define DUMP_BANDWIDTH_STATS 0 | #define DUMP_BANDWIDTH_STATS 0 | ||||
| #define DUMP_TOPOLOGY 0 | |||||
| #define TOPOLOGY_LOG(text) \ | #define TOPOLOGY_LOG(text) \ | ||||
| JUCE_BLOCK_WITH_FORCED_SEMICOLON (juce::String buf ("Topology Src: "); \ | JUCE_BLOCK_WITH_FORCED_SEMICOLON (juce::String buf ("Topology Src: "); \ | ||||
| @@ -46,9 +47,21 @@ | |||||
| #include "internal/juce_BandwidthStatsLogger.cpp" | #include "internal/juce_BandwidthStatsLogger.cpp" | ||||
| #endif | #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_MidiDeviceConnection.cpp" | ||||
| #include "internal/juce_MIDIDeviceDetector.cpp" | #include "internal/juce_MIDIDeviceDetector.cpp" | ||||
| #include "internal/juce_DeviceInfo.cpp" | #include "internal/juce_DeviceInfo.cpp" | ||||
| #include "internal/juce_DepreciatedVersionReader.cpp" | |||||
| #include "internal/juce_ConnectedDeviceGroup.cpp" | #include "internal/juce_ConnectedDeviceGroup.cpp" | ||||
| #include "internal/juce_BlockImplementation.cpp" | #include "internal/juce_BlockImplementation.cpp" | ||||
| #include "internal/juce_Detector.cpp" | #include "internal/juce_Detector.cpp" | ||||