#include <float.h>
#include "../client/client.h"
#include "../client/snd_loc.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);
#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;
cvar_t *s_wavonly;
static bool dsound_init;
static bool wav_init;
static bool snd_firsttime = true, snd_isdirect, snd_iswave;
static bool primary_format_set;
static int snd_buffer_count = 0;
static int sample16;
static int snd_sent, snd_completed;
HANDLE hData;
HPSTR lpData, lpData2;
HGLOBAL hWaveHdr;
LPWAVEHDR lpWaveHdr;
HWAVEOUT hWaveOut;
WAVEOUTCAPS wavecaps;
DWORD gSndBufSize;
MMTIME mmstarttime;
LPDIRECTSOUND pDS;
LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf;
HINSTANCE hInstDS;
sndinitstat SNDDMA_InitDirect(void);
bool SNDDMA_InitWav(void);
void FreeSound(void);
static const char *DSoundError(int error) {
switch(error) {
case DSERR_BUFFERLOST:
return "DSERR_BUFFERLOST";
case DSERR_INVALIDCALL:
return "DSERR_INVALIDCALLS";
case DSERR_INVALIDPARAM:
return "DSERR_INVALIDPARAM";
case DSERR_PRIOLEVELNEEDED:
return "DSERR_PRIOLEVELNEEDED";
}
return "unknown";
}
static bool DS_CreateBuffers(void) {
DSBUFFERDESC dsbuf;
DSBCAPS dsbcaps;
WAVEFORMATEX pformat, format;
DWORD dwWrite;
memset(&format, 0, sizeof(format));
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = dma.channels;
format.wBitsPerSample = dma.samplebits;
format.nSamplesPerSec = dma.speed;
format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
format.cbSize = 0;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
Com_Printf("Creating DS buffers\n");
Com_DPrintf("...setting EXCLUSIVE coop level: ");
if(DS_OK != pDS->lpVtbl->SetCooperativeLevel(pDS, cl_hwnd, DSSCL_EXCLUSIVE)) {
Com_Printf("failed\n");
FreeSound();
return false;
}
Com_DPrintf("ok\n");
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;
Com_DPrintf("...creating primary buffer: ");
if(DS_OK == pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL)) {
pformat = format;
Com_DPrintf("ok\n");
if(DS_OK != pDSPBuf->lpVtbl->SetFormat(pDSPBuf, &pformat)) {
if(snd_firsttime)
Com_DPrintf("...setting primary sound format: failed\n");
} else {
if(snd_firsttime)
Com_DPrintf("...setting primary sound format: ok\n");
primary_format_set = true;
}
} else
Com_Printf("failed\n");
if(!primary_format_set || !s_primary->value) {
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);
Com_DPrintf("...creating secondary buffer: ");
if(DS_OK != pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL)) {
Com_Printf("failed\n");
FreeSound();
return false;
}
Com_DPrintf("ok\n");
dma.channels = format.nChannels;
dma.samplebits = format.wBitsPerSample;
dma.speed = format.nSamplesPerSec;
if(DS_OK != pDSBuf->lpVtbl->GetCaps(pDSBuf, &dsbcaps)) {
Com_Printf("*** GetCaps failed ***\n");
FreeSound();
return false;
}
Com_Printf("...using secondary sound buffer\n");
} else {
Com_Printf("...using primary buffer\n");
Com_DPrintf("...setting WRITEPRIMARY coop level: ");
if(DS_OK != pDS->lpVtbl->SetCooperativeLevel(pDS, cl_hwnd, DSSCL_WRITEPRIMARY)) {
Com_Printf("failed\n");
FreeSound();
return false;
}
Com_DPrintf("ok\n");
if(DS_OK != pDSPBuf->lpVtbl->GetCaps(pDSPBuf, &dsbcaps)) {
Com_Printf("*** GetCaps failed ***\n");
return false;
}
pDSBuf = pDSPBuf;
}
pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
if(snd_firsttime)
Com_Printf(" %d channel(s)\n"
" %d bits/sample\n"
" %d bytes/sec\n",
dma.channels, dma.samplebits, dma.speed);
gSndBufSize = dsbcaps.dwBufferBytes;
lpData = NULL;
pDSBuf->lpVtbl->Stop(pDSBuf);
pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &mmstarttime.u.sample, &dwWrite);
pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
dma.samples = gSndBufSize / (dma.samplebits / 8);
dma.samplepos = 0;
dma.submission_chunk = 1;
dma.buffer = (unsigned char *)lpData;
sample16 = (dma.samplebits / 8) - 1;
return true;
}
static void DS_DestroyBuffers(void) {
Com_DPrintf("Destroying DS buffers\n");
if(pDS) {
Com_DPrintf("...setting NORMAL coop level\n");
pDS->lpVtbl->SetCooperativeLevel(pDS, cl_hwnd, DSSCL_NORMAL);
}
if(pDSBuf) {
Com_DPrintf("...stopping and releasing sound buffer\n");
pDSBuf->lpVtbl->Stop(pDSBuf);
pDSBuf->lpVtbl->Release(pDSBuf);
}
if(pDSPBuf && (pDSBuf != pDSPBuf)) {
Com_DPrintf("...releasing primary buffer\n");
pDSPBuf->lpVtbl->Release(pDSPBuf);
}
pDSBuf = NULL;
pDSPBuf = NULL;
dma.buffer = NULL;
}
void FreeSound(void) {
int i;
Com_DPrintf("Shutting down sound system\n");
if(pDS)
DS_DestroyBuffers();
if(hWaveOut) {
Com_DPrintf("...resetting waveOut\n");
waveOutReset(hWaveOut);
if(lpWaveHdr) {
Com_DPrintf("...unpreparing headers\n");
for(i = 0; i < WAV_BUFFERS; i++)
waveOutUnprepareHeader(hWaveOut, lpWaveHdr + i, sizeof(WAVEHDR));
}
Com_DPrintf("...closing waveOut\n");
waveOutClose(hWaveOut);
if(hWaveHdr) {
Com_DPrintf("...freeing WAV header\n");
GlobalUnlock(hWaveHdr);
GlobalFree(hWaveHdr);
}
if(hData) {
Com_DPrintf("...freeing WAV buffer\n");
GlobalUnlock(hData);
GlobalFree(hData);
}
}
if(pDS) {
Com_DPrintf("...releasing DS object\n");
pDS->lpVtbl->Release(pDS);
}
if(hInstDS) {
Com_DPrintf("...freeing DSOUND.DLL\n");
FreeLibrary(hInstDS);
hInstDS = NULL;
}
pDS = NULL;
pDSBuf = NULL;
pDSPBuf = NULL;
hWaveOut = 0;
hData = 0;
hWaveHdr = 0;
lpData = NULL;
lpWaveHdr = NULL;
dsound_init = false;
wav_init = false;
}
sndinitstat SNDDMA_InitDirect(void) {
DSCAPS dscaps;
HRESULT hresult;
dma.channels = 2;
dma.samplebits = 16;
if(s_khz->value == 44)
dma.speed = 44100;
if(s_khz->value == 22)
dma.speed = 22050;
else
dma.speed = 11025;
Com_Printf("Initializing DirectSound\n");
if(!hInstDS) {
Com_DPrintf("...loading dsound.dll: ");
hInstDS = LoadLibrary("dsound.dll");
if(hInstDS == NULL) {
Com_Printf("failed\n");
return SIS_FAILURE;
}
Com_DPrintf("ok\n");
pDirectSoundCreate = (void *)GetProcAddress(hInstDS, "DirectSoundCreate");
if(!pDirectSoundCreate) {
Com_Printf("*** couldn't get DS proc addr ***\n");
return SIS_FAILURE;
}
}
Com_DPrintf("...creating DS object: ");
while((hresult = iDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK) {
if(hresult != DSERR_ALLOCATED) {
Com_Printf("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) {
Com_Printf("failed, hardware already in use\n");
return SIS_NOTAVAIL;
}
}
Com_DPrintf("ok\n");
dscaps.dwSize = sizeof(dscaps);
if(DS_OK != pDS->lpVtbl->GetCaps(pDS, &dscaps)) {
Com_Printf("*** couldn't get DS caps ***\n");
}
if(dscaps.dwFlags & DSCAPS_EMULDRIVER) {
Com_DPrintf("...no DSound driver found\n");
FreeSound();
return SIS_FAILURE;
}
if(!DS_CreateBuffers())
return SIS_FAILURE;
dsound_init = true;
Com_DPrintf("...completed successfully\n");
return SIS_SUCCESS;
}
bool SNDDMA_InitWav(void) {
WAVEFORMATEX format;
int i;
HRESULT hr;
Com_Printf("Initializing wave sound\n");
snd_sent = 0;
snd_completed = 0;
dma.channels = 2;
dma.samplebits = 16;
if(s_khz->value == 44)
dma.speed = 44100;
if(s_khz->value == 22)
dma.speed = 22050;
else
dma.speed = 11025;
memset(&format, 0, sizeof(format));
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = dma.channels;
format.wBitsPerSample = dma.samplebits;
format.nSamplesPerSec = dma.speed;
format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
format.cbSize = 0;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
Com_DPrintf("...opening waveform device: ");
while((hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER, &format, 0, 0L, CALLBACK_NULL)) != MMSYSERR_NOERROR) {
if(hr != MMSYSERR_ALLOCATED) {
Com_Printf("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 2 with no sound.",
"Sound not available", MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY) {
Com_Printf("hw in use\n");
return false;
}
}
Com_DPrintf("ok\n");
Com_DPrintf("...allocating waveform buffer: ");
gSndBufSize = WAV_BUFFERS * WAV_BUFFER_SIZE;
hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize);
if(!hData) {
Com_Printf(" failed\n");
FreeSound();
return false;
}
Com_DPrintf("ok\n");
Com_DPrintf("...locking waveform buffer: ");
lpData = GlobalLock(hData);
if(!lpData) {
Com_Printf(" failed\n");
FreeSound();
return false;
}
memset(lpData, 0, gSndBufSize);
Com_DPrintf("ok\n");
Com_DPrintf("...allocating waveform header: ");
hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (DWORD)sizeof(WAVEHDR) * WAV_BUFFERS);
if(hWaveHdr == NULL) {
Com_Printf("failed\n");
FreeSound();
return false;
}
Com_DPrintf("ok\n");
Com_DPrintf("...locking waveform header: ");
lpWaveHdr = (LPWAVEHDR)GlobalLock(hWaveHdr);
if(lpWaveHdr == NULL) {
Com_Printf("failed\n");
FreeSound();
return false;
}
memset(lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS);
Com_DPrintf("ok\n");
Com_DPrintf("...preparing 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) {
Com_Printf("failed\n");
FreeSound();
return false;
}
}
Com_DPrintf("ok\n");
dma.samples = gSndBufSize / (dma.samplebits / 8);
dma.samplepos = 0;
dma.submission_chunk = 512;
dma.buffer = (unsigned char *)lpData;
sample16 = (dma.samplebits / 8) - 1;
wav_init = true;
return true;
}
int SNDDMA_Init(void) {
sndinitstat stat;
memset((void *)&dma, 0, sizeof(dma));
s_wavonly = Cvar_Get("s_wavonly", "0", 0);
dsound_init = wav_init = 0;
stat = SIS_FAILURE;
if(!s_wavonly->value) {
if(snd_firsttime || snd_isdirect) {
stat = SNDDMA_InitDirect();
if(stat == SIS_SUCCESS) {
snd_isdirect = true;
if(snd_firsttime)
Com_Printf("dsound init succeeded\n");
} else {
snd_isdirect = false;
Com_Printf("*** dsound init failed ***\n");
}
}
}
if(!dsound_init && (stat != SIS_NOTAVAIL)) {
if(snd_firsttime || snd_iswave) {
snd_iswave = SNDDMA_InitWav();
if(snd_iswave) {
if(snd_firsttime)
Com_Printf("Wave sound init succeeded\n");
} else {
Com_Printf("Wave sound init failed\n");
}
}
}
snd_firsttime = false;
snd_buffer_count = 1;
if(!dsound_init && !wav_init) {
if(snd_firsttime)
Com_Printf("*** No sound device initialized ***\n");
return 0;
}
return 1;
}
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 &= (dma.samples - 1);
return s;
}
DWORD locksize;
void SNDDMA_BeginPainting(void) {
int reps;
DWORD dwSize2;
DWORD *pbuf, *pbuf2;
HRESULT hresult;
DWORD dwStatus;
if(!pDSBuf)
return;
if(pDSBuf->lpVtbl->GetStatus(pDSBuf, &dwStatus) != DS_OK)
Com_Printf("Couldn't get sound buffer status\n");
if(dwStatus & DSBSTATUS_BUFFERLOST)
pDSBuf->lpVtbl->Restore(pDSBuf);
if(!(dwStatus & DSBSTATUS_PLAYING))
pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
reps = 0;
dma.buffer = NULL;
while((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, (void **)&pbuf, &locksize, (void **)&pbuf2, &dwSize2, 0)) !=
DS_OK) {
if(hresult != DSERR_BUFFERLOST) {
Com_Printf("S_TransferStereo16: Lock failed with error '%s'\n", DSoundError(hresult));
S_Shutdown();
return;
} else {
pDSBuf->lpVtbl->Restore(pDSBuf);
}
if(++reps > 2)
return;
}
dma.buffer = (unsigned char *)pbuf;
}
void SNDDMA_Submit(void) {
LPWAVEHDR h;
int wResult;
if(!dma.buffer)
return;
if(pDSBuf)
pDSBuf->lpVtbl->Unlock(pDSBuf, dma.buffer, locksize, NULL, 0);
if(!wav_init)
return;
while(1) {
if(snd_completed == snd_sent) {
Com_DPrintf("Sound overrun\n");
break;
}
if(!(lpWaveHdr[snd_completed & WAV_MASK].dwFlags & WHDR_DONE)) {
break;
}
snd_completed++; }
while(((snd_sent - snd_completed) >> sample16) < 8) {
h = lpWaveHdr + (snd_sent & WAV_MASK);
if(paintedtime / 256 <= snd_sent)
break; snd_sent++;
wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR));
if(wResult != MMSYSERR_NOERROR) {
Com_Printf("Failed to write block to device\n");
FreeSound();
return;
}
}
}
void SNDDMA_Shutdown(void) { FreeSound(); }
void S_Activate(bool active) {
if(active) {
if(pDS && cl_hwnd && snd_isdirect) {
DS_CreateBuffers();
}
} else {
if(pDS && cl_hwnd && snd_isdirect) {
DS_DestroyBuffers();
}
}
}