1 /*
2  * snd_dsound.c
3  * $Id: snd_dsound.c 4767 2012-06-16 20:48:51Z sezero $
4  *
5  * Copyright (C) 1996-1997  Id Software, Inc.
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or (at
10  * your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15  *
16  * See the GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22 
23 #include "quakedef.h"
24 #include "snd_sys.h"
25 
26 #if HAVE_WIN_DX_SOUND
27 
28 #include "winquake.h"
29 #include "snd_dsound.h"
30 #include <mmsystem.h>
31 #include <dsound.h>
32 
33 static char s_ds_driver[] = "DirectSound";
34 
35 /* DirectSound : */
36 #ifndef DSBSIZE_MIN
37 #define DSBSIZE_MIN		4
38 #endif
39 #ifndef DSBSIZE_MAX
40 #define DSBSIZE_MAX		0x0FFFFFFF
41 #endif
42 
43 static LPDIRECTSOUND		pDS;
44 static LPDIRECTSOUNDBUFFER	pDSBuf, pDSPBuf;
45 
46 #if defined(DX_DLSYM)	/* dynamic loading of dsound symbols */
47 static HINSTANCE	hInstDS;
48 static HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter);
49 #else	/* ! DX_DLSYM : we're linked to dsound */
50 #define	pDirectSoundCreate		DirectSoundCreate
51 #endif	/* DX_DLSYM */
52 
53 static qboolean	primary_format_set;
54 
55 static int	sample16;
56 static int	ds_sbuf_size;
57 
58 static HPSTR	lpData;
59 
60 static DWORD	gSndBufSize;
61 static MMTIME	mmstarttime;
62 
63 
64 /*
65 ==================
66 FreeSound
67 ==================
68 */
FreeSound(void)69 static void FreeSound (void)
70 {
71 	if (pDSBuf)
72 	{
73 		IDirectSoundBuffer_Stop(pDSBuf);
74 		IDirectSound_Release(pDSBuf);
75 	}
76 
77 // only release primary buffer if it's not also the mixing buffer we just released
78 	if (pDSPBuf && (pDSBuf != pDSPBuf))
79 	{
80 		IDirectSound_Release(pDSPBuf);
81 	}
82 
83 	if (pDS)
84 	{
85 		IDirectSound_SetCooperativeLevel(pDS, mainwindow, DSSCL_NORMAL);
86 		IDirectSound_Release(pDS);
87 	}
88 
89 	pDS = NULL;
90 	pDSBuf = NULL;
91 	pDSPBuf = NULL;
92 	lpData = NULL;
93 }
94 
95 
96 /*
97 ==================
98 SNDDMA_InitDirect
99 
100 Direct-Sound support
101 ==================
102 */
S_DS_Init(dma_t * dma)103 static qboolean S_DS_Init (dma_t *dma)
104 {
105 	DSBUFFERDESC	dsbuf;
106 	DSBCAPS			dsbcaps;
107 	DWORD			dwSize, dwWrite;
108 	DSCAPS			dscaps;
109 	WAVEFORMATEX	format, pformat;
110 	HRESULT			hresult;
111 	int				reps;
112 
113 	memset((void *) dma, 0, sizeof(dma_t));
114 	shm = dma;
115 
116 	shm->channels = desired_channels;
117 	shm->samplebits = desired_bits;
118 	shm->speed = desired_speed;
119 	/* Calculate the DS buffer size to store 2 secs
120 	 * of data, round up to the next power of 2.  */
121 	ds_sbuf_size = 1 << (Q_log2((desired_bits  >> 3) * (desired_speed << 1)) + 1);
122 
123 	memset (&format, 0, sizeof(format));
124 	format.wFormatTag = WAVE_FORMAT_PCM;
125 	format.nChannels = shm->channels;
126 	format.wBitsPerSample = shm->samplebits;
127 	format.nSamplesPerSec = shm->speed;
128 	format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
129 	format.cbSize = 0;
130 	format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
131 
132 #if defined(DX_DLSYM)
133 	if (!hInstDS)
134 	{
135 		hInstDS = LoadLibrary("dsound.dll");
136 
137 		if (hInstDS == NULL)
138 		{
139 			Con_SafePrintf ("Couldn't load dsound.dll\n");
140 			return false;
141 		}
142 
143 		pDirectSoundCreate = (HRESULT (WINAPI *)(GUID FAR *, LPDIRECTSOUND FAR *, IUnknown FAR *))
144 								GetProcAddress(hInstDS,"DirectSoundCreate");
145 
146 		if (!pDirectSoundCreate)
147 		{
148 			Con_SafePrintf ("Couldn't get DS proc addr\n");
149 			return false;
150 		}
151 	}
152 #endif	/* DX_DLSYM */
153 
154 	hresult = pDirectSoundCreate(NULL, &pDS, NULL);
155 	if (hresult != DS_OK)
156 	{
157 		if (hresult != DSERR_ALLOCATED)
158 		{
159 			Con_SafePrintf ("DirectSound create failed\n");
160 			return false;
161 		}
162 
163 		Con_SafePrintf ("DirectSoundCreate failure, hardware already in use\n");
164 		return false;
165 	}
166 
167 	dscaps.dwSize = sizeof(dscaps);
168 
169 	if (DS_OK != IDirectSound_GetCaps(pDS, &dscaps))
170 	{
171 		Con_SafePrintf ("Couldn't get DS caps\n");
172 	}
173 
174 	if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
175 	{
176 		Con_SafePrintf ("No DirectSound driver installed\n");
177 		goto fail;
178 	}
179 
180 	// if (DS_OK != IDirectSound_SetCooperativeLevel(pDS, mainwindow, DSSCL_EXCLUSIVE))
181 	/* Pa3PyX: Some MIDI synthesizers are software and require access to
182 	   waveOut; so if we set the coop level to exclusive, MIDI will fail
183 	   to init because the device is locked. We use priority level instead.
184 	   That way we don't lock out software synths and other apps, but can
185 	   still set the sound buffer format. */
186 	if (DS_OK != IDirectSound_SetCooperativeLevel(pDS, mainwindow, DSSCL_PRIORITY))
187 	{
188 		Con_SafePrintf ("Set coop level failed\n");
189 		goto fail;
190 	}
191 
192 // get access to the primary buffer, if possible, so we can set the
193 // sound hardware format
194 	memset (&dsbuf, 0, sizeof(dsbuf));
195 	dsbuf.dwSize = sizeof(DSBUFFERDESC);
196 	dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
197 	dsbuf.dwBufferBytes = 0;
198 	dsbuf.lpwfxFormat = NULL;
199 
200 	memset(&dsbcaps, 0, sizeof(dsbcaps));
201 	dsbcaps.dwSize = sizeof(dsbcaps);
202 	primary_format_set = false;
203 
204 	if (!COM_CheckParm ("-snoforceformat"))
205 	{
206 		if (DS_OK == IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL))
207 		{
208 			pformat = format;
209 
210 			if (DS_OK != IDirectSoundBuffer_SetFormat(pDSPBuf, &pformat))
211 			{
212 				Con_SafePrintf ("Set primary sound buffer format: no\n");
213 			}
214 			else
215 			{
216 				Con_SafePrintf ("Set primary sound buffer format: yes\n");
217 				primary_format_set = true;
218 			}
219 		}
220 	}
221 
222 	if (!primary_format_set || !COM_CheckParm ("-primarysound"))
223 	{
224 	// create the secondary buffer we'll actually work with
225 		memset (&dsbuf, 0, sizeof(dsbuf));
226 		dsbuf.dwSize = sizeof(DSBUFFERDESC);
227 		dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE;
228 		if (ds_sbuf_size < DSBSIZE_MIN)
229 			ds_sbuf_size = 1 << (Q_log2(DSBSIZE_MIN) + 1);
230 		if (ds_sbuf_size > DSBSIZE_MAX)
231 			ds_sbuf_size = 1 << Q_log2(DSBSIZE_MAX);
232 		dsbuf.dwBufferBytes = ds_sbuf_size;
233 		dsbuf.lpwfxFormat = &format;
234 
235 		memset(&dsbcaps, 0, sizeof(dsbcaps));
236 		dsbcaps.dwSize = sizeof(dsbcaps);
237 
238 		if (DS_OK != IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL))
239 		{
240 			Con_SafePrintf ("DS:CreateSoundBuffer Failed");
241 			goto fail;
242 		}
243 
244 		shm->channels = format.nChannels;
245 		shm->samplebits = format.wBitsPerSample;
246 		shm->speed = format.nSamplesPerSec;
247 
248 		if (DS_OK != IDirectSound_GetCaps(pDSBuf, &dsbcaps))
249 		{
250 			Con_SafePrintf ("DS:GetCaps failed\n");
251 			goto fail;
252 		}
253 
254 		Con_SafePrintf ("Using secondary sound buffer\n");
255 	}
256 	else
257 	{
258 		if (DS_OK != IDirectSound_SetCooperativeLevel(pDS, mainwindow, DSSCL_WRITEPRIMARY))
259 		{
260 			Con_SafePrintf ("Set coop level failed\n");
261 			goto fail;
262 		}
263 
264 		if (DS_OK != IDirectSound_GetCaps(pDSPBuf, &dsbcaps))
265 		{
266 			Con_Printf ("DS:GetCaps failed\n");
267 			return false;
268 		}
269 
270 		pDSBuf = pDSPBuf;
271 		Con_SafePrintf ("Using primary sound buffer\n");
272 	}
273 
274 	// Make sure mixer is active
275 	IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
276 
277 	Con_SafePrintf ("%lu bytes in sound buffer\n", (unsigned long)dsbcaps.dwBufferBytes);
278 
279 	gSndBufSize = dsbcaps.dwBufferBytes;
280 
281 // initialize the buffer
282 	reps = 0;
283 
284 	while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (LPVOID *) (HPSTR) &lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
285 	{
286 		if (hresult != DSERR_BUFFERLOST)
287 		{
288 			Con_SafePrintf ("%s: DS::Lock Sound Buffer Failed\n", __thisfunc__);
289 			goto fail;
290 		}
291 
292 		if (++reps > 10000)
293 		{
294 			Con_SafePrintf ("%s: DS: couldn't restore buffer\n", __thisfunc__);
295 			goto fail;
296 		}
297 	}
298 
299 	memset(lpData, 0, dwSize);
300 //	lpData[4] = lpData[5] = 0x7f;	// force a pop for debugging
301 
302 	IDirectSoundBuffer_Unlock(pDSBuf, lpData, dwSize, NULL, 0);
303 
304 	/* we don't want anyone to access the buffer directly w/o locking it first. */
305 	lpData = NULL;
306 
307 	IDirectSoundBuffer_Stop(pDSBuf);
308 	IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &mmstarttime.u.sample, &dwWrite);
309 	IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
310 
311 	shm->samples = gSndBufSize / (shm->samplebits / 8);
312 	shm->samplepos = 0;
313 	shm->submission_chunk = 1;
314 	shm->buffer = (unsigned char *) lpData;
315 	sample16 = (shm->samplebits / 8) - 1;
316 
317 	Con_SafePrintf ("DirectSound initialized\n");
318 	return true;
319 
320 fail:
321 	FreeSound ();
322 	Con_SafePrintf ("DirectSound failed to init\n");
323 	return false;
324 }
325 
326 
327 /*
328 ==============
329 SNDDMA_GetDMAPos
330 
331 return the current sample position (in mono samples read)
332 inside the recirculating dma buffer, so the mixing code will know
333 how many sample are required to fill it up.
334 ===============
335 */
S_DS_GetDMAPos(void)336 static int S_DS_GetDMAPos (void)
337 {
338 	MMTIME	mmtime;
339 	int		s;
340 	DWORD	dwWrite;
341 
342 	mmtime.wType = TIME_SAMPLES;
343 	IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &mmtime.u.sample, &dwWrite);
344 	s = mmtime.u.sample - mmstarttime.u.sample;
345 	s >>= sample16;
346 	s &= (shm->samples - 1);
347 
348 	return s;
349 }
350 
351 /*
352 ==============
353 SNDDMA_LockBuffer
354 
355 Makes sure dma buffer is valid
356 ===============
357 */
358 static DWORD	locksize;
S_DS_LockBuffer(void)359 static void S_DS_LockBuffer (void)
360 {
361 	if (pDSBuf)
362 	{
363 		void	*pData;
364 		int		reps;
365 		HRESULT	hresult;
366 		DWORD	dwStatus;
367 
368 		reps = 0;
369 		shm->buffer = NULL;
370 
371 		if (IDirectSoundBuffer_GetStatus(pDSBuf, &dwStatus) != DS_OK)
372 			Con_Printf ("Couldn't get sound buffer status\n");
373 
374 		if (dwStatus & DSBSTATUS_BUFFERLOST)
375 			IDirectSoundBuffer_Restore(pDSBuf);
376 
377 		if (!(dwStatus & DSBSTATUS_PLAYING))
378 			IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
379 
380 		while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (void **) &pData, &locksize, NULL, NULL, 0)) != DS_OK)
381 		{
382 			if (hresult != DSERR_BUFFERLOST)
383 			{
384 				Con_Printf ("%s: DS::Lock Sound Buffer Failed\n", __thisfunc__);
385 				S_Shutdown ();
386 				return;
387 			}
388 
389 			if (++reps > 10000)
390 			{
391 				Con_Printf ("%s: DS: couldn't restore buffer\n", __thisfunc__);
392 				S_Shutdown ();
393 				return;
394 			}
395 		}
396 
397 		shm->buffer = (unsigned char *) pData;
398 	}
399 }
400 
401 /*
402 ==============
403 SNDDMA_Submit
404 
405 Unlock the dma buffer /
406 Send sound to the device
407 ===============
408 */
S_DS_Submit(void)409 static void S_DS_Submit (void)
410 {
411 	if (pDSBuf)
412 		IDirectSoundBuffer_Unlock(pDSBuf, shm->buffer, locksize, NULL, 0);
413 }
414 
415 /*
416 ==================
417 SNDDMA_BlockSound
418 ==================
419 */
S_DS_BlockSound(void)420 static void S_DS_BlockSound (void)
421 {
422 	// DirectSound takes care of blocking itself
423 }
424 
425 
426 /*
427 ==================
428 SNDDMA_UnblockSound
429 ==================
430 */
S_DS_UnblockSound(void)431 static void S_DS_UnblockSound (void)
432 {
433 }
434 
435 
436 /*
437 ==============
438 SNDDMA_Shutdown
439 
440 Reset the sound device for exiting
441 ===============
442 */
S_DS_Shutdown(void)443 static void S_DS_Shutdown (void)
444 {
445 	FreeSound ();
446 #if defined(DX_DLSYM)
447 	if (hInstDS)
448 	{
449 		FreeLibrary(hInstDS);
450 		hInstDS = NULL;
451 	}
452 #endif	/* DX_DLSYM */
453 }
454 
455 snd_driver_t snddrv_dsound =
456 {
457 	S_DS_Init,
458 	S_DS_Shutdown,
459 	S_DS_GetDMAPos,
460 	S_DS_LockBuffer,
461 	S_DS_Submit,
462 	S_DS_BlockSound,
463 	S_DS_UnblockSound,
464 	s_ds_driver,
465 	SNDDRV_ID_DSOUND,
466 	false,
467 	NULL
468 };
469 
470 #endif	/* HAVE_WIN_DX_SOUND */
471 
472