31 auto lowerBound (
int portId)
const
33 const auto comparator = [] (
const auto& port,
const auto& id) {
return port->getPortId() < id; };
37 auto findPortIterator (
int portId)
const
39 const auto iter = lowerBound (portId);
40 return (iter == ports.end() || (*iter)->getPortId() != portId) ? ports.end() : iter;
48 jassert (activeCallbacks.get() == 0);
50 if (handle !=
nullptr)
52 snd_seq_delete_simple_port (handle, announcementsIn);
53 snd_seq_close (handle);
57 static String getAlsaMidiName()
59 #ifdef JUCE_ALSA_MIDI_NAME
60 return JUCE_ALSA_MIDI_NAME;
62 if (
auto* app = JUCEApplicationBase::getInstance())
63 return app->getApplicationName();
73 explicit Port (
bool forInput) noexcept
74 : isInput (forInput) {}
81 enableCallback (
false);
83 snd_midi_event_free (midiParser);
85 snd_seq_delete_simple_port (client->get(), portId);
89 void connectWith (
int sourceClient,
int sourcePort)
const noexcept
92 snd_seq_connect_from (client->get(), portId, sourceClient, sourcePort);
94 snd_seq_connect_to (client->get(), portId, sourceClient, sourcePort);
97 bool isValid() const noexcept
99 return client->get() !=
nullptr && portId >= 0;
102 void setupInput (MidiInput* input, MidiInputCallback* cb)
104 jassert (cb !=
nullptr && input !=
nullptr);
112 snd_midi_event_new ((
size_t) maxEventSize, &midiParser);
115 void enableCallback (
bool enable)
117 callbackEnabled = enable;
120 bool sendMessageNow (
const MidiMessage& message)
122 if (message.getRawDataSize() > maxEventSize)
124 maxEventSize = message.getRawDataSize();
125 snd_midi_event_free (midiParser);
126 snd_midi_event_new ((
size_t) maxEventSize, &midiParser);
129 snd_seq_event_t event;
130 snd_seq_ev_clear (&event);
132 auto numBytes = (
long) message.getRawDataSize();
133 auto*
data = message.getRawData();
135 auto seqHandle = client->get();
140 auto numSent = snd_midi_event_encode (midiParser, data, numBytes, &event);
144 success = numSent == 0;
151 snd_seq_ev_set_source (&event, (
unsigned char) portId);
152 snd_seq_ev_set_subs (&event);
153 snd_seq_ev_set_direct (&event);
155 if (snd_seq_event_output_direct (seqHandle, &event) < 0)
162 snd_midi_event_reset_encode (midiParser);
167 bool operator== (
const Port& lhs)
const noexcept
169 return portId != -1 && portId == lhs.portId;
172 void createPort (
const String& name,
bool enableSubscription)
174 if (
auto seqHandle = client->get())
176 const unsigned int caps =
177 isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0))
178 : (SND_SEQ_PORT_CAP_READ | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0));
181 portId = snd_seq_create_simple_port (seqHandle, portName.toUTF8(), caps,
182 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
183 SND_SEQ_PORT_TYPE_APPLICATION);
187 void handleIncomingMidiMessage (
const MidiMessage& message)
const
190 callback->handleIncomingMidiMessage (midiInput, message);
193 void handlePartialSysexMessage (
const uint8* messageData,
int numBytesSoFar,
double timeStamp)
196 callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp);
199 int getPortId()
const {
return portId; }
200 const String& getPortName()
const {
return portName; }
205 MidiInputCallback* callback =
nullptr;
206 snd_midi_event_t* midiParser =
nullptr;
207 MidiInput* midiInput =
nullptr;
211 int maxEventSize = 4096, portId = -1;
213 bool isInput =
false;
220 if (
auto locked = ptr.
lock())
228 void handleIncomingMidiMessage (snd_seq_event* event,
const MidiMessage& message)
232 if (
auto* port = findPort (event->dest.port))
233 port->handleIncomingMidiMessage (message);
236 void handlePartialSysexMessage (snd_seq_event* event,
const uint8* messageData,
int numBytesSoFar,
double timeStamp)
240 if (
auto* port = findPort (event->dest.port))
241 port->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp);
244 snd_seq_t*
get() const noexcept {
return handle; }
245 int getId() const noexcept {
return clientId; }
247 Port* createPort (
const String& name,
bool forInput,
bool enableSubscription)
251 auto port =
new Port (forInput);
252 port->createPort (name, enableSubscription);
254 const auto iter = lowerBound (port->getPortId());
255 jassert (iter == ports.end() || port->getPortId() < (*iter)->getPortId());
256 ports.insert (iter, rawToUniquePtr (port));
261 void deletePort (Port* port)
265 if (
const auto iter = findPortIterator (port->getPortId()); iter != ports.end())
272 snd_seq_open (&handle,
"default", SND_SEQ_OPEN_DUPLEX, 0);
274 if (handle !=
nullptr)
276 snd_seq_nonblock (handle, SND_SEQ_NONBLOCK);
277 snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8());
278 clientId = snd_seq_client_id (handle);
283 announcementsIn = snd_seq_create_simple_port (handle,
284 TRANS (
"announcements").toRawUTF8(),
285 SND_SEQ_PORT_CAP_WRITE,
286 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
287 snd_seq_connect_from (handle, announcementsIn, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);
289 inputThread.emplace (*
this);
293 Port* findPort (
int portId)
295 if (
const auto iter = findPortIterator (portId); iter != ports.end())
301 snd_seq_t* handle =
nullptr;
303 int announcementsIn = 0;
305 Atomic<int> activeCallbacks;
306 CriticalSection callbackLock;
309 class SequencerThread
312 explicit SequencerThread (AlsaClient& c)
317 ~SequencerThread() noexcept
337 class UpdateNotifier final :
private AsyncUpdater
340 ~UpdateNotifier()
override { cancelPendingUpdate(); }
341 using AsyncUpdater::triggerAsyncUpdate;
344 void handleAsyncUpdate()
override { MidiDeviceListConnectionBroadcaster::get().notify(); }
348 MidiDataConcatenator concatenator { 2048 };
350 UpdateNotifier notifier;
353 Thread::setCurrentThreadName (
"JUCE MIDI Input");
355 auto seqHandle = client.get();
357 const int maxEventSize = 16 * 1024;
358 snd_midi_event_t* midiParser;
360 if (snd_midi_event_new (maxEventSize, &midiParser) >= 0)
362 const ScopeGuard freeMidiEvent { [&] { snd_midi_event_free (midiParser); } };
364 const auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN);
366 snd_seq_poll_descriptors (seqHandle, pfd.data(), (
unsigned int) numPfds, POLLIN);
373 if (poll (pfd.data(), (nfds_t) numPfds, 100) > 0)
380 snd_seq_event_t* inputEvent =
nullptr;
382 if (snd_seq_event_input (seqHandle, &inputEvent) >= 0)
384 const ScopeGuard freeInputEvent { [&] { snd_seq_free_event (inputEvent); } };
386 constexpr int systemEvents[]
388 SND_SEQ_EVENT_CLIENT_CHANGE,
389 SND_SEQ_EVENT_CLIENT_START,
390 SND_SEQ_EVENT_CLIENT_EXIT,
391 SND_SEQ_EVENT_PORT_CHANGE,
392 SND_SEQ_EVENT_PORT_START,
393 SND_SEQ_EVENT_PORT_EXIT,
394 SND_SEQ_EVENT_PORT_SUBSCRIBED,
395 SND_SEQ_EVENT_PORT_UNSUBSCRIBED,
402 if (foundEvent !=
std::end (systemEvents))
404 notifier.triggerAsyncUpdate();
409 const auto numBytes = snd_midi_event_decode (midiParser,
414 snd_midi_event_reset_decode (midiParser);
416 concatenator.pushMidiData (buffer.data(), (
int) numBytes,
417 Time::getMillisecondCounter() * 0.001,
421 while (snd_seq_event_input_pending (seqHandle, 0) > 0);
432static String getFormattedPortIdentifier (
int clientId,
int portId)
434 return String (clientId) +
"-" + String (portId);
437static AlsaClient::Port* iterateMidiClient (AlsaClient& client,
438 snd_seq_client_info_t* clientInfo,
440 Array<MidiDeviceInfo>& devices,
441 const String& deviceIdentifierToOpen)
443 AlsaClient::Port* port =
nullptr;
445 auto seqHandle = client.get();
446 snd_seq_port_info_t* portInfo =
nullptr;
448 snd_seq_port_info_alloca (&portInfo);
450 auto numPorts = snd_seq_client_info_get_num_ports (clientInfo);
451 auto sourceClient = snd_seq_client_info_get_client (clientInfo);
453 snd_seq_port_info_set_client (portInfo, sourceClient);
454 snd_seq_port_info_set_port (portInfo, -1);
456 while (--numPorts >= 0)
458 if (snd_seq_query_next_port (seqHandle, portInfo) == 0
459 && (snd_seq_port_info_get_capability (portInfo)
460 & (forInput ? SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_SUBS_WRITE)) != 0)
462 String portName (snd_seq_port_info_get_name (portInfo));
463 auto portID = snd_seq_port_info_get_port (portInfo);
465 MidiDeviceInfo device (portName, getFormattedPortIdentifier (sourceClient, portID));
466 devices.add (device);
468 if (deviceIdentifierToOpen.isNotEmpty() && deviceIdentifierToOpen == device.identifier)
472 port = client.createPort (portName, forInput,
false);
474 port->connectWith (sourceClient, portID);
484static AlsaClient::Port* iterateMidiDevices (
bool forInput,
485 Array<MidiDeviceInfo>& devices,
486 const String& deviceIdentifierToOpen)
488 AlsaClient::Port* port =
nullptr;
489 auto client = AlsaClient::getInstance();
491 if (
auto seqHandle = client->get())
493 snd_seq_system_info_t* systemInfo =
nullptr;
494 snd_seq_client_info_t* clientInfo =
nullptr;
496 snd_seq_system_info_alloca (&systemInfo);
497 jassert (systemInfo !=
nullptr);
499 if (snd_seq_system_info (seqHandle, systemInfo) == 0)
501 snd_seq_client_info_alloca (&clientInfo);
502 jassert (clientInfo !=
nullptr);
504 auto numClients = snd_seq_system_info_get_cur_clients (systemInfo);
506 while (--numClients >= 0)
508 if (snd_seq_query_next_client (seqHandle, clientInfo) == 0)
510 port = iterateMidiClient (*client,
514 deviceIdentifierToOpen);
528 explicit AlsaPortPtr (AlsaClient::Port* p)
531 virtual ~AlsaPortPtr() noexcept { AlsaClient::getInstance()->deletePort (ptr); }
533 AlsaClient::Port* ptr =
nullptr;
537class MidiInput::Pimpl final :
public AlsaPortPtr
540 using AlsaPortPtr::AlsaPortPtr;
545 Array<MidiDeviceInfo> devices;
546 iterateMidiDevices (
true, devices, {});
558 if (deviceIdentifier.isEmpty())
561 Array<MidiDeviceInfo> devices;
562 auto* port = iterateMidiDevices (
true, devices, deviceIdentifier);
564 if (port ==
nullptr || ! port->isValid())
571 port->setupInput (midiInput.get(), callback);
579 auto client = AlsaClient::getInstance();
580 auto* port = client->createPort (deviceName,
true,
true);
582 if (port ==
nullptr || ! port->isValid())
585 std::unique_ptr<MidiInput> midiInput (
new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->getPortId())));
587 port->setupInput (midiInput.get(), callback);
593StringArray MidiInput::getDevices()
595 StringArray deviceNames;
598 deviceNames.add (d.name);
600 deviceNames.appendNumbersToDuplicates (
true,
true);
605int MidiInput::getDefaultDeviceIndex()
615MidiInput::MidiInput (
const String& deviceName,
const String& deviceIdentifier)
616 : deviceInfo (deviceName, deviceIdentifier)
620MidiInput::~MidiInput()
625void MidiInput::start()
627 internal->ptr->enableCallback (
true);
630void MidiInput::stop()
632 internal->ptr->enableCallback (
false);
636class MidiOutput::Pimpl final :
public AlsaPortPtr
639 using AlsaPortPtr::AlsaPortPtr;
642Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
644 Array<MidiDeviceInfo> devices;
645 iterateMidiDevices (
false, devices, {});
650MidiDeviceInfo MidiOutput::getDefaultDevice()
652 return getAvailableDevices().getFirst();
657 if (deviceIdentifier.isEmpty())
660 Array<MidiDeviceInfo> devices;
661 auto* port = iterateMidiDevices (
false, devices, deviceIdentifier);
663 if (port ==
nullptr || ! port->isValid())
676 auto client = AlsaClient::getInstance();
677 auto* port = client->createPort (deviceName,
false,
true);
679 if (port ==
nullptr || ! port->isValid())
682 std::unique_ptr<MidiOutput> midiOutput (
new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->getPortId())));
690StringArray MidiOutput::getDevices()
692 StringArray deviceNames;
694 for (
auto& d : getAvailableDevices())
695 deviceNames.add (d.name);
697 deviceNames.appendNumbersToDuplicates (
true,
true);
702int MidiOutput::getDefaultDeviceIndex()
709 return openDevice (getAvailableDevices()[index].identifier);
712MidiOutput::~MidiOutput()
714 stopBackgroundThread();
717void MidiOutput::sendMessageNow (
const MidiMessage& message)
719 internal->ptr->sendMessageNow (message);
722MidiDeviceListConnection MidiDeviceListConnection::make (
std::function<
void()> cb)
724 auto& broadcaster = MidiDeviceListConnectionBroadcaster::get();
728 return { &broadcaster, broadcaster.add ([fn = std::move (cb), client = AlsaClient::getInstance()]
730 NullCheckedInvocation::invoke (fn);
735MidiOutput::MidiOutput (
const String& deviceName,
const String& deviceIdentifier)
736 : Thread (
"midi out"), deviceInfo (deviceName, deviceIdentifier)
746MidiInput::MidiInput (
const String& deviceName,
const String& deviceID)
747 : deviceInfo (deviceName, deviceID)
759int MidiInput::getDefaultDeviceIndex() {
return 0;}
771int MidiOutput::getDefaultDeviceIndex() {
return 0;}
776 auto& broadcaster = MidiDeviceListConnectionBroadcaster::get();
777 return { &broadcaster, broadcaster.add (std::move (cb)) };
Holds a resizable array of primitive or copy-by-value objects.
To find out when the available MIDI devices change, call MidiDeviceListConnection::make(),...
static MidiDeviceListConnection make(std::function< void()>)
Registers a function to be called whenever the midi device list changes.
Encapsulates a MIDI message.
static MidiDeviceInfo getDefaultDevice()
Returns the MidiDeviceInfo of the default midi output device to use.
static std::unique_ptr< MidiOutput > openDevice(const String &deviceIdentifier)
Tries to open one of the midi output devices.
static std::unique_ptr< MidiOutput > createNewDevice(const String &deviceName)
This will try to create a new midi output device (only available on Linux, macOS and iOS).
void sendMessageNow(const MidiMessage &message)
Sends out a MIDI message immediately.
~MidiOutput() override
Destructor.
static Array< MidiDeviceInfo > getAvailableDevices()
Returns a list of the available midi output devices.
A special array for holding a list of strings.
#define TRANS(stringLiteral)
Uses the LocalisedStrings class to translate the given string literal.
auto & get(ProcessorChain< Processors... > &chain) noexcept
Non-member equivalent of ProcessorChain::get which avoids awkward member template syntax.
CriticalSection::ScopedLockType ScopedLock
Automatically locks and unlocks a CriticalSection object.
This struct contains information about a MIDI input or output device.