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 namespace juce
24 {
25 
26 #ifndef JUCE_WASAPI_LOGGING
27  #define JUCE_WASAPI_LOGGING 0
28 #endif
29 
30 //==============================================================================
31 namespace WasapiClasses
32 {
33 
logFailure(HRESULT hr)34 void logFailure (HRESULT hr)
35 {
36     ignoreUnused (hr);
37     jassert (hr != (HRESULT) 0x800401f0); // If you hit this, it means you're trying to call from
38                                           // a thread which hasn't been initialised with CoInitialize().
39 
40    #if JUCE_WASAPI_LOGGING
41     if (FAILED (hr))
42     {
43         const char* m = nullptr;
44 
45         switch (hr)
46         {
47             case E_POINTER:     m = "E_POINTER"; break;
48             case E_INVALIDARG:  m = "E_INVALIDARG"; break;
49             case E_NOINTERFACE: m = "E_NOINTERFACE"; break;
50 
51             #define JUCE_WASAPI_ERR(desc, n) \
52                 case MAKE_HRESULT(1, 0x889, n): m = #desc; break;
53 
54             JUCE_WASAPI_ERR (AUDCLNT_E_NOT_INITIALIZED, 0x001)
55             JUCE_WASAPI_ERR (AUDCLNT_E_ALREADY_INITIALIZED, 0x002)
56             JUCE_WASAPI_ERR (AUDCLNT_E_WRONG_ENDPOINT_TYPE, 0x003)
57             JUCE_WASAPI_ERR (AUDCLNT_E_DEVICE_INVALIDATED, 0x004)
58             JUCE_WASAPI_ERR (AUDCLNT_E_NOT_STOPPED, 0x005)
59             JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_TOO_LARGE, 0x006)
60             JUCE_WASAPI_ERR (AUDCLNT_E_OUT_OF_ORDER, 0x007)
61             JUCE_WASAPI_ERR (AUDCLNT_E_UNSUPPORTED_FORMAT, 0x008)
62             JUCE_WASAPI_ERR (AUDCLNT_E_INVALID_SIZE, 0x009)
63             JUCE_WASAPI_ERR (AUDCLNT_E_DEVICE_IN_USE, 0x00a)
64             JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_OPERATION_PENDING, 0x00b)
65             JUCE_WASAPI_ERR (AUDCLNT_E_THREAD_NOT_REGISTERED, 0x00c)
66             JUCE_WASAPI_ERR (AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED, 0x00e)
67             JUCE_WASAPI_ERR (AUDCLNT_E_ENDPOINT_CREATE_FAILED, 0x00f)
68             JUCE_WASAPI_ERR (AUDCLNT_E_SERVICE_NOT_RUNNING, 0x010)
69             JUCE_WASAPI_ERR (AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED, 0x011)
70             JUCE_WASAPI_ERR (AUDCLNT_E_EXCLUSIVE_MODE_ONLY, 0x012)
71             JUCE_WASAPI_ERR (AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL, 0x013)
72             JUCE_WASAPI_ERR (AUDCLNT_E_EVENTHANDLE_NOT_SET, 0x014)
73             JUCE_WASAPI_ERR (AUDCLNT_E_INCORRECT_BUFFER_SIZE, 0x015)
74             JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_SIZE_ERROR, 0x016)
75             JUCE_WASAPI_ERR (AUDCLNT_E_CPUUSAGE_EXCEEDED, 0x017)
76             JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_ERROR, 0x018)
77             JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED, 0x019)
78             JUCE_WASAPI_ERR (AUDCLNT_E_INVALID_DEVICE_PERIOD, 0x020)
79             default: break;
80         }
81 
82         Logger::writeToLog ("WASAPI error: " + (m != nullptr ? String (m)
83                                                              : String::toHexString ((int) hr)));
84     }
85    #endif
86 }
87 
88 #undef check
89 
check(HRESULT hr)90 bool check (HRESULT hr)
91 {
92     logFailure (hr);
93     return SUCCEEDED (hr);
94 }
95 
96 //==============================================================================
97 }
98 
99 #if JUCE_MINGW
100  struct PROPERTYKEY
101  {
102     GUID fmtid;
103     DWORD pid;
104  };
105 
106  WINOLEAPI PropVariantClear (PROPVARIANT*);
107 #endif
108 
109 #if JUCE_MINGW && defined (KSDATAFORMAT_SUBTYPE_PCM)
110  #undef KSDATAFORMAT_SUBTYPE_PCM
111  #undef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
112 #endif
113 
114 #ifndef KSDATAFORMAT_SUBTYPE_PCM
115  #define KSDATAFORMAT_SUBTYPE_PCM         uuidFromString ("00000001-0000-0010-8000-00aa00389b71")
116  #define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT  uuidFromString ("00000003-0000-0010-8000-00aa00389b71")
117 #endif
118 
119 #define JUCE_IUNKNOWNCLASS(name, guid)   JUCE_COMCLASS(name, guid) : public IUnknown
120 #define JUCE_COMCALL                     virtual HRESULT STDMETHODCALLTYPE
121 
122 enum EDataFlow
123 {
124     eRender = 0,
125     eCapture = (eRender + 1),
126     eAll = (eCapture + 1)
127 };
128 
129 enum
130 {
131     DEVICE_STATE_ACTIVE = 1
132 };
133 
134 enum
135 {
136     AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY = 1,
137     AUDCLNT_BUFFERFLAGS_SILENT = 2
138 };
139 
140 JUCE_IUNKNOWNCLASS (IPropertyStore, "886d8eeb-8cf2-4446-8d02-cdba1dbdcf99")
141 {
142     JUCE_COMCALL GetCount (DWORD*) = 0;
143     JUCE_COMCALL GetAt (DWORD, PROPERTYKEY*) = 0;
144     JUCE_COMCALL GetValue (const PROPERTYKEY&, PROPVARIANT*) = 0;
145     JUCE_COMCALL SetValue (const PROPERTYKEY&, const PROPVARIANT&) = 0;
146     JUCE_COMCALL Commit() = 0;
147 };
148 
149 JUCE_IUNKNOWNCLASS (IMMDevice, "D666063F-1587-4E43-81F1-B948E807363F")
150 {
151     JUCE_COMCALL Activate (REFIID, DWORD, PROPVARIANT*, void**) = 0;
152     JUCE_COMCALL OpenPropertyStore (DWORD, IPropertyStore**) = 0;
153     JUCE_COMCALL GetId (LPWSTR*) = 0;
154     JUCE_COMCALL GetState (DWORD*) = 0;
155 };
156 
157 JUCE_IUNKNOWNCLASS (IMMEndpoint, "1BE09788-6894-4089-8586-9A2A6C265AC5")
158 {
159     JUCE_COMCALL GetDataFlow (EDataFlow*) = 0;
160 };
161 
162 struct IMMDeviceCollection : public IUnknown
163 {
164     JUCE_COMCALL GetCount (UINT*) = 0;
165     JUCE_COMCALL Item (UINT, IMMDevice**) = 0;
166 };
167 
168 enum ERole
169 {
170     eConsole = 0,
171     eMultimedia = (eConsole + 1),
172     eCommunications = (eMultimedia + 1)
173 };
174 
175 JUCE_IUNKNOWNCLASS (IMMNotificationClient, "7991EEC9-7E89-4D85-8390-6C703CEC60C0")
176 {
177     JUCE_COMCALL OnDeviceStateChanged (LPCWSTR, DWORD) = 0;
178     JUCE_COMCALL OnDeviceAdded (LPCWSTR) = 0;
179     JUCE_COMCALL OnDeviceRemoved (LPCWSTR) = 0;
180     JUCE_COMCALL OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) = 0;
181     JUCE_COMCALL OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) = 0;
182 };
183 
184 JUCE_IUNKNOWNCLASS (IMMDeviceEnumerator, "A95664D2-9614-4F35-A746-DE8DB63617E6")
185 {
186     JUCE_COMCALL EnumAudioEndpoints (EDataFlow, DWORD, IMMDeviceCollection**) = 0;
187     JUCE_COMCALL GetDefaultAudioEndpoint (EDataFlow, ERole, IMMDevice**) = 0;
188     JUCE_COMCALL GetDevice (LPCWSTR, IMMDevice**) = 0;
189     JUCE_COMCALL RegisterEndpointNotificationCallback (IMMNotificationClient*) = 0;
190     JUCE_COMCALL UnregisterEndpointNotificationCallback (IMMNotificationClient*) = 0;
191 };
192 
193 JUCE_COMCLASS (MMDeviceEnumerator, "BCDE0395-E52F-467C-8E3D-C4579291692E");
194 
195 using REFERENCE_TIME = LONGLONG;
196 
197 enum AVRT_PRIORITY
198 {
199     AVRT_PRIORITY_LOW = -1,
200     AVRT_PRIORITY_NORMAL,
201     AVRT_PRIORITY_HIGH,
202     AVRT_PRIORITY_CRITICAL
203 };
204 
205 enum AUDCLNT_SHAREMODE
206 {
207     AUDCLNT_SHAREMODE_SHARED,
208     AUDCLNT_SHAREMODE_EXCLUSIVE
209 };
210 
211 enum AUDIO_STREAM_CATEGORY
212 {
213     AudioCategory_Other = 0,
214     AudioCategory_ForegroundOnlyMedia,
215     AudioCategory_BackgroundCapableMedia,
216     AudioCategory_Communications,
217     AudioCategory_Alerts,
218     AudioCategory_SoundEffects,
219     AudioCategory_GameEffects,
220     AudioCategory_GameMedia,
221     AudioCategory_GameChat,
222     AudioCategory_Speech,
223     AudioCategory_Movie,
224     AudioCategory_Media
225 };
226 
227 struct AudioClientProperties
228 {
229     UINT32                  cbSize;
230     BOOL                    bIsOffload;
231     AUDIO_STREAM_CATEGORY   eCategory;
232 };
233 
234 JUCE_IUNKNOWNCLASS (IAudioClient, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2")
235 {
236     JUCE_COMCALL Initialize (AUDCLNT_SHAREMODE, DWORD, REFERENCE_TIME, REFERENCE_TIME, const WAVEFORMATEX*, LPCGUID) = 0;
237     JUCE_COMCALL GetBufferSize (UINT32*) = 0;
238     JUCE_COMCALL GetStreamLatency (REFERENCE_TIME*) = 0;
239     JUCE_COMCALL GetCurrentPadding (UINT32*) = 0;
240     JUCE_COMCALL IsFormatSupported (AUDCLNT_SHAREMODE, const WAVEFORMATEX*, WAVEFORMATEX**) = 0;
241     JUCE_COMCALL GetMixFormat (WAVEFORMATEX**) = 0;
242     JUCE_COMCALL GetDevicePeriod (REFERENCE_TIME*, REFERENCE_TIME*) = 0;
243     JUCE_COMCALL Start() = 0;
244     JUCE_COMCALL Stop() = 0;
245     JUCE_COMCALL Reset() = 0;
246     JUCE_COMCALL SetEventHandle (HANDLE) = 0;
247     JUCE_COMCALL GetService (REFIID, void**) = 0;
248 };
249 
250 JUCE_COMCLASS (IAudioClient2, "726778CD-F60A-4eda-82DE-E47610CD78AA") : public IAudioClient
251 {
252     JUCE_COMCALL IsOffloadCapable (AUDIO_STREAM_CATEGORY, BOOL*) = 0;
253     JUCE_COMCALL SetClientProperties (const AudioClientProperties*) = 0;
254     JUCE_COMCALL GetBufferSizeLimits (const WAVEFORMATEX*, BOOL, REFERENCE_TIME*, REFERENCE_TIME*) = 0;
255 };
256 
257 JUCE_COMCLASS (IAudioClient3, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") : public IAudioClient2
258 {
259     JUCE_COMCALL GetSharedModeEnginePeriod (const WAVEFORMATEX*, UINT32*, UINT32*, UINT32*, UINT32*) = 0;
260     JUCE_COMCALL GetCurrentSharedModeEnginePeriod (WAVEFORMATEX**, UINT32*) = 0;
261     JUCE_COMCALL InitializeSharedAudioStream (DWORD, UINT32, const WAVEFORMATEX*, LPCGUID) = 0;
262 };
263 
264 JUCE_IUNKNOWNCLASS (IAudioCaptureClient, "C8ADBD64-E71E-48a0-A4DE-185C395CD317")
265 {
266     JUCE_COMCALL GetBuffer (BYTE**, UINT32*, DWORD*, UINT64*, UINT64*) = 0;
267     JUCE_COMCALL ReleaseBuffer (UINT32) = 0;
268     JUCE_COMCALL GetNextPacketSize (UINT32*) = 0;
269 };
270 
271 JUCE_IUNKNOWNCLASS (IAudioRenderClient, "F294ACFC-3146-4483-A7BF-ADDCA7C260E2")
272 {
273     JUCE_COMCALL GetBuffer (UINT32, BYTE**) = 0;
274     JUCE_COMCALL ReleaseBuffer (UINT32, DWORD) = 0;
275 };
276 
277 JUCE_IUNKNOWNCLASS (IAudioEndpointVolume, "5CDF2C82-841E-4546-9722-0CF74078229A")
278 {
279     JUCE_COMCALL RegisterControlChangeNotify (void*) = 0;
280     JUCE_COMCALL UnregisterControlChangeNotify (void*) = 0;
281     JUCE_COMCALL GetChannelCount (UINT*) = 0;
282     JUCE_COMCALL SetMasterVolumeLevel (float, LPCGUID) = 0;
283     JUCE_COMCALL SetMasterVolumeLevelScalar (float, LPCGUID) = 0;
284     JUCE_COMCALL GetMasterVolumeLevel (float*) = 0;
285     JUCE_COMCALL GetMasterVolumeLevelScalar (float*) = 0;
286     JUCE_COMCALL SetChannelVolumeLevel (UINT, float, LPCGUID) = 0;
287     JUCE_COMCALL SetChannelVolumeLevelScalar (UINT, float, LPCGUID) = 0;
288     JUCE_COMCALL GetChannelVolumeLevel (UINT, float*) = 0;
289     JUCE_COMCALL GetChannelVolumeLevelScalar (UINT, float*) = 0;
290     JUCE_COMCALL SetMute (BOOL, LPCGUID) = 0;
291     JUCE_COMCALL GetMute (BOOL*) = 0;
292     JUCE_COMCALL GetVolumeStepInfo (UINT*, UINT*) = 0;
293     JUCE_COMCALL VolumeStepUp (LPCGUID) = 0;
294     JUCE_COMCALL VolumeStepDown (LPCGUID) = 0;
295     JUCE_COMCALL QueryHardwareSupport (DWORD*) = 0;
296     JUCE_COMCALL GetVolumeRange (float*, float*, float*) = 0;
297 };
298 
299 enum AudioSessionDisconnectReason
300 {
301     DisconnectReasonDeviceRemoval         = 0,
302     DisconnectReasonServerShutdown        = 1,
303     DisconnectReasonFormatChanged         = 2,
304     DisconnectReasonSessionLogoff         = 3,
305     DisconnectReasonSessionDisconnected   = 4,
306     DisconnectReasonExclusiveModeOverride = 5
307 };
308 
309 enum AudioSessionState
310 {
311     AudioSessionStateInactive = 0,
312     AudioSessionStateActive   = 1,
313     AudioSessionStateExpired  = 2
314 };
315 
316 JUCE_IUNKNOWNCLASS (IAudioSessionEvents, "24918ACC-64B3-37C1-8CA9-74A66E9957A8")
317 {
318     JUCE_COMCALL OnDisplayNameChanged (LPCWSTR, LPCGUID) = 0;
319     JUCE_COMCALL OnIconPathChanged (LPCWSTR, LPCGUID) = 0;
320     JUCE_COMCALL OnSimpleVolumeChanged (float, BOOL, LPCGUID) = 0;
321     JUCE_COMCALL OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID) = 0;
322     JUCE_COMCALL OnGroupingParamChanged (LPCGUID, LPCGUID) = 0;
323     JUCE_COMCALL OnStateChanged (AudioSessionState) = 0;
324     JUCE_COMCALL OnSessionDisconnected (AudioSessionDisconnectReason) = 0;
325 };
326 
327 JUCE_IUNKNOWNCLASS (IAudioSessionControl, "F4B1A599-7266-4319-A8CA-E70ACB11E8CD")
328 {
329     JUCE_COMCALL GetState (AudioSessionState*) = 0;
330     JUCE_COMCALL GetDisplayName (LPWSTR*) = 0;
331     JUCE_COMCALL SetDisplayName (LPCWSTR, LPCGUID) = 0;
332     JUCE_COMCALL GetIconPath (LPWSTR*) = 0;
333     JUCE_COMCALL SetIconPath (LPCWSTR, LPCGUID) = 0;
334     JUCE_COMCALL GetGroupingParam (GUID*) = 0;
335     JUCE_COMCALL SetGroupingParam (LPCGUID, LPCGUID) = 0;
336     JUCE_COMCALL RegisterAudioSessionNotification (IAudioSessionEvents*) = 0;
337     JUCE_COMCALL UnregisterAudioSessionNotification (IAudioSessionEvents*) = 0;
338 };
339 
340 #undef JUCE_COMCALL
341 #undef JUCE_COMCLASS
342 #undef JUCE_IUNKNOWNCLASS
343 
344 //==============================================================================
345 namespace WasapiClasses
346 {
347 
348 String getDeviceID (IMMDevice* device)
349 {
350     String s;
351     WCHAR* deviceId = nullptr;
352 
353     if (check (device->GetId (&deviceId)))
354     {
355         s = String (deviceId);
356         CoTaskMemFree (deviceId);
357     }
358 
359     return s;
360 }
361 
362 static EDataFlow getDataFlow (const ComSmartPtr<IMMDevice>& device)
363 {
364     EDataFlow flow = eRender;
365     if (auto endpoint = device.getInterface<IMMEndpoint>())
366         (void) check (endpoint->GetDataFlow (&flow));
367 
368     return flow;
369 }
370 
371 static int refTimeToSamples (const REFERENCE_TIME& t, double sampleRate) noexcept
372 {
373     return roundToInt (sampleRate * ((double) t) * 0.0000001);
374 }
375 
376 static REFERENCE_TIME samplesToRefTime (int numSamples, double sampleRate) noexcept
377 {
378     return (REFERENCE_TIME) ((numSamples * 10000.0 * 1000.0 / sampleRate) + 0.5);
379 }
380 
381 static void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* src) noexcept
382 {
383     memcpy (&dest, src, src->wFormatTag == WAVE_FORMAT_EXTENSIBLE ? sizeof (WAVEFORMATEXTENSIBLE)
384                                                                   : sizeof (WAVEFORMATEX));
385 }
386 
387 static bool isExclusiveMode (WASAPIDeviceMode deviceMode) noexcept
388 {
389     return deviceMode == WASAPIDeviceMode::exclusive;
390 }
391 
392 static bool isLowLatencyMode (WASAPIDeviceMode deviceMode) noexcept
393 {
394     return deviceMode == WASAPIDeviceMode::sharedLowLatency;
395 }
396 
397 static bool supportsSampleRateConversion (WASAPIDeviceMode deviceMode) noexcept
398 {
399     return deviceMode == WASAPIDeviceMode::shared;
400 }
401 
402 //==============================================================================
403 class WASAPIDeviceBase
404 {
405 public:
406     WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
407         : device (d),
408           deviceMode (mode)
409     {
410         clientEvent = CreateEvent (nullptr, false, false, nullptr);
411 
412         ComSmartPtr<IAudioClient> tempClient (createClient());
413 
414         if (tempClient == nullptr)
415             return;
416 
417         WAVEFORMATEXTENSIBLE format;
418 
419         if (! getClientMixFormat (tempClient, format))
420             return;
421 
422         actualNumChannels = numChannels = format.Format.nChannels;
423         defaultSampleRate = format.Format.nSamplesPerSec;
424         rates.addUsingDefaultSort (defaultSampleRate);
425         mixFormatChannelMask = format.dwChannelMask;
426 
427         if (isExclusiveMode (deviceMode))
428             findSupportedFormat (tempClient, defaultSampleRate, mixFormatChannelMask, format);
429 
430         querySupportedBufferSizes (format, tempClient);
431         querySupportedSampleRates (format, tempClient);
432     }
433 
434     virtual ~WASAPIDeviceBase()
435     {
436         device = nullptr;
437         CloseHandle (clientEvent);
438     }
439 
440     bool isOk() const noexcept   { return defaultBufferSize > 0 && defaultSampleRate > 0; }
441 
442     bool openClient (const double newSampleRate, const BigInteger& newChannels, const int bufferSizeSamples)
443     {
444         sampleRate = newSampleRate;
445         channels = newChannels;
446         channels.setRange (actualNumChannels, channels.getHighestBit() + 1 - actualNumChannels, false);
447         numChannels = channels.getHighestBit() + 1;
448 
449         if (numChannels == 0)
450             return true;
451 
452         client = createClient();
453 
454         if (client != nullptr
455              && tryInitialisingWithBufferSize (bufferSizeSamples))
456         {
457             sampleRateHasChanged = false;
458             shouldShutdown = false;
459 
460             channelMaps.clear();
461 
462             for (int i = 0; i <= channels.getHighestBit(); ++i)
463                 if (channels[i])
464                     channelMaps.add (i);
465 
466             REFERENCE_TIME latency;
467 
468             if (check (client->GetStreamLatency (&latency)))
469                 latencySamples = refTimeToSamples (latency, sampleRate);
470 
471             (void) check (client->GetBufferSize (&actualBufferSize));
472             createSessionEventCallback();
473             return check (client->SetEventHandle (clientEvent));
474         }
475 
476         return false;
477     }
478 
479     void closeClient()
480     {
481         if (client != nullptr)
482             client->Stop();
483 
484         // N.B. this is needed to prevent a double-deletion of the IAudioSessionEvents object
485         // on older versions of Windows
486         Thread::sleep (5);
487 
488         deleteSessionEventCallback();
489         client = nullptr;
490         ResetEvent (clientEvent);
491     }
492 
493     void deviceSampleRateChanged()
494     {
495         sampleRateHasChanged = true;
496     }
497 
498     void deviceSessionBecameInactive()
499     {
500         isActive = false;
501     }
502 
503     void deviceSessionExpired()
504     {
505         shouldShutdown = true;
506     }
507 
508     void deviceSessionBecameActive()
509     {
510         isActive = true;
511     }
512 
513     //==============================================================================
514     ComSmartPtr<IMMDevice> device;
515     ComSmartPtr<IAudioClient> client;
516 
517     WASAPIDeviceMode deviceMode;
518 
519     double sampleRate = 0, defaultSampleRate = 0;
520     int numChannels = 0, actualNumChannels = 0;
521     int minBufferSize = 0, defaultBufferSize = 0, latencySamples = 0;
522     int lowLatencyBufferSizeMultiple = 0, lowLatencyMaxBufferSize = 0;
523     DWORD mixFormatChannelMask = 0;
524     Array<double> rates;
525     HANDLE clientEvent = {};
526     BigInteger channels;
527     Array<int> channelMaps;
528     UINT32 actualBufferSize = 0;
529     int bytesPerSample = 0, bytesPerFrame = 0;
530     std::atomic<bool> sampleRateHasChanged { false }, shouldShutdown { false }, isActive { true };
531 
532     virtual void updateFormat (bool isFloat) = 0;
533 
534 private:
535     //==============================================================================
536     struct SessionEventCallback  : public ComBaseClassHelper<IAudioSessionEvents>
537     {
538         SessionEventCallback (WASAPIDeviceBase& d) : owner (d) {}
539 
540         JUCE_COMRESULT OnDisplayNameChanged (LPCWSTR, LPCGUID)                 { return S_OK; }
541         JUCE_COMRESULT OnIconPathChanged (LPCWSTR, LPCGUID)                    { return S_OK; }
542         JUCE_COMRESULT OnSimpleVolumeChanged (float, BOOL, LPCGUID)            { return S_OK; }
543         JUCE_COMRESULT OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID)  { return S_OK; }
544         JUCE_COMRESULT OnGroupingParamChanged (LPCGUID, LPCGUID)               { return S_OK; }
545 
546         JUCE_COMRESULT OnStateChanged (AudioSessionState state)
547         {
548             switch (state)
549             {
550             case AudioSessionStateInactive:
551                 owner.deviceSessionBecameInactive();
552                 break;
553             case AudioSessionStateExpired:
554                 owner.deviceSessionExpired();
555                 break;
556             case AudioSessionStateActive:
557                 owner.deviceSessionBecameActive();
558                 break;
559             }
560 
561             return S_OK;
562         }
563 
564         JUCE_COMRESULT OnSessionDisconnected (AudioSessionDisconnectReason reason)
565         {
566             if (reason == DisconnectReasonFormatChanged)
567                 owner.deviceSampleRateChanged();
568 
569             return S_OK;
570         }
571 
572         WASAPIDeviceBase& owner;
573         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SessionEventCallback)
574     };
575 
576     ComSmartPtr<IAudioSessionControl> audioSessionControl;
577     ComSmartPtr<SessionEventCallback> sessionEventCallback;
578 
579     void createSessionEventCallback()
580     {
581         deleteSessionEventCallback();
582         client->GetService (__uuidof (IAudioSessionControl),
583                             (void**) audioSessionControl.resetAndGetPointerAddress());
584 
585         if (audioSessionControl != nullptr)
586         {
587             sessionEventCallback = new SessionEventCallback (*this);
588             audioSessionControl->RegisterAudioSessionNotification (sessionEventCallback);
589             sessionEventCallback->Release(); // (required because ComBaseClassHelper objects are constructed with a ref count of 1)
590         }
591     }
592 
593     void deleteSessionEventCallback()
594     {
595         if (audioSessionControl != nullptr && sessionEventCallback != nullptr)
596             audioSessionControl->UnregisterAudioSessionNotification (sessionEventCallback);
597 
598         audioSessionControl = nullptr;
599         sessionEventCallback = nullptr;
600     }
601 
602     //==============================================================================
603     ComSmartPtr<IAudioClient> createClient()
604     {
605         ComSmartPtr<IAudioClient> newClient;
606 
607         if (device != nullptr)
608             logFailure (device->Activate (__uuidof (IAudioClient), CLSCTX_INPROC_SERVER,
609                                           nullptr, (void**) newClient.resetAndGetPointerAddress()));
610 
611         return newClient;
612     }
613 
614     static bool getClientMixFormat (ComSmartPtr<IAudioClient>& client, WAVEFORMATEXTENSIBLE& format)
615     {
616         WAVEFORMATEX* mixFormat = nullptr;
617 
618         if (! check (client->GetMixFormat (&mixFormat)))
619             return false;
620 
621         copyWavFormat (format, mixFormat);
622         CoTaskMemFree (mixFormat);
623 
624         return true;
625     }
626 
627     //==============================================================================
628     void querySupportedBufferSizes (WAVEFORMATEXTENSIBLE format, ComSmartPtr<IAudioClient>& audioClient)
629     {
630         if (isLowLatencyMode (deviceMode))
631         {
632             if (auto audioClient3 = audioClient.getInterface<IAudioClient3>())
633             {
634                 UINT32 defaultPeriod = 0, fundamentalPeriod = 0, minPeriod = 0, maxPeriod = 0;
635 
636                 if (check (audioClient3->GetSharedModeEnginePeriod ((WAVEFORMATEX*) &format,
637                                                                     &defaultPeriod,
638                                                                     &fundamentalPeriod,
639                                                                     &minPeriod,
640                                                                     &maxPeriod)))
641                 {
642                     minBufferSize = minPeriod;
643                     defaultBufferSize = defaultPeriod;
644                     lowLatencyMaxBufferSize = maxPeriod;
645                     lowLatencyBufferSizeMultiple = fundamentalPeriod;
646                 }
647             }
648         }
649         else
650         {
651             REFERENCE_TIME defaultPeriod, minPeriod;
652 
653             if (! check (audioClient->GetDevicePeriod (&defaultPeriod, &minPeriod)))
654                 return;
655 
656             minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate);
657             defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate);
658         }
659     }
660 
661     void querySupportedSampleRates (WAVEFORMATEXTENSIBLE format, ComSmartPtr<IAudioClient>& audioClient)
662     {
663         for (auto rate : { 8000, 11025, 16000, 22050, 32000,
664                            44100, 48000, 88200, 96000, 176400,
665                            192000, 352800, 384000, 705600, 768000 })
666         {
667             if (rates.contains (rate))
668                 continue;
669 
670             format.Format.nSamplesPerSec  = (DWORD) rate;
671             format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nChannels * format.Format.wBitsPerSample / 8);
672 
673             WAVEFORMATEX* nearestFormat = nullptr;
674 
675             if (SUCCEEDED (audioClient->IsFormatSupported (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE
676                                                                                         : AUDCLNT_SHAREMODE_SHARED,
677                                                            (WAVEFORMATEX*) &format,
678                                                            isExclusiveMode (deviceMode) ? nullptr
679                                                                                         : &nearestFormat)))
680             {
681                 if (nearestFormat != nullptr)
682                     rate = nearestFormat->nSamplesPerSec;
683 
684                 if (! rates.contains (rate))
685                     rates.addUsingDefaultSort (rate);
686             }
687 
688             CoTaskMemFree (nearestFormat);
689         }
690     }
691 
692     struct AudioSampleFormat
693     {
694         bool useFloat;
695         int  bitsPerSampleToTry;
696         int  bytesPerSampleContainer;
697     };
698 
699     bool tryFormat (const AudioSampleFormat sampleFormat, IAudioClient* clientToUse, double newSampleRate,
700                     DWORD newMixFormatChannelMask, WAVEFORMATEXTENSIBLE& format) const
701     {
702         zerostruct (format);
703 
704         if (numChannels <= 2 && sampleFormat.bitsPerSampleToTry <= 16)
705         {
706             format.Format.wFormatTag = WAVE_FORMAT_PCM;
707         }
708         else
709         {
710             format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
711             format.Format.cbSize = sizeof (WAVEFORMATEXTENSIBLE) - sizeof (WAVEFORMATEX);
712         }
713 
714         format.Format.nSamplesPerSec       = (DWORD) newSampleRate;
715         format.Format.nChannels            = (WORD) numChannels;
716         format.Format.wBitsPerSample       = (WORD) (8 * sampleFormat.bytesPerSampleContainer);
717         format.Samples.wValidBitsPerSample = (WORD) (sampleFormat.bitsPerSampleToTry);
718         format.Format.nBlockAlign          = (WORD) (format.Format.nChannels * format.Format.wBitsPerSample / 8);
719         format.Format.nAvgBytesPerSec      = (DWORD) (format.Format.nSamplesPerSec * format.Format.nBlockAlign);
720         format.SubFormat                   = sampleFormat.useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
721         format.dwChannelMask               = newMixFormatChannelMask;
722 
723         WAVEFORMATEX* nearestFormat = nullptr;
724 
725         HRESULT hr = clientToUse->IsFormatSupported (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE
726                                                                                   : AUDCLNT_SHAREMODE_SHARED,
727                                                      (WAVEFORMATEX*) &format,
728                                                      isExclusiveMode (deviceMode) ? nullptr
729                                                                                   : &nearestFormat);
730         logFailure (hr);
731 
732         auto supportsSRC = supportsSampleRateConversion (deviceMode);
733 
734         if (hr == S_FALSE
735             && nearestFormat != nullptr
736             && (format.Format.nSamplesPerSec == nearestFormat->nSamplesPerSec
737                 || supportsSRC))
738         {
739             copyWavFormat (format, nearestFormat);
740 
741             if (supportsSRC)
742             {
743                 format.Format.nSamplesPerSec  = (DWORD) newSampleRate;
744                 format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nBlockAlign);
745             }
746 
747             hr = S_OK;
748         }
749 
750         CoTaskMemFree (nearestFormat);
751         return hr == S_OK;
752     }
753 
754     bool findSupportedFormat (IAudioClient* clientToUse, double newSampleRate,
755                               DWORD newMixFormatChannelMask, WAVEFORMATEXTENSIBLE& format) const
756     {
757         static const AudioSampleFormat formats[] =
758         {
759             { true,  32, 4 },
760             { false, 32, 4 },
761             { false, 24, 4 },
762             { false, 24, 3 },
763             { false, 20, 4 },
764             { false, 20, 3 },
765             { false, 16, 2 }
766         };
767 
768         for (int i = 0; i < numElementsInArray (formats); ++i)
769             if (tryFormat (formats[i], clientToUse, newSampleRate, newMixFormatChannelMask, format))
770                 return true;
771 
772         return false;
773     }
774 
775     DWORD getStreamFlags()
776     {
777         DWORD streamFlags = 0x40000; /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/
778 
779         if (supportsSampleRateConversion (deviceMode))
780             streamFlags |= (0x80000000    /*AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM*/
781                             | 0x8000000); /*AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY*/
782 
783         return streamFlags;
784     }
785 
786     bool initialiseLowLatencyClient (int bufferSizeSamples, WAVEFORMATEXTENSIBLE format)
787     {
788         if (auto audioClient3 = client.getInterface<IAudioClient3>())
789             return check (audioClient3->InitializeSharedAudioStream (getStreamFlags(),
790                                                                      bufferSizeSamples,
791                                                                      (WAVEFORMATEX*) &format,
792                                                                      nullptr));
793 
794         return false;
795     }
796 
797     bool initialiseStandardClient (int bufferSizeSamples, WAVEFORMATEXTENSIBLE format)
798     {
799         REFERENCE_TIME defaultPeriod = 0, minPeriod = 0;
800 
801         check (client->GetDevicePeriod (&defaultPeriod, &minPeriod));
802 
803         if (isExclusiveMode (deviceMode) && bufferSizeSamples > 0)
804             defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec));
805 
806         for (;;)
807         {
808             GUID session;
809             auto hr = client->Initialize (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE
810                                                                        : AUDCLNT_SHAREMODE_SHARED,
811                                           getStreamFlags(),
812                                           defaultPeriod,
813                                           isExclusiveMode (deviceMode) ? defaultPeriod : 0,
814                                           (WAVEFORMATEX*) &format,
815                                           &session);
816 
817             if (check (hr))
818                 return true;
819 
820             // Handle the "alignment dance" : http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875(v=vs.85).aspx (see Remarks)
821             if (hr != MAKE_HRESULT (1, 0x889, 0x19)) // AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED
822                 break;
823 
824             UINT32 numFrames = 0;
825             if (! check (client->GetBufferSize (&numFrames)))
826                 break;
827 
828             // Recreate client
829             client = nullptr;
830             client = createClient();
831 
832             defaultPeriod = samplesToRefTime (numFrames, format.Format.nSamplesPerSec);
833         }
834 
835         return false;
836     }
837 
838     bool tryInitialisingWithBufferSize (int bufferSizeSamples)
839     {
840         WAVEFORMATEXTENSIBLE format;
841 
842         if (findSupportedFormat (client, sampleRate, mixFormatChannelMask, format))
843         {
844             auto isInitialised = isLowLatencyMode (deviceMode) ? initialiseLowLatencyClient (bufferSizeSamples, format)
845                                                                : initialiseStandardClient   (bufferSizeSamples, format);
846 
847             if (isInitialised)
848             {
849                 actualNumChannels  = format.Format.nChannels;
850                 const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
851                 bytesPerSample     = format.Format.wBitsPerSample / 8;
852                 bytesPerFrame      = format.Format.nBlockAlign;
853 
854                 updateFormat (isFloat);
855 
856                 return true;
857             }
858         }
859 
860         return false;
861     }
862 
863     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIDeviceBase)
864 };
865 
866 //==============================================================================
867 class WASAPIInputDevice  : public WASAPIDeviceBase
868 {
869 public:
870     WASAPIInputDevice (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
871         : WASAPIDeviceBase (d, mode)
872     {
873     }
874 
875     ~WASAPIInputDevice()
876     {
877         close();
878     }
879 
880     bool open (double newSampleRate, const BigInteger& newChannels, int bufferSizeSamples)
881     {
882         return openClient (newSampleRate, newChannels, bufferSizeSamples)
883                 && (numChannels == 0 || check (client->GetService (__uuidof (IAudioCaptureClient),
884                                                                    (void**) captureClient.resetAndGetPointerAddress())));
885     }
886 
887     void close()
888     {
889         closeClient();
890         captureClient = nullptr;
891         reservoir.reset();
892         reservoirReadPos = 0;
893         reservoirWritePos = 0;
894     }
895 
896     template<class SourceType>
897     void updateFormatWithType (SourceType*) noexcept
898     {
899         using NativeType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>;
900         converter.reset (new AudioData::ConverterInstance<AudioData::Pointer<SourceType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const>, NativeType> (actualNumChannels, 1));
901     }
902 
903     void updateFormat (bool isFloat) override
904     {
905         if (isFloat)                    updateFormatWithType ((AudioData::Float32*) nullptr);
906         else if (bytesPerSample == 4)   updateFormatWithType ((AudioData::Int32*)   nullptr);
907         else if (bytesPerSample == 3)   updateFormatWithType ((AudioData::Int24*)   nullptr);
908         else                            updateFormatWithType ((AudioData::Int16*)   nullptr);
909     }
910 
911     bool start (int userBufferSize)
912     {
913         reservoirSize = actualBufferSize + userBufferSize;
914         reservoirMask = nextPowerOfTwo (reservoirSize) - 1;
915         reservoir.setSize ((reservoirMask + 1) * bytesPerFrame, true);
916         reservoirReadPos = 0;
917         reservoirWritePos = 0;
918         xruns = 0;
919 
920         if (! check (client->Start()))
921             return false;
922 
923         purgeInputBuffers();
924         isActive = true;
925 
926         return true;
927     }
928 
929     void purgeInputBuffers()
930     {
931         uint8* inputData;
932         UINT32 numSamplesAvailable;
933         DWORD flags;
934 
935         while (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, nullptr, nullptr)
936                   != MAKE_HRESULT (0, 0x889, 0x1) /* AUDCLNT_S_BUFFER_EMPTY */)
937             captureClient->ReleaseBuffer (numSamplesAvailable);
938     }
939 
940     int getNumSamplesInReservoir() const noexcept    { return reservoirWritePos.load() - reservoirReadPos.load(); }
941 
942     void handleDeviceBuffer()
943     {
944         if (numChannels <= 0)
945             return;
946 
947         uint8* inputData;
948         UINT32 numSamplesAvailable;
949         DWORD flags;
950 
951         while (check (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, nullptr, nullptr)) && numSamplesAvailable > 0)
952         {
953             if ((flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0)
954                 xruns++;
955 
956             int samplesLeft = (int) numSamplesAvailable;
957 
958             while (samplesLeft > 0)
959             {
960                 auto localWrite = reservoirWritePos.load() & reservoirMask;
961                 auto samplesToDo = jmin (samplesLeft, reservoirMask + 1 - localWrite);
962                 auto samplesToDoBytes = samplesToDo * bytesPerFrame;
963 
964                 void* reservoirPtr = addBytesToPointer (reservoir.getData(), localWrite * bytesPerFrame);
965 
966                 if ((flags & AUDCLNT_BUFFERFLAGS_SILENT) != 0)
967                     zeromem (reservoirPtr, samplesToDoBytes);
968                 else
969                     memcpy (reservoirPtr, inputData, samplesToDoBytes);
970 
971                 reservoirWritePos += samplesToDo;
972                 inputData += samplesToDoBytes;
973                 samplesLeft -= samplesToDo;
974             }
975 
976             if (getNumSamplesInReservoir() > reservoirSize)
977                 reservoirReadPos = reservoirWritePos.load() - reservoirSize;
978 
979             captureClient->ReleaseBuffer (numSamplesAvailable);
980         }
981     }
982 
983     void copyBuffersFromReservoir (float** destBuffers, int numDestBuffers, int bufferSize)
984     {
985         if ((numChannels <= 0 && bufferSize == 0) || reservoir.getSize() == 0)
986             return;
987 
988         int offset = jmax (0, bufferSize - getNumSamplesInReservoir());
989 
990         if (offset > 0)
991         {
992             for (int i = 0; i < numDestBuffers; ++i)
993                 zeromem (destBuffers[i], offset * sizeof (float));
994 
995             bufferSize -= offset;
996             reservoirReadPos -= offset / 2;
997         }
998 
999         while (bufferSize > 0)
1000         {
1001             auto localRead = reservoirReadPos.load() & reservoirMask;
1002             auto samplesToDo = jmin (bufferSize, getNumSamplesInReservoir(), reservoirMask + 1 - localRead);
1003 
1004             if (samplesToDo <= 0)
1005                 break;
1006 
1007             auto reservoirOffset = localRead * bytesPerFrame;
1008 
1009             for (int i = 0; i < numDestBuffers; ++i)
1010                 converter->convertSamples (destBuffers[i] + offset, 0, addBytesToPointer (reservoir.getData(), reservoirOffset), channelMaps.getUnchecked(i), samplesToDo);
1011 
1012             bufferSize -= samplesToDo;
1013             offset += samplesToDo;
1014             reservoirReadPos += samplesToDo;
1015         }
1016     }
1017 
1018     ComSmartPtr<IAudioCaptureClient> captureClient;
1019     MemoryBlock reservoir;
1020     int reservoirSize, reservoirMask, xruns;
1021     std::atomic<int> reservoirReadPos, reservoirWritePos;
1022 
1023     std::unique_ptr<AudioData::Converter> converter;
1024 
1025 private:
1026     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIInputDevice)
1027 };
1028 
1029 //==============================================================================
1030 class WASAPIOutputDevice  : public WASAPIDeviceBase
1031 {
1032 public:
1033     WASAPIOutputDevice (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
1034         : WASAPIDeviceBase (d, mode)
1035     {
1036     }
1037 
1038     ~WASAPIOutputDevice()
1039     {
1040         close();
1041     }
1042 
1043     bool open (double newSampleRate, const BigInteger& newChannels, int bufferSizeSamples)
1044     {
1045         return openClient (newSampleRate, newChannels, bufferSizeSamples)
1046                 && (numChannels == 0 || check (client->GetService (__uuidof (IAudioRenderClient),
1047                                                                    (void**) renderClient.resetAndGetPointerAddress())));
1048     }
1049 
1050     void close()
1051     {
1052         closeClient();
1053         renderClient = nullptr;
1054     }
1055 
1056     template<class DestType>
1057     void updateFormatWithType (DestType*)
1058     {
1059         using NativeType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>;
1060         converter.reset (new AudioData::ConverterInstance<NativeType, AudioData::Pointer<DestType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst>> (1, actualNumChannels));
1061     }
1062 
1063     void updateFormat (bool isFloat) override
1064     {
1065         if (isFloat)                    updateFormatWithType ((AudioData::Float32*) nullptr);
1066         else if (bytesPerSample == 4)   updateFormatWithType ((AudioData::Int32*)   nullptr);
1067         else if (bytesPerSample == 3)   updateFormatWithType ((AudioData::Int24*)   nullptr);
1068         else                            updateFormatWithType ((AudioData::Int16*)   nullptr);
1069     }
1070 
1071     bool start()
1072     {
1073         auto samplesToDo = getNumSamplesAvailableToCopy();
1074         uint8* outputData;
1075 
1076         if (check (renderClient->GetBuffer (samplesToDo, &outputData)))
1077             renderClient->ReleaseBuffer (samplesToDo, AUDCLNT_BUFFERFLAGS_SILENT);
1078 
1079         if (! check (client->Start()))
1080             return false;
1081 
1082         isActive = true;
1083 
1084         return true;
1085     }
1086 
1087     int getNumSamplesAvailableToCopy() const
1088     {
1089         if (numChannels <= 0)
1090             return 0;
1091 
1092         if (! isExclusiveMode (deviceMode))
1093         {
1094             UINT32 padding = 0;
1095 
1096             if (check (client->GetCurrentPadding (&padding)))
1097                 return actualBufferSize - (int) padding;
1098         }
1099 
1100         return actualBufferSize;
1101     }
1102 
1103     void copyBuffers (const float** srcBuffers, int numSrcBuffers, int bufferSize,
1104                       WASAPIInputDevice* inputDevice, Thread& thread)
1105     {
1106         if (numChannels <= 0)
1107             return;
1108 
1109         int offset = 0;
1110 
1111         while (bufferSize > 0)
1112         {
1113             // This is needed in order not to drop any input data if the output device endpoint buffer was full
1114             if ((! isExclusiveMode (deviceMode)) && inputDevice != nullptr
1115                   && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0)
1116                 inputDevice->handleDeviceBuffer();
1117 
1118             int samplesToDo = jmin (getNumSamplesAvailableToCopy(), bufferSize);
1119 
1120             if (samplesToDo == 0)
1121             {
1122                 // This can ONLY occur in non-exclusive mode
1123                 if (! thread.threadShouldExit() && WaitForSingleObject (clientEvent, 1000) == WAIT_OBJECT_0)
1124                     continue;
1125 
1126                 break;
1127             }
1128 
1129             if (isExclusiveMode (deviceMode) && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
1130                 break;
1131 
1132             uint8* outputData = nullptr;
1133             if (check (renderClient->GetBuffer ((UINT32) samplesToDo, &outputData)))
1134             {
1135                 for (int i = 0; i < numSrcBuffers; ++i)
1136                     converter->convertSamples (outputData, channelMaps.getUnchecked(i), srcBuffers[i] + offset, 0, samplesToDo);
1137 
1138                 renderClient->ReleaseBuffer ((UINT32) samplesToDo, 0);
1139             }
1140 
1141             bufferSize -= samplesToDo;
1142             offset += samplesToDo;
1143         }
1144     }
1145 
1146     ComSmartPtr<IAudioRenderClient> renderClient;
1147     std::unique_ptr<AudioData::Converter> converter;
1148 
1149 private:
1150     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIOutputDevice)
1151 };
1152 
1153 //==============================================================================
1154 class WASAPIAudioIODevice  : public AudioIODevice,
1155                              public Thread,
1156                              private AsyncUpdater
1157 {
1158 public:
1159     WASAPIAudioIODevice (const String& deviceName,
1160                          const String& typeName,
1161                          const String& outputDeviceID,
1162                          const String& inputDeviceID,
1163                          WASAPIDeviceMode mode)
1164         : AudioIODevice (deviceName, typeName),
1165           Thread ("JUCE WASAPI"),
1166           outputDeviceId (outputDeviceID),
1167           inputDeviceId (inputDeviceID),
1168           deviceMode (mode)
1169     {
1170     }
1171 
1172     ~WASAPIAudioIODevice()
1173     {
1174         cancelPendingUpdate();
1175         close();
1176     }
1177 
1178     bool initialise()
1179     {
1180         latencyIn = latencyOut = 0;
1181         Array<double> ratesIn, ratesOut;
1182 
1183         if (createDevices())
1184         {
1185             jassert (inputDevice != nullptr || outputDevice != nullptr);
1186 
1187             sampleRates.clear();
1188 
1189             if (inputDevice != nullptr && outputDevice != nullptr)
1190             {
1191                 defaultSampleRate = jmin (inputDevice->defaultSampleRate, outputDevice->defaultSampleRate);
1192                 minBufferSize = jmax (inputDevice->minBufferSize, outputDevice->minBufferSize);
1193                 defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize);
1194 
1195                 if (isLowLatencyMode (deviceMode))
1196                 {
1197                     lowLatencyMaxBufferSize = jmin (inputDevice->lowLatencyMaxBufferSize, outputDevice->lowLatencyMaxBufferSize);
1198                     lowLatencyBufferSizeMultiple = jmax (inputDevice->lowLatencyBufferSizeMultiple, outputDevice->lowLatencyBufferSizeMultiple);
1199                 }
1200 
1201                 sampleRates.addArray (inputDevice->rates);
1202 
1203                 if (supportsSampleRateConversion (deviceMode))
1204                 {
1205                     for (auto r : outputDevice->rates)
1206                         if (! sampleRates.contains (r))
1207                             sampleRates.addUsingDefaultSort (r);
1208                 }
1209                 else
1210                 {
1211                     sampleRates.removeValuesNotIn (outputDevice->rates);
1212                 }
1213             }
1214             else
1215             {
1216                 auto* d = inputDevice != nullptr ? static_cast<WASAPIDeviceBase*> (inputDevice.get())
1217                                                  : static_cast<WASAPIDeviceBase*> (outputDevice.get());
1218 
1219                 defaultSampleRate = d->defaultSampleRate;
1220                 minBufferSize = d->minBufferSize;
1221                 defaultBufferSize = d->defaultBufferSize;
1222 
1223                 if (isLowLatencyMode (deviceMode))
1224                 {
1225                     lowLatencyMaxBufferSize = d->lowLatencyMaxBufferSize;
1226                     lowLatencyBufferSizeMultiple = d->lowLatencyBufferSizeMultiple;
1227                 }
1228 
1229                 sampleRates = d->rates;
1230             }
1231 
1232             bufferSizes.clear();
1233             bufferSizes.addUsingDefaultSort (defaultBufferSize);
1234 
1235             if (minBufferSize != defaultBufferSize)
1236                 bufferSizes.addUsingDefaultSort (minBufferSize);
1237 
1238             if (isLowLatencyMode (deviceMode))
1239             {
1240                 auto size = minBufferSize;
1241 
1242                 while (size < lowLatencyMaxBufferSize)
1243                 {
1244                     size += lowLatencyBufferSizeMultiple;
1245 
1246                     if (! bufferSizes.contains (size))
1247                         bufferSizes.addUsingDefaultSort (size);
1248                 }
1249             }
1250             else
1251             {
1252                 int n = 64;
1253                 for (int i = 0; i < 40; ++i)
1254                 {
1255                     if (n >= minBufferSize && n <= 2048 && ! bufferSizes.contains (n))
1256                         bufferSizes.addUsingDefaultSort (n);
1257 
1258                     n += (n < 512) ? 32 : (n < 1024 ? 64 : 128);
1259                 }
1260             }
1261 
1262             return true;
1263         }
1264 
1265         return false;
1266     }
1267 
1268     StringArray getOutputChannelNames() override
1269     {
1270         StringArray outChannels;
1271 
1272         if (outputDevice != nullptr)
1273             for (int i = 1; i <= outputDevice->actualNumChannels; ++i)
1274                 outChannels.add ("Output channel " + String (i));
1275 
1276         return outChannels;
1277     }
1278 
1279     StringArray getInputChannelNames() override
1280     {
1281         StringArray inChannels;
1282 
1283         if (inputDevice != nullptr)
1284             for (int i = 1; i <= inputDevice->actualNumChannels; ++i)
1285                 inChannels.add ("Input channel " + String (i));
1286 
1287         return inChannels;
1288     }
1289 
1290     Array<double> getAvailableSampleRates() override        { return sampleRates; }
1291     Array<int> getAvailableBufferSizes() override           { return bufferSizes; }
1292     int getDefaultBufferSize() override                     { return defaultBufferSize; }
1293 
1294     int getCurrentBufferSizeSamples() override              { return currentBufferSizeSamples; }
1295     double getCurrentSampleRate() override                  { return currentSampleRate; }
1296     int getCurrentBitDepth() override                       { return 32; }
1297     int getOutputLatencyInSamples() override                { return latencyOut; }
1298     int getInputLatencyInSamples() override                 { return latencyIn; }
1299     BigInteger getActiveOutputChannels() const override     { return outputDevice != nullptr ? outputDevice->channels : BigInteger(); }
1300     BigInteger getActiveInputChannels() const override      { return inputDevice  != nullptr ? inputDevice->channels  : BigInteger(); }
1301     String getLastError() override                          { return lastError; }
1302     int getXRunCount() const noexcept override              { return inputDevice != nullptr ? inputDevice->xruns : -1; }
1303 
1304     String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
1305                  double sampleRate, int bufferSizeSamples) override
1306     {
1307         close();
1308         lastError.clear();
1309 
1310         if (sampleRates.size() == 0 && inputDevice != nullptr && outputDevice != nullptr)
1311         {
1312             lastError = TRANS("The input and output devices don't share a common sample rate!");
1313             return lastError;
1314         }
1315 
1316         currentBufferSizeSamples  = bufferSizeSamples <= 0 ? defaultBufferSize : jmax (bufferSizeSamples, minBufferSize);
1317         currentSampleRate         = sampleRate > 0 ? sampleRate : defaultSampleRate;
1318         lastKnownInputChannels    = inputChannels;
1319         lastKnownOutputChannels   = outputChannels;
1320 
1321         if (inputDevice != nullptr && ! inputDevice->open (currentSampleRate, inputChannels, bufferSizeSamples))
1322         {
1323             lastError = TRANS("Couldn't open the input device!");
1324             return lastError;
1325         }
1326 
1327         if (outputDevice != nullptr && ! outputDevice->open (currentSampleRate, outputChannels, bufferSizeSamples))
1328         {
1329             close();
1330             lastError = TRANS("Couldn't open the output device!");
1331             return lastError;
1332         }
1333 
1334         if (isExclusiveMode (deviceMode))
1335         {
1336             // This is to make sure that the callback uses actualBufferSize in case of exclusive mode
1337             if (inputDevice != nullptr && outputDevice != nullptr && inputDevice->actualBufferSize != outputDevice->actualBufferSize)
1338             {
1339                 close();
1340                 lastError = TRANS("Couldn't open the output device (buffer size mismatch)");
1341                 return lastError;
1342             }
1343 
1344             currentBufferSizeSamples = outputDevice != nullptr ? outputDevice->actualBufferSize
1345                                                                : inputDevice->actualBufferSize;
1346         }
1347 
1348         if (inputDevice != nullptr)   ResetEvent (inputDevice->clientEvent);
1349         if (outputDevice != nullptr)  ResetEvent (outputDevice->clientEvent);
1350 
1351         shouldShutdown = false;
1352         deviceSampleRateChanged = false;
1353 
1354         startThread (8);
1355         Thread::sleep (5);
1356 
1357         if (inputDevice != nullptr && inputDevice->client != nullptr)
1358         {
1359             latencyIn = (int) (inputDevice->latencySamples + currentBufferSizeSamples);
1360 
1361             if (! inputDevice->start (currentBufferSizeSamples))
1362             {
1363                 close();
1364                 lastError = TRANS("Couldn't start the input device!");
1365                 return lastError;
1366             }
1367         }
1368 
1369         if (outputDevice != nullptr && outputDevice->client != nullptr)
1370         {
1371             latencyOut = (int) (outputDevice->latencySamples + currentBufferSizeSamples);
1372 
1373             if (! outputDevice->start())
1374             {
1375                 close();
1376                 lastError = TRANS("Couldn't start the output device!");
1377                 return lastError;
1378             }
1379         }
1380 
1381         isOpen_ = true;
1382         return lastError;
1383     }
1384 
1385     void close() override
1386     {
1387         stop();
1388         signalThreadShouldExit();
1389 
1390         if (inputDevice != nullptr)   SetEvent (inputDevice->clientEvent);
1391         if (outputDevice != nullptr)  SetEvent (outputDevice->clientEvent);
1392 
1393         stopThread (5000);
1394 
1395         if (inputDevice != nullptr)   inputDevice->close();
1396         if (outputDevice != nullptr)  outputDevice->close();
1397 
1398         isOpen_ = false;
1399     }
1400 
1401     bool isOpen() override       { return isOpen_ && isThreadRunning(); }
1402     bool isPlaying() override    { return isStarted && isOpen_ && isThreadRunning(); }
1403 
1404     void start (AudioIODeviceCallback* call) override
1405     {
1406         if (isOpen_ && call != nullptr && ! isStarted)
1407         {
1408             if (! isThreadRunning())
1409             {
1410                 // something's gone wrong and the thread's stopped..
1411                 isOpen_ = false;
1412                 return;
1413             }
1414 
1415             call->audioDeviceAboutToStart (this);
1416 
1417             const ScopedLock sl (startStopLock);
1418             callback = call;
1419             isStarted = true;
1420         }
1421     }
1422 
1423     void stop() override
1424     {
1425         if (isStarted)
1426         {
1427             auto* callbackLocal = callback;
1428 
1429             {
1430                 const ScopedLock sl (startStopLock);
1431                 isStarted = false;
1432             }
1433 
1434             if (callbackLocal != nullptr)
1435                 callbackLocal->audioDeviceStopped();
1436         }
1437     }
1438 
1439     void setMMThreadPriority()
1440     {
1441         DynamicLibrary dll ("avrt.dll");
1442         JUCE_LOAD_WINAPI_FUNCTION (dll, AvSetMmThreadCharacteristicsW, avSetMmThreadCharacteristics, HANDLE, (LPCWSTR, LPDWORD))
1443         JUCE_LOAD_WINAPI_FUNCTION (dll, AvSetMmThreadPriority, avSetMmThreadPriority, HANDLE, (HANDLE, AVRT_PRIORITY))
1444 
1445         if (avSetMmThreadCharacteristics != 0 && avSetMmThreadPriority != 0)
1446         {
1447             DWORD dummy = 0;
1448 
1449             if (auto h = avSetMmThreadCharacteristics (L"Pro Audio", &dummy))
1450                 avSetMmThreadPriority (h, AVRT_PRIORITY_NORMAL);
1451         }
1452     }
1453 
1454     void run() override
1455     {
1456         setMMThreadPriority();
1457 
1458         auto bufferSize        = currentBufferSizeSamples;
1459         auto numInputBuffers   = getActiveInputChannels().countNumberOfSetBits();
1460         auto numOutputBuffers  = getActiveOutputChannels().countNumberOfSetBits();
1461 
1462         AudioBuffer<float> ins  (jmax (1, numInputBuffers),  bufferSize + 32);
1463         AudioBuffer<float> outs (jmax (1, numOutputBuffers), bufferSize + 32);
1464         auto inputBuffers  = ins.getArrayOfWritePointers();
1465         auto outputBuffers = outs.getArrayOfWritePointers();
1466         ins.clear();
1467         outs.clear();
1468 
1469         while (! threadShouldExit())
1470         {
1471             if ((outputDevice != nullptr && outputDevice->shouldShutdown)
1472                 || (inputDevice != nullptr && inputDevice->shouldShutdown))
1473             {
1474                 shouldShutdown = true;
1475                 triggerAsyncUpdate();
1476 
1477                 break;
1478             }
1479 
1480             auto inputDeviceActive = (inputDevice != nullptr && inputDevice->isActive);
1481             auto outputDeviceActive = (outputDevice != nullptr && outputDevice->isActive);
1482 
1483             if (! inputDeviceActive && ! outputDeviceActive)
1484                 continue;
1485 
1486             if (inputDeviceActive)
1487             {
1488                 if (outputDevice == nullptr)
1489                 {
1490                     if (WaitForSingleObject (inputDevice->clientEvent, 1000) == WAIT_TIMEOUT)
1491                         break;
1492 
1493                     inputDevice->handleDeviceBuffer();
1494 
1495                     if (inputDevice->getNumSamplesInReservoir() < bufferSize)
1496                         continue;
1497                 }
1498                 else
1499                 {
1500                     if (isExclusiveMode (deviceMode) && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0)
1501                         inputDevice->handleDeviceBuffer();
1502                 }
1503 
1504                 inputDevice->copyBuffersFromReservoir (inputBuffers, numInputBuffers, bufferSize);
1505 
1506                 if (inputDevice->sampleRateHasChanged)
1507                 {
1508                     deviceSampleRateChanged = true;
1509                     triggerAsyncUpdate();
1510 
1511                     break;
1512                 }
1513             }
1514 
1515             {
1516                 const ScopedTryLock sl (startStopLock);
1517 
1518                 if (sl.isLocked() && isStarted)
1519                     callback->audioDeviceIOCallback (const_cast<const float**> (inputBuffers), numInputBuffers,
1520                                                      outputBuffers, numOutputBuffers, bufferSize);
1521                 else
1522                     outs.clear();
1523             }
1524 
1525             if (outputDeviceActive)
1526             {
1527                 // Note that this function is handed the input device so it can check for the event and make sure
1528                 // the input reservoir is filled up correctly even when bufferSize > device actualBufferSize
1529                 outputDevice->copyBuffers (const_cast<const float**> (outputBuffers), numOutputBuffers, bufferSize, inputDevice.get(), *this);
1530 
1531                 if (outputDevice->sampleRateHasChanged)
1532                 {
1533                     deviceSampleRateChanged = true;
1534                     triggerAsyncUpdate();
1535 
1536                     break;
1537                 }
1538             }
1539         }
1540     }
1541 
1542     //==============================================================================
1543     String outputDeviceId, inputDeviceId;
1544     String lastError;
1545 
1546 private:
1547     // Device stats...
1548     std::unique_ptr<WASAPIInputDevice> inputDevice;
1549     std::unique_ptr<WASAPIOutputDevice> outputDevice;
1550     WASAPIDeviceMode deviceMode;
1551     double defaultSampleRate = 0;
1552     int minBufferSize = 0, defaultBufferSize = 0;
1553     int lowLatencyMaxBufferSize = 0, lowLatencyBufferSizeMultiple = 0;
1554     int latencyIn = 0, latencyOut = 0;
1555     Array<double> sampleRates;
1556     Array<int> bufferSizes;
1557 
1558     // Active state...
1559     bool isOpen_ = false, isStarted = false;
1560     int currentBufferSizeSamples = 0;
1561     double currentSampleRate = 0;
1562 
1563     AudioIODeviceCallback* callback = {};
1564     CriticalSection startStopLock;
1565 
1566     std::atomic<bool> shouldShutdown { false }, deviceSampleRateChanged { false };
1567 
1568     BigInteger lastKnownInputChannels, lastKnownOutputChannels;
1569 
1570     //==============================================================================
1571     bool createDevices()
1572     {
1573         ComSmartPtr<IMMDeviceEnumerator> enumerator;
1574 
1575         if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator))))
1576             return false;
1577 
1578         ComSmartPtr<IMMDeviceCollection> deviceCollection;
1579 
1580         if (! check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress())))
1581             return false;
1582 
1583         UINT32 numDevices = 0;
1584 
1585         if (! check (deviceCollection->GetCount (&numDevices)))
1586             return false;
1587 
1588         for (UINT32 i = 0; i < numDevices; ++i)
1589         {
1590             ComSmartPtr<IMMDevice> device;
1591 
1592             if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress())))
1593                 continue;
1594 
1595             auto deviceId = getDeviceID (device);
1596 
1597             if (deviceId.isEmpty())
1598                 continue;
1599 
1600             auto flow = getDataFlow (device);
1601 
1602             if (deviceId == inputDeviceId && flow == eCapture)
1603                 inputDevice.reset (new WASAPIInputDevice (device, deviceMode));
1604             else if (deviceId == outputDeviceId && flow == eRender)
1605                 outputDevice.reset (new WASAPIOutputDevice (device, deviceMode));
1606         }
1607 
1608         return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk()))
1609              && (inputDeviceId.isEmpty() || (inputDevice != nullptr && inputDevice->isOk()));
1610     }
1611 
1612     //==============================================================================
1613     void handleAsyncUpdate() override
1614     {
1615         auto closeDevices = [this]
1616         {
1617             close();
1618 
1619             outputDevice = nullptr;
1620             inputDevice = nullptr;
1621         };
1622 
1623         if (shouldShutdown)
1624         {
1625             closeDevices();
1626         }
1627         else if (deviceSampleRateChanged)
1628         {
1629             auto sampleRateChangedByInput = (inputDevice != nullptr && inputDevice->sampleRateHasChanged);
1630 
1631             closeDevices();
1632             initialise();
1633 
1634             auto changedSampleRate = [this, sampleRateChangedByInput]()
1635             {
1636                 if (inputDevice != nullptr && sampleRateChangedByInput)
1637                     return inputDevice->defaultSampleRate;
1638 
1639                 if (outputDevice != nullptr && ! sampleRateChangedByInput)
1640                     return outputDevice->defaultSampleRate;
1641 
1642                 return 0.0;
1643             }();
1644 
1645             open (lastKnownInputChannels, lastKnownOutputChannels,
1646                   changedSampleRate, currentBufferSizeSamples);
1647 
1648             start (callback);
1649         }
1650     }
1651 
1652     //==============================================================================
1653     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODevice)
1654 };
1655 
1656 
1657 //==============================================================================
1658 class WASAPIAudioIODeviceType  : public AudioIODeviceType,
1659                                  private DeviceChangeDetector
1660 {
1661 public:
1662     WASAPIAudioIODeviceType (WASAPIDeviceMode mode)
1663         : AudioIODeviceType (getDeviceTypename (mode)),
1664           DeviceChangeDetector (L"Windows Audio"),
1665           deviceMode (mode)
1666     {
1667     }
1668 
1669     ~WASAPIAudioIODeviceType()
1670     {
1671         if (notifyClient != nullptr)
1672             enumerator->UnregisterEndpointNotificationCallback (notifyClient);
1673     }
1674 
1675     //==============================================================================
1676     void scanForDevices() override
1677     {
1678         hasScanned = true;
1679 
1680         outputDeviceNames.clear();
1681         inputDeviceNames.clear();
1682         outputDeviceIds.clear();
1683         inputDeviceIds.clear();
1684 
1685         scan (outputDeviceNames, inputDeviceNames,
1686               outputDeviceIds, inputDeviceIds);
1687     }
1688 
1689     StringArray getDeviceNames (bool wantInputNames) const override
1690     {
1691         jassert (hasScanned); // need to call scanForDevices() before doing this
1692 
1693         return wantInputNames ? inputDeviceNames
1694                               : outputDeviceNames;
1695     }
1696 
1697     int getDefaultDeviceIndex (bool /*forInput*/) const override
1698     {
1699         jassert (hasScanned); // need to call scanForDevices() before doing this
1700         return 0;
1701     }
1702 
1703     int getIndexOfDevice (AudioIODevice* device, bool asInput) const override
1704     {
1705         jassert (hasScanned); // need to call scanForDevices() before doing this
1706 
1707         if (auto d = dynamic_cast<WASAPIAudioIODevice*> (device))
1708             return asInput ? inputDeviceIds.indexOf (d->inputDeviceId)
1709                            : outputDeviceIds.indexOf (d->outputDeviceId);
1710 
1711         return -1;
1712     }
1713 
1714     bool hasSeparateInputsAndOutputs() const override    { return true; }
1715 
1716     AudioIODevice* createDevice (const String& outputDeviceName,
1717                                  const String& inputDeviceName) override
1718     {
1719         jassert (hasScanned); // need to call scanForDevices() before doing this
1720 
1721         std::unique_ptr<WASAPIAudioIODevice> device;
1722 
1723         auto outputIndex = outputDeviceNames.indexOf (outputDeviceName);
1724         auto inputIndex = inputDeviceNames.indexOf (inputDeviceName);
1725 
1726         if (outputIndex >= 0 || inputIndex >= 0)
1727         {
1728             device.reset (new WASAPIAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
1729                                                                                  : inputDeviceName,
1730                                                    getTypeName(),
1731                                                    outputDeviceIds [outputIndex],
1732                                                    inputDeviceIds [inputIndex],
1733                                                    deviceMode));
1734 
1735             if (! device->initialise())
1736                 device = nullptr;
1737         }
1738 
1739         return device.release();
1740     }
1741 
1742     //==============================================================================
1743     StringArray outputDeviceNames, outputDeviceIds;
1744     StringArray inputDeviceNames, inputDeviceIds;
1745 
1746 private:
1747     WASAPIDeviceMode deviceMode;
1748     bool hasScanned = false;
1749     ComSmartPtr<IMMDeviceEnumerator> enumerator;
1750 
1751     //==============================================================================
1752     class ChangeNotificationClient : public ComBaseClassHelper<IMMNotificationClient>
1753     {
1754     public:
1755         ChangeNotificationClient (WASAPIAudioIODeviceType* d)
1756             : ComBaseClassHelper<IMMNotificationClient> (0), device (d) {}
1757 
1758         HRESULT STDMETHODCALLTYPE OnDeviceAdded (LPCWSTR)                             { return notify(); }
1759         HRESULT STDMETHODCALLTYPE OnDeviceRemoved (LPCWSTR)                           { return notify(); }
1760         HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR, DWORD)                { return notify(); }
1761         HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR)  { return notify(); }
1762         HRESULT STDMETHODCALLTYPE OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); }
1763 
1764     private:
1765         WeakReference<WASAPIAudioIODeviceType> device;
1766 
1767         HRESULT notify()
1768         {
1769             if (device != nullptr)
1770                 device->triggerAsyncDeviceChangeCallback();
1771 
1772             return S_OK;
1773         }
1774 
1775         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChangeNotificationClient)
1776     };
1777 
1778     ComSmartPtr<ChangeNotificationClient> notifyClient;
1779 
1780     //==============================================================================
1781     static String getDefaultEndpoint (IMMDeviceEnumerator* enumerator, bool forCapture)
1782     {
1783         String s;
1784         IMMDevice* dev = nullptr;
1785 
1786         if (check (enumerator->GetDefaultAudioEndpoint (forCapture ? eCapture : eRender,
1787                                                         eMultimedia, &dev)))
1788         {
1789             WCHAR* deviceId = nullptr;
1790 
1791             if (check (dev->GetId (&deviceId)))
1792             {
1793                 s = deviceId;
1794                 CoTaskMemFree (deviceId);
1795             }
1796 
1797             dev->Release();
1798         }
1799 
1800         return s;
1801     }
1802 
1803     //==============================================================================
1804     void scan (StringArray& outDeviceNames,
1805                StringArray& inDeviceNames,
1806                StringArray& outDeviceIds,
1807                StringArray& inDeviceIds)
1808     {
1809         if (enumerator == nullptr)
1810         {
1811             if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator))))
1812                 return;
1813 
1814             notifyClient = new ChangeNotificationClient (this);
1815             enumerator->RegisterEndpointNotificationCallback (notifyClient);
1816         }
1817 
1818         auto defaultRenderer = getDefaultEndpoint (enumerator, false);
1819         auto defaultCapture  = getDefaultEndpoint (enumerator, true);
1820 
1821         ComSmartPtr<IMMDeviceCollection> deviceCollection;
1822         UINT32 numDevices = 0;
1823 
1824         if (! (check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress()))
1825                 && check (deviceCollection->GetCount (&numDevices))))
1826             return;
1827 
1828         for (UINT32 i = 0; i < numDevices; ++i)
1829         {
1830             ComSmartPtr<IMMDevice> device;
1831 
1832             if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress())))
1833                 continue;
1834 
1835             DWORD state = 0;
1836 
1837             if (! (check (device->GetState (&state)) && state == DEVICE_STATE_ACTIVE))
1838                 continue;
1839 
1840             auto deviceId = getDeviceID (device);
1841             String name;
1842 
1843             {
1844                 ComSmartPtr<IPropertyStore> properties;
1845 
1846                 if (! check (device->OpenPropertyStore (STGM_READ, properties.resetAndGetPointerAddress())))
1847                     continue;
1848 
1849                 PROPVARIANT value;
1850                 zerostruct (value);
1851 
1852                 const PROPERTYKEY PKEY_Device_FriendlyName
1853                     = { { 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } }, 14 };
1854 
1855                 if (check (properties->GetValue (PKEY_Device_FriendlyName, &value)))
1856                     name = value.pwszVal;
1857 
1858                 PropVariantClear (&value);
1859             }
1860 
1861             auto flow = getDataFlow (device);
1862 
1863             if (flow == eRender)
1864             {
1865                 const int index = (deviceId == defaultRenderer) ? 0 : -1;
1866                 outDeviceIds.insert (index, deviceId);
1867                 outDeviceNames.insert (index, name);
1868             }
1869             else if (flow == eCapture)
1870             {
1871                 const int index = (deviceId == defaultCapture) ? 0 : -1;
1872                 inDeviceIds.insert (index, deviceId);
1873                 inDeviceNames.insert (index, name);
1874             }
1875         }
1876 
1877         inDeviceNames.appendNumbersToDuplicates (false, false);
1878         outDeviceNames.appendNumbersToDuplicates (false, false);
1879     }
1880 
1881     //==============================================================================
1882     void systemDeviceChanged() override
1883     {
1884         StringArray newOutNames, newInNames, newOutIds, newInIds;
1885         scan (newOutNames, newInNames, newOutIds, newInIds);
1886 
1887         if (newOutNames != outputDeviceNames
1888              || newInNames != inputDeviceNames
1889              || newOutIds != outputDeviceIds
1890              || newInIds != inputDeviceIds)
1891         {
1892             hasScanned = true;
1893             outputDeviceNames = newOutNames;
1894             inputDeviceNames = newInNames;
1895             outputDeviceIds = newOutIds;
1896             inputDeviceIds = newInIds;
1897         }
1898 
1899         callDeviceChangeListeners();
1900     }
1901 
1902     //==============================================================================
1903     static String getDeviceTypename (WASAPIDeviceMode mode)
1904     {
1905         if (mode == WASAPIDeviceMode::shared)            return "Windows Audio";
1906         if (mode == WASAPIDeviceMode::sharedLowLatency)  return "Windows Audio (Low Latency Mode)";
1907         if (mode == WASAPIDeviceMode::exclusive)         return "Windows Audio (Exclusive Mode)";
1908 
1909         jassertfalse;
1910         return {};
1911     }
1912 
1913     //==============================================================================
1914     JUCE_DECLARE_WEAK_REFERENCEABLE (WASAPIAudioIODeviceType)
1915     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType)
1916 };
1917 
1918 //==============================================================================
1919 struct MMDeviceMasterVolume
1920 {
1921     MMDeviceMasterVolume()
1922     {
1923         ComSmartPtr<IMMDeviceEnumerator> enumerator;
1924 
1925         if (check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator))))
1926         {
1927             ComSmartPtr<IMMDevice> device;
1928 
1929             if (check (enumerator->GetDefaultAudioEndpoint (eRender, eConsole, device.resetAndGetPointerAddress())))
1930                 check (device->Activate (__uuidof (IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr,
1931                                          (void**) endpointVolume.resetAndGetPointerAddress()));
1932         }
1933     }
1934 
1935     float getGain() const
1936     {
1937         float vol = 0.0f;
1938 
1939         if (endpointVolume != nullptr)
1940             check (endpointVolume->GetMasterVolumeLevelScalar (&vol));
1941 
1942         return vol;
1943     }
1944 
1945     bool setGain (float newGain) const
1946     {
1947         return endpointVolume != nullptr
1948                 && check (endpointVolume->SetMasterVolumeLevelScalar (jlimit (0.0f, 1.0f, newGain), nullptr));
1949     }
1950 
1951     bool isMuted() const
1952     {
1953         BOOL mute = 0;
1954         return endpointVolume != nullptr
1955                  && check (endpointVolume->GetMute (&mute)) && mute != 0;
1956     }
1957 
1958     bool setMuted (bool shouldMute) const
1959     {
1960         return endpointVolume != nullptr
1961                 && check (endpointVolume->SetMute (shouldMute, nullptr));
1962     }
1963 
1964     ComSmartPtr<IAudioEndpointVolume> endpointVolume;
1965 
1966     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MMDeviceMasterVolume)
1967 };
1968 
1969 }
1970 
1971 //==============================================================================
1972 #define JUCE_SYSTEMAUDIOVOL_IMPLEMENTED 1
1973 float JUCE_CALLTYPE SystemAudioVolume::getGain()              { return WasapiClasses::MMDeviceMasterVolume().getGain(); }
1974 bool  JUCE_CALLTYPE SystemAudioVolume::setGain (float gain)   { return WasapiClasses::MMDeviceMasterVolume().setGain (gain); }
1975 bool  JUCE_CALLTYPE SystemAudioVolume::isMuted()              { return WasapiClasses::MMDeviceMasterVolume().isMuted(); }
1976 bool  JUCE_CALLTYPE SystemAudioVolume::setMuted (bool mute)   { return WasapiClasses::MMDeviceMasterVolume().setMuted (mute); }
1977 
1978 } // namespace juce
1979