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.

599 lines
19KB

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