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