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.

777 lines
27KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  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. #if JUCE_ALSA
  20. //==============================================================================
  21. class AlsaClient
  22. {
  23. auto lowerBound (int portId) const
  24. {
  25. const auto comparator = [] (const auto& port, const auto& id) { return port->getPortId() < id; };
  26. return std::lower_bound (ports.begin(), ports.end(), portId, comparator);
  27. }
  28. auto findPortIterator (int portId) const
  29. {
  30. const auto iter = lowerBound (portId);
  31. return (iter == ports.end() || (*iter)->getPortId() != portId) ? ports.end() : iter;
  32. }
  33. public:
  34. ~AlsaClient()
  35. {
  36. inputThread.reset();
  37. jassert (activeCallbacks.get() == 0);
  38. if (handle != nullptr)
  39. {
  40. snd_seq_delete_simple_port (handle, announcementsIn);
  41. snd_seq_close (handle);
  42. }
  43. }
  44. static String getAlsaMidiName()
  45. {
  46. #ifdef JUCE_ALSA_MIDI_NAME
  47. return JUCE_ALSA_MIDI_NAME;
  48. #else
  49. if (auto* app = JUCEApplicationBase::getInstance())
  50. return app->getApplicationName();
  51. return "JUCE";
  52. #endif
  53. }
  54. //==============================================================================
  55. // represents an input or output port of the supplied AlsaClient
  56. struct Port
  57. {
  58. explicit Port (bool forInput) noexcept
  59. : isInput (forInput) {}
  60. ~Port()
  61. {
  62. if (isValid())
  63. {
  64. if (isInput)
  65. enableCallback (false);
  66. else
  67. snd_midi_event_free (midiParser);
  68. snd_seq_delete_simple_port (client->get(), portId);
  69. }
  70. }
  71. void connectWith (int sourceClient, int sourcePort) const noexcept
  72. {
  73. if (isInput)
  74. snd_seq_connect_from (client->get(), portId, sourceClient, sourcePort);
  75. else
  76. snd_seq_connect_to (client->get(), portId, sourceClient, sourcePort);
  77. }
  78. bool isValid() const noexcept
  79. {
  80. return client->get() != nullptr && portId >= 0;
  81. }
  82. void setupInput (MidiInput* input, MidiInputCallback* cb)
  83. {
  84. jassert (cb != nullptr && input != nullptr);
  85. callback = cb;
  86. midiInput = input;
  87. }
  88. void setupOutput()
  89. {
  90. jassert (! isInput);
  91. snd_midi_event_new ((size_t) maxEventSize, &midiParser);
  92. }
  93. void enableCallback (bool enable)
  94. {
  95. callbackEnabled = enable;
  96. }
  97. bool sendMessageNow (const MidiMessage& message)
  98. {
  99. if (message.getRawDataSize() > maxEventSize)
  100. {
  101. maxEventSize = message.getRawDataSize();
  102. snd_midi_event_free (midiParser);
  103. snd_midi_event_new ((size_t) maxEventSize, &midiParser);
  104. }
  105. snd_seq_event_t event;
  106. snd_seq_ev_clear (&event);
  107. auto numBytes = (long) message.getRawDataSize();
  108. auto* data = message.getRawData();
  109. auto seqHandle = client->get();
  110. bool success = true;
  111. while (numBytes > 0)
  112. {
  113. auto numSent = snd_midi_event_encode (midiParser, data, numBytes, &event);
  114. if (numSent <= 0)
  115. {
  116. success = numSent == 0;
  117. break;
  118. }
  119. numBytes -= numSent;
  120. data += numSent;
  121. snd_seq_ev_set_source (&event, (unsigned char) portId);
  122. snd_seq_ev_set_subs (&event);
  123. snd_seq_ev_set_direct (&event);
  124. if (snd_seq_event_output_direct (seqHandle, &event) < 0)
  125. {
  126. success = false;
  127. break;
  128. }
  129. }
  130. snd_midi_event_reset_encode (midiParser);
  131. return success;
  132. }
  133. bool operator== (const Port& lhs) const noexcept
  134. {
  135. return portId != -1 && portId == lhs.portId;
  136. }
  137. void createPort (const String& name, bool enableSubscription)
  138. {
  139. if (auto seqHandle = client->get())
  140. {
  141. const unsigned int caps =
  142. isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0))
  143. : (SND_SEQ_PORT_CAP_READ | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0));
  144. portName = name;
  145. portId = snd_seq_create_simple_port (seqHandle, portName.toUTF8(), caps,
  146. SND_SEQ_PORT_TYPE_MIDI_GENERIC |
  147. SND_SEQ_PORT_TYPE_APPLICATION);
  148. }
  149. }
  150. void handleIncomingMidiMessage (const MidiMessage& message) const
  151. {
  152. if (callbackEnabled)
  153. callback->handleIncomingMidiMessage (midiInput, message);
  154. }
  155. void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp)
  156. {
  157. if (callbackEnabled)
  158. callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp);
  159. }
  160. int getPortId() const { return portId; }
  161. const String& getPortName() const { return portName; }
  162. private:
  163. const std::shared_ptr<AlsaClient> client = AlsaClient::getInstance();
  164. MidiInputCallback* callback = nullptr;
  165. snd_midi_event_t* midiParser = nullptr;
  166. MidiInput* midiInput = nullptr;
  167. String portName;
  168. int maxEventSize = 4096, portId = -1;
  169. std::atomic<bool> callbackEnabled { false };
  170. bool isInput = false;
  171. };
  172. static std::shared_ptr<AlsaClient> getInstance()
  173. {
  174. static std::weak_ptr<AlsaClient> ptr;
  175. if (auto locked = ptr.lock())
  176. return locked;
  177. std::shared_ptr<AlsaClient> result (new AlsaClient());
  178. ptr = result;
  179. return result;
  180. }
  181. void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message)
  182. {
  183. const ScopedLock sl (callbackLock);
  184. if (auto* port = findPort (event->dest.port))
  185. port->handleIncomingMidiMessage (message);
  186. }
  187. void handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp)
  188. {
  189. const ScopedLock sl (callbackLock);
  190. if (auto* port = findPort (event->dest.port))
  191. port->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp);
  192. }
  193. snd_seq_t* get() const noexcept { return handle; }
  194. int getId() const noexcept { return clientId; }
  195. Port* createPort (const String& name, bool forInput, bool enableSubscription)
  196. {
  197. const ScopedLock sl (callbackLock);
  198. auto port = new Port (forInput);
  199. port->createPort (name, enableSubscription);
  200. const auto iter = lowerBound (port->getPortId());
  201. jassert (iter == ports.end() || port->getPortId() < (*iter)->getPortId());
  202. ports.insert (iter, rawToUniquePtr (port));
  203. return port;
  204. }
  205. void deletePort (Port* port)
  206. {
  207. const ScopedLock sl (callbackLock);
  208. if (const auto iter = findPortIterator (port->getPortId()); iter != ports.end())
  209. ports.erase (iter);
  210. }
  211. private:
  212. AlsaClient()
  213. {
  214. snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
  215. if (handle != nullptr)
  216. {
  217. snd_seq_nonblock (handle, SND_SEQ_NONBLOCK);
  218. snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8());
  219. clientId = snd_seq_client_id (handle);
  220. // It's good idea to pre-allocate a good number of elements
  221. ports.reserve (32);
  222. announcementsIn = snd_seq_create_simple_port (handle,
  223. TRANS ("announcements").toRawUTF8(),
  224. SND_SEQ_PORT_CAP_WRITE,
  225. SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
  226. snd_seq_connect_from (handle, announcementsIn, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);
  227. inputThread.emplace (*this);
  228. }
  229. }
  230. Port* findPort (int portId)
  231. {
  232. if (const auto iter = findPortIterator (portId); iter != ports.end())
  233. return iter->get();
  234. return nullptr;
  235. }
  236. snd_seq_t* handle = nullptr;
  237. int clientId = 0;
  238. int announcementsIn = 0;
  239. std::vector<std::unique_ptr<Port>> ports;
  240. Atomic<int> activeCallbacks;
  241. CriticalSection callbackLock;
  242. //==============================================================================
  243. class SequencerThread
  244. {
  245. public:
  246. explicit SequencerThread (AlsaClient& c)
  247. : client (c)
  248. {
  249. }
  250. ~SequencerThread() noexcept
  251. {
  252. shouldStop = true;
  253. thread.join();
  254. }
  255. private:
  256. // If we directly call MidiDeviceListConnectionBroadcaster::get() from the background thread,
  257. // there's a possibility that we'll deadlock in the following scenario:
  258. // - The main thread calls MidiDeviceListConnectionBroadcaster::get() for the first time
  259. // (e.g. to register a listener). The static MidiDeviceListConnectionBroadcaster singleton
  260. // begins construction. During the constructor, an AlsaClient is created to iterate midi
  261. // ins/outs.
  262. // - The AlsaClient starts a new SequencerThread. If connections are updated, the
  263. // SequencerThread may call MidiDeviceListConnectionBroadcaster::get().notify()
  264. // while the MidiDeviceListConnectionBroadcaster singleton is still being created.
  265. // - The SequencerThread blocks until the MidiDeviceListConnectionBroadcaster has been
  266. // created on the main thread, but the MidiDeviceListConnectionBroadcaster's constructor
  267. // can't complete until the AlsaClient's destructor has run, which in turn requires the
  268. // SequencerThread to join.
  269. class UpdateNotifier final : private AsyncUpdater
  270. {
  271. public:
  272. ~UpdateNotifier() override { cancelPendingUpdate(); }
  273. using AsyncUpdater::triggerAsyncUpdate;
  274. private:
  275. void handleAsyncUpdate() override { MidiDeviceListConnectionBroadcaster::get().notify(); }
  276. };
  277. AlsaClient& client;
  278. MidiDataConcatenator concatenator { 2048 };
  279. std::atomic<bool> shouldStop { false };
  280. UpdateNotifier notifier;
  281. std::thread thread { [this]
  282. {
  283. Thread::setCurrentThreadName ("JUCE MIDI Input");
  284. auto seqHandle = client.get();
  285. const int maxEventSize = 16 * 1024;
  286. snd_midi_event_t* midiParser;
  287. if (snd_midi_event_new (maxEventSize, &midiParser) >= 0)
  288. {
  289. const ScopeGuard freeMidiEvent { [&] { snd_midi_event_free (midiParser); } };
  290. const auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN);
  291. std::vector<pollfd> pfd (static_cast<size_t> (numPfds));
  292. snd_seq_poll_descriptors (seqHandle, pfd.data(), (unsigned int) numPfds, POLLIN);
  293. std::vector<uint8> buffer (maxEventSize);
  294. while (! shouldStop)
  295. {
  296. // This timeout shouldn't be too long, so that the program can exit in a timely manner
  297. if (poll (pfd.data(), (nfds_t) numPfds, 100) > 0)
  298. {
  299. if (shouldStop)
  300. break;
  301. do
  302. {
  303. snd_seq_event_t* inputEvent = nullptr;
  304. if (snd_seq_event_input (seqHandle, &inputEvent) >= 0)
  305. {
  306. const ScopeGuard freeInputEvent { [&] { snd_seq_free_event (inputEvent); } };
  307. constexpr int systemEvents[]
  308. {
  309. SND_SEQ_EVENT_CLIENT_CHANGE,
  310. SND_SEQ_EVENT_CLIENT_START,
  311. SND_SEQ_EVENT_CLIENT_EXIT,
  312. SND_SEQ_EVENT_PORT_CHANGE,
  313. SND_SEQ_EVENT_PORT_START,
  314. SND_SEQ_EVENT_PORT_EXIT,
  315. SND_SEQ_EVENT_PORT_SUBSCRIBED,
  316. SND_SEQ_EVENT_PORT_UNSUBSCRIBED,
  317. };
  318. const auto foundEvent = std::find (std::begin (systemEvents),
  319. std::end (systemEvents),
  320. inputEvent->type);
  321. if (foundEvent != std::end (systemEvents))
  322. {
  323. notifier.triggerAsyncUpdate();
  324. continue;
  325. }
  326. // xxx what about SYSEXes that are too big for the buffer?
  327. const auto numBytes = snd_midi_event_decode (midiParser,
  328. buffer.data(),
  329. maxEventSize,
  330. inputEvent);
  331. snd_midi_event_reset_decode (midiParser);
  332. concatenator.pushMidiData (buffer.data(), (int) numBytes,
  333. Time::getMillisecondCounter() * 0.001,
  334. inputEvent, client);
  335. }
  336. }
  337. while (snd_seq_event_input_pending (seqHandle, 0) > 0);
  338. }
  339. }
  340. }
  341. } };
  342. };
  343. std::optional<SequencerThread> inputThread;
  344. };
  345. //==============================================================================
  346. static String getFormattedPortIdentifier (int clientId, int portId)
  347. {
  348. return String (clientId) + "-" + String (portId);
  349. }
  350. static AlsaClient::Port* iterateMidiClient (AlsaClient& client,
  351. snd_seq_client_info_t* clientInfo,
  352. bool forInput,
  353. Array<MidiDeviceInfo>& devices,
  354. const String& deviceIdentifierToOpen)
  355. {
  356. AlsaClient::Port* port = nullptr;
  357. auto seqHandle = client.get();
  358. snd_seq_port_info_t* portInfo = nullptr;
  359. snd_seq_port_info_alloca (&portInfo);
  360. jassert (portInfo != nullptr);
  361. auto numPorts = snd_seq_client_info_get_num_ports (clientInfo);
  362. auto sourceClient = snd_seq_client_info_get_client (clientInfo);
  363. snd_seq_port_info_set_client (portInfo, sourceClient);
  364. snd_seq_port_info_set_port (portInfo, -1);
  365. while (--numPorts >= 0)
  366. {
  367. if (snd_seq_query_next_port (seqHandle, portInfo) == 0
  368. && (snd_seq_port_info_get_capability (portInfo)
  369. & (forInput ? SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_SUBS_WRITE)) != 0)
  370. {
  371. String portName (snd_seq_port_info_get_name (portInfo));
  372. auto portID = snd_seq_port_info_get_port (portInfo);
  373. MidiDeviceInfo device (portName, getFormattedPortIdentifier (sourceClient, portID));
  374. devices.add (device);
  375. if (deviceIdentifierToOpen.isNotEmpty() && deviceIdentifierToOpen == device.identifier)
  376. {
  377. if (portID != -1)
  378. {
  379. port = client.createPort (portName, forInput, false);
  380. jassert (port->isValid());
  381. port->connectWith (sourceClient, portID);
  382. break;
  383. }
  384. }
  385. }
  386. }
  387. return port;
  388. }
  389. static AlsaClient::Port* iterateMidiDevices (bool forInput,
  390. Array<MidiDeviceInfo>& devices,
  391. const String& deviceIdentifierToOpen)
  392. {
  393. AlsaClient::Port* port = nullptr;
  394. auto client = AlsaClient::getInstance();
  395. if (auto seqHandle = client->get())
  396. {
  397. snd_seq_system_info_t* systemInfo = nullptr;
  398. snd_seq_client_info_t* clientInfo = nullptr;
  399. snd_seq_system_info_alloca (&systemInfo);
  400. jassert (systemInfo != nullptr);
  401. if (snd_seq_system_info (seqHandle, systemInfo) == 0)
  402. {
  403. snd_seq_client_info_alloca (&clientInfo);
  404. jassert (clientInfo != nullptr);
  405. auto numClients = snd_seq_system_info_get_cur_clients (systemInfo);
  406. while (--numClients >= 0)
  407. {
  408. if (snd_seq_query_next_client (seqHandle, clientInfo) == 0)
  409. {
  410. port = iterateMidiClient (*client,
  411. clientInfo,
  412. forInput,
  413. devices,
  414. deviceIdentifierToOpen);
  415. if (port != nullptr)
  416. break;
  417. }
  418. }
  419. }
  420. }
  421. return port;
  422. }
  423. struct AlsaPortPtr
  424. {
  425. explicit AlsaPortPtr (AlsaClient::Port* p)
  426. : ptr (p) {}
  427. virtual ~AlsaPortPtr() noexcept { AlsaClient::getInstance()->deletePort (ptr); }
  428. AlsaClient::Port* ptr = nullptr;
  429. };
  430. //==============================================================================
  431. class MidiInput::Pimpl final : public AlsaPortPtr
  432. {
  433. public:
  434. using AlsaPortPtr::AlsaPortPtr;
  435. };
  436. Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
  437. {
  438. Array<MidiDeviceInfo> devices;
  439. iterateMidiDevices (true, devices, {});
  440. return devices;
  441. }
  442. MidiDeviceInfo MidiInput::getDefaultDevice()
  443. {
  444. return getAvailableDevices().getFirst();
  445. }
  446. std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
  447. {
  448. if (deviceIdentifier.isEmpty())
  449. return {};
  450. Array<MidiDeviceInfo> devices;
  451. auto* port = iterateMidiDevices (true, devices, deviceIdentifier);
  452. if (port == nullptr || ! port->isValid())
  453. return {};
  454. jassert (port->isValid());
  455. std::unique_ptr<MidiInput> midiInput (new MidiInput (port->getPortName(), deviceIdentifier));
  456. port->setupInput (midiInput.get(), callback);
  457. midiInput->internal = std::make_unique<Pimpl> (port);
  458. return midiInput;
  459. }
  460. std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
  461. {
  462. auto client = AlsaClient::getInstance();
  463. auto* port = client->createPort (deviceName, true, true);
  464. if (port == nullptr || ! port->isValid())
  465. return {};
  466. std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->getPortId())));
  467. port->setupInput (midiInput.get(), callback);
  468. midiInput->internal = std::make_unique<Pimpl> (port);
  469. return midiInput;
  470. }
  471. StringArray MidiInput::getDevices()
  472. {
  473. StringArray deviceNames;
  474. for (auto& d : getAvailableDevices())
  475. deviceNames.add (d.name);
  476. deviceNames.appendNumbersToDuplicates (true, true);
  477. return deviceNames;
  478. }
  479. int MidiInput::getDefaultDeviceIndex()
  480. {
  481. return 0;
  482. }
  483. std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback)
  484. {
  485. return openDevice (getAvailableDevices()[index].identifier, callback);
  486. }
  487. MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
  488. : deviceInfo (deviceName, deviceIdentifier)
  489. {
  490. }
  491. MidiInput::~MidiInput()
  492. {
  493. stop();
  494. }
  495. void MidiInput::start()
  496. {
  497. internal->ptr->enableCallback (true);
  498. }
  499. void MidiInput::stop()
  500. {
  501. internal->ptr->enableCallback (false);
  502. }
  503. //==============================================================================
  504. class MidiOutput::Pimpl final : public AlsaPortPtr
  505. {
  506. public:
  507. using AlsaPortPtr::AlsaPortPtr;
  508. };
  509. Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
  510. {
  511. Array<MidiDeviceInfo> devices;
  512. iterateMidiDevices (false, devices, {});
  513. return devices;
  514. }
  515. MidiDeviceInfo MidiOutput::getDefaultDevice()
  516. {
  517. return getAvailableDevices().getFirst();
  518. }
  519. std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
  520. {
  521. if (deviceIdentifier.isEmpty())
  522. return {};
  523. Array<MidiDeviceInfo> devices;
  524. auto* port = iterateMidiDevices (false, devices, deviceIdentifier);
  525. if (port == nullptr || ! port->isValid())
  526. return {};
  527. std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (port->getPortName(), deviceIdentifier));
  528. port->setupOutput();
  529. midiOutput->internal = std::make_unique<Pimpl> (port);
  530. return midiOutput;
  531. }
  532. std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String& deviceName)
  533. {
  534. auto client = AlsaClient::getInstance();
  535. auto* port = client->createPort (deviceName, false, true);
  536. if (port == nullptr || ! port->isValid())
  537. return {};
  538. std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->getPortId())));
  539. port->setupOutput();
  540. midiOutput->internal = std::make_unique<Pimpl> (port);
  541. return midiOutput;
  542. }
  543. StringArray MidiOutput::getDevices()
  544. {
  545. StringArray deviceNames;
  546. for (auto& d : getAvailableDevices())
  547. deviceNames.add (d.name);
  548. deviceNames.appendNumbersToDuplicates (true, true);
  549. return deviceNames;
  550. }
  551. int MidiOutput::getDefaultDeviceIndex()
  552. {
  553. return 0;
  554. }
  555. std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index)
  556. {
  557. return openDevice (getAvailableDevices()[index].identifier);
  558. }
  559. MidiOutput::~MidiOutput()
  560. {
  561. stopBackgroundThread();
  562. }
  563. void MidiOutput::sendMessageNow (const MidiMessage& message)
  564. {
  565. internal->ptr->sendMessageNow (message);
  566. }
  567. MidiDeviceListConnection MidiDeviceListConnection::make (std::function<void()> cb)
  568. {
  569. auto& broadcaster = MidiDeviceListConnectionBroadcaster::get();
  570. // We capture the AlsaClient instance here to ensure that it remains alive for at least as long
  571. // as the MidiDeviceListConnection. This is necessary because system change messages will only
  572. // be processed when the AlsaClient's SequencerThread is running.
  573. return { &broadcaster, broadcaster.add ([fn = std::move (cb), client = AlsaClient::getInstance()]
  574. {
  575. NullCheckedInvocation::invoke (fn);
  576. }) };
  577. }
  578. //==============================================================================
  579. #else
  580. class MidiInput::Pimpl {};
  581. // (These are just stub functions if ALSA is unavailable...)
  582. MidiInput::MidiInput (const String& deviceName, const String& deviceID)
  583. : deviceInfo (deviceName, deviceID)
  584. {
  585. }
  586. MidiInput::~MidiInput() {}
  587. void MidiInput::start() {}
  588. void MidiInput::stop() {}
  589. Array<MidiDeviceInfo> MidiInput::getAvailableDevices() { return {}; }
  590. MidiDeviceInfo MidiInput::getDefaultDevice() { return {}; }
  591. std::unique_ptr<MidiInput> MidiInput::openDevice (const String&, MidiInputCallback*) { return {}; }
  592. std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String&, MidiInputCallback*) { return {}; }
  593. StringArray MidiInput::getDevices() { return {}; }
  594. int MidiInput::getDefaultDeviceIndex() { return 0;}
  595. std::unique_ptr<MidiInput> MidiInput::openDevice (int, MidiInputCallback*) { return {}; }
  596. class MidiOutput::Pimpl {};
  597. MidiOutput::~MidiOutput() {}
  598. void MidiOutput::sendMessageNow (const MidiMessage&) {}
  599. Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; }
  600. MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; }
  601. std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String&) { return {}; }
  602. std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String&) { return {}; }
  603. StringArray MidiOutput::getDevices() { return {}; }
  604. int MidiOutput::getDefaultDeviceIndex() { return 0;}
  605. std::unique_ptr<MidiOutput> MidiOutput::openDevice (int) { return {}; }
  606. MidiDeviceListConnection MidiDeviceListConnection::make (std::function<void()> cb)
  607. {
  608. auto& broadcaster = MidiDeviceListConnectionBroadcaster::get();
  609. return { &broadcaster, broadcaster.add (std::move (cb)) };
  610. }
  611. #endif
  612. } // namespace juce