/* s_backend.c - sound hardware output Copyright (C) 2009 Uncle Mike 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 3 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. */ #include "common.h" #include "sound.h" #include #define iDirectSoundCreate( a, b, c ) pDirectSoundCreate( a, b, c ) static HRESULT ( _stdcall *pDirectSoundCreate)(GUID* lpGUID, LPDIRECTSOUND* lpDS, IUnknown* pUnkOuter ); static dllfunc_t dsound_funcs[] = { { "DirectSoundCreate", (void **) &pDirectSoundCreate }, { NULL, NULL } }; dll_info_t dsound_dll = { "dsound.dll", dsound_funcs, false }; #define SAMPLE_16BIT_SHIFT 1 #define SECONDARY_BUFFER_SIZE 0x10000 typedef enum { SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL } si_state_t; static qboolean snd_firsttime = true; static qboolean primary_format_set; static HWND snd_hwnd; /* ======================================================================= Global variables. Must be visible to window-procedure function so it can unlock and free the data block after it has been played. ======================================================================= */ static DWORD locksize; static HPSTR lpData, lpData2; static DWORD gSndBufSize; static MMTIME mmstarttime; static LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf; static LPDIRECTSOUND pDS; qboolean SNDDMA_InitDirect( void *hInst ); void SNDDMA_FreeSound( void ); static const char *DSoundError( int error ) { switch( error ) { case DSERR_BUFFERLOST: return "buffer is lost"; case DSERR_INVALIDCALL: return "invalid call"; case DSERR_INVALIDPARAM: return "invalid param"; case DSERR_PRIOLEVELNEEDED: return "invalid priority level"; } return "unknown error"; } /* ================== DS_CreateBuffers ================== */ static qboolean DS_CreateBuffers( void *hInst ) { WAVEFORMATEX pformat, format; DSBCAPS dsbcaps; DSBUFFERDESC dsbuf; memset( &format, 0, sizeof( format )); format.wFormatTag = WAVE_FORMAT_PCM; format.nChannels = 2; format.wBitsPerSample = 16; format.nSamplesPerSec = SOUND_DMA_SPEED; format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.cbSize = 0; if( pDS->lpVtbl->SetCooperativeLevel( pDS, hInst, DSSCL_EXCLUSIVE ) != DS_OK ) { Con_DPrintf( S_ERROR "DirectSound: failed to set EXCLUSIVE coop level\n" ); SNDDMA_FreeSound(); return false; } // 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( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSPBuf, NULL ) == DS_OK ) { pformat = format; if( pDSPBuf->lpVtbl->SetFormat( pDSPBuf, &pformat ) != DS_OK ) { if( snd_firsttime ) Con_DPrintf( S_ERROR "DirectSound: failed to set primary sound format\n" ); } else { primary_format_set = true; } } // 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( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) != DS_OK ) { // couldn't get hardware, fallback to software. dsbuf.dwFlags = (DSBCAPS_LOCSOFTWARE|DSBCAPS_GETCURRENTPOSITION2); if( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) != DS_OK ) { Con_DPrintf( S_ERROR "DirectSound: failed to create secondary buffer\n" ); SNDDMA_FreeSound (); return false; } } if( pDSBuf->lpVtbl->GetCaps( pDSBuf, &dsbcaps ) != DS_OK ) { Con_DPrintf( S_ERROR "DirectSound: failed to get capabilities\n" ); SNDDMA_FreeSound (); return false; } // make sure mixer is active if( pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING ) != DS_OK ) { Con_DPrintf( S_ERROR "DirectSound: failed to create circular buffer\n" ); SNDDMA_FreeSound (); return false; } // we don't want anyone to access the buffer directly w/o locking it first lpData = NULL; dma.samplepos = 0; snd_hwnd = (HWND)hInst; gSndBufSize = dsbcaps.dwBufferBytes; dma.samples = gSndBufSize / 2; dma.buffer = (byte *)lpData; SNDDMA_BeginPainting(); if( dma.buffer ) memset( dma.buffer, 0, dma.samples * 2 ); SNDDMA_Submit(); return true; } /* ================== DS_DestroyBuffers ================== */ static void DS_DestroyBuffers( void ) { if( pDS ) pDS->lpVtbl->SetCooperativeLevel( pDS, snd_hwnd, DSSCL_NORMAL ); 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 ); dma.buffer = NULL; pDSPBuf = NULL; pDSBuf = NULL; } /* ================== SNDDMA_LockSound ================== */ void SNDDMA_LockSound( void ) { if( pDSBuf != NULL ) pDSBuf->lpVtbl->Stop( pDSBuf ); } /* ================== SNDDMA_LockSound ================== */ void SNDDMA_UnlockSound( void ) { if( pDSBuf != NULL ) pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING ); } /* ================== SNDDMA_FreeSound ================== */ void SNDDMA_FreeSound( void ) { if( pDS ) { DS_DestroyBuffers(); pDS->lpVtbl->Release( pDS ); Sys_FreeLibrary( &dsound_dll ); } lpData = NULL; pDSPBuf = NULL; pDSBuf = NULL; pDS = NULL; } /* ================== SNDDMA_InitDirect Direct-Sound support ================== */ si_state_t SNDDMA_InitDirect( void *hInst ) { DSCAPS dscaps; HRESULT hresult; if( !dsound_dll.link ) { if( !Sys_LoadLibrary( &dsound_dll )) return SIS_FAILURE; } if(( hresult = iDirectSoundCreate( NULL, &pDS, NULL )) != DS_OK ) { if( hresult != DSERR_ALLOCATED ) return SIS_FAILURE; Con_DPrintf( S_ERROR "DirectSound: hardware already in use\n" ); return SIS_NOTAVAIL; } dscaps.dwSize = sizeof( dscaps ); if( pDS->lpVtbl->GetCaps( pDS, &dscaps ) != DS_OK ) Con_DPrintf( S_ERROR "DirectSound: failed to get capabilities\n" ); if( FBitSet( dscaps.dwFlags, DSCAPS_EMULDRIVER )) { Con_DPrintf( S_ERROR "DirectSound: driver not installed\n" ); SNDDMA_FreeSound(); return SIS_FAILURE; } if( !DS_CreateBuffers( hInst )) return SIS_FAILURE; return SIS_SUCCESS; } /* ================== SNDDMA_Init Try to find a sound device to mix for. Returns false if nothing is found. ================== */ int SNDDMA_Init( void *hInst ) { // already initialized if( dma.initialized ) return true; memset( &dma, 0, sizeof( dma )); // init DirectSound if( SNDDMA_InitDirect( hInst ) != SIS_SUCCESS ) return false; if( snd_firsttime ) Con_Printf( "Audio: DirectSound\n" ); dma.initialized = true; snd_firsttime = false; return true; } /* ============== 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 ) { int s; MMTIME mmtime; DWORD dwWrite; if( !dma.initialized ) return 0; mmtime.wType = TIME_SAMPLES; pDSBuf->lpVtbl->GetCurrentPosition( pDSBuf, &mmtime.u.sample, &dwWrite ); s = mmtime.u.sample - mmstarttime.u.sample; s >>= SAMPLE_16BIT_SHIFT; s &= (dma.samples - 1); return s; } /* ============== SNDDMA_GetSoundtime update global soundtime =============== */ int SNDDMA_GetSoundtime( void ) { static int buffers, oldsamplepos; int samplepos, fullsamples; fullsamples = dma.samples / 2; // it is possible to miscount buffers // if it has wrapped twice between // calls to S_Update. Oh well. samplepos = SNDDMA_GetDMAPos(); if( samplepos < oldsamplepos ) { buffers++; // buffer wrapped if( paintedtime > 0x40000000 ) { // time to chop things off to avoid 32 bit limits buffers = 0; paintedtime = fullsamples; S_StopAllSounds( true ); } } oldsamplepos = samplepos; return (buffers * fullsamples + samplepos / 2); } /* ============== SNDDMA_BeginPainting Makes sure dma.buffer is valid =============== */ void SNDDMA_BeginPainting( void ) { int reps; DWORD dwSize2; DWORD *pbuf, *pbuf2; HRESULT hr; DWORD dwStatus; if( !pDSBuf ) return; // if the buffer was lost or stopped, restore it and/or restart it if( pDSBuf->lpVtbl->GetStatus( pDSBuf, &dwStatus ) != DS_OK ) Con_DPrintf( S_ERROR "BeginPainting: couldn't get sound buffer status\n" ); if( dwStatus & DSBSTATUS_BUFFERLOST ) pDSBuf->lpVtbl->Restore( pDSBuf ); if( !FBitSet( dwStatus, DSBSTATUS_PLAYING )) pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING ); // lock the dsound buffer dma.buffer = NULL; reps = 0; while(( hr = pDSBuf->lpVtbl->Lock( pDSBuf, 0, gSndBufSize, &pbuf, &locksize, &pbuf2, &dwSize2, 0 )) != DS_OK ) { if( hr != DSERR_BUFFERLOST ) { Con_DPrintf( S_ERROR "BeginPainting: %s\n", DSoundError( hr )); S_Shutdown (); return; } else pDSBuf->lpVtbl->Restore( pDSBuf ); if( ++reps > 2 ) return; } dma.buffer = (byte *)pbuf; } /* ============== SNDDMA_Submit Send sound to device if buffer isn't really the dma buffer Also unlocks the dsound buffer =============== */ void SNDDMA_Submit( void ) { if( !dma.buffer ) return; // unlock the dsound buffer if( pDSBuf ) pDSBuf->lpVtbl->Unlock( pDSBuf, dma.buffer, locksize, NULL, 0 ); } /* ============== SNDDMA_Shutdown Reset the sound device for exiting =============== */ void SNDDMA_Shutdown( void ) { if( !dma.initialized ) return; dma.initialized = false; SNDDMA_FreeSound(); } /* =========== S_Activate Called when the main window gains or loses focus. The window have been destroyed and recreated between a deactivate and an activate. =========== */ void S_Activate( qboolean active, void *hInst ) { if( !dma.initialized ) return; snd_hwnd = (HWND)hInst; if( !pDS || !snd_hwnd ) return; if( active ) DS_CreateBuffers( snd_hwnd ); else DS_DestroyBuffers(); }