2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #ifndef DIRECTSOUND_VERSION
24 # define DIRECTSOUND_VERSION 0x0500 /* Version 5.0 */
30 extern HWND mainwindow;
32 HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter);
34 // Wave output: 64KB in 64 buffers of 1KB
35 // (64KB is > 1 sec at 16-bit 22050 Hz mono, and is 1/3 sec at 16-bit 44100 Hz stereo)
36 #define WAV_BUFFERS 64
37 #define WAV_MASK (WAV_BUFFERS - 1)
38 #define WAV_BUFFER_SIZE 1024
40 // DirectSound output: 64KB in 1 buffer
41 #define SECONDARY_BUFFER_SIZE (64 * 1024)
43 typedef enum sndinitstat_e {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat;
45 static qboolean dsound_init;
46 static qboolean wav_init;
47 static qboolean primary_format_set;
49 static int snd_sent, snd_completed;
51 static int prev_painted;
52 static unsigned int paintpot;
54 static unsigned int dsound_time;
58 * Global variables. Must be visible to window-procedure function
59 * so it can unlock and free the data block after it has been played.
63 HPSTR lpData, lpData2;
77 LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf;
81 qboolean SNDDMA_InitWav (void);
82 sndinitstat SNDDMA_InitDirect (void);
87 SndSys_InitDirectSound
92 static sndinitstat SndSys_InitDirectSound (const snd_format_t* requested, snd_format_t* suggested)
98 WAVEFORMATEX format, pformat;
102 memset (&format, 0, sizeof(format));
103 format.wFormatTag = WAVE_FORMAT_PCM;
104 format.nChannels = requested->channels;
105 format.wBitsPerSample = requested->width * 8;
106 format.nSamplesPerSec = requested->speed;
107 format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
108 format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
113 hInstDS = LoadLibrary("dsound.dll");
117 Con_Print("Couldn't load dsound.dll\n");
121 pDirectSoundCreate = (void *)GetProcAddress(hInstDS,"DirectSoundCreate");
123 if (!pDirectSoundCreate)
125 Con_Print("Couldn't get DS proc addr\n");
130 while ((hresult = pDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK)
132 if (hresult != DSERR_ALLOCATED)
134 Con_Print("DirectSound create failed\n");
138 if (MessageBox (NULL,
139 "The sound hardware is in use by another app.\n\n"
140 "Select Retry to try to start sound again or Cancel to run Quake with no sound.",
141 "Sound not available",
142 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
144 Con_Print("DirectSoundCreate failure\n hardware already in use\n");
149 dscaps.dwSize = sizeof(dscaps);
151 if (DS_OK != IDirectSound_GetCaps (pDS, &dscaps))
153 Con_Print("Couldn't get DS caps\n");
156 if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
158 Con_Print("No DirectSound driver installed\n");
163 if (DS_OK != IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_EXCLUSIVE))
165 Con_Print("Set coop level failed\n");
170 // get access to the primary buffer, if possible, so we can set the
171 // sound hardware format
172 memset (&dsbuf, 0, sizeof(dsbuf));
173 dsbuf.dwSize = sizeof(DSBUFFERDESC);
174 dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
175 dsbuf.dwBufferBytes = 0;
176 dsbuf.lpwfxFormat = NULL;
178 memset(&dsbcaps, 0, sizeof(dsbcaps));
179 dsbcaps.dwSize = sizeof(dsbcaps);
180 primary_format_set = false;
182 // COMMANDLINEOPTION: Windows DirectSound: -snoforceformat uses the format that DirectSound returns, rather than forcing it
183 if (!COM_CheckParm ("-snoforceformat"))
185 if (DS_OK == IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL))
189 if (DS_OK != IDirectSoundBuffer_SetFormat (pDSPBuf, &pformat))
191 Con_Print("Set primary sound buffer format: no\n");
195 Con_Print("Set primary sound buffer format: yes\n");
197 primary_format_set = true;
202 // COMMANDLINEOPTION: Windows DirectSound: -primarysound locks the sound hardware for exclusive use
203 if (!primary_format_set || !COM_CheckParm ("-primarysound"))
207 // create the secondary buffer we'll actually work with
208 memset (&dsbuf, 0, sizeof(dsbuf));
209 dsbuf.dwSize = sizeof(DSBUFFERDESC);
210 dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE;
211 dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
212 dsbuf.lpwfxFormat = &format;
214 memset(&dsbcaps, 0, sizeof(dsbcaps));
215 dsbcaps.dwSize = sizeof(dsbcaps);
217 result = IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL);
218 if (result != DS_OK ||
219 requested->channels != format.nChannels ||
220 requested->width != format.wBitsPerSample / 8 ||
221 requested->speed != format.nSamplesPerSec)
223 Con_Printf("DS:CreateSoundBuffer Failed (%d): channels=%u, width=%u, speed=%u\n",
224 result, format.nChannels, format.wBitsPerSample / 8, format.nSamplesPerSec);
229 if (DS_OK != IDirectSoundBuffer_GetCaps (pDSBuf, &dsbcaps))
231 Con_Print("DS:GetCaps failed\n");
236 Con_Print("Using secondary sound buffer\n");
240 if (DS_OK != IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_WRITEPRIMARY))
242 Con_Print("Set coop level failed\n");
247 if (DS_OK != IDirectSoundBuffer_GetCaps (pDSPBuf, &dsbcaps))
249 Con_Print("DS:GetCaps failed\n");
254 Con_Print("Using primary sound buffer\n");
257 // Make sure mixer is active
258 IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
260 Con_DPrintf(" %d channel(s)\n"
263 requested->channels, requested->width * 8, requested->speed);
265 gSndBufSize = dsbcaps.dwBufferBytes;
267 // initialize the buffer
270 while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (LPVOID*)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
272 if (hresult != DSERR_BUFFERLOST)
274 Con_Print("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n");
281 Con_Print("SNDDMA_InitDirect: DS: couldn't restore buffer\n");
288 memset(lpData, 0, dwSize);
289 IDirectSoundBuffer_Unlock(pDSBuf, lpData, dwSize, NULL, 0);
291 IDirectSoundBuffer_Stop(pDSBuf);
292 IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
296 snd_renderbuffer = Snd_CreateRingBuffer(requested, gSndBufSize / (requested->width * requested->channels), lpData);
308 Crappy windows multimedia base
311 static qboolean SndSys_InitMmsystem (const snd_format_t* requested, snd_format_t* suggested)
317 memset (&format, 0, sizeof(format));
318 format.wFormatTag = WAVE_FORMAT_PCM;
319 format.nChannels = requested->channels;
320 format.wBitsPerSample = requested->width * 8;
321 format.nSamplesPerSec = requested->speed;
322 format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
323 format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
326 // Open a waveform device for output using window callback
327 while ((hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER,
329 0, 0L, CALLBACK_NULL)) != MMSYSERR_NOERROR)
331 if (hr != MMSYSERR_ALLOCATED)
333 Con_Print("waveOutOpen failed\n");
337 if (MessageBox (NULL,
338 "The sound hardware is in use by another app.\n\n"
339 "Select Retry to try to start sound again or Cancel to run Quake with no sound.",
340 "Sound not available",
341 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
343 Con_Print("waveOutOpen failure;\n hardware already in use\n");
349 * Allocate and lock memory for the waveform data. The memory
350 * for waveform data must be globally allocated with
351 * GMEM_MOVEABLE and GMEM_SHARE flags.
353 gSndBufSize = WAV_BUFFERS*WAV_BUFFER_SIZE;
354 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize);
357 Con_Print("Sound: Out of memory.\n");
361 lpData = GlobalLock(hData);
364 Con_Print("Sound: Failed to lock.\n");
368 memset (lpData, 0, gSndBufSize);
371 * Allocate and lock memory for the header. This memory must
372 * also be globally allocated with GMEM_MOVEABLE and
375 hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
376 (DWORD) sizeof(WAVEHDR) * WAV_BUFFERS);
378 if (hWaveHdr == NULL)
380 Con_Print("Sound: Failed to Alloc header.\n");
385 lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr);
387 if (lpWaveHdr == NULL)
389 Con_Print("Sound: Failed to lock header.\n");
394 memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS);
396 // After allocation, set up and prepare headers
397 for (i=0 ; i<WAV_BUFFERS ; i++)
399 lpWaveHdr[i].dwBufferLength = WAV_BUFFER_SIZE;
400 lpWaveHdr[i].lpData = lpData + i*WAV_BUFFER_SIZE;
402 if (waveOutPrepareHeader(hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)) !=
405 Con_Print("Sound: failed to prepare wave headers\n");
411 snd_renderbuffer = Snd_CreateRingBuffer(requested, gSndBufSize / (requested->width * requested->channels), lpData);
429 Create "snd_renderbuffer" with the proper sound format if the call is successful
430 May return a suggested format if the requested format isn't available
433 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
438 Con_Print ("SndSys_Init: using the Win32 module\n");
440 // COMMANDLINEOPTION: Windows Sound: -wavonly uses wave sound instead of DirectSound
441 wavonly = (COM_CheckParm ("-wavonly") != 0);
445 stat = SIS_FAILURE; // assume DirectSound won't initialize
450 stat = SndSys_InitDirectSound (requested, suggested);
452 if (stat == SIS_SUCCESS)
453 Con_Print("DirectSound initialized\n");
455 Con_Print("DirectSound failed to init\n");
458 // if DirectSound didn't succeed in initializing, try to initialize
459 // waveOut sound, unless DirectSound failed because the hardware is
460 // already allocated (in which case the user has already chosen not
462 if (!dsound_init && (stat != SIS_NOTAVAIL))
464 if (SndSys_InitMmsystem (requested, suggested))
465 Con_Print("Wave sound (MMSYSTEM) initialized\n");
467 Con_Print("Wave sound failed to init\n");
470 return (dsound_init || wav_init);
478 Stop the sound card, delete "snd_renderbuffer" and free its other resources
481 void SndSys_Shutdown (void)
485 IDirectSoundBuffer_Stop(pDSBuf);
486 IDirectSoundBuffer_Release(pDSBuf);
489 // only release primary buffer if it's not also the mixing buffer we just released
490 if (pDSPBuf && (pDSBuf != pDSPBuf))
492 IDirectSoundBuffer_Release(pDSPBuf);
497 IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_NORMAL);
498 IDirectSound_Release(pDS);
503 waveOutReset (hWaveOut);
509 for (i=0 ; i< WAV_BUFFERS ; i++)
510 waveOutUnprepareHeader (hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR));
513 waveOutClose (hWaveOut);
517 GlobalUnlock(hWaveHdr);
518 GlobalFree(hWaveHdr);
528 if (snd_renderbuffer != NULL)
530 Mem_Free(snd_renderbuffer);
531 snd_renderbuffer = NULL;
551 Submit the contents of "snd_renderbuffer" to the sound card
554 void SndSys_Submit (void)
559 // DirectSound doesn't need this
563 paintpot += (snd_renderbuffer->endframe - prev_painted) * snd_renderbuffer->format.channels * snd_renderbuffer->format.width;
564 if (paintpot > WAV_BUFFERS * WAV_BUFFER_SIZE)
565 paintpot = WAV_BUFFERS * WAV_BUFFER_SIZE;
566 prev_painted = snd_renderbuffer->endframe;
568 // submit new sound blocks
569 while (paintpot > WAV_BUFFER_SIZE)
571 h = lpWaveHdr + (snd_sent & WAV_MASK);
575 * Now the data block can be sent to the output device. The
576 * waveOutWrite function returns immediately and waveform
577 * data is sent to the output device in the background.
579 wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR));
581 if (wResult != MMSYSERR_NOERROR)
583 Con_Print("Failed to write block to device\n");
588 paintpot -= WAV_BUFFER_SIZE;
598 Returns the number of sample frames consumed since the sound started
601 unsigned int SndSys_GetSoundTime (void)
609 IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &dwTime, NULL);
610 s = (dwTime - dwStartTime) & (gSndBufSize - 1);
611 dwStartTime = dwTime;
613 dsound_time += s / (snd_renderbuffer->format.width * snd_renderbuffer->format.channels);
619 // Find which sound blocks have completed
622 if (snd_completed == snd_sent)
624 Con_DPrint("Sound overrun\n");
628 if (!(lpWaveHdr[snd_completed & WAV_MASK].dwFlags & WHDR_DONE))
631 snd_completed++; // this buffer has been played
634 s = snd_completed * WAV_BUFFER_SIZE;
636 return s / (snd_renderbuffer->format.width * snd_renderbuffer->format.channels);
643 static DWORD dsound_dwSize;
644 static DWORD dsound_dwSize2;
645 static DWORD *dsound_pbuf;
646 static DWORD *dsound_pbuf2;
650 SndSys_LockRenderBuffer
652 Get the exclusive lock on "snd_renderbuffer"
655 qboolean SndSys_LockRenderBuffer (void)
663 // if the buffer was lost or stopped, restore it and/or restart it
664 if (IDirectSoundBuffer_GetStatus (pDSBuf, &dwStatus) != DS_OK)
665 Con_Print("Couldn't get sound buffer status\n");
667 if (dwStatus & DSBSTATUS_BUFFERLOST)
669 Con_Print("DSound buffer is lost!!\n");
670 IDirectSoundBuffer_Restore (pDSBuf);
673 if (!(dwStatus & DSBSTATUS_PLAYING))
674 IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
678 while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (LPVOID*)&dsound_pbuf, &dsound_dwSize, (LPVOID*)&dsound_pbuf2, &dsound_dwSize2, 0)) != DS_OK)
680 if (hresult != DSERR_BUFFERLOST)
682 Con_Print("S_LockBuffer: DS: Lock Sound Buffer Failed\n");
690 Con_Print("S_LockBuffer: DS: couldn't restore buffer\n");
697 if ((void*)dsound_pbuf != snd_renderbuffer->ring)
698 Sys_Error("SndSys_LockRenderBuffer: the ring address has changed!!!\n");
708 SndSys_UnlockRenderBuffer
710 Release the exclusive lock on "snd_renderbuffer"
713 void SndSys_UnlockRenderBuffer (void)
716 IDirectSoundBuffer_Unlock(pDSBuf, dsound_pbuf, dsound_dwSize, dsound_pbuf2, dsound_dwSize2);