1 /*
2 	snd_dx.c
3 
4 	(description)
5 
6 	Copyright (C) 1996-1997  Id Software, Inc.
7 
8 	This program is free software; you can redistribute it and/or
9 	modify it under the terms of the GNU General Public License
10 	as published by the Free Software Foundation; either version 2
11 	of the License, or (at your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 	See the GNU General Public License for more details.
18 
19 	You should have received a copy of the GNU General Public License
20 	along with this program; if not, write to:
21 
22 		Free Software Foundation, Inc.
23 		59 Temple Place - Suite 330
24 		Boston, MA  02111-1307, USA
25 
26 */
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 
31 #define CINTERFACE
32 
33 #include "winquake.h"
34 #include "QF/cvar.h"
35 #include "QF/qargs.h"
36 #include "QF/sys.h"
37 
38 #include "snd_internal.h"
39 
40 #define iDirectSoundCreate(a,b,c)	pDirectSoundCreate(a,b,c)
41 
42 HRESULT (WINAPI * pDirectSoundCreate) (GUID FAR * lpGUID,
43 									   LPDIRECTSOUND FAR * lplpDS,
44 									   IUnknown FAR * pUnkOuter);
45 
46 // 64K is > 1 second at 16-bit, 22050 Hz
47 #define	WAV_BUFFERS				64
48 #define	WAV_MASK				0x3F
49 #define	WAV_BUFFER_SIZE			0x0400
50 #define SECONDARY_BUFFER_SIZE	0x10000
51 
52 typedef enum { SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL } sndinitstat;
53 
54 static qboolean dsound_init;
55 static qboolean snd_firsttime = true;
56 static qboolean primary_format_set;
57 
58 static int  sample16;
59 static volatile dma_t sn;
60 
61 /*
62   Global variables. Must be visible to window-procedure function
63   so it can unlock and free the data block after it has been played.
64 */
65 
66 static HANDLE      hData;
67 static HPSTR       lpData;//, lpData2;
68 
69 static HGLOBAL     hWaveHdr;
70 static LPWAVEHDR   lpWaveHdr;
71 
72 static HWAVEOUT    hWaveOut;
73 
74 //static WAVEOUTCAPS wavecaps;
75 
76 static DWORD       gSndBufSize;
77 
78 static MMTIME      mmstarttime;
79 
80 static LPDIRECTSOUND pDS;
81 static LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf;
82 
83 static HINSTANCE   hInstDS;
84 
85 static sndinitstat SNDDMA_InitDirect (void);
86 
87 static cvar_t	   *snd_stereo;
88 static cvar_t	   *snd_rate;
89 static cvar_t	   *snd_bits;
90 
91 static plugin_t				plugin_info;
92 static plugin_data_t		plugin_info_data;
93 static plugin_funcs_t		plugin_info_funcs;
94 static general_data_t		plugin_info_general_data;
95 static general_funcs_t		plugin_info_general_funcs;
96 static snd_output_data_t	plugin_info_snd_output_data;
97 static snd_output_funcs_t	plugin_info_snd_output_funcs;
98 
99 
100 static void
SNDDMA_Init_Cvars(void)101 SNDDMA_Init_Cvars (void)
102 {
103 	snd_stereo = Cvar_Get ("snd_stereo", "1", CVAR_ROM, NULL,
104 						   "sound stereo output");
105 	snd_rate = Cvar_Get ("snd_rate", "11025", CVAR_ROM, NULL,
106 						 "sound playback rate. 0 is system default");
107 	snd_bits = Cvar_Get ("snd_bits", "16", CVAR_ROM, NULL,
108 						 "sound sample depth. 0 is system default");
109 }
110 
111 static void
SNDDMA_BlockSound(void)112 SNDDMA_BlockSound (void)
113 {
114 }
115 
116 static void
SNDDMA_UnblockSound(void)117 SNDDMA_UnblockSound (void)
118 {
119 }
120 
121 static void
FreeSound(void)122 FreeSound (void)
123 {
124 	if (pDSBuf) {
125 		IDirectSoundBuffer_Stop (pDSBuf);
126 		IDirectSound_Release (pDSBuf);
127 	}
128 // release primary buffer only if it's not also the mixing buffer we just released
129 	if (pDSPBuf && (pDSBuf != pDSPBuf)) {
130 		IDirectSound_Release (pDSPBuf);
131 	}
132 
133 	if (pDS) {
134 		IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_NORMAL);
135 		IDirectSound_Release (pDS);
136 	}
137 	pDS = NULL;
138 	pDSBuf = NULL;
139 	pDSPBuf = NULL;
140 	hWaveOut = 0;
141 	hData = 0;
142 	hWaveHdr = 0;
143 	lpData = NULL;
144 	lpWaveHdr = NULL;
145 }
146 
147 /*
148 	SNDDMA_InitDirect
149 
150 	Direct-Sound support
151 */
152 static sndinitstat
SNDDMA_InitDirect(void)153 SNDDMA_InitDirect (void)
154 {
155 	int				reps;
156 	DSBUFFERDESC	dsbuf;
157 	DSBCAPS			dsbcaps;
158 	DSCAPS			dscaps;
159 	DWORD			dwSize, dwWrite;
160 	HRESULT			hresult;
161 	WAVEFORMATEX	format, pformat;
162 
163 	memset ((void *) &sn, 0, sizeof (sn));
164 
165 	if (!snd_stereo->int_val) {
166 		sn.channels = 1;
167 	} else {
168 		sn.channels = 2;
169 	}
170 
171 	sn.samplebits = snd_bits->int_val;
172 	sn.speed = snd_rate->int_val;
173 
174 	memset (&format, 0, sizeof (format));
175 	format.wFormatTag = WAVE_FORMAT_PCM;
176 	format.nChannels = sn.channels;
177 	format.wBitsPerSample = sn.samplebits;
178 	format.nSamplesPerSec = sn.speed;
179 	format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
180 	format.cbSize = 0;
181 	format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
182 
183 	if (!hInstDS) {
184 		hInstDS = LoadLibrary ("dsound.dll");
185 
186 		if (hInstDS == NULL) {
187 			Sys_Printf ("Couldn't load dsound.dll\n");
188 			return SIS_FAILURE;
189 		}
190 
191 		pDirectSoundCreate =
192 			(void *) GetProcAddress (hInstDS, "DirectSoundCreate");
193 
194 		if (!pDirectSoundCreate) {
195 			Sys_Printf ("Couldn't get DS proc addr\n");
196 			return SIS_FAILURE;
197 		}
198 	}
199 
200 	while ((hresult = iDirectSoundCreate (NULL, &pDS, NULL)) != DS_OK) {
201 		if (hresult != DSERR_ALLOCATED) {
202 			Sys_Printf ("DirectSound create failed\n");
203 			return SIS_FAILURE;
204 		}
205 		Sys_Printf ("DirectSoundCreate failure\n"
206 					"  hardware already in use\n");
207 		return SIS_NOTAVAIL;
208 	}
209 
210 	dscaps.dwSize = sizeof (dscaps);
211 	if (DS_OK != IDirectSound_GetCaps (pDS, &dscaps)) {
212 		Sys_Printf ("Couldn't get DS caps\n");
213 	}
214 
215 	if (dscaps.dwFlags & DSCAPS_EMULDRIVER) {
216 		Sys_Printf ("No DirectSound driver installed\n");
217 		FreeSound ();
218 		return SIS_FAILURE;
219 	}
220 
221 	if (DS_OK !=
222 		IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_EXCLUSIVE)) {
223 		Sys_Printf ("Set coop level failed\n");
224 		FreeSound ();
225 		return SIS_FAILURE;
226 	}
227 	// get access to the primary buffer, if possible, so we can set the
228 	// sound hardware format
229 	memset (&dsbuf, 0, sizeof (dsbuf));
230 	dsbuf.dwSize = sizeof (DSBUFFERDESC);
231 	dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
232 	dsbuf.dwBufferBytes = 0;
233 	dsbuf.lpwfxFormat = NULL;
234 
235 	memset (&dsbcaps, 0, sizeof (dsbcaps));
236 	dsbcaps.dwSize = sizeof (dsbcaps);
237 	primary_format_set = false;
238 
239 	if (!COM_CheckParm ("-snoforceformat")) {
240 		if (DS_OK ==
241 			IDirectSound_CreateSoundBuffer (pDS, &dsbuf, &pDSPBuf, NULL)) {
242 			pformat = format;
243 
244 			if (DS_OK != IDirectSoundBuffer_SetFormat (pDSPBuf, &pformat)) {
245 			} else
246 				primary_format_set = true;
247 		}
248 	}
249 
250 	if (!primary_format_set || !COM_CheckParm ("-primarysound")) {
251 		// create the secondary buffer we'll actually work with
252 		memset (&dsbuf, 0, sizeof (dsbuf));
253 		dsbuf.dwSize = sizeof (DSBUFFERDESC);
254 		dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE;
255 		dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
256 		dsbuf.lpwfxFormat = &format;
257 
258 		memset (&dsbcaps, 0, sizeof (dsbcaps));
259 		dsbcaps.dwSize = sizeof (dsbcaps);
260 
261 		if (DS_OK !=
262 			IDirectSound_CreateSoundBuffer (pDS, &dsbuf, &pDSBuf, NULL)) {
263 			Sys_Printf ("DS:CreateSoundBuffer Failed");
264 			FreeSound ();
265 			return SIS_FAILURE;
266 		}
267 
268 		sn.channels = format.nChannels;
269 		sn.samplebits = format.wBitsPerSample;
270 		sn.speed = format.nSamplesPerSec;
271 
272 		if (DS_OK != IDirectSound_GetCaps (pDSBuf, &dsbcaps)) {
273 			Sys_Printf ("DS:GetCaps failed\n");
274 			FreeSound ();
275 			return SIS_FAILURE;
276 		}
277 	} else {
278 		if (DS_OK !=
279 			IDirectSound_SetCooperativeLevel (pDS, mainwindow,
280 											  DSSCL_WRITEPRIMARY)) {
281 			Sys_Printf ("Set coop level failed\n");
282 			FreeSound ();
283 			return SIS_FAILURE;
284 		}
285 
286 		if (DS_OK != IDirectSound_GetCaps (pDSPBuf, &dsbcaps)) {
287 			Sys_Printf ("DS:GetCaps failed\n");
288 			return SIS_FAILURE;
289 		}
290 
291 		pDSBuf = pDSPBuf;
292 	}
293 
294 	// Make sure mixer is active
295 	IDirectSoundBuffer_Play (pDSBuf, 0, 0, DSBPLAY_LOOPING);
296 
297 	gSndBufSize = dsbcaps.dwBufferBytes;
298 
299 	// initialize the buffer
300 	reps = 0;
301 
302 	while ((hresult = IDirectSoundBuffer_Lock (pDSBuf, 0, gSndBufSize,
303 		(LPVOID *)(char *)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK) {
304 		if (hresult != DSERR_BUFFERLOST) {
305 			Sys_Printf ("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n");
306 			FreeSound ();
307 			return SIS_FAILURE;
308 		}
309 
310 		if (++reps > 10000) {
311 			Sys_Printf ("SNDDMA_InitDirect: DS: couldn't restore buffer\n");
312 			FreeSound ();
313 			return SIS_FAILURE;
314 		}
315 
316 	}
317 
318 	memset (lpData, 0, dwSize);
319 //	lpData[4] = lpData[5] = 0x7f;   // force a pop for debugging
320 
321 	IDirectSoundBuffer_Unlock (pDSBuf, lpData, dwSize, NULL, 0);
322 
323 	/* we don't want anyone to access the buffer directly w/o locking it
324 	   first. */
325 //	lpData = NULL;
326 
327 	IDirectSoundBuffer_Stop (pDSBuf);
328 	IDirectSoundBuffer_GetCurrentPosition (pDSBuf, &mmstarttime.u.sample,
329 										   &dwWrite);
330 	IDirectSoundBuffer_Play (pDSBuf, 0, 0, DSBPLAY_LOOPING);
331 
332 	sn.frames = gSndBufSize / (sn.samplebits / 8) / sn.channels;
333 	sn.framepos = 0;
334 	sn.submission_chunk = 1;
335 	sn.buffer = (byte *) lpData;
336 	sample16 = (sn.samplebits / 8) - 1;
337 
338 	dsound_init = true;
339 
340 	return SIS_SUCCESS;
341 }
342 
343 
344 /*
345 	SNDDMA_Init
346 
347 	Try to find a sound device to mix for.
348 	Returns false if nothing is found.
349 */
350 static volatile dma_t *
SNDDMA_Init(void)351 SNDDMA_Init (void)
352 {
353 	sndinitstat stat;
354 
355 	stat = SIS_FAILURE;					// assume DirectSound won't
356 	// initialize
357 
358 	/* Init DirectSound */
359 	if (snd_firsttime) {
360 		snd_firsttime = false;
361 		stat = SNDDMA_InitDirect ();
362 
363 		if (stat == SIS_SUCCESS) {
364 			Sys_Printf ("DirectSound initialized\n");
365 		} else {
366 			Sys_Printf ("DirectSound failed to init\n");
367 			return 0;
368 		}
369 	}
370 
371 	return &sn;
372 }
373 
374 /*
375 	SNDDMA_GetDMAPos
376 
377 	return the current sample position (in mono samples read)
378 	inside the recirculating dma buffer, so the mixing code will know
379 	how many sample are required to fill it up.
380 */
381 static int
SNDDMA_GetDMAPos(void)382 SNDDMA_GetDMAPos (void)
383 {
384 	int		s = 0;
385 	DWORD		dwWrite;
386 	MMTIME	mmtime;
387 	unsigned long *pbuf;
388 
389 	pbuf = DSOUND_LockBuffer (true);
390 	if (!pbuf) {
391 		Sys_Printf ("DSOUND_LockBuffer fails!\n");
392 		return -1;
393 	}
394 	sn.buffer = (unsigned char *) pbuf;
395 	mmtime.wType = TIME_SAMPLES;
396 	IDirectSoundBuffer_GetCurrentPosition (pDSBuf, &mmtime.u.sample,
397 											   &dwWrite);
398 	s = mmtime.u.sample - mmstarttime.u.sample;
399 
400 	s >>= sample16;
401 	s /= sn.channels;
402 
403 	s %= sn.frames;
404 	sn.framepos = s;
405 
406 	return sn.framepos;
407 }
408 
409 /*
410 	SNDDMA_Submit
411 
412 	Send sound to device if buffer isn't really the dma buffer
413 */
414 static void
SNDDMA_Submit(void)415 SNDDMA_Submit (void)
416 {
417 	DSOUND_LockBuffer (false);
418 }
419 
420 /*
421 	SNDDMA_Shutdown
422 
423 	Reset the sound device for exiting
424 */
425 static void
SNDDMA_Shutdown(void)426 SNDDMA_Shutdown (void)
427 {
428 	FreeSound ();
429 }
430 
431 DWORD      *
DSOUND_LockBuffer(qboolean lockit)432 DSOUND_LockBuffer (qboolean lockit)
433 {
434 	int		reps;
435 
436 	static DWORD dwSize;
437 	static DWORD dwSize2;
438 	static DWORD *pbuf1;
439 	static DWORD *pbuf2;
440 	HRESULT     hresult;
441 
442 	if (!pDSBuf)
443 		return NULL;
444 
445 	if (lockit) {
446 		reps = 0;
447 		while ((hresult = IDirectSoundBuffer_Lock
448 				(pDSBuf, 0, gSndBufSize, (LPVOID *)(char *) &pbuf1, &dwSize,
449 				 (LPVOID *)(char *) &pbuf2, &dwSize2, 0)) != DS_OK) {
450 			if (hresult != DSERR_BUFFERLOST) {
451 				Sys_Printf
452 					("S_TransferStereo16: DS::Lock Sound Buffer Failed\n");
453 				SNDDMA_Shutdown ();
454 				SNDDMA_Init ();
455 				return NULL;
456 			}
457 
458 			if (++reps > 10000) {
459 				Sys_Printf
460 					("S_TransferStereo16: DS: couldn't restore buffer\n");
461 				SNDDMA_Shutdown ();
462 				SNDDMA_Init ();
463 				return NULL;
464 			}
465 		}
466 	} else {
467 		IDirectSoundBuffer_Unlock (pDSBuf, pbuf1, dwSize, NULL, 0);
468 		pbuf1 = NULL;
469 		pbuf2 = NULL;
470 		dwSize = 0;
471 		dwSize2 = 0;
472 	}
473 	return (pbuf1);
474 }
475 
476 void
DSOUND_ClearBuffer(int clear)477 DSOUND_ClearBuffer (int clear)
478 {
479 	DWORD      *pData;
480 
481 // FIXME: this should be called with 2nd pbuf2 = NULL, dwsize =0
482 	pData = DSOUND_LockBuffer (true);
483 	memset (pData, clear, sn.frames * sn.channels * sn.samplebits / 8);
484 	DSOUND_LockBuffer (false);
485 }
486 
487 void
DSOUND_Restore(void)488 DSOUND_Restore (void)
489 {
490 // if the buffer was lost or stopped, restore it and/or restart it
491 	DWORD       dwStatus;
492 
493 	if (!pDSBuf)
494 		return;
495 
496 	if (IDirectSoundBuffer_GetStatus (pDSBuf, &dwStatus) != DS_OK)
497 		Sys_Printf ("Couldn't get sound buffer status\n");
498 
499 	if (dwStatus & DSBSTATUS_BUFFERLOST)
500 		IDirectSoundBuffer_Restore (pDSBuf);
501 
502 	if (!(dwStatus & DSBSTATUS_PLAYING))
503 		IDirectSoundBuffer_Play (pDSBuf, 0, 0, DSBPLAY_LOOPING);
504 
505 	return;
506 }
507 
PLUGIN_INFO(snd_output,dx)508 PLUGIN_INFO(snd_output, dx)
509 {
510 	plugin_info.type = qfp_snd_output;
511 	plugin_info.api_version = QFPLUGIN_VERSION;
512 	plugin_info.plugin_version = "0.1";
513 	plugin_info.description = "Windows DirectX output";
514 	plugin_info.copyright = "Copyright (C) 1996-1997 id Software, Inc.\n"
515 		"Copyright (C) 1999,2000,2001,2002,2003  contributors of the QuakeForge "
516 		"project\n"
517 		"Please see the file \"AUTHORS\" for a list of contributors";
518 	plugin_info.functions = &plugin_info_funcs;
519 	plugin_info.data = &plugin_info_data;
520 
521 	plugin_info_data.general = &plugin_info_general_data;
522 	plugin_info_data.input = NULL;
523 	plugin_info_data.snd_output = &plugin_info_snd_output_data;
524 
525 	plugin_info_funcs.general = &plugin_info_general_funcs;
526 	plugin_info_funcs.input = NULL;
527 	plugin_info_funcs.snd_output = &plugin_info_snd_output_funcs;
528 
529 	plugin_info_general_funcs.p_Init = SNDDMA_Init_Cvars;
530 	plugin_info_general_funcs.p_Shutdown = NULL;
531 	plugin_info_snd_output_funcs.pS_O_Init = SNDDMA_Init;
532 	plugin_info_snd_output_funcs.pS_O_Shutdown = SNDDMA_Shutdown;
533 	plugin_info_snd_output_funcs.pS_O_GetDMAPos = SNDDMA_GetDMAPos;
534 	plugin_info_snd_output_funcs.pS_O_Submit = SNDDMA_Submit;
535 	plugin_info_snd_output_funcs.pS_O_BlockSound = SNDDMA_BlockSound;
536 	plugin_info_snd_output_funcs.pS_O_UnblockSound = SNDDMA_UnblockSound;
537 
538 	return &plugin_info;
539 }
540