1 /* FluidSynth - A Software Synthesizer
2 *
3 * Copyright (C) 2003 Peter Hanappe and others.
4 * Copyright (C) 2021 Chris Xiong
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public License
8 * as published by the Free Software Foundation; either version 2.1 of
9 * the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free
18 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 * 02110-1301, USA
20 */
21
22 #include "fluid_synth.h"
23 #include "fluid_adriver.h"
24 #include "fluid_settings.h"
25
26 #if WASAPI_SUPPORT
27
28 #define CINTERFACE
29 #define COBJMACROS
30
31 #include <objbase.h>
32 #include <windows.h>
33 #include <initguid.h>
34 #include <mmdeviceapi.h>
35 #include <audioclient.h>
36 #include <audiosessiontypes.h>
37 #include <functiondiscoverykeys_devpkey.h>
38 #include <mmreg.h>
39 #include <oaidl.h>
40 #include <ksguid.h>
41 #include <ksmedia.h>
42
43 // these symbols are either never found in headers, or
44 // only defined but there are no library containing the actual symbol...
45
46 #ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
47 #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
48 #endif
49
50 #ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
51 #define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
52 #endif
53
54 static const CLSID _CLSID_MMDeviceEnumerator =
55 {
56 0xbcde0395, 0xe52f, 0x467c, {0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e}
57 };
58
59 static const IID _IID_IMMDeviceEnumerator =
60 {
61 0xa95664d2, 0x9614, 0x4f35, {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6}
62 };
63 static const IID _IID_IAudioClient =
64 {
65 0x1cb9ad4c, 0xdbfa, 0x4c32, {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2}
66 };
67 static const IID _IID_IAudioRenderClient =
68 {
69 0xf294acfc, 0x3146, 0x4483, {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2}
70 };
71 /*
72 * WASAPI Driver for fluidsynth
73 *
74 * Current limitations:
75 * - Only one stereo audio output.
76 * - If audio.sample-format is "16bits", a conversion from float
77 * without dithering is used.
78 *
79 * Available settings:
80 * - audio.wasapi.exclusive-mode
81 * 0 = shared mode, 1 = exclusive mode
82 * - audio.wasapi.device
83 * Used for device selection. Leave unchanged (or use the value "default")
84 * to use the default Windows audio device for media playback.
85 *
86 * Notes:
87 * - If exclusive mode is selected, audio.period-size is used as the periodicity
88 * of the IAudioClient stream, which is the sole factor of audio latency.
89 * audio.periods still determines the buffer size, but has no direct impact on
90 * the latency (at least according to Microsoft). The valid range for
91 * audio.period-size may vary depending on the driver and sample rate.
92 * - In shared mode, audio.period-size is completely ignored. Instead, a value
93 * provided by the audio driver is used. In theory this means the latency in
94 * shared mode is out of fluidsynth's control, but you may still increase
95 * audio.periods for a larger buffer to fix buffer underruns in case there
96 * are any.
97 * - The sample rate and sample format of fluidsynth must be supported by the
98 * audio device in exclusive mode. Otherwise driver creation will fail. Use
99 * `fluidsynth ---query-audio-devices` to find out the modes supported by
100 * the soundcards installed on the system.
101 * - In shared mode, if the sample rate of the synth doesn't match what is
102 * configured in the 'advanced' tab of the audio device properties dialog,
103 * Windows will automatically resample the output (obviously). Windows
104 * Vista doesn't seem to support the resampling method with better quality.
105 * - Under Windows 10, this driver may report a latency of 0ms in shared mode.
106 * This is nonsensical and should be disregarded.
107 *
108 */
109
110 #define FLUID_WASAPI_MAX_OUTPUTS 1
111
112 typedef void(*fluid_wasapi_devenum_callback_t)(IMMDevice *, void *);
113
114 static DWORD WINAPI fluid_wasapi_audio_run(void *p);
115 static int fluid_wasapi_write_processed_channels(void *data, int len,
116 int channels_count,
117 void *channels_out[], int channels_off[],
118 int channels_incr[]);
119 static void fluid_wasapi_foreach_device(fluid_wasapi_devenum_callback_t callback, void *data);
120 static void fluid_wasapi_register_callback(IMMDevice *dev, void *data);
121 static void fluid_wasapi_finddev_callback(IMMDevice *dev, void *data);
122 static IMMDevice *fluid_wasapi_find_device(IMMDeviceEnumerator *denum, const char *name);
123 static FLUID_INLINE int16_t round_clip_to_i16(float x);
124
125 typedef struct
126 {
127 const char *name;
128 wchar_t *id;
129 } fluid_wasapi_finddev_data_t;
130
131 typedef struct
132 {
133 fluid_audio_driver_t driver;
134
135 void *user_pointer;
136 fluid_audio_func_t func;
137 fluid_audio_channels_callback_t write;
138 float **drybuf;
139
140 UINT32 nframes;
141 double buffer_duration;
142 int channels_count;
143 int float_samples;
144
145 HANDLE start_ev;
146 HANDLE thread;
147 DWORD thread_id;
148 HANDLE quit_ev;
149
150 IAudioClient *aucl;
151 IAudioRenderClient *arcl;
152
153 double sample_rate;
154 int periods, period_size;
155 fluid_long_long_t buffer_duration_reftime;
156 fluid_long_long_t periods_reftime;
157 fluid_long_long_t latency_reftime;
158 int audio_channels;
159 int sample_size;
160 char *dname;
161 int exclusive;
162 unsigned short sample_format;
163
164 } fluid_wasapi_audio_driver_t;
165
new_fluid_wasapi_audio_driver(fluid_settings_t * settings,fluid_synth_t * synth)166 fluid_audio_driver_t *new_fluid_wasapi_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
167 {
168 return new_fluid_wasapi_audio_driver2(settings, (fluid_audio_func_t)fluid_synth_process, synth);
169 }
170
new_fluid_wasapi_audio_driver2(fluid_settings_t * settings,fluid_audio_func_t func,void * data)171 fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data)
172 {
173 DWORD ret;
174 HANDLE wait_handles[2];
175 fluid_wasapi_audio_driver_t *dev = NULL;
176 OSVERSIONINFOEXW vi = {sizeof(vi), 6, 0, 0, 0, {0}, 0, 0, 0, 0, 0};
177
178 if(!VerifyVersionInfoW(&vi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR,
179 VerSetConditionMask(VerSetConditionMask(VerSetConditionMask(0,
180 VER_MAJORVERSION, VER_GREATER_EQUAL),
181 VER_MINORVERSION, VER_GREATER_EQUAL),
182 VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL)))
183 {
184 FLUID_LOG(FLUID_ERR, "wasapi: this driver requires Windows Vista or newer.");
185 return NULL;
186 }
187
188 dev = FLUID_NEW(fluid_wasapi_audio_driver_t);
189
190 if(dev == NULL)
191 {
192 FLUID_LOG(FLUID_ERR, "wasapi: out of memory.");
193 return NULL;
194 }
195
196 FLUID_MEMSET(dev, 0, sizeof(fluid_wasapi_audio_driver_t));
197
198 /* Retrieve the settings */
199
200 fluid_settings_getnum(settings, "synth.sample-rate", &dev->sample_rate);
201 fluid_settings_getint(settings, "audio.periods", &dev->periods);
202 fluid_settings_getint(settings, "audio.period-size", &dev->period_size);
203 fluid_settings_getint(settings, "synth.audio-channels", &dev->audio_channels);
204 fluid_settings_getint(settings, "audio.wasapi.exclusive-mode", &dev->exclusive);
205
206 if(dev->audio_channels > FLUID_WASAPI_MAX_OUTPUTS)
207 {
208 FLUID_LOG(FLUID_ERR, "wasapi: channel configuration with more than one stereo pair is not supported.");
209 goto cleanup;
210 }
211
212 if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits"))
213 {
214 dev->sample_size = sizeof(int16_t);
215 dev->sample_format = WAVE_FORMAT_PCM;
216 }
217 else
218 {
219 dev->sample_size = sizeof(float);
220 dev->sample_format = WAVE_FORMAT_IEEE_FLOAT;
221 }
222
223 if(fluid_settings_dupstr(settings, "audio.wasapi.device", &dev->dname) != FLUID_OK)
224 {
225 FLUID_LOG(FLUID_ERR, "wasapi: out of memory.");
226 goto cleanup;
227 }
228
229 dev->func = func;
230 dev->user_pointer = data;
231 dev->buffer_duration = dev->periods * dev->period_size / dev->sample_rate;
232 dev->channels_count = dev->audio_channels * 2;
233 dev->float_samples = (dev->sample_format == WAVE_FORMAT_IEEE_FLOAT);
234 dev->buffer_duration_reftime = (fluid_long_long_t)(dev->buffer_duration * 1e7 + .5);
235 dev->periods_reftime = (fluid_long_long_t)(dev->period_size / dev->sample_rate * 1e7 + .5);
236
237 dev->quit_ev = CreateEvent(NULL, FALSE, FALSE, NULL);
238
239 if(dev->quit_ev == NULL)
240 {
241 FLUID_LOG(FLUID_ERR, "wasapi: failed to create quit event: '%s'", fluid_get_windows_error());
242 goto cleanup;
243 }
244
245 dev->start_ev = CreateEvent(NULL, FALSE, FALSE, NULL);
246
247 if(dev->start_ev == NULL)
248 {
249 FLUID_LOG(FLUID_ERR, "wasapi: failed to create start event: '%s'", fluid_get_windows_error());
250 goto cleanup;
251 }
252
253 dev->thread = CreateThread(NULL, 0, fluid_wasapi_audio_run, dev, 0, &dev->thread_id);
254
255 if(dev->thread == NULL)
256 {
257 FLUID_LOG(FLUID_ERR, "wasapi: failed to create audio thread: '%s'", fluid_get_windows_error());
258 goto cleanup;
259 }
260
261 /* start event must be first */
262 wait_handles[0] = dev->start_ev;
263 wait_handles[1] = dev->thread;
264 ret = WaitForMultipleObjects(FLUID_N_ELEMENTS(wait_handles), wait_handles, FALSE, 2000);
265
266 switch(ret)
267 {
268 case WAIT_OBJECT_0:
269 return &dev->driver;
270
271 case WAIT_TIMEOUT:
272 FLUID_LOG(FLUID_WARN, "wasapi: initialization timeout!");
273 break;
274
275 default:
276 break;
277 }
278
279 cleanup:
280
281 delete_fluid_wasapi_audio_driver(&dev->driver);
282 return NULL;
283 }
284
delete_fluid_wasapi_audio_driver(fluid_audio_driver_t * p)285 void delete_fluid_wasapi_audio_driver(fluid_audio_driver_t *p)
286 {
287 fluid_wasapi_audio_driver_t *dev = (fluid_wasapi_audio_driver_t *) p;
288 int i;
289
290 fluid_return_if_fail(dev != NULL);
291
292 if(dev->thread != NULL)
293 {
294 SetEvent(dev->quit_ev);
295
296 if(WaitForSingleObject(dev->thread, 2000) != WAIT_OBJECT_0)
297 {
298 FLUID_LOG(FLUID_WARN, "wasapi: couldn't join the audio thread. killing it.");
299 TerminateThread(dev->thread, 0);
300 }
301
302 CloseHandle(dev->thread);
303 }
304
305 if(dev->quit_ev != NULL)
306 {
307 CloseHandle(dev->quit_ev);
308 }
309
310 if(dev->start_ev != NULL)
311 {
312 CloseHandle(dev->start_ev);
313 }
314
315 if(dev->drybuf)
316 {
317 for(i = 0; i < dev->channels_count; ++i)
318 {
319 FLUID_FREE(dev->drybuf[i]);
320 }
321 }
322
323 FLUID_FREE(dev->dname);
324
325 FLUID_FREE(dev->drybuf);
326
327 FLUID_FREE(dev);
328 }
329
fluid_wasapi_audio_driver_settings(fluid_settings_t * settings)330 void fluid_wasapi_audio_driver_settings(fluid_settings_t *settings)
331 {
332 fluid_settings_register_int(settings, "audio.wasapi.exclusive-mode", 0, 0, 1, FLUID_HINT_TOGGLED);
333 fluid_settings_register_str(settings, "audio.wasapi.device", "default", 0);
334 fluid_settings_add_option(settings, "audio.wasapi.device", "default");
335 fluid_wasapi_foreach_device(fluid_wasapi_register_callback, settings);
336 }
337
fluid_wasapi_audio_run(void * p)338 static DWORD WINAPI fluid_wasapi_audio_run(void *p)
339 {
340 fluid_wasapi_audio_driver_t *dev = (fluid_wasapi_audio_driver_t *)p;
341 DWORD time_to_sleep;
342 UINT32 pos;
343 DWORD len;
344 void *channels_out[2];
345 int channels_off[2] = {0, 1};
346 int channels_incr[2] = {2, 2};
347 BYTE *pbuf;
348 HRESULT ret;
349 IMMDeviceEnumerator *denum = NULL;
350 IMMDevice *mmdev = NULL;
351 DWORD flags = 0;
352 WAVEFORMATEXTENSIBLE wfx;
353 WAVEFORMATEXTENSIBLE *rwfx = NULL;
354 AUDCLNT_SHAREMODE share_mode;
355 OSVERSIONINFOEXW vi = {sizeof(vi), 6, 0, 0, 0, {0}, 0, 0, 0, 0, 0};
356 int needs_com_uninit = FALSE;
357 int i;
358
359 /* Clear format structure */
360 ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
361
362 wfx.Format.nChannels = 2;
363 wfx.Format.wBitsPerSample = dev->sample_size * 8;
364 wfx.Format.nBlockAlign = dev->sample_size * wfx.Format.nChannels;
365 wfx.Format.nSamplesPerSec = (DWORD) dev->sample_rate;
366 wfx.Format.nAvgBytesPerSec = (DWORD) dev->sample_rate * wfx.Format.nBlockAlign;
367 wfx.Format.wFormatTag = dev->sample_format;
368 //wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
369 //wfx.Format.cbSize = 22;
370 //wfx.SubFormat = guid_float;
371 //wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
372 //wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
373
374 /* initialize COM in a worker thread to avoid a potential double initialization in the callers thread */
375 ret = CoInitializeEx(NULL, COINIT_MULTITHREADED);
376
377 if(FAILED(ret))
378 {
379 FLUID_LOG(FLUID_ERR, "wasapi: cannot initialize COM. 0x%x", (unsigned)ret);
380 goto cleanup;
381 }
382
383 needs_com_uninit = TRUE;
384
385 ret = CoCreateInstance(
386 &_CLSID_MMDeviceEnumerator, NULL,
387 CLSCTX_ALL, &_IID_IMMDeviceEnumerator,
388 (void **)&denum);
389
390 if(FAILED(ret))
391 {
392 FLUID_LOG(FLUID_ERR, "wasapi: cannot create device enumerator. 0x%x", (unsigned)ret);
393 goto cleanup;
394 }
395
396 mmdev = fluid_wasapi_find_device(denum, dev->dname);
397
398 if(mmdev == NULL)
399 {
400 goto cleanup;
401 }
402
403 ret = IMMDevice_Activate(mmdev,
404 &_IID_IAudioClient,
405 CLSCTX_ALL, NULL,
406 (void **)&dev->aucl);
407
408 if(FAILED(ret))
409 {
410 FLUID_LOG(FLUID_ERR, "wasapi: cannot activate audio client. 0x%x", (unsigned)ret);
411 goto cleanup;
412 }
413
414
415 if(dev->exclusive)
416 {
417 share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
418 FLUID_LOG(FLUID_DBG, "wasapi: using exclusive mode.");
419 }
420 else
421 {
422 fluid_long_long_t defp;
423 share_mode = AUDCLNT_SHAREMODE_SHARED;
424 FLUID_LOG(FLUID_DBG, "wasapi: using shared mode.");
425 dev->periods_reftime = 0;
426
427 //use default period size of the device
428 if(SUCCEEDED(IAudioClient_GetDevicePeriod(dev->aucl, &defp, NULL)))
429 {
430 dev->period_size = (int)(defp / 1e7 * dev->sample_rate);
431 dev->buffer_duration = dev->periods * dev->period_size / dev->sample_rate;
432 dev->buffer_duration_reftime = (fluid_long_long_t)(dev->buffer_duration * 1e7 + .5);
433 FLUID_LOG(FLUID_DBG, "wasapi: using device period size: %d", dev->period_size);
434 }
435 }
436
437 ret = IAudioClient_IsFormatSupported(dev->aucl, share_mode, (const WAVEFORMATEX *)&wfx, (WAVEFORMATEX **)&rwfx);
438
439 if(FAILED(ret))
440 {
441 FLUID_LOG(FLUID_ERR, "wasapi: device doesn't support the mode we want. 0x%x", (unsigned)ret);
442 goto cleanup;
443 }
444 else if(ret == S_FALSE)
445 {
446 //rwfx is non-null only in this case
447 FLUID_LOG(FLUID_INFO, "wasapi: requested mode cannot be fully satisfied.");
448
449 if(rwfx->Format.nSamplesPerSec != wfx.Format.nSamplesPerSec) // needs resampling
450 {
451 flags = AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM;
452 vi.dwMinorVersion = 1;
453
454 if(VerifyVersionInfoW(&vi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR,
455 VerSetConditionMask(VerSetConditionMask(VerSetConditionMask(0,
456 VER_MAJORVERSION, VER_GREATER_EQUAL),
457 VER_MINORVERSION, VER_GREATER_EQUAL),
458 VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL)))
459 //IAudioClient::Initialize in Vista fails with E_INVALIDARG if this flag is set
460 {
461 flags |= AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
462 }
463 }
464
465 CoTaskMemFree(rwfx);
466 }
467
468 ret = IAudioClient_Initialize(dev->aucl, share_mode, flags,
469 dev->buffer_duration_reftime, dev->periods_reftime, (WAVEFORMATEX *)&wfx, &GUID_NULL);
470
471 if(FAILED(ret))
472 {
473 FLUID_LOG(FLUID_ERR, "wasapi: failed to initialize audio client. 0x%x", (unsigned)ret);
474
475 if(ret == AUDCLNT_E_INVALID_DEVICE_PERIOD)
476 {
477 fluid_long_long_t defp, minp;
478 FLUID_LOG(FLUID_ERR, "wasapi: the device period size is invalid.");
479
480 if(SUCCEEDED(IAudioClient_GetDevicePeriod(dev->aucl, &defp, &minp)))
481 {
482 int defpf = (int)(defp / 1e7 * dev->sample_rate);
483 int minpf = (int)(minp / 1e7 * dev->sample_rate);
484 FLUID_LOG(FLUID_ERR, "wasapi: minimum period is %d, default period is %d. selected %d.", minpf, defpf, dev->period_size);
485 }
486 }
487
488 goto cleanup;
489 }
490
491 ret = IAudioClient_GetBufferSize(dev->aucl, &dev->nframes);
492
493 if(FAILED(ret))
494 {
495 FLUID_LOG(FLUID_ERR, "wasapi: cannot get audio buffer size. 0x%x", (unsigned)ret);
496 goto cleanup;
497 }
498
499 FLUID_LOG(FLUID_DBG, "wasapi: requested %d frames of buffers, got %u.", dev->periods * dev->period_size, dev->nframes);
500 dev->buffer_duration = dev->nframes / dev->sample_rate;
501 time_to_sleep = dev->buffer_duration * 1000 / 2;
502 if(time_to_sleep < 1)
503 {
504 time_to_sleep = 1;
505 }
506
507 dev->drybuf = FLUID_ARRAY(float *, dev->audio_channels * 2);
508
509 if(dev->drybuf == NULL)
510 {
511 FLUID_LOG(FLUID_ERR, "wasapi: out of memory");
512 goto cleanup;
513 }
514
515 FLUID_MEMSET(dev->drybuf, 0, sizeof(float *) * dev->audio_channels * 2);
516
517 for(i = 0; i < dev->audio_channels * 2; ++i)
518 {
519 dev->drybuf[i] = FLUID_ARRAY(float, dev->nframes);
520
521 if(dev->drybuf[i] == NULL)
522 {
523 FLUID_LOG(FLUID_ERR, "wasapi: out of memory");
524 goto cleanup;
525 }
526 }
527
528 ret = IAudioClient_GetService(dev->aucl, &_IID_IAudioRenderClient, (void **)&dev->arcl);
529
530 if(FAILED(ret))
531 {
532 FLUID_LOG(FLUID_ERR, "wasapi: cannot get audio render device. 0x%x", (unsigned)ret);
533 goto cleanup;
534 }
535
536 if(SUCCEEDED(IAudioClient_GetStreamLatency(dev->aucl, &dev->latency_reftime)))
537 {
538 FLUID_LOG(FLUID_DBG, "wasapi: latency: %fms.", dev->latency_reftime / 1e4);
539 }
540
541 ret = IAudioClient_Start(dev->aucl);
542
543 if(FAILED(ret))
544 {
545 FLUID_LOG(FLUID_ERR, "wasapi: failed to start audio client. 0x%x", (unsigned)ret);
546 goto cleanup;
547 }
548
549 /* Signal the success of the driver initialization */
550 SetEvent(dev->start_ev);
551
552 for(;;)
553 {
554 ret = IAudioClient_GetCurrentPadding(dev->aucl, &pos);
555
556 if(FAILED(ret))
557 {
558 FLUID_LOG(FLUID_ERR, "wasapi: cannot get buffer padding. 0x%x", (unsigned)ret);
559 goto cleanup;
560 }
561
562 len = dev->nframes - pos;
563
564 if(len == 0)
565 {
566 Sleep(0);
567 continue;
568 }
569
570 ret = IAudioRenderClient_GetBuffer(dev->arcl, len, &pbuf);
571
572 if(FAILED(ret))
573 {
574 FLUID_LOG(FLUID_ERR, "wasapi: cannot get buffer. 0x%x", (unsigned)ret);
575 goto cleanup;
576 }
577
578 channels_out[0] = channels_out[1] = (void *)pbuf;
579
580 fluid_wasapi_write_processed_channels(dev, len, 2,
581 channels_out, channels_off, channels_incr);
582
583 ret = IAudioRenderClient_ReleaseBuffer(dev->arcl, len, 0);
584
585 if(FAILED(ret))
586 {
587 FLUID_LOG(FLUID_ERR, "wasapi: failed to release buffer. 0x%x", (unsigned)ret);
588 goto cleanup;
589 }
590
591 if(WaitForSingleObject(dev->quit_ev, time_to_sleep) == WAIT_OBJECT_0)
592 {
593 break;
594 }
595 }
596
597 cleanup:
598
599 if(dev->aucl != NULL)
600 {
601 IAudioClient_Stop(dev->aucl);
602 IAudioClient_Release(dev->aucl);
603 }
604
605 if(dev->arcl != NULL)
606 {
607 IAudioRenderClient_Release(dev->arcl);
608 }
609
610 if(mmdev != NULL)
611 {
612 IMMDevice_Release(mmdev);
613 }
614
615 if(denum != NULL)
616 {
617 IMMDeviceEnumerator_Release(denum);
618 }
619
620 if(needs_com_uninit)
621 {
622 CoUninitialize();
623 }
624
625 return 0;
626 }
627
fluid_wasapi_write_processed_channels(void * data,int len,int channels_count,void * channels_out[],int channels_off[],int channels_incr[])628 static int fluid_wasapi_write_processed_channels(void *data, int len,
629 int channels_count,
630 void *channels_out[], int channels_off[],
631 int channels_incr[])
632 {
633 int i, ch;
634 int ret;
635 fluid_wasapi_audio_driver_t *drv = (fluid_wasapi_audio_driver_t *) data;
636 float *optr[FLUID_WASAPI_MAX_OUTPUTS * 2];
637 int16_t *ioptr[FLUID_WASAPI_MAX_OUTPUTS * 2];
638 int efx_nch = 0;
639 float **efx_buf = NULL;
640
641 for(ch = 0; ch < drv->channels_count; ++ch)
642 {
643 FLUID_MEMSET(drv->drybuf[ch], 0, len * sizeof(float));
644 optr[ch] = (float *)channels_out[ch] + channels_off[ch];
645 ioptr[ch] = (int16_t *)channels_out[ch] + channels_off[ch];
646 }
647
648 if(drv->func == (fluid_audio_func_t)fluid_synth_process)
649 {
650 efx_nch = drv->channels_count;
651 efx_buf = drv->drybuf;
652 }
653 ret = drv->func(drv->user_pointer, len, efx_nch, efx_buf, drv->channels_count, drv->drybuf);
654
655 for(ch = 0; ch < drv->channels_count; ++ch)
656 {
657 for(i = 0; i < len; ++i)
658 {
659 if(drv->float_samples)
660 {
661 *optr[ch] = drv->drybuf[ch][i];
662 optr[ch] += channels_incr[ch];
663 }
664 else //This code is taken from fluid_synth.c. No dithering yet.
665 {
666 *ioptr[ch] = round_clip_to_i16(drv->drybuf[ch][i] * 32766.0f);
667 ioptr[ch] += channels_incr[ch];
668 }
669 }
670 }
671
672 return ret;
673 }
674
fluid_wasapi_foreach_device(fluid_wasapi_devenum_callback_t callback,void * data)675 static void fluid_wasapi_foreach_device(fluid_wasapi_devenum_callback_t callback, void *data)
676 {
677 IMMDeviceEnumerator *denum = NULL;
678 IMMDeviceCollection *dcoll = NULL;
679 UINT cnt, i;
680 HRESULT ret;
681 int com_was_initialized = FALSE;
682
683 ret = CoInitializeEx(NULL, COINIT_MULTITHREADED);
684
685 if(FAILED(ret))
686 {
687 if(ret == RPC_E_CHANGED_MODE)
688 {
689 com_was_initialized = TRUE;
690 FLUID_LOG(FLUID_DBG, "wasapi: COM was already initialized");
691 }
692 else
693 {
694 FLUID_LOG(FLUID_ERR, "wasapi: cannot initialize COM. 0x%x", (unsigned)ret);
695 return;
696 }
697 }
698
699 ret = CoCreateInstance(
700 &_CLSID_MMDeviceEnumerator, NULL,
701 CLSCTX_ALL, &_IID_IMMDeviceEnumerator,
702 (void **)&denum);
703
704 if(FAILED(ret))
705 {
706 FLUID_LOG(FLUID_ERR, "wasapi: cannot create device enumerator. 0x%x", (unsigned)ret);
707 goto cleanup;
708 }
709
710 ret = IMMDeviceEnumerator_EnumAudioEndpoints(
711 denum, eRender,
712 DEVICE_STATE_ACTIVE, &dcoll);
713
714 if(FAILED(ret))
715 {
716 FLUID_LOG(FLUID_ERR, "wasapi: cannot enumerate audio devices. 0x%x", (unsigned)ret);
717 goto cleanup;
718 }
719
720 ret = IMMDeviceCollection_GetCount(dcoll, &cnt);
721
722 if(FAILED(ret))
723 {
724 FLUID_LOG(FLUID_ERR, "wasapi: cannot get device count. 0x%x", (unsigned)ret);
725 goto cleanup;
726 }
727
728 for(i = 0; i < cnt; ++i)
729 {
730 IMMDevice *dev = NULL;
731
732 ret = IMMDeviceCollection_Item(dcoll, i, &dev);
733
734 if(FAILED(ret))
735 {
736 FLUID_LOG(FLUID_ERR, "wasapi: cannot get device #%u. 0x%x", i, (unsigned)ret);
737 continue;
738 }
739
740 callback(dev, data);
741
742 IMMDevice_Release(dev);
743 }
744
745 cleanup:
746
747 if(dcoll != NULL)
748 {
749 IMMDeviceCollection_Release(dcoll);
750 }
751
752 if(denum != NULL)
753 {
754 IMMDeviceEnumerator_Release(denum);
755 }
756
757 if(!com_was_initialized)
758 {
759 CoUninitialize();
760 }
761 }
762
fluid_wasapi_register_callback(IMMDevice * dev,void * data)763 static void fluid_wasapi_register_callback(IMMDevice *dev, void *data)
764 {
765 fluid_settings_t *settings = (fluid_settings_t *)data;
766 IPropertyStore *prop = NULL;
767 PROPVARIANT var;
768 int ret;
769
770 ret = IMMDevice_OpenPropertyStore(dev, STGM_READ, &prop);
771
772 if(FAILED(ret))
773 {
774 FLUID_LOG(FLUID_ERR, "wasapi: cannot get properties of device. 0x%x", (unsigned)ret);
775 return;
776 }
777
778 PropVariantInit(&var);
779
780 ret = IPropertyStore_GetValue(prop, &PKEY_Device_FriendlyName, &var);
781
782 if(FAILED(ret))
783 {
784 FLUID_LOG(FLUID_ERR, "wasapi: cannot get friendly name of device. 0x%x", (unsigned)ret);
785 }
786 else
787 {
788 int nsz;
789 char *name;
790
791 nsz = WideCharToMultiByte(CP_ACP, 0, var.pwszVal, -1, 0, 0, 0, 0);
792 name = FLUID_ARRAY(char, nsz + 1);
793 WideCharToMultiByte(CP_ACP, 0, var.pwszVal, -1, name, nsz, 0, 0);
794 fluid_settings_add_option(settings, "audio.wasapi.device", name);
795 FLUID_FREE(name);
796 }
797
798 IPropertyStore_Release(prop);
799 PropVariantClear(&var);
800 }
801
fluid_wasapi_finddev_callback(IMMDevice * dev,void * data)802 static void fluid_wasapi_finddev_callback(IMMDevice *dev, void *data)
803 {
804 fluid_wasapi_finddev_data_t *d = (fluid_wasapi_finddev_data_t *)data;
805 int nsz;
806 char *name = NULL;
807 wchar_t *id = NULL;
808 IPropertyStore *prop = NULL;
809 PROPVARIANT var;
810 HRESULT ret;
811
812 ret = IMMDevice_OpenPropertyStore(dev, STGM_READ, &prop);
813
814 if(FAILED(ret))
815 {
816 FLUID_LOG(FLUID_ERR, "wasapi: cannot get properties of device. 0x%x", (unsigned)ret);
817 return;
818 }
819
820 PropVariantInit(&var);
821
822 ret = IPropertyStore_GetValue(prop, &PKEY_Device_FriendlyName, &var);
823
824 if(FAILED(ret))
825 {
826 FLUID_LOG(FLUID_ERR, "wasapi: cannot get friendly name of device. 0x%x", (unsigned)ret);
827 goto cleanup;
828 }
829
830 nsz = WideCharToMultiByte(CP_ACP, 0, var.pwszVal, -1, 0, 0, 0, 0);
831 name = FLUID_ARRAY(char, nsz + 1);
832 WideCharToMultiByte(CP_ACP, 0, var.pwszVal, -1, name, nsz, 0, 0);
833
834 if(!FLUID_STRCASECMP(name, d->name))
835 {
836 ret = IMMDevice_GetId(dev, &id);
837
838 if(FAILED(ret))
839 {
840 FLUID_LOG(FLUID_ERR, "wasapi: cannot get id of device. 0x%x", (unsigned)ret);
841 goto cleanup;
842 }
843
844 nsz = wcslen(id);
845 d->id = FLUID_ARRAY(wchar_t, nsz + 1);
846 FLUID_MEMCPY(d->id, id, sizeof(wchar_t) * (nsz + 1));
847 }
848
849 cleanup:
850 PropVariantClear(&var);
851 IPropertyStore_Release(prop);
852 CoTaskMemFree(id);
853 FLUID_FREE(name);
854 }
855
fluid_wasapi_find_device(IMMDeviceEnumerator * denum,const char * name)856 static IMMDevice *fluid_wasapi_find_device(IMMDeviceEnumerator *denum, const char *name)
857 {
858 fluid_wasapi_finddev_data_t d;
859 IMMDevice *dev;
860 HRESULT ret;
861 d.name = name;
862 d.id = NULL;
863
864 if(!FLUID_STRCASECMP(name, "default"))
865 {
866 ret = IMMDeviceEnumerator_GetDefaultAudioEndpoint(
867 denum,
868 eRender,
869 eMultimedia,
870 &dev);
871
872 if(FAILED(ret))
873 {
874 FLUID_LOG(FLUID_ERR, "wasapi: cannot get default audio device. 0x%x", (unsigned)ret);
875 return NULL;
876 }
877 else
878 {
879 return dev;
880 }
881 }
882
883 fluid_wasapi_foreach_device(fluid_wasapi_finddev_callback, &d);
884
885 if(d.id != NULL)
886 {
887 ret = IMMDeviceEnumerator_GetDevice(denum, d.id, &dev);
888 FLUID_FREE(d.id);
889
890 if(FAILED(ret))
891 {
892 FLUID_LOG(FLUID_ERR, "wasapi: cannot find device with id. 0x%x", (unsigned)ret);
893 return NULL;
894 }
895
896 return dev;
897 }
898 else
899 {
900 FLUID_LOG(FLUID_ERR, "wasapi: cannot find device \"%s\".", name);
901 return NULL;
902 }
903 }
904
905 static FLUID_INLINE int16_t
round_clip_to_i16(float x)906 round_clip_to_i16(float x)
907 {
908 long i;
909
910 if(x >= 0.0f)
911 {
912 i = (long)(x + 0.5f);
913
914 if(FLUID_UNLIKELY(i > 32767))
915 {
916 i = 32767;
917 }
918 }
919 else
920 {
921 i = (long)(x - 0.5f);
922
923 if(FLUID_UNLIKELY(i < -32768))
924 {
925 i = -32768;
926 }
927 }
928
929 return (int16_t)i;
930 }
931 #endif /* WASAPI_SUPPORT */
932
933