1 /*
2  * sound.c - Win32 port specific code
3  *
4  * Copyright (C) 2000 Krzysztof Nikiel
5  * Copyright (C) 2000-2003 Atari800 development team (see DOC/CREDITS)
6  *
7  * This file is part of the Atari800 emulator project which emulates
8  * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers.
9  *
10  * Atari800 is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * Atari800 is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Atari800; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 */
24 
25 #include "config.h"
26 
27 #ifdef SOUND
28 
29 #include <windows.h>
30 #ifdef DIRECTX
31 # define DIRECTSOUND_VERSION 0x0500
32 # include <dsound.h>
33 #endif
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include "sound.h"
37 #include "main.h"
38 #include "pokeysnd.h"
39 #include "atari.h"
40 #include "log.h"
41 
42 #define MIXBUFSIZE	0x1000
43 #define WAVSHIFT	9
44 #define WAVSIZE		(1 << WAVSHIFT)
45 int buffers = 0;
46 
47 enum {SOUND_NONE, SOUND_DX, SOUND_WAV};
48 
49 static int usesound = TRUE;
50 static int issound = SOUND_NONE;
51 static int dsprate = 44100;
52 static int snddelay = 40;	/* delay in milliseconds */
53 static int snddelaywav = 100;
54 static int bit16 = FALSE;
55 
56 static HANDLE event;
57 static HWAVEOUT wout;
58 static WAVEHDR *waves;
59 
60 #ifdef DIRECTX
61 static int wavonly = FALSE;
62 static DWORD sbufsize = 0;
63 static int samples = 0; 	/* #samples to be in play buffer */
64 static UBYTE mixbuf[MIXBUFSIZE];
65 static DWORD bufpos = 0;
66 
67 static LPDIRECTSOUND lpDS = NULL;
68 static LPDIRECTSOUNDBUFFER pDSB = NULL;
69 
initsound_dx(void)70 static int initsound_dx(void)
71 {
72   DWORD i;
73   int err;
74   DSBUFFERDESC dsBD =
75   {0};
76   WAVEFORMATEX wfx;
77   DSBCAPS bc;
78 
79   LPVOID pMem1, pMem2;
80   DWORD dwSize1, dwSize2;
81 
82   if (issound != SOUND_NONE)
83     return 0;
84 
85   if ((err = DirectSoundCreate(NULL, &lpDS, NULL)) < 0)
86     return err;
87   if ((err = IDirectSound_SetCooperativeLevel(lpDS, hWndMain,
88 					      DSSCL_EXCLUSIVE)) < 0)
89     goto end;
90 
91   dsBD.dwSize = sizeof(dsBD);
92   dsBD.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
93   dsBD.dwBufferBytes = 0x4000;
94   dsBD.lpwfxFormat = &wfx;
95 
96   wfx.wFormatTag = WAVE_FORMAT_PCM;
97 #ifdef STEREO_SOUND
98   wfx.nChannels = POKEYSND_stereo_enabled ? 2 : 1;
99 #else
100   wfx.nChannels = 1;
101 #endif /* STEREO_SOUND */
102   wfx.nSamplesPerSec = dsprate;
103   wfx.nAvgBytesPerSec = dsprate * wfx.nChannels * (bit16 ? 2 : 1);
104   wfx.nBlockAlign = wfx.nChannels * (bit16 ? 2 : 1);
105   wfx.wBitsPerSample = bit16 ? 16 : 8;
106   wfx.cbSize = 0;
107 
108   if ((err = IDirectSound_CreateSoundBuffer(lpDS, &dsBD, &pDSB, NULL)) < 0)
109     goto end;
110 
111   bc.dwSize = sizeof(bc);
112   IDirectSoundBuffer_GetCaps(pDSB, &bc);
113   sbufsize = bc.dwBufferBytes;
114 
115   if ((err = IDirectSoundBuffer_Lock(pDSB, 0, sbufsize,
116 			       &pMem1, &dwSize1, &pMem2, &dwSize2,
117 			       DSBLOCK_FROMWRITECURSOR)) < 0)
118     goto end;
119 
120   for (i = 0; i < dwSize1 >> 1; i++)
121     *((short *) pMem1 + i) = 0;
122   for (i = 0; i < dwSize2 >> 1; i++)
123     *((short *) pMem2 + i) = 0;
124   IDirectSoundBuffer_Unlock(pDSB, pMem1, dwSize1, pMem2, dwSize2);
125 
126   IDirectSoundBuffer_Play(pDSB, 0, 0, DSBPLAY_LOOPING);
127 
128   POKEYSND_Init(POKEYSND_FREQ_17_EXACT, (UWORD) dsprate, (UBYTE) wfx.nChannels, (bit16 ? POKEYSND_BIT16 : 0));
129 
130   samples = dsprate * snddelay / 1000;
131 
132   IDirectSoundBuffer_GetCurrentPosition(pDSB, 0, &bufpos);
133 
134   issound = SOUND_DX;
135   return 0;
136 
137 end:
138   MessageBox(hWndMain, "DirectSound Init FAILED", myname, MB_OK);
139   Sound_Exit();
140   return err;
141 }
142 
uninitsound_dx(void)143 static void uninitsound_dx(void)
144 {
145   if (issound != SOUND_DX)
146     return;
147 
148   if (lpDS)
149     {
150       if (pDSB)
151 	{
152 	  IDirectSoundBuffer_Stop(pDSB);
153 	  IDirectSoundBuffer_Release(pDSB);
154 	  pDSB = NULL;
155 	}
156       IDirectSound_Release(lpDS);
157       lpDS = NULL;
158     }
159   issound = SOUND_NONE;
160 }
161 
sound_update_dx(void)162 static void sound_update_dx(void)
163 {
164   DWORD wc;
165   int d1;
166   LPVOID pMem1, pMem2;
167   DWORD dwSize1, dwSize2;
168   signed short *p1s;
169   signed short *p2s;
170   UBYTE *p1b;
171   UBYTE *p2b;
172   signed short *pbufs;
173   UBYTE *pbufb;
174   int s1, s2;
175   int err;
176   int i;
177   int samplesize = bit16 ? 2 : 1;
178 
179 #ifdef STEREO_SOUND
180   if (POKEYSND_stereo_enabled) samplesize *= 2;
181 #endif
182 
183   if (issound != SOUND_DX)
184     return;
185 
186   IDirectSoundBuffer_GetCurrentPosition(pDSB, 0, &wc);
187 
188   d1 = (wc - bufpos);
189 	if ((DWORD) abs(d1) > (sbufsize >> 1)) {
190 		if (d1 < 0)
191 			d1 += sbufsize;
192 		else
193 			d1 -= sbufsize;
194 	}
195   if (d1 < (-samples * samplesize)) // there is more than necessary bytes filled?
196     return;
197 
198   d1 = (samples * samplesize) + d1; // bytes to fill
199   d1 = (d1 / samplesize) * samplesize; //round to a sample pair
200 
201   if (d1 > (sizeof(mixbuf) / sizeof(mixbuf[0])))
202     {
203       d1 = (sizeof(mixbuf) / sizeof(mixbuf[0]));
204     }
205 
206   if ((err = IDirectSoundBuffer_Lock(pDSB,
207 				     bufpos,
208 				     d1,
209 				     &pMem1, &dwSize1,
210 				     &pMem2, &dwSize2,
211 				     0)) < 0)
212     {
213       if (err == DSERR_BUFFERLOST)
214 	Sound_Continue();
215       return;
216     }
217 
218   s1 = dwSize1;
219   s2 = dwSize2;
220   p1s = (signed short *)pMem1;
221   p2s = (signed short *)pMem2;
222   p1b = (UBYTE *)pMem1;
223   p2b = (UBYTE *)pMem2;
224   bufpos += (s1 + s2);
225   if (bufpos >= sbufsize)
226     bufpos -= sbufsize;
227 
228   if (bit16)
229   {
230     s1 /= 2;
231     s2 /= 2;
232   }
233   i = s1 + s2;
234 
235   POKEYSND_Process(mixbuf, i);
236 
237   pbufs = (signed short *)mixbuf;
238   pbufb = (UBYTE *)mixbuf;
239   if (s1)
240     {
241       if (bit16)
242       for (; s1; s1--)
243 	  *p1s++ = *pbufs++;
244       else
245 	for (; s1; s1--)
246 	  *p1b++ = *pbufb++;
247     }
248   if (s2)
249     {
250       if (bit16)
251       for (; s2; s2--)
252 	  *p2s++ = *pbufs++;
253       else
254 	for (; s2; s2--)
255 	  *p2b++ = *pbufb++;
256     }
257 
258   IDirectSoundBuffer_Unlock(pDSB, pMem1, dwSize1, pMem2, dwSize2);
259 
260   return;
261 }
262 #endif /* DIRECTX */
263 
uninitsound_wav(void)264 static void uninitsound_wav(void)
265 {
266   int i;
267   MMRESULT err;
268 
269   if (issound != SOUND_WAV)
270     return;
271 
272 l0:
273   for (i = 0; i < buffers; i++)
274     {
275       if (!(waves[i].dwFlags & WHDR_DONE))
276 	{
277 	  WaitForSingleObject(event, 5000);
278 	  ResetEvent(event);
279 	  goto l0;
280 	}
281     }
282 
283   waveOutReset (wout);
284 
285   for (i = 0; i < buffers; i++)
286     {
287       err = waveOutUnprepareHeader(wout, &waves[i], sizeof (waves[i]));
288       if (err != MMSYSERR_NOERROR)
289 	{
290 	  fprintf(stderr, "warning: cannot unprepare wave header (%x)\n", err);
291 	}
292       free(waves[i].lpData);
293     }
294   free(waves);
295 
296   waveOutClose(wout);
297   CloseHandle(event);
298 
299   issound = SOUND_NONE;
300 }
301 
initsound_wav(void)302 static int initsound_wav(void)
303 {
304   int i;
305   WAVEFORMATEX wfx;
306   MMRESULT err;
307 
308   event = CreateEvent(NULL, TRUE, FALSE, NULL);
309 
310   memset(&wfx, 0, sizeof(wfx));
311 
312   wfx.wFormatTag = WAVE_FORMAT_PCM;
313 #ifdef STEREO_SOUND
314   wfx.nChannels = POKEYSND_stereo_enabled ? 2 : 1;
315 #else
316   wfx.nChannels = 1;
317 #endif /* STEREO_SOUND */
318   wfx.nSamplesPerSec = dsprate;
319   wfx.nAvgBytesPerSec = dsprate * wfx.nChannels * (bit16 ? 2 : 1);
320   wfx.nBlockAlign = wfx.nChannels * (bit16 ? 2 : 1);
321   wfx.wBitsPerSample = bit16 ? 16 : 8;
322   wfx.cbSize = 0;
323 
324   err = waveOutOpen(&wout, WAVE_MAPPER, &wfx, (int)event, 0, CALLBACK_EVENT);
325   if (err == WAVERR_BADFORMAT)
326     {
327       Log_print("wave output parameters unsupported\n");
328       exit(1);
329     }
330   if (err != MMSYSERR_NOERROR)
331     {
332       Log_print("cannot open wave output (%x)\n", err);
333       exit(1);
334     }
335 
336   buffers = ((wfx.nAvgBytesPerSec * snddelaywav / 1000) >> WAVSHIFT) + 1;
337   waves = malloc(buffers * sizeof(*waves));
338   for (i = 0; i < buffers; i++)
339     {
340       memset(&waves[i], 0, sizeof (waves[i]));
341       if (!(waves[i].lpData = (UBYTE *)malloc(WAVSIZE)))
342 	{
343 	  Log_print("could not get wave buffer memory\n");
344 	  exit(1);
345 	}
346       waves[i].dwBufferLength = WAVSIZE;
347       err = waveOutPrepareHeader(wout, &waves[i], sizeof(waves[i]));
348       if (err != MMSYSERR_NOERROR)
349 	{
350 	  Log_print("cannot prepare wave header (%x)\n", err);
351 	  exit(1);
352 	}
353       waves[i].dwFlags |= WHDR_DONE;
354     }
355 
356   POKEYSND_Init(POKEYSND_FREQ_17_EXACT, (UWORD) dsprate, (UBYTE) wfx.nChannels, (bit16 ? POKEYSND_BIT16 : 0));
357 
358   issound = SOUND_WAV;
359   return 0;
360 }
361 
Sound_Initialise(int * argc,char * argv[])362 int Sound_Initialise(int *argc, char *argv[])
363 {
364   int i, j;
365   int help = FALSE;
366 
367   if (issound != SOUND_NONE)
368     return TRUE;
369 
370   for (i = j = 1; i < *argc; i++)
371     {
372       int i_a = (i + 1 < *argc); /* is argument available? */
373       int a_m = FALSE; /* error, argument missing! */
374 
375       if (strcmp(argv[i], "-sound") == 0)
376 	usesound = TRUE;
377       else if (strcmp(argv[i], "-nosound") == 0)
378 	usesound = FALSE;
379       else if (strcmp(argv[i], "-audio16") == 0)
380 	bit16 = TRUE;
381       else if (strcmp(argv[i], "-dsprate") == 0)
382       {
383 	if (i_a)
384 	  sscanf(argv[++i], "%d", &dsprate);
385 	else a_m = TRUE;
386       }
387       else if (strcmp(argv[i], "-snddelay") == 0)
388       {
389 	if (i_a)
390 	{
391 	  sscanf(argv[++i], "%d", &snddelay);
392 	  snddelaywav = snddelay;
393 	}
394 	else a_m = TRUE;
395       }
396       else if (strcmp(argv[i], "-quality") == 0) {
397 	if (i_a) {
398 	  int quality;
399 	  sscanf(argv[++i], "%d", &quality);
400 	  if (quality > 1) {
401 	    POKEYSND_SetMzQuality(quality - 1);
402 	    POKEYSND_enable_new_pokey = 1;
403 	  }
404 	  else
405 	    POKEYSND_enable_new_pokey = 0;
406 	}
407 	else a_m = TRUE;
408       }
409 #ifdef DIRECTX
410       else if (strcmp(argv[i], "-wavonly") == 0)
411 	wavonly = TRUE;
412 #endif
413       else
414       {
415 	if (strcmp(argv[i], "-help") == 0)
416 	{
417 	  Log_print("\t-sound           Enable sound\n"
418 		 "\t-nosound         Disable sound\n"
419 #ifdef DIRECTX
420 		 "\t-wavonly         Disable direct sound\n"
421 #endif
422 		 "\t-dsprate <rate>  Set dsp rate\n"
423 		 "\t-snddelay <ms>   Set sound delay\n"
424 		 "\t-audio16         Use 16 bit mixing\n"
425 		 "\t-quality <level> Set sound quality"
426 		);
427 	  help = TRUE;
428 	}
429 	argv[j++] = argv[i];
430       }
431 
432       if (a_m) {
433 	Log_print("Missing argument for '%s'", argv[i]);
434 	usesound = FALSE;
435 	return FALSE;
436       }
437     }
438   *argc = j;
439 
440   if (help || !usesound) {
441     usesound = FALSE;
442     return TRUE;
443   }
444 
445 #ifdef DIRECTX
446   if (!wavonly)
447   {
448     i = initsound_dx();
449     if (!i)
450       return TRUE;
451   }
452 #endif
453   initsound_wav();
454   return TRUE;
455 }
456 
Sound_Reinit(void)457 void Sound_Reinit(void)
458 {
459   if (usesound)
460   {
461   Sound_Exit();
462 #ifdef DIRECTX
463   if (!wavonly)
464   {
465     int i = initsound_dx();
466     if (!i)
467       return;
468   }
469 #endif
470   initsound_wav();
471   }
472   return;
473 }
474 
Sound_Exit(void)475 void Sound_Exit(void)
476 {
477 #ifdef DIRECTX
478   if (issound == SOUND_DX)
479     uninitsound_dx();
480 #endif
481   if (issound == SOUND_WAV)
482     uninitsound_wav();
483 
484   issound = SOUND_NONE;
485 }
486 
getwave(void)487 static WAVEHDR *getwave(void)
488 {
489   int i;
490 
491   for (i = 0; i < buffers; i++)
492     {
493       if (waves[i].dwFlags & WHDR_DONE)
494 	{
495 	  waves[i].dwFlags &= ~WHDR_DONE;
496 	  return &waves[i];
497 	}
498     }
499 
500   return NULL;
501 }
502 
Sound_Update(void)503 void Sound_Update(void)
504 {
505   MMRESULT err;
506   WAVEHDR *wh;
507 
508   switch (issound)
509   {
510   case SOUND_WAV:
511     while ((wh = getwave()))
512     {
513       POKEYSND_Process(wh->lpData, wh->dwBufferLength >> (bit16 ? 1 : 0));
514       err = waveOutWrite(wout, wh, sizeof(*wh));
515       if (err != MMSYSERR_NOERROR)
516       {
517 	Log_print("cannot write wave output (%x)\n", err);
518 	return;
519       }
520     }
521     break;
522 #ifdef DIRECTX
523   case SOUND_DX:
524     sound_update_dx();
525 #endif
526   }
527 }
528 
Sound_Pause(void)529 void Sound_Pause(void)
530 {
531 #ifdef DIRECTX
532   if (issound != SOUND_DX)
533     return;
534   if (!pDSB)
535     return;
536   IDirectSoundBuffer_Stop(pDSB);
537 #endif
538 }
539 
Sound_Continue(void)540 void Sound_Continue(void)
541 {
542 #ifdef DIRECTX
543   if (issound != SOUND_DX)
544     return;
545   if (!pDSB)
546     return;
547   IDirectSoundBuffer_Restore(pDSB);
548   IDirectSoundBuffer_Play(pDSB, 0, 0, DSBPLAY_LOOPING);
549   IDirectSoundBuffer_GetCurrentPosition(pDSB, 0, &bufpos);
550 #endif
551 }
552 
553 #endif	/* SOUND */
554