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