The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

567 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. The code included in this file is provided under the terms of the ISC license
  8. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  9. To use, copy, modify, and/or distribute this software for any purpose with or
  10. without fee is hereby granted provided that the above copyright notice and
  11. this permission notice appear in all copies.
  12. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  13. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  14. DISCLAIMED.
  15. ==============================================================================
  16. */
  17. namespace juce
  18. {
  19. namespace
  20. {
  21. static Block::Timestamp deviceTimestampToHost (uint32 timestamp) noexcept
  22. {
  23. return static_cast<Block::Timestamp> (timestamp);
  24. }
  25. }
  26. template <typename Detector>
  27. struct ConnectedDeviceGroup : private juce::AsyncUpdater,
  28. private juce::Timer
  29. {
  30. //==============================================================================
  31. ConnectedDeviceGroup (Detector& d, const juce::String& name, PhysicalTopologySource::DeviceConnection* connection)
  32. : detector (d), deviceName (name), deviceConnection (connection)
  33. {
  34. deviceConnection->handleMessageFromDevice = [this] (const void* data, size_t dataSize)
  35. {
  36. this->handleIncomingMessage (data, dataSize);
  37. };
  38. startTimer (200);
  39. sendTopologyRequest();
  40. }
  41. bool isStillConnected (const juce::StringArray& detectedDevices) const noexcept
  42. {
  43. return detectedDevices.contains (deviceName)
  44. && ! failedToGetTopology();
  45. }
  46. int getIndexFromDeviceID (Block::UID uid) const noexcept
  47. {
  48. for (auto& d : currentDeviceInfo)
  49. if (d.uid == uid)
  50. return d.index;
  51. return -1;
  52. }
  53. const DeviceInfo* getDeviceInfoFromUID (Block::UID uid) const noexcept
  54. {
  55. for (auto& d : currentDeviceInfo)
  56. if (d.uid == uid)
  57. return &d;
  58. return nullptr;
  59. }
  60. const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept
  61. {
  62. for (auto&& status : currentTopologyDevices)
  63. if (getBlockUIDFromSerialNumber (status.serialNumber) == deviceID)
  64. return &status;
  65. return nullptr;
  66. }
  67. void notifyBlockIsRestarting (Block::UID deviceID)
  68. {
  69. forceApiDisconnected (deviceID);
  70. }
  71. //==============================================================================
  72. // The following methods will be called by the HostPacketDecoder:
  73. void beginTopology (int numDevices, int numConnections)
  74. {
  75. incomingTopologyDevices.clearQuick();
  76. incomingTopologyDevices.ensureStorageAllocated (numDevices);
  77. incomingTopologyConnections.clearQuick();
  78. incomingTopologyConnections.ensureStorageAllocated (numConnections);
  79. }
  80. void extendTopology (int numDevices, int numConnections)
  81. {
  82. incomingTopologyDevices.ensureStorageAllocated (incomingTopologyDevices.size() + numDevices);
  83. incomingTopologyConnections.ensureStorageAllocated (incomingTopologyConnections.size() + numConnections);
  84. }
  85. void handleTopologyDevice (BlocksProtocol::DeviceStatus status)
  86. {
  87. incomingTopologyDevices.add (status);
  88. }
  89. void handleTopologyConnection (BlocksProtocol::DeviceConnection connection)
  90. {
  91. incomingTopologyConnections.add (connection);
  92. }
  93. void endTopology()
  94. {
  95. currentDeviceInfo = getArrayOfDeviceInfo (incomingTopologyDevices);
  96. currentDeviceConnections = getArrayOfConnections (incomingTopologyConnections);
  97. currentTopologyDevices = incomingTopologyDevices;
  98. lastTopologyReceiveTime = juce::Time::getCurrentTime();
  99. const int numRemoved = blockPings.removeIf ([this] (auto& ping)
  100. {
  101. for (auto& info : currentDeviceInfo)
  102. if (info.uid == ping.blockUID)
  103. return false;
  104. LOG_CONNECTIVITY ("API Disconnected by topology update " << ping.blockUID);
  105. return true;
  106. });
  107. if (numRemoved > 0)
  108. detector.handleTopologyChange();
  109. }
  110. void handleVersion (BlocksProtocol::DeviceVersion version)
  111. {
  112. for (auto& d : currentDeviceInfo)
  113. if (d.index == version.index && version.version.length > 1)
  114. d.version = version.version;
  115. }
  116. void handleName (BlocksProtocol::DeviceName name)
  117. {
  118. for (auto& d : currentDeviceInfo)
  119. if (d.index == name.index && name.name.length > 1)
  120. d.name = name.name;
  121. }
  122. void handleControlButtonUpDown (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp,
  123. BlocksProtocol::ControlButtonID buttonID, bool isDown)
  124. {
  125. if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
  126. detector.handleButtonChange (deviceID, deviceTimestampToHost (timestamp), buttonID.get(), isDown);
  127. }
  128. void handleCustomMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, const int32* data)
  129. {
  130. if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
  131. detector.handleCustomMessage (deviceID, deviceTimestampToHost (timestamp), data);
  132. }
  133. void handleTouchChange (BlocksProtocol::TopologyIndex deviceIndex,
  134. uint32 timestamp,
  135. BlocksProtocol::TouchIndex touchIndex,
  136. BlocksProtocol::TouchPosition position,
  137. BlocksProtocol::TouchVelocity velocity,
  138. bool isStart, bool isEnd)
  139. {
  140. if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
  141. {
  142. TouchSurface::Touch touch;
  143. touch.index = (int) touchIndex.get();
  144. touch.x = position.x.toUnipolarFloat();
  145. touch.y = position.y.toUnipolarFloat();
  146. touch.z = position.z.toUnipolarFloat();
  147. touch.xVelocity = velocity.vx.toBipolarFloat();
  148. touch.yVelocity = velocity.vy.toBipolarFloat();
  149. touch.zVelocity = velocity.vz.toBipolarFloat();
  150. touch.eventTimestamp = deviceTimestampToHost (timestamp);
  151. touch.isTouchStart = isStart;
  152. touch.isTouchEnd = isEnd;
  153. touch.blockUID = deviceID;
  154. setTouchStartPosition (touch);
  155. detector.handleTouchChange (deviceID, touch);
  156. }
  157. }
  158. void setTouchStartPosition (TouchSurface::Touch& touch)
  159. {
  160. auto& startPos = touchStartPositions.getValue (touch);
  161. if (touch.isTouchStart)
  162. startPos = { touch.x, touch.y };
  163. touch.startX = startPos.x;
  164. touch.startY = startPos.y;
  165. }
  166. void handlePacketACK (BlocksProtocol::TopologyIndex deviceIndex,
  167. BlocksProtocol::PacketCounter counter)
  168. {
  169. if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
  170. {
  171. detector.handleSharedDataACK (deviceID, counter);
  172. updateApiPing (deviceID);
  173. }
  174. }
  175. void handleFirmwareUpdateACK (BlocksProtocol::TopologyIndex deviceIndex,
  176. BlocksProtocol::FirmwareUpdateACKCode resultCode,
  177. BlocksProtocol::FirmwareUpdateACKDetail resultDetail)
  178. {
  179. if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
  180. {
  181. detector.handleFirmwareUpdateACK (deviceID, (uint8) resultCode.get(), (uint32) resultDetail.get());
  182. updateApiPing (deviceID);
  183. }
  184. }
  185. void handleConfigUpdateMessage (BlocksProtocol::TopologyIndex deviceIndex,
  186. int32 item, int32 value, int32 min, int32 max)
  187. {
  188. if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
  189. detector.handleConfigUpdateMessage (deviceID, item, value, min, max);
  190. }
  191. void handleConfigSetMessage (BlocksProtocol::TopologyIndex deviceIndex,
  192. int32 item, int32 value)
  193. {
  194. if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
  195. detector.handleConfigSetMessage (deviceID, item, value);
  196. }
  197. void handleConfigFactorySyncEndMessage (BlocksProtocol::TopologyIndex deviceIndex)
  198. {
  199. if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
  200. detector.handleConfigFactorySyncEndMessage (deviceID);
  201. }
  202. void handleConfigFactorySyncResetMessage (BlocksProtocol::TopologyIndex deviceIndex)
  203. {
  204. if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
  205. detector.handleConfigFactorySyncResetMessage (deviceID);
  206. }
  207. void handleLogMessage (BlocksProtocol::TopologyIndex deviceIndex, const String& message)
  208. {
  209. if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
  210. detector.handleLogMessage (deviceID, message);
  211. }
  212. //==============================================================================
  213. template <typename PacketBuilder>
  214. bool sendMessageToDevice (const PacketBuilder& builder) const
  215. {
  216. if (deviceConnection->sendMessageToDevice (builder.getData(), (size_t) builder.size()))
  217. {
  218. #if DUMP_BANDWIDTH_STATS
  219. registerBytesOut (builder.size());
  220. #endif
  221. return true;
  222. }
  223. return false;
  224. }
  225. PhysicalTopologySource::DeviceConnection* getDeviceConnection()
  226. {
  227. return deviceConnection.get();
  228. }
  229. juce::Array<DeviceInfo> getCurrentDeviceInfo()
  230. {
  231. auto blocks = currentDeviceInfo;
  232. blocks.removeIf ([this] (DeviceInfo& info) { return ! isApiConnected (info.uid); });
  233. return blocks;
  234. }
  235. juce::Array<BlockDeviceConnection> getCurrentDeviceConnections()
  236. {
  237. auto connections = currentDeviceConnections;
  238. connections.removeIf ([this] (BlockDeviceConnection& c) { return ! isApiConnected (c.device1) || ! isApiConnected (c.device2); });
  239. return connections;
  240. }
  241. Detector& detector;
  242. juce::String deviceName;
  243. static constexpr double pingTimeoutSeconds = 6.0;
  244. private:
  245. //==============================================================================
  246. juce::Array<DeviceInfo> currentDeviceInfo;
  247. juce::Array<BlockDeviceConnection> currentDeviceConnections;
  248. std::unique_ptr<PhysicalTopologySource::DeviceConnection> deviceConnection;
  249. juce::Array<BlocksProtocol::DeviceStatus> incomingTopologyDevices, currentTopologyDevices;
  250. juce::Array<BlocksProtocol::DeviceConnection> incomingTopologyConnections;
  251. juce::CriticalSection incomingPacketLock;
  252. juce::Array<juce::MemoryBlock> incomingPackets;
  253. struct TouchStart { float x, y; };
  254. TouchList<TouchStart> touchStartPositions;
  255. Block::UID masterBlock = 0;
  256. //==============================================================================
  257. juce::Time lastTopologyRequestTime, lastTopologyReceiveTime;
  258. int numTopologyRequestsSent = 0;
  259. void scheduleNewTopologyRequest()
  260. {
  261. numTopologyRequestsSent = 0;
  262. lastTopologyReceiveTime = juce::Time();
  263. lastTopologyRequestTime = juce::Time::getCurrentTime();
  264. }
  265. void sendTopologyRequest()
  266. {
  267. ++numTopologyRequestsSent;
  268. lastTopologyRequestTime = juce::Time::getCurrentTime();
  269. sendCommandMessage (0, BlocksProtocol::requestTopologyMessage);
  270. }
  271. void timerCallback() override
  272. {
  273. const auto now = juce::Time::getCurrentTime();
  274. if ((now > lastTopologyReceiveTime + juce::RelativeTime::seconds (30.0))
  275. && now > lastTopologyRequestTime + juce::RelativeTime::seconds (1.0)
  276. && numTopologyRequestsSent < 4)
  277. sendTopologyRequest();
  278. checkApiTimeouts (now);
  279. startApiModeOnConnectedBlocks();
  280. }
  281. bool failedToGetTopology() const noexcept
  282. {
  283. return numTopologyRequestsSent > 4 && lastTopologyReceiveTime == juce::Time();
  284. }
  285. bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const
  286. {
  287. BlocksProtocol::HostPacketBuilder<64> p;
  288. p.writePacketSysexHeaderBytes (deviceIndex);
  289. p.deviceControlMessage (commandID);
  290. p.writePacketSysexFooter();
  291. return sendMessageToDevice (p);
  292. }
  293. //==============================================================================
  294. struct BlockPingTime
  295. {
  296. Block::UID blockUID;
  297. juce::Time lastPing;
  298. };
  299. juce::Array<BlockPingTime> blockPings;
  300. void updateApiPing (Block::UID uid)
  301. {
  302. const auto now = juce::Time::getCurrentTime();
  303. if (auto* ping = getPing (uid))
  304. {
  305. LOG_PING ("Ping: " << uid << " " << now.formatted ("%Mm %Ss"));
  306. ping->lastPing = now;
  307. }
  308. else
  309. {
  310. LOG_CONNECTIVITY ("API Connected " << uid);
  311. blockPings.add ({ uid, now });
  312. detector.handleTopologyChange();
  313. }
  314. }
  315. BlockPingTime* getPing (Block::UID uid)
  316. {
  317. for (auto& ping : blockPings)
  318. if (uid == ping.blockUID)
  319. return &ping;
  320. return nullptr;
  321. }
  322. void removeDeviceInfo (Block::UID uid)
  323. {
  324. currentDeviceInfo.removeIf ([uid] (DeviceInfo& info) { return uid == info.uid; });
  325. }
  326. bool isApiConnected (Block::UID uid)
  327. {
  328. return getPing (uid) != nullptr;
  329. }
  330. void forceApiDisconnected (Block::UID uid)
  331. {
  332. if (isApiConnected (uid))
  333. {
  334. // Clear all known API connections and broadcast an empty topology,
  335. // as DNA blocks connected to the restarting block may be offline.
  336. LOG_CONNECTIVITY ("API Disconnected " << uid << ", re-probing topology");
  337. currentDeviceInfo.clearQuick();
  338. blockPings.clearQuick();
  339. detector.handleTopologyChange();
  340. scheduleNewTopologyRequest();
  341. }
  342. }
  343. void checkApiTimeouts (juce::Time now)
  344. {
  345. const auto timedOut = [this, now] (BlockPingTime& ping)
  346. {
  347. if (ping.lastPing >= now - juce::RelativeTime::seconds (pingTimeoutSeconds))
  348. return false;
  349. LOG_CONNECTIVITY ("Ping timeout: " << ping.blockUID);
  350. removeDeviceInfo (ping.blockUID);
  351. return true;
  352. };
  353. if (blockPings.removeIf (timedOut) > 0)
  354. {
  355. scheduleNewTopologyRequest();
  356. detector.handleTopologyChange();
  357. }
  358. }
  359. void startApiModeOnConnectedBlocks()
  360. {
  361. for (auto& info : currentDeviceInfo)
  362. {
  363. if (! isApiConnected (info.uid))
  364. {
  365. LOG_CONNECTIVITY ("API Try " << info.uid);
  366. sendCommandMessage (info.index, BlocksProtocol::endAPIMode);
  367. sendCommandMessage (info.index, BlocksProtocol::beginAPIMode);
  368. }
  369. }
  370. }
  371. //==============================================================================
  372. Block::UID getDeviceIDFromIndex (BlocksProtocol::TopologyIndex index) const noexcept
  373. {
  374. for (auto& d : currentDeviceInfo)
  375. if (d.index == index)
  376. return d.uid;
  377. return {};
  378. }
  379. Block::UID getDeviceIDFromMessageIndex (BlocksProtocol::TopologyIndex index) noexcept
  380. {
  381. const auto uid = getDeviceIDFromIndex (index);
  382. // re-request topology if we get an event from an unknown block
  383. if (uid == Block::UID())
  384. scheduleNewTopologyRequest();
  385. return uid;
  386. }
  387. juce::Array<BlockDeviceConnection> getArrayOfConnections (const juce::Array<BlocksProtocol::DeviceConnection>& connections)
  388. {
  389. juce::Array<BlockDeviceConnection> result;
  390. for (auto&& c : connections)
  391. {
  392. BlockDeviceConnection dc;
  393. dc.device1 = getDeviceIDFromIndex (c.device1);
  394. dc.device2 = getDeviceIDFromIndex (c.device2);
  395. if (dc.device1 <= 0 || dc.device2 <= 0)
  396. continue;
  397. dc.connectionPortOnDevice1 = convertConnectionPort (dc.device1, c.port1);
  398. dc.connectionPortOnDevice2 = convertConnectionPort (dc.device2, c.port2);
  399. result.add (dc);
  400. }
  401. return result;
  402. }
  403. Block::ConnectionPort convertConnectionPort (Block::UID uid, BlocksProtocol::ConnectorPort p) noexcept
  404. {
  405. if (auto* info = getDeviceInfoFromUID (uid))
  406. return BlocksProtocol::BlockDataSheet (info->serial).convertPortIndexToConnectorPort (p);
  407. jassertfalse;
  408. return { Block::ConnectionPort::DeviceEdge::north, 0 };
  409. }
  410. //==============================================================================
  411. void handleIncomingMessage (const void* data, size_t dataSize)
  412. {
  413. juce::MemoryBlock mb (data, dataSize);
  414. {
  415. const juce::ScopedLock sl (incomingPacketLock);
  416. incomingPackets.add (std::move (mb));
  417. }
  418. triggerAsyncUpdate();
  419. #if DUMP_BANDWIDTH_STATS
  420. registerBytesIn ((int) dataSize);
  421. #endif
  422. }
  423. void handleAsyncUpdate() override
  424. {
  425. juce::Array<juce::MemoryBlock> packets;
  426. packets.ensureStorageAllocated (32);
  427. {
  428. const juce::ScopedLock sl (incomingPacketLock);
  429. incomingPackets.swapWith (packets);
  430. }
  431. for (auto& packet : packets)
  432. {
  433. auto data = static_cast<const uint8*> (packet.getData());
  434. BlocksProtocol::HostPacketDecoder<ConnectedDeviceGroup>
  435. ::processNextPacket (*this, *data, data + 1, (int) packet.getSize() - 1);
  436. }
  437. }
  438. //==============================================================================
  439. juce::Array<DeviceInfo> getArrayOfDeviceInfo (const juce::Array<BlocksProtocol::DeviceStatus>& devices)
  440. {
  441. juce::Array<DeviceInfo> result;
  442. for (auto& device : devices)
  443. {
  444. BlocksProtocol::VersionNumber version;
  445. BlocksProtocol::BlockName name;
  446. const auto uid = getBlockUIDFromSerialNumber (device.serialNumber);
  447. // For backwards compatibility we assume the first device we see in a group is the master and won't change
  448. if (masterBlock == 0)
  449. masterBlock = uid;
  450. result.add ({ uid,
  451. device.index,
  452. device.serialNumber,
  453. version,
  454. name,
  455. masterBlock == uid });
  456. }
  457. return result;
  458. }
  459. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectedDeviceGroup)
  460. };
  461. } // namespace juce