1 /*
2 * sounddx.c - Implementation of the DirectSound sound device.
3 *
4 * Written by
5 * Tibor Biczo <crown@mail.matav.hu>
6 * Ettore Perazzoli <ettore@comm2000.it>
7 * Andreas Matthies <andreas.matthies@gmx.net>
8 * Marco van den Heuvel <blackystardust68@yahoo.com>
9 *
10 * This file is part of VICE, the Versatile Commodore Emulator.
11 * See README for copyright notice.
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
26 * 02111-1307 USA.
27 *
28 */
29
30 #include "vice.h"
31
32 #ifdef USE_DXSOUND
33
34 #include <stdio.h>
35
36 #ifndef HAVE_GUIDLIB
37 #define INITGUID
38 #endif
39
40 #ifndef HAVE_DSOUND_LIB
41 #define _WIN32_DCOM
42 #endif
43
44 #include <windows.h>
45 #include <mmsystem.h>
46
47 #define DIRECTSOUND_VERSION 0x0500
48 #include <dsound.h>
49
50 #if defined(USE_SDLUI) || defined(USE_SDLUI2)
51 #define INCLUDE_SDL_SYSWM_H
52 #include "vice_sdl.h"
53 #endif
54
55 #include "lib.h"
56 #include "log.h"
57 #include "machine.h"
58 #include "sound.h"
59 #include "types.h"
60 #include "uiapi.h"
61
62 /* FIXME: each of the following should probably get moved to archdep */
63 #if defined(USE_SDLUI) || defined(USE_SDLUI2)
64
ui_get_main_hwnd(void)65 static HWND ui_get_main_hwnd(void)
66 {
67 SDL_SysWMinfo info;
68
69 SDL_GetWMInfo(&info);
70
71 return info.window;
72 }
73
74 #elif defined(USE_NATIVE_GTK3)
75
76 #include <gtk/gtk.h>
77 #include <gdk/gdk.h>
78 #include <gdk/gdkwin32.h>
79 #include "ui.h"
80
ui_get_main_hwnd(void)81 static HWND ui_get_main_hwnd(void)
82 {
83 GdkWindow *gdk_window = gtk_widget_get_window(ui_get_window_by_index(0));
84 HWND hWnd = NULL;
85
86 if (gdk_window) {
87 if (gdk_window_ensure_native(gdk_window)) {
88 hWnd = gdk_win32_window_get_impl_hwnd(gdk_window);
89 }
90 }
91
92 /*
93 * We could fallback to the desktop window - but we shouldn't have to!
94 * DirectSound uses this window to figure out something to do with
95 * how the sound device should be shared among multiple applications.
96 * So I believe it's worth keeping the above in a state that works.
97 * Which means better to get the bug report that the DirectSound
98 * driver isn't working for someone.
99 */
100 #if 0
101 if (!hWnd) {
102 hWnd = GetDesktopWindow();
103 }
104 #endif
105
106 return hWnd;
107 }
108
109 #else /* Headless UI */
110
ui_get_main_hwnd(void)111 static HWND ui_get_main_hwnd(void)
112 {
113 return GetDesktopWindow();
114 }
115
116 #endif
117
118 /* ------------------------------------------------------------------------ */
119
120 #define DEBUG_SOUND 0
121
122 /* Debugging stuff. */
123 #if DEBUG_SOUND
sound_debug(const char * format,...)124 static void sound_debug(const char *format, ...)
125 {
126 char tmp[1024];
127 va_list args;
128
129 va_start(args, format);
130 vsprintf(tmp, format, args);
131 va_end(args);
132 log_debug(tmp);
133 }
134 #define SDEBUG(x) sound_debug x
135 #else
136 #define SDEBUG(x)
137 #endif
138
ds_error(HRESULT result)139 static char *ds_error(HRESULT result)
140 {
141 static char tmp[0x20];
142 switch (result) {
143 case DSERR_ALLOCATED:
144 return "Already allocated resource";
145 case DSERR_CONTROLUNAVAIL:
146 return "Control not available";
147 case DSERR_INVALIDPARAM:
148 return "Parameter not valid";
149 case DSERR_INVALIDCALL:
150 return "Call not valid";
151 case DSERR_GENERIC:
152 return "Generic error";
153 case DSERR_PRIOLEVELNEEDED:
154 return "Priority level needed";
155 case DSERR_OUTOFMEMORY:
156 return "Out of memory";
157 case DSERR_BADFORMAT:
158 return "Specified WAVE format not supported";
159 case DSERR_UNSUPPORTED:
160 return "Not supported";
161 case DSERR_NODRIVER:
162 return "No sound driver is available for use";
163 case DSERR_ALREADYINITIALIZED:
164 return "Object already initialized";
165 case DSERR_NOAGGREGATION:
166 return "Object does not support aggregation";
167 case DSERR_BUFFERLOST:
168 return "Buffer lost";
169 case DSERR_OTHERAPPHASPRIO:
170 return "Another app has a higher priority level";
171 case DSERR_UNINITIALIZED:
172 return "Object not initialized";
173 case DSERR_NOINTERFACE:
174 return "Requested COM interface is not available";
175 default:
176 break;
177 }
178 sprintf(tmp, "Error 0x%x", (unsigned int)result);
179 return tmp;
180 }
181
182 /* ------------------------------------------------------------------------ */
183
184 /* DirectSound object. */
185 static LPDIRECTSOUND ds = NULL;
186
187 /* Audio buffer. */
188 static LPDIRECTSOUNDBUFFER buffer = NULL;
189 static LPDIRECTSOUNDBUFFER pbuffer = NULL;
190
191 /* Buffer offset. */
192 static DWORD buffer_offset;
193
194 /* Buffer size. */
195 static DWORD buffer_size;
196
197 /* Fragment size. */
198 static int fragment_size;
199
200 /* Channels */
201 static int num_of_channels;
202
203 /* Expected buffer fill state: maximum amount. */
204 static int earlier_bufferspace;
205
206 /* Flag: are we in exclusive mode? */
207 /* static int is_exclusive; */
208
209 #if 0
210 /* DirectSoundNotify Interface, if present */
211 static LPDIRECTSOUNDNOTIFY notify;
212
213 typedef enum {
214 STREAM_NOTIFY,
215 STREAM_TIMER
216 } streammode_t;
217
218 /* Flag: streaming mode */
219 static streammode_t streammode = STREAM_TIMER;
220
221 /* Notify Position Array */
222 static DSBPOSITIONNOTIFY *notifypositions;
223
224 /* Notify Event */
225 static HANDLE notifyevent;
226
227 /* End Event */
228 static HANDLE endevent;
229
230 /* Event Table */
231 static HANDLE events[2];
232
233 /* ID of Notify Thread */
234 static DWORD notifyThreadID;
235
236 /* Handle of Notify Thread */
237 static HANDLE notifyThreadHandle;
238
239 /* Pointer for waiting fragment */
240 static LPVOID fragment_pointer;
241 #endif
242
243 /* Last played sample. This will be played in underflow condition */
244 static int16_t last_buffered_sample[2];
245
246 /* Flag: is soundcard a 16bit or 8bit card? */
247 static int is16bit;
248
249 #if 0
250 /* Streaming buffer */
251 static int16_t *stream_buffer;
252
253 /* Offset of first buffered sample */
254 static volatile int stream_buffer_first;
255
256 /* Offset of last buffered sample */
257 static volatile int stream_buffer_last;
258
259 /* Offset of first buffered sample in shadow counter */
260 static volatile DWORD stream_buffer_shadow_first;
261
262 /* Offset of last buffered sample in shadow counter */
263 static volatile DWORD stream_buffer_shadow_last;
264 #endif
265
266 /* Size of streaming buffer */
267 static int stream_buffer_size;
268
269 #if 0
270 /* Timer callback interval */
271 static int timer_interval;
272
273 /* ID of timer event */
274 static UINT timer_id;
275 #endif
276
277 /* ------------------------------------------------------------------------ */
278
279 DSBUFFERDESC desc;
280 PCMWAVEFORMAT pcmwf;
281 DSCAPS capabilities;
282 WAVEFORMATEX wfex;
283
284 HWND ui_get_main_hwnd(void);
285
dx_clear(void)286 static void dx_clear(void)
287 {
288 LPVOID lpvPtr1;
289 DWORD dwBytes1;
290 LPVOID lpvPtr2;
291 DWORD dwBytes2;
292 HRESULT result;
293
294 result = IDirectSoundBuffer_Lock(buffer, 0, buffer_size,
295 &lpvPtr1, &dwBytes1, &lpvPtr2,
296 &dwBytes2, 0);
297 if (result == DSERR_BUFFERLOST) {
298 IDirectSoundBuffer_Restore(buffer);
299 } else {
300 if (is16bit) {
301 memset(lpvPtr1, 0, dwBytes1);
302 if (lpvPtr2) {
303 memset(lpvPtr2, 0, dwBytes2);
304 }
305 } else {
306 memset(lpvPtr1, 0x80, dwBytes1);
307 if (lpvPtr2) {
308 memset(lpvPtr2, 0x80, dwBytes2);
309 }
310 }
311 result = IDirectSoundBuffer_Unlock(buffer, lpvPtr1, dwBytes1,
312 lpvPtr2, dwBytes2);
313 }
314 }
315
dx_init(const char * param,int * speed,int * fragsize,int * fragnr,int * channels)316 static int dx_init(const char *param, int *speed, int *fragsize, int *fragnr,
317 int *channels)
318 {
319 HRESULT result;
320
321 SDEBUG(("DirectSound driver initialization: speed = %d, fragsize = %d, fragnr = %d, channels = %d\n",
322 *speed, *fragsize, *fragnr, *channels));
323
324 if (ds == NULL) {
325 #ifdef HAVE_DSOUND_LIB
326 result = DirectSoundCreate(NULL, &ds, NULL);
327 #else
328 result = CoCreateInstance(&CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER, &IID_IDirectSound, (PVOID*)&ds);
329 if (result == S_OK) {
330 result = IDirectSound_Initialize(ds, NULL);
331 }
332 #endif
333 if (result != DS_OK) {
334 ui_error("Cannot initialize DirectSound:\n%s", ds_error(result));
335 return -1;
336 }
337
338 if (console_mode || video_disabled_mode) {
339 result = IDirectSound_SetCooperativeLevel(ds, GetForegroundWindow() ? GetForegroundWindow() : GetDesktopWindow(), DSSCL_PRIORITY);
340 } else {
341 result = IDirectSound_SetCooperativeLevel(ds, ui_get_main_hwnd(), DSSCL_PRIORITY);
342 }
343
344 if (result != DS_OK) {
345 log_error(LOG_DEFAULT, "Cannot set cooperative level:\n%s", ds_error(result));
346 return -1;
347 }
348 }
349
350 memset(&capabilities, 0, sizeof(DSCAPS));
351 capabilities.dwSize = sizeof(DSCAPS);
352
353 IDirectSound_GetCaps(ds, &capabilities);
354 if ((capabilities.dwFlags & DSCAPS_PRIMARY16BIT)
355 || (capabilities.dwFlags & DSCAPS_SECONDARY16BIT)) {
356 is16bit = 1;
357 } else {
358 is16bit = 0;
359 }
360 if (!((capabilities.dwFlags & DSCAPS_PRIMARYSTEREO) || (capabilities.dwFlags & DSCAPS_SECONDARYSTEREO))) {
361 *channels = 1;
362 }
363 num_of_channels = *channels;
364
365 SDEBUG(("16bit flag: %d", is16bit));
366 SDEBUG(("Channels: %d", *channels));
367 SDEBUG(("Capabilities %08x", capabilities.dwFlags));
368 SDEBUG(("Secondary min Hz: %d", capabilities.dwMinSecondarySampleRate));
369 SDEBUG(("Secondary max Hz: %d", capabilities.dwMaxSecondarySampleRate));
370
371 memset(&pcmwf, 0, sizeof(PCMWAVEFORMAT));
372 pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM;
373 pcmwf.wf.nChannels = *channels;
374 pcmwf.wf.nSamplesPerSec = *speed;
375 pcmwf.wBitsPerSample = is16bit ? 16 : 8;
376 /* Hack to fix if mmsystem header is bad
377 ((WORD*)&pcmwf)[7] = 16;
378 */
379 pcmwf.wf.nBlockAlign = (is16bit ? 2 : 1) * *channels;
380 pcmwf.wf.nAvgBytesPerSec = pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;
381
382 memset(&desc, 0, sizeof(DSBUFFERDESC));
383 desc.dwSize = sizeof(DSBUFFERDESC);
384 desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
385
386 fragment_size = *fragsize; /* frames */
387 buffer_size = *fragsize * *fragnr * (is16bit ? 2 : 1) * *channels; /* bytes */
388 stream_buffer_size = fragment_size * *fragnr * *channels; /* nr of samples */
389 buffer_offset = 0; /* bytes */
390
391 result = IDirectSound_CreateSoundBuffer(ds, &desc, &pbuffer, NULL);
392
393 if (result != DS_OK) {
394 ui_error("Cannot create Primary DirectSound bufer: %s",
395 ds_error(result));
396 return -1;
397 }
398
399 memset(&desc, 0, sizeof(DSBUFFERDESC));
400 desc.dwSize = sizeof(DSBUFFERDESC);
401 desc.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2
402 | DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLPAN
403 | DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS;
404
405 desc.dwBufferBytes = buffer_size;
406 desc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf;
407
408 result = IDirectSound_CreateSoundBuffer(ds, &desc, &buffer, NULL);
409 if (result != DS_OK) {
410 ui_error("Cannot create DirectSound buffer:\n%s", ds_error(result));
411 return -1;
412 }
413
414 memset(&wfex, 0, sizeof(WAVEFORMATEX));
415 wfex.wFormatTag = WAVE_FORMAT_PCM;
416 wfex.nChannels = *channels;
417 wfex.nSamplesPerSec = *speed;
418 wfex.wBitsPerSample = is16bit ? 16 : 8;
419 wfex.nBlockAlign = (is16bit ? 2 : 1) * *channels;
420 wfex.nAvgBytesPerSec = wfex.nSamplesPerSec * wfex.nBlockAlign;
421
422 result = IDirectSoundBuffer_SetFormat(pbuffer, &wfex);
423 if (result != DS_OK) {
424 ui_error("Cannot set Output format for primary sound buffer:\n%s",
425 ds_error(result));
426 return -1;
427 }
428
429 dx_clear();
430 /* Let's go... */
431 result = IDirectSoundBuffer_Play(buffer, 0, 0, DSBPLAY_LOOPING);
432 if (result == DSERR_BUFFERLOST) {
433 ui_error("Restoring DirectSound buffer.");
434 if ((result = IDirectSoundBuffer_Restore(buffer)) != DS_OK) {
435 ui_error("Cannot restore buffer:\n%s", ds_error(result));
436 }
437 result = IDirectSoundBuffer_Play(buffer, 0, 0, DSBPLAY_LOOPING);
438 }
439 if (result != DS_OK) {
440 ui_error("Cannot play DirectSound buffer:\n%s", ds_error(result));
441 return -1;
442 }
443
444 SDEBUG(("DirectSound initialization done succesfully.\n"));
445
446 return 0;
447 }
448
dx_close(void)449 static void dx_close(void)
450 {
451 /* Stop buffer play */
452 if (ds == NULL) {
453 return;
454 }
455
456 IDirectSoundBuffer_Stop(buffer);
457
458 /* Release buffer */
459 IDirectSoundBuffer_Release(buffer);
460 /* Release DirectSoundObject */
461 IDirectSound_Release(ds);
462 buffer = NULL;
463 ds = NULL;
464 }
465
dx_bufferspace(void)466 static int dx_bufferspace(void)
467 {
468 DWORD play_cursor;
469 int free_samples, buffer_samples;
470
471 IDirectSoundBuffer_GetCurrentPosition(buffer, &play_cursor, NULL);
472 /* We should properly distinguish between buffer empty and buffer fill
473 * case. However, it's absolutely essential that the state where play and
474 * write cursors overlap is read as buffer being filled with data. */
475 if (play_cursor < buffer_offset) {
476 free_samples = buffer_size - (buffer_offset - play_cursor);
477 } else {
478 free_samples = play_cursor - buffer_offset;
479 }
480
481 SDEBUG(("play=%d, ourwrite=%d, free=%d", play_cursor, buffer_offset, free_samples));
482
483 free_samples /= (is16bit ? 2 : 1) * num_of_channels;
484
485 /* test for underrun condition. It generally looks like we suddenly we have
486 * a filled buffer instead of nearly empty one, because the play cursor
487 * stepped over the write cursor. We generally have sound core calling
488 * bufferspace() with every write, so we can rely on frequent calls here.
489 */
490 buffer_samples = buffer_size / (is16bit ? 2 : 1) / num_of_channels;
491 if (free_samples < fragment_size
492 && earlier_bufferspace > buffer_samples - fragment_size) {
493 /* don't trigger again */
494 earlier_bufferspace = 0;
495 /* report underrun */
496 return buffer_samples;
497 }
498 earlier_bufferspace = free_samples;
499
500 return free_samples;
501 }
502
dx_write(int16_t * pbuf,size_t nr)503 static int dx_write(int16_t *pbuf, size_t nr)
504 {
505 LPVOID lpvPtr1;
506 DWORD dwBytes1;
507 LPVOID lpvPtr2;
508 DWORD dwBytes2;
509 HRESULT result;
510 DWORD buffer_lock_size; /* buffer_lock_end; */
511 unsigned int i, count;
512
513 count = (unsigned int)nr / fragment_size;
514 buffer_lock_size = fragment_size * (is16bit ? 2 : 1);
515
516 /* Write one fragment at a time. FIXME: This could be faster. */
517 for (i = 0; i < count; i++) {
518 /* lock buffer for writing */
519 do {
520 result = IDirectSoundBuffer_Lock(buffer, buffer_offset,
521 buffer_lock_size,
522 &lpvPtr1, &dwBytes1, &lpvPtr2,
523 &dwBytes2, 0);
524
525 if (result == DSERR_BUFFERLOST) {
526 IDirectSoundBuffer_Restore(buffer);
527 dwBytes1 = dwBytes2 = 0;
528 }
529 } while (dwBytes1 + dwBytes2 != buffer_lock_size);
530
531 /* put data as-is, or convert to 8 bits first */
532 if (is16bit) {
533 memcpy(lpvPtr1, pbuf, dwBytes1);
534 if (lpvPtr2) {
535 memcpy(lpvPtr2, (BYTE *)pbuf + dwBytes1, dwBytes2);
536 }
537 pbuf += fragment_size;
538 } else {
539 for (i = 0; i < dwBytes1; i++) {
540 ((BYTE *)lpvPtr1)[i] = (*(pbuf++) >> 8) + 0x80;
541 }
542 if (lpvPtr2 != NULL) {
543 for (i = 0; i < dwBytes2; i++) {
544 ((BYTE *)lpvPtr2)[i] = (*(pbuf++) >> 8) + 0x80;
545 }
546 }
547 }
548
549 /* done. */
550 result = IDirectSoundBuffer_Unlock(buffer, lpvPtr1, dwBytes1,
551 lpvPtr2, dwBytes2);
552 buffer_offset += buffer_lock_size;
553
554 /* loop */
555 if (buffer_offset == buffer_size) {
556 buffer_offset = 0;
557 }
558 }
559
560 pbuf -= num_of_channels;
561
562 for (i = 0; i < (unsigned int)num_of_channels; i++) {
563 last_buffered_sample[i] = *pbuf++;
564 }
565
566 return 0;
567 }
568
dx_suspend(void)569 static int dx_suspend(void)
570 {
571 int i;
572 int16_t *p = lib_malloc(stream_buffer_size * sizeof(int16_t));
573
574 if (!p) {
575 return 0;
576 }
577
578 for (i = 0; i < stream_buffer_size; i++) {
579 p[i] = last_buffered_sample[i % num_of_channels];
580 }
581
582 i = dx_write(p, stream_buffer_size);
583 lib_free(p);
584
585 return 0;
586 }
587
588
dx_resume(void)589 static int dx_resume(void)
590 {
591 buffer_offset = 0;
592 IDirectSoundBuffer_Play(buffer, 0, 0, DSBPLAY_LOOPING);
593 return 0;
594 }
595
596 static sound_device_t dx_device =
597 {
598 "dx",
599 dx_init,
600 dx_write,
601 NULL,
602 NULL,
603 dx_bufferspace,
604 dx_close,
605 dx_suspend,
606 dx_resume,
607 0,
608 2, /* FIXME: should account for mono and stereo devices */
609 true
610 };
611
sound_init_dx_device(void)612 int sound_init_dx_device(void)
613 {
614 return sound_register_device(&dx_device);
615 }
616
617 #endif /*USE_DXSOUND*/
618