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