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.

701 lines
24KB

  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 bool containsBlockWithUID (const Block::Array& blocks, Block::UID uid) noexcept
  22. {
  23. for (auto&& block : blocks)
  24. if (block->uid == uid)
  25. return true;
  26. return false;
  27. }
  28. static bool versionNumberChanged (const DeviceInfo& device, juce::String version) noexcept
  29. {
  30. auto deviceVersion = asString (device.version);
  31. return deviceVersion != version && deviceVersion.isNotEmpty();
  32. }
  33. static void setVersionNumberForBlock (const DeviceInfo& deviceInfo, Block& block) noexcept
  34. {
  35. jassert (deviceInfo.uid == block.uid);
  36. block.versionNumber = asString (deviceInfo.version);
  37. }
  38. static void setNameForBlock (const DeviceInfo& deviceInfo, Block& block)
  39. {
  40. jassert (deviceInfo.uid == block.uid);
  41. block.name = asString (deviceInfo.name);
  42. }
  43. }
  44. //==============================================================================
  45. /** This is the main singleton object that keeps track of connected blocks */
  46. struct Detector : public juce::ReferenceCountedObject,
  47. private juce::Timer
  48. {
  49. using BlockImpl = BlockImplementation<Detector>;
  50. Detector() : defaultDetector (new MIDIDeviceDetector()), deviceDetector (*defaultDetector)
  51. {
  52. startTimer (10);
  53. }
  54. Detector (PhysicalTopologySource::DeviceDetector& dd) : deviceDetector (dd)
  55. {
  56. startTimer (10);
  57. }
  58. ~Detector()
  59. {
  60. jassert (activeTopologySources.isEmpty());
  61. }
  62. using Ptr = juce::ReferenceCountedObjectPtr<Detector>;
  63. static Detector::Ptr getDefaultDetector()
  64. {
  65. auto& d = getDefaultDetectorPointer();
  66. if (d == nullptr)
  67. d = new Detector();
  68. return d;
  69. }
  70. static Detector::Ptr& getDefaultDetectorPointer()
  71. {
  72. static Detector::Ptr defaultDetector;
  73. return defaultDetector;
  74. }
  75. void detach (PhysicalTopologySource* pts)
  76. {
  77. activeTopologySources.removeAllInstancesOf (pts);
  78. if (activeTopologySources.isEmpty())
  79. {
  80. for (auto& b : currentTopology.blocks)
  81. if (auto bi = BlockImpl::getFrom (*b))
  82. bi->sendCommandMessage (BlocksProtocol::endAPIMode);
  83. currentTopology = {};
  84. lastTopology = {};
  85. auto& d = getDefaultDetectorPointer();
  86. if (d != nullptr && d->getReferenceCount() == 2)
  87. getDefaultDetectorPointer() = nullptr;
  88. }
  89. }
  90. bool isConnected (Block::UID deviceID) const noexcept
  91. {
  92. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED // This method must only be called from the message thread!
  93. for (auto&& b : currentTopology.blocks)
  94. if (b->uid == deviceID)
  95. return true;
  96. return false;
  97. }
  98. const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept
  99. {
  100. for (auto d : connectedDeviceGroups)
  101. if (auto status = d->getLastStatus (deviceID))
  102. return status;
  103. return nullptr;
  104. }
  105. void handleTopologyChange()
  106. {
  107. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  108. {
  109. juce::Array<DeviceInfo> newDeviceInfo;
  110. juce::Array<BlockDeviceConnection> newDeviceConnections;
  111. for (auto d : connectedDeviceGroups)
  112. {
  113. newDeviceInfo.addArray (d->getCurrentDeviceInfo());
  114. newDeviceConnections.addArray (d->getCurrentDeviceConnections());
  115. }
  116. for (int i = currentTopology.blocks.size(); --i >= 0;)
  117. {
  118. auto currentBlock = currentTopology.blocks.getUnchecked (i);
  119. auto newDeviceIter = std::find_if (newDeviceInfo.begin(), newDeviceInfo.end(),
  120. [&] (DeviceInfo& info) { return info.uid == currentBlock->uid; });
  121. auto* blockImpl = BlockImpl::getFrom (*currentBlock);
  122. if (newDeviceIter == newDeviceInfo.end())
  123. {
  124. if (blockImpl != nullptr)
  125. blockImpl->markDisconnected();
  126. disconnectedBlocks.addIfNotAlreadyThere (currentTopology.blocks.removeAndReturn (i).get());
  127. }
  128. else
  129. {
  130. if (blockImpl != nullptr && blockImpl->wasPowerCycled())
  131. {
  132. blockImpl->resetPowerCycleFlag();
  133. blockImpl->markReconnected (*newDeviceIter);
  134. }
  135. updateCurrentBlockInfo (currentBlock, *newDeviceIter);
  136. }
  137. }
  138. static const int maxBlocksToSave = 100;
  139. if (disconnectedBlocks.size() > maxBlocksToSave)
  140. disconnectedBlocks.removeRange (0, 2 * (disconnectedBlocks.size() - maxBlocksToSave));
  141. for (auto& info : newDeviceInfo)
  142. if (info.serial.isValid() && ! containsBlockWithUID (currentTopology.blocks, getBlockUIDFromSerialNumber (info.serial)))
  143. addBlock (info);
  144. currentTopology.connections.swapWith (newDeviceConnections);
  145. }
  146. broadcastTopology();
  147. }
  148. void notifyBlockIsRestarting (Block::UID deviceID)
  149. {
  150. for (auto& group : connectedDeviceGroups)
  151. group->notifyBlockIsRestarting (deviceID);
  152. }
  153. void handleSharedDataACK (Block::UID deviceID, uint32 packetCounter) const
  154. {
  155. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  156. if (auto* bi = getBlockImplementationWithUID (deviceID))
  157. bi->handleSharedDataACK (packetCounter);
  158. }
  159. void handleFirmwareUpdateACK (Block::UID deviceID, uint8 resultCode, uint32 resultDetail)
  160. {
  161. if (auto* bi = getBlockImplementationWithUID (deviceID))
  162. bi->handleFirmwareUpdateACK (resultCode, resultDetail);
  163. }
  164. void handleConfigUpdateMessage (Block::UID deviceID, int32 item, int32 value, int32 min, int32 max)
  165. {
  166. if (auto* bi = getBlockImplementationWithUID (deviceID))
  167. bi->handleConfigUpdateMessage (item, value, min, max);
  168. }
  169. void notifyBlockOfConfigChange (BlockImpl& bi, uint32 item)
  170. {
  171. if (auto configChangedCallback = bi.configChangedCallback)
  172. {
  173. if (item >= bi.getMaxConfigIndex())
  174. configChangedCallback (bi, {}, item);
  175. else
  176. configChangedCallback (bi, bi.getLocalConfigMetaData (item), item);
  177. }
  178. }
  179. void handleConfigSetMessage (Block::UID deviceID, int32 item, int32 value)
  180. {
  181. if (auto* bi = getBlockImplementationWithUID (deviceID))
  182. {
  183. bi->handleConfigSetMessage (item, value);
  184. notifyBlockOfConfigChange (*bi, uint32 (item));
  185. }
  186. }
  187. void handleConfigFactorySyncEndMessage (Block::UID deviceID)
  188. {
  189. if (auto* bi = getBlockImplementationWithUID (deviceID))
  190. notifyBlockOfConfigChange (*bi, bi->getMaxConfigIndex());
  191. }
  192. void handleConfigFactorySyncResetMessage (Block::UID deviceID)
  193. {
  194. if (auto* bi = getBlockImplementationWithUID (deviceID))
  195. bi->resetConfigListActiveStatus();
  196. }
  197. void handleLogMessage (Block::UID deviceID, const String& message) const
  198. {
  199. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  200. if (auto* bi = getBlockImplementationWithUID (deviceID))
  201. bi->handleLogMessage (message);
  202. }
  203. void handleButtonChange (Block::UID deviceID, Block::Timestamp timestamp, uint32 buttonIndex, bool isDown) const
  204. {
  205. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  206. if (auto* bi = getBlockImplementationWithUID (deviceID))
  207. {
  208. bi->pingFromDevice();
  209. if (isPositiveAndBelow (buttonIndex, bi->getButtons().size()))
  210. if (auto* cbi = dynamic_cast<BlockImpl::ControlButtonImplementation*> (bi->getButtons().getUnchecked (int (buttonIndex))))
  211. cbi->broadcastButtonChange (timestamp, bi->modelData.buttons[(int) buttonIndex].type, isDown);
  212. }
  213. }
  214. void handleTouchChange (Block::UID deviceID, const TouchSurface::Touch& touchEvent)
  215. {
  216. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  217. auto block = currentTopology.getBlockWithUID (deviceID);
  218. if (block != nullptr)
  219. {
  220. if (auto* surface = dynamic_cast<BlockImpl::TouchSurfaceImplementation*> (block->getTouchSurface()))
  221. {
  222. TouchSurface::Touch scaledEvent (touchEvent);
  223. scaledEvent.x *= block->getWidth();
  224. scaledEvent.y *= block->getHeight();
  225. scaledEvent.startX *= block->getWidth();
  226. scaledEvent.startY *= block->getHeight();
  227. surface->broadcastTouchChange (scaledEvent);
  228. }
  229. }
  230. }
  231. void cancelAllActiveTouches() noexcept
  232. {
  233. for (auto& block : currentTopology.blocks)
  234. if (auto* surface = block->getTouchSurface())
  235. surface->cancelAllActiveTouches();
  236. }
  237. void handleCustomMessage (Block::UID deviceID, Block::Timestamp timestamp, const int32* data)
  238. {
  239. if (auto* bi = getBlockImplementationWithUID (deviceID))
  240. bi->handleCustomMessage (timestamp, data);
  241. }
  242. //==============================================================================
  243. int getIndexFromDeviceID (Block::UID deviceID) const noexcept
  244. {
  245. for (auto* c : connectedDeviceGroups)
  246. {
  247. auto index = c->getIndexFromDeviceID (deviceID);
  248. if (index >= 0)
  249. return index;
  250. }
  251. return -1;
  252. }
  253. template <typename PacketBuilder>
  254. bool sendMessageToDevice (Block::UID deviceID, const PacketBuilder& builder) const
  255. {
  256. for (auto* c : connectedDeviceGroups)
  257. if (c->getIndexFromDeviceID (deviceID) >= 0)
  258. return c->sendMessageToDevice (builder);
  259. return false;
  260. }
  261. static Detector* getFrom (Block& b) noexcept
  262. {
  263. if (auto* bi = BlockImpl::getFrom (b))
  264. return (bi->detector);
  265. jassertfalse;
  266. return nullptr;
  267. }
  268. PhysicalTopologySource::DeviceConnection* getDeviceConnectionFor (const Block& b)
  269. {
  270. for (const auto& d : connectedDeviceGroups)
  271. {
  272. for (const auto& info : d->getCurrentDeviceInfo())
  273. {
  274. if (info.uid == b.uid)
  275. return d->getDeviceConnection();
  276. }
  277. }
  278. return nullptr;
  279. }
  280. const PhysicalTopologySource::DeviceConnection* getDeviceConnectionFor (const Block& b) const
  281. {
  282. for (const auto& d : connectedDeviceGroups)
  283. {
  284. for (const auto& info : d->getCurrentDeviceInfo())
  285. {
  286. if (info.uid == b.uid)
  287. return d->getDeviceConnection();
  288. }
  289. }
  290. return nullptr;
  291. }
  292. std::unique_ptr<MIDIDeviceDetector> defaultDetector;
  293. PhysicalTopologySource::DeviceDetector& deviceDetector;
  294. juce::Array<PhysicalTopologySource*> activeTopologySources;
  295. BlockTopology currentTopology, lastTopology;
  296. juce::ReferenceCountedArray<Block, CriticalSection> disconnectedBlocks;
  297. private:
  298. void timerCallback() override
  299. {
  300. startTimer (1500);
  301. auto detectedDevices = deviceDetector.scanForDevices();
  302. handleDevicesRemoved (detectedDevices);
  303. handleDevicesAdded (detectedDevices);
  304. }
  305. void handleDevicesRemoved (const juce::StringArray& detectedDevices)
  306. {
  307. bool anyDevicesRemoved = false;
  308. for (int i = connectedDeviceGroups.size(); --i >= 0;)
  309. {
  310. if (! connectedDeviceGroups.getUnchecked(i)->isStillConnected (detectedDevices))
  311. {
  312. connectedDeviceGroups.remove (i);
  313. anyDevicesRemoved = true;
  314. }
  315. }
  316. if (anyDevicesRemoved)
  317. handleTopologyChange();
  318. }
  319. void handleDevicesAdded (const juce::StringArray& detectedDevices)
  320. {
  321. for (const auto& devName : detectedDevices)
  322. {
  323. if (! hasDeviceFor (devName))
  324. {
  325. if (auto d = deviceDetector.openDevice (detectedDevices.indexOf (devName)))
  326. {
  327. connectedDeviceGroups.add (new ConnectedDeviceGroup<Detector> (*this, devName, d));
  328. }
  329. }
  330. }
  331. }
  332. bool hasDeviceFor (const juce::String& devName) const
  333. {
  334. for (auto d : connectedDeviceGroups)
  335. if (d->deviceName == devName)
  336. return true;
  337. return false;
  338. }
  339. void addBlock (DeviceInfo info)
  340. {
  341. if (! reactivateBlockIfKnown (info))
  342. addNewBlock (info);
  343. }
  344. bool reactivateBlockIfKnown (DeviceInfo info)
  345. {
  346. const auto uid = getBlockUIDFromSerialNumber (info.serial);
  347. for (int i = disconnectedBlocks.size(); --i >= 0;)
  348. {
  349. if (uid != disconnectedBlocks.getUnchecked (i)->uid)
  350. continue;
  351. auto block = disconnectedBlocks.removeAndReturn (i);
  352. if (auto* blockImpl = BlockImpl::getFrom (*block))
  353. {
  354. blockImpl->markReconnected (info);
  355. currentTopology.blocks.add (block);
  356. return true;
  357. }
  358. }
  359. return false;
  360. }
  361. void addNewBlock (DeviceInfo info)
  362. {
  363. currentTopology.blocks.add (new BlockImpl (info.serial, *this, info.version,
  364. info.name, info.isMaster));
  365. }
  366. void updateCurrentBlockInfo (Block::Ptr blockToUpdate, DeviceInfo& updatedInfo)
  367. {
  368. jassert (updatedInfo.uid == blockToUpdate->uid);
  369. if (versionNumberChanged (updatedInfo, blockToUpdate->versionNumber))
  370. setVersionNumberForBlock (updatedInfo, *blockToUpdate);
  371. if (updatedInfo.name.isNotEmpty())
  372. setNameForBlock (updatedInfo, *blockToUpdate);
  373. if (updatedInfo.isMaster != blockToUpdate->isMasterBlock())
  374. BlockImpl::getFrom (*blockToUpdate)->setToMaster (updatedInfo.isMaster);
  375. }
  376. BlockImpl* getBlockImplementationWithUID (Block::UID deviceID) const noexcept
  377. {
  378. if (auto&& block = currentTopology.getBlockWithUID (deviceID))
  379. return BlockImpl::getFrom (*block);
  380. return nullptr;
  381. }
  382. juce::OwnedArray<ConnectedDeviceGroup<Detector>> connectedDeviceGroups;
  383. //==============================================================================
  384. /** This is a friend of the BlocksImplementation that will scan and set the
  385. physical positions of the blocks */
  386. struct BlocksTraverser
  387. {
  388. void traverseBlockArray (const BlockTopology& topology)
  389. {
  390. juce::Array<Block::UID> visited;
  391. for (auto& block : topology.blocks)
  392. {
  393. if (block->isMasterBlock() && ! visited.contains (block->uid))
  394. {
  395. if (auto* bi = dynamic_cast<BlockImpl*> (block))
  396. {
  397. bi->masterUID = {};
  398. bi->position = {};
  399. bi->rotation = 0;
  400. }
  401. layoutNeighbours (*block, topology, block->uid, visited);
  402. }
  403. }
  404. }
  405. // returns the distance from corner clockwise
  406. int getUnitForIndex (Block::Ptr block, Block::ConnectionPort::DeviceEdge edge, int index)
  407. {
  408. if (block->getType() == Block::seaboardBlock)
  409. {
  410. if (edge == Block::ConnectionPort::DeviceEdge::north)
  411. {
  412. if (index == 0) return 1;
  413. if (index == 1) return 4;
  414. }
  415. else if (edge != Block::ConnectionPort::DeviceEdge::south)
  416. {
  417. return 1;
  418. }
  419. }
  420. if (edge == Block::ConnectionPort::DeviceEdge::south)
  421. return block->getWidth() - (index + 1);
  422. if (edge == Block::ConnectionPort::DeviceEdge::west)
  423. return block->getHeight() - (index + 1);
  424. return index;
  425. }
  426. // returns how often north needs to rotate by 90 degrees
  427. int getRotationForEdge (Block::ConnectionPort::DeviceEdge edge)
  428. {
  429. switch (edge)
  430. {
  431. case Block::ConnectionPort::DeviceEdge::north: return 0;
  432. case Block::ConnectionPort::DeviceEdge::east: return 1;
  433. case Block::ConnectionPort::DeviceEdge::south: return 2;
  434. case Block::ConnectionPort::DeviceEdge::west: return 3;
  435. }
  436. jassertfalse;
  437. return 0;
  438. }
  439. void layoutNeighbours (Block::Ptr block, const BlockTopology& topology,
  440. Block::UID masterUid, juce::Array<Block::UID>& visited)
  441. {
  442. visited.add (block->uid);
  443. for (auto& connection : topology.connections)
  444. {
  445. if ((connection.device1 == block->uid && ! visited.contains (connection.device2))
  446. || (connection.device2 == block->uid && ! visited.contains (connection.device1)))
  447. {
  448. const auto theirUid = connection.device1 == block->uid ? connection.device2 : connection.device1;
  449. const auto neighbourPtr = topology.getBlockWithUID (theirUid);
  450. if (auto* neighbour = dynamic_cast<BlockImpl*> (neighbourPtr.get()))
  451. {
  452. const auto myBounds = block->getBlockAreaWithinLayout();
  453. const auto& myPort = connection.device1 == block->uid ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2;
  454. const auto& theirPort = connection.device1 == block->uid ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1;
  455. const auto myOffset = getUnitForIndex (block, myPort.edge, myPort.index);
  456. const auto theirOffset = getUnitForIndex (neighbourPtr, theirPort.edge, theirPort.index);
  457. neighbour->masterUID = masterUid;
  458. neighbour->rotation = (2 + block->getRotation()
  459. + getRotationForEdge (myPort.edge)
  460. - getRotationForEdge (theirPort.edge)) % 4;
  461. Point<int> delta;
  462. const auto theirBounds = neighbour->getBlockAreaWithinLayout();
  463. switch ((block->getRotation() + getRotationForEdge (myPort.edge)) % 4)
  464. {
  465. case 0: // over me
  466. delta = { myOffset - (theirBounds.getWidth() - (theirOffset + 1)), -theirBounds.getHeight() };
  467. break;
  468. case 1: // right of me
  469. delta = { myBounds.getWidth(), myOffset - (theirBounds.getHeight() - (theirOffset + 1)) };
  470. break;
  471. case 2: // under me
  472. delta = { (myBounds.getWidth() - (myOffset + 1)) - theirOffset, myBounds.getHeight() };
  473. break;
  474. case 3: // left of me
  475. delta = { -theirBounds.getWidth(), (myBounds.getHeight() - (myOffset + 1)) - theirOffset };
  476. break;
  477. }
  478. neighbour->position = myBounds.getPosition() + delta;
  479. }
  480. layoutNeighbours (neighbourPtr, topology, masterUid, visited);
  481. }
  482. }
  483. }
  484. };
  485. //==============================================================================
  486. #if DUMP_TOPOLOGY
  487. static juce::String idToSerialNum (const BlockTopology& topology, Block::UID uid)
  488. {
  489. for (auto* b : topology.blocks)
  490. if (b->uid == uid)
  491. return b->serialNumber;
  492. return "???";
  493. }
  494. static juce::String portEdgeToString (Block::ConnectionPort port)
  495. {
  496. switch (port.edge)
  497. {
  498. case Block::ConnectionPort::DeviceEdge::north: return "north";
  499. case Block::ConnectionPort::DeviceEdge::south: return "south";
  500. case Block::ConnectionPort::DeviceEdge::east: return "east";
  501. case Block::ConnectionPort::DeviceEdge::west: return "west";
  502. }
  503. return {};
  504. }
  505. static juce::String portToString (Block::ConnectionPort port)
  506. {
  507. return portEdgeToString (port) + "_" + juce::String (port.index);
  508. }
  509. static void dumpTopology (const BlockTopology& topology)
  510. {
  511. MemoryOutputStream m;
  512. m << "=============================================================================" << newLine
  513. << "Topology: " << topology.blocks.size() << " device(s)" << newLine
  514. << newLine;
  515. int index = 0;
  516. for (auto block : topology.blocks)
  517. {
  518. m << "Device " << index++ << (block->isMasterBlock() ? ": (MASTER)" : ":") << newLine;
  519. m << " Description: " << block->getDeviceDescription() << newLine
  520. << " Serial: " << block->serialNumber << newLine;
  521. if (auto bi = BlockImplementation<Detector>::getFrom (*block))
  522. m << " Short address: " << (int) bi->getDeviceIndex() << newLine;
  523. m << " Battery level: " + juce::String (juce::roundToInt (100.0f * block->getBatteryLevel())) + "%" << newLine
  524. << " Battery charging: " + juce::String (block->isBatteryCharging() ? "y" : "n") << newLine
  525. << " Width: " << block->getWidth() << newLine
  526. << " Height: " << block->getHeight() << newLine
  527. << " Millimeters per unit: " << block->getMillimetersPerUnit() << newLine
  528. << newLine;
  529. }
  530. for (auto& connection : topology.connections)
  531. {
  532. m << idToSerialNum (topology, connection.device1)
  533. << ":" << portToString (connection.connectionPortOnDevice1)
  534. << " <-> "
  535. << idToSerialNum (topology, connection.device2)
  536. << ":" << portToString (connection.connectionPortOnDevice2) << newLine;
  537. }
  538. m << "=============================================================================" << newLine;
  539. Logger::outputDebugString (m.toString());
  540. }
  541. #endif
  542. //==============================================================================
  543. void broadcastTopology()
  544. {
  545. if (currentTopology != lastTopology)
  546. {
  547. lastTopology = currentTopology;
  548. BlocksTraverser traverser;
  549. traverser.traverseBlockArray (currentTopology);
  550. for (auto* d : activeTopologySources)
  551. d->listeners.call ([] (TopologySource::Listener& l) { l.topologyChanged(); });
  552. #if DUMP_TOPOLOGY
  553. dumpTopology (lastTopology);
  554. #endif
  555. }
  556. }
  557. //==============================================================================
  558. JUCE_DECLARE_WEAK_REFERENCEABLE (Detector)
  559. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Detector)
  560. };
  561. } // namespace juce