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