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