/* Copyright (C) 1997-2001 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 "win_local.h" #include #include #include "snd_local.h" typedef HRESULT (WINAPI *LPDIRECTSOUNDCREATE)( LPCGUID, LPDIRECTSOUND *, LPUNKNOWN ); // 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 // starts at 0 for disabled static int sample16; static DWORD locksize; static MMTIME mmstarttime; static LPDIRECTSOUND pDS; static LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf; static HINSTANCE hInstDS; static DWORD gSndBufSize; static const char *DSoundError( int error ) { switch ( error ) { case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST"; case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL"; case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM"; case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED"; } return ""; } /* ** DS_DestroyBuffers */ static void DS_DestroyBuffers( void ) { Com_DPrintf( "Destroying DS buffers\n" ); if( pDS ) { Com_DPrintf( "...setting NORMAL coop level\n" ); IDirectSound_SetCooperativeLevel( pDS, win.wnd, DSSCL_NORMAL ); } if( pDSBuf ) { Com_DPrintf( "...stopping and releasing sound buffer\n" ); IDirectSoundBuffer_Stop( pDSBuf ); IDirectSoundBuffer_Release( pDSBuf ); } // only release primary buffer if it's not also the mixing buffer we just released if( pDSPBuf && ( pDSBuf != pDSPBuf ) ) { Com_DPrintf( "...releasing primary buffer\n" ); IDirectSoundBuffer_Release( pDSPBuf ); } pDSBuf = NULL; pDSPBuf = NULL; dma.buffer = NULL; } /* ============== DS_Shutdown Reset the sound device for exiting =============== */ static void DS_Shutdown(void) { Com_Printf( "Shutting down DirectSound\n" ); if( pDS ) { DS_DestroyBuffers(); Com_DPrintf( "...releasing DS object\n" ); IDirectSound_Release( pDS ); } if ( hInstDS ) { Com_DPrintf( "...freeing DSOUND.DLL\n" ); FreeLibrary( hInstDS ); hInstDS = NULL; } pDS = NULL; pDSBuf = NULL; pDSPBuf = NULL; } /* ** DS_CreateBuffers */ static qboolean DS_CreateBuffers( void ) { DSBUFFERDESC dsbuf; DSBCAPS dsbcaps; WAVEFORMATEX 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 = sizeof( format ); format.nAvgBytesPerSec = format.nSamplesPerSec*format.nBlockAlign; Com_DPrintf( "Creating DS buffer\n" ); Com_DPrintf("...setting PRIORITY coop level: " ); if ( DS_OK != IDirectSound_SetCooperativeLevel( pDS, win.wnd, DSSCL_PRIORITY ) ) { Com_DPrintf ("failed\n"); return qfalse; } Com_DPrintf("ok\n" ); // create the secondary buffer we'll actually work with memset (&dsbuf, 0, sizeof(dsbuf)); dsbuf.dwSize = sizeof(DSBUFFERDESC); dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCHARDWARE; 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 != IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL)) { dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE; if (DS_OK != IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL)) { Com_DPrintf( "failed\n" ); return qfalse; } Com_DPrintf( "ok\n...forced to software\n" ); } else { Com_DPrintf( "ok\n...locked hardware\n" ); } dma.channels = format.nChannels; dma.samplebits = format.wBitsPerSample; dma.speed = format.nSamplesPerSec; if (DS_OK != IDirectSoundBuffer_GetCaps (pDSBuf, &dsbcaps)) { Com_DPrintf ("*** GetCaps failed ***\n"); return qfalse; } // Make sure mixer is active if (DS_OK != IDirectSoundBuffer_Play (pDSBuf, 0, 0, DSBPLAY_LOOPING)) { Com_DPrintf ("*** Play failed ***\n"); return qfalse; } Com_DPrintf( " %d channel(s)\n" " %d bits/sample\n" " %d bytes/sec\n", dma.channels, dma.samplebits, dma.speed); gSndBufSize = dsbcaps.dwBufferBytes; IDirectSoundBuffer_Stop(pDSBuf); IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &mmstarttime.u.sample, &dwWrite); IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); dma.samples = gSndBufSize/(dma.samplebits/8); dma.samplepos = 0; dma.submission_chunk = 1; dma.buffer = NULL; sample16 = (dma.samplebits/8) - 1; return qtrue; } /* ================== DS_Init Direct-Sound support ================== */ static sndinitstat DS_Init (void) { DSCAPS dscaps; HRESULT hresult; LPDIRECTSOUNDCREATE pDirectSoundCreate; memset (&dma, 0, sizeof (dma)); dma.channels = 2; dma.samplebits = 16; switch( s_khz->integer ) { case 48: dma.speed = 48000; break; case 44: dma.speed = 44100; break; case 22: dma.speed = 22050; break; default: dma.speed = 11025; break; } Com_Printf( "Initializing DirectSound\n"); if ( !hInstDS ) { Com_DPrintf( "...loading dsound.dll: " ); hInstDS = LoadLibrary("dsound.dll"); if (hInstDS == NULL) { Com_DPrintf ("failed\n"); return SIS_FAILURE; } Com_DPrintf ("ok\n"); } pDirectSoundCreate = ( LPDIRECTSOUNDCREATE ) GetProcAddress(hInstDS,"DirectSoundCreate"); if (!pDirectSoundCreate) { Com_DPrintf ("...couldn't get DS proc addr\n"); return SIS_FAILURE; } Com_DPrintf( "...creating DS object: " ); while ( ( hresult = pDirectSoundCreate( NULL, &pDS, NULL ) ) != DS_OK ) { if (hresult != DSERR_ALLOCATED) { Com_DPrintf( "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 " APPLICATION " with no sound.", "Sound not available", MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY) { Com_DPrintf ("failed, hardware already in use\n" ); return SIS_NOTAVAIL; } } Com_DPrintf( "ok\n" ); dscaps.dwSize = sizeof(dscaps); if( DS_OK != IDirectSound_GetCaps( pDS, &dscaps ) ) { Com_DPrintf ("...couldn't get DS caps\n"); DS_Shutdown(); return SIS_FAILURE; } if( dscaps.dwFlags & DSCAPS_EMULDRIVER ) { Com_DPrintf ("...no DSound driver found\n" ); DS_Shutdown(); return SIS_FAILURE; } if( !DS_CreateBuffers() ) { DS_Shutdown (); return SIS_FAILURE; } Com_DPrintf("...completed successfully\n" ); return SIS_SUCCESS; } /* ============== DS_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. =============== */ static int DS_GetDMAPos(void) { MMTIME mmtime; int s; DWORD dwWrite; if (!pDSBuf) { return 0; } mmtime.wType = TIME_SAMPLES; IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &mmtime.u.sample, &dwWrite); s = mmtime.u.sample - mmstarttime.u.sample; s >>= sample16; s &= (dma.samples-1); return s; } /* ============== DS_BeginPainting Makes sure dma.buffer is valid =============== */ static void DS_BeginPainting (void) { int reps; DWORD dwSize2; DWORD *pbuf, *pbuf2; HRESULT hresult; DWORD dwStatus; if (!pDSBuf) return; // if the buffer was lost or stopped, restore it and/or restart it if (IDirectSoundBuffer_GetStatus (pDSBuf, &dwStatus) != DS_OK) { Com_EPrintf ("DS_BeginPainting: Couldn't get sound buffer status\n"); DS_Shutdown (); return; } if (dwStatus & DSBSTATUS_BUFFERLOST) IDirectSoundBuffer_Restore (pDSBuf); if (!(dwStatus & DSBSTATUS_PLAYING)) IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); // lock the dsound buffer reps = 0; dma.buffer = NULL; while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, ( void ** )&pbuf, &locksize, ( void ** )&pbuf2, &dwSize2, 0)) != DS_OK) { if (hresult != DSERR_BUFFERLOST) { Com_EPrintf( "DS_BeginPainting: Lock failed with error '%s'\n", DSoundError( hresult ) ); DS_Shutdown (); return; } IDirectSoundBuffer_Restore( pDSBuf ); if (++reps > 2) return; } dma.buffer = (byte *)pbuf; } /* ============== DS_Submit Send sound to device if buffer isn't really the dma buffer Also unlocks the dsound buffer =============== */ static void DS_Submit(void) { if (!pDSBuf) return; // unlock the dsound buffer IDirectSoundBuffer_Unlock(pDSBuf, dma.buffer, locksize, NULL, 0); } /* =========== DS_Activate Called when the main window gains or loses focus. The window have been destroyed and recreated between a deactivate and an activate. =========== */ static void DS_Activate (qboolean active) { if( !pDS ) { return; } if( active ) { if( !DS_CreateBuffers() ) { Com_EPrintf( "DS_Activate: DS_CreateBuffers failed\n" ); DS_Shutdown(); } } else { DS_DestroyBuffers(); } } void DS_FillAPI( snddmaAPI_t *api ) { api->Init = DS_Init; api->GetDMAPos = DS_GetDMAPos; api->Shutdown = DS_Shutdown; api->BeginPainting = DS_BeginPainting; api->Submit = DS_Submit; api->Activate = DS_Activate; }