1 /// Translated from C to D
2 module soundio.wasapi;
3 
4 extern(C): @nogc: nothrow: __gshared:
5 
6 import soundio.soundio_internal;
7 import soundio.soundio_private;
8 import soundio.os;
9 import soundio.list;
10 import soundio.atomics;
11 import soundio.headers.wasapiheader;
12 import core.stdc.stdio;
13 import core.sys.windows.windows;
14 import core.stdc.stdlib: free;
15 import core.stdc..string: strlen;
16 import soundio.headers.wasapiheader;
17 import soundio.headers.wasapiheader: WAVEFORMATEX;
18 
19 import core.atomic; // atomicOp!"+=" to replace InterlockedIncrement
20 
21 // @nogc nothrow definitions
22 extern(Windows) {
23     void CoTaskMemFree(PVOID);
24     HRESULT CoCreateInstance(REFCLSID, LPUNKNOWN, DWORD, REFIID, PVOID*);
25 }
26 
27 private:
28 
29 /+
30 import mmdeviceapi;
31 import audioclient;
32 import audiosessiontypes;
33 import audiopolicy;
34 version = INITGUID;
35 version = CINTERFACE;
36 version = COBJMACROS;
37 version = CONST_VTABLE;
38 import initguid;
39 import audioclient;
40 import endpointvolume;
41 import mmdeviceapi;
42 import mmreg;
43 import functiondiscoverykeys_devpkey;
44 import wasapi;
45 +/
46 
47 enum AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM = 0x80000000;
48 enum AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY = 0x08000000;
49 
50 package struct SoundIoDeviceWasapi {
51     double period_duration;
52     IMMDevice* mm_device;
53 }
54 
55 package struct SoundIoWasapi {
56     SoundIoOsMutex* mutex;
57     SoundIoOsCond* cond;
58     SoundIoOsCond* scan_devices_cond;
59     SoundIoOsMutex* scan_devices_mutex;
60     SoundIoOsThread* thread;
61     bool abort_flag;
62     // this one is ready to be read with flush_events. protected by mutex
63     SoundIoDevicesInfo* ready_devices_info;
64     bool have_devices_flag;
65     bool device_scan_queued;
66     int shutdown_err;
67     bool emitted_shutdown_cb;
68 
69     IMMDeviceEnumerator* device_enumerator;
70     IMMNotificationClient device_events;
71     LONG device_events_refs;
72 }
73 
74 package struct SoundIoOutStreamWasapi {
75     IAudioClient* audio_client;
76     IAudioClockAdjustment* audio_clock_adjustment;
77     IAudioRenderClient* audio_render_client;
78     IAudioSessionControl* audio_session_control;
79     ISimpleAudioVolume* audio_volume_control;
80     LPWSTR stream_name;
81     bool need_resample;
82     SoundIoOsThread* thread;
83     SoundIoOsMutex* mutex;
84     SoundIoOsCond* cond;
85     SoundIoOsCond* start_cond;
86     SoundIoAtomicFlag thread_exit_flag;
87     bool is_raw;
88     int writable_frame_count;
89     UINT32 buffer_frame_count;
90     int write_frame_count;
91     HANDLE h_event;
92     SoundIoAtomicBool desired_pause_state;
93     SoundIoAtomicFlag pause_resume_flag;
94     SoundIoAtomicFlag clear_buffer_flag;
95     bool is_paused;
96     bool open_complete;
97     int open_err;
98     bool started;
99     UINT32 min_padding_frames;
100     float volume;
101     SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas;
102 }
103 
104 package struct SoundIoInStreamWasapi {
105     IAudioClient* audio_client;
106     IAudioCaptureClient* audio_capture_client;
107     IAudioSessionControl* audio_session_control;
108     LPWSTR stream_name;
109     SoundIoOsThread* thread;
110     SoundIoOsMutex* mutex;
111     SoundIoOsCond* cond;
112     SoundIoOsCond* start_cond;
113     SoundIoAtomicFlag thread_exit_flag;
114     bool is_raw;
115     int readable_frame_count;
116     UINT32 buffer_frame_count;
117     int read_frame_count;
118     HANDLE h_event;
119     bool is_paused;
120     bool open_complete;
121     int open_err;
122     bool started;
123     char* read_buf;
124     int read_buf_frames_left;
125 	int opened_buf_frames;
126     SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas;
127 }
128 
129 enum E_NOTFOUND = 0x80070490;
130 
131 /+
132 // In C++ mode, IsEqualGUID() takes its arguments by reference
133 enum string IS_EQUAL_GUID(string a, string b) = ` IsEqualGUID(*(a), *(b))`;
134 enum string IS_EQUAL_IID(string a, string b) = ` IsEqualIID((a), *(b))`;
135 
136 // And some constants are passed by reference
137 enum IID_IAUDIOCLIENT =                      (IID_IAudioClient);
138 enum IID_IMMENDPOINT =                       (IID_IMMEndpoint);
139 enum IID_IAUDIOCLOCKADJUSTMENT =             (IID_IAudioClockAdjustment);
140 enum IID_IAUDIOSESSIONCONTROL =              (IID_IAudioSessionControl);
141 enum IID_IAUDIORENDERCLIENT =                (IID_IAudioRenderClient);
142 enum IID_IMMDEVICEENUMERATOR =               (IID_IMMDeviceEnumerator);
143 enum IID_IAUDIOCAPTURECLIENT =               (IID_IAudioCaptureClient);
144 enum IID_ISIMPLEAUDIOVOLUME =                (IID_ISimpleAudioVolume);
145 enum CLSID_MMDEVICEENUMERATOR =              (CLSID_MMDeviceEnumerator);
146 enum PKEY_DEVICE_FRIENDLYNAME =              (PKEY_Device_FriendlyName);
147 enum PKEY_AUDIOENGINE_DEVICEFORMAT =         (PKEY_AudioEngine_DeviceFormat);
148 +/
149 
150 // And some GUID are never implemented (Ignoring the INITGUID define)
151 static const(CLSID) CLSID_MMDeviceEnumerator = CLSID(
152     0xBCDE0395, 0xE52F, 0x467C, [0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E]
153 );
154 // TODO: Verify this equals __uuidof(MMDeviceEnumerator);
155 // class DECLSPEC_UUID("BCDE0395-E52F-467C-8E3D-C4579291692E")
156 // MMDeviceEnumerator;
157 
158 static const(IID) IID_IMMDeviceEnumerator = IID(
159     //MIDL_INTERFACE("A95664D2-9614-4F35-A746-DE8DB63617E6")
160     0xa95664d2, 0x9614, 0x4f35, [0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6]
161 );
162 static const(IID) IID_IMMNotificationClient = IID(
163     //MIDL_INTERFACE("7991EEC9-7E89-4D85-8390-6C703CEC60C0")
164     0x7991eec9, 0x7e89, 0x4d85, [0x83, 0x90, 0x6c, 0x70, 0x3c, 0xec, 0x60, 0xc0]
165 );
166 static const(IID) IID_IAudioClient = IID(
167     //MIDL_INTERFACE("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2")
168     0x1cb9ad4c, 0xdbfa, 0x4c32, [0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2]
169 );
170 static const(IID) IID_IAudioRenderClient = IID(
171     //MIDL_INTERFACE("F294ACFC-3146-4483-A7BF-ADDCA7C260E2")
172     0xf294acfc, 0x3146, 0x4483, [0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2]
173 );
174 static const(IID) IID_IAudioSessionControl = IID(
175     //MIDL_INTERFACE("F4B1A599-7266-4319-A8CA-E70ACB11E8CD")
176     0xf4b1a599, 0x7266, 0x4319, [0xa8, 0xca, 0xe7, 0x0a, 0xcb, 0x11, 0xe8, 0xcd]
177 );
178 static const(IID) IID_IAudioSessionEvents = IID(
179     //MIDL_INTERFACE("24918ACC-64B3-37C1-8CA9-74A66E9957A8")
180     0x24918acc, 0x64b3, 0x37c1, [0x8c, 0xa9, 0x74, 0xa6, 0x6e, 0x99, 0x57, 0xa8]
181 );
182 static const(IID) IID_IMMEndpoint = IID(
183     //MIDL_INTERFACE("1BE09788-6894-4089-8586-9A2A6C265AC5")
184     0x1be09788, 0x6894, 0x4089, [0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5]
185 );
186 static const(IID) IID_IAudioClockAdjustment = IID(
187     //MIDL_INTERFACE("f6e4c0a0-46d9-4fb8-be21-57a3ef2b626c")
188     0xf6e4c0a0, 0x46d9, 0x4fb8, [0xbe, 0x21, 0x57, 0xa3, 0xef, 0x2b, 0x62, 0x6c]
189 );
190 static const(IID) IID_IAudioCaptureClient = IID(
191     //MIDL_INTERFACE("C8ADBD64-E71E-48a0-A4DE-185C395CD317")
192     0xc8adbd64, 0xe71e, 0x48a0, [0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17]
193 );
194 static const(IID) IID_ISimpleAudioVolume = IID(
195     //MIDL_INTERFACE("87ce5498-68d6-44e5-9215-6da47ef883d8")
196     0x87ce5498, 0x68d6, 0x44e5,[0x92, 0x15, 0x6d, 0xa4, 0x7e, 0xf8, 0x83, 0xd8 ]
197 );
198 
199 extern(Windows) BOOL IsEqualGUID(REFGUID rguid1, REFGUID rguid2);
200 alias IsEqualIID = IsEqualGUID;
201 bool IS_EQUAL_GUID(const(GUID)* a, const(GUID)* b) { return cast(bool) IsEqualGUID(a, b);}
202 bool IS_EQUAL_IID(const(IID)* a, const(IID)* b) { return cast(bool) IsEqualIID(a, b);}
203 
204 enum IID_IAUDIOCLIENT = (&IID_IAudioClient);
205 enum IID_IMMENDPOINT = (&IID_IMMEndpoint);
206 enum PKEY_DEVICE_FRIENDLYNAME = (&PKEY_Device_FriendlyName);
207 enum PKEY_AUDIOENGINE_DEVICEFORMAT = (&PKEY_AudioEngine_DeviceFormat);
208 enum CLSID_MMDEVICEENUMERATOR = (&CLSID_MMDeviceEnumerator);
209 enum IID_IAUDIOCLOCKADJUSTMENT = (&IID_IAudioClockAdjustment);
210 enum IID_IAUDIOSESSIONCONTROL = (&IID_IAudioSessionControl);
211 enum IID_IAUDIORENDERCLIENT = (&IID_IAudioRenderClient);
212 enum IID_IMMDEVICEENUMERATOR = (&IID_IMMDeviceEnumerator);
213 enum IID_IAUDIOCAPTURECLIENT = (&IID_IAudioCaptureClient);
214 enum IID_ISIMPLEAUDIOVOLUME = (&IID_ISimpleAudioVolume);
215 
216 // Attempting to use the Windows-supplied versions of these constants resulted
217 // in `undefined reference` linker errors.
218 static const(GUID) SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = GUID(
219     0x00000003,0x0000,0x0010, [0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]);
220 
221 static const(GUID) SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = GUID(
222     0x00000001,0x0000,0x0010, [0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]);
223 
224 // Adding more common sample rates helps the heuristics; feel free to do that.
225 static int[19] test_sample_rates = [
226     8000,
227     11025,
228     16000,
229     22050,
230     32000,
231     37800,
232     44056,
233     44100,
234     47250,
235     48000,
236     50000,
237     50400,
238     88200,
239     96000,
240     176400,
241     192000,
242     352800,
243     2822400,
244     5644800,
245 ];
246 
247 // If you modify this list, also modify `to_wave_format_format` appropriately.
248 immutable SoundIoFormat[6] test_formats = [
249     SoundIoFormat.U8,
250     SoundIoFormat.S16LE,
251     SoundIoFormat.S24LE,
252     SoundIoFormat.S32LE,
253     SoundIoFormat.Float32LE,
254     SoundIoFormat.Float64LE,
255 ];
256 
257 // If you modify this list, also modify `to_wave_format_layout` appropriately.
258 immutable SoundIoChannelLayoutId[7] test_layouts = [
259     SoundIoChannelLayoutId.Mono,
260     SoundIoChannelLayoutId.Stereo,
261     SoundIoChannelLayoutId.Quad,
262     SoundIoChannelLayoutId._4Point0,
263     SoundIoChannelLayoutId._5Point1,
264     SoundIoChannelLayoutId._7Point1,
265     SoundIoChannelLayoutId._5Point1Back,
266 ];
267 
268 /*
269 // useful for debugging but no point in compiling into binary
270 static const char *hresult_to_str(HRESULT hr) {
271     switch (hr) {
272         default: return "(unknown)";
273         case AUDCLNT_E_NOT_INITIALIZED: return "AUDCLNT_E_NOT_INITIALIZED";
274         case AUDCLNT_E_ALREADY_INITIALIZED: return "AUDCLNT_E_ALREADY_INITIALIZED";
275         case AUDCLNT_E_WRONG_ENDPOINT_TYPE: return "AUDCLNT_E_WRONG_ENDPOINT_TYPE";
276         case AUDCLNT_E_DEVICE_INVALIDATED: return "AUDCLNT_E_DEVICE_INVALIDATED";
277         case AUDCLNT_E_NOT_STOPPED: return "AUDCLNT_E_NOT_STOPPED";
278         case AUDCLNT_E_BUFFER_TOO_LARGE: return "AUDCLNT_E_BUFFER_TOO_LARGE";
279         case AUDCLNT_E_OUT_OF_ORDER: return "AUDCLNT_E_OUT_OF_ORDER";
280         case AUDCLNT_E_UNSUPPORTED_FORMAT: return "AUDCLNT_E_UNSUPPORTED_FORMAT";
281         case AUDCLNT_E_INVALID_SIZE: return "AUDCLNT_E_INVALID_SIZE";
282         case AUDCLNT_E_DEVICE_IN_USE: return "AUDCLNT_E_DEVICE_IN_USE";
283         case AUDCLNT_E_BUFFER_OPERATION_PENDING: return "AUDCLNT_E_BUFFER_OPERATION_PENDING";
284         case AUDCLNT_E_THREAD_NOT_REGISTERED: return "AUDCLNT_E_THREAD_NOT_REGISTERED";
285         case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: return "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED";
286         case AUDCLNT_E_ENDPOINT_CREATE_FAILED: return "AUDCLNT_E_ENDPOINT_CREATE_FAILED";
287         case AUDCLNT_E_SERVICE_NOT_RUNNING: return "AUDCLNT_E_SERVICE_NOT_RUNNING";
288         case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: return "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED";
289         case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: return "AUDCLNT_E_EXCLUSIVE_MODE_ONLY";
290         case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: return "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL";
291         case AUDCLNT_E_EVENTHANDLE_NOT_SET: return "AUDCLNT_E_EVENTHANDLE_NOT_SET";
292         case AUDCLNT_E_INCORRECT_BUFFER_SIZE: return "AUDCLNT_E_INCORRECT_BUFFER_SIZE";
293         case AUDCLNT_E_BUFFER_SIZE_ERROR: return "AUDCLNT_E_BUFFER_SIZE_ERROR";
294         case AUDCLNT_E_CPUUSAGE_EXCEEDED: return "AUDCLNT_E_CPUUSAGE_EXCEEDED";
295         case AUDCLNT_E_BUFFER_ERROR: return "AUDCLNT_E_BUFFER_ERROR";
296         case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED: return "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED";
297         case AUDCLNT_E_INVALID_DEVICE_PERIOD: return "AUDCLNT_E_INVALID_DEVICE_PERIOD";
298         case AUDCLNT_E_INVALID_STREAM_FLAG: return "AUDCLNT_E_INVALID_STREAM_FLAG";
299         case AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE: return "AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE";
300         case AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES: return "AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES";
301         case AUDCLNT_E_OFFLOAD_MODE_ONLY: return "AUDCLNT_E_OFFLOAD_MODE_ONLY";
302         case AUDCLNT_E_NONOFFLOAD_MODE_ONLY: return "AUDCLNT_E_NONOFFLOAD_MODE_ONLY";
303         case AUDCLNT_E_RESOURCES_INVALIDATED: return "AUDCLNT_E_RESOURCES_INVALIDATED";
304         case AUDCLNT_S_BUFFER_EMPTY: return "AUDCLNT_S_BUFFER_EMPTY";
305         case AUDCLNT_S_THREAD_ALREADY_REGISTERED: return "AUDCLNT_S_THREAD_ALREADY_REGISTERED";
306         case AUDCLNT_S_POSITION_STALLED: return "AUDCLNT_S_POSITION_STALLED";
307 
308         case E_POINTER: return "E_POINTER";
309         case E_INVALIDARG: return "E_INVALIDARG";
310         case E_OUTOFMEMORY: return "E_OUTOFMEMORY";
311     }
312 }
313 */
314 
315 // converts a windows wide string to a UTF-8 encoded char *
316 // Possible errors:
317 //  * SoundIoError.oMem
318 //  * SoundIoError.EncodingString
319 static int from_lpwstr(LPWSTR lpwstr, char** out_str, int* out_str_len) {
320     DWORD flags = 0;
321     int buf_size = WideCharToMultiByte(CP_UTF8, flags, lpwstr, -1, null, 0, null, null);
322 
323     if (buf_size == 0)
324         return SoundIoError.EncodingString;
325 
326     char* buf = ALLOCATE!char(buf_size);
327     if (!buf)
328         return SoundIoError.NoMem;
329 
330     if (WideCharToMultiByte(CP_UTF8, flags, lpwstr, -1, buf, buf_size, null, null) != buf_size) {
331         free(buf);
332         return SoundIoError.EncodingString;
333     }
334 
335     *out_str = buf;
336     *out_str_len = buf_size - 1;
337 
338     return 0;
339 }
340 
341 static int to_lpwstr(const(char)* str, int str_len, LPWSTR* out_lpwstr) {
342     DWORD flags = 0;
343     int w_len = MultiByteToWideChar(CP_UTF8, flags, str, str_len, null, 0);
344     if (w_len <= 0)
345         return SoundIoError.EncodingString;
346 
347     LPWSTR buf = ALLOCATE!wchar(w_len + 1);
348     if (!buf)
349         return SoundIoError.NoMem;
350 
351     if (MultiByteToWideChar(CP_UTF8, flags, str, str_len, buf, w_len) != w_len) {
352         free(buf);
353         return SoundIoError.EncodingString;
354     }
355 
356     *out_lpwstr = buf;
357     return 0;
358 }
359 
360 static void from_channel_mask_layout(UINT channel_mask, SoundIoChannelLayout* layout) {
361     layout.channel_count = 0;
362     if (channel_mask & SPEAKER_FRONT_LEFT)
363         layout.channels[layout.channel_count++] = SoundIoChannelId.FrontLeft;
364     if (channel_mask & SPEAKER_FRONT_RIGHT)
365         layout.channels[layout.channel_count++] = SoundIoChannelId.FrontRight;
366     if (channel_mask & SPEAKER_FRONT_CENTER)
367         layout.channels[layout.channel_count++] = SoundIoChannelId.FrontCenter;
368     if (channel_mask & SPEAKER_LOW_FREQUENCY)
369         layout.channels[layout.channel_count++] = SoundIoChannelId.Lfe;
370     if (channel_mask & SPEAKER_BACK_LEFT)
371         layout.channels[layout.channel_count++] = SoundIoChannelId.BackLeft;
372     if (channel_mask & SPEAKER_BACK_RIGHT)
373         layout.channels[layout.channel_count++] = SoundIoChannelId.BackRight;
374     if (channel_mask & SPEAKER_FRONT_LEFT_OF_CENTER)
375         layout.channels[layout.channel_count++] = SoundIoChannelId.FrontLeftCenter;
376     if (channel_mask & SPEAKER_FRONT_RIGHT_OF_CENTER)
377         layout.channels[layout.channel_count++] = SoundIoChannelId.FrontRightCenter;
378     if (channel_mask & SPEAKER_BACK_CENTER)
379         layout.channels[layout.channel_count++] = SoundIoChannelId.BackCenter;
380     if (channel_mask & SPEAKER_SIDE_LEFT)
381         layout.channels[layout.channel_count++] = SoundIoChannelId.SideLeft;
382     if (channel_mask & SPEAKER_SIDE_RIGHT)
383         layout.channels[layout.channel_count++] = SoundIoChannelId.SideRight;
384     if (channel_mask & SPEAKER_TOP_CENTER)
385         layout.channels[layout.channel_count++] = SoundIoChannelId.TopCenter;
386     if (channel_mask & SPEAKER_TOP_FRONT_LEFT)
387         layout.channels[layout.channel_count++] = SoundIoChannelId.TopFrontLeft;
388     if (channel_mask & SPEAKER_TOP_FRONT_CENTER)
389         layout.channels[layout.channel_count++] = SoundIoChannelId.TopFrontCenter;
390     if (channel_mask & SPEAKER_TOP_FRONT_RIGHT)
391         layout.channels[layout.channel_count++] = SoundIoChannelId.TopFrontRight;
392     if (channel_mask & SPEAKER_TOP_BACK_LEFT)
393         layout.channels[layout.channel_count++] = SoundIoChannelId.TopBackLeft;
394     if (channel_mask & SPEAKER_TOP_BACK_CENTER)
395         layout.channels[layout.channel_count++] = SoundIoChannelId.TopBackCenter;
396     if (channel_mask & SPEAKER_TOP_BACK_RIGHT)
397         layout.channels[layout.channel_count++] = SoundIoChannelId.TopBackRight;
398 
399     soundio_channel_layout_detect_builtin(layout);
400 }
401 
402 static void from_wave_format_layout(WAVEFORMATEXTENSIBLE* wave_format, SoundIoChannelLayout* layout) {
403     assert(wave_format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE);
404     layout.channel_count = 0;
405     from_channel_mask_layout(wave_format.dwChannelMask, layout);
406 }
407 
408 static SoundIoFormat from_wave_format_format(WAVEFORMATEXTENSIBLE* wave_format) {
409     assert(wave_format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE);
410     bool is_pcm = IS_EQUAL_GUID(&wave_format.SubFormat, &SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM);
411     bool is_float = IS_EQUAL_GUID(&wave_format.SubFormat, &SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT);
412 
413     if (wave_format.Samples.wValidBitsPerSample == wave_format.Format.wBitsPerSample) {
414         if (wave_format.Format.wBitsPerSample == 8) {
415             if (is_pcm)
416                 return SoundIoFormat.U8;
417         } else if (wave_format.Format.wBitsPerSample == 16) {
418             if (is_pcm)
419                 return SoundIoFormat.S16LE;
420         } else if (wave_format.Format.wBitsPerSample == 32) {
421             if (is_pcm)
422                 return SoundIoFormat.S32LE;
423             else if (is_float)
424                 return SoundIoFormat.Float32LE;
425         } else if (wave_format.Format.wBitsPerSample == 64) {
426             if (is_float)
427                 return SoundIoFormat.Float64LE;
428         }
429     } else if (wave_format.Format.wBitsPerSample == 32 &&
430             wave_format.Samples.wValidBitsPerSample == 24)
431     {
432         return SoundIoFormat.S24LE;
433     }
434 
435     return SoundIoFormat.Invalid;
436 }
437 
438 // only needs to support the layouts in test_layouts
439 static void to_wave_format_layout(const(SoundIoChannelLayout)* layout, WAVEFORMATEXTENSIBLE* wave_format) {
440     wave_format.dwChannelMask = 0;
441     wave_format.Format.nChannels = cast(ushort) layout.channel_count;
442     for (int i = 0; i < layout.channel_count; i += 1) {
443         SoundIoChannelId channel_id = layout.channels[i];
444         switch (channel_id) {
445             case SoundIoChannelId.FrontLeft:
446                 wave_format.dwChannelMask |= SPEAKER_FRONT_LEFT;
447                 break;
448             case SoundIoChannelId.FrontRight:
449                 wave_format.dwChannelMask |= SPEAKER_FRONT_RIGHT;
450                 break;
451             case SoundIoChannelId.FrontCenter:
452                 wave_format.dwChannelMask |= SPEAKER_FRONT_CENTER;
453                 break;
454             case SoundIoChannelId.Lfe:
455                 wave_format.dwChannelMask |= SPEAKER_LOW_FREQUENCY;
456                 break;
457             case SoundIoChannelId.BackLeft:
458                 wave_format.dwChannelMask |= SPEAKER_BACK_LEFT;
459                 break;
460             case SoundIoChannelId.BackRight:
461                 wave_format.dwChannelMask |= SPEAKER_BACK_RIGHT;
462                 break;
463             case SoundIoChannelId.FrontLeftCenter:
464                 wave_format.dwChannelMask |= SPEAKER_FRONT_LEFT_OF_CENTER;
465                 break;
466             case SoundIoChannelId.FrontRightCenter:
467                 wave_format.dwChannelMask |= SPEAKER_FRONT_RIGHT_OF_CENTER;
468                 break;
469             case SoundIoChannelId.BackCenter:
470                 wave_format.dwChannelMask |= SPEAKER_BACK_CENTER;
471                 break;
472             case SoundIoChannelId.SideLeft:
473                 wave_format.dwChannelMask |= SPEAKER_SIDE_LEFT;
474                 break;
475             case SoundIoChannelId.SideRight:
476                 wave_format.dwChannelMask |= SPEAKER_SIDE_RIGHT;
477                 break;
478             case SoundIoChannelId.TopCenter:
479                 wave_format.dwChannelMask |= SPEAKER_TOP_CENTER;
480                 break;
481             case SoundIoChannelId.TopFrontLeft:
482                 wave_format.dwChannelMask |= SPEAKER_TOP_FRONT_LEFT;
483                 break;
484             case SoundIoChannelId.TopFrontCenter:
485                 wave_format.dwChannelMask |= SPEAKER_TOP_FRONT_CENTER;
486                 break;
487             case SoundIoChannelId.TopFrontRight:
488                 wave_format.dwChannelMask |= SPEAKER_TOP_FRONT_RIGHT;
489                 break;
490             case SoundIoChannelId.TopBackLeft:
491                 wave_format.dwChannelMask |= SPEAKER_TOP_BACK_LEFT;
492                 break;
493             case SoundIoChannelId.TopBackCenter:
494                 wave_format.dwChannelMask |= SPEAKER_TOP_BACK_CENTER;
495                 break;
496             case SoundIoChannelId.TopBackRight:
497                 wave_format.dwChannelMask |= SPEAKER_TOP_BACK_RIGHT;
498                 break;
499             default:
500                 soundio_panic("to_wave_format_layout: unsupported channel id");
501         }
502     }
503 }
504 
505 // only needs to support the formats in test_formats
506 static void to_wave_format_format(SoundIoFormat format, WAVEFORMATEXTENSIBLE* wave_format) {
507     switch (format) {
508     case SoundIoFormat.U8:
509         wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
510         wave_format.Format.wBitsPerSample = 8;
511         wave_format.Samples.wValidBitsPerSample = 8;
512         break;
513     case SoundIoFormat.S16LE:
514         wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
515         wave_format.Format.wBitsPerSample = 16;
516         wave_format.Samples.wValidBitsPerSample = 16;
517         break;
518     case SoundIoFormat.S24LE:
519         wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
520         wave_format.Format.wBitsPerSample = 32;
521         wave_format.Samples.wValidBitsPerSample = 24;
522         break;
523     case SoundIoFormat.S32LE:
524         wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
525         wave_format.Format.wBitsPerSample = 32;
526         wave_format.Samples.wValidBitsPerSample = 32;
527         break;
528     case SoundIoFormat.Float32LE:
529         wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
530         wave_format.Format.wBitsPerSample = 32;
531         wave_format.Samples.wValidBitsPerSample = 32;
532         break;
533     case SoundIoFormat.Float64LE:
534         wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
535         wave_format.Format.wBitsPerSample = 64;
536         wave_format.Samples.wValidBitsPerSample = 64;
537         break;
538     default:
539         soundio_panic("to_wave_format_format: unsupported format");
540     }
541 }
542 
543 static void complete_wave_format_data(WAVEFORMATEXTENSIBLE* wave_format) {
544     wave_format.Format.nBlockAlign = cast(ushort) ((wave_format.Format.wBitsPerSample * wave_format.Format.nChannels) / 8);
545     wave_format.Format.nAvgBytesPerSec = wave_format.Format.nSamplesPerSec * wave_format.Format.nBlockAlign;
546 }
547 
548 static SoundIoDeviceAim data_flow_to_aim(EDataFlow data_flow) {
549     return (data_flow == eRender) ? SoundIoDeviceAim.Output : SoundIoDeviceAim.Input;
550 }
551 
552 
553 static double from_reference_time(REFERENCE_TIME rt) {
554     return (cast(double)rt) / 10000000.0;
555 }
556 
557 static REFERENCE_TIME to_reference_time(double seconds) {
558     return cast(REFERENCE_TIME)(seconds * 10000000.0 + 0.5);
559 }
560 
561 static void destruct_device(SoundIoDevicePrivate* dev) {
562     SoundIoDeviceWasapi* dw = &dev.backend_data.wasapi;
563     if (dw.mm_device)
564         IMMDevice_Release(dw.mm_device);
565 }
566 
567 struct RefreshDevices {
568     IMMDeviceCollection* collection;
569     IMMDevice* mm_device;
570     IMMDevice* default_render_device;
571     IMMDevice* default_capture_device;
572     IMMEndpoint* endpoint;
573     IPropertyStore* prop_store;
574     IAudioClient* audio_client;
575     LPWSTR lpwstr;
576     PROPVARIANT prop_variant_value;
577     WAVEFORMATEXTENSIBLE* wave_format;
578     bool prop_variant_value_inited;
579     SoundIoDevicesInfo* devices_info;
580     SoundIoDevice* device_shared;
581     SoundIoDevice* device_raw;
582     char* default_render_id;
583     int default_render_id_len;
584     char* default_capture_id;
585     int default_capture_id_len;
586 }
587 // static assert(RefreshDevices.sizeof == 160); 64-bit
588 
589 static void deinit_refresh_devices(RefreshDevices* rd) {
590     soundio_destroy_devices_info(rd.devices_info);
591     soundio_device_unref(rd.device_shared);
592     soundio_device_unref(rd.device_raw);
593     if (rd.mm_device)
594         IMMDevice_Release(rd.mm_device);
595     if (rd.default_render_device)
596     {
597         IMMDevice_Release(rd.default_render_device);
598         free(rd.default_render_id);
599     }
600     if (rd.default_capture_device)
601     {
602         IMMDevice_Release(rd.default_capture_device);
603         free(rd.default_capture_id);
604     }
605     if (rd.collection)
606         IMMDeviceCollection_Release(rd.collection);
607     if (rd.lpwstr)
608         CoTaskMemFree(rd.lpwstr);
609     if (rd.endpoint)
610         IMMEndpoint_Release(rd.endpoint);
611     if (rd.prop_store)
612         IPropertyStore_Release(rd.prop_store);
613     if (rd.prop_variant_value_inited)
614         PropVariantClear(&rd.prop_variant_value);
615     if (rd.wave_format)
616         CoTaskMemFree(rd.wave_format);
617     if (rd.audio_client)
618         IUnknown_Release(rd.audio_client);
619 }
620 
621 static int detect_valid_layouts(RefreshDevices* rd, WAVEFORMATEXTENSIBLE* wave_format, SoundIoDevicePrivate* dev, AUDCLNT_SHAREMODE share_mode) {
622     SoundIoDevice* device = &dev.pub;
623     HRESULT hr;
624 
625     device.layout_count = 0;
626     device.layouts = ALLOCATE!SoundIoChannelLayout(test_layouts.length);
627     if (!device.layouts)
628         return SoundIoError.NoMem;
629 
630     WAVEFORMATEX* closest_match = null;
631     WAVEFORMATEXTENSIBLE orig_wave_format = *wave_format;
632 
633     for (int i = 0; i < test_formats.length; i += 1) {
634         SoundIoChannelLayoutId test_layout_id = test_layouts[i];
635         const(SoundIoChannelLayout)* test_layout = soundio_channel_layout_get_builtin(test_layout_id);
636         to_wave_format_layout(test_layout, wave_format);
637         complete_wave_format_data(wave_format);
638 
639         hr = IAudioClient_IsFormatSupported(rd.audio_client, share_mode,
640                 cast(WAVEFORMATEX*)wave_format, &closest_match);
641         if (closest_match) {
642             CoTaskMemFree(closest_match);
643             closest_match = null;
644         }
645         if (hr == S_OK) {
646             device.layouts[device.layout_count++] = *test_layout;
647         } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == S_FALSE || hr == E_INVALIDARG) {
648             continue;
649         } else {
650             *wave_format = orig_wave_format;
651             return SoundIoError.OpeningDevice;
652         }
653     }
654 
655     *wave_format = orig_wave_format;
656     return 0;
657 }
658 
659 static int detect_valid_formats(RefreshDevices* rd, WAVEFORMATEXTENSIBLE* wave_format, SoundIoDevicePrivate* dev, AUDCLNT_SHAREMODE share_mode) {
660     SoundIoDevice* device = &dev.pub;
661     HRESULT hr;
662 
663     device.format_count = 0;
664     device.formats = ALLOCATE!SoundIoFormat(test_formats.length);
665     if (!device.formats)
666         return SoundIoError.NoMem;
667 
668     WAVEFORMATEX* closest_match = null;
669     WAVEFORMATEXTENSIBLE orig_wave_format = *wave_format;
670 
671     for (int i = 0; i < test_formats.length; i += 1) {
672         SoundIoFormat test_format = test_formats[i];
673         to_wave_format_format(test_format, wave_format);
674         complete_wave_format_data(wave_format);
675 
676         hr = IAudioClient_IsFormatSupported(rd.audio_client, share_mode,
677                 cast(WAVEFORMATEX*)wave_format, &closest_match);
678         if (closest_match) {
679             CoTaskMemFree(closest_match);
680             closest_match = null;
681         }
682         if (hr == S_OK) {
683             device.formats[device.format_count++] = test_format;
684         } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == S_FALSE || hr == E_INVALIDARG) {
685             continue;
686         } else {
687             *wave_format = orig_wave_format;
688             return SoundIoError.OpeningDevice;
689         }
690     }
691 
692     *wave_format = orig_wave_format;
693     return 0;
694 }
695 
696 static int add_sample_rate(SoundIoListSampleRateRange* sample_rates, int* current_min, int the_max) {
697     if (auto err = sample_rates.add_one())
698         return err;
699 
700     SoundIoSampleRateRange* last_range = sample_rates.last_ptr();
701     last_range.min = *current_min;
702     last_range.max = the_max;
703     return 0;
704 }
705 
706 static int do_sample_rate_test(RefreshDevices* rd, SoundIoDevicePrivate* dev, WAVEFORMATEXTENSIBLE* wave_format, int test_sample_rate, AUDCLNT_SHAREMODE share_mode, int* current_min, int* last_success_rate) {
707     WAVEFORMATEX* closest_match = null;
708 
709     wave_format.Format.nSamplesPerSec = test_sample_rate;
710     HRESULT hr = IAudioClient_IsFormatSupported(rd.audio_client, share_mode,
711             cast(WAVEFORMATEX*)wave_format, &closest_match);
712     if (closest_match) {
713         CoTaskMemFree(closest_match);
714         closest_match = null;
715     }
716     if (hr == S_OK) {
717         if (*current_min == -1) {
718             *current_min = test_sample_rate;
719         }
720         *last_success_rate = test_sample_rate;
721     } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == S_FALSE || hr == E_INVALIDARG) {
722         if (*current_min != -1) {
723             if (auto err = add_sample_rate(&dev.sample_rates, current_min, *last_success_rate))
724                 return err;
725             *current_min = -1;
726         }
727     } else {
728         return SoundIoError.OpeningDevice;
729     }
730 
731     return 0;
732 }
733 
734 static int detect_valid_sample_rates(RefreshDevices* rd, WAVEFORMATEXTENSIBLE* wave_format, SoundIoDevicePrivate* dev, AUDCLNT_SHAREMODE share_mode) {
735     DWORD orig_sample_rate = wave_format.Format.nSamplesPerSec;
736 
737     assert(dev.sample_rates.length == 0);
738 
739     int current_min = -1;
740     int last_success_rate = -1;
741     for (int i = 0; i < test_sample_rates.length; i += 1) {
742         for (int offset = -1; offset <= 1; offset += 1) {
743             int test_sample_rate = test_sample_rates[i] + offset;
744             if (auto err = do_sample_rate_test(rd, dev, wave_format, test_sample_rate, share_mode,
745                             &current_min, &last_success_rate))
746             {
747                 wave_format.Format.nSamplesPerSec = orig_sample_rate;
748                 return err;
749             }
750         }
751     }
752 
753     if (current_min != -1) {
754         if (auto err = add_sample_rate(&dev.sample_rates, &current_min, last_success_rate)) {
755             wave_format.Format.nSamplesPerSec = orig_sample_rate;
756             return err;
757         }
758     }
759 
760     SoundIoDevice* device = &dev.pub;
761 
762     device.sample_rate_count = dev.sample_rates.length;
763     device.sample_rates = dev.sample_rates.items;
764 
765     wave_format.Format.nSamplesPerSec = orig_sample_rate;
766     return 0;
767 }
768 
769 
770 static int refresh_devices(SoundIoPrivate* si) {
771     SoundIo* soundio = &si.pub;
772     SoundIoWasapi* siw = &si.backend_data.wasapi;
773     RefreshDevices rd = RefreshDevices.init; // todo: is this zero?
774     HRESULT hr;
775 
776     if (FAILED(hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(siw.device_enumerator, eRender,
777                     eMultimedia, &rd.default_render_device)))
778     {
779         if(hr != E_NOTFOUND) {
780             deinit_refresh_devices(&rd);
781             if(hr == E_OUTOFMEMORY) {
782                 return SoundIoError.NoMem;
783             }
784             return SoundIoError.OpeningDevice;
785         }
786     }
787     if(rd.default_render_device) {
788         if (rd.lpwstr) {
789             CoTaskMemFree(rd.lpwstr);
790             rd.lpwstr = null;
791         }
792         if (FAILED(hr = IMMDevice_GetId(rd.default_render_device, &rd.lpwstr))) {
793             deinit_refresh_devices(&rd);
794             // MSDN states the IMMDevice_GetId can fail if the device is NULL, or if we're out of memory
795             // We know the device point isn't NULL so we're necessarily out of memory
796             return SoundIoError.NoMem;
797         }
798         if (auto err = from_lpwstr(rd.lpwstr, &rd.default_render_id, &rd.default_render_id_len)) {
799             deinit_refresh_devices(&rd);
800             return err;
801         }
802     }
803 
804 
805     if (FAILED(hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(siw.device_enumerator, eCapture,
806                     eMultimedia, &rd.default_capture_device)))
807     {
808         if(hr != E_NOTFOUND) {
809             deinit_refresh_devices(&rd);
810             if(hr == E_OUTOFMEMORY) {
811                 return SoundIoError.NoMem;
812             }
813             return SoundIoError.OpeningDevice;
814         }
815     }
816     if(rd.default_capture_device) {
817         if (rd.lpwstr) {
818             CoTaskMemFree(rd.lpwstr);
819             rd.lpwstr = null;
820         }
821         if (FAILED(hr = IMMDevice_GetId(rd.default_capture_device, &rd.lpwstr))) {
822             deinit_refresh_devices(&rd);
823             if(hr == E_OUTOFMEMORY) {
824                 return SoundIoError.NoMem;
825             }
826             return SoundIoError.OpeningDevice;
827         }
828         if (auto err = from_lpwstr(rd.lpwstr, &rd.default_capture_id, &rd.default_capture_id_len)) {
829             deinit_refresh_devices(&rd);
830             return err;
831         }
832     }
833 
834 
835     if (FAILED(hr = IMMDeviceEnumerator_EnumAudioEndpoints(siw.device_enumerator,
836                     eAll, DEVICE_STATE_ACTIVE, &rd.collection)))
837     {
838         deinit_refresh_devices(&rd);
839         if(hr == E_OUTOFMEMORY) {
840             return SoundIoError.NoMem;
841         }
842         return SoundIoError.OpeningDevice;
843     }
844 
845     UINT unsigned_count;
846     if (FAILED(hr = IMMDeviceCollection_GetCount(rd.collection, &unsigned_count))) {
847         // In theory this shouldn't happen since the only documented failure case is that
848         // rd.collection is NULL, but then EnumAudioEndpoints should have failed.
849         deinit_refresh_devices(&rd);
850         return SoundIoError.OpeningDevice;
851     }
852 
853     if (unsigned_count > cast(UINT)int.max) {
854         deinit_refresh_devices(&rd);
855         return SoundIoError.IncompatibleDevice;
856     }
857 
858     int device_count = unsigned_count;
859 
860     if (!cast(bool)(rd.devices_info = ALLOCATE!SoundIoDevicesInfo(1))) {
861         deinit_refresh_devices(&rd);
862         return SoundIoError.NoMem;
863     }
864     rd.devices_info.default_input_index = -1;
865     rd.devices_info.default_output_index = -1;
866 
867     for (int device_i = 0; device_i < device_count; device_i += 1) {
868         if (rd.mm_device) {
869             IMMDevice_Release(rd.mm_device);
870             rd.mm_device = null;
871         }
872         if (FAILED(hr = IMMDeviceCollection_Item(rd.collection, device_i, &rd.mm_device))) {
873             continue;
874         }
875         if (rd.lpwstr) {
876             CoTaskMemFree(rd.lpwstr);
877             rd.lpwstr = null;
878         }
879         if (FAILED(hr = IMMDevice_GetId(rd.mm_device, &rd.lpwstr))) {
880             continue;
881         }
882 
883 
884 
885         SoundIoDevicePrivate* dev_shared = ALLOCATE!SoundIoDevicePrivate(1);
886         if (!dev_shared) {
887             deinit_refresh_devices(&rd);
888             return SoundIoError.NoMem;
889         }
890         SoundIoDeviceWasapi* dev_w_shared = &dev_shared.backend_data.wasapi;
891         dev_shared.destruct = &destruct_device;
892         assert(!rd.device_shared);
893         rd.device_shared = &dev_shared.pub;
894         rd.device_shared.ref_count = 1;
895         rd.device_shared.soundio = soundio;
896         rd.device_shared.is_raw = false;
897         rd.device_shared.software_latency_max = 2.0;
898 
899         SoundIoDevicePrivate* dev_raw = ALLOCATE!SoundIoDevicePrivate(1);
900         if (!dev_raw) {
901             deinit_refresh_devices(&rd);
902             return SoundIoError.NoMem;
903         }
904         SoundIoDeviceWasapi* dev_w_raw = &dev_raw.backend_data.wasapi;
905         dev_raw.destruct = &destruct_device;
906         assert(!rd.device_raw);
907         rd.device_raw = &dev_raw.pub;
908         rd.device_raw.ref_count = 1;
909         rd.device_raw.soundio = soundio;
910         rd.device_raw.is_raw = true;
911         rd.device_raw.software_latency_max = 0.5;
912 
913         int device_id_len;
914         if (auto err = from_lpwstr(rd.lpwstr, &rd.device_shared.id, &device_id_len)) {
915             deinit_refresh_devices(&rd);
916             return err;
917         }
918 
919         rd.device_raw.id = soundio_str_dupe(rd.device_shared.id, device_id_len);
920         if (!rd.device_raw.id) {
921             deinit_refresh_devices(&rd);
922             return SoundIoError.NoMem;
923         }
924 
925         if (rd.endpoint) {
926             IMMEndpoint_Release(rd.endpoint);
927             rd.endpoint = null;
928         }
929         if (FAILED(hr = IMMDevice_QueryInterface(rd.mm_device, IID_IMMENDPOINT, cast(void**)&rd.endpoint))) {
930             rd.device_shared.probe_error = SoundIoError.OpeningDevice;
931             rd.device_raw.probe_error = SoundIoError.OpeningDevice;
932             rd.device_shared = null;
933             rd.device_raw = null;
934             continue;
935         }
936 
937         EDataFlow data_flow;
938         if (FAILED(hr = IMMEndpoint_GetDataFlow(rd.endpoint, &data_flow))) {
939             rd.device_shared.probe_error = SoundIoError.OpeningDevice;
940             rd.device_raw.probe_error = SoundIoError.OpeningDevice;
941             rd.device_shared = null;
942             rd.device_raw = null;
943             continue;
944         }
945 
946         rd.device_shared.aim = data_flow_to_aim(data_flow);
947         rd.device_raw.aim = rd.device_shared.aim;
948 
949         SoundIoListDevicePtr* device_list;
950         if (rd.device_shared.aim == SoundIoDeviceAim.Output) {
951             device_list = &rd.devices_info.output_devices;
952             if (soundio_streql(rd.device_shared.id, device_id_len,
953                         rd.default_render_id, rd.default_render_id_len))
954             {
955                 rd.devices_info.default_output_index = device_list.length;
956             }
957         } else {
958             assert(rd.device_shared.aim == SoundIoDeviceAim.Input);
959             device_list = &rd.devices_info.input_devices;
960             if (soundio_streql(rd.device_shared.id, device_id_len,
961                         rd.default_capture_id, rd.default_capture_id_len))
962             {
963                 rd.devices_info.default_input_index = device_list.length;
964             }
965         }
966 
967         if (auto err = device_list.append(rd.device_shared)) {
968             deinit_refresh_devices(&rd);
969             return err;
970         }
971         if (auto err = device_list.append(rd.device_raw)) {
972             deinit_refresh_devices(&rd);
973             return err;
974         }
975 
976         if (rd.audio_client) {
977             IUnknown_Release(rd.audio_client);
978             rd.audio_client = null;
979         }
980         if (FAILED(hr = IMMDevice_Activate(rd.mm_device, IID_IAUDIOCLIENT,
981                         CLSCTX_ALL, null, cast(void**)&rd.audio_client)))
982         {
983             rd.device_shared.probe_error = SoundIoError.OpeningDevice;
984             rd.device_raw.probe_error = SoundIoError.OpeningDevice;
985             rd.device_shared = null;
986             rd.device_raw = null;
987             continue;
988         }
989 
990         REFERENCE_TIME default_device_period;
991         REFERENCE_TIME min_device_period;
992         if (FAILED(hr = IAudioClient_GetDevicePeriod(rd.audio_client,
993                         &default_device_period, &min_device_period)))
994         {
995             rd.device_shared.probe_error = SoundIoError.OpeningDevice;
996             rd.device_raw.probe_error = SoundIoError.OpeningDevice;
997             rd.device_shared = null;
998             rd.device_raw = null;
999             continue;
1000         }
1001         dev_w_shared.period_duration = from_reference_time(default_device_period);
1002         rd.device_shared.software_latency_current = dev_w_shared.period_duration;
1003 
1004         dev_w_raw.period_duration = from_reference_time(min_device_period);
1005         rd.device_raw.software_latency_min = dev_w_raw.period_duration * 2;
1006 
1007         if (rd.prop_store) {
1008             IPropertyStore_Release(rd.prop_store);
1009             rd.prop_store = null;
1010         }
1011         if (FAILED(hr = IMMDevice_OpenPropertyStore(rd.mm_device, STGM_READ, &rd.prop_store))) {
1012             rd.device_shared.probe_error = SoundIoError.OpeningDevice;
1013             rd.device_raw.probe_error = SoundIoError.OpeningDevice;
1014             rd.device_shared = null;
1015             rd.device_raw = null;
1016             continue;
1017         }
1018 
1019         if (rd.prop_variant_value_inited) {
1020             PropVariantClear(&rd.prop_variant_value);
1021             rd.prop_variant_value_inited = false;
1022         }
1023         PropVariantInit(&rd.prop_variant_value);
1024         rd.prop_variant_value_inited = true;
1025         if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store,
1026                         PKEY_DEVICE_FRIENDLYNAME, &rd.prop_variant_value)))
1027         {
1028             rd.device_shared.probe_error = SoundIoError.OpeningDevice;
1029             rd.device_raw.probe_error = SoundIoError.OpeningDevice;
1030             rd.device_shared = null;
1031             rd.device_raw = null;
1032             continue;
1033         }
1034         if (!rd.prop_variant_value.pwszVal) {
1035             rd.device_shared.probe_error = SoundIoError.OpeningDevice;
1036             rd.device_raw.probe_error = SoundIoError.OpeningDevice;
1037             rd.device_shared = null;
1038             rd.device_raw = null;
1039             continue;
1040         }
1041         int device_name_len;
1042         if (auto err = from_lpwstr(rd.prop_variant_value.pwszVal, &rd.device_shared.name, &device_name_len)) {
1043             rd.device_shared.probe_error = err;
1044             rd.device_raw.probe_error = err;
1045             rd.device_shared = null;
1046             rd.device_raw = null;
1047             continue;
1048         }
1049 
1050         rd.device_raw.name = soundio_str_dupe(rd.device_shared.name, device_name_len);
1051         if (!rd.device_raw.name) {
1052             deinit_refresh_devices(&rd);
1053             return SoundIoError.NoMem;
1054         }
1055 
1056         // Get the format that WASAPI opens the device with for shared streams.
1057         // This is guaranteed to work, so we use this to modulate the sample
1058         // rate while holding the format constant and vice versa.
1059         if (rd.prop_variant_value_inited) {
1060             PropVariantClear(&rd.prop_variant_value);
1061             rd.prop_variant_value_inited = false;
1062         }
1063         PropVariantInit(&rd.prop_variant_value);
1064         rd.prop_variant_value_inited = true;
1065         if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store, PKEY_AUDIOENGINE_DEVICEFORMAT,
1066                         &rd.prop_variant_value)))
1067         {
1068             rd.device_shared.probe_error = SoundIoError.OpeningDevice;
1069             rd.device_raw.probe_error = SoundIoError.OpeningDevice;
1070             rd.device_shared = null;
1071             rd.device_raw = null;
1072             continue;
1073         }
1074         WAVEFORMATEXTENSIBLE* valid_wave_format = cast(WAVEFORMATEXTENSIBLE*)rd.prop_variant_value.blob.pBlobData;
1075         if (valid_wave_format.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
1076             rd.device_shared.probe_error = SoundIoError.OpeningDevice;
1077             rd.device_raw.probe_error = SoundIoError.OpeningDevice;
1078             rd.device_shared = null;
1079             rd.device_raw = null;
1080             continue;
1081         }
1082         if (auto err = detect_valid_sample_rates(&rd, valid_wave_format, dev_raw,
1083                         AUDCLNT_SHAREMODE_EXCLUSIVE))
1084         {
1085             rd.device_raw.probe_error = err;
1086             rd.device_raw = null;
1087         }
1088         if (rd.device_raw) if (auto err = detect_valid_formats(&rd, valid_wave_format, dev_raw,
1089                         AUDCLNT_SHAREMODE_EXCLUSIVE))
1090         {
1091             rd.device_raw.probe_error = err;
1092             rd.device_raw = null;
1093         }
1094         if (rd.device_raw) if (auto err = detect_valid_layouts(&rd, valid_wave_format, dev_raw,
1095             AUDCLNT_SHAREMODE_EXCLUSIVE))
1096         {
1097             rd.device_raw.probe_error = err;
1098             rd.device_raw = null;
1099         }
1100 
1101         if (rd.wave_format) {
1102             CoTaskMemFree(rd.wave_format);
1103             rd.wave_format = null;
1104         }
1105         if (FAILED(hr = IAudioClient_GetMixFormat(rd.audio_client, cast(WAVEFORMATEX**)&rd.wave_format))) {
1106             // According to MSDN GetMixFormat only applies to shared-mode devices.
1107             rd.device_shared.probe_error = SoundIoError.OpeningDevice;
1108             rd.device_shared = null;
1109         }
1110         else if(rd.wave_format && (rd.wave_format.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)) {
1111             rd.device_shared.probe_error = SoundIoError.OpeningDevice;
1112             rd.device_shared = null;
1113         }
1114 
1115         if(rd.device_shared) {
1116             rd.device_shared.sample_rate_current = rd.wave_format.Format.nSamplesPerSec;
1117             rd.device_shared.current_format = from_wave_format_format(rd.wave_format);
1118 
1119             if (rd.device_shared.aim == SoundIoDeviceAim.Output) {
1120                 // For output streams in shared mode,
1121                 // WASAPI performs resampling, so any value is valid.
1122                 // Let's pick some reasonable min and max values.
1123                 rd.device_shared.sample_rate_count = 1;
1124                 rd.device_shared.sample_rates = &dev_shared.prealloc_sample_rate_range;
1125                 rd.device_shared.sample_rates[0].min = soundio_int_min(SOUNDIO_MIN_SAMPLE_RATE,
1126                     rd.device_shared.sample_rate_current);
1127                 rd.device_shared.sample_rates[0].max = soundio_int_max(SOUNDIO_MAX_SAMPLE_RATE,
1128                     rd.device_shared.sample_rate_current);
1129             }
1130             else {
1131                 // Shared mode input stream: mix format is all we can do.
1132                 rd.device_shared.sample_rate_count = 1;
1133                 rd.device_shared.sample_rates = &dev_shared.prealloc_sample_rate_range;
1134                 rd.device_shared.sample_rates[0].min = rd.device_shared.sample_rate_current;
1135                 rd.device_shared.sample_rates[0].max = rd.device_shared.sample_rate_current;
1136             }
1137 
1138             if (auto err = detect_valid_formats(&rd, rd.wave_format, dev_shared,
1139                 AUDCLNT_SHAREMODE_SHARED))
1140             {
1141                 rd.device_shared.probe_error = err;
1142                 rd.device_shared = null;
1143             }
1144             else {
1145                 from_wave_format_layout(rd.wave_format, &rd.device_shared.current_layout);
1146                 rd.device_shared.layout_count = 1;
1147                 rd.device_shared.layouts = &rd.device_shared.current_layout;
1148             }
1149         }
1150 
1151         IMMDevice_AddRef(rd.mm_device);
1152         dev_w_shared.mm_device = rd.mm_device;
1153         dev_w_raw.mm_device = rd.mm_device;
1154         rd.mm_device = null;
1155 
1156         rd.device_shared = null;
1157         rd.device_raw = null;
1158     }
1159 
1160     soundio_os_mutex_lock(siw.mutex);
1161     soundio_destroy_devices_info(siw.ready_devices_info);
1162     siw.ready_devices_info = rd.devices_info;
1163     siw.have_devices_flag = true;
1164     soundio_os_cond_signal(siw.cond, siw.mutex);
1165     soundio.on_events_signal(soundio);
1166     soundio_os_mutex_unlock(siw.mutex);
1167 
1168     rd.devices_info = null;
1169     deinit_refresh_devices(&rd);
1170 
1171     return 0;
1172 }
1173 
1174 
1175 static void shutdown_backend(SoundIoPrivate* si, int err) {
1176     SoundIo* soundio = &si.pub;
1177     SoundIoWasapi* siw = &si.backend_data.wasapi;
1178     soundio_os_mutex_lock(siw.mutex);
1179     siw.shutdown_err = err;
1180     soundio_os_cond_signal(siw.cond, siw.mutex);
1181     soundio.on_events_signal(soundio);
1182     soundio_os_mutex_unlock(siw.mutex);
1183 }
1184 
1185 static void device_thread_run(void* arg) {
1186     SoundIoPrivate* si = cast(SoundIoPrivate*)arg;
1187     SoundIoWasapi* siw = &si.backend_data.wasapi;
1188     int err;
1189 
1190     HRESULT hr = CoCreateInstance(CLSID_MMDEVICEENUMERATOR, null,
1191             CLSCTX_ALL, IID_IMMDEVICEENUMERATOR, cast(void**)&siw.device_enumerator);
1192     if (FAILED(hr)) {
1193         shutdown_backend(si, SoundIoError.SystemResources);
1194         return;
1195     }
1196 
1197     if (FAILED(hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(
1198                     siw.device_enumerator, &siw.device_events)))
1199     {
1200         shutdown_backend(si, SoundIoError.SystemResources);
1201         return;
1202     }
1203 
1204     soundio_os_mutex_lock(siw.scan_devices_mutex);
1205     for (;;) {
1206         if (siw.abort_flag)
1207             break;
1208         if (siw.device_scan_queued) {
1209             siw.device_scan_queued = false;
1210             soundio_os_mutex_unlock(siw.scan_devices_mutex);
1211             err = refresh_devices(si);
1212             if (err) {
1213                 shutdown_backend(si, err);
1214                 return;
1215             }
1216             soundio_os_mutex_lock(siw.scan_devices_mutex);
1217             continue;
1218         }
1219         soundio_os_cond_wait(siw.scan_devices_cond, siw.scan_devices_mutex);
1220     }
1221     soundio_os_mutex_unlock(siw.scan_devices_mutex);
1222 
1223     IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(siw.device_enumerator, &siw.device_events);
1224     IMMDeviceEnumerator_Release(siw.device_enumerator);
1225     siw.device_enumerator = null;
1226 }
1227 
1228 private extern(D) void my_flush_events(SoundIoPrivate* si, bool wait) {
1229     SoundIo* soundio = &si.pub;
1230     SoundIoWasapi* siw = &si.backend_data.wasapi;
1231 
1232     bool change = false;
1233     bool cb_shutdown = false;
1234     SoundIoDevicesInfo* old_devices_info = null;
1235 
1236     soundio_os_mutex_lock(siw.mutex);
1237 
1238     // block until have devices
1239     while (wait || (!siw.have_devices_flag && !siw.shutdown_err)) {
1240         soundio_os_cond_wait(siw.cond, siw.mutex);
1241         wait = false;
1242     }
1243 
1244     if (siw.shutdown_err && !siw.emitted_shutdown_cb) {
1245         siw.emitted_shutdown_cb = true;
1246         cb_shutdown = true;
1247     } else if (siw.ready_devices_info) {
1248         old_devices_info = si.safe_devices_info;
1249         si.safe_devices_info = siw.ready_devices_info;
1250         siw.ready_devices_info = null;
1251         change = true;
1252     }
1253 
1254     soundio_os_mutex_unlock(siw.mutex);
1255 
1256     if (cb_shutdown)
1257         soundio.on_backend_disconnect(soundio, siw.shutdown_err);
1258     else if (change)
1259         soundio.on_devices_change(soundio);
1260 
1261     soundio_destroy_devices_info(old_devices_info);
1262 }
1263 
1264 static void flush_events_wasapi(SoundIoPrivate* si) {
1265     my_flush_events(si, false);
1266 }
1267 
1268 static void wait_events_wasapi(SoundIoPrivate* si) {
1269     my_flush_events(si, false);
1270     my_flush_events(si, true);
1271 }
1272 
1273 static void wakeup_wasapi(SoundIoPrivate* si) {
1274     SoundIoWasapi* siw = &si.backend_data.wasapi;
1275     soundio_os_cond_signal(siw.cond, siw.mutex);
1276 }
1277 
1278 static void force_device_scan_wasapi(SoundIoPrivate* si) {
1279     SoundIoWasapi* siw = &si.backend_data.wasapi;
1280     soundio_os_mutex_lock(siw.scan_devices_mutex);
1281     siw.device_scan_queued = true;
1282     soundio_os_cond_signal(siw.scan_devices_cond, siw.scan_devices_mutex);
1283     soundio_os_mutex_unlock(siw.scan_devices_mutex);
1284 }
1285 
1286 static void outstream_thread_deinit(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1287     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1288 
1289     if (osw.audio_volume_control)
1290         IUnknown_Release(osw.audio_volume_control);
1291     if (osw.audio_render_client)
1292         IUnknown_Release(osw.audio_render_client);
1293     if (osw.audio_session_control)
1294         IUnknown_Release(osw.audio_session_control);
1295     if (osw.audio_clock_adjustment)
1296         IUnknown_Release(osw.audio_clock_adjustment);
1297     if (osw.audio_client)
1298         IUnknown_Release(osw.audio_client);
1299 }
1300 
1301 static void outstream_destroy_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1302     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1303 
1304     if (osw.thread) {
1305         SOUNDIO_ATOMIC_FLAG_CLEAR(osw.thread_exit_flag);
1306         if (osw.h_event)
1307             SetEvent(osw.h_event);
1308 
1309         soundio_os_mutex_lock(osw.mutex);
1310         soundio_os_cond_signal(osw.cond, osw.mutex);
1311         soundio_os_cond_signal(osw.start_cond, osw.mutex);
1312         soundio_os_mutex_unlock(osw.mutex);
1313 
1314         soundio_os_thread_destroy(osw.thread);
1315 
1316         osw.thread = null;
1317     }
1318 
1319     if (osw.h_event) {
1320         CloseHandle(osw.h_event);
1321         osw.h_event = null;
1322     }
1323 
1324     free(osw.stream_name);
1325     osw.stream_name = null;
1326 
1327     soundio_os_cond_destroy(osw.cond);
1328     osw.cond = null;
1329 
1330     soundio_os_cond_destroy(osw.start_cond);
1331     osw.start_cond = null;
1332 
1333     soundio_os_mutex_destroy(osw.mutex);
1334     osw.mutex = null;
1335 }
1336 
1337 static int outstream_do_open(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1338     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1339     SoundIoOutStream* outstream = &os.pub;
1340     SoundIoDevice* device = outstream.device;
1341     SoundIoDevicePrivate* dev = cast(SoundIoDevicePrivate*)device;
1342     SoundIoDeviceWasapi* dw = &dev.backend_data.wasapi;
1343     HRESULT hr;
1344 
1345     if (FAILED(hr = IMMDevice_Activate(dw.mm_device, IID_IAUDIOCLIENT,
1346                     CLSCTX_ALL, null, cast(void**)&osw.audio_client)))
1347     {
1348         return SoundIoError.OpeningDevice;
1349     }
1350 
1351 
1352     AUDCLNT_SHAREMODE share_mode;
1353     DWORD flags;
1354     REFERENCE_TIME buffer_duration;
1355     REFERENCE_TIME periodicity;
1356     WAVEFORMATEXTENSIBLE wave_format = WAVEFORMATEXTENSIBLE.init; // TODO: equal to 0?
1357     wave_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
1358     wave_format.Format.cbSize = WAVEFORMATEXTENSIBLE.sizeof - WAVEFORMATEX.sizeof;
1359     if (osw.is_raw) {
1360         wave_format.Format.nSamplesPerSec = outstream.sample_rate;
1361         flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
1362         share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
1363         periodicity = to_reference_time(dw.period_duration);
1364         buffer_duration = periodicity;
1365     } else {
1366         WAVEFORMATEXTENSIBLE* mix_format;
1367         if (FAILED(hr = IAudioClient_GetMixFormat(osw.audio_client, cast(WAVEFORMATEX**)&mix_format))) {
1368             return SoundIoError.OpeningDevice;
1369         }
1370         wave_format.Format.nSamplesPerSec = cast(DWORD)outstream.sample_rate;
1371         osw.need_resample = (mix_format.Format.nSamplesPerSec != wave_format.Format.nSamplesPerSec);
1372         CoTaskMemFree(mix_format);
1373         mix_format = null;
1374         flags = osw.need_resample ? AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY : 0;
1375         share_mode = AUDCLNT_SHAREMODE_SHARED;
1376         periodicity = 0;
1377         buffer_duration = to_reference_time(4.0);
1378     }
1379     to_wave_format_layout(&outstream.layout, &wave_format);
1380     to_wave_format_format(outstream.format, &wave_format);
1381     complete_wave_format_data(&wave_format);
1382 
1383     if (FAILED(hr = IAudioClient_Initialize(osw.audio_client, share_mode, flags,
1384             buffer_duration, periodicity, cast(WAVEFORMATEX*)&wave_format, null)))
1385     {
1386         if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
1387             if (FAILED(hr = IAudioClient_GetBufferSize(osw.audio_client, &osw.buffer_frame_count))) {
1388                 return SoundIoError.OpeningDevice;
1389             }
1390             IUnknown_Release(osw.audio_client);
1391             osw.audio_client = null;
1392             if (FAILED(hr = IMMDevice_Activate(dw.mm_device, IID_IAUDIOCLIENT,
1393                             CLSCTX_ALL, null, cast(void**)&osw.audio_client)))
1394             {
1395                 return SoundIoError.OpeningDevice;
1396             }
1397             if (!osw.is_raw) {
1398                 WAVEFORMATEXTENSIBLE* mix_format;
1399                 if (FAILED(hr = IAudioClient_GetMixFormat(osw.audio_client, cast(WAVEFORMATEX**)&mix_format))) {
1400                     return SoundIoError.OpeningDevice;
1401                 }
1402                 wave_format.Format.nSamplesPerSec = cast(DWORD)outstream.sample_rate;
1403                 osw.need_resample = (mix_format.Format.nSamplesPerSec != wave_format.Format.nSamplesPerSec);
1404                 CoTaskMemFree(mix_format);
1405                 mix_format = null;
1406                 flags = osw.need_resample ? AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY : 0;
1407                 to_wave_format_layout(&outstream.layout, &wave_format);
1408                 to_wave_format_format(outstream.format, &wave_format);
1409                 complete_wave_format_data(&wave_format);
1410             }
1411 
1412             buffer_duration = to_reference_time(osw.buffer_frame_count / cast(double)outstream.sample_rate);
1413             if (osw.is_raw)
1414                 periodicity = buffer_duration;
1415             if (FAILED(hr = IAudioClient_Initialize(osw.audio_client, share_mode, flags,
1416                     buffer_duration, periodicity, cast(WAVEFORMATEX*)&wave_format, null)))
1417             {
1418                 if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
1419                     return SoundIoError.IncompatibleDevice;
1420                 } else if (hr == E_OUTOFMEMORY) {
1421                     return SoundIoError.NoMem;
1422                 } else {
1423                     return SoundIoError.OpeningDevice;
1424                 }
1425             }
1426         } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
1427             return SoundIoError.IncompatibleDevice;
1428         } else if (hr == E_OUTOFMEMORY) {
1429             return SoundIoError.NoMem;
1430         } else {
1431             return SoundIoError.OpeningDevice;
1432         }
1433     }
1434     REFERENCE_TIME max_latency_ref_time;
1435     if (FAILED(hr = IAudioClient_GetStreamLatency(osw.audio_client, &max_latency_ref_time))) {
1436         return SoundIoError.OpeningDevice;
1437     }
1438     double max_latency_sec = from_reference_time(max_latency_ref_time);
1439     osw.min_padding_frames = cast(int) ((max_latency_sec * outstream.sample_rate) + 0.5);
1440 
1441 
1442     if (FAILED(hr = IAudioClient_GetBufferSize(osw.audio_client, &osw.buffer_frame_count))) {
1443         return SoundIoError.OpeningDevice;
1444     }
1445     outstream.software_latency = osw.buffer_frame_count / cast(double)outstream.sample_rate;
1446 
1447     if (osw.is_raw) {
1448         if (FAILED(hr = IAudioClient_SetEventHandle(osw.audio_client, osw.h_event))) {
1449             return SoundIoError.OpeningDevice;
1450         }
1451     }
1452 
1453     if (outstream.name) {
1454         if (FAILED(hr = IAudioClient_GetService(osw.audio_client, IID_IAUDIOSESSIONCONTROL,
1455                         cast(void**)&osw.audio_session_control)))
1456         {
1457             return SoundIoError.OpeningDevice;
1458         }
1459 
1460         if (auto err = to_lpwstr(outstream.name, cast(int) strlen(outstream.name), &osw.stream_name)) {
1461             return err;
1462         }
1463         if (FAILED(hr = IAudioSessionControl_SetDisplayName(osw.audio_session_control,
1464                         osw.stream_name, null)))
1465         {
1466             return SoundIoError.OpeningDevice;
1467         }
1468     }
1469 
1470     if (FAILED(hr = IAudioClient_GetService(osw.audio_client, IID_IAUDIORENDERCLIENT,
1471                     cast(void**)&osw.audio_render_client)))
1472     {
1473         return SoundIoError.OpeningDevice;
1474     }
1475 
1476     if (FAILED(hr = IAudioClient_GetService(osw.audio_client, IID_ISIMPLEAUDIOVOLUME,
1477                     cast(void**)&osw.audio_volume_control)))
1478     {
1479         return SoundIoError.OpeningDevice;
1480     }
1481 
1482     if (FAILED(hr = osw.audio_volume_control.lpVtbl.GetMasterVolume(osw.audio_volume_control, &outstream.volume)))
1483     {
1484         return SoundIoError.OpeningDevice;
1485     }
1486 
1487     return 0;
1488 }
1489 
1490 static void outstream_shared_run(SoundIoOutStreamPrivate* os) {
1491     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1492     SoundIoOutStream* outstream = &os.pub;
1493 
1494     HRESULT hr;
1495 
1496     UINT32 frames_used;
1497     if (FAILED(hr = IAudioClient_GetCurrentPadding(osw.audio_client, &frames_used))) {
1498         outstream.error_callback(outstream, SoundIoError.Streaming);
1499         return;
1500     }
1501     osw.writable_frame_count = osw.buffer_frame_count - frames_used;
1502     if (osw.writable_frame_count <= 0) {
1503         outstream.error_callback(outstream, SoundIoError.Streaming);
1504         return;
1505     }
1506     int frame_count_min = soundio_int_max(0, cast(int)osw.min_padding_frames - cast(int)frames_used);
1507     outstream.write_callback(outstream, frame_count_min, osw.writable_frame_count);
1508 
1509     if (FAILED(hr = IAudioClient_Start(osw.audio_client))) {
1510         outstream.error_callback(outstream, SoundIoError.Streaming);
1511         return;
1512     }
1513 
1514     for (;;) {
1515         if (FAILED(hr = IAudioClient_GetCurrentPadding(osw.audio_client, &frames_used))) {
1516             outstream.error_callback(outstream, SoundIoError.Streaming);
1517             return;
1518         }
1519         osw.writable_frame_count = osw.buffer_frame_count - frames_used;
1520         double time_until_underrun = frames_used / cast(double)outstream.sample_rate;
1521         double wait_time = time_until_underrun / 2.0;
1522         soundio_os_mutex_lock(osw.mutex);
1523         soundio_os_cond_timed_wait(osw.cond, osw.mutex, wait_time);
1524         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.thread_exit_flag)) {
1525             soundio_os_mutex_unlock(osw.mutex);
1526             return;
1527         }
1528         soundio_os_mutex_unlock(osw.mutex);
1529         bool reset_buffer = false;
1530         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.clear_buffer_flag)) {
1531             if (!osw.is_paused) {
1532                 if (FAILED(hr = IAudioClient_Stop(osw.audio_client))) {
1533                     outstream.error_callback(outstream, SoundIoError.Streaming);
1534                     return;
1535                 }
1536                 osw.is_paused = true;
1537             }
1538             if (FAILED(hr = IAudioClient_Reset(osw.audio_client))) {
1539                 outstream.error_callback(outstream, SoundIoError.Streaming);
1540                 return;
1541             }
1542             SOUNDIO_ATOMIC_FLAG_CLEAR(osw.pause_resume_flag);
1543             reset_buffer = true;
1544         }
1545         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.pause_resume_flag)) {
1546             bool pause = SOUNDIO_ATOMIC_LOAD(osw.desired_pause_state);
1547             if (pause && !osw.is_paused) {
1548                 if (FAILED(hr = IAudioClient_Stop(osw.audio_client))) {
1549                     outstream.error_callback(outstream, SoundIoError.Streaming);
1550                     return;
1551                 }
1552                 osw.is_paused = true;
1553             } else if (!pause && osw.is_paused) {
1554                 if (FAILED(hr = IAudioClient_Start(osw.audio_client))) {
1555                     outstream.error_callback(outstream, SoundIoError.Streaming);
1556                     return;
1557                 }
1558                 osw.is_paused = false;
1559             }
1560         }
1561 
1562         if (FAILED(hr = IAudioClient_GetCurrentPadding(osw.audio_client, &frames_used))) {
1563             outstream.error_callback(outstream, SoundIoError.Streaming);
1564             return;
1565         }
1566         osw.writable_frame_count = osw.buffer_frame_count - frames_used;
1567         if (osw.writable_frame_count > 0) {
1568             if (frames_used == 0 && !reset_buffer)
1569                 outstream.underflow_callback(outstream);
1570             int frame_count_min1 = soundio_int_max(0, cast(int)osw.min_padding_frames - cast(int)frames_used);
1571             outstream.write_callback(outstream, frame_count_min1, osw.writable_frame_count);
1572         }
1573     }
1574 }
1575 
1576 static void outstream_raw_run(SoundIoOutStreamPrivate* os) {
1577     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1578     SoundIoOutStream* outstream = &os.pub;
1579 
1580     HRESULT hr;
1581 
1582     outstream.write_callback(outstream, osw.buffer_frame_count, osw.buffer_frame_count);
1583 
1584     if (FAILED(hr = IAudioClient_Start(osw.audio_client))) {
1585         outstream.error_callback(outstream, SoundIoError.Streaming);
1586         return;
1587     }
1588 
1589     for (;;) {
1590         WaitForSingleObject(osw.h_event, INFINITE);
1591         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.thread_exit_flag))
1592             return;
1593         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.pause_resume_flag)) {
1594             bool pause = SOUNDIO_ATOMIC_LOAD(osw.desired_pause_state);
1595             if (pause && !osw.is_paused) {
1596                 if (FAILED(hr = IAudioClient_Stop(osw.audio_client))) {
1597                     outstream.error_callback(outstream, SoundIoError.Streaming);
1598                     return;
1599                 }
1600                 osw.is_paused = true;
1601             } else if (!pause && osw.is_paused) {
1602                 if (FAILED(hr = IAudioClient_Start(osw.audio_client))) {
1603                     outstream.error_callback(outstream, SoundIoError.Streaming);
1604                     return;
1605                 }
1606                 osw.is_paused = false;
1607             }
1608         }
1609 
1610         outstream.write_callback(outstream, osw.buffer_frame_count, osw.buffer_frame_count);
1611     }
1612 }
1613 
1614 static void outstream_thread_run(void* arg) {
1615     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)arg;
1616     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1617     SoundIoOutStream* outstream = &os.pub;
1618     SoundIoDevice* device = outstream.device;
1619     SoundIo* soundio = device.soundio;
1620     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
1621 
1622     if (auto err = outstream_do_open(si, os)) {
1623         outstream_thread_deinit(si, os);
1624 
1625         soundio_os_mutex_lock(osw.mutex);
1626         osw.open_err = err;
1627         osw.open_complete = true;
1628         soundio_os_cond_signal(osw.cond, osw.mutex);
1629         soundio_os_mutex_unlock(osw.mutex);
1630         return;
1631     }
1632 
1633     soundio_os_mutex_lock(osw.mutex);
1634     osw.open_complete = true;
1635     soundio_os_cond_signal(osw.cond, osw.mutex);
1636     for (;;) {
1637         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.thread_exit_flag)) {
1638             soundio_os_mutex_unlock(osw.mutex);
1639             return;
1640         }
1641         if (osw.started) {
1642             soundio_os_mutex_unlock(osw.mutex);
1643             break;
1644         }
1645         soundio_os_cond_wait(osw.start_cond, osw.mutex);
1646     }
1647 
1648     if (osw.is_raw)
1649         outstream_raw_run(os);
1650     else
1651         outstream_shared_run(os);
1652 
1653     outstream_thread_deinit(si, os);
1654 }
1655 
1656 static int outstream_open_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1657     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1658     SoundIoOutStream* outstream = &os.pub;
1659     SoundIoDevice* device = outstream.device;
1660     SoundIo* soundio = &si.pub;
1661 
1662     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.pause_resume_flag);
1663     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.clear_buffer_flag);
1664     SOUNDIO_ATOMIC_STORE(osw.desired_pause_state, false);
1665 
1666     // All the COM functions are supposed to be called from the same thread. libsoundio API does not
1667     // restrict the calling thread context in this way. Furthermore, the user might have called
1668     // CoInitializeEx with a different threading model than Single Threaded Apartment.
1669     // So we create a thread to do all the initialization and teardown, and communicate state
1670     // via conditions and signals. The thread for initialization and teardown is also used
1671     // for the realtime code calls the user write_callback.
1672 
1673     osw.is_raw = device.is_raw;
1674 
1675     if (!cast(bool)(osw.cond = soundio_os_cond_create())) {
1676         outstream_destroy_wasapi(si, os);
1677         return SoundIoError.NoMem;
1678     }
1679 
1680     if (!cast(bool)(osw.start_cond = soundio_os_cond_create())) {
1681         outstream_destroy_wasapi(si, os);
1682         return SoundIoError.NoMem;
1683     }
1684 
1685     if (!cast(bool)(osw.mutex = soundio_os_mutex_create())) {
1686         outstream_destroy_wasapi(si, os);
1687         return SoundIoError.NoMem;
1688     }
1689 
1690     if (osw.is_raw) {
1691         osw.h_event = CreateEvent(null, FALSE, FALSE, null);
1692         if (!osw.h_event) {
1693             outstream_destroy_wasapi(si, os);
1694             return SoundIoError.OpeningDevice;
1695         }
1696     }
1697 
1698     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.thread_exit_flag);
1699     if (auto err = soundio_os_thread_create(&outstream_thread_run, os,
1700                     soundio.emit_rtprio_warning, &osw.thread))
1701     {
1702         outstream_destroy_wasapi(si, os);
1703         return err;
1704     }
1705 
1706     soundio_os_mutex_lock(osw.mutex);
1707     while (!osw.open_complete)
1708         soundio_os_cond_wait(osw.cond, osw.mutex);
1709     soundio_os_mutex_unlock(osw.mutex);
1710 
1711     if (osw.open_err) {
1712         outstream_destroy_wasapi(si, os);
1713         return osw.open_err;
1714     }
1715 
1716     return 0;
1717 }
1718 
1719 static int outstream_pause_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, bool pause) {
1720     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1721 
1722     SOUNDIO_ATOMIC_STORE(osw.desired_pause_state, pause);
1723     SOUNDIO_ATOMIC_FLAG_CLEAR(osw.pause_resume_flag);
1724     if (osw.h_event) {
1725         SetEvent(osw.h_event);
1726     } else {
1727         soundio_os_mutex_lock(osw.mutex);
1728         soundio_os_cond_signal(osw.cond, osw.mutex);
1729         soundio_os_mutex_unlock(osw.mutex);
1730     }
1731 
1732     return 0;
1733 }
1734 
1735 static int outstream_start_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1736     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1737 
1738     soundio_os_mutex_lock(osw.mutex);
1739     osw.started = true;
1740     soundio_os_cond_signal(osw.start_cond, osw.mutex);
1741     soundio_os_mutex_unlock(osw.mutex);
1742 
1743     return 0;
1744 }
1745 
1746 static int outstream_begin_write_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, SoundIoChannelArea** out_areas, int* frame_count) {
1747     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1748     SoundIoOutStream* outstream = &os.pub;
1749     HRESULT hr;
1750 
1751     osw.write_frame_count = *frame_count;
1752 
1753 
1754     char* data;
1755     if (FAILED(hr = IAudioRenderClient_GetBuffer(osw.audio_render_client,
1756                     osw.write_frame_count, cast(BYTE**)&data)))
1757     {
1758         return SoundIoError.Streaming;
1759     }
1760 
1761     for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
1762         osw.areas[ch].ptr = data + ch * outstream.bytes_per_sample;
1763         osw.areas[ch].step = outstream.bytes_per_frame;
1764     }
1765 
1766     *out_areas = osw.areas.ptr;
1767 
1768     return 0;
1769 }
1770 
1771 static int outstream_end_write_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1772     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1773     HRESULT hr;
1774     if (FAILED(hr = IAudioRenderClient_ReleaseBuffer(osw.audio_render_client, osw.write_frame_count, 0))) {
1775         return SoundIoError.Streaming;
1776     }
1777     return 0;
1778 }
1779 
1780 static int outstream_clear_buffer_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1781     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1782 
1783     if (osw.h_event) {
1784         return SoundIoError.IncompatibleDevice;
1785     } else {
1786         SOUNDIO_ATOMIC_FLAG_CLEAR(osw.clear_buffer_flag);
1787         soundio_os_mutex_lock(osw.mutex);
1788         soundio_os_cond_signal(osw.cond, osw.mutex);
1789         soundio_os_mutex_unlock(osw.mutex);
1790     }
1791 
1792     return 0;
1793 }
1794 
1795 static int outstream_get_latency_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, double* out_latency) {
1796     SoundIoOutStream* outstream = &os.pub;
1797     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1798 
1799     HRESULT hr;
1800     UINT32 frames_used;
1801     if (FAILED(hr = IAudioClient_GetCurrentPadding(osw.audio_client, &frames_used))) {
1802         return SoundIoError.Streaming;
1803     }
1804 
1805     *out_latency = frames_used / cast(double)outstream.sample_rate;
1806     return 0;
1807 }
1808 
1809 static int outstream_set_volume_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, float volume) {
1810     SoundIoOutStream* outstream = &os.pub;
1811     SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi;
1812 
1813     HRESULT hr;
1814     if (FAILED(hr = osw.audio_volume_control.lpVtbl.SetMasterVolume(osw.audio_volume_control, volume, null)))
1815     {
1816         return SoundIoError.IncompatibleDevice;
1817     }
1818 
1819     outstream.volume = volume;
1820     return 0;
1821 }
1822 
1823 static void instream_thread_deinit(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1824     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
1825 
1826     if (isw.audio_capture_client)
1827         IUnknown_Release(isw.audio_capture_client);
1828     if (isw.audio_client)
1829         IUnknown_Release(isw.audio_client);
1830 }
1831 
1832 
1833 static void instream_destroy_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1834     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
1835 
1836     if (isw.thread) {
1837         SOUNDIO_ATOMIC_FLAG_CLEAR(isw.thread_exit_flag);
1838         if (isw.h_event)
1839             SetEvent(isw.h_event);
1840 
1841         soundio_os_mutex_lock(isw.mutex);
1842         soundio_os_cond_signal(isw.cond, isw.mutex);
1843         soundio_os_cond_signal(isw.start_cond, isw.mutex);
1844         soundio_os_mutex_unlock(isw.mutex);
1845         soundio_os_thread_destroy(isw.thread);
1846 
1847         isw.thread = null;
1848     }
1849 
1850     if (isw.h_event) {
1851         CloseHandle(isw.h_event);
1852         isw.h_event = null;
1853     }
1854 
1855     soundio_os_cond_destroy(isw.cond);
1856     isw.cond = null;
1857 
1858     soundio_os_cond_destroy(isw.start_cond);
1859     isw.start_cond = null;
1860 
1861     soundio_os_mutex_destroy(isw.mutex);
1862     isw.mutex = null;
1863 }
1864 
1865 static int instream_do_open(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1866     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
1867     SoundIoInStream* instream = &is_.pub;
1868     SoundIoDevice* device = instream.device;
1869     SoundIoDevicePrivate* dev = cast(SoundIoDevicePrivate*)device;
1870     SoundIoDeviceWasapi* dw = &dev.backend_data.wasapi;
1871     HRESULT hr;
1872 
1873     if (FAILED(hr = IMMDevice_Activate(dw.mm_device, IID_IAUDIOCLIENT,
1874                     CLSCTX_ALL, null, cast(void**)&isw.audio_client)))
1875     {
1876         return SoundIoError.OpeningDevice;
1877     }
1878 
1879     AUDCLNT_SHAREMODE share_mode;
1880     DWORD flags;
1881     REFERENCE_TIME buffer_duration;
1882     REFERENCE_TIME periodicity;
1883     WAVEFORMATEXTENSIBLE wave_format = WAVEFORMATEXTENSIBLE.init; // todo: zero?
1884     wave_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
1885     wave_format.Format.cbSize = WAVEFORMATEXTENSIBLE.sizeof - WAVEFORMATEX.sizeof;
1886     if (isw.is_raw) {
1887         wave_format.Format.nSamplesPerSec = instream.sample_rate;
1888         flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
1889         share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
1890         periodicity = to_reference_time(dw.period_duration);
1891         buffer_duration = periodicity;
1892     } else {
1893         WAVEFORMATEXTENSIBLE* mix_format;
1894         if (FAILED(hr = IAudioClient_GetMixFormat(isw.audio_client, cast(WAVEFORMATEX**)&mix_format))) {
1895             return SoundIoError.OpeningDevice;
1896         }
1897         wave_format.Format.nSamplesPerSec = mix_format.Format.nSamplesPerSec;
1898         CoTaskMemFree(mix_format);
1899         mix_format = null;
1900         if (wave_format.Format.nSamplesPerSec != cast(DWORD)instream.sample_rate) {
1901             return SoundIoError.IncompatibleDevice;
1902         }
1903         flags = 0;
1904         share_mode = AUDCLNT_SHAREMODE_SHARED;
1905         periodicity = 0;
1906         buffer_duration = to_reference_time(4.0);
1907     }
1908     to_wave_format_layout(&instream.layout, &wave_format);
1909     to_wave_format_format(instream.format, &wave_format);
1910     complete_wave_format_data(&wave_format);
1911 
1912     if (FAILED(hr = IAudioClient_Initialize(isw.audio_client, share_mode, flags,
1913             buffer_duration, periodicity, cast(WAVEFORMATEX*)&wave_format, null)))
1914     {
1915         if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
1916             if (FAILED(hr = IAudioClient_GetBufferSize(isw.audio_client, &isw.buffer_frame_count))) {
1917                 return SoundIoError.OpeningDevice;
1918             }
1919             IUnknown_Release(isw.audio_client);
1920             isw.audio_client = null;
1921             if (FAILED(hr = IMMDevice_Activate(dw.mm_device, IID_IAUDIOCLIENT,
1922                             CLSCTX_ALL, null, cast(void**)&isw.audio_client)))
1923             {
1924                 return SoundIoError.OpeningDevice;
1925             }
1926             if (!isw.is_raw) {
1927                 WAVEFORMATEXTENSIBLE* mix_format;
1928                 if (FAILED(hr = IAudioClient_GetMixFormat(isw.audio_client, cast(WAVEFORMATEX**)&mix_format))) {
1929                     return SoundIoError.OpeningDevice;
1930                 }
1931                 wave_format.Format.nSamplesPerSec = mix_format.Format.nSamplesPerSec;
1932                 CoTaskMemFree(mix_format);
1933                 mix_format = null;
1934                 flags = 0;
1935                 to_wave_format_layout(&instream.layout, &wave_format);
1936                 to_wave_format_format(instream.format, &wave_format);
1937                 complete_wave_format_data(&wave_format);
1938             }
1939 
1940             buffer_duration = to_reference_time(isw.buffer_frame_count / cast(double)instream.sample_rate);
1941             if (isw.is_raw)
1942                 periodicity = buffer_duration;
1943             if (FAILED(hr = IAudioClient_Initialize(isw.audio_client, share_mode, flags,
1944                     buffer_duration, periodicity, cast(WAVEFORMATEX*)&wave_format, null)))
1945             {
1946                 if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
1947                     return SoundIoError.IncompatibleDevice;
1948                 } else if (hr == E_OUTOFMEMORY) {
1949                     return SoundIoError.NoMem;
1950                 } else {
1951                     return SoundIoError.OpeningDevice;
1952                 }
1953             }
1954         } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
1955             return SoundIoError.IncompatibleDevice;
1956         } else if (hr == E_OUTOFMEMORY) {
1957             return SoundIoError.NoMem;
1958         } else {
1959             return SoundIoError.OpeningDevice;
1960         }
1961     }
1962     if (FAILED(hr = IAudioClient_GetBufferSize(isw.audio_client, &isw.buffer_frame_count))) {
1963         return SoundIoError.OpeningDevice;
1964     }
1965     if (instream.software_latency == 0.0)
1966         instream.software_latency = 1.0;
1967     instream.software_latency = soundio_double_clamp(device.software_latency_min,
1968             instream.software_latency, device.software_latency_max);
1969     if (isw.is_raw)
1970         instream.software_latency = isw.buffer_frame_count / cast(double)instream.sample_rate;
1971 
1972     if (isw.is_raw) {
1973         if (FAILED(hr = IAudioClient_SetEventHandle(isw.audio_client, isw.h_event))) {
1974             return SoundIoError.OpeningDevice;
1975         }
1976     }
1977 
1978     if (instream.name) {
1979         if (FAILED(hr = IAudioClient_GetService(isw.audio_client, IID_IAUDIOSESSIONCONTROL,
1980                         cast(void**)&isw.audio_session_control)))
1981         {
1982             return SoundIoError.OpeningDevice;
1983         }
1984 
1985         if (auto err = to_lpwstr(instream.name, cast(int) strlen(instream.name), &isw.stream_name)) {
1986             return err;
1987         }
1988         if (FAILED(hr = IAudioSessionControl_SetDisplayName(isw.audio_session_control,
1989                         isw.stream_name, null)))
1990         {
1991             return SoundIoError.OpeningDevice;
1992         }
1993     }
1994 
1995     if (FAILED(hr = IAudioClient_GetService(isw.audio_client, IID_IAUDIOCAPTURECLIENT,
1996                     cast(void**)&isw.audio_capture_client)))
1997     {
1998         return SoundIoError.OpeningDevice;
1999     }
2000 
2001     return 0;
2002 }
2003 
2004 static void instream_raw_run(SoundIoInStreamPrivate* is_) {
2005     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
2006     SoundIoInStream* instream = &is_.pub;
2007 
2008     HRESULT hr;
2009 
2010     if (FAILED(hr = IAudioClient_Start(isw.audio_client))) {
2011         instream.error_callback(instream, SoundIoError.Streaming);
2012         return;
2013     }
2014 
2015     for (;;) {
2016         WaitForSingleObject(isw.h_event, INFINITE);
2017         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isw.thread_exit_flag))
2018             return;
2019 
2020         instream.read_callback(instream, isw.buffer_frame_count, isw.buffer_frame_count);
2021     }
2022 }
2023 
2024 static void instream_shared_run(SoundIoInStreamPrivate* is_) {
2025     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
2026     SoundIoInStream* instream = &is_.pub;
2027 
2028     HRESULT hr;
2029 
2030     if (FAILED(hr = IAudioClient_Start(isw.audio_client))) {
2031         instream.error_callback(instream, SoundIoError.Streaming);
2032         return;
2033     }
2034 
2035     for (;;) {
2036         soundio_os_mutex_lock(isw.mutex);
2037         soundio_os_cond_timed_wait(isw.cond, isw.mutex, instream.software_latency / 2.0);
2038         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isw.thread_exit_flag)) {
2039             soundio_os_mutex_unlock(isw.mutex);
2040             return;
2041         }
2042         soundio_os_mutex_unlock(isw.mutex);
2043 
2044         UINT32 frames_available;
2045         if (FAILED(hr = IAudioClient_GetCurrentPadding(isw.audio_client, &frames_available))) {
2046             instream.error_callback(instream, SoundIoError.Streaming);
2047             return;
2048         }
2049 
2050         isw.readable_frame_count = frames_available;
2051         if (isw.readable_frame_count > 0)
2052             instream.read_callback(instream, 0, isw.readable_frame_count);
2053     }
2054 }
2055 
2056 static void instream_thread_run(void* arg) {
2057     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)arg;
2058     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
2059     SoundIoInStream* instream = &is_.pub;
2060     SoundIoDevice* device = instream.device;
2061     SoundIo* soundio = device.soundio;
2062     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
2063 
2064     if (auto err = instream_do_open(si, is_)) {
2065         instream_thread_deinit(si, is_);
2066 
2067         soundio_os_mutex_lock(isw.mutex);
2068         isw.open_err = err;
2069         isw.open_complete = true;
2070         soundio_os_cond_signal(isw.cond, isw.mutex);
2071         soundio_os_mutex_unlock(isw.mutex);
2072         return;
2073     }
2074 
2075     soundio_os_mutex_lock(isw.mutex);
2076     isw.open_complete = true;
2077     soundio_os_cond_signal(isw.cond, isw.mutex);
2078     for (;;) {
2079         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isw.thread_exit_flag)) {
2080             soundio_os_mutex_unlock(isw.mutex);
2081             return;
2082         }
2083         if (isw.started) {
2084             soundio_os_mutex_unlock(isw.mutex);
2085             break;
2086         }
2087         soundio_os_cond_wait(isw.start_cond, isw.mutex);
2088     }
2089 
2090     if (isw.is_raw)
2091         instream_raw_run(is_);
2092     else
2093         instream_shared_run(is_);
2094 
2095     instream_thread_deinit(si, is_);
2096 }
2097 
2098 static int instream_open_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
2099     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
2100     SoundIoInStream* instream = &is_.pub;
2101     SoundIoDevice* device = instream.device;
2102     SoundIo* soundio = &si.pub;
2103 
2104     // All the COM functions are supposed to be called from the same thread. libsoundio API does not
2105     // restrict the calling thread context in this way. Furthermore, the user might have called
2106     // CoInitializeEx with a different threading model than Single Threaded Apartment.
2107     // So we create a thread to do all the initialization and teardown, and communicate state
2108     // via conditions and signals. The thread for initialization and teardown is also used
2109     // for the realtime code calls the user write_callback.
2110 
2111     isw.is_raw = device.is_raw;
2112 
2113     isw.cond = soundio_os_cond_create();
2114     if (!isw.cond) {
2115         instream_destroy_wasapi(si, is_);
2116         return SoundIoError.NoMem;
2117     }
2118 
2119     isw.start_cond = soundio_os_cond_create();
2120     if (!isw.start_cond) {
2121         instream_destroy_wasapi(si, is_);
2122         return SoundIoError.NoMem;
2123     }
2124 
2125     isw.mutex = soundio_os_mutex_create();
2126     if (!isw.mutex) {
2127         instream_destroy_wasapi(si, is_);
2128         return SoundIoError.NoMem;
2129     }
2130 
2131     if (isw.is_raw) {
2132         isw.h_event = CreateEvent(null, FALSE, FALSE, null);
2133         if (!isw.h_event) {
2134             instream_destroy_wasapi(si, is_);
2135             return SoundIoError.OpeningDevice;
2136         }
2137     }
2138 
2139     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isw.thread_exit_flag);
2140     if (auto err = soundio_os_thread_create(&instream_thread_run, is_,
2141                     soundio.emit_rtprio_warning, &isw.thread))
2142     {
2143         instream_destroy_wasapi(si, is_);
2144         return err;
2145     }
2146 
2147     soundio_os_mutex_lock(isw.mutex);
2148     while (!isw.open_complete)
2149         soundio_os_cond_wait(isw.cond, isw.mutex);
2150     soundio_os_mutex_unlock(isw.mutex);
2151 
2152     if (isw.open_err) {
2153         instream_destroy_wasapi(si, is_);
2154         return isw.open_err;
2155     }
2156 
2157     return 0;
2158 }
2159 
2160 static int instream_pause_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, bool pause) {
2161     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
2162     HRESULT hr;
2163     if (pause && !isw.is_paused) {
2164         if (FAILED(hr = IAudioClient_Stop(isw.audio_client)))
2165             return SoundIoError.Streaming;
2166         isw.is_paused = true;
2167     } else if (!pause && isw.is_paused) {
2168         if (FAILED(hr = IAudioClient_Start(isw.audio_client)))
2169             return SoundIoError.Streaming;
2170         isw.is_paused = false;
2171     }
2172     return 0;
2173 }
2174 
2175 static int instream_start_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
2176     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
2177 
2178     soundio_os_mutex_lock(isw.mutex);
2179     isw.started = true;
2180     soundio_os_cond_signal(isw.start_cond, isw.mutex);
2181     soundio_os_mutex_unlock(isw.mutex);
2182 
2183     return 0;
2184 }
2185 
2186 static int instream_begin_read_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, SoundIoChannelArea** out_areas, int* frame_count) {
2187     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
2188     SoundIoInStream* instream = &is_.pub;
2189     HRESULT hr;
2190 
2191     if (isw.read_buf_frames_left <= 0) {
2192         UINT32 frames_to_read;
2193         DWORD flags;
2194         if (FAILED(hr = IAudioCaptureClient_GetBuffer(isw.audio_capture_client,
2195                         cast(BYTE**)&isw.read_buf, &frames_to_read, &flags, null, null)))
2196         {
2197             return SoundIoError.Streaming;
2198         }
2199 		isw.opened_buf_frames = frames_to_read;
2200 		isw.read_buf_frames_left = frames_to_read;
2201 
2202         if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
2203             isw.read_buf = null;
2204     }
2205 
2206     isw.read_frame_count = soundio_int_min(*frame_count, isw.read_buf_frames_left);
2207     *frame_count = isw.read_frame_count;
2208 
2209     if (isw.read_buf) {
2210         for (int ch = 0; ch < instream.layout.channel_count; ch += 1) {
2211             isw.areas[ch].ptr = isw.read_buf + ch * instream.bytes_per_sample;
2212             isw.areas[ch].step = instream.bytes_per_frame;
2213 
2214 			isw.areas[ch].ptr += instream.bytes_per_frame * (isw.opened_buf_frames - isw.read_buf_frames_left);
2215         }
2216 
2217         *out_areas = isw.areas.ptr;
2218     } else {
2219         *out_areas = null;
2220     }
2221 
2222     return 0;
2223 }
2224 
2225 static int instream_end_read_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
2226     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
2227     HRESULT hr;
2228 
2229 	isw.read_buf_frames_left -= isw.read_frame_count;
2230 
2231 	if (isw.read_buf_frames_left <= 0) {
2232 		if (FAILED(hr = IAudioCaptureClient_ReleaseBuffer(isw.audio_capture_client, isw.opened_buf_frames))) {
2233 			return SoundIoError.Streaming;
2234 		}
2235 	}
2236 
2237     return 0;
2238 }
2239 
2240 static int instream_get_latency_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, double* out_latency) {
2241     SoundIoInStream* instream = &is_.pub;
2242     SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi;
2243 
2244     HRESULT hr;
2245     UINT32 frames_used;
2246     if (FAILED(hr = IAudioClient_GetCurrentPadding(isw.audio_client, &frames_used))) {
2247         return SoundIoError.Streaming;
2248     }
2249 
2250     *out_latency = frames_used / cast(double)instream.sample_rate;
2251     return 0;
2252 }
2253 
2254 
2255 static void destroy_wasapi(SoundIoPrivate* si) {
2256     SoundIoWasapi* siw = &si.backend_data.wasapi;
2257 
2258     if (siw.thread) {
2259         soundio_os_mutex_lock(siw.scan_devices_mutex);
2260         siw.abort_flag = true;
2261         soundio_os_cond_signal(siw.scan_devices_cond, siw.scan_devices_mutex);
2262         soundio_os_mutex_unlock(siw.scan_devices_mutex);
2263         soundio_os_thread_destroy(siw.thread);
2264     }
2265 
2266     if (siw.cond)
2267         soundio_os_cond_destroy(siw.cond);
2268 
2269     if (siw.scan_devices_cond)
2270         soundio_os_cond_destroy(siw.scan_devices_cond);
2271 
2272     if (siw.scan_devices_mutex)
2273         soundio_os_mutex_destroy(siw.scan_devices_mutex);
2274 
2275     if (siw.mutex)
2276         soundio_os_mutex_destroy(siw.mutex);
2277 
2278     soundio_destroy_devices_info(siw.ready_devices_info);
2279 }
2280 
2281 pragma(inline, true) static shared(SoundIoPrivate)* soundio_MMNotificationClient_si(IMMNotificationClient* client) {
2282     auto siw = cast(SoundIoWasapi*)((cast(ubyte*)client) - SoundIoWasapi.device_events.offsetof);
2283     auto si = cast(shared(SoundIoPrivate)*)((cast(ubyte*)siw)   - SoundIoPrivate.backend_data.offsetof);
2284     return si;
2285 }
2286 
2287 static extern(Windows) HRESULT soundio_MMNotificationClient_QueryInterface(IMMNotificationClient* client, REFIID riid, void** ppv) {
2288     if (IS_EQUAL_IID(riid, &IID_IUnknown) || IS_EQUAL_IID(riid, &IID_IMMNotificationClient)) {
2289         *ppv = client;
2290         IUnknown_AddRef(client);
2291         return S_OK;
2292     } else {
2293        *ppv = null;
2294         return E_NOINTERFACE;
2295     }
2296 }
2297 
2298 static extern(Windows) ULONG soundio_MMNotificationClient_AddRef(IMMNotificationClient* client) {
2299     shared SoundIoPrivate* si = soundio_MMNotificationClient_si(client);
2300     shared SoundIoWasapi* siw = &si.backend_data.wasapi;
2301     // workaround because `InterlockedIncrement` is missing in D's Kernel32 import library
2302     return atomicOp!"+="(siw.device_events_refs, 1);
2303     //return InterlockedIncrement(&siw.device_events_refs);
2304 }
2305 
2306 static extern(Windows) ULONG soundio_MMNotificationClient_Release(IMMNotificationClient* client) {
2307     shared SoundIoPrivate* si = soundio_MMNotificationClient_si(client);
2308     shared SoundIoWasapi* siw = &si.backend_data.wasapi;
2309     // workaround because `InterlockedDecrement` is missing in D's Kernel32 import library
2310     return atomicOp!"-="(siw.device_events_refs, 1);
2311     //return InterlockedDecrement(&siw.device_events_refs);
2312 }
2313 
2314 static HRESULT queue_device_scan(IMMNotificationClient* client) {
2315     auto si = cast(SoundIoPrivate*) soundio_MMNotificationClient_si(client); // cast away shared
2316     force_device_scan_wasapi(si);
2317     return S_OK;
2318 }
2319 
2320 static extern(Windows) HRESULT soundio_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient* client, LPCWSTR wid, DWORD state) {
2321     return queue_device_scan(client);
2322 }
2323 
2324 static extern(Windows) HRESULT soundio_MMNotificationClient_OnDeviceAdded(IMMNotificationClient* client, LPCWSTR wid) {
2325     return queue_device_scan(client);
2326 }
2327 
2328 static extern(Windows) HRESULT soundio_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient* client, LPCWSTR wid) {
2329     return queue_device_scan(client);
2330 }
2331 
2332 static extern(Windows) HRESULT soundio_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient* client, EDataFlow flow, ERole role, LPCWSTR wid) {
2333     return queue_device_scan(client);
2334 }
2335 
2336 static extern(Windows) HRESULT soundio_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient* client, LPCWSTR wid, const(PROPERTYKEY) key) {
2337     return queue_device_scan(client);
2338 }
2339 
2340 
2341 static IMMNotificationClientVtbl soundio_MMNotificationClient = IMMNotificationClientVtbl(
2342     &soundio_MMNotificationClient_QueryInterface,
2343     &soundio_MMNotificationClient_AddRef,
2344     &soundio_MMNotificationClient_Release,
2345     &soundio_MMNotificationClient_OnDeviceStateChanged,
2346     &soundio_MMNotificationClient_OnDeviceAdded,
2347     &soundio_MMNotificationClient_OnDeviceRemoved,
2348     &soundio_MMNotificationClient_OnDefaultDeviceChange,
2349     &soundio_MMNotificationClient_OnPropertyValueChanged,
2350 );
2351 
2352 package int soundio_wasapi_init(SoundIoPrivate* si) {
2353     SoundIoWasapi* siw = &si.backend_data.wasapi;
2354 
2355     siw.device_scan_queued = true;
2356 
2357     siw.mutex = soundio_os_mutex_create();
2358     if (!siw.mutex) {
2359         destroy_wasapi(si);
2360         return SoundIoError.NoMem;
2361     }
2362 
2363     siw.scan_devices_mutex = soundio_os_mutex_create();
2364     if (!siw.scan_devices_mutex) {
2365         destroy_wasapi(si);
2366         return SoundIoError.NoMem;
2367     }
2368 
2369     siw.cond = soundio_os_cond_create();
2370     if (!siw.cond) {
2371         destroy_wasapi(si);
2372         return SoundIoError.NoMem;
2373     }
2374 
2375     siw.scan_devices_cond = soundio_os_cond_create();
2376     if (!siw.scan_devices_cond) {
2377         destroy_wasapi(si);
2378         return SoundIoError.NoMem;
2379     }
2380 
2381     siw.device_events.lpVtbl = &soundio_MMNotificationClient;
2382     siw.device_events_refs = 1;
2383 
2384     if (auto err = soundio_os_thread_create(&device_thread_run, si, null, &siw.thread)) {
2385         destroy_wasapi(si);
2386         return err;
2387     }
2388 
2389     si.destroy = &destroy_wasapi;
2390     si.flush_events = &flush_events_wasapi;
2391     si.wait_events = &wait_events_wasapi;
2392     si.wakeup = &wakeup_wasapi;
2393     si.force_device_scan = &force_device_scan_wasapi;
2394 
2395     si.outstream_open = &outstream_open_wasapi;
2396     si.outstream_destroy = &outstream_destroy_wasapi;
2397     si.outstream_start = &outstream_start_wasapi;
2398     si.outstream_begin_write = &outstream_begin_write_wasapi;
2399     si.outstream_end_write = &outstream_end_write_wasapi;
2400     si.outstream_clear_buffer = &outstream_clear_buffer_wasapi;
2401     si.outstream_pause = &outstream_pause_wasapi;
2402     si.outstream_get_latency = &outstream_get_latency_wasapi;
2403     si.outstream_set_volume = &outstream_set_volume_wasapi;
2404 
2405     si.instream_open = &instream_open_wasapi;
2406     si.instream_destroy = &instream_destroy_wasapi;
2407     si.instream_start = &instream_start_wasapi;
2408     si.instream_begin_read = &instream_begin_read_wasapi;
2409     si.instream_end_read = &instream_end_read_wasapi;
2410     si.instream_pause = &instream_pause_wasapi;
2411     si.instream_get_latency = &instream_get_latency_wasapi;
2412 
2413     return 0;
2414 }