1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 #if JUCE_ALSA
27 
28 //==============================================================================
29 class AlsaClient  : public ReferenceCountedObject
30 {
31 public:
AlsaClient()32     AlsaClient()
33     {
34         jassert (instance == nullptr);
35 
36         snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
37 
38         if (handle != nullptr)
39         {
40             snd_seq_nonblock (handle, SND_SEQ_NONBLOCK);
41             snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8());
42             clientId = snd_seq_client_id (handle);
43 
44             // It's good idea to pre-allocate a good number of elements
45             ports.ensureStorageAllocated (32);
46         }
47     }
48 
~AlsaClient()49     ~AlsaClient()
50     {
51         jassert (instance != nullptr);
52         instance = nullptr;
53 
54         if (handle != nullptr)
55             snd_seq_close (handle);
56 
57         jassert (activeCallbacks.get() == 0);
58 
59         if (inputThread)
60             inputThread->stopThread (3000);
61     }
62 
getAlsaMidiName()63     static String getAlsaMidiName()
64     {
65         #ifdef JUCE_ALSA_MIDI_NAME
66          return JUCE_ALSA_MIDI_NAME;
67         #else
68          if (auto* app = JUCEApplicationBase::getInstance())
69              return app->getApplicationName();
70 
71          return "JUCE";
72         #endif
73     }
74 
75     using Ptr = ReferenceCountedObjectPtr<AlsaClient>;
76 
77     //==============================================================================
78     // represents an input or output port of the supplied AlsaClient
79     struct Port
80     {
Portjuce::AlsaClient::Port81         Port (AlsaClient& c, bool forInput) noexcept
82             : client (c), isInput (forInput)
83         {}
84 
~Portjuce::AlsaClient::Port85         ~Port()
86         {
87             if (isValid())
88             {
89                 if (isInput)
90                     enableCallback (false);
91                 else
92                     snd_midi_event_free (midiParser);
93 
94                 snd_seq_delete_simple_port (client.get(), portId);
95             }
96         }
97 
connectWithjuce::AlsaClient::Port98         void connectWith (int sourceClient, int sourcePort) const noexcept
99         {
100             if (isInput)
101                 snd_seq_connect_from (client.get(), portId, sourceClient, sourcePort);
102             else
103                 snd_seq_connect_to (client.get(), portId, sourceClient, sourcePort);
104         }
105 
isValidjuce::AlsaClient::Port106         bool isValid() const noexcept
107         {
108             return client.get() != nullptr && portId >= 0;
109         }
110 
setupInputjuce::AlsaClient::Port111         void setupInput (MidiInput* input, MidiInputCallback* cb)
112         {
113             jassert (cb != nullptr && input != nullptr);
114             callback = cb;
115             midiInput = input;
116         }
117 
setupOutputjuce::AlsaClient::Port118         void setupOutput()
119         {
120             jassert (! isInput);
121             snd_midi_event_new ((size_t) maxEventSize, &midiParser);
122         }
123 
enableCallbackjuce::AlsaClient::Port124         void enableCallback (bool enable)
125         {
126             if (callbackEnabled != enable)
127             {
128                 callbackEnabled = enable;
129 
130                 if (enable)
131                     client.registerCallback();
132                 else
133                     client.unregisterCallback();
134             }
135         }
136 
sendMessageNowjuce::AlsaClient::Port137         bool sendMessageNow (const MidiMessage& message)
138         {
139             if (message.getRawDataSize() > maxEventSize)
140             {
141                 maxEventSize = message.getRawDataSize();
142                 snd_midi_event_free (midiParser);
143                 snd_midi_event_new ((size_t) maxEventSize, &midiParser);
144             }
145 
146             snd_seq_event_t event;
147             snd_seq_ev_clear (&event);
148 
149             auto numBytes = (long) message.getRawDataSize();
150             auto* data = message.getRawData();
151 
152             auto seqHandle = client.get();
153             bool success = true;
154 
155             while (numBytes > 0)
156             {
157                 auto numSent = snd_midi_event_encode (midiParser, data, numBytes, &event);
158 
159                 if (numSent <= 0)
160                 {
161                     success = numSent == 0;
162                     break;
163                 }
164 
165                 numBytes -= numSent;
166                 data += numSent;
167 
168                 snd_seq_ev_set_source (&event, (unsigned char) portId);
169                 snd_seq_ev_set_subs (&event);
170                 snd_seq_ev_set_direct (&event);
171 
172                 if (snd_seq_event_output_direct (seqHandle, &event) < 0)
173                 {
174                     success = false;
175                     break;
176                 }
177             }
178 
179             snd_midi_event_reset_encode (midiParser);
180             return success;
181         }
182 
183 
operator ==juce::AlsaClient::Port184         bool operator== (const Port& lhs) const noexcept
185         {
186             return portId != -1 && portId == lhs.portId;
187         }
188 
createPortjuce::AlsaClient::Port189         void createPort (const String& name, bool enableSubscription)
190         {
191             if (auto seqHandle = client.get())
192             {
193                 const unsigned int caps =
194                     isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0))
195                             : (SND_SEQ_PORT_CAP_READ  | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0));
196 
197                 portName = name;
198                 portId = snd_seq_create_simple_port (seqHandle, portName.toUTF8(), caps,
199                                                      SND_SEQ_PORT_TYPE_MIDI_GENERIC |
200                                                      SND_SEQ_PORT_TYPE_APPLICATION);
201             }
202         }
203 
handleIncomingMidiMessagejuce::AlsaClient::Port204         void handleIncomingMidiMessage (const MidiMessage& message) const
205         {
206             callback->handleIncomingMidiMessage (midiInput, message);
207         }
208 
handlePartialSysexMessagejuce::AlsaClient::Port209         void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp)
210         {
211             callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp);
212         }
213 
214         AlsaClient& client;
215 
216         MidiInputCallback* callback = nullptr;
217         snd_midi_event_t* midiParser = nullptr;
218         MidiInput* midiInput = nullptr;
219 
220         String portName;
221 
222         int maxEventSize = 4096, portId = -1;
223         bool callbackEnabled = false, isInput = false;
224     };
225 
getInstance()226     static Ptr getInstance()
227     {
228         if (instance == nullptr)
229             instance = new AlsaClient();
230 
231         return instance;
232     }
233 
registerCallback()234     void registerCallback()
235     {
236         if (inputThread == nullptr)
237             inputThread.reset (new MidiInputThread (*this));
238 
239         if (++activeCallbacks == 1)
240             inputThread->startThread();
241     }
242 
unregisterCallback()243     void unregisterCallback()
244     {
245         jassert (activeCallbacks.get() > 0);
246 
247         if (--activeCallbacks == 0 && inputThread->isThreadRunning())
248             inputThread->signalThreadShouldExit();
249     }
250 
handleIncomingMidiMessage(snd_seq_event * event,const MidiMessage & message)251     void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message)
252     {
253         if (event->dest.port < ports.size() && ports[event->dest.port]->callbackEnabled)
254             ports[event->dest.port]->handleIncomingMidiMessage (message);
255     }
256 
handlePartialSysexMessage(snd_seq_event * event,const uint8 * messageData,int numBytesSoFar,double timeStamp)257     void handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp)
258     {
259         if (event->dest.port < ports.size()
260             && ports[event->dest.port]->callbackEnabled)
261             ports[event->dest.port]->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp);
262     }
263 
get() const264     snd_seq_t* get() const noexcept     { return handle; }
getId() const265     int getId() const noexcept          { return clientId; }
266 
createPort(const String & name,bool forInput,bool enableSubscription)267     Port* createPort (const String& name, bool forInput, bool enableSubscription)
268     {
269         auto port = new Port (*this, forInput);
270         port->createPort (name, enableSubscription);
271         ports.set (port->portId, port);
272         incReferenceCount();
273         return port;
274     }
275 
deletePort(Port * port)276     void deletePort (Port* port)
277     {
278         ports.set (port->portId, nullptr);
279         decReferenceCount();
280     }
281 
282 private:
283     snd_seq_t* handle = nullptr;
284     int clientId = 0;
285     OwnedArray<Port> ports;
286     Atomic<int> activeCallbacks;
287     CriticalSection callbackLock;
288 
289     static AlsaClient* instance;
290 
291     //==============================================================================
292     class MidiInputThread   : public Thread
293     {
294     public:
MidiInputThread(AlsaClient & c)295         MidiInputThread (AlsaClient& c)
296             : Thread ("JUCE MIDI Input"), client (c)
297         {
298             jassert (client.get() != nullptr);
299         }
300 
run()301         void run() override
302         {
303             auto seqHandle = client.get();
304 
305             const int maxEventSize = 16 * 1024;
306             snd_midi_event_t* midiParser;
307 
308             if (snd_midi_event_new (maxEventSize, &midiParser) >= 0)
309             {
310                 auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN);
311                 HeapBlock<pollfd> pfd (numPfds);
312                 snd_seq_poll_descriptors (seqHandle, pfd, (unsigned int) numPfds, POLLIN);
313 
314                 HeapBlock<uint8> buffer (maxEventSize);
315 
316                 while (! threadShouldExit())
317                 {
318                     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
319                     {
320                         if (threadShouldExit())
321                             break;
322 
323                         do
324                         {
325                             snd_seq_event_t* inputEvent = nullptr;
326 
327                             if (snd_seq_event_input (seqHandle, &inputEvent) >= 0)
328                             {
329                                 // xxx what about SYSEXes that are too big for the buffer?
330                                 auto numBytes = snd_midi_event_decode (midiParser, buffer,
331                                                                        maxEventSize, inputEvent);
332 
333                                 snd_midi_event_reset_decode (midiParser);
334 
335                                 concatenator.pushMidiData (buffer, (int) numBytes,
336                                                            Time::getMillisecondCounter() * 0.001,
337                                                            inputEvent, client);
338 
339                                 snd_seq_free_event (inputEvent);
340                             }
341                         }
342                         while (snd_seq_event_input_pending (seqHandle, 0) > 0);
343                     }
344                 }
345 
346                 snd_midi_event_free (midiParser);
347             }
348         }
349 
350     private:
351         AlsaClient& client;
352         MidiDataConcatenator concatenator { 2048 };
353     };
354 
355     std::unique_ptr<MidiInputThread> inputThread;
356 };
357 
358 AlsaClient* AlsaClient::instance = nullptr;
359 
360 //==============================================================================
getFormattedPortIdentifier(int clientId,int portId)361 static String getFormattedPortIdentifier (int clientId, int portId)
362 {
363     return String (clientId) + "-" + String (portId);
364 }
365 
iterateMidiClient(const AlsaClient::Ptr & client,snd_seq_client_info_t * clientInfo,bool forInput,Array<MidiDeviceInfo> & devices,const String & deviceIdentifierToOpen)366 static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client,
367                                             snd_seq_client_info_t* clientInfo,
368                                             bool forInput,
369                                             Array<MidiDeviceInfo>& devices,
370                                             const String& deviceIdentifierToOpen)
371 {
372     AlsaClient::Port* port = nullptr;
373 
374     auto seqHandle = client->get();
375     snd_seq_port_info_t* portInfo = nullptr;
376 
377     snd_seq_port_info_alloca (&portInfo);
378     jassert (portInfo != nullptr);
379     auto numPorts = snd_seq_client_info_get_num_ports (clientInfo);
380     auto sourceClient = snd_seq_client_info_get_client (clientInfo);
381 
382     snd_seq_port_info_set_client (portInfo, sourceClient);
383     snd_seq_port_info_set_port (portInfo, -1);
384 
385     while (--numPorts >= 0)
386     {
387         if (snd_seq_query_next_port (seqHandle, portInfo) == 0
388             && (snd_seq_port_info_get_capability (portInfo)
389                 & (forInput ? SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_SUBS_WRITE)) != 0)
390         {
391             String portName (snd_seq_port_info_get_name (portInfo));
392             auto portID = snd_seq_port_info_get_port (portInfo);
393 
394             MidiDeviceInfo device (portName, getFormattedPortIdentifier (sourceClient, portID));
395             devices.add (device);
396 
397             if (deviceIdentifierToOpen.isNotEmpty() && deviceIdentifierToOpen == device.identifier)
398             {
399                 if (portID != -1)
400                 {
401                     port = client->createPort (portName, forInput, false);
402                     jassert (port->isValid());
403                     port->connectWith (sourceClient, portID);
404                     break;
405                 }
406             }
407         }
408     }
409 
410     return port;
411 }
412 
iterateMidiDevices(bool forInput,Array<MidiDeviceInfo> & devices,const String & deviceIdentifierToOpen)413 static AlsaClient::Port* iterateMidiDevices (bool forInput,
414                                              Array<MidiDeviceInfo>& devices,
415                                              const String& deviceIdentifierToOpen)
416 {
417     AlsaClient::Port* port = nullptr;
418     auto client = AlsaClient::getInstance();
419 
420     if (auto seqHandle = client->get())
421     {
422         snd_seq_system_info_t* systemInfo = nullptr;
423         snd_seq_client_info_t* clientInfo = nullptr;
424 
425         snd_seq_system_info_alloca (&systemInfo);
426         jassert (systemInfo != nullptr);
427 
428         if (snd_seq_system_info (seqHandle, systemInfo) == 0)
429         {
430             snd_seq_client_info_alloca (&clientInfo);
431             jassert (clientInfo != nullptr);
432 
433             auto numClients = snd_seq_system_info_get_cur_clients (systemInfo);
434 
435             while (--numClients >= 0)
436             {
437                 if (snd_seq_query_next_client (seqHandle, clientInfo) == 0)
438                 {
439                     port = iterateMidiClient (client, clientInfo, forInput,
440                                               devices, deviceIdentifierToOpen);
441 
442                     if (port != nullptr)
443                         break;
444                 }
445             }
446         }
447     }
448 
449     return port;
450 }
451 
452 struct AlsaPortPtr
453 {
AlsaPortPtrjuce::AlsaPortPtr454     explicit AlsaPortPtr (AlsaClient::Port* p)
455         : ptr (p) {}
456 
~AlsaPortPtrjuce::AlsaPortPtr457     ~AlsaPortPtr() noexcept { AlsaClient::getInstance()->deletePort (ptr); }
458 
459     AlsaClient::Port* ptr = nullptr;
460 };
461 
462 //==============================================================================
463 class MidiInput::Pimpl : public AlsaPortPtr
464 {
465 public:
466     using AlsaPortPtr::AlsaPortPtr;
467 };
468 
getAvailableDevices()469 Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
470 {
471     Array<MidiDeviceInfo> devices;
472     iterateMidiDevices (true, devices, {});
473 
474     return devices;
475 }
476 
getDefaultDevice()477 MidiDeviceInfo MidiInput::getDefaultDevice()
478 {
479     return getAvailableDevices().getFirst();
480 }
481 
openDevice(const String & deviceIdentifier,MidiInputCallback * callback)482 std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
483 {
484     if (deviceIdentifier.isEmpty())
485         return {};
486 
487     Array<MidiDeviceInfo> devices;
488     auto* port = iterateMidiDevices (true, devices, deviceIdentifier);
489 
490     if (port == nullptr || ! port->isValid())
491         return {};
492 
493     jassert (port->isValid());
494 
495     std::unique_ptr<MidiInput> midiInput (new MidiInput (port->portName, deviceIdentifier));
496 
497     port->setupInput (midiInput.get(), callback);
498     midiInput->internal = std::make_unique<Pimpl> (port);
499 
500     return midiInput;
501 }
502 
createNewDevice(const String & deviceName,MidiInputCallback * callback)503 std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
504 {
505     auto client = AlsaClient::getInstance();
506     auto* port = client->createPort (deviceName, true, true);
507 
508     if (port == nullptr || ! port->isValid())
509         return {};
510 
511     std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId)));
512 
513     port->setupInput (midiInput.get(), callback);
514     midiInput->internal = std::make_unique<Pimpl> (port);
515 
516     return midiInput;
517 }
518 
getDevices()519 StringArray MidiInput::getDevices()
520 {
521     StringArray deviceNames;
522 
523     for (auto& d : getAvailableDevices())
524         deviceNames.add (d.name);
525 
526     deviceNames.appendNumbersToDuplicates (true, true);
527 
528     return deviceNames;
529 }
530 
getDefaultDeviceIndex()531 int MidiInput::getDefaultDeviceIndex()
532 {
533     return 0;
534 }
535 
openDevice(int index,MidiInputCallback * callback)536 std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback)
537 {
538     return openDevice (getAvailableDevices()[index].identifier, callback);
539 }
540 
MidiInput(const String & deviceName,const String & deviceIdentifier)541 MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
542     : deviceInfo (deviceName, deviceIdentifier)
543 {
544 }
545 
~MidiInput()546 MidiInput::~MidiInput()
547 {
548     stop();
549 }
550 
start()551 void MidiInput::start()
552 {
553     internal->ptr->enableCallback (true);
554 }
555 
stop()556 void MidiInput::stop()
557 {
558     internal->ptr->enableCallback (false);
559 }
560 
561 //==============================================================================
562 class MidiOutput::Pimpl : public AlsaPortPtr
563 {
564 public:
565     using AlsaPortPtr::AlsaPortPtr;
566 };
567 
getAvailableDevices()568 Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
569 {
570     Array<MidiDeviceInfo> devices;
571     iterateMidiDevices (false, devices, {});
572 
573     return devices;
574 }
575 
getDefaultDevice()576 MidiDeviceInfo MidiOutput::getDefaultDevice()
577 {
578     return getAvailableDevices().getFirst();
579 }
580 
openDevice(const String & deviceIdentifier)581 std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
582 {
583     if (deviceIdentifier.isEmpty())
584         return {};
585 
586     Array<MidiDeviceInfo> devices;
587     auto* port = iterateMidiDevices (false, devices, deviceIdentifier);
588 
589     if (port == nullptr || ! port->isValid())
590         return {};
591 
592     std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (port->portName, deviceIdentifier));
593 
594     port->setupOutput();
595     midiOutput->internal = std::make_unique<Pimpl> (port);
596 
597     return midiOutput;
598 }
599 
createNewDevice(const String & deviceName)600 std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String& deviceName)
601 {
602     auto client = AlsaClient::getInstance();
603     auto* port = client->createPort (deviceName, false, true);
604 
605     if (port == nullptr || ! port->isValid())
606         return {};
607 
608     std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId)));
609 
610     port->setupOutput();
611     midiOutput->internal = std::make_unique<Pimpl> (port);
612 
613     return midiOutput;
614 }
615 
getDevices()616 StringArray MidiOutput::getDevices()
617 {
618     StringArray deviceNames;
619 
620     for (auto& d : getAvailableDevices())
621         deviceNames.add (d.name);
622 
623     deviceNames.appendNumbersToDuplicates (true, true);
624 
625     return deviceNames;
626 }
627 
getDefaultDeviceIndex()628 int MidiOutput::getDefaultDeviceIndex()
629 {
630     return 0;
631 }
632 
openDevice(int index)633 std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index)
634 {
635     return openDevice (getAvailableDevices()[index].identifier);
636 }
637 
~MidiOutput()638 MidiOutput::~MidiOutput()
639 {
640     stopBackgroundThread();
641 }
642 
sendMessageNow(const MidiMessage & message)643 void MidiOutput::sendMessageNow (const MidiMessage& message)
644 {
645     internal->ptr->sendMessageNow (message);
646 }
647 
648 //==============================================================================
649 #else
650 
651 class MidiInput::Pimpl {};
652 
653 // (These are just stub functions if ALSA is unavailable...)
654 MidiInput::MidiInput (const String& deviceName, const String& deviceID)
655     : deviceInfo (deviceName, deviceID)
656 {
657 }
658 
659 MidiInput::~MidiInput()                                                                   {}
660 void MidiInput::start()                                                                   {}
661 void MidiInput::stop()                                                                    {}
662 Array<MidiDeviceInfo> MidiInput::getAvailableDevices()                                    { return {}; }
663 MidiDeviceInfo MidiInput::getDefaultDevice()                                              { return {}; }
664 std::unique_ptr<MidiInput> MidiInput::openDevice (const String&, MidiInputCallback*)      { return {}; }
665 std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String&, MidiInputCallback*) { return {}; }
666 StringArray MidiInput::getDevices()                                                       { return {}; }
667 int MidiInput::getDefaultDeviceIndex()                                                    { return 0;}
668 std::unique_ptr<MidiInput> MidiInput::openDevice (int, MidiInputCallback*)                { return {}; }
669 
670 class MidiOutput::Pimpl {};
671 
672 MidiOutput::~MidiOutput()                                                                 {}
673 void MidiOutput::sendMessageNow (const MidiMessage&)                                      {}
674 Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()                                   { return {}; }
675 MidiDeviceInfo MidiOutput::getDefaultDevice()                                             { return {}; }
676 std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String&)                        { return {}; }
677 std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String&)                   { return {}; }
678 StringArray MidiOutput::getDevices()                                                      { return {}; }
679 int MidiOutput::getDefaultDeviceIndex()                                                   { return 0;}
680 std::unique_ptr<MidiOutput> MidiOutput::openDevice (int)                                  { return {}; }
681 
682 #endif
683 
684 } // namespace juce
685