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 (&timespan);
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