xash3d-fwgs/engine/client/s_backend.c

483 lines
10 KiB
C

/*
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 <dsound.h>
#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();
}