VST3: fetch midi mappings all at once, use it for note/sound-off
[carla.git] / source / modules / juce_audio_devices / native / juce_linux_Midi.cpp
blob3f87c27d0cd1fd3434ad493e0ad39552b50ef7c2
1 /*
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
8 licensing.
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
18 DISCLAIMED.
20 ==============================================================================
23 namespace juce
26 #if JUCE_ALSA
28 //==============================================================================
29 class AlsaClient : public ReferenceCountedObject
31 public:
32 AlsaClient()
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);
49 ~AlsaClient()
51 jassert (instance != nullptr);
52 instance = nullptr;
54 jassert (activeCallbacks.get() == 0);
56 if (inputThread)
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;
67 #else
68 if (auto* app = JUCEApplicationBase::getInstance())
69 return app->getApplicationName();
71 return "JUCE";
72 #endif
75 using Ptr = ReferenceCountedObjectPtr<AlsaClient>;
77 //==============================================================================
78 // represents an input or output port of the supplied AlsaClient
79 struct Port
81 Port (AlsaClient& c, bool forInput) noexcept
82 : client (c), isInput (forInput)
85 ~Port()
87 if (isValid())
89 if (isInput)
90 enableCallback (false);
91 else
92 snd_midi_event_free (midiParser);
94 snd_seq_delete_simple_port (client.get(), portId);
98 void connectWith (int sourceClient, int sourcePort) const noexcept
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);
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);
114 callback = cb;
115 midiInput = input;
118 void setupOutput()
120 jassert (! isInput);
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)
130 if (enable)
131 client.registerCallback();
132 else
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();
153 bool success = true;
155 while (numBytes > 0)
157 auto numSent = snd_midi_event_encode (midiParser, data, numBytes, &event);
159 if (numSent <= 0)
161 success = numSent == 0;
162 break;
165 numBytes -= numSent;
166 data += numSent;
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)
174 success = false;
175 break;
179 snd_midi_event_reset_encode (midiParser);
180 return success;
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));
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);
204 void handleIncomingMidiMessage (const MidiMessage& message) const
206 if (callbackEnabled)
207 callback->handleIncomingMidiMessage (midiInput, message);
210 void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp)
212 if (callbackEnabled)
213 callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp);
216 int getPortId() const { return portId; }
217 const String& getPortName() const { return portName; }
219 private:
220 AlsaClient& client;
222 MidiInputCallback* callback = nullptr;
223 snd_midi_event_t* midiParser = nullptr;
224 MidiInput* midiInput = nullptr;
226 String portName;
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();
238 return instance;
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);
284 incReferenceCount();
285 return port;
288 void deletePort (Port* port)
290 const ScopedLock sl (callbackLock);
292 ports.set (port->getPortId(), nullptr);
293 decReferenceCount();
296 private:
297 snd_seq_t* handle = nullptr;
298 int clientId = 0;
299 OwnedArray<Port> ports;
300 Atomic<int> activeCallbacks;
301 CriticalSection callbackLock;
303 static AlsaClient* instance;
305 //==============================================================================
306 class MidiInputThread : public Thread
308 public:
309 MidiInputThread (AlsaClient& c)
310 : Thread ("JUCE MIDI Input"), client (c)
312 jassert (client.get() != nullptr);
315 void run() override
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())
335 break;
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,
351 inputEvent, client);
353 snd_seq_free_event (inputEvent);
356 while (snd_seq_event_input_pending (seqHandle, 0) > 0);
360 snd_midi_event_free (midiParser);
364 private:
365 AlsaClient& client;
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,
382 bool forInput,
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)
413 if (portID != -1)
415 port = client->createPort (portName, forInput, false);
416 jassert (port->isValid());
417 port->connectWith (sourceClient, portID);
418 break;
424 return port;
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);
456 if (port != nullptr)
457 break;
463 return port;
466 struct AlsaPortPtr
468 explicit AlsaPortPtr (AlsaClient::Port* p)
469 : ptr (p) {}
471 ~AlsaPortPtr() noexcept { AlsaClient::getInstance()->deletePort (ptr); }
473 AlsaClient::Port* ptr = nullptr;
476 //==============================================================================
477 class MidiInput::Pimpl : public AlsaPortPtr
479 public:
480 using AlsaPortPtr::AlsaPortPtr;
483 Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
485 Array<MidiDeviceInfo> devices;
486 iterateMidiDevices (true, devices, {});
488 return 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())
499 return {};
501 Array<MidiDeviceInfo> devices;
502 auto* port = iterateMidiDevices (true, devices, deviceIdentifier);
504 if (port == nullptr || ! port->isValid())
505 return {};
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);
514 return midiInput;
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())
523 return {};
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);
530 return midiInput;
533 StringArray MidiInput::getDevices()
535 StringArray deviceNames;
537 for (auto& d : getAvailableDevices())
538 deviceNames.add (d.name);
540 deviceNames.appendNumbersToDuplicates (true, true);
542 return deviceNames;
545 int MidiInput::getDefaultDeviceIndex()
547 return 0;
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()
562 stop();
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
578 public:
579 using AlsaPortPtr::AlsaPortPtr;
582 Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
584 Array<MidiDeviceInfo> devices;
585 iterateMidiDevices (false, devices, {});
587 return devices;
590 MidiDeviceInfo MidiOutput::getDefaultDevice()
592 return getAvailableDevices().getFirst();
595 std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
597 if (deviceIdentifier.isEmpty())
598 return {};
600 Array<MidiDeviceInfo> devices;
601 auto* port = iterateMidiDevices (false, devices, deviceIdentifier);
603 if (port == nullptr || ! port->isValid())
604 return {};
606 std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (port->getPortName(), deviceIdentifier));
608 port->setupOutput();
609 midiOutput->internal = std::make_unique<Pimpl> (port);
611 return midiOutput;
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())
620 return {};
622 std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->getPortId())));
624 port->setupOutput();
625 midiOutput->internal = std::make_unique<Pimpl> (port);
627 return midiOutput;
630 StringArray MidiOutput::getDevices()
632 StringArray deviceNames;
634 for (auto& d : getAvailableDevices())
635 deviceNames.add (d.name);
637 deviceNames.appendNumbersToDuplicates (true, true);
639 return deviceNames;
642 int MidiOutput::getDefaultDeviceIndex()
644 return 0;
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 //==============================================================================
663 #else
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 {}; }
696 #endif
698 } // namespace juce