1/*
2Copyright (C) 1996-1997 Id Software, Inc.
3
4This program is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6as published by the Free Software Foundation; either version 2
7of the License, or (at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13See the GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19*/
20#include "quakedef.h"
21#include "winquake.h"
22
23#define iDirectSoundCreate(a,b,c)	pDirectSoundCreate(a,b,c)
24
25HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter);
26
27// 64K is > 1 second at 16-bit, 22050 Hz
28#define	WAV_BUFFERS				64
29#define	WAV_MASK				0x3F
30#define	WAV_BUFFER_SIZE			0x0400
31#define SECONDARY_BUFFER_SIZE	0x10000
32
33typedef enum {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat;
34
35static qboolean	wavonly;
36static qboolean	dsound_init;
37static qboolean	wav_init;
38static qboolean	snd_firsttime = true, snd_isdirect, snd_iswave;
39static qboolean	primary_format_set;
40
41static int	sample16;
42static int	snd_sent, snd_completed;
43
44
45/*
46 * Global variables. Must be visible to window-procedure function
47 *  so it can unlock and free the data block after it has been played.
48 */
49
50HANDLE		hData;
51HPSTR		lpData, lpData2;
52
53HGLOBAL		hWaveHdr;
54LPWAVEHDR	lpWaveHdr;
55
56HWAVEOUT    hWaveOut;
57
58WAVEOUTCAPS	wavecaps;
59
60DWORD	gSndBufSize;
61
62MMTIME		mmstarttime;
63
64LPDIRECTSOUND pDS;
65LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf;
66
67HINSTANCE hInstDS;
68
69qboolean SNDDMA_InitDirect (void);
70qboolean SNDDMA_InitWav (void);
71
72
73/*
74==================
75S_BlockSound
76==================
77*/
78void S_BlockSound (void)
79{
80
81// DirectSound takes care of blocking itself
82	if (snd_iswave)
83	{
84		snd_blocked++;
85
86		if (snd_blocked == 1)
87			waveOutReset (hWaveOut);
88	}
89}
90
91
92/*
93==================
94S_UnblockSound
95==================
96*/
97void S_UnblockSound (void)
98{
99
100// DirectSound takes care of blocking itself
101	if (snd_iswave)
102	{
103		snd_blocked--;
104	}
105}
106
107
108/*
109==================
110FreeSound
111==================
112*/
113void FreeSound (void)
114{
115	int		i;
116
117	if (pDSBuf)
118	{
119		pDSBuf->lpVtbl->Stop(pDSBuf);
120		pDSBuf->lpVtbl->Release(pDSBuf);
121	}
122
123// only release primary buffer if it's not also the mixing buffer we just released
124	if (pDSPBuf && (pDSBuf != pDSPBuf))
125	{
126		pDSPBuf->lpVtbl->Release(pDSPBuf);
127	}
128
129	if (pDS)
130	{
131		pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_NORMAL);
132		pDS->lpVtbl->Release(pDS);
133	}
134
135	if (hWaveOut)
136	{
137		waveOutReset (hWaveOut);
138
139		if (lpWaveHdr)
140		{
141			for (i=0 ; i< WAV_BUFFERS ; i++)
142				waveOutUnprepareHeader (hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR));
143		}
144
145		waveOutClose (hWaveOut);
146
147		if (hWaveHdr)
148		{
149			GlobalUnlock(hWaveHdr);
150			GlobalFree(hWaveHdr);
151		}
152
153		if (hData)
154		{
155			GlobalUnlock(hData);
156			GlobalFree(hData);
157		}
158
159	}
160
161	pDS = NULL;
162	pDSBuf = NULL;
163	pDSPBuf = NULL;
164	hWaveOut = 0;
165	hData = 0;
166	hWaveHdr = 0;
167	lpData = NULL;
168	lpWaveHdr = NULL;
169	dsound_init = false;
170	wav_init = false;
171}
172
173
174/*
175==================
176SNDDMA_InitDirect
177
178Direct-Sound support
179==================
180*/
181sndinitstat SNDDMA_InitDirect (void)
182{
183	DSBUFFERDESC	dsbuf;
184	DSBCAPS			dsbcaps;
185	DWORD			dwSize, dwWrite;
186	DSCAPS			dscaps;
187	WAVEFORMATEX	format, pformat;
188	HRESULT			hresult;
189	int				reps;
190
191	memset ((void *)&sn, 0, sizeof (sn));
192
193	shm = &sn;
194
195	shm->channels = 2;
196	shm->samplebits = 16;
197	shm->speed = 11025;
198
199	memset (&format, 0, sizeof(format));
200	format.wFormatTag = WAVE_FORMAT_PCM;
201    format.nChannels = shm->channels;
202    format.wBitsPerSample = shm->samplebits;
203    format.nSamplesPerSec = shm->speed;
204    format.nBlockAlign = format.nChannels
205		*format.wBitsPerSample / 8;
206    format.cbSize = 0;
207    format.nAvgBytesPerSec = format.nSamplesPerSec
208		*format.nBlockAlign;
209
210	if (!hInstDS)
211	{
212		hInstDS = LoadLibrary("dsound.dll");
213
214		if (hInstDS == NULL)
215		{
216			Con_SafePrintf ("Couldn't load dsound.dll\n");
217			return SIS_FAILURE;
218		}
219
220		pDirectSoundCreate = (void *)GetProcAddress(hInstDS,"DirectSoundCreate");
221
222		if (!pDirectSoundCreate)
223		{
224			Con_SafePrintf ("Couldn't get DS proc addr\n");
225			return SIS_FAILURE;
226		}
227	}
228
229	while ((hresult = iDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK)
230	{
231		if (hresult != DSERR_ALLOCATED)
232		{
233			Con_SafePrintf ("DirectSound create failed\n");
234			return SIS_FAILURE;
235		}
236
237		if (MessageBox (NULL,
238						"The sound hardware is in use by another app.\n\n"
239					    "Select Retry to try to start sound again or Cancel to run Quake with no sound.",
240						"Sound not available",
241						MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
242		{
243			Con_SafePrintf ("DirectSoundCreate failure\n"
244							"  hardware already in use\n");
245			return SIS_NOTAVAIL;
246		}
247	}
248
249	dscaps.dwSize = sizeof(dscaps);
250
251	if (DS_OK != pDS->lpVtbl->GetCaps (pDS, &dscaps))
252	{
253		Con_SafePrintf ("Couldn't get DS caps\n");
254	}
255
256	if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
257	{
258		Con_SafePrintf ("No DirectSound driver installed\n");
259		FreeSound ();
260		return SIS_FAILURE;
261	}
262
263	if (DS_OK != pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_EXCLUSIVE))
264	{
265		Con_SafePrintf ("Set coop level failed\n");
266		FreeSound ();
267		return SIS_FAILURE;
268	}
269
270// get access to the primary buffer, if possible, so we can set the
271// sound hardware format
272	memset (&dsbuf, 0, sizeof(dsbuf));
273	dsbuf.dwSize = sizeof(DSBUFFERDESC);
274	dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
275	dsbuf.dwBufferBytes = 0;
276	dsbuf.lpwfxFormat = NULL;
277
278	memset(&dsbcaps, 0, sizeof(dsbcaps));
279	dsbcaps.dwSize = sizeof(dsbcaps);
280	primary_format_set = false;
281
282	if (!COM_CheckParm ("-snoforceformat"))
283	{
284		if (DS_OK == pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL))
285		{
286			pformat = format;
287
288			if (DS_OK != pDSPBuf->lpVtbl->SetFormat (pDSPBuf, &pformat))
289			{
290//				if (snd_firsttime)
291//					Con_SafePrintf ("Set primary sound buffer format: no\n");
292			}
293			else
294//			{
295//				if (snd_firsttime)
296//					Con_SafePrintf ("Set primary sound buffer format: yes\n");
297
298				primary_format_set = true;
299//			}
300		}
301	}
302
303	if (!primary_format_set || !COM_CheckParm ("-primarysound"))
304	{
305	// create the secondary buffer we'll actually work with
306		memset (&dsbuf, 0, sizeof(dsbuf));
307		dsbuf.dwSize = sizeof(DSBUFFERDESC);
308		dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE;
309		dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
310		dsbuf.lpwfxFormat = &format;
311
312		memset(&dsbcaps, 0, sizeof(dsbcaps));
313		dsbcaps.dwSize = sizeof(dsbcaps);
314
315		if (DS_OK != pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL))
316		{
317			Con_SafePrintf ("DS:CreateSoundBuffer Failed");
318			FreeSound ();
319			return SIS_FAILURE;
320		}
321
322		shm->channels = format.nChannels;
323		shm->samplebits = format.wBitsPerSample;
324		shm->speed = format.nSamplesPerSec;
325
326		if (DS_OK != pDSBuf->lpVtbl->GetCaps (pDSBuf, &dsbcaps))
327		{
328			Con_SafePrintf ("DS:GetCaps failed\n");
329			FreeSound ();
330			return SIS_FAILURE;
331		}
332
333//		if (snd_firsttime)
334//			Con_SafePrintf ("Using secondary sound buffer\n");
335	}
336	else
337	{
338		if (DS_OK != pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_WRITEPRIMARY))
339		{
340			Con_SafePrintf ("Set coop level failed\n");
341			FreeSound ();
342			return SIS_FAILURE;
343		}
344
345		if (DS_OK != pDSPBuf->lpVtbl->GetCaps (pDSPBuf, &dsbcaps))
346		{
347			Con_Printf ("DS:GetCaps failed\n");
348			return SIS_FAILURE;
349		}
350
351		pDSBuf = pDSPBuf;
352//		Con_SafePrintf ("Using primary sound buffer\n");
353	}
354
355	// Make sure mixer is active
356	pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
357
358/*	if (snd_firsttime)
359		Con_SafePrintf("   %d channel(s)\n"
360		               "   %d bits/sample\n"
361					   "   %d bytes/sec\n",
362					   shm->channels, shm->samplebits, shm->speed);*/
363
364	gSndBufSize = dsbcaps.dwBufferBytes;
365
366// initialize the buffer
367	reps = 0;
368
369	while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, &lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
370	{
371		if (hresult != DSERR_BUFFERLOST)
372		{
373			Con_SafePrintf ("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n");
374			FreeSound ();
375			return SIS_FAILURE;
376		}
377
378		if (++reps > 10000)
379		{
380			Con_SafePrintf ("SNDDMA_InitDirect: DS: couldn't restore buffer\n");
381			FreeSound ();
382			return SIS_FAILURE;
383		}
384
385	}
386
387	memset(lpData, 0, dwSize);
388//		lpData[4] = lpData[5] = 0x7f;	// force a pop for debugging
389
390	pDSBuf->lpVtbl->Unlock(pDSBuf, lpData, dwSize, NULL, 0);
391
392	/* we don't want anyone to access the buffer directly w/o locking it first. */
393	lpData = NULL;
394
395	pDSBuf->lpVtbl->Stop(pDSBuf);
396	pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &mmstarttime.u.sample, &dwWrite);
397	pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
398
399	shm->soundalive = true;
400	shm->splitbuffer = false;
401	shm->samples = gSndBufSize/(shm->samplebits/8);
402	shm->samplepos = 0;
403	shm->submission_chunk = 1;
404	shm->buffer = (unsigned char *) lpData;
405	sample16 = (shm->samplebits/8) - 1;
406
407	dsound_init = true;
408
409	return SIS_SUCCESS;
410}
411
412
413/*
414==================
415SNDDM_InitWav
416
417Crappy windows multimedia base
418==================
419*/
420qboolean SNDDMA_InitWav (void)
421{
422	WAVEFORMATEX  format;
423	int				i;
424	HRESULT			hr;
425
426	snd_sent = 0;
427	snd_completed = 0;
428
429	shm = &sn;
430
431	shm->channels = 2;
432	shm->samplebits = 16;
433	shm->speed = 11025;
434
435	memset (&format, 0, sizeof(format));
436	format.wFormatTag = WAVE_FORMAT_PCM;
437	format.nChannels = shm->channels;
438	format.wBitsPerSample = shm->samplebits;
439	format.nSamplesPerSec = shm->speed;
440	format.nBlockAlign = format.nChannels
441		*format.wBitsPerSample / 8;
442	format.cbSize = 0;
443	format.nAvgBytesPerSec = format.nSamplesPerSec
444		*format.nBlockAlign;
445
446	/* Open a waveform device for output using window callback. */
447	while ((hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER,
448					&format,
449					0, 0L, CALLBACK_NULL)) != MMSYSERR_NOERROR)
450	{
451		if (hr != MMSYSERR_ALLOCATED)
452		{
453			Con_SafePrintf ("waveOutOpen failed\n");
454			return false;
455		}
456
457		if (MessageBox (NULL,
458						"The sound hardware is in use by another app.\n\n"
459					    "Select Retry to try to start sound again or Cancel to run Quake with no sound.",
460						"Sound not available",
461						MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
462		{
463			Con_SafePrintf ("waveOutOpen failure;\n"
464							"  hardware already in use\n");
465			return false;
466		}
467	}
468
469	/*
470	 * Allocate and lock memory for the waveform data. The memory
471	 * for waveform data must be globally allocated with
472	 * GMEM_MOVEABLE and GMEM_SHARE flags.
473
474	*/
475	gSndBufSize = WAV_BUFFERS*WAV_BUFFER_SIZE;
476	hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize);
477	if (!hData)
478	{
479		Con_SafePrintf ("Sound: Out of memory.\n");
480		FreeSound ();
481		return false;
482	}
483	lpData = GlobalLock(hData);
484	if (!lpData)
485	{
486		Con_SafePrintf ("Sound: Failed to lock.\n");
487		FreeSound ();
488		return false;
489	}
490	memset (lpData, 0, gSndBufSize);
491
492	/*
493	 * Allocate and lock memory for the header. This memory must
494	 * also be globally allocated with GMEM_MOVEABLE and
495	 * GMEM_SHARE flags.
496	 */
497	hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
498		(DWORD) sizeof(WAVEHDR) * WAV_BUFFERS);
499
500	if (hWaveHdr == NULL)
501	{
502		Con_SafePrintf ("Sound: Failed to Alloc header.\n");
503		FreeSound ();
504		return false;
505	}
506
507	lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr);
508
509	if (lpWaveHdr == NULL)
510	{
511		Con_SafePrintf ("Sound: Failed to lock header.\n");
512		FreeSound ();
513		return false;
514	}
515
516	memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS);
517
518	/* After allocation, set up and prepare headers. */
519	for (i=0 ; i<WAV_BUFFERS ; i++)
520	{
521		lpWaveHdr[i].dwBufferLength = WAV_BUFFER_SIZE;
522		lpWaveHdr[i].lpData = lpData + i*WAV_BUFFER_SIZE;
523
524		if (waveOutPrepareHeader(hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)) !=
525				MMSYSERR_NOERROR)
526		{
527			Con_SafePrintf ("Sound: failed to prepare wave headers\n");
528			FreeSound ();
529			return false;
530		}
531	}
532
533	shm->soundalive = true;
534	shm->splitbuffer = false;
535	shm->samples = gSndBufSize/(shm->samplebits/8);
536	shm->samplepos = 0;
537	shm->submission_chunk = 1;
538	shm->buffer = (unsigned char *) lpData;
539	sample16 = (shm->samplebits/8) - 1;
540
541	wav_init = true;
542
543	return true;
544}
545
546/*
547==================
548SNDDMA_Init
549
550Try to find a sound device to mix for.
551Returns false if nothing is found.
552==================
553*/
554
555int SNDDMA_Init(void)
556{
557	sndinitstat	stat;
558
559	if (COM_CheckParm ("-wavonly"))
560		wavonly = true;
561
562	dsound_init = wav_init = 0;
563
564	stat = SIS_FAILURE;	// assume DirectSound won't initialize
565
566	/* Init DirectSound */
567	if (!wavonly)
568	{
569		if (snd_firsttime || snd_isdirect)
570		{
571			stat = SNDDMA_InitDirect ();;
572
573			if (stat == SIS_SUCCESS)
574			{
575				snd_isdirect = true;
576
577				if (snd_firsttime)
578					Con_SafePrintf ("DirectSound initialized\n");
579			}
580			else
581			{
582				snd_isdirect = false;
583				Con_SafePrintf ("DirectSound failed to init\n");
584			}
585		}
586	}
587
588// if DirectSound didn't succeed in initializing, try to initialize
589// waveOut sound, unless DirectSound failed because the hardware is
590// already allocated (in which case the user has already chosen not
591// to have sound)
592	if (!dsound_init && (stat != SIS_NOTAVAIL))
593	{
594		if (snd_firsttime || snd_iswave)
595		{
596
597			snd_iswave = SNDDMA_InitWav ();
598
599			if (snd_iswave)
600			{
601				if (snd_firsttime)
602					Con_SafePrintf ("Wave sound initialized\n");
603			}
604			else
605			{
606				Con_SafePrintf ("Wave sound failed to init\n");
607			}
608		}
609	}
610
611	snd_firsttime = false;
612
613	if (!dsound_init && !wav_init)
614	{
615		if (snd_firsttime)
616			Con_SafePrintf ("No sound device initialized\n");
617
618		return 0;
619	}
620
621	return 1;
622}
623
624/*
625==============
626SNDDMA_GetDMAPos
627
628return the current sample position (in mono samples read)
629inside the recirculating dma buffer, so the mixing code will know
630how many sample are required to fill it up.
631===============
632*/
633int SNDDMA_GetDMAPos(void)
634{
635	MMTIME	mmtime;
636	int		s;
637	DWORD	dwWrite;
638
639	if (dsound_init)
640	{
641		mmtime.wType = TIME_SAMPLES;
642		pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &mmtime.u.sample, &dwWrite);
643		s = mmtime.u.sample - mmstarttime.u.sample;
644	}
645	else if (wav_init)
646	{
647		s = snd_sent * WAV_BUFFER_SIZE;
648	}
649
650
651	s >>= sample16;
652
653	s &= (shm->samples-1);
654
655	return s;
656}
657
658/*
659==============
660SNDDMA_Submit
661
662Send sound to device if buffer isn't really the dma buffer
663===============
664*/
665void SNDDMA_Submit(void)
666{
667	LPWAVEHDR	h;
668	int			wResult;
669
670	if (!wav_init)
671		return;
672
673	//
674	// find which sound blocks have completed
675	//
676	while (1)
677	{
678		if ( snd_completed == snd_sent )
679		{
680			Con_DPrintf ("Sound overrun\n");
681			break;
682		}
683
684		if ( ! (lpWaveHdr[ snd_completed & WAV_MASK].dwFlags & WHDR_DONE) )
685		{
686			break;
687		}
688
689		snd_completed++;	// this buffer has been played
690	}
691
692	//
693	// submit two new sound blocks
694	//
695	while (((snd_sent - snd_completed) >> sample16) < 4)
696	{
697		h = lpWaveHdr + ( snd_sent&WAV_MASK );
698
699		snd_sent++;
700		/*
701		 * Now the data block can be sent to the output device. The
702		 * waveOutWrite function returns immediately and waveform
703		 * data is sent to the output device in the background.
704		 */
705		wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR));
706
707		if (wResult != MMSYSERR_NOERROR)
708		{
709			Con_SafePrintf ("Failed to write block to device\n");
710			FreeSound ();
711			return;
712		}
713	}
714}
715
716/*
717==============
718SNDDMA_Shutdown
719
720Reset the sound device for exiting
721===============
722*/
723void SNDDMA_Shutdown(void)
724{
725	FreeSound ();
726}
727
728