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 #ifndef DRV_QUERYDEVICEINTERFACE
24 #define DRV_RESERVED 0x0800
25 #define DRV_QUERYDEVICEINTERFACE (DRV_RESERVED + 12)
26 #define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13)
27 #endif
28
29 namespace juce
30 {
31
32 class MidiInput::Pimpl
33 {
34 public:
35 virtual ~Pimpl() noexcept = default;
36
37 virtual String getDeviceIdentifier() = 0;
38 virtual String getDeviceName() = 0;
39
40 virtual void start() = 0;
41 virtual void stop() = 0;
42 };
43
44 class MidiOutput::Pimpl
45 {
46 public:
47 virtual ~Pimpl() noexcept = default;
48
49 virtual String getDeviceIdentifier() = 0;
50 virtual String getDeviceName() = 0;
51
52 virtual void sendMessageNow (const MidiMessage&) = 0;
53 };
54
55 struct MidiServiceType
56 {
57 MidiServiceType() = default;
58 virtual ~MidiServiceType() noexcept = default;
59
60 virtual Array<MidiDeviceInfo> getAvailableDevices (bool) = 0;
61 virtual MidiDeviceInfo getDefaultDevice (bool) = 0;
62
63 virtual MidiInput::Pimpl* createInputWrapper (MidiInput&, const String&, MidiInputCallback&) = 0;
64 virtual MidiOutput::Pimpl* createOutputWrapper (const String&) = 0;
65
66 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType)
67 };
68
69 //==============================================================================
70 struct Win32MidiService : public MidiServiceType,
71 private Timer
72 {
Win32MidiServicejuce::Win32MidiService73 Win32MidiService() {}
74
getAvailableDevicesjuce::Win32MidiService75 Array<MidiDeviceInfo> getAvailableDevices (bool isInput) override
76 {
77 return isInput ? Win32InputWrapper::getAvailableDevices()
78 : Win32OutputWrapper::getAvailableDevices();
79 }
80
getDefaultDevicejuce::Win32MidiService81 MidiDeviceInfo getDefaultDevice (bool isInput) override
82 {
83 return isInput ? Win32InputWrapper::getDefaultDevice()
84 : Win32OutputWrapper::getDefaultDevice();
85 }
86
createInputWrapperjuce::Win32MidiService87 MidiInput::Pimpl* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override
88 {
89 return new Win32InputWrapper (*this, input, deviceIdentifier, callback);
90 }
91
createOutputWrapperjuce::Win32MidiService92 MidiOutput::Pimpl* createOutputWrapper (const String& deviceIdentifier) override
93 {
94 return new Win32OutputWrapper (*this, deviceIdentifier);
95 }
96
97 private:
98 struct Win32InputWrapper;
99
100 //==============================================================================
101 struct MidiInCollector : public ReferenceCountedObject
102 {
MidiInCollectorjuce::Win32MidiService::MidiInCollector103 MidiInCollector (Win32MidiService& s, MidiDeviceInfo d)
104 : deviceInfo (d), midiService (s)
105 {
106 }
107
~MidiInCollectorjuce::Win32MidiService::MidiInCollector108 ~MidiInCollector()
109 {
110 stop();
111
112 if (deviceHandle != 0)
113 {
114 for (int count = 5; --count >= 0;)
115 {
116 if (midiInClose (deviceHandle) == MMSYSERR_NOERROR)
117 break;
118
119 Sleep (20);
120 }
121 }
122 }
123
124 using Ptr = ReferenceCountedObjectPtr<MidiInCollector>;
125
addClientjuce::Win32MidiService::MidiInCollector126 void addClient (Win32InputWrapper* c)
127 {
128 const ScopedLock sl (clientLock);
129 jassert (! clients.contains (c));
130 clients.add (c);
131 }
132
removeClientjuce::Win32MidiService::MidiInCollector133 void removeClient (Win32InputWrapper* c)
134 {
135 const ScopedLock sl (clientLock);
136 clients.removeFirstMatchingValue (c);
137 startOrStop();
138 midiService.asyncCheckForUnusedCollectors();
139 }
140
handleMessagejuce::Win32MidiService::MidiInCollector141 void handleMessage (const uint8* bytes, uint32 timeStamp)
142 {
143 if (bytes[0] >= 0x80 && isStarted.load())
144 {
145 {
146 auto len = MidiMessage::getMessageLengthFromFirstByte (bytes[0]);
147 auto time = convertTimeStamp (timeStamp);
148 const ScopedLock sl (clientLock);
149
150 for (auto* c : clients)
151 c->pushMidiData (bytes, len, time);
152 }
153
154 writeFinishedBlocks();
155 }
156 }
157
handleSysExjuce::Win32MidiService::MidiInCollector158 void handleSysEx (MIDIHDR* hdr, uint32 timeStamp)
159 {
160 if (isStarted.load() && hdr->dwBytesRecorded > 0)
161 {
162 {
163 auto time = convertTimeStamp (timeStamp);
164 const ScopedLock sl (clientLock);
165
166 for (auto* c : clients)
167 c->pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded, time);
168 }
169
170 writeFinishedBlocks();
171 }
172 }
173
startOrStopjuce::Win32MidiService::MidiInCollector174 void startOrStop()
175 {
176 const ScopedLock sl (clientLock);
177
178 if (countRunningClients() == 0)
179 stop();
180 else
181 start();
182 }
183
startjuce::Win32MidiService::MidiInCollector184 void start()
185 {
186 if (deviceHandle != 0 && ! isStarted.load())
187 {
188 activeMidiCollectors.addIfNotAlreadyThere (this);
189
190 for (int i = 0; i < (int) numHeaders; ++i)
191 {
192 headers[i].prepare (deviceHandle);
193 headers[i].write (deviceHandle);
194 }
195
196 startTime = Time::getMillisecondCounterHiRes();
197 auto res = midiInStart (deviceHandle);
198
199 if (res == MMSYSERR_NOERROR)
200 isStarted = true;
201 else
202 unprepareAllHeaders();
203 }
204 }
205
stopjuce::Win32MidiService::MidiInCollector206 void stop()
207 {
208 if (isStarted.load())
209 {
210 isStarted = false;
211 midiInReset (deviceHandle);
212 midiInStop (deviceHandle);
213 activeMidiCollectors.removeFirstMatchingValue (this);
214 unprepareAllHeaders();
215 }
216 }
217
midiInCallbackjuce::Win32MidiService::MidiInCollector218 static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance,
219 DWORD_PTR midiMessage, DWORD_PTR timeStamp)
220 {
221 auto* collector = reinterpret_cast<MidiInCollector*> (dwInstance);
222
223 // This is primarily a check for the collector being a dangling
224 // pointer, as the callback can sometimes be delayed
225 if (activeMidiCollectors.contains (collector))
226 {
227 if (uMsg == MIM_DATA)
228 collector->handleMessage ((const uint8*) &midiMessage, (uint32) timeStamp);
229 else if (uMsg == MIM_LONGDATA)
230 collector->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp);
231 }
232 }
233
234 MidiDeviceInfo deviceInfo;
235 HMIDIIN deviceHandle = 0;
236
237 private:
238 Win32MidiService& midiService;
239 CriticalSection clientLock;
240 Array<Win32InputWrapper*> clients;
241 std::atomic<bool> isStarted { false };
242 double startTime = 0;
243
244 // This static array is used to prevent occasional callbacks to objects that are
245 // in the process of being deleted
246 static Array<MidiInCollector*, CriticalSection> activeMidiCollectors;
247
countRunningClientsjuce::Win32MidiService::MidiInCollector248 int countRunningClients() const
249 {
250 int num = 0;
251
252 for (auto* c : clients)
253 if (c->started)
254 ++num;
255
256 return num;
257 }
258
259 struct MidiHeader
260 {
MidiHeaderjuce::Win32MidiService::MidiInCollector::MidiHeader261 MidiHeader() {}
262
preparejuce::Win32MidiService::MidiInCollector::MidiHeader263 void prepare (HMIDIIN device)
264 {
265 zerostruct (hdr);
266 hdr.lpData = data;
267 hdr.dwBufferLength = (DWORD) numElementsInArray (data);
268
269 midiInPrepareHeader (device, &hdr, sizeof (hdr));
270 }
271
unpreparejuce::Win32MidiService::MidiInCollector::MidiHeader272 void unprepare (HMIDIIN device)
273 {
274 if ((hdr.dwFlags & WHDR_DONE) != 0)
275 {
276 int c = 10;
277 while (--c >= 0 && midiInUnprepareHeader (device, &hdr, sizeof (hdr)) == MIDIERR_STILLPLAYING)
278 Thread::sleep (20);
279
280 jassert (c >= 0);
281 }
282 }
283
writejuce::Win32MidiService::MidiInCollector::MidiHeader284 void write (HMIDIIN device)
285 {
286 hdr.dwBytesRecorded = 0;
287 midiInAddBuffer (device, &hdr, sizeof (hdr));
288 }
289
writeIfFinishedjuce::Win32MidiService::MidiInCollector::MidiHeader290 void writeIfFinished (HMIDIIN device)
291 {
292 if ((hdr.dwFlags & WHDR_DONE) != 0)
293 write (device);
294 }
295
296 MIDIHDR hdr;
297 char data[256];
298
299 JUCE_DECLARE_NON_COPYABLE (MidiHeader)
300 };
301
302 enum { numHeaders = 32 };
303 MidiHeader headers[numHeaders];
304
writeFinishedBlocksjuce::Win32MidiService::MidiInCollector305 void writeFinishedBlocks()
306 {
307 for (int i = 0; i < (int) numHeaders; ++i)
308 headers[i].writeIfFinished (deviceHandle);
309 }
310
unprepareAllHeadersjuce::Win32MidiService::MidiInCollector311 void unprepareAllHeaders()
312 {
313 for (int i = 0; i < (int) numHeaders; ++i)
314 headers[i].unprepare (deviceHandle);
315 }
316
convertTimeStampjuce::Win32MidiService::MidiInCollector317 double convertTimeStamp (uint32 timeStamp)
318 {
319 auto t = startTime + timeStamp;
320 auto now = Time::getMillisecondCounterHiRes();
321
322 if (t > now)
323 {
324 if (t > now + 2.0)
325 startTime -= 1.0;
326
327 t = now;
328 }
329
330 return t * 0.001;
331 }
332
333 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInCollector)
334 };
335
336 //==============================================================================
337 template<class WrapperType>
338 struct Win32MidiDeviceQuery
339 {
getAvailableDevicesjuce::Win32MidiService::Win32MidiDeviceQuery340 static Array<MidiDeviceInfo> getAvailableDevices()
341 {
342 StringArray deviceNames, deviceIDs;
343 auto deviceCaps = WrapperType::getDeviceCaps();
344
345 for (int i = 0; i < deviceCaps.size(); ++i)
346 {
347 deviceNames.add (deviceCaps[i].szPname);
348
349 auto identifier = getInterfaceIDForDevice ((UINT) i);
350
351 if (identifier.isNotEmpty())
352 deviceIDs.add (identifier);
353 else
354 deviceIDs.add (deviceNames[i]);
355 }
356
357 deviceNames.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
358 deviceIDs .appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
359
360 Array<MidiDeviceInfo> devices;
361
362 for (int i = 0; i < deviceNames.size(); ++i)
363 devices.add ({ deviceNames[i], deviceIDs[i] });
364
365 return devices;
366 }
367
368 private:
getInterfaceIDForDevicejuce::Win32MidiService::Win32MidiDeviceQuery369 static String getInterfaceIDForDevice (UINT id)
370 {
371 ULONG size = 0;
372
373 if (WrapperType::sendMidiMessage ((UINT_PTR) id, DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR) &size, 0) == MMSYSERR_NOERROR)
374 {
375 WCHAR interfaceName[512] = {};
376
377 if (isPositiveAndBelow (size, sizeof (interfaceName))
378 && WrapperType::sendMidiMessage ((UINT_PTR) id, DRV_QUERYDEVICEINTERFACE,
379 (DWORD_PTR) interfaceName, sizeof (interfaceName)) == MMSYSERR_NOERROR)
380 {
381 return interfaceName;
382 }
383 }
384
385 return {};
386 }
387 };
388
389 struct Win32InputWrapper : public MidiInput::Pimpl,
390 public Win32MidiDeviceQuery<Win32InputWrapper>
391 {
Win32InputWrapperjuce::Win32MidiService::Win32InputWrapper392 Win32InputWrapper (Win32MidiService& parentService, MidiInput& midiInput, const String& deviceIdentifier, MidiInputCallback& c)
393 : input (midiInput), callback (c)
394 {
395 collector = getOrCreateCollector (parentService, deviceIdentifier);
396 collector->addClient (this);
397 }
398
~Win32InputWrapperjuce::Win32MidiService::Win32InputWrapper399 ~Win32InputWrapper()
400 {
401 collector->removeClient (this);
402 }
403
getOrCreateCollectorjuce::Win32MidiService::Win32InputWrapper404 static MidiInCollector::Ptr getOrCreateCollector (Win32MidiService& parentService, const String& deviceIdentifier)
405 {
406 UINT deviceID = MIDI_MAPPER;
407 String deviceName;
408 auto devices = getAvailableDevices();
409
410 for (int i = 0; i < devices.size(); ++i)
411 {
412 auto d = devices.getUnchecked (i);
413
414 if (d.identifier == deviceIdentifier)
415 {
416 deviceID = i;
417 deviceName = d.name;
418 break;
419 }
420 }
421
422 const ScopedLock sl (parentService.activeCollectorLock);
423
424 for (auto& c : parentService.activeCollectors)
425 if (c->deviceInfo.identifier == deviceIdentifier)
426 return c;
427
428 MidiInCollector::Ptr c (new MidiInCollector (parentService, { deviceName, deviceIdentifier }));
429
430 HMIDIIN h;
431 auto err = midiInOpen (&h, deviceID,
432 (DWORD_PTR) &MidiInCollector::midiInCallback,
433 (DWORD_PTR) (MidiInCollector*) c.get(),
434 CALLBACK_FUNCTION);
435
436 if (err != MMSYSERR_NOERROR)
437 throw std::runtime_error ("Failed to create Windows input device wrapper");
438
439 c->deviceHandle = h;
440 parentService.activeCollectors.add (c);
441 return c;
442 }
443
sendMidiMessagejuce::Win32MidiService::Win32InputWrapper444 static DWORD sendMidiMessage (UINT_PTR deviceID, UINT msg, DWORD_PTR arg1, DWORD_PTR arg2)
445 {
446 return midiInMessage ((HMIDIIN) deviceID, msg, arg1, arg2);
447 }
448
getDeviceCapsjuce::Win32MidiService::Win32InputWrapper449 static Array<MIDIINCAPS> getDeviceCaps()
450 {
451 Array<MIDIINCAPS> devices;
452
453 for (UINT i = 0; i < midiInGetNumDevs(); ++i)
454 {
455 MIDIINCAPS mc = {};
456
457 if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
458 devices.add (mc);
459 }
460
461 return devices;
462 }
463
getDefaultDevicejuce::Win32MidiService::Win32InputWrapper464 static MidiDeviceInfo getDefaultDevice() { return getAvailableDevices().getFirst(); }
465
startjuce::Win32MidiService::Win32InputWrapper466 void start() override { started = true; concatenator.reset(); collector->startOrStop(); }
stopjuce::Win32MidiService::Win32InputWrapper467 void stop() override { started = false; collector->startOrStop(); concatenator.reset(); }
468
getDeviceIdentifierjuce::Win32MidiService::Win32InputWrapper469 String getDeviceIdentifier() override { return collector->deviceInfo.identifier; }
getDeviceNamejuce::Win32MidiService::Win32InputWrapper470 String getDeviceName() override { return collector->deviceInfo.name; }
471
pushMidiDatajuce::Win32MidiService::Win32InputWrapper472 void pushMidiData (const void* inputData, int numBytes, double time)
473 {
474 concatenator.pushMidiData (inputData, numBytes, time, &input, callback);
475 }
476
477 MidiInput& input;
478 MidiInputCallback& callback;
479 MidiDataConcatenator concatenator { 4096 };
480 MidiInCollector::Ptr collector;
481 bool started = false;
482
483 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32InputWrapper)
484 };
485
486 //==============================================================================
487 struct MidiOutHandle : public ReferenceCountedObject
488 {
489 using Ptr = ReferenceCountedObjectPtr<MidiOutHandle>;
490
MidiOutHandlejuce::Win32MidiService::MidiOutHandle491 MidiOutHandle (Win32MidiService& parent, MidiDeviceInfo d, HMIDIOUT h)
492 : owner (parent), deviceInfo (d), handle (h)
493 {
494 owner.activeOutputHandles.add (this);
495 }
496
~MidiOutHandlejuce::Win32MidiService::MidiOutHandle497 ~MidiOutHandle()
498 {
499 if (handle != nullptr)
500 midiOutClose (handle);
501
502 owner.activeOutputHandles.removeFirstMatchingValue (this);
503 }
504
505 Win32MidiService& owner;
506 MidiDeviceInfo deviceInfo;
507 HMIDIOUT handle;
508
509 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutHandle)
510 };
511
512 //==============================================================================
513 struct Win32OutputWrapper : public MidiOutput::Pimpl,
514 public Win32MidiDeviceQuery<Win32OutputWrapper>
515 {
Win32OutputWrapperjuce::Win32MidiService::Win32OutputWrapper516 Win32OutputWrapper (Win32MidiService& p, const String& deviceIdentifier)
517 : parent (p)
518 {
519 auto devices = getAvailableDevices();
520 UINT deviceID = MIDI_MAPPER;
521 String deviceName;
522
523 for (int i = 0; i < devices.size(); ++i)
524 {
525 auto d = devices.getUnchecked (i);
526
527 if (d.identifier == deviceIdentifier)
528 {
529 deviceID = i;
530 deviceName = d.name;
531 break;
532 }
533 }
534
535 if (deviceID == MIDI_MAPPER)
536 {
537 // use the microsoft sw synth as a default - best not to allow deviceID
538 // to be MIDI_MAPPER, or else device sharing breaks
539 for (int i = 0; i < devices.size(); ++i)
540 if (devices[i].name.containsIgnoreCase ("microsoft"))
541 deviceID = (UINT) i;
542 }
543
544 for (int i = parent.activeOutputHandles.size(); --i >= 0;)
545 {
546 auto* activeHandle = parent.activeOutputHandles.getUnchecked (i);
547
548 if (activeHandle->deviceInfo.identifier == deviceIdentifier)
549 {
550 han = activeHandle;
551 return;
552 }
553 }
554
555 for (int i = 4; --i >= 0;)
556 {
557 HMIDIOUT h = 0;
558 auto res = midiOutOpen (&h, deviceID, 0, 0, CALLBACK_NULL);
559
560 if (res == MMSYSERR_NOERROR)
561 {
562 han = new MidiOutHandle (parent, { deviceName, deviceIdentifier }, h);
563 return;
564 }
565
566 if (res == MMSYSERR_ALLOCATED)
567 Sleep (100);
568 else
569 break;
570 }
571
572 throw std::runtime_error ("Failed to create Windows output device wrapper");
573 }
574
sendMessageNowjuce::Win32MidiService::Win32OutputWrapper575 void sendMessageNow (const MidiMessage& message) override
576 {
577 if (message.getRawDataSize() > 3 || message.isSysEx())
578 {
579 MIDIHDR h = {};
580
581 h.lpData = (char*) message.getRawData();
582 h.dwBytesRecorded = h.dwBufferLength = (DWORD) message.getRawDataSize();
583
584 if (midiOutPrepareHeader (han->handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR)
585 {
586 auto res = midiOutLongMsg (han->handle, &h, sizeof (MIDIHDR));
587
588 if (res == MMSYSERR_NOERROR)
589 {
590 while ((h.dwFlags & MHDR_DONE) == 0)
591 Sleep (1);
592
593 int count = 500; // 1 sec timeout
594
595 while (--count >= 0)
596 {
597 res = midiOutUnprepareHeader (han->handle, &h, sizeof (MIDIHDR));
598
599 if (res == MIDIERR_STILLPLAYING)
600 Sleep (2);
601 else
602 break;
603 }
604 }
605 }
606 }
607 else
608 {
609 for (int i = 0; i < 50; ++i)
610 {
611 if (midiOutShortMsg (han->handle, *(unsigned int*) message.getRawData()) != MIDIERR_NOTREADY)
612 break;
613
614 Sleep (1);
615 }
616 }
617 }
618
sendMidiMessagejuce::Win32MidiService::Win32OutputWrapper619 static DWORD sendMidiMessage (UINT_PTR deviceID, UINT msg, DWORD_PTR arg1, DWORD_PTR arg2)
620 {
621 return midiOutMessage ((HMIDIOUT) deviceID, msg, arg1, arg2);
622 }
623
getDeviceCapsjuce::Win32MidiService::Win32OutputWrapper624 static Array<MIDIOUTCAPS> getDeviceCaps()
625 {
626 Array<MIDIOUTCAPS> devices;
627
628 for (UINT i = 0; i < midiOutGetNumDevs(); ++i)
629 {
630 MIDIOUTCAPS mc = {};
631
632 if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
633 devices.add (mc);
634 }
635
636 return devices;
637 }
638
getDefaultDevicejuce::Win32MidiService::Win32OutputWrapper639 static MidiDeviceInfo getDefaultDevice()
640 {
641 auto defaultIndex = []()
642 {
643 auto deviceCaps = getDeviceCaps();
644
645 for (int i = 0; i < deviceCaps.size(); ++i)
646 if ((deviceCaps[i].wTechnology & MOD_MAPPER) != 0)
647 return i;
648
649 return 0;
650 }();
651
652 return getAvailableDevices()[defaultIndex];
653 }
654
getDeviceIdentifierjuce::Win32MidiService::Win32OutputWrapper655 String getDeviceIdentifier() override { return han->deviceInfo.identifier; }
getDeviceNamejuce::Win32MidiService::Win32OutputWrapper656 String getDeviceName() override { return han->deviceInfo.name; }
657
658 Win32MidiService& parent;
659 MidiOutHandle::Ptr han;
660
661 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32OutputWrapper)
662 };
663
664 //==============================================================================
asyncCheckForUnusedCollectorsjuce::Win32MidiService665 void asyncCheckForUnusedCollectors()
666 {
667 startTimer (10);
668 }
669
timerCallbackjuce::Win32MidiService670 void timerCallback() override
671 {
672 stopTimer();
673
674 const ScopedLock sl (activeCollectorLock);
675
676 for (int i = activeCollectors.size(); --i >= 0;)
677 if (activeCollectors.getObjectPointer(i)->getReferenceCount() == 1)
678 activeCollectors.remove (i);
679 }
680
681 CriticalSection activeCollectorLock;
682 ReferenceCountedArray<MidiInCollector> activeCollectors;
683 Array<MidiOutHandle*> activeOutputHandles;
684 };
685
686 Array<Win32MidiService::MidiInCollector*, CriticalSection> Win32MidiService::MidiInCollector::activeMidiCollectors;
687
688 //==============================================================================
689 //==============================================================================
690 #if JUCE_USE_WINRT_MIDI
691
692 #ifndef JUCE_FORCE_WINRT_MIDI
693 #define JUCE_FORCE_WINRT_MIDI 0
694 #endif
695
696 #ifndef JUCE_WINRT_MIDI_LOGGING
697 #define JUCE_WINRT_MIDI_LOGGING 0
698 #endif
699
700 #if JUCE_WINRT_MIDI_LOGGING
701 #define JUCE_WINRT_MIDI_LOG(x) DBG(x)
702 #else
703 #define JUCE_WINRT_MIDI_LOG(x)
704 #endif
705
706 using namespace Microsoft::WRL;
707
708 using namespace ABI::Windows::Foundation;
709 using namespace ABI::Windows::Foundation::Collections;
710 using namespace ABI::Windows::Devices::Midi;
711 using namespace ABI::Windows::Devices::Enumeration;
712 using namespace ABI::Windows::Storage::Streams;
713
714 //==============================================================================
715 struct WinRTMidiService : public MidiServiceType
716 {
717 public:
718 //==============================================================================
WinRTMidiServicejuce::WinRTMidiService719 WinRTMidiService()
720 {
721 auto* wrtWrapper = WinRTWrapper::getInstance();
722
723 if (! wrtWrapper->isInitialised())
724 throw std::runtime_error ("Failed to initialise the WinRT wrapper");
725
726 midiInFactory = wrtWrapper->getWRLFactory<IMidiInPortStatics> (&RuntimeClass_Windows_Devices_Midi_MidiInPort[0]);
727
728 if (midiInFactory == nullptr)
729 throw std::runtime_error ("Failed to create midi in factory");
730
731 midiOutFactory = wrtWrapper->getWRLFactory<IMidiOutPortStatics> (&RuntimeClass_Windows_Devices_Midi_MidiOutPort[0]);
732
733 if (midiOutFactory == nullptr)
734 throw std::runtime_error ("Failed to create midi out factory");
735
736 // The WinRT BLE MIDI API doesn't provide callbacks when devices become disconnected,
737 // but it does require a disconnection via the API before a device will reconnect again.
738 // We can monitor the BLE connection state of paired devices to get callbacks when
739 // connections are broken.
740 bleDeviceWatcher.reset (new BLEDeviceWatcher());
741
742 if (! bleDeviceWatcher->start())
743 throw std::runtime_error ("Failed to start the BLE device watcher");
744
745 inputDeviceWatcher.reset (new MidiIODeviceWatcher<IMidiInPortStatics> (midiInFactory));
746
747 if (! inputDeviceWatcher->start())
748 throw std::runtime_error ("Failed to start the midi input device watcher");
749
750 outputDeviceWatcher.reset (new MidiIODeviceWatcher<IMidiOutPortStatics> (midiOutFactory));
751
752 if (! outputDeviceWatcher->start())
753 throw std::runtime_error ("Failed to start the midi output device watcher");
754 }
755
getAvailableDevicesjuce::WinRTMidiService756 Array<MidiDeviceInfo> getAvailableDevices (bool isInput) override
757 {
758 return isInput ? inputDeviceWatcher ->getAvailableDevices()
759 : outputDeviceWatcher->getAvailableDevices();
760 }
761
getDefaultDevicejuce::WinRTMidiService762 MidiDeviceInfo getDefaultDevice (bool isInput) override
763 {
764 return isInput ? inputDeviceWatcher ->getDefaultDevice()
765 : outputDeviceWatcher->getDefaultDevice();
766 }
767
createInputWrapperjuce::WinRTMidiService768 MidiInput::Pimpl* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override
769 {
770 return new WinRTInputWrapper (*this, input, deviceIdentifier, callback);
771 }
772
createOutputWrapperjuce::WinRTMidiService773 MidiOutput::Pimpl* createOutputWrapper (const String& deviceIdentifier) override
774 {
775 return new WinRTOutputWrapper (*this, deviceIdentifier);
776 }
777
778 private:
779 //==============================================================================
780 class DeviceCallbackHandler
781 {
782 public:
~DeviceCallbackHandler()783 virtual ~DeviceCallbackHandler() {};
784
785 virtual HRESULT addDevice (IDeviceInformation*) = 0;
786 virtual HRESULT removeDevice (IDeviceInformationUpdate*) = 0;
787 virtual HRESULT updateDevice (IDeviceInformationUpdate*) = 0;
788
attach(HSTRING deviceSelector,DeviceInformationKind infoKind)789 bool attach (HSTRING deviceSelector, DeviceInformationKind infoKind)
790 {
791 auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
792
793 if (wrtWrapper == nullptr)
794 {
795 JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
796 return false;
797 }
798
799 auto deviceInfoFactory = wrtWrapper->getWRLFactory<IDeviceInformationStatics2> (&RuntimeClass_Windows_Devices_Enumeration_DeviceInformation[0]);
800
801 if (deviceInfoFactory == nullptr)
802 return false;
803
804 // A quick way of getting an IVector<HSTRING>...
805 auto requestedProperties = [wrtWrapper]
806 {
807 auto devicePicker = wrtWrapper->activateInstance<IDevicePicker> (&RuntimeClass_Windows_Devices_Enumeration_DevicePicker[0],
808 __uuidof (IDevicePicker));
809 jassert (devicePicker != nullptr);
810
811 IVector<HSTRING>* result;
812 auto hr = devicePicker->get_RequestedProperties (&result);
813 jassert (SUCCEEDED (hr));
814
815 hr = result->Clear();
816 jassert (SUCCEEDED (hr));
817
818 return result;
819 }();
820
821 StringArray propertyKeys ("System.Devices.ContainerId",
822 "System.Devices.Aep.ContainerId",
823 "System.Devices.Aep.IsConnected");
824
825 for (auto& key : propertyKeys)
826 {
827 WinRTWrapper::ScopedHString hstr (key);
828 auto hr = requestedProperties->Append (hstr.get());
829
830 if (FAILED (hr))
831 {
832 jassertfalse;
833 return false;
834 }
835 }
836
837 WinRTWrapper::ComPtr<IIterable<HSTRING>> iter;
838 auto hr = requestedProperties->QueryInterface (__uuidof (IIterable<HSTRING>), (void**) iter.resetAndGetPointerAddress());
839
840 if (FAILED (hr))
841 {
842 jassertfalse;
843 return false;
844 }
845
846 hr = deviceInfoFactory->CreateWatcherWithKindAqsFilterAndAdditionalProperties (deviceSelector, iter, infoKind,
847 watcher.resetAndGetPointerAddress());
848
849 if (FAILED (hr))
850 {
851 jassertfalse;
852 return false;
853 }
854
855 enumerationThread.startThread();
856
857 return true;
858 };
859
detach()860 void detach()
861 {
862 enumerationThread.stopThread (2000);
863
864 if (watcher == nullptr)
865 return;
866
867 auto hr = watcher->Stop();
868 jassert (SUCCEEDED (hr));
869
870 if (deviceAddedToken.value != 0)
871 {
872 hr = watcher->remove_Added (deviceAddedToken);
873 jassert (SUCCEEDED (hr));
874 deviceAddedToken.value = 0;
875 }
876
877 if (deviceUpdatedToken.value != 0)
878 {
879 hr = watcher->remove_Updated (deviceUpdatedToken);
880 jassert (SUCCEEDED (hr));
881 deviceUpdatedToken.value = 0;
882 }
883
884 if (deviceRemovedToken.value != 0)
885 {
886 hr = watcher->remove_Removed (deviceRemovedToken);
887 jassert (SUCCEEDED (hr));
888 deviceRemovedToken.value = 0;
889 }
890
891 watcher = nullptr;
892 }
893
894 template<typename InfoType>
getValueFromDeviceInfo(String key,InfoType * info)895 IInspectable* getValueFromDeviceInfo (String key, InfoType* info)
896 {
897 __FIMapView_2_HSTRING_IInspectable* properties;
898 info->get_Properties (&properties);
899
900 boolean found = false;
901 WinRTWrapper::ScopedHString keyHstr (key);
902 auto hr = properties->HasKey (keyHstr.get(), &found);
903
904 if (FAILED (hr))
905 {
906 jassertfalse;
907 return nullptr;
908 }
909
910 if (! found)
911 return nullptr;
912
913 IInspectable* inspectable;
914 hr = properties->Lookup (keyHstr.get(), &inspectable);
915
916 if (FAILED (hr))
917 {
918 jassertfalse;
919 return nullptr;
920 }
921
922 return inspectable;
923 }
924
getGUIDFromInspectable(IInspectable & inspectable)925 String getGUIDFromInspectable (IInspectable& inspectable)
926 {
927 WinRTWrapper::ComPtr<IReference<GUID>> guidRef;
928 auto hr = inspectable.QueryInterface (__uuidof (IReference<GUID>),
929 (void**) guidRef.resetAndGetPointerAddress());
930
931 if (FAILED (hr))
932 {
933 jassertfalse;
934 return {};
935 }
936
937 GUID result;
938 hr = guidRef->get_Value (&result);
939
940 if (FAILED (hr))
941 {
942 jassertfalse;
943 return {};
944 }
945
946 OLECHAR* resultString;
947 StringFromCLSID (result, &resultString);
948
949 return resultString;
950 }
951
getBoolFromInspectable(IInspectable & inspectable)952 bool getBoolFromInspectable (IInspectable& inspectable)
953 {
954 WinRTWrapper::ComPtr<IReference<bool>> boolRef;
955 auto hr = inspectable.QueryInterface (__uuidof (IReference<bool>),
956 (void**) boolRef.resetAndGetPointerAddress());
957
958 if (FAILED (hr))
959 {
960 jassertfalse;
961 return false;
962 }
963
964 boolean result;
965 hr = boolRef->get_Value (&result);
966
967 if (FAILED (hr))
968 {
969 jassertfalse;
970 return false;
971 }
972
973 return result;
974 }
975
976 private:
977 //==============================================================================
978 struct DeviceEnumerationThread : public Thread
979 {
DeviceEnumerationThreadjuce::WinRTMidiService::DeviceCallbackHandler::DeviceEnumerationThread980 DeviceEnumerationThread (DeviceCallbackHandler& h,
981 WinRTWrapper::ComPtr<IDeviceWatcher>& w,
982 EventRegistrationToken& added,
983 EventRegistrationToken& removed,
984 EventRegistrationToken& updated)
985 : Thread ("WinRT Device Enumeration Thread"), handler (h), watcher (w),
986 deviceAddedToken (added), deviceRemovedToken (removed), deviceUpdatedToken (updated)
987 {}
988
runjuce::WinRTMidiService::DeviceCallbackHandler::DeviceEnumerationThread989 void run() override
990 {
991 auto handlerPtr = std::addressof (handler);
992
993 watcher->add_Added (
994 Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformation*>> (
995 [handlerPtr] (IDeviceWatcher*, IDeviceInformation* info) { return handlerPtr->addDevice (info); }
996 ).Get(),
997 &deviceAddedToken);
998
999 watcher->add_Removed (
1000 Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>> (
1001 [handlerPtr] (IDeviceWatcher*, IDeviceInformationUpdate* infoUpdate) { return handlerPtr->removeDevice (infoUpdate); }
1002 ).Get(),
1003 &deviceRemovedToken);
1004
1005 watcher->add_Updated (
1006 Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>> (
1007 [handlerPtr] (IDeviceWatcher*, IDeviceInformationUpdate* infoUpdate) { return handlerPtr->updateDevice (infoUpdate); }
1008 ).Get(),
1009 &deviceUpdatedToken);
1010
1011 watcher->Start();
1012 }
1013
1014 DeviceCallbackHandler& handler;
1015 WinRTWrapper::ComPtr<IDeviceWatcher>& watcher;
1016 EventRegistrationToken& deviceAddedToken, deviceRemovedToken, deviceUpdatedToken;
1017 };
1018
1019 //==============================================================================
1020 WinRTWrapper::ComPtr<IDeviceWatcher> watcher;
1021
1022 EventRegistrationToken deviceAddedToken { 0 },
1023 deviceRemovedToken { 0 },
1024 deviceUpdatedToken { 0 };
1025
1026 DeviceEnumerationThread enumerationThread { *this, watcher,
1027 deviceAddedToken,
1028 deviceRemovedToken,
1029 deviceUpdatedToken };
1030 };
1031
1032 //==============================================================================
1033 struct BLEDeviceWatcher final : private DeviceCallbackHandler
1034 {
1035 struct DeviceInfo
1036 {
1037 String containerID;
1038 bool isConnected = false;
1039 };
1040
1041 BLEDeviceWatcher() = default;
1042
~BLEDeviceWatcherjuce::WinRTMidiService::BLEDeviceWatcher1043 ~BLEDeviceWatcher()
1044 {
1045 detach();
1046 }
1047
1048 //==============================================================================
addDevicejuce::WinRTMidiService::BLEDeviceWatcher1049 HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override
1050 {
1051 HSTRING deviceIDHst;
1052 auto hr = addedDeviceInfo->get_Id (&deviceIDHst);
1053
1054 if (FAILED (hr))
1055 {
1056 JUCE_WINRT_MIDI_LOG ("Failed to query added BLE device ID!");
1057 return S_OK;
1058 }
1059
1060 auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
1061
1062 if (wrtWrapper == nullptr)
1063 {
1064 JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
1065 return false;
1066 }
1067
1068 auto deviceID = wrtWrapper->hStringToString (deviceIDHst);
1069 JUCE_WINRT_MIDI_LOG ("Detected paired BLE device: " << deviceID);
1070
1071 if (auto* containerIDValue = getValueFromDeviceInfo ("System.Devices.Aep.ContainerId", addedDeviceInfo))
1072 {
1073 auto containerID = getGUIDFromInspectable (*containerIDValue);
1074
1075 if (containerID.isNotEmpty())
1076 {
1077 DeviceInfo info = { containerID };
1078
1079 if (auto* connectedValue = getValueFromDeviceInfo ("System.Devices.Aep.IsConnected", addedDeviceInfo))
1080 info.isConnected = getBoolFromInspectable (*connectedValue);
1081
1082 JUCE_WINRT_MIDI_LOG ("Adding BLE device: " << deviceID << " " << info.containerID
1083 << " " << (info.isConnected ? "connected" : "disconnected"));
1084 devices.set (deviceID, info);
1085
1086 return S_OK;
1087 }
1088 }
1089
1090 JUCE_WINRT_MIDI_LOG ("Failed to get a container ID for BLE device: " << deviceID);
1091 return S_OK;
1092 }
1093
removeDevicejuce::WinRTMidiService::BLEDeviceWatcher1094 HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo) override
1095 {
1096 HSTRING removedDeviceIdHstr;
1097 auto hr = removedDeviceInfo->get_Id (&removedDeviceIdHstr);
1098
1099 if (FAILED (hr))
1100 {
1101 JUCE_WINRT_MIDI_LOG ("Failed to query removed BLE device ID!");
1102 return S_OK;
1103 }
1104
1105 auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
1106
1107 if (wrtWrapper == nullptr)
1108 {
1109 JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
1110 return false;
1111 }
1112
1113 auto removedDeviceId = wrtWrapper->hStringToString (removedDeviceIdHstr);
1114
1115 JUCE_WINRT_MIDI_LOG ("Removing BLE device: " << removedDeviceId);
1116
1117 {
1118 const ScopedLock lock (deviceChanges);
1119
1120 if (devices.contains (removedDeviceId))
1121 {
1122 auto& info = devices.getReference (removedDeviceId);
1123 listeners.call ([&info] (Listener& l) { l.bleDeviceDisconnected (info.containerID); });
1124 devices.remove (removedDeviceId);
1125 JUCE_WINRT_MIDI_LOG ("Removed BLE device: " << removedDeviceId);
1126 }
1127 }
1128
1129 return S_OK;
1130 }
1131
updateDevicejuce::WinRTMidiService::BLEDeviceWatcher1132 HRESULT updateDevice (IDeviceInformationUpdate* updatedDeviceInfo) override
1133 {
1134 HSTRING updatedDeviceIdHstr;
1135 auto hr = updatedDeviceInfo->get_Id (&updatedDeviceIdHstr);
1136
1137 if (FAILED (hr))
1138 {
1139 JUCE_WINRT_MIDI_LOG ("Failed to query updated BLE device ID!");
1140 return S_OK;
1141 }
1142
1143 auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
1144
1145 if (wrtWrapper == nullptr)
1146 {
1147 JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
1148 return false;
1149 }
1150
1151 auto updatedDeviceId = wrtWrapper->hStringToString (updatedDeviceIdHstr);
1152
1153 JUCE_WINRT_MIDI_LOG ("Updating BLE device: " << updatedDeviceId);
1154
1155 if (auto* connectedValue = getValueFromDeviceInfo ("System.Devices.Aep.IsConnected", updatedDeviceInfo))
1156 {
1157 auto isConnected = getBoolFromInspectable (*connectedValue);
1158
1159 {
1160 const ScopedLock lock (deviceChanges);
1161
1162 if (! devices.contains (updatedDeviceId))
1163 return S_OK;
1164
1165 auto& info = devices.getReference (updatedDeviceId);
1166
1167 if (info.isConnected && ! isConnected)
1168 {
1169 JUCE_WINRT_MIDI_LOG ("BLE device connection status change: " << updatedDeviceId << " " << info.containerID << " " << (isConnected ? "connected" : "disconnected"));
1170 listeners.call ([&info] (Listener& l) { l.bleDeviceDisconnected (info.containerID); });
1171 }
1172
1173 info.isConnected = isConnected;
1174 }
1175 }
1176
1177 return S_OK;
1178 }
1179
1180 //==============================================================================
startjuce::WinRTMidiService::BLEDeviceWatcher1181 bool start()
1182 {
1183 WinRTWrapper::ScopedHString deviceSelector ("System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\""
1184 " AND System.Devices.Aep.IsPaired:=System.StructuredQueryType.Boolean#True");
1185 return attach (deviceSelector.get(), DeviceInformationKind::DeviceInformationKind_AssociationEndpoint);
1186 }
1187
1188 //==============================================================================
1189 struct Listener
1190 {
~Listenerjuce::WinRTMidiService::BLEDeviceWatcher::Listener1191 virtual ~Listener() {};
1192 virtual void bleDeviceAdded (const String& containerID) = 0;
1193 virtual void bleDeviceDisconnected (const String& containerID) = 0;
1194 };
1195
addListenerjuce::WinRTMidiService::BLEDeviceWatcher1196 void addListener (Listener* l)
1197 {
1198 listeners.add (l);
1199 }
1200
removeListenerjuce::WinRTMidiService::BLEDeviceWatcher1201 void removeListener (Listener* l)
1202 {
1203 listeners.remove (l);
1204 }
1205
1206 //==============================================================================
1207 ListenerList<Listener> listeners;
1208 HashMap<String, DeviceInfo> devices;
1209 CriticalSection deviceChanges;
1210
1211 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BLEDeviceWatcher);
1212 };
1213
1214 //==============================================================================
1215 struct WinRTMIDIDeviceInfo
1216 {
1217 String deviceID, containerID, name;
1218 bool isDefault = false;
1219 };
1220
1221 //==============================================================================
1222 template <typename COMFactoryType>
1223 struct MidiIODeviceWatcher final : private DeviceCallbackHandler
1224 {
MidiIODeviceWatcherjuce::WinRTMidiService::MidiIODeviceWatcher1225 MidiIODeviceWatcher (WinRTWrapper::ComPtr<COMFactoryType>& comFactory)
1226 : factory (comFactory)
1227 {
1228 }
1229
~MidiIODeviceWatcherjuce::WinRTMidiService::MidiIODeviceWatcher1230 ~MidiIODeviceWatcher()
1231 {
1232 detach();
1233 }
1234
addDevicejuce::WinRTMidiService::MidiIODeviceWatcher1235 HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override
1236 {
1237 WinRTMIDIDeviceInfo info;
1238
1239 HSTRING deviceID;
1240 auto hr = addedDeviceInfo->get_Id (&deviceID);
1241
1242 if (FAILED (hr))
1243 {
1244 JUCE_WINRT_MIDI_LOG ("Failed to query added MIDI device ID!");
1245 return S_OK;
1246 }
1247
1248 auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
1249
1250 if (wrtWrapper == nullptr)
1251 {
1252 JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
1253 return false;
1254 }
1255
1256 info.deviceID = wrtWrapper->hStringToString (deviceID);
1257
1258 JUCE_WINRT_MIDI_LOG ("Detected MIDI device: " << info.deviceID);
1259
1260 boolean isEnabled = false;
1261 hr = addedDeviceInfo->get_IsEnabled (&isEnabled);
1262
1263 if (FAILED (hr) || ! isEnabled)
1264 {
1265 JUCE_WINRT_MIDI_LOG ("MIDI device not enabled: " << info.deviceID);
1266 return S_OK;
1267 }
1268
1269 // We use the container ID to match a MIDI device with a generic BLE device, if possible
1270 if (auto* containerIDValue = getValueFromDeviceInfo ("System.Devices.ContainerId", addedDeviceInfo))
1271 info.containerID = getGUIDFromInspectable (*containerIDValue);
1272
1273 HSTRING name;
1274 hr = addedDeviceInfo->get_Name (&name);
1275
1276 if (FAILED (hr))
1277 {
1278 JUCE_WINRT_MIDI_LOG ("Failed to query detected MIDI device name for " << info.deviceID);
1279 return S_OK;
1280 }
1281
1282 info.name = wrtWrapper->hStringToString (name);
1283
1284 boolean isDefault = false;
1285 hr = addedDeviceInfo->get_IsDefault (&isDefault);
1286
1287 if (FAILED (hr))
1288 {
1289 JUCE_WINRT_MIDI_LOG ("Failed to query detected MIDI device defaultness for " << info.deviceID << " " << info.name);
1290 return S_OK;
1291 }
1292
1293 info.isDefault = isDefault;
1294
1295 JUCE_WINRT_MIDI_LOG ("Adding MIDI device: " << info.deviceID << " " << info.containerID << " " << info.name);
1296
1297 {
1298 const ScopedLock lock (deviceChanges);
1299 connectedDevices.add (info);
1300 }
1301
1302 return S_OK;
1303 }
1304
removeDevicejuce::WinRTMidiService::MidiIODeviceWatcher1305 HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo) override
1306 {
1307 HSTRING removedDeviceIdHstr;
1308 auto hr = removedDeviceInfo->get_Id (&removedDeviceIdHstr);
1309
1310 if (FAILED (hr))
1311 {
1312 JUCE_WINRT_MIDI_LOG ("Failed to query removed MIDI device ID!");
1313 return S_OK;
1314 }
1315
1316 auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
1317
1318 if (wrtWrapper == nullptr)
1319 {
1320 JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
1321 return false;
1322 }
1323
1324 auto removedDeviceId = wrtWrapper->hStringToString (removedDeviceIdHstr);
1325
1326 JUCE_WINRT_MIDI_LOG ("Removing MIDI device: " << removedDeviceId);
1327
1328 {
1329 const ScopedLock lock (deviceChanges);
1330
1331 for (int i = 0; i < connectedDevices.size(); ++i)
1332 {
1333 if (connectedDevices[i].deviceID == removedDeviceId)
1334 {
1335 connectedDevices.remove (i);
1336 JUCE_WINRT_MIDI_LOG ("Removed MIDI device: " << removedDeviceId);
1337 break;
1338 }
1339 }
1340 }
1341
1342 return S_OK;
1343 }
1344
1345 // This is never called
updateDevicejuce::WinRTMidiService::MidiIODeviceWatcher1346 HRESULT updateDevice (IDeviceInformationUpdate*) override { return S_OK; }
1347
startjuce::WinRTMidiService::MidiIODeviceWatcher1348 bool start()
1349 {
1350 HSTRING deviceSelector;
1351 auto hr = factory->GetDeviceSelector (&deviceSelector);
1352
1353 if (FAILED (hr))
1354 {
1355 JUCE_WINRT_MIDI_LOG ("Failed to get MIDI device selector!");
1356 return false;
1357 }
1358
1359 return attach (deviceSelector, DeviceInformationKind::DeviceInformationKind_DeviceInterface);
1360 }
1361
getAvailableDevicesjuce::WinRTMidiService::MidiIODeviceWatcher1362 Array<MidiDeviceInfo> getAvailableDevices()
1363 {
1364 {
1365 const ScopedLock lock (deviceChanges);
1366 lastQueriedConnectedDevices = connectedDevices;
1367 }
1368
1369 StringArray deviceNames, deviceIDs;
1370
1371 for (auto info : lastQueriedConnectedDevices.get())
1372 {
1373 deviceNames.add (info.name);
1374 deviceIDs .add (info.containerID);
1375 }
1376
1377 deviceNames.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
1378 deviceIDs .appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
1379
1380 Array<MidiDeviceInfo> devices;
1381
1382 for (int i = 0; i < deviceNames.size(); ++i)
1383 devices.add ({ deviceNames[i], deviceIDs[i] });
1384
1385 return devices;
1386 }
1387
getDefaultDevicejuce::WinRTMidiService::MidiIODeviceWatcher1388 MidiDeviceInfo getDefaultDevice()
1389 {
1390 auto& lastDevices = lastQueriedConnectedDevices.get();
1391
1392 for (auto& d : lastDevices)
1393 if (d.isDefault)
1394 return { d.name, d.containerID };
1395
1396 return {};
1397 }
1398
getWinRTDeviceInfoForDevicejuce::WinRTMidiService::MidiIODeviceWatcher1399 WinRTMIDIDeviceInfo getWinRTDeviceInfoForDevice (const String& deviceIdentifier)
1400 {
1401 auto devices = getAvailableDevices();
1402
1403 for (int i = 0; i < devices.size(); ++i)
1404 if (devices.getUnchecked (i).identifier == deviceIdentifier)
1405 return lastQueriedConnectedDevices.get()[i];
1406
1407 return {};
1408 }
1409
1410 WinRTWrapper::ComPtr<COMFactoryType>& factory;
1411
1412 Array<WinRTMIDIDeviceInfo> connectedDevices;
1413 CriticalSection deviceChanges;
1414 ThreadLocalValue<Array<WinRTMIDIDeviceInfo>> lastQueriedConnectedDevices;
1415
1416 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher);
1417 };
1418
1419 //==============================================================================
1420 template <typename COMFactoryType, typename COMInterfaceType, typename COMType>
1421 struct OpenMidiPortThread : public Thread
1422 {
OpenMidiPortThreadjuce::WinRTMidiService::OpenMidiPortThread1423 OpenMidiPortThread (String threadName, String midiDeviceID,
1424 WinRTWrapper::ComPtr<COMFactoryType>& comFactory,
1425 WinRTWrapper::ComPtr<COMInterfaceType>& comPort)
1426 : Thread (threadName),
1427 deviceID (midiDeviceID),
1428 factory (comFactory),
1429 port (comPort)
1430 {
1431 }
1432
~OpenMidiPortThreadjuce::WinRTMidiService::OpenMidiPortThread1433 ~OpenMidiPortThread()
1434 {
1435 stopThread (2000);
1436 }
1437
runjuce::WinRTMidiService::OpenMidiPortThread1438 void run() override
1439 {
1440 WinRTWrapper::ScopedHString hDeviceId (deviceID);
1441 WinRTWrapper::ComPtr<IAsyncOperation<COMType*>> asyncOp;
1442 auto hr = factory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress());
1443
1444 if (FAILED (hr))
1445 return;
1446
1447 hr = asyncOp->put_Completed (Callback<IAsyncOperationCompletedHandler<COMType*>> (
1448 [this] (IAsyncOperation<COMType*>* asyncOpPtr, AsyncStatus)
1449 {
1450 if (asyncOpPtr == nullptr)
1451 return E_ABORT;
1452
1453 auto hr = asyncOpPtr->GetResults (port.resetAndGetPointerAddress());
1454
1455 if (FAILED (hr))
1456 return hr;
1457
1458 portOpened.signal();
1459 return S_OK;
1460 }
1461 ).Get());
1462
1463 // We need to use a timeout here, rather than waiting indefinitely, as the
1464 // WinRT API can occasionally hang!
1465 portOpened.wait (2000);
1466 }
1467
1468 const String deviceID;
1469 WinRTWrapper::ComPtr<COMFactoryType>& factory;
1470 WinRTWrapper::ComPtr<COMInterfaceType>& port;
1471 WaitableEvent portOpened { true };
1472 };
1473
1474 //==============================================================================
1475 template <typename MIDIIOStaticsType, typename MIDIPort>
1476 class WinRTIOWrapper : private BLEDeviceWatcher::Listener
1477 {
1478 public:
WinRTIOWrapper(BLEDeviceWatcher & bleWatcher,MidiIODeviceWatcher<MIDIIOStaticsType> & midiDeviceWatcher,const String & deviceIdentifier)1479 WinRTIOWrapper (BLEDeviceWatcher& bleWatcher,
1480 MidiIODeviceWatcher<MIDIIOStaticsType>& midiDeviceWatcher,
1481 const String& deviceIdentifier)
1482 : bleDeviceWatcher (bleWatcher)
1483 {
1484 {
1485 const ScopedLock lock (midiDeviceWatcher.deviceChanges);
1486 deviceInfo = midiDeviceWatcher.getWinRTDeviceInfoForDevice (deviceIdentifier);
1487 }
1488
1489 if (deviceInfo.deviceID.isEmpty())
1490 throw std::runtime_error ("Invalid device index");
1491
1492 JUCE_WINRT_MIDI_LOG ("Creating JUCE MIDI IO: " << deviceInfo.deviceID);
1493
1494 if (deviceInfo.containerID.isNotEmpty())
1495 {
1496 bleDeviceWatcher.addListener (this);
1497
1498 const ScopedLock lock (bleDeviceWatcher.deviceChanges);
1499
1500 HashMap<String, BLEDeviceWatcher::DeviceInfo>::Iterator iter (bleDeviceWatcher.devices);
1501
1502 while (iter.next())
1503 {
1504 if (iter.getValue().containerID == deviceInfo.containerID)
1505 {
1506 isBLEDevice = true;
1507 break;
1508 }
1509 }
1510 }
1511 }
1512
~WinRTIOWrapper()1513 virtual ~WinRTIOWrapper()
1514 {
1515 bleDeviceWatcher.removeListener (this);
1516
1517 disconnect();
1518 }
1519
1520 //==============================================================================
disconnect()1521 virtual void disconnect()
1522 {
1523 if (midiPort != nullptr)
1524 {
1525 if (isBLEDevice)
1526 midiPort->Release();
1527 }
1528
1529 midiPort = nullptr;
1530 }
1531
1532 private:
1533 //==============================================================================
bleDeviceAdded(const String & containerID)1534 void bleDeviceAdded (const String& containerID) override
1535 {
1536 if (containerID == deviceInfo.containerID)
1537 isBLEDevice = true;
1538 }
1539
bleDeviceDisconnected(const String & containerID)1540 void bleDeviceDisconnected (const String& containerID) override
1541 {
1542 if (containerID == deviceInfo.containerID)
1543 {
1544 JUCE_WINRT_MIDI_LOG ("Disconnecting MIDI port from BLE disconnection: " << deviceInfo.deviceID
1545 << " " << deviceInfo.containerID << " " << deviceInfo.name);
1546 disconnect();
1547 }
1548 }
1549
1550 protected:
1551 //==============================================================================
1552 BLEDeviceWatcher& bleDeviceWatcher;
1553 WinRTMIDIDeviceInfo deviceInfo;
1554 bool isBLEDevice = false;
1555 WinRTWrapper::ComPtr<MIDIPort> midiPort;
1556 };
1557
1558 //==============================================================================
1559 struct WinRTInputWrapper final : public MidiInput::Pimpl,
1560 private WinRTIOWrapper<IMidiInPortStatics, IMidiInPort>
1561
1562 {
WinRTInputWrapperjuce::WinRTMidiService::WinRTInputWrapper1563 WinRTInputWrapper (WinRTMidiService& service, MidiInput& input, const String& deviceIdentifier, MidiInputCallback& cb)
1564 : WinRTIOWrapper <IMidiInPortStatics, IMidiInPort> (*service.bleDeviceWatcher, *service.inputDeviceWatcher, deviceIdentifier),
1565 inputDevice (input),
1566 callback (cb)
1567 {
1568 OpenMidiPortThread<IMidiInPortStatics, IMidiInPort, MidiInPort> portThread ("Open WinRT MIDI input port",
1569 deviceInfo.deviceID,
1570 service.midiInFactory,
1571 midiPort);
1572 portThread.startThread();
1573 portThread.waitForThreadToExit (-1);
1574
1575 if (midiPort == nullptr)
1576 {
1577 JUCE_WINRT_MIDI_LOG ("Timed out waiting for midi input port creation");
1578 return;
1579 }
1580
1581 startTime = Time::getMillisecondCounterHiRes();
1582
1583 auto hr = midiPort->add_MessageReceived (
1584 Callback<ITypedEventHandler<MidiInPort*, MidiMessageReceivedEventArgs*>> (
1585 [this] (IMidiInPort*, IMidiMessageReceivedEventArgs* args) { return midiInMessageReceived (args); }
1586 ).Get(),
1587 &midiInMessageToken);
1588
1589 if (FAILED (hr))
1590 {
1591 JUCE_WINRT_MIDI_LOG ("Failed to set MIDI input callback");
1592 jassertfalse;
1593 }
1594 }
1595
~WinRTInputWrapperjuce::WinRTMidiService::WinRTInputWrapper1596 ~WinRTInputWrapper()
1597 {
1598 disconnect();
1599 }
1600
1601 //==============================================================================
startjuce::WinRTMidiService::WinRTInputWrapper1602 void start() override
1603 {
1604 if (! isStarted)
1605 {
1606 concatenator.reset();
1607 isStarted = true;
1608 }
1609 }
1610
stopjuce::WinRTMidiService::WinRTInputWrapper1611 void stop() override
1612 {
1613 if (isStarted)
1614 {
1615 isStarted = false;
1616 concatenator.reset();
1617 }
1618 }
1619
getDeviceIdentifierjuce::WinRTMidiService::WinRTInputWrapper1620 String getDeviceIdentifier() override { return deviceInfo.containerID; }
getDeviceNamejuce::WinRTMidiService::WinRTInputWrapper1621 String getDeviceName() override { return deviceInfo.name; }
1622
1623 //==============================================================================
disconnectjuce::WinRTMidiService::WinRTInputWrapper1624 void disconnect() override
1625 {
1626 stop();
1627
1628 if (midiPort != nullptr && midiInMessageToken.value != 0)
1629 midiPort->remove_MessageReceived (midiInMessageToken);
1630
1631 WinRTIOWrapper<IMidiInPortStatics, IMidiInPort>::disconnect();
1632 }
1633
1634 //==============================================================================
midiInMessageReceivedjuce::WinRTMidiService::WinRTInputWrapper1635 HRESULT midiInMessageReceived (IMidiMessageReceivedEventArgs* args)
1636 {
1637 if (! isStarted)
1638 return S_OK;
1639
1640 WinRTWrapper::ComPtr<IMidiMessage> message;
1641 auto hr = args->get_Message (message.resetAndGetPointerAddress());
1642
1643 if (FAILED (hr))
1644 return hr;
1645
1646 WinRTWrapper::ComPtr<IBuffer> buffer;
1647 hr = message->get_RawData (buffer.resetAndGetPointerAddress());
1648
1649 if (FAILED (hr))
1650 return hr;
1651
1652 WinRTWrapper::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
1653 hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress());
1654
1655 if (FAILED (hr))
1656 return hr;
1657
1658 uint8_t* bufferData = nullptr;
1659 hr = bufferByteAccess->Buffer (&bufferData);
1660
1661 if (FAILED (hr))
1662 return hr;
1663
1664 uint32_t numBytes = 0;
1665 hr = buffer->get_Length (&numBytes);
1666
1667 if (FAILED (hr))
1668 return hr;
1669
1670 ABI::Windows::Foundation::TimeSpan timespan;
1671 hr = message->get_Timestamp (×pan);
1672
1673 if (FAILED (hr))
1674 return hr;
1675
1676 concatenator.pushMidiData (bufferData, numBytes,
1677 convertTimeStamp (timespan.Duration),
1678 &inputDevice, callback);
1679 return S_OK;
1680 }
1681
convertTimeStampjuce::WinRTMidiService::WinRTInputWrapper1682 double convertTimeStamp (int64 timestamp)
1683 {
1684 auto millisecondsSinceStart = static_cast<double> (timestamp) / 10000.0;
1685 auto t = startTime + millisecondsSinceStart;
1686 auto now = Time::getMillisecondCounterHiRes();
1687
1688 if (t > now)
1689 {
1690 if (t > now + 2.0)
1691 startTime -= 1.0;
1692
1693 t = now;
1694 }
1695
1696 return t * 0.001;
1697 }
1698
1699 //==============================================================================
1700 MidiInput& inputDevice;
1701 MidiInputCallback& callback;
1702
1703 MidiDataConcatenator concatenator { 4096 };
1704 EventRegistrationToken midiInMessageToken { 0 };
1705
1706 double startTime = 0;
1707 bool isStarted = false;
1708
1709 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTInputWrapper);
1710 };
1711
1712 //==============================================================================
1713 struct WinRTOutputWrapper final : public MidiOutput::Pimpl,
1714 private WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort>
1715 {
WinRTOutputWrapperjuce::WinRTMidiService::WinRTOutputWrapper1716 WinRTOutputWrapper (WinRTMidiService& service, const String& deviceIdentifier)
1717 : WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort> (*service.bleDeviceWatcher, *service.outputDeviceWatcher, deviceIdentifier)
1718 {
1719 OpenMidiPortThread<IMidiOutPortStatics, IMidiOutPort, IMidiOutPort> portThread ("Open WinRT MIDI output port",
1720 deviceInfo.deviceID,
1721 service.midiOutFactory,
1722 midiPort);
1723 portThread.startThread();
1724 portThread.waitForThreadToExit (-1);
1725
1726 if (midiPort == nullptr)
1727 throw std::runtime_error ("Timed out waiting for midi output port creation");
1728
1729 auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
1730
1731 if (wrtWrapper == nullptr)
1732 throw std::runtime_error ("Failed to get the WinRTWrapper singleton!");
1733
1734 auto bufferFactory = wrtWrapper->getWRLFactory<IBufferFactory> (&RuntimeClass_Windows_Storage_Streams_Buffer[0]);
1735
1736 if (bufferFactory == nullptr)
1737 throw std::runtime_error ("Failed to create output buffer factory");
1738
1739 auto hr = bufferFactory->Create (static_cast<UINT32> (65536), buffer.resetAndGetPointerAddress());
1740
1741 if (FAILED (hr))
1742 throw std::runtime_error ("Failed to create output buffer");
1743
1744 hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress());
1745
1746 if (FAILED (hr))
1747 throw std::runtime_error ("Failed to get buffer byte access");
1748
1749 hr = bufferByteAccess->Buffer (&bufferData);
1750
1751 if (FAILED (hr))
1752 throw std::runtime_error ("Failed to get buffer data pointer");
1753 }
1754
1755 //==============================================================================
sendMessageNowjuce::WinRTMidiService::WinRTOutputWrapper1756 void sendMessageNow (const MidiMessage& message) override
1757 {
1758 if (midiPort == nullptr)
1759 return;
1760
1761 auto numBytes = message.getRawDataSize();
1762 auto hr = buffer->put_Length (numBytes);
1763
1764 if (FAILED (hr))
1765 {
1766 jassertfalse;
1767 return;
1768 }
1769
1770 memcpy_s (bufferData, numBytes, message.getRawData(), numBytes);
1771 midiPort->SendBuffer (buffer);
1772 }
1773
getDeviceIdentifierjuce::WinRTMidiService::WinRTOutputWrapper1774 String getDeviceIdentifier() override { return deviceInfo.containerID; }
getDeviceNamejuce::WinRTMidiService::WinRTOutputWrapper1775 String getDeviceName() override { return deviceInfo.name; }
1776
1777 //==============================================================================
1778 WinRTWrapper::ComPtr<IBuffer> buffer;
1779 WinRTWrapper::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
1780 uint8_t* bufferData = nullptr;
1781
1782 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTOutputWrapper);
1783 };
1784
1785 WinRTWrapper::ComPtr<IMidiInPortStatics> midiInFactory;
1786 WinRTWrapper::ComPtr<IMidiOutPortStatics> midiOutFactory;
1787
1788 std::unique_ptr<MidiIODeviceWatcher<IMidiInPortStatics>> inputDeviceWatcher;
1789 std::unique_ptr<MidiIODeviceWatcher<IMidiOutPortStatics>> outputDeviceWatcher;
1790 std::unique_ptr<BLEDeviceWatcher> bleDeviceWatcher;
1791
1792 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTMidiService)
1793 };
1794
1795 #endif // JUCE_USE_WINRT_MIDI
1796
1797 //==============================================================================
1798 //==============================================================================
1799 #if ! JUCE_MINGW
1800 extern RTL_OSVERSIONINFOW getWindowsVersionInfo();
1801 #endif
1802
1803 struct MidiService : public DeletedAtShutdown
1804 {
MidiServicejuce::MidiService1805 MidiService()
1806 {
1807 #if JUCE_USE_WINRT_MIDI && ! JUCE_MINGW
1808 #if ! JUCE_FORCE_WINRT_MIDI
1809 auto windowsVersionInfo = getWindowsVersionInfo();
1810 if (windowsVersionInfo.dwMajorVersion >= 10 && windowsVersionInfo.dwBuildNumber >= 17763)
1811 #endif
1812 {
1813 try
1814 {
1815 internal.reset (new WinRTMidiService());
1816 return;
1817 }
1818 catch (std::runtime_error&) {}
1819 }
1820 #endif
1821
1822 internal.reset (new Win32MidiService());
1823 }
1824
~MidiServicejuce::MidiService1825 ~MidiService()
1826 {
1827 clearSingletonInstance();
1828 }
1829
getServicejuce::MidiService1830 static MidiServiceType& getService()
1831 {
1832 jassert (getInstance()->internal != nullptr);
1833 return *getInstance()->internal.get();
1834 }
1835
1836 JUCE_DECLARE_SINGLETON (MidiService, false)
1837
1838 private:
1839 std::unique_ptr<MidiServiceType> internal;
1840 };
1841
JUCE_IMPLEMENT_SINGLETON(MidiService)1842 JUCE_IMPLEMENT_SINGLETON (MidiService)
1843
1844 //==============================================================================
1845 static int findDefaultDeviceIndex (const Array<MidiDeviceInfo>& available, const MidiDeviceInfo& defaultDevice)
1846 {
1847 for (int i = 0; i < available.size(); ++i)
1848 if (available.getUnchecked (i) == defaultDevice)
1849 return i;
1850
1851 return 0;
1852 }
1853
getAvailableDevices()1854 Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
1855 {
1856 return MidiService::getService().getAvailableDevices (true);
1857 }
1858
getDefaultDevice()1859 MidiDeviceInfo MidiInput::getDefaultDevice()
1860 {
1861 return MidiService::getService().getDefaultDevice (true);
1862 }
1863
openDevice(const String & deviceIdentifier,MidiInputCallback * callback)1864 std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
1865 {
1866 if (deviceIdentifier.isEmpty() || callback == nullptr)
1867 return {};
1868
1869 std::unique_ptr<MidiInput> in (new MidiInput ({}, deviceIdentifier));
1870 std::unique_ptr<Pimpl> wrapper;
1871
1872 try
1873 {
1874 wrapper.reset (MidiService::getService().createInputWrapper (*in, deviceIdentifier, *callback));
1875 }
1876 catch (std::runtime_error&)
1877 {
1878 return {};
1879 }
1880
1881 in->setName (wrapper->getDeviceName());
1882 in->internal = std::move (wrapper);
1883
1884 return in;
1885 }
1886
getDevices()1887 StringArray MidiInput::getDevices()
1888 {
1889 StringArray deviceNames;
1890
1891 for (auto& d : getAvailableDevices())
1892 deviceNames.add (d.name);
1893
1894 return deviceNames;
1895 }
1896
getDefaultDeviceIndex()1897 int MidiInput::getDefaultDeviceIndex()
1898 {
1899 return findDefaultDeviceIndex (getAvailableDevices(), getDefaultDevice());
1900 }
1901
openDevice(int index,MidiInputCallback * callback)1902 std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback)
1903 {
1904 return openDevice (getAvailableDevices()[index].identifier, callback);
1905 }
1906
MidiInput(const String & deviceName,const String & deviceIdentifier)1907 MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
1908 : deviceInfo (deviceName, deviceIdentifier)
1909 {
1910 }
1911
1912 MidiInput::~MidiInput() = default;
1913
start()1914 void MidiInput::start() { internal->start(); }
stop()1915 void MidiInput::stop() { internal->stop(); }
1916
1917 //==============================================================================
getAvailableDevices()1918 Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
1919 {
1920 return MidiService::getService().getAvailableDevices (false);
1921 }
1922
getDefaultDevice()1923 MidiDeviceInfo MidiOutput::getDefaultDevice()
1924 {
1925 return MidiService::getService().getDefaultDevice (false);
1926 }
1927
openDevice(const String & deviceIdentifier)1928 std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
1929 {
1930 if (deviceIdentifier.isEmpty())
1931 return {};
1932
1933 std::unique_ptr<Pimpl> wrapper;
1934
1935 try
1936 {
1937 wrapper.reset (MidiService::getService().createOutputWrapper (deviceIdentifier));
1938 }
1939 catch (std::runtime_error&)
1940 {
1941 return {};
1942 }
1943
1944 std::unique_ptr<MidiOutput> out;
1945 out.reset (new MidiOutput (wrapper->getDeviceName(), deviceIdentifier));
1946
1947 out->internal = std::move (wrapper);
1948
1949 return out;
1950 }
1951
getDevices()1952 StringArray MidiOutput::getDevices()
1953 {
1954 StringArray deviceNames;
1955
1956 for (auto& d : getAvailableDevices())
1957 deviceNames.add (d.name);
1958
1959 return deviceNames;
1960 }
1961
getDefaultDeviceIndex()1962 int MidiOutput::getDefaultDeviceIndex()
1963 {
1964 return findDefaultDeviceIndex (getAvailableDevices(), getDefaultDevice());
1965 }
1966
openDevice(int index)1967 std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index)
1968 {
1969 return openDevice (getAvailableDevices()[index].identifier);
1970 }
1971
~MidiOutput()1972 MidiOutput::~MidiOutput()
1973 {
1974 stopBackgroundThread();
1975 }
1976
sendMessageNow(const MidiMessage & message)1977 void MidiOutput::sendMessageNow (const MidiMessage& message)
1978 {
1979 internal->sendMessageNow (message);
1980 }
1981
1982 } // namespace juce
1983