diff options
Diffstat (limited to 'QW/client/snd_win.c')
| -rw-r--r-- | QW/client/snd_win.c | 727 |
1 files changed, 727 insertions, 0 deletions
diff --git a/QW/client/snd_win.c b/QW/client/snd_win.c new file mode 100644 index 0000000..f75db02 --- /dev/null +++ b/QW/client/snd_win.c @@ -0,0 +1,727 @@ +/* +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 (); +} + |
