/* | |
Copyright (C) 1996-1997 Id Software, Inc. | |
This program is free software; you can redistribute it and/or | |
modify it under the terms of the GNU General Public License | |
as published by the Free Software Foundation; either version 2 | |
of the License, or (at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
See the GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with this program; if not, write to the Free Software | |
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
*/ | |
#include "quakedef.h" | |
#include "winquake.h" | |
#define iDirectSoundCreate(a,b,c) pDirectSoundCreate(a,b,c) | |
HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter); | |
// 64K is > 1 second at 16-bit, 22050 Hz | |
#define WAV_BUFFERS 64 | |
#define WAV_MASK 0x3F | |
#define WAV_BUFFER_SIZE 0x0400 | |
#define SECONDARY_BUFFER_SIZE 0x10000 | |
typedef enum {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat; | |
static qboolean wavonly; | |
static qboolean dsound_init; | |
static qboolean wav_init; | |
static qboolean snd_firsttime = true, snd_isdirect, snd_iswave; | |
static qboolean primary_format_set; | |
static int sample16; | |
static int snd_sent, snd_completed; | |
/* | |
* Global variables. Must be visible to window-procedure function | |
* so it can unlock and free the data block after it has been played. | |
*/ | |
HANDLE hData; | |
HPSTR lpData, lpData2; | |
HGLOBAL hWaveHdr; | |
LPWAVEHDR lpWaveHdr; | |
HWAVEOUT hWaveOut; | |
WAVEOUTCAPS wavecaps; | |
DWORD gSndBufSize; | |
MMTIME mmstarttime; | |
LPDIRECTSOUND pDS; | |
LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf; | |
HINSTANCE hInstDS; | |
qboolean SNDDMA_InitDirect (void); | |
qboolean SNDDMA_InitWav (void); | |
/* | |
================== | |
S_BlockSound | |
================== | |
*/ | |
void S_BlockSound (void) | |
{ | |
// DirectSound takes care of blocking itself | |
if (snd_iswave) | |
{ | |
snd_blocked++; | |
if (snd_blocked == 1) | |
{ | |
waveOutReset (hWaveOut); | |
} | |
} | |
} | |
/* | |
================== | |
S_UnblockSound | |
================== | |
*/ | |
void S_UnblockSound (void) | |
{ | |
// DirectSound takes care of blocking itself | |
if (snd_iswave) | |
{ | |
snd_blocked--; | |
} | |
} | |
/* | |
================== | |
FreeSound | |
================== | |
*/ | |
void FreeSound (void) | |
{ | |
int i; | |
if (pDSBuf) | |
{ | |
pDSBuf->lpVtbl->Stop(pDSBuf); | |
pDSBuf->lpVtbl->Release(pDSBuf); | |
} | |
// only release primary buffer if it's not also the mixing buffer we just released | |
if (pDSPBuf && (pDSBuf != pDSPBuf)) | |
{ | |
pDSPBuf->lpVtbl->Release(pDSPBuf); | |
} | |
if (pDS) | |
{ | |
pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_NORMAL); | |
pDS->lpVtbl->Release(pDS); | |
} | |
if (hWaveOut) | |
{ | |
waveOutReset (hWaveOut); | |
if (lpWaveHdr) | |
{ | |
for (i=0 ; i< WAV_BUFFERS ; i++) | |
waveOutUnprepareHeader (hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)); | |
} | |
waveOutClose (hWaveOut); | |
if (hWaveHdr) | |
{ | |
GlobalUnlock(hWaveHdr); | |
GlobalFree(hWaveHdr); | |
} | |
if (hData) | |
{ | |
GlobalUnlock(hData); | |
GlobalFree(hData); | |
} | |
} | |
pDS = NULL; | |
pDSBuf = NULL; | |
pDSPBuf = NULL; | |
hWaveOut = 0; | |
hData = 0; | |
hWaveHdr = 0; | |
lpData = NULL; | |
lpWaveHdr = NULL; | |
dsound_init = false; | |
wav_init = false; | |
} | |
/* | |
================== | |
SNDDMA_InitDirect | |
Direct-Sound support | |
================== | |
*/ | |
sndinitstat SNDDMA_InitDirect (void) | |
{ | |
DSBUFFERDESC dsbuf; | |
DSBCAPS dsbcaps; | |
DWORD dwSize, dwWrite; | |
DSCAPS dscaps; | |
WAVEFORMATEX format, pformat; | |
HRESULT hresult; | |
int reps; | |
memset ((void *)&sn, 0, sizeof (sn)); | |
shm = &sn; | |
shm->channels = 2; | |
shm->samplebits = 16; | |
shm->speed = 11025; | |
memset (&format, 0, sizeof(format)); | |
format.wFormatTag = WAVE_FORMAT_PCM; | |
format.nChannels = shm->channels; | |
format.wBitsPerSample = shm->samplebits; | |
format.nSamplesPerSec = shm->speed; | |
format.nBlockAlign = format.nChannels | |
*format.wBitsPerSample / 8; | |
format.cbSize = 0; | |
format.nAvgBytesPerSec = format.nSamplesPerSec | |
*format.nBlockAlign; | |
if (!hInstDS) | |
{ | |
hInstDS = LoadLibrary("dsound.dll"); | |
if (hInstDS == NULL) | |
{ | |
Con_SafePrintf ("Couldn't load dsound.dll\n"); | |
return SIS_FAILURE; | |
} | |
pDirectSoundCreate = (void *)GetProcAddress(hInstDS,"DirectSoundCreate"); | |
if (!pDirectSoundCreate) | |
{ | |
Con_SafePrintf ("Couldn't get DS proc addr\n"); | |
return SIS_FAILURE; | |
} | |
} | |
while ((hresult = iDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK) | |
{ | |
if (hresult != DSERR_ALLOCATED) | |
{ | |
Con_SafePrintf ("DirectSound create failed\n"); | |
return SIS_FAILURE; | |
} | |
if (MessageBox (NULL, | |
"The sound hardware is in use by another app.\n\n" | |
"Select Retry to try to start sound again or Cancel to run Quake with no sound.", | |
"Sound not available", | |
MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY) | |
{ | |
Con_SafePrintf ("DirectSoundCreate failure\n" | |
" hardware already in use\n"); | |
return SIS_NOTAVAIL; | |
} | |
} | |
dscaps.dwSize = sizeof(dscaps); | |
if (DS_OK != pDS->lpVtbl->GetCaps (pDS, &dscaps)) | |
{ | |
Con_SafePrintf ("Couldn't get DS caps\n"); | |
} | |
if (dscaps.dwFlags & DSCAPS_EMULDRIVER) | |
{ | |
Con_SafePrintf ("No DirectSound driver installed\n"); | |
FreeSound (); | |
return SIS_FAILURE; | |
} | |
if (DS_OK != pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_EXCLUSIVE)) | |
{ | |
Con_SafePrintf ("Set coop level failed\n"); | |
FreeSound (); | |
return SIS_FAILURE; | |
} | |
// get access to the primary buffer, if possible, so we can set the | |
// sound hardware format | |
memset (&dsbuf, 0, sizeof(dsbuf)); | |
dsbuf.dwSize = sizeof(DSBUFFERDESC); | |
dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER; | |
dsbuf.dwBufferBytes = 0; | |
dsbuf.lpwfxFormat = NULL; | |
memset(&dsbcaps, 0, sizeof(dsbcaps)); | |
dsbcaps.dwSize = sizeof(dsbcaps); | |
primary_format_set = false; | |
if (!COM_CheckParm ("-snoforceformat")) | |
{ | |
if (DS_OK == pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL)) | |
{ | |
pformat = format; | |
if (DS_OK != pDSPBuf->lpVtbl->SetFormat (pDSPBuf, &pformat)) | |
{ | |
if (snd_firsttime) | |
Con_SafePrintf ("Set primary sound buffer format: no\n"); | |
} | |
else | |
{ | |
if (snd_firsttime) | |
Con_SafePrintf ("Set primary sound buffer format: yes\n"); | |
primary_format_set = true; | |
} | |
} | |
} | |
if (!primary_format_set || !COM_CheckParm ("-primarysound")) | |
{ | |
// create the secondary buffer we'll actually work with | |
memset (&dsbuf, 0, sizeof(dsbuf)); | |
dsbuf.dwSize = sizeof(DSBUFFERDESC); | |
dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE; | |
dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE; | |
dsbuf.lpwfxFormat = &format; | |
memset(&dsbcaps, 0, sizeof(dsbcaps)); | |
dsbcaps.dwSize = sizeof(dsbcaps); | |
if (DS_OK != pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL)) | |
{ | |
Con_SafePrintf ("DS:CreateSoundBuffer Failed"); | |
FreeSound (); | |
return SIS_FAILURE; | |
} | |
shm->channels = format.nChannels; | |
shm->samplebits = format.wBitsPerSample; | |
shm->speed = format.nSamplesPerSec; | |
if (DS_OK != pDSBuf->lpVtbl->GetCaps (pDSBuf, &dsbcaps)) | |
{ | |
Con_SafePrintf ("DS:GetCaps failed\n"); | |
FreeSound (); | |
return SIS_FAILURE; | |
} | |
if (snd_firsttime) | |
Con_SafePrintf ("Using secondary sound buffer\n"); | |
} | |
else | |
{ | |
if (DS_OK != pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_WRITEPRIMARY)) | |
{ | |
Con_SafePrintf ("Set coop level failed\n"); | |
FreeSound (); | |
return SIS_FAILURE; | |
} | |
if (DS_OK != pDSPBuf->lpVtbl->GetCaps (pDSPBuf, &dsbcaps)) | |
{ | |
Con_Printf ("DS:GetCaps failed\n"); | |
return SIS_FAILURE; | |
} | |
pDSBuf = pDSPBuf; | |
Con_SafePrintf ("Using primary sound buffer\n"); | |
} | |
// Make sure mixer is active | |
pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); | |
if (snd_firsttime) | |
Con_SafePrintf(" %d channel(s)\n" | |
" %d bits/sample\n" | |
" %d bytes/sec\n", | |
shm->channels, shm->samplebits, shm->speed); | |
gSndBufSize = dsbcaps.dwBufferBytes; | |
// initialize the buffer | |
reps = 0; | |
while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, &lpData, &dwSize, NULL, NULL, 0)) != DS_OK) | |
{ | |
if (hresult != DSERR_BUFFERLOST) | |
{ | |
Con_SafePrintf ("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n"); | |
FreeSound (); | |
return SIS_FAILURE; | |
} | |
if (++reps > 10000) | |
{ | |
Con_SafePrintf ("SNDDMA_InitDirect: DS: couldn't restore buffer\n"); | |
FreeSound (); | |
return SIS_FAILURE; | |
} | |
} | |
memset(lpData, 0, dwSize); | |
// lpData[4] = lpData[5] = 0x7f; // force a pop for debugging | |
pDSBuf->lpVtbl->Unlock(pDSBuf, lpData, dwSize, NULL, 0); | |
/* we don't want anyone to access the buffer directly w/o locking it first. */ | |
lpData = NULL; | |
pDSBuf->lpVtbl->Stop(pDSBuf); | |
pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &mmstarttime.u.sample, &dwWrite); | |
pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); | |
shm->soundalive = true; | |
shm->splitbuffer = false; | |
shm->samples = gSndBufSize/(shm->samplebits/8); | |
shm->samplepos = 0; | |
shm->submission_chunk = 1; | |
shm->buffer = (unsigned char *) lpData; | |
sample16 = (shm->samplebits/8) - 1; | |
dsound_init = true; | |
return SIS_SUCCESS; | |
} | |
/* | |
================== | |
SNDDM_InitWav | |
Crappy windows multimedia base | |
================== | |
*/ | |
qboolean SNDDMA_InitWav (void) | |
{ | |
WAVEFORMATEX format; | |
int i; | |
HRESULT hr; | |
snd_sent = 0; | |
snd_completed = 0; | |
shm = &sn; | |
shm->channels = 2; | |
shm->samplebits = 16; | |
shm->speed = 11025; | |
memset (&format, 0, sizeof(format)); | |
format.wFormatTag = WAVE_FORMAT_PCM; | |
format.nChannels = shm->channels; | |
format.wBitsPerSample = shm->samplebits; | |
format.nSamplesPerSec = shm->speed; | |
format.nBlockAlign = format.nChannels | |
*format.wBitsPerSample / 8; | |
format.cbSize = 0; | |
format.nAvgBytesPerSec = format.nSamplesPerSec | |
*format.nBlockAlign; | |
/* Open a waveform device for output using window callback. */ | |
while ((hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER, | |
&format, | |
0, 0L, CALLBACK_NULL)) != MMSYSERR_NOERROR) | |
{ | |
if (hr != MMSYSERR_ALLOCATED) | |
{ | |
Con_SafePrintf ("waveOutOpen failed\n"); | |
return false; | |
} | |
if (MessageBox (NULL, | |
"The sound hardware is in use by another app.\n\n" | |
"Select Retry to try to start sound again or Cancel to run Quake with no sound.", | |
"Sound not available", | |
MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY) | |
{ | |
Con_SafePrintf ("waveOutOpen failure;\n" | |
" hardware already in use\n"); | |
return false; | |
} | |
} | |
/* | |
* Allocate and lock memory for the waveform data. The memory | |
* for waveform data must be globally allocated with | |
* GMEM_MOVEABLE and GMEM_SHARE flags. | |
*/ | |
gSndBufSize = WAV_BUFFERS*WAV_BUFFER_SIZE; | |
hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize); | |
if (!hData) | |
{ | |
Con_SafePrintf ("Sound: Out of memory.\n"); | |
FreeSound (); | |
return false; | |
} | |
lpData = GlobalLock(hData); | |
if (!lpData) | |
{ | |
Con_SafePrintf ("Sound: Failed to lock.\n"); | |
FreeSound (); | |
return false; | |
} | |
memset (lpData, 0, gSndBufSize); | |
/* | |
* Allocate and lock memory for the header. This memory must | |
* also be globally allocated with GMEM_MOVEABLE and | |
* GMEM_SHARE flags. | |
*/ | |
hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, | |
(DWORD) sizeof(WAVEHDR) * WAV_BUFFERS); | |
if (hWaveHdr == NULL) | |
{ | |
Con_SafePrintf ("Sound: Failed to Alloc header.\n"); | |
FreeSound (); | |
return false; | |
} | |
lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr); | |
if (lpWaveHdr == NULL) | |
{ | |
Con_SafePrintf ("Sound: Failed to lock header.\n"); | |
FreeSound (); | |
return false; | |
} | |
memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS); | |
/* After allocation, set up and prepare headers. */ | |
for (i=0 ; i<WAV_BUFFERS ; i++) | |
{ | |
lpWaveHdr[i].dwBufferLength = WAV_BUFFER_SIZE; | |
lpWaveHdr[i].lpData = lpData + i*WAV_BUFFER_SIZE; | |
if (waveOutPrepareHeader(hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)) != | |
MMSYSERR_NOERROR) | |
{ | |
Con_SafePrintf ("Sound: failed to prepare wave headers\n"); | |
FreeSound (); | |
return false; | |
} | |
} | |
shm->soundalive = true; | |
shm->splitbuffer = false; | |
shm->samples = gSndBufSize/(shm->samplebits/8); | |
shm->samplepos = 0; | |
shm->submission_chunk = 1; | |
shm->buffer = (unsigned char *) lpData; | |
sample16 = (shm->samplebits/8) - 1; | |
wav_init = true; | |
return true; | |
} | |
/* | |
================== | |
SNDDMA_Init | |
Try to find a sound device to mix for. | |
Returns false if nothing is found. | |
================== | |
*/ | |
int SNDDMA_Init(void) | |
{ | |
sndinitstat stat; | |
if (COM_CheckParm ("-wavonly")) | |
wavonly = true; | |
dsound_init = wav_init = 0; | |
stat = SIS_FAILURE; // assume DirectSound won't initialize | |
/* Init DirectSound */ | |
if (!wavonly) | |
{ | |
if (snd_firsttime || snd_isdirect) | |
{ | |
stat = SNDDMA_InitDirect ();; | |
if (stat == SIS_SUCCESS) | |
{ | |
snd_isdirect = true; | |
if (snd_firsttime) | |
Con_SafePrintf ("DirectSound initialized\n"); | |
} | |
else | |
{ | |
snd_isdirect = false; | |
Con_SafePrintf ("DirectSound failed to init\n"); | |
} | |
} | |
} | |
// if DirectSound didn't succeed in initializing, try to initialize | |
// waveOut sound, unless DirectSound failed because the hardware is | |
// already allocated (in which case the user has already chosen not | |
// to have sound) | |
if (!dsound_init && (stat != SIS_NOTAVAIL)) | |
{ | |
if (snd_firsttime || snd_iswave) | |
{ | |
snd_iswave = SNDDMA_InitWav (); | |
if (snd_iswave) | |
{ | |
if (snd_firsttime) | |
Con_SafePrintf ("Wave sound initialized\n"); | |
} | |
else | |
{ | |
Con_SafePrintf ("Wave sound failed to init\n"); | |
} | |
} | |
} | |
snd_firsttime = false; | |
if (!dsound_init && !wav_init) | |
{ | |
if (snd_firsttime) | |
Con_SafePrintf ("No sound device initialized\n"); | |
return 0; | |
} | |
return 1; | |
} | |
/* | |
============== | |
SNDDMA_GetDMAPos | |
return the current sample position (in mono samples read) | |
inside the recirculating dma buffer, so the mixing code will know | |
how many sample are required to fill it up. | |
=============== | |
*/ | |
int SNDDMA_GetDMAPos(void) | |
{ | |
MMTIME mmtime; | |
int s; | |
DWORD dwWrite; | |
if (dsound_init) | |
{ | |
mmtime.wType = TIME_SAMPLES; | |
pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &mmtime.u.sample, &dwWrite); | |
s = mmtime.u.sample - mmstarttime.u.sample; | |
} | |
else if (wav_init) | |
{ | |
s = snd_sent * WAV_BUFFER_SIZE; | |
} | |
s >>= sample16; | |
s &= (shm->samples-1); | |
return s; | |
} | |
/* | |
============== | |
SNDDMA_Submit | |
Send sound to device if buffer isn't really the dma buffer | |
=============== | |
*/ | |
void SNDDMA_Submit(void) | |
{ | |
LPWAVEHDR h; | |
int wResult; | |
if (!wav_init) | |
return; | |
// | |
// find which sound blocks have completed | |
// | |
while (1) | |
{ | |
if ( snd_completed == snd_sent ) | |
{ | |
Con_DPrintf ("Sound overrun\n"); | |
break; | |
} | |
if ( ! (lpWaveHdr[ snd_completed & WAV_MASK].dwFlags & WHDR_DONE) ) | |
{ | |
break; | |
} | |
snd_completed++; // this buffer has been played | |
} | |
// | |
// submit two new sound blocks | |
// | |
while (((snd_sent - snd_completed) >> sample16) < 4) | |
{ | |
h = lpWaveHdr + ( snd_sent&WAV_MASK ); | |
snd_sent++; | |
/* | |
* Now the data block can be sent to the output device. The | |
* waveOutWrite function returns immediately and waveform | |
* data is sent to the output device in the background. | |
*/ | |
wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR)); | |
if (wResult != MMSYSERR_NOERROR) | |
{ | |
Con_SafePrintf ("Failed to write block to device\n"); | |
FreeSound (); | |
return; | |
} | |
} | |
} | |
/* | |
============== | |
SNDDMA_Shutdown | |
Reset the sound device for exiting | |
=============== | |
*/ | |
void SNDDMA_Shutdown(void) | |
{ | |
FreeSound (); | |
} | |