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