Audio plugin host https://kx.studio/carla
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.

juce_linux_Midi.cpp 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  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 : public ReferenceCountedObject
  22. {
  23. public:
  24. AlsaClient()
  25. {
  26. jassert (instance == nullptr);
  27. snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
  28. if (handle != nullptr)
  29. {
  30. snd_seq_nonblock (handle, SND_SEQ_NONBLOCK);
  31. snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8());
  32. clientId = snd_seq_client_id (handle);
  33. // It's good idea to pre-allocate a good number of elements
  34. ports.ensureStorageAllocated (32);
  35. }
  36. }
  37. ~AlsaClient()
  38. {
  39. jassert (instance != nullptr);
  40. instance = nullptr;
  41. jassert (activeCallbacks.get() == 0);
  42. if (inputThread)
  43. inputThread->stopThread (3000);
  44. if (handle != nullptr)
  45. snd_seq_close (handle);
  46. }
  47. static String getAlsaMidiName()
  48. {
  49. #ifdef JUCE_ALSA_MIDI_NAME
  50. return JUCE_ALSA_MIDI_NAME;
  51. #else
  52. if (auto* app = JUCEApplicationBase::getInstance())
  53. return app->getApplicationName();
  54. return "JUCE";
  55. #endif
  56. }
  57. using Ptr = ReferenceCountedObjectPtr<AlsaClient>;
  58. //==============================================================================
  59. // represents an input or output port of the supplied AlsaClient
  60. struct Port
  61. {
  62. Port (AlsaClient& c, bool forInput) noexcept
  63. : client (c), isInput (forInput)
  64. {}
  65. ~Port()
  66. {
  67. if (isValid())
  68. {
  69. if (isInput)
  70. enableCallback (false);
  71. else
  72. snd_midi_event_free (midiParser);
  73. snd_seq_delete_simple_port (client.get(), portId);
  74. }
  75. }
  76. void connectWith (int sourceClient, int sourcePort) const noexcept
  77. {
  78. if (isInput)
  79. snd_seq_connect_from (client.get(), portId, sourceClient, sourcePort);
  80. else
  81. snd_seq_connect_to (client.get(), portId, sourceClient, sourcePort);
  82. }
  83. bool isValid() const noexcept
  84. {
  85. return client.get() != nullptr && portId >= 0;
  86. }
  87. void setupInput (MidiInput* input, MidiInputCallback* cb)
  88. {
  89. jassert (cb != nullptr && input != nullptr);
  90. callback = cb;
  91. midiInput = input;
  92. }
  93. void setupOutput()
  94. {
  95. jassert (! isInput);
  96. snd_midi_event_new ((size_t) maxEventSize, &midiParser);
  97. }
  98. void enableCallback (bool enable)
  99. {
  100. const auto oldValue = callbackEnabled.exchange (enable);
  101. if (oldValue != enable)
  102. {
  103. if (enable)
  104. client.registerCallback();
  105. else
  106. client.unregisterCallback();
  107. }
  108. }
  109. bool sendMessageNow (const MidiMessage& message)
  110. {
  111. if (message.getRawDataSize() > maxEventSize)
  112. {
  113. maxEventSize = message.getRawDataSize();
  114. snd_midi_event_free (midiParser);
  115. snd_midi_event_new ((size_t) maxEventSize, &midiParser);
  116. }
  117. snd_seq_event_t event;
  118. snd_seq_ev_clear (&event);
  119. auto numBytes = (long) message.getRawDataSize();
  120. auto* data = message.getRawData();
  121. auto seqHandle = client.get();
  122. bool success = true;
  123. while (numBytes > 0)
  124. {
  125. auto numSent = snd_midi_event_encode (midiParser, data, numBytes, &event);
  126. if (numSent <= 0)
  127. {
  128. success = numSent == 0;
  129. break;
  130. }
  131. numBytes -= numSent;
  132. data += numSent;
  133. snd_seq_ev_set_source (&event, (unsigned char) portId);
  134. snd_seq_ev_set_subs (&event);
  135. snd_seq_ev_set_direct (&event);
  136. if (snd_seq_event_output_direct (seqHandle, &event) < 0)
  137. {
  138. success = false;
  139. break;
  140. }
  141. }
  142. snd_midi_event_reset_encode (midiParser);
  143. return success;
  144. }
  145. bool operator== (const Port& lhs) const noexcept
  146. {
  147. return portId != -1 && portId == lhs.portId;
  148. }
  149. void createPort (const String& name, bool enableSubscription)
  150. {
  151. if (auto seqHandle = client.get())
  152. {
  153. const unsigned int caps =
  154. isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0))
  155. : (SND_SEQ_PORT_CAP_READ | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0));
  156. portName = name;
  157. portId = snd_seq_create_simple_port (seqHandle, portName.toUTF8(), caps,
  158. SND_SEQ_PORT_TYPE_MIDI_GENERIC |
  159. SND_SEQ_PORT_TYPE_APPLICATION);
  160. }
  161. }
  162. void handleIncomingMidiMessage (const MidiMessage& message) const
  163. {
  164. if (callbackEnabled)
  165. callback->handleIncomingMidiMessage (midiInput, message);
  166. }
  167. void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp)
  168. {
  169. if (callbackEnabled)
  170. callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp);
  171. }
  172. int getPortId() const { return portId; }
  173. const String& getPortName() const { return portName; }
  174. private:
  175. AlsaClient& client;
  176. MidiInputCallback* callback = nullptr;
  177. snd_midi_event_t* midiParser = nullptr;
  178. MidiInput* midiInput = nullptr;
  179. String portName;
  180. int maxEventSize = 4096, portId = -1;
  181. std::atomic<bool> callbackEnabled { false };
  182. bool isInput = false;
  183. };
  184. static Ptr getInstance()
  185. {
  186. if (instance == nullptr)
  187. instance = new AlsaClient();
  188. return instance;
  189. }
  190. void registerCallback()
  191. {
  192. if (inputThread == nullptr)
  193. inputThread.reset (new MidiInputThread (*this));
  194. if (++activeCallbacks == 1)
  195. inputThread->startThread();
  196. }
  197. void unregisterCallback()
  198. {
  199. jassert (activeCallbacks.get() > 0);
  200. if (--activeCallbacks == 0 && inputThread->isThreadRunning())
  201. inputThread->signalThreadShouldExit();
  202. }
  203. void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message)
  204. {
  205. const ScopedLock sl (callbackLock);
  206. if (auto* port = ports[event->dest.port])
  207. port->handleIncomingMidiMessage (message);
  208. }
  209. void handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp)
  210. {
  211. const ScopedLock sl (callbackLock);
  212. if (auto* port = ports[event->dest.port])
  213. port->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp);
  214. }
  215. snd_seq_t* get() const noexcept { return handle; }
  216. int getId() const noexcept { return clientId; }
  217. Port* createPort (const String& name, bool forInput, bool enableSubscription)
  218. {
  219. const ScopedLock sl (callbackLock);
  220. auto port = new Port (*this, forInput);
  221. port->createPort (name, enableSubscription);
  222. ports.set (port->getPortId(), port);
  223. incReferenceCount();
  224. return port;
  225. }
  226. void deletePort (Port* port)
  227. {
  228. const ScopedLock sl (callbackLock);
  229. ports.set (port->getPortId(), nullptr);
  230. decReferenceCount();
  231. }
  232. private:
  233. snd_seq_t* handle = nullptr;
  234. int clientId = 0;
  235. OwnedArray<Port> ports;
  236. Atomic<int> activeCallbacks;
  237. CriticalSection callbackLock;
  238. static AlsaClient* instance;
  239. //==============================================================================
  240. class MidiInputThread : public Thread
  241. {
  242. public:
  243. MidiInputThread (AlsaClient& c)
  244. : Thread ("JUCE MIDI Input"), client (c)
  245. {
  246. jassert (client.get() != nullptr);
  247. }
  248. void run() override
  249. {
  250. auto seqHandle = client.get();
  251. const int maxEventSize = 16 * 1024;
  252. snd_midi_event_t* midiParser;
  253. if (snd_midi_event_new (maxEventSize, &midiParser) >= 0)
  254. {
  255. auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN);
  256. HeapBlock<pollfd> pfd (numPfds);
  257. snd_seq_poll_descriptors (seqHandle, pfd, (unsigned int) numPfds, POLLIN);
  258. HeapBlock<uint8> buffer (maxEventSize);
  259. while (! threadShouldExit())
  260. {
  261. if (poll (pfd, (nfds_t) numPfds, 100) > 0) // there was a "500" here which is a bit long when we exit the program and have to wait for a timeout on this poll call
  262. {
  263. if (threadShouldExit())
  264. break;
  265. do
  266. {
  267. snd_seq_event_t* inputEvent = nullptr;
  268. if (snd_seq_event_input (seqHandle, &inputEvent) >= 0)
  269. {
  270. // xxx what about SYSEXes that are too big for the buffer?
  271. auto numBytes = snd_midi_event_decode (midiParser, buffer,
  272. maxEventSize, inputEvent);
  273. snd_midi_event_reset_decode (midiParser);
  274. concatenator.pushMidiData (buffer, (int) numBytes,
  275. Time::getMillisecondCounter() * 0.001,
  276. inputEvent, client);
  277. snd_seq_free_event (inputEvent);
  278. }
  279. }
  280. while (snd_seq_event_input_pending (seqHandle, 0) > 0);
  281. }
  282. }
  283. snd_midi_event_free (midiParser);
  284. }
  285. }
  286. private:
  287. AlsaClient& client;
  288. MidiDataConcatenator concatenator { 2048 };
  289. };
  290. std::unique_ptr<MidiInputThread> inputThread;
  291. };
  292. AlsaClient* AlsaClient::instance = nullptr;
  293. //==============================================================================
  294. static String getFormattedPortIdentifier (int clientId, int portId)
  295. {
  296. return String (clientId) + "-" + String (portId);
  297. }
  298. static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client,
  299. snd_seq_client_info_t* clientInfo,
  300. bool forInput,
  301. Array<MidiDeviceInfo>& devices,
  302. const String& deviceIdentifierToOpen)
  303. {
  304. AlsaClient::Port* port = nullptr;
  305. auto seqHandle = client->get();
  306. snd_seq_port_info_t* portInfo = nullptr;
  307. snd_seq_port_info_alloca (&portInfo);
  308. jassert (portInfo != nullptr);
  309. auto numPorts = snd_seq_client_info_get_num_ports (clientInfo);
  310. auto sourceClient = snd_seq_client_info_get_client (clientInfo);
  311. snd_seq_port_info_set_client (portInfo, sourceClient);
  312. snd_seq_port_info_set_port (portInfo, -1);
  313. while (--numPorts >= 0)
  314. {
  315. if (snd_seq_query_next_port (seqHandle, portInfo) == 0
  316. && (snd_seq_port_info_get_capability (portInfo)
  317. & (forInput ? SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_SUBS_WRITE)) != 0)
  318. {
  319. String portName (snd_seq_port_info_get_name (portInfo));
  320. auto portID = snd_seq_port_info_get_port (portInfo);
  321. MidiDeviceInfo device (portName, getFormattedPortIdentifier (sourceClient, portID));
  322. devices.add (device);
  323. if (deviceIdentifierToOpen.isNotEmpty() && deviceIdentifierToOpen == device.identifier)
  324. {
  325. if (portID != -1)
  326. {
  327. port = client->createPort (portName, forInput, false);
  328. jassert (port->isValid());
  329. port->connectWith (sourceClient, portID);
  330. break;
  331. }
  332. }
  333. }
  334. }
  335. return port;
  336. }
  337. static AlsaClient::Port* iterateMidiDevices (bool forInput,
  338. Array<MidiDeviceInfo>& devices,
  339. const String& deviceIdentifierToOpen)
  340. {
  341. AlsaClient::Port* port = nullptr;
  342. auto client = AlsaClient::getInstance();
  343. if (auto seqHandle = client->get())
  344. {
  345. snd_seq_system_info_t* systemInfo = nullptr;
  346. snd_seq_client_info_t* clientInfo = nullptr;
  347. snd_seq_system_info_alloca (&systemInfo);
  348. jassert (systemInfo != nullptr);
  349. if (snd_seq_system_info (seqHandle, systemInfo) == 0)
  350. {
  351. snd_seq_client_info_alloca (&clientInfo);
  352. jassert (clientInfo != nullptr);
  353. auto numClients = snd_seq_system_info_get_cur_clients (systemInfo);
  354. while (--numClients >= 0)
  355. {
  356. if (snd_seq_query_next_client (seqHandle, clientInfo) == 0)
  357. {
  358. port = iterateMidiClient (client, clientInfo, forInput,
  359. devices, deviceIdentifierToOpen);
  360. if (port != nullptr)
  361. break;
  362. }
  363. }
  364. }
  365. }
  366. return port;
  367. }
  368. struct AlsaPortPtr
  369. {
  370. explicit AlsaPortPtr (AlsaClient::Port* p)
  371. : ptr (p) {}
  372. ~AlsaPortPtr() noexcept { AlsaClient::getInstance()->deletePort (ptr); }
  373. AlsaClient::Port* ptr = nullptr;
  374. };
  375. //==============================================================================
  376. class MidiInput::Pimpl : public AlsaPortPtr
  377. {
  378. public:
  379. using AlsaPortPtr::AlsaPortPtr;
  380. };
  381. Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
  382. {
  383. Array<MidiDeviceInfo> devices;
  384. iterateMidiDevices (true, devices, {});
  385. return devices;
  386. }
  387. MidiDeviceInfo MidiInput::getDefaultDevice()
  388. {
  389. return getAvailableDevices().getFirst();
  390. }
  391. std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
  392. {
  393. if (deviceIdentifier.isEmpty())
  394. return {};
  395. Array<MidiDeviceInfo> devices;
  396. auto* port = iterateMidiDevices (true, devices, deviceIdentifier);
  397. if (port == nullptr || ! port->isValid())
  398. return {};
  399. jassert (port->isValid());
  400. std::unique_ptr<MidiInput> midiInput (new MidiInput (port->getPortName(), deviceIdentifier));
  401. port->setupInput (midiInput.get(), callback);
  402. midiInput->internal = std::make_unique<Pimpl> (port);
  403. return midiInput;
  404. }
  405. std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
  406. {
  407. auto client = AlsaClient::getInstance();
  408. auto* port = client->createPort (deviceName, true, true);
  409. if (port == nullptr || ! port->isValid())
  410. return {};
  411. std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->getPortId())));
  412. port->setupInput (midiInput.get(), callback);
  413. midiInput->internal = std::make_unique<Pimpl> (port);
  414. return midiInput;
  415. }
  416. StringArray MidiInput::getDevices()
  417. {
  418. StringArray deviceNames;
  419. for (auto& d : getAvailableDevices())
  420. deviceNames.add (d.name);
  421. deviceNames.appendNumbersToDuplicates (true, true);
  422. return deviceNames;
  423. }
  424. int MidiInput::getDefaultDeviceIndex()
  425. {
  426. return 0;
  427. }
  428. std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback)
  429. {
  430. return openDevice (getAvailableDevices()[index].identifier, callback);
  431. }
  432. MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
  433. : deviceInfo (deviceName, deviceIdentifier)
  434. {
  435. }
  436. MidiInput::~MidiInput()
  437. {
  438. stop();
  439. }
  440. void MidiInput::start()
  441. {
  442. internal->ptr->enableCallback (true);
  443. }
  444. void MidiInput::stop()
  445. {
  446. internal->ptr->enableCallback (false);
  447. }
  448. //==============================================================================
  449. class MidiOutput::Pimpl : public AlsaPortPtr
  450. {
  451. public:
  452. using AlsaPortPtr::AlsaPortPtr;
  453. };
  454. Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
  455. {
  456. Array<MidiDeviceInfo> devices;
  457. iterateMidiDevices (false, devices, {});
  458. return devices;
  459. }
  460. MidiDeviceInfo MidiOutput::getDefaultDevice()
  461. {
  462. return getAvailableDevices().getFirst();
  463. }
  464. std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
  465. {
  466. if (deviceIdentifier.isEmpty())
  467. return {};
  468. Array<MidiDeviceInfo> devices;
  469. auto* port = iterateMidiDevices (false, devices, deviceIdentifier);
  470. if (port == nullptr || ! port->isValid())
  471. return {};
  472. std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (port->getPortName(), deviceIdentifier));
  473. port->setupOutput();
  474. midiOutput->internal = std::make_unique<Pimpl> (port);
  475. return midiOutput;
  476. }
  477. std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String& deviceName)
  478. {
  479. auto client = AlsaClient::getInstance();
  480. auto* port = client->createPort (deviceName, false, true);
  481. if (port == nullptr || ! port->isValid())
  482. return {};
  483. std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->getPortId())));
  484. port->setupOutput();
  485. midiOutput->internal = std::make_unique<Pimpl> (port);
  486. return midiOutput;
  487. }
  488. StringArray MidiOutput::getDevices()
  489. {
  490. StringArray deviceNames;
  491. for (auto& d : getAvailableDevices())
  492. deviceNames.add (d.name);
  493. deviceNames.appendNumbersToDuplicates (true, true);
  494. return deviceNames;
  495. }
  496. int MidiOutput::getDefaultDeviceIndex()
  497. {
  498. return 0;
  499. }
  500. std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index)
  501. {
  502. return openDevice (getAvailableDevices()[index].identifier);
  503. }
  504. MidiOutput::~MidiOutput()
  505. {
  506. stopBackgroundThread();
  507. }
  508. void MidiOutput::sendMessageNow (const MidiMessage& message)
  509. {
  510. internal->ptr->sendMessageNow (message);
  511. }
  512. //==============================================================================
  513. #else
  514. class MidiInput::Pimpl {};
  515. // (These are just stub functions if ALSA is unavailable...)
  516. MidiInput::MidiInput (const String& deviceName, const String& deviceID)
  517. : deviceInfo (deviceName, deviceID)
  518. {
  519. }
  520. MidiInput::~MidiInput() {}
  521. void MidiInput::start() {}
  522. void MidiInput::stop() {}
  523. Array<MidiDeviceInfo> MidiInput::getAvailableDevices() { return {}; }
  524. MidiDeviceInfo MidiInput::getDefaultDevice() { return {}; }
  525. std::unique_ptr<MidiInput> MidiInput::openDevice (const String&, MidiInputCallback*) { return {}; }
  526. std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String&, MidiInputCallback*) { return {}; }
  527. StringArray MidiInput::getDevices() { return {}; }
  528. int MidiInput::getDefaultDeviceIndex() { return 0;}
  529. std::unique_ptr<MidiInput> MidiInput::openDevice (int, MidiInputCallback*) { return {}; }
  530. class MidiOutput::Pimpl {};
  531. MidiOutput::~MidiOutput() {}
  532. void MidiOutput::sendMessageNow (const MidiMessage&) {}
  533. Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; }
  534. MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; }
  535. std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String&) { return {}; }
  536. std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String&) { return {}; }
  537. StringArray MidiOutput::getDevices() { return {}; }
  538. int MidiOutput::getDefaultDeviceIndex() { return 0;}
  539. std::unique_ptr<MidiOutput> MidiOutput::openDevice (int) { return {}; }
  540. #endif
  541. } // namespace juce