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.

638 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #if JUCE_ALSA
  18. // You can define these strings in your app if you want to override the default names:
  19. #ifndef JUCE_ALSA_MIDI_INPUT_NAME
  20. #define JUCE_ALSA_MIDI_INPUT_NAME "Juce Midi Input"
  21. #endif
  22. #ifndef JUCE_ALSA_MIDI_OUTPUT_NAME
  23. #define JUCE_ALSA_MIDI_OUTPUT_NAME "Juce Midi Output"
  24. #endif
  25. //==============================================================================
  26. namespace
  27. {
  28. class AlsaPortAndCallback;
  29. //==============================================================================
  30. class AlsaClient : public ReferenceCountedObject
  31. {
  32. public:
  33. typedef ReferenceCountedObjectPtr<AlsaClient> Ptr;
  34. static Ptr getInstance (bool forInput)
  35. {
  36. AlsaClient*& instance = (forInput ? inInstance : outInstance);
  37. if (instance == nullptr)
  38. instance = new AlsaClient (forInput);
  39. return instance;
  40. }
  41. bool isInput() const noexcept { return input; }
  42. void registerCallback (AlsaPortAndCallback* cb)
  43. {
  44. if (cb != nullptr)
  45. {
  46. {
  47. const ScopedLock sl (callbackLock);
  48. activeCallbacks.add (cb);
  49. if (inputThread == nullptr)
  50. inputThread = new MidiInputThread (*this);
  51. }
  52. inputThread->startThread();
  53. }
  54. }
  55. void unregisterCallback (AlsaPortAndCallback* cb)
  56. {
  57. const ScopedLock sl (callbackLock);
  58. jassert (activeCallbacks.contains (cb));
  59. activeCallbacks.removeAllInstancesOf (cb);
  60. if (activeCallbacks.size() == 0 && inputThread->isThreadRunning())
  61. inputThread->signalThreadShouldExit();
  62. }
  63. void handleIncomingMidiMessage (snd_seq_event*, const MidiMessage&);
  64. void handlePartialSysexMessage (snd_seq_event*, const uint8*, int, double);
  65. snd_seq_t* get() const noexcept { return handle; }
  66. private:
  67. bool input;
  68. snd_seq_t* handle;
  69. Array<AlsaPortAndCallback*> activeCallbacks;
  70. CriticalSection callbackLock;
  71. static AlsaClient* inInstance;
  72. static AlsaClient* outInstance;
  73. //==============================================================================
  74. friend class ReferenceCountedObjectPtr<AlsaClient>;
  75. friend struct ContainerDeletePolicy<AlsaClient>;
  76. AlsaClient (bool forInput)
  77. : input (forInput), handle (nullptr)
  78. {
  79. AlsaClient*& instance = (input ? inInstance : outInstance);
  80. jassert (instance == nullptr);
  81. instance = this;
  82. snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT
  83. : SND_SEQ_OPEN_OUTPUT, 0);
  84. snd_seq_set_client_name (handle, forInput ? JUCE_ALSA_MIDI_INPUT_NAME
  85. : JUCE_ALSA_MIDI_OUTPUT_NAME);
  86. }
  87. ~AlsaClient()
  88. {
  89. AlsaClient*& instance = (input ? inInstance : outInstance);
  90. jassert (instance != nullptr);
  91. instance = nullptr;
  92. if (handle != nullptr)
  93. {
  94. snd_seq_close (handle);
  95. handle = nullptr;
  96. }
  97. jassert (activeCallbacks.size() == 0);
  98. if (inputThread)
  99. {
  100. inputThread->stopThread (3000);
  101. inputThread = nullptr;
  102. }
  103. }
  104. //==============================================================================
  105. class MidiInputThread : public Thread
  106. {
  107. public:
  108. MidiInputThread (AlsaClient& c)
  109. : Thread ("Juce MIDI Input"), client (c), concatenator (2048)
  110. {
  111. jassert (client.input && client.get() != nullptr);
  112. }
  113. void run() override
  114. {
  115. const int maxEventSize = 16 * 1024;
  116. snd_midi_event_t* midiParser;
  117. snd_seq_t* seqHandle = client.get();
  118. if (snd_midi_event_new (maxEventSize, &midiParser) >= 0)
  119. {
  120. const int numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN);
  121. HeapBlock<pollfd> pfd ((size_t) numPfds);
  122. snd_seq_poll_descriptors (seqHandle, pfd, (unsigned int) numPfds, POLLIN);
  123. HeapBlock<uint8> buffer (maxEventSize);
  124. while (! threadShouldExit())
  125. {
  126. 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
  127. {
  128. if (threadShouldExit())
  129. break;
  130. snd_seq_nonblock (seqHandle, 1);
  131. do
  132. {
  133. snd_seq_event_t* inputEvent = nullptr;
  134. if (snd_seq_event_input (seqHandle, &inputEvent) >= 0)
  135. {
  136. // xxx what about SYSEXes that are too big for the buffer?
  137. const long numBytes = snd_midi_event_decode (midiParser, buffer,
  138. maxEventSize, inputEvent);
  139. snd_midi_event_reset_decode (midiParser);
  140. concatenator.pushMidiData (buffer, (int) numBytes,
  141. Time::getMillisecondCounter() * 0.001,
  142. inputEvent, client);
  143. snd_seq_free_event (inputEvent);
  144. }
  145. }
  146. while (snd_seq_event_input_pending (seqHandle, 0) > 0);
  147. }
  148. }
  149. snd_midi_event_free (midiParser);
  150. }
  151. };
  152. private:
  153. AlsaClient& client;
  154. MidiDataConcatenator concatenator;
  155. };
  156. ScopedPointer<MidiInputThread> inputThread;
  157. };
  158. AlsaClient* AlsaClient::inInstance = nullptr;
  159. AlsaClient* AlsaClient::outInstance = nullptr;
  160. //==============================================================================
  161. // represents an input or output port of the supplied AlsaClient
  162. class AlsaPort
  163. {
  164. public:
  165. AlsaPort() noexcept : portId (-1) {}
  166. AlsaPort (const AlsaClient::Ptr& c, int port) noexcept : client (c), portId (port) {}
  167. void createPort (const AlsaClient::Ptr& c, const String& name, bool forInput)
  168. {
  169. client = c;
  170. if (snd_seq_t* handle = client->get())
  171. portId = snd_seq_create_simple_port (handle, name.toUTF8(),
  172. forInput ? (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)
  173. : (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ),
  174. SND_SEQ_PORT_TYPE_MIDI_GENERIC);
  175. }
  176. void deletePort()
  177. {
  178. if (isValid())
  179. {
  180. snd_seq_delete_simple_port (client->get(), portId);
  181. portId = -1;
  182. }
  183. }
  184. void connectWith (int sourceClient, int sourcePort)
  185. {
  186. if (client->isInput())
  187. snd_seq_connect_from (client->get(), portId, sourceClient, sourcePort);
  188. else
  189. snd_seq_connect_to (client->get(), portId, sourceClient, sourcePort);
  190. }
  191. bool isValid() const noexcept
  192. {
  193. return client != nullptr && client->get() != nullptr && portId >= 0;
  194. }
  195. AlsaClient::Ptr client;
  196. int portId;
  197. };
  198. //==============================================================================
  199. class AlsaPortAndCallback
  200. {
  201. public:
  202. AlsaPortAndCallback (AlsaPort p, MidiInput* in, MidiInputCallback* cb)
  203. : port (p), midiInput (in), callback (cb), callbackEnabled (false)
  204. {
  205. }
  206. ~AlsaPortAndCallback()
  207. {
  208. enableCallback (false);
  209. port.deletePort();
  210. }
  211. void enableCallback (bool enable)
  212. {
  213. if (callbackEnabled != enable)
  214. {
  215. callbackEnabled = enable;
  216. if (enable)
  217. port.client->registerCallback (this);
  218. else
  219. port.client->unregisterCallback (this);
  220. }
  221. }
  222. void handleIncomingMidiMessage (const MidiMessage& message) const
  223. {
  224. callback->handleIncomingMidiMessage (midiInput, message);
  225. }
  226. void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp)
  227. {
  228. callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp);
  229. }
  230. private:
  231. AlsaPort port;
  232. MidiInput* midiInput;
  233. MidiInputCallback* callback;
  234. bool callbackEnabled;
  235. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlsaPortAndCallback)
  236. };
  237. void AlsaClient::handleIncomingMidiMessage (snd_seq_event_t* event, const MidiMessage& message)
  238. {
  239. const ScopedLock sl (callbackLock);
  240. if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port])
  241. cb->handleIncomingMidiMessage (message);
  242. }
  243. void AlsaClient::handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp)
  244. {
  245. const ScopedLock sl (callbackLock);
  246. if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port])
  247. cb->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp);
  248. }
  249. //==============================================================================
  250. static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq,
  251. snd_seq_client_info_t* clientInfo,
  252. const bool forInput,
  253. StringArray& deviceNamesFound,
  254. const int deviceIndexToOpen)
  255. {
  256. AlsaPort port;
  257. snd_seq_t* seqHandle = seq->get();
  258. snd_seq_port_info_t* portInfo = nullptr;
  259. if (snd_seq_port_info_malloc (&portInfo) == 0)
  260. {
  261. int numPorts = snd_seq_client_info_get_num_ports (clientInfo);
  262. const int client = snd_seq_client_info_get_client (clientInfo);
  263. snd_seq_port_info_set_client (portInfo, client);
  264. snd_seq_port_info_set_port (portInfo, -1);
  265. while (--numPorts >= 0)
  266. {
  267. if (snd_seq_query_next_port (seqHandle, portInfo) == 0
  268. && (snd_seq_port_info_get_capability (portInfo) & (forInput ? SND_SEQ_PORT_CAP_READ
  269. : SND_SEQ_PORT_CAP_WRITE)) != 0)
  270. {
  271. const String clientName = snd_seq_client_info_get_name (clientInfo);
  272. const String portName = snd_seq_port_info_get_name(portInfo);
  273. if (clientName == portName)
  274. deviceNamesFound.add (clientName);
  275. else
  276. deviceNamesFound.add (clientName + ": " + portName);
  277. if (deviceNamesFound.size() == deviceIndexToOpen + 1)
  278. {
  279. const int sourcePort = snd_seq_port_info_get_port (portInfo);
  280. if (sourcePort != -1)
  281. {
  282. const int sourceClient = snd_seq_client_info_get_client (clientInfo);
  283. port.createPort (seq, portName, forInput);
  284. port.connectWith (sourceClient, sourcePort);
  285. }
  286. }
  287. }
  288. }
  289. snd_seq_port_info_free (portInfo);
  290. }
  291. return port;
  292. }
  293. static AlsaPort iterateMidiDevices (const bool forInput,
  294. StringArray& deviceNamesFound,
  295. const int deviceIndexToOpen)
  296. {
  297. AlsaPort port;
  298. const AlsaClient::Ptr client (AlsaClient::getInstance (forInput));
  299. if (snd_seq_t* const seqHandle = client->get())
  300. {
  301. snd_seq_system_info_t* systemInfo = nullptr;
  302. snd_seq_client_info_t* clientInfo = nullptr;
  303. if (snd_seq_system_info_malloc (&systemInfo) == 0)
  304. {
  305. if (snd_seq_system_info (seqHandle, systemInfo) == 0
  306. && snd_seq_client_info_malloc (&clientInfo) == 0)
  307. {
  308. int numClients = snd_seq_system_info_get_cur_clients (systemInfo);
  309. while (--numClients >= 0 && ! port.isValid())
  310. if (snd_seq_query_next_client (seqHandle, clientInfo) == 0)
  311. port = iterateMidiClient (client, clientInfo, forInput,
  312. deviceNamesFound, deviceIndexToOpen);
  313. snd_seq_client_info_free (clientInfo);
  314. }
  315. snd_seq_system_info_free (systemInfo);
  316. }
  317. }
  318. deviceNamesFound.appendNumbersToDuplicates (true, true);
  319. return port;
  320. }
  321. //==============================================================================
  322. class MidiOutputDevice
  323. {
  324. public:
  325. MidiOutputDevice (MidiOutput* const output, const AlsaPort& p)
  326. : midiOutput (output), port (p),
  327. maxEventSize (16 * 1024)
  328. {
  329. jassert (port.isValid() && midiOutput != nullptr);
  330. snd_midi_event_new ((size_t) maxEventSize, &midiParser);
  331. }
  332. ~MidiOutputDevice()
  333. {
  334. snd_midi_event_free (midiParser);
  335. port.deletePort();
  336. }
  337. bool sendMessageNow (const MidiMessage& message)
  338. {
  339. if (message.getRawDataSize() > maxEventSize)
  340. {
  341. maxEventSize = message.getRawDataSize();
  342. snd_midi_event_free (midiParser);
  343. snd_midi_event_new ((size_t) maxEventSize, &midiParser);
  344. }
  345. snd_seq_event_t event;
  346. snd_seq_ev_clear (&event);
  347. long numBytes = (long) message.getRawDataSize();
  348. const uint8* data = message.getRawData();
  349. snd_seq_t* seqHandle = port.client->get();
  350. bool success = true;
  351. while (numBytes > 0)
  352. {
  353. const long numSent = snd_midi_event_encode (midiParser, data, numBytes, &event);
  354. if (numSent <= 0)
  355. {
  356. success = numSent == 0;
  357. break;
  358. }
  359. numBytes -= numSent;
  360. data += numSent;
  361. snd_seq_ev_set_source (&event, port.portId);
  362. snd_seq_ev_set_subs (&event);
  363. snd_seq_ev_set_direct (&event);
  364. if (snd_seq_event_output_direct (seqHandle, &event) < 0)
  365. {
  366. success = false;
  367. break;
  368. }
  369. }
  370. snd_midi_event_reset_encode (midiParser);
  371. return success;
  372. }
  373. private:
  374. MidiOutput* const midiOutput;
  375. AlsaPort port;
  376. snd_midi_event_t* midiParser;
  377. int maxEventSize;
  378. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutputDevice)
  379. };
  380. } // namespace
  381. StringArray MidiOutput::getDevices()
  382. {
  383. StringArray devices;
  384. iterateMidiDevices (false, devices, -1);
  385. return devices;
  386. }
  387. int MidiOutput::getDefaultDeviceIndex()
  388. {
  389. return 0;
  390. }
  391. MidiOutput* MidiOutput::openDevice (int deviceIndex)
  392. {
  393. MidiOutput* newDevice = nullptr;
  394. StringArray devices;
  395. AlsaPort port (iterateMidiDevices (false, devices, deviceIndex));
  396. if (port.isValid())
  397. {
  398. newDevice = new MidiOutput (devices [deviceIndex]);
  399. newDevice->internal = new MidiOutputDevice (newDevice, port);
  400. }
  401. return newDevice;
  402. }
  403. MidiOutput* MidiOutput::createNewDevice (const String& deviceName)
  404. {
  405. MidiOutput* newDevice = nullptr;
  406. AlsaPort port;
  407. const AlsaClient::Ptr client (AlsaClient::getInstance (false));
  408. port.createPort (client, deviceName, false);
  409. if (port.isValid())
  410. {
  411. newDevice = new MidiOutput (deviceName);
  412. newDevice->internal = new MidiOutputDevice (newDevice, port);
  413. }
  414. return newDevice;
  415. }
  416. MidiOutput::~MidiOutput()
  417. {
  418. stopBackgroundThread();
  419. delete static_cast<MidiOutputDevice*> (internal);
  420. }
  421. void MidiOutput::sendMessageNow (const MidiMessage& message)
  422. {
  423. static_cast<MidiOutputDevice*> (internal)->sendMessageNow (message);
  424. }
  425. //==============================================================================
  426. MidiInput::MidiInput (const String& nm)
  427. : name (nm), internal (nullptr)
  428. {
  429. }
  430. MidiInput::~MidiInput()
  431. {
  432. stop();
  433. delete static_cast<AlsaPortAndCallback*> (internal);
  434. }
  435. void MidiInput::start()
  436. {
  437. static_cast<AlsaPortAndCallback*> (internal)->enableCallback (true);
  438. }
  439. void MidiInput::stop()
  440. {
  441. static_cast<AlsaPortAndCallback*> (internal)->enableCallback (false);
  442. }
  443. int MidiInput::getDefaultDeviceIndex()
  444. {
  445. return 0;
  446. }
  447. StringArray MidiInput::getDevices()
  448. {
  449. StringArray devices;
  450. iterateMidiDevices (true, devices, -1);
  451. return devices;
  452. }
  453. MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback)
  454. {
  455. MidiInput* newDevice = nullptr;
  456. StringArray devices;
  457. AlsaPort port (iterateMidiDevices (true, devices, deviceIndex));
  458. if (port.isValid())
  459. {
  460. newDevice = new MidiInput (devices [deviceIndex]);
  461. newDevice->internal = new AlsaPortAndCallback (port, newDevice, callback);
  462. }
  463. return newDevice;
  464. }
  465. MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
  466. {
  467. MidiInput* newDevice = nullptr;
  468. AlsaPort port;
  469. const AlsaClient::Ptr client (AlsaClient::getInstance (true));
  470. port.createPort (client, deviceName, true);
  471. if (port.isValid())
  472. {
  473. newDevice = new MidiInput (deviceName);
  474. newDevice->internal = new AlsaPortAndCallback (port, newDevice, callback);
  475. }
  476. return newDevice;
  477. }
  478. //==============================================================================
  479. #else
  480. // (These are just stub functions if ALSA is unavailable...)
  481. StringArray MidiOutput::getDevices() { return StringArray(); }
  482. int MidiOutput::getDefaultDeviceIndex() { return 0; }
  483. MidiOutput* MidiOutput::openDevice (int) { return nullptr; }
  484. MidiOutput* MidiOutput::createNewDevice (const String&) { return nullptr; }
  485. MidiOutput::~MidiOutput() {}
  486. void MidiOutput::sendMessageNow (const MidiMessage&) {}
  487. MidiInput::MidiInput (const String& nm) : name (nm), internal (nullptr) {}
  488. MidiInput::~MidiInput() {}
  489. void MidiInput::start() {}
  490. void MidiInput::stop() {}
  491. int MidiInput::getDefaultDeviceIndex() { return 0; }
  492. StringArray MidiInput::getDevices() { return StringArray(); }
  493. MidiInput* MidiInput::openDevice (int, MidiInputCallback*) { return nullptr; }
  494. MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*) { return nullptr; }
  495. #endif