JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_Midi_linux.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - 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
23namespace juce
24{
25
26#if JUCE_ALSA
27
28//==============================================================================
29class AlsaClient
30{
31 auto lowerBound (int portId) const
32 {
33 const auto comparator = [] (const auto& port, const auto& id) { return port->getPortId() < id; };
34 return std::lower_bound (ports.begin(), ports.end(), portId, comparator);
35 }
36
37 auto findPortIterator (int portId) const
38 {
39 const auto iter = lowerBound (portId);
40 return (iter == ports.end() || (*iter)->getPortId() != portId) ? ports.end() : iter;
41 }
42
43public:
45 {
46 inputThread.reset();
47
48 jassert (activeCallbacks.get() == 0);
49
50 if (handle != nullptr)
51 {
53 snd_seq_close (handle);
54 }
55 }
56
57 static String getAlsaMidiName()
58 {
59 #ifdef JUCE_ALSA_MIDI_NAME
61 #else
63 return app->getApplicationName();
64
65 return "JUCE";
66 #endif
67 }
68
69 //==============================================================================
70 // represents an input or output port of the supplied AlsaClient
71 struct Port
72 {
73 explicit Port (bool forInput) noexcept
74 : isInput (forInput) {}
75
76 ~Port()
77 {
78 if (isValid())
79 {
80 if (isInput)
81 enableCallback (false);
82 else
83 snd_midi_event_free (midiParser);
84
85 snd_seq_delete_simple_port (client->get(), portId);
86 }
87 }
88
89 void connectWith (int sourceClient, int sourcePort) const noexcept
90 {
91 if (isInput)
92 snd_seq_connect_from (client->get(), portId, sourceClient, sourcePort);
93 else
94 snd_seq_connect_to (client->get(), portId, sourceClient, sourcePort);
95 }
96
97 bool isValid() const noexcept
98 {
99 return client->get() != nullptr && portId >= 0;
100 }
101
102 void setupInput (MidiInput* input, MidiInputCallback* cb)
103 {
104 jassert (cb != nullptr && input != nullptr);
105 callback = cb;
106 midiInput = input;
107 }
108
109 void setupOutput()
110 {
111 jassert (! isInput);
112 snd_midi_event_new ((size_t) maxEventSize, &midiParser);
113 }
114
115 void enableCallback (bool enable)
116 {
117 callbackEnabled = enable;
118 }
119
120 bool sendMessageNow (const MidiMessage& message)
121 {
122 if (message.getRawDataSize() > maxEventSize)
123 {
124 maxEventSize = message.getRawDataSize();
125 snd_midi_event_free (midiParser);
126 snd_midi_event_new ((size_t) maxEventSize, &midiParser);
127 }
128
129 snd_seq_event_t event;
130 snd_seq_ev_clear (&event);
131
132 auto numBytes = (long) message.getRawDataSize();
133 auto* data = message.getRawData();
134
135 auto seqHandle = client->get();
136 bool success = true;
137
138 while (numBytes > 0)
139 {
140 auto numSent = snd_midi_event_encode (midiParser, data, numBytes, &event);
141
142 if (numSent <= 0)
143 {
144 success = numSent == 0;
145 break;
146 }
147
148 numBytes -= numSent;
149 data += numSent;
150
151 snd_seq_ev_set_source (&event, (unsigned char) portId);
152 snd_seq_ev_set_subs (&event);
153 snd_seq_ev_set_direct (&event);
154
155 if (snd_seq_event_output_direct (seqHandle, &event) < 0)
156 {
157 success = false;
158 break;
159 }
160 }
161
162 snd_midi_event_reset_encode (midiParser);
163 return success;
164 }
165
166
167 bool operator== (const Port& lhs) const noexcept
168 {
169 return portId != -1 && portId == lhs.portId;
170 }
171
172 void createPort (const String& name, bool enableSubscription)
173 {
174 if (auto seqHandle = client->get())
175 {
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));
179
180 portName = name;
181 portId = snd_seq_create_simple_port (seqHandle, portName.toUTF8(), caps,
182 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
183 SND_SEQ_PORT_TYPE_APPLICATION);
184 }
185 }
186
187 void handleIncomingMidiMessage (const MidiMessage& message) const
188 {
189 if (callbackEnabled)
190 callback->handleIncomingMidiMessage (midiInput, message);
191 }
192
193 void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp)
194 {
195 if (callbackEnabled)
196 callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp);
197 }
198
199 int getPortId() const { return portId; }
200 const String& getPortName() const { return portName; }
201
202 private:
203 const std::shared_ptr<AlsaClient> client = AlsaClient::getInstance();
204
205 MidiInputCallback* callback = nullptr;
206 snd_midi_event_t* midiParser = nullptr;
207 MidiInput* midiInput = nullptr;
208
209 String portName;
210
211 int maxEventSize = 4096, portId = -1;
212 std::atomic<bool> callbackEnabled { false };
213 bool isInput = false;
214 };
215
216 static std::shared_ptr<AlsaClient> getInstance()
217 {
218 static std::weak_ptr<AlsaClient> ptr;
219
220 if (auto locked = ptr.lock())
221 return locked;
222
224 ptr = result;
225 return result;
226 }
227
228 void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message)
229 {
230 const ScopedLock sl (callbackLock);
231
232 if (auto* port = findPort (event->dest.port))
233 port->handleIncomingMidiMessage (message);
234 }
235
236 void handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp)
237 {
238 const ScopedLock sl (callbackLock);
239
240 if (auto* port = findPort (event->dest.port))
241 port->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp);
242 }
243
244 snd_seq_t* get() const noexcept { return handle; }
245 int getId() const noexcept { return clientId; }
246
247 Port* createPort (const String& name, bool forInput, bool enableSubscription)
248 {
249 const ScopedLock sl (callbackLock);
250
251 auto port = new Port (forInput);
252 port->createPort (name, enableSubscription);
253
254 const auto iter = lowerBound (port->getPortId());
255 jassert (iter == ports.end() || port->getPortId() < (*iter)->getPortId());
256 ports.insert (iter, rawToUniquePtr (port));
257
258 return port;
259 }
260
261 void deletePort (Port* port)
262 {
263 const ScopedLock sl (callbackLock);
264
265 if (const auto iter = findPortIterator (port->getPortId()); iter != ports.end())
266 ports.erase (iter);
267 }
268
269private:
270 AlsaClient()
271 {
272 snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
273
274 if (handle != nullptr)
275 {
277 snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8());
278 clientId = snd_seq_client_id (handle);
279
280 // It's good idea to pre-allocate a good number of elements
281 ports.reserve (32);
282
284 TRANS ("announcements").toRawUTF8(),
288
289 inputThread.emplace (*this);
290 }
291 }
292
293 Port* findPort (int portId)
294 {
295 if (const auto iter = findPortIterator (portId); iter != ports.end())
296 return iter->get();
297
298 return nullptr;
299 }
300
301 snd_seq_t* handle = nullptr;
302 int clientId = 0;
303 int announcementsIn = 0;
305 Atomic<int> activeCallbacks;
306 CriticalSection callbackLock;
307
308 //==============================================================================
309 class SequencerThread
310 {
311 public:
312 explicit SequencerThread (AlsaClient& c)
313 : client (c)
314 {
315 }
316
317 ~SequencerThread() noexcept
318 {
319 shouldStop = true;
320 thread.join();
321 }
322
323 private:
324 // If we directly call MidiDeviceListConnectionBroadcaster::get() from the background thread,
325 // there's a possibility that we'll deadlock in the following scenario:
326 // - The main thread calls MidiDeviceListConnectionBroadcaster::get() for the first time
327 // (e.g. to register a listener). The static MidiDeviceListConnectionBroadcaster singleton
328 // begins construction. During the constructor, an AlsaClient is created to iterate midi
329 // ins/outs.
330 // - The AlsaClient starts a new SequencerThread. If connections are updated, the
331 // SequencerThread may call MidiDeviceListConnectionBroadcaster::get().notify()
332 // while the MidiDeviceListConnectionBroadcaster singleton is still being created.
333 // - The SequencerThread blocks until the MidiDeviceListConnectionBroadcaster has been
334 // created on the main thread, but the MidiDeviceListConnectionBroadcaster's constructor
335 // can't complete until the AlsaClient's destructor has run, which in turn requires the
336 // SequencerThread to join.
337 class UpdateNotifier final : private AsyncUpdater
338 {
339 public:
340 ~UpdateNotifier() override { cancelPendingUpdate(); }
341 using AsyncUpdater::triggerAsyncUpdate;
342
343 private:
344 void handleAsyncUpdate() override { MidiDeviceListConnectionBroadcaster::get().notify(); }
345 };
346
347 AlsaClient& client;
348 MidiDataConcatenator concatenator { 2048 };
349 std::atomic<bool> shouldStop { false };
350 UpdateNotifier notifier;
351 std::thread thread { [this]
352 {
353 Thread::setCurrentThreadName ("JUCE MIDI Input");
354
355 auto seqHandle = client.get();
356
357 const int maxEventSize = 16 * 1024;
358 snd_midi_event_t* midiParser;
359
360 if (snd_midi_event_new (maxEventSize, &midiParser) >= 0)
361 {
362 const ScopeGuard freeMidiEvent { [&] { snd_midi_event_free (midiParser); } };
363
364 const auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN);
365 std::vector<pollfd> pfd (static_cast<size_t> (numPfds));
366 snd_seq_poll_descriptors (seqHandle, pfd.data(), (unsigned int) numPfds, POLLIN);
367
368 std::vector<uint8> buffer (maxEventSize);
369
370 while (! shouldStop)
371 {
372 // This timeout shouldn't be too long, so that the program can exit in a timely manner
373 if (poll (pfd.data(), (nfds_t) numPfds, 100) > 0)
374 {
375 if (shouldStop)
376 break;
377
378 do
379 {
380 snd_seq_event_t* inputEvent = nullptr;
381
382 if (snd_seq_event_input (seqHandle, &inputEvent) >= 0)
383 {
384 const ScopeGuard freeInputEvent { [&] { snd_seq_free_event (inputEvent); } };
385
386 constexpr int systemEvents[]
387 {
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,
396 };
397
398 const auto foundEvent = std::find (std::begin (systemEvents),
399 std::end (systemEvents),
400 inputEvent->type);
401
402 if (foundEvent != std::end (systemEvents))
403 {
404 notifier.triggerAsyncUpdate();
405 continue;
406 }
407
408 // xxx what about SYSEXes that are too big for the buffer?
409 const auto numBytes = snd_midi_event_decode (midiParser,
410 buffer.data(),
411 maxEventSize,
412 inputEvent);
413
414 snd_midi_event_reset_decode (midiParser);
415
416 concatenator.pushMidiData (buffer.data(), (int) numBytes,
417 Time::getMillisecondCounter() * 0.001,
418 inputEvent, client);
419 }
420 }
421 while (snd_seq_event_input_pending (seqHandle, 0) > 0);
422 }
423 }
424 }
425 } };
426 };
427
429};
430
431//==============================================================================
432static String getFormattedPortIdentifier (int clientId, int portId)
433{
434 return String (clientId) + "-" + String (portId);
435}
436
437static AlsaClient::Port* iterateMidiClient (AlsaClient& client,
439 bool forInput,
441 const String& deviceIdentifierToOpen)
442{
443 AlsaClient::Port* port = nullptr;
444
445 auto seqHandle = client.get();
446 snd_seq_port_info_t* portInfo = nullptr;
447
449 jassert (portInfo != nullptr);
452
455
456 while (--numPorts >= 0)
457 {
461 {
464
465 MidiDeviceInfo device (portName, getFormattedPortIdentifier (sourceClient, portID));
466 devices.add (device);
467
468 if (deviceIdentifierToOpen.isNotEmpty() && deviceIdentifierToOpen == device.identifier)
469 {
470 if (portID != -1)
471 {
472 port = client.createPort (portName, forInput, false);
473 jassert (port->isValid());
474 port->connectWith (sourceClient, portID);
475 break;
476 }
477 }
478 }
479 }
480
481 return port;
482}
483
484static AlsaClient::Port* iterateMidiDevices (bool forInput,
486 const String& deviceIdentifierToOpen)
487{
488 AlsaClient::Port* port = nullptr;
489 auto client = AlsaClient::getInstance();
490
491 if (auto seqHandle = client->get())
492 {
495
497 jassert (systemInfo != nullptr);
498
500 {
502 jassert (clientInfo != nullptr);
503
505
506 while (--numClients >= 0)
507 {
509 {
510 port = iterateMidiClient (*client,
512 forInput,
513 devices,
515
516 if (port != nullptr)
517 break;
518 }
519 }
520 }
521 }
522
523 return port;
524}
525
526struct AlsaPortPtr
527{
528 explicit AlsaPortPtr (AlsaClient::Port* p)
529 : ptr (p) {}
530
531 virtual ~AlsaPortPtr() noexcept { AlsaClient::getInstance()->deletePort (ptr); }
532
533 AlsaClient::Port* ptr = nullptr;
534};
535
536//==============================================================================
537class MidiInput::Pimpl final : public AlsaPortPtr
538{
539public:
540 using AlsaPortPtr::AlsaPortPtr;
541};
542
544{
546 iterateMidiDevices (true, devices, {});
547
548 return devices;
549}
550
551MidiDeviceInfo MidiInput::getDefaultDevice()
552{
553 return getAvailableDevices().getFirst();
554}
555
556std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
557{
558 if (deviceIdentifier.isEmpty())
559 return {};
560
562 auto* port = iterateMidiDevices (true, devices, deviceIdentifier);
563
564 if (port == nullptr || ! port->isValid())
565 return {};
566
567 jassert (port->isValid());
568
569 std::unique_ptr<MidiInput> midiInput (new MidiInput (port->getPortName(), deviceIdentifier));
570
571 port->setupInput (midiInput.get(), callback);
572 midiInput->internal = std::make_unique<Pimpl> (port);
573
574 return midiInput;
575}
576
577std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
578{
579 auto client = AlsaClient::getInstance();
580 auto* port = client->createPort (deviceName, true, true);
581
582 if (port == nullptr || ! port->isValid())
583 return {};
584
585 std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->getPortId())));
586
587 port->setupInput (midiInput.get(), callback);
588 midiInput->internal = std::make_unique<Pimpl> (port);
589
590 return midiInput;
591}
592
593StringArray MidiInput::getDevices()
594{
595 StringArray deviceNames;
596
597 for (auto& d : getAvailableDevices())
598 deviceNames.add (d.name);
599
600 deviceNames.appendNumbersToDuplicates (true, true);
601
602 return deviceNames;
603}
604
605int MidiInput::getDefaultDeviceIndex()
606{
607 return 0;
608}
609
610std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback)
611{
612 return openDevice (getAvailableDevices()[index].identifier, callback);
613}
614
615MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
616 : deviceInfo (deviceName, deviceIdentifier)
617{
618}
619
620MidiInput::~MidiInput()
621{
622 stop();
623}
624
625void MidiInput::start()
626{
627 internal->ptr->enableCallback (true);
628}
629
630void MidiInput::stop()
631{
632 internal->ptr->enableCallback (false);
633}
634
635//==============================================================================
636class MidiOutput::Pimpl final : public AlsaPortPtr
637{
638public:
639 using AlsaPortPtr::AlsaPortPtr;
640};
641
642Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
643{
644 Array<MidiDeviceInfo> devices;
645 iterateMidiDevices (false, devices, {});
646
647 return devices;
648}
649
650MidiDeviceInfo MidiOutput::getDefaultDevice()
651{
652 return getAvailableDevices().getFirst();
653}
654
655std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
656{
657 if (deviceIdentifier.isEmpty())
658 return {};
659
660 Array<MidiDeviceInfo> devices;
661 auto* port = iterateMidiDevices (false, devices, deviceIdentifier);
662
663 if (port == nullptr || ! port->isValid())
664 return {};
665
666 std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (port->getPortName(), deviceIdentifier));
667
668 port->setupOutput();
669 midiOutput->internal = std::make_unique<Pimpl> (port);
670
671 return midiOutput;
672}
673
674std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String& deviceName)
675{
676 auto client = AlsaClient::getInstance();
677 auto* port = client->createPort (deviceName, false, true);
678
679 if (port == nullptr || ! port->isValid())
680 return {};
681
682 std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->getPortId())));
683
684 port->setupOutput();
685 midiOutput->internal = std::make_unique<Pimpl> (port);
686
687 return midiOutput;
688}
689
690StringArray MidiOutput::getDevices()
691{
692 StringArray deviceNames;
693
694 for (auto& d : getAvailableDevices())
695 deviceNames.add (d.name);
696
697 deviceNames.appendNumbersToDuplicates (true, true);
698
699 return deviceNames;
700}
701
702int MidiOutput::getDefaultDeviceIndex()
703{
704 return 0;
705}
706
707std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index)
708{
709 return openDevice (getAvailableDevices()[index].identifier);
710}
711
712MidiOutput::~MidiOutput()
713{
714 stopBackgroundThread();
715}
716
717void MidiOutput::sendMessageNow (const MidiMessage& message)
718{
719 internal->ptr->sendMessageNow (message);
720}
721
722MidiDeviceListConnection MidiDeviceListConnection::make (std::function<void()> cb)
723{
724 auto& broadcaster = MidiDeviceListConnectionBroadcaster::get();
725 // We capture the AlsaClient instance here to ensure that it remains alive for at least as long
726 // as the MidiDeviceListConnection. This is necessary because system change messages will only
727 // be processed when the AlsaClient's SequencerThread is running.
728 return { &broadcaster, broadcaster.add ([fn = std::move (cb), client = AlsaClient::getInstance()]
729 {
730 NullCheckedInvocation::invoke (fn);
731 }) };
732}
733
734// moved here from trkn/juce_audio_devices/midi_io/juce_MidiDevices.cpp
735MidiOutput::MidiOutput (const String& deviceName, const String& deviceIdentifier)
736 : Thread ("midi out"), deviceInfo (deviceName, deviceIdentifier)
737{
738}
739
740//==============================================================================
741#else
742
744
745// (These are just stub functions if ALSA is unavailable...)
746MidiInput::MidiInput (const String& deviceName, const String& deviceID)
747 : deviceInfo (deviceName, deviceID)
748{
749}
750
758StringArray MidiInput::getDevices() { return {}; }
759int MidiInput::getDefaultDeviceIndex() { return 0;}
760std::unique_ptr<MidiInput> MidiInput::openDevice (int, MidiInputCallback*) { return {}; }
761
763
770StringArray MidiOutput::getDevices() { return {}; }
771int MidiOutput::getDefaultDeviceIndex() { return 0;}
773
775{
776 auto& broadcaster = MidiDeviceListConnectionBroadcaster::get();
777 return { &broadcaster, broadcaster.add (std::move (cb)) };
778}
779
780#endif
781
782} // namespace juce
T begin(T... args)
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:56
static JUCEApplicationBase * getInstance() noexcept
Returns the global instance of the application object that's running.
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.
Receives incoming messages from a physical MIDI input device.
~MidiInput()
Destructor.
void stop()
Stops the device running.
static Array< MidiDeviceInfo > getAvailableDevices()
Returns a list of the available midi input devices.
static MidiDeviceInfo getDefaultDevice()
Returns the MidiDeviceInfo of the default midi input device to use.
static std::unique_ptr< MidiInput > createNewDevice(const String &deviceName, MidiInputCallback *callback)
This will try to create a new midi input device (only available on Linux, macOS and iOS).
static std::unique_ptr< MidiInput > openDevice(const String &deviceIdentifier, MidiInputCallback *callback)
Tries to open one of the midi input devices.
void start()
Starts the device running.
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.
The JUCE String class!
Definition juce_String.h:53
T data(T... args)
T end(T... args)
T find(T... args)
#define TRANS(stringLiteral)
Uses the LocalisedStrings class to translate the given string literal.
#define jassert(expression)
Platform-independent assertion macro.
auto & get(ProcessorChain< Processors... > &chain) noexcept
Non-member equivalent of ProcessorChain::get which avoids awkward member template syntax.
T internal(T... args)
T lock(T... args)
T lower_bound(T... args)
JUCE Namespace.
CriticalSection::ScopedLockType ScopedLock
Automatically locks and unlocks a CriticalSection object.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
unsigned char uint8
A platform-independent 8-bit unsigned integer type.
std::unique_ptr< T > rawToUniquePtr(T *ptr)
Converts an owning raw pointer into a unique_ptr, deriving the type of the unique_ptr automatically.
This struct contains information about a MIDI input or output device.
typedef long