2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
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.
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
20 ==============================================================================
28 //==============================================================================
29 class AlsaClient
: public ReferenceCountedObject
34 jassert (instance
== nullptr);
36 snd_seq_open (&handle
, "default", SND_SEQ_OPEN_DUPLEX
, 0);
38 if (handle
!= nullptr)
40 snd_seq_nonblock (handle
, SND_SEQ_NONBLOCK
);
41 snd_seq_set_client_name (handle
, getAlsaMidiName().toRawUTF8());
42 clientId
= snd_seq_client_id (handle
);
44 // It's good idea to pre-allocate a good number of elements
45 ports
.ensureStorageAllocated (32);
51 jassert (instance
!= nullptr);
54 jassert (activeCallbacks
.get() == 0);
57 inputThread
->stopThread (3000);
59 if (handle
!= nullptr)
60 snd_seq_close (handle
);
63 static String
getAlsaMidiName()
65 #ifdef JUCE_ALSA_MIDI_NAME
66 return JUCE_ALSA_MIDI_NAME
;
68 if (auto* app
= JUCEApplicationBase::getInstance())
69 return app
->getApplicationName();
75 using Ptr
= ReferenceCountedObjectPtr
<AlsaClient
>;
77 //==============================================================================
78 // represents an input or output port of the supplied AlsaClient
81 Port (AlsaClient
& c
, bool forInput
) noexcept
82 : client (c
), isInput (forInput
)
90 enableCallback (false);
92 snd_midi_event_free (midiParser
);
94 snd_seq_delete_simple_port (client
.get(), portId
);
98 void connectWith (int sourceClient
, int sourcePort
) const noexcept
101 snd_seq_connect_from (client
.get(), portId
, sourceClient
, sourcePort
);
103 snd_seq_connect_to (client
.get(), portId
, sourceClient
, sourcePort
);
106 bool isValid() const noexcept
108 return client
.get() != nullptr && portId
>= 0;
111 void setupInput (MidiInput
* input
, MidiInputCallback
* cb
)
113 jassert (cb
!= nullptr && input
!= nullptr);
121 snd_midi_event_new ((size_t) maxEventSize
, &midiParser
);
124 void enableCallback (bool enable
)
126 const auto oldValue
= callbackEnabled
.exchange (enable
);
128 if (oldValue
!= enable
)
131 client
.registerCallback();
133 client
.unregisterCallback();
137 bool sendMessageNow (const MidiMessage
& message
)
139 if (message
.getRawDataSize() > maxEventSize
)
141 maxEventSize
= message
.getRawDataSize();
142 snd_midi_event_free (midiParser
);
143 snd_midi_event_new ((size_t) maxEventSize
, &midiParser
);
146 snd_seq_event_t event
;
147 snd_seq_ev_clear (&event
);
149 auto numBytes
= (long) message
.getRawDataSize();
150 auto* data
= message
.getRawData();
152 auto seqHandle
= client
.get();
157 auto numSent
= snd_midi_event_encode (midiParser
, data
, numBytes
, &event
);
161 success
= numSent
== 0;
168 snd_seq_ev_set_source (&event
, (unsigned char) portId
);
169 snd_seq_ev_set_subs (&event
);
170 snd_seq_ev_set_direct (&event
);
172 if (snd_seq_event_output_direct (seqHandle
, &event
) < 0)
179 snd_midi_event_reset_encode (midiParser
);
184 bool operator== (const Port
& lhs
) const noexcept
186 return portId
!= -1 && portId
== lhs
.portId
;
189 void createPort (const String
& name
, bool enableSubscription
)
191 if (auto seqHandle
= client
.get())
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));
198 portId
= snd_seq_create_simple_port (seqHandle
, portName
.toUTF8(), caps
,
199 SND_SEQ_PORT_TYPE_MIDI_GENERIC
|
200 SND_SEQ_PORT_TYPE_APPLICATION
);
204 void handleIncomingMidiMessage (const MidiMessage
& message
) const
207 callback
->handleIncomingMidiMessage (midiInput
, message
);
210 void handlePartialSysexMessage (const uint8
* messageData
, int numBytesSoFar
, double timeStamp
)
213 callback
->handlePartialSysexMessage (midiInput
, messageData
, numBytesSoFar
, timeStamp
);
216 int getPortId() const { return portId
; }
217 const String
& getPortName() const { return portName
; }
222 MidiInputCallback
* callback
= nullptr;
223 snd_midi_event_t
* midiParser
= nullptr;
224 MidiInput
* midiInput
= nullptr;
228 int maxEventSize
= 4096, portId
= -1;
229 std::atomic
<bool> callbackEnabled
{ false };
230 bool isInput
= false;
233 static Ptr
getInstance()
235 if (instance
== nullptr)
236 instance
= new AlsaClient();
241 void registerCallback()
243 if (inputThread
== nullptr)
244 inputThread
.reset (new MidiInputThread (*this));
246 if (++activeCallbacks
== 1)
247 inputThread
->startThread();
250 void unregisterCallback()
252 jassert (activeCallbacks
.get() > 0);
254 if (--activeCallbacks
== 0 && inputThread
->isThreadRunning())
255 inputThread
->signalThreadShouldExit();
258 void handleIncomingMidiMessage (snd_seq_event
* event
, const MidiMessage
& message
)
260 const ScopedLock
sl (callbackLock
);
262 if (auto* port
= ports
[event
->dest
.port
])
263 port
->handleIncomingMidiMessage (message
);
266 void handlePartialSysexMessage (snd_seq_event
* event
, const uint8
* messageData
, int numBytesSoFar
, double timeStamp
)
268 const ScopedLock
sl (callbackLock
);
270 if (auto* port
= ports
[event
->dest
.port
])
271 port
->handlePartialSysexMessage (messageData
, numBytesSoFar
, timeStamp
);
274 snd_seq_t
* get() const noexcept
{ return handle
; }
275 int getId() const noexcept
{ return clientId
; }
277 Port
* createPort (const String
& name
, bool forInput
, bool enableSubscription
)
279 const ScopedLock
sl (callbackLock
);
281 auto port
= new Port (*this, forInput
);
282 port
->createPort (name
, enableSubscription
);
283 ports
.set (port
->getPortId(), port
);
288 void deletePort (Port
* port
)
290 const ScopedLock
sl (callbackLock
);
292 ports
.set (port
->getPortId(), nullptr);
297 snd_seq_t
* handle
= nullptr;
299 OwnedArray
<Port
> ports
;
300 Atomic
<int> activeCallbacks
;
301 CriticalSection callbackLock
;
303 static AlsaClient
* instance
;
305 //==============================================================================
306 class MidiInputThread
: public Thread
309 MidiInputThread (AlsaClient
& c
)
310 : Thread ("JUCE MIDI Input"), client (c
)
312 jassert (client
.get() != nullptr);
317 auto seqHandle
= client
.get();
319 const int maxEventSize
= 16 * 1024;
320 snd_midi_event_t
* midiParser
;
322 if (snd_midi_event_new (maxEventSize
, &midiParser
) >= 0)
324 auto numPfds
= snd_seq_poll_descriptors_count (seqHandle
, POLLIN
);
325 HeapBlock
<pollfd
> pfd (numPfds
);
326 snd_seq_poll_descriptors (seqHandle
, pfd
, (unsigned int) numPfds
, POLLIN
);
328 HeapBlock
<uint8
> buffer (maxEventSize
);
330 while (! threadShouldExit())
332 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
334 if (threadShouldExit())
339 snd_seq_event_t
* inputEvent
= nullptr;
341 if (snd_seq_event_input (seqHandle
, &inputEvent
) >= 0)
343 // xxx what about SYSEXes that are too big for the buffer?
344 auto numBytes
= snd_midi_event_decode (midiParser
, buffer
,
345 maxEventSize
, inputEvent
);
347 snd_midi_event_reset_decode (midiParser
);
349 concatenator
.pushMidiData (buffer
, (int) numBytes
,
350 Time::getMillisecondCounter() * 0.001,
353 snd_seq_free_event (inputEvent
);
356 while (snd_seq_event_input_pending (seqHandle
, 0) > 0);
360 snd_midi_event_free (midiParser
);
366 MidiDataConcatenator concatenator
{ 2048 };
369 std::unique_ptr
<MidiInputThread
> inputThread
;
372 AlsaClient
* AlsaClient::instance
= nullptr;
374 //==============================================================================
375 static String
getFormattedPortIdentifier (int clientId
, int portId
)
377 return String (clientId
) + "-" + String (portId
);
380 static AlsaClient::Port
* iterateMidiClient (const AlsaClient::Ptr
& client
,
381 snd_seq_client_info_t
* clientInfo
,
383 Array
<MidiDeviceInfo
>& devices
,
384 const String
& deviceIdentifierToOpen
)
386 AlsaClient::Port
* port
= nullptr;
388 auto seqHandle
= client
->get();
389 snd_seq_port_info_t
* portInfo
= nullptr;
391 snd_seq_port_info_alloca (&portInfo
);
392 jassert (portInfo
!= nullptr);
393 auto numPorts
= snd_seq_client_info_get_num_ports (clientInfo
);
394 auto sourceClient
= snd_seq_client_info_get_client (clientInfo
);
396 snd_seq_port_info_set_client (portInfo
, sourceClient
);
397 snd_seq_port_info_set_port (portInfo
, -1);
399 while (--numPorts
>= 0)
401 if (snd_seq_query_next_port (seqHandle
, portInfo
) == 0
402 && (snd_seq_port_info_get_capability (portInfo
)
403 & (forInput
? SND_SEQ_PORT_CAP_SUBS_READ
: SND_SEQ_PORT_CAP_SUBS_WRITE
)) != 0)
405 String
portName (snd_seq_port_info_get_name (portInfo
));
406 auto portID
= snd_seq_port_info_get_port (portInfo
);
408 MidiDeviceInfo
device (portName
, getFormattedPortIdentifier (sourceClient
, portID
));
409 devices
.add (device
);
411 if (deviceIdentifierToOpen
.isNotEmpty() && deviceIdentifierToOpen
== device
.identifier
)
415 port
= client
->createPort (portName
, forInput
, false);
416 jassert (port
->isValid());
417 port
->connectWith (sourceClient
, portID
);
427 static AlsaClient::Port
* iterateMidiDevices (bool forInput
,
428 Array
<MidiDeviceInfo
>& devices
,
429 const String
& deviceIdentifierToOpen
)
431 AlsaClient::Port
* port
= nullptr;
432 auto client
= AlsaClient::getInstance();
434 if (auto seqHandle
= client
->get())
436 snd_seq_system_info_t
* systemInfo
= nullptr;
437 snd_seq_client_info_t
* clientInfo
= nullptr;
439 snd_seq_system_info_alloca (&systemInfo
);
440 jassert (systemInfo
!= nullptr);
442 if (snd_seq_system_info (seqHandle
, systemInfo
) == 0)
444 snd_seq_client_info_alloca (&clientInfo
);
445 jassert (clientInfo
!= nullptr);
447 auto numClients
= snd_seq_system_info_get_cur_clients (systemInfo
);
449 while (--numClients
>= 0)
451 if (snd_seq_query_next_client (seqHandle
, clientInfo
) == 0)
453 port
= iterateMidiClient (client
, clientInfo
, forInput
,
454 devices
, deviceIdentifierToOpen
);
468 explicit AlsaPortPtr (AlsaClient::Port
* p
)
471 ~AlsaPortPtr() noexcept
{ AlsaClient::getInstance()->deletePort (ptr
); }
473 AlsaClient::Port
* ptr
= nullptr;
476 //==============================================================================
477 class MidiInput::Pimpl
: public AlsaPortPtr
480 using AlsaPortPtr::AlsaPortPtr
;
483 Array
<MidiDeviceInfo
> MidiInput::getAvailableDevices()
485 Array
<MidiDeviceInfo
> devices
;
486 iterateMidiDevices (true, devices
, {});
491 MidiDeviceInfo
MidiInput::getDefaultDevice()
493 return getAvailableDevices().getFirst();
496 std::unique_ptr
<MidiInput
> MidiInput::openDevice (const String
& deviceIdentifier
, MidiInputCallback
* callback
)
498 if (deviceIdentifier
.isEmpty())
501 Array
<MidiDeviceInfo
> devices
;
502 auto* port
= iterateMidiDevices (true, devices
, deviceIdentifier
);
504 if (port
== nullptr || ! port
->isValid())
507 jassert (port
->isValid());
509 std::unique_ptr
<MidiInput
> midiInput (new MidiInput (port
->getPortName(), deviceIdentifier
));
511 port
->setupInput (midiInput
.get(), callback
);
512 midiInput
->internal
= std::make_unique
<Pimpl
> (port
);
517 std::unique_ptr
<MidiInput
> MidiInput::createNewDevice (const String
& deviceName
, MidiInputCallback
* callback
)
519 auto client
= AlsaClient::getInstance();
520 auto* port
= client
->createPort (deviceName
, true, true);
522 if (port
== nullptr || ! port
->isValid())
525 std::unique_ptr
<MidiInput
> midiInput (new MidiInput (deviceName
, getFormattedPortIdentifier (client
->getId(), port
->getPortId())));
527 port
->setupInput (midiInput
.get(), callback
);
528 midiInput
->internal
= std::make_unique
<Pimpl
> (port
);
533 StringArray
MidiInput::getDevices()
535 StringArray deviceNames
;
537 for (auto& d
: getAvailableDevices())
538 deviceNames
.add (d
.name
);
540 deviceNames
.appendNumbersToDuplicates (true, true);
545 int MidiInput::getDefaultDeviceIndex()
550 std::unique_ptr
<MidiInput
> MidiInput::openDevice (int index
, MidiInputCallback
* callback
)
552 return openDevice (getAvailableDevices()[index
].identifier
, callback
);
555 MidiInput::MidiInput (const String
& deviceName
, const String
& deviceIdentifier
)
556 : deviceInfo (deviceName
, deviceIdentifier
)
560 MidiInput::~MidiInput()
565 void MidiInput::start()
567 internal
->ptr
->enableCallback (true);
570 void MidiInput::stop()
572 internal
->ptr
->enableCallback (false);
575 //==============================================================================
576 class MidiOutput::Pimpl
: public AlsaPortPtr
579 using AlsaPortPtr::AlsaPortPtr
;
582 Array
<MidiDeviceInfo
> MidiOutput::getAvailableDevices()
584 Array
<MidiDeviceInfo
> devices
;
585 iterateMidiDevices (false, devices
, {});
590 MidiDeviceInfo
MidiOutput::getDefaultDevice()
592 return getAvailableDevices().getFirst();
595 std::unique_ptr
<MidiOutput
> MidiOutput::openDevice (const String
& deviceIdentifier
)
597 if (deviceIdentifier
.isEmpty())
600 Array
<MidiDeviceInfo
> devices
;
601 auto* port
= iterateMidiDevices (false, devices
, deviceIdentifier
);
603 if (port
== nullptr || ! port
->isValid())
606 std::unique_ptr
<MidiOutput
> midiOutput (new MidiOutput (port
->getPortName(), deviceIdentifier
));
609 midiOutput
->internal
= std::make_unique
<Pimpl
> (port
);
614 std::unique_ptr
<MidiOutput
> MidiOutput::createNewDevice (const String
& deviceName
)
616 auto client
= AlsaClient::getInstance();
617 auto* port
= client
->createPort (deviceName
, false, true);
619 if (port
== nullptr || ! port
->isValid())
622 std::unique_ptr
<MidiOutput
> midiOutput (new MidiOutput (deviceName
, getFormattedPortIdentifier (client
->getId(), port
->getPortId())));
625 midiOutput
->internal
= std::make_unique
<Pimpl
> (port
);
630 StringArray
MidiOutput::getDevices()
632 StringArray deviceNames
;
634 for (auto& d
: getAvailableDevices())
635 deviceNames
.add (d
.name
);
637 deviceNames
.appendNumbersToDuplicates (true, true);
642 int MidiOutput::getDefaultDeviceIndex()
647 std::unique_ptr
<MidiOutput
> MidiOutput::openDevice (int index
)
649 return openDevice (getAvailableDevices()[index
].identifier
);
652 MidiOutput::~MidiOutput()
654 stopBackgroundThread();
657 void MidiOutput::sendMessageNow (const MidiMessage
& message
)
659 internal
->ptr
->sendMessageNow (message
);
662 //==============================================================================
665 class MidiInput::Pimpl
{};
667 // (These are just stub functions if ALSA is unavailable...)
668 MidiInput::MidiInput (const String
& deviceName
, const String
& deviceID
)
669 : deviceInfo (deviceName
, deviceID
)
673 MidiInput::~MidiInput() {}
674 void MidiInput::start() {}
675 void MidiInput::stop() {}
676 Array
<MidiDeviceInfo
> MidiInput::getAvailableDevices() { return {}; }
677 MidiDeviceInfo
MidiInput::getDefaultDevice() { return {}; }
678 std::unique_ptr
<MidiInput
> MidiInput::openDevice (const String
&, MidiInputCallback
*) { return {}; }
679 std::unique_ptr
<MidiInput
> MidiInput::createNewDevice (const String
&, MidiInputCallback
*) { return {}; }
680 StringArray
MidiInput::getDevices() { return {}; }
681 int MidiInput::getDefaultDeviceIndex() { return 0;}
682 std::unique_ptr
<MidiInput
> MidiInput::openDevice (int, MidiInputCallback
*) { return {}; }
684 class MidiOutput::Pimpl
{};
686 MidiOutput::~MidiOutput() {}
687 void MidiOutput::sendMessageNow (const MidiMessage
&) {}
688 Array
<MidiDeviceInfo
> MidiOutput::getAvailableDevices() { return {}; }
689 MidiDeviceInfo
MidiOutput::getDefaultDevice() { return {}; }
690 std::unique_ptr
<MidiOutput
> MidiOutput::openDevice (const String
&) { return {}; }
691 std::unique_ptr
<MidiOutput
> MidiOutput::createNewDevice (const String
&) { return {}; }
692 StringArray
MidiOutput::getDevices() { return {}; }
693 int MidiOutput::getDefaultDeviceIndex() { return 0;}
694 std::unique_ptr
<MidiOutput
> MidiOutput::openDevice (int) { return {}; }