This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/engine/client/sound/s_backend.c

529 lines
12 KiB
C

//=======================================================================
// Copyright XashXT Group 2009 ©
// s_direct.c - sound hardware output
//=======================================================================
#include <dsound.h>
#include "common.h"
#include "sound.h"
#define iDirectSoundCreate( a, b, c ) pDirectSoundCreate( a, b, c )
static HRESULT ( _stdcall *pDirectSoundCreate)(GUID* lpGUID, LPDIRECTSOUND* lplpDS, IUnknown* pUnkOuter );
static dllfunc_t dsound_funcs[] =
{
{ "DirectSoundCreate", (void **) &pDirectSoundCreate },
{ NULL, NULL }
};
dll_info_t dsound_dll = { "dsound.dll", dsound_funcs, NULL, NULL, NULL, false, 0, 0 };
#define SAMPLE_16BIT_SHIFT 1
#define SECONDARY_BUFFER_SIZE 0x10000
typedef enum
{
SIS_SUCCESS,
SIS_FAILURE,
SIS_NOTAVAIL
} si_state_t;
convar_t *s_primary;
static HWND snd_hwnd;
static qboolean snd_firsttime = true;
static qboolean primary_format_set;
/*
=======================================================================
Global variables. Must be visible to window-procedure function
so it can unlock and free the data block after it has been played.
=======================================================================
*/
DWORD locksize;
HPSTR lpData, lpData2;
LPWAVEHDR lpWaveHdr;
DWORD gSndBufSize;
MMTIME mmstarttime;
LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf;
LPDIRECTSOUND pDS;
qboolean SNDDMA_InitDirect( void *hInst );
void SNDDMA_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 Error";
}
/*
==================
DS_CreateBuffers
==================
*/
static qboolean DS_CreateBuffers( void *hInst )
{
DSBUFFERDESC dsbuf;
DSBCAPS dsbcaps;
WAVEFORMATEX pformat, format;
Mem_Set( &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;
MsgDev( D_NOTE, "DS_CreateBuffers: initialize\n" );
MsgDev( D_NOTE, "DS_CreateBuffers: setting EXCLUSIVE coop level " );
if( DS_OK != pDS->lpVtbl->SetCooperativeLevel( pDS, hInst, DSSCL_EXCLUSIVE ))
{
MsgDev( D_NOTE, "- failed\n" );
SNDDMA_FreeSound();
return false;
}
MsgDev( D_NOTE, "- ok\n" );
// get access to the primary buffer, if possible, so we can set the sound hardware format
Mem_Set( &dsbuf, 0, sizeof( dsbuf ));
dsbuf.dwSize = sizeof( DSBUFFERDESC );
dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
dsbuf.dwBufferBytes = 0;
dsbuf.lpwfxFormat = NULL;
Mem_Set( &dsbcaps, 0, sizeof( dsbcaps ));
dsbcaps.dwSize = sizeof( dsbcaps );
primary_format_set = false;
MsgDev( D_NOTE, "DS_CreateBuffers: creating primary buffer " );
if( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSPBuf, NULL ) == DS_OK )
{
pformat = format;
MsgDev( D_NOTE, "- ok\n" );
if( snd_firsttime )
MsgDev( D_NOTE, "DS_CreateBuffers: setting primary sound format " );
if( pDSPBuf->lpVtbl->SetFormat( pDSPBuf, &pformat ) != DS_OK )
{
if( snd_firsttime )
MsgDev( D_NOTE, "- failed\n" );
}
else
{
if( snd_firsttime )
MsgDev( D_NOTE, "- ok\n" );
primary_format_set = true;
}
}
else MsgDev( D_NOTE, "- failed\n" );
if( !primary_format_set || !s_primary->integer )
{
// create the secondary buffer we'll actually work with
Mem_Set( &dsbuf, 0, sizeof( dsbuf ));
dsbuf.dwSize = sizeof( DSBUFFERDESC );
dsbuf.dwFlags = (DSBCAPS_CTRLFREQUENCY|DSBCAPS_LOCSOFTWARE);
dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
dsbuf.lpwfxFormat = &format;
Mem_Set( &dsbcaps, 0, sizeof( dsbcaps ));
dsbcaps.dwSize = sizeof( dsbcaps );
MsgDev( D_NOTE, "DS_CreateBuffers: creating secondary buffer " );
if( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) == DS_OK )
{
MsgDev( D_NOTE, "- ok\n" );
}
else
{
// couldn't get hardware, fallback to software.
dsbuf.dwFlags = (DSBCAPS_LOCSOFTWARE|DSBCAPS_GETCURRENTPOSITION2);
if( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) != DS_OK )
{
MsgDev( D_NOTE, "- failed\n" );
SNDDMA_FreeSound ();
return false;
}
MsgDev( D_INFO, "- failed. forced to software\n" );
}
if( pDSBuf->lpVtbl->GetCaps( pDSBuf, &dsbcaps ) != DS_OK )
{
MsgDev( D_ERROR, "DS_CreateBuffers: GetCaps failed\n");
SNDDMA_FreeSound ();
return false;
}
MsgDev( D_NOTE, "DS_CreateBuffers: using secondary sound buffer\n" );
}
else
{
MsgDev( D_NOTE, "DS_CreateBuffers: using primary sound buffer\n" );
MsgDev( D_NOTE, "DS_CreateBuffers: setting WRITEPRIMARY coop level " );
if( pDS->lpVtbl->SetCooperativeLevel( pDS, hInst, DSSCL_WRITEPRIMARY ) != DS_OK )
{
MsgDev( D_NOTE, "- failed\n" );
SNDDMA_FreeSound ();
return false;
}
MsgDev( D_NOTE, "- ok\n" );
if( pDSPBuf->lpVtbl->GetCaps( pDSPBuf, &dsbcaps ) != DS_OK )
{
MsgDev( D_ERROR, "DS_CreateBuffers: GetCaps failed\n");
SNDDMA_FreeSound ();
return false;
}
pDSBuf = pDSPBuf;
}
// make sure mixer is active
if( pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING ) != DS_OK )
{
MsgDev( D_ERROR, "DS_CreateBuffers: looped sound play failed\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 ) Mem_Set( dma.buffer, 0, dma.samples * 2 );
SNDDMA_Submit();
return true;
}
/*
==================
DS_DestroyBuffers
==================
*/
static void DS_DestroyBuffers( void )
{
MsgDev( D_NOTE, "DS_DestroyBuffers: shutdown\n" );
if( pDS )
{
MsgDev( D_NOTE, "DS_DestroyBuffers: setting NORMAL coop level\n" );
pDS->lpVtbl->SetCooperativeLevel( pDS, snd_hwnd, DSSCL_NORMAL );
}
if( pDSBuf )
{
MsgDev( D_NOTE, "DS_DestroyBuffers: stopping and releasing sound buffer\n" );
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 ))
{
MsgDev( D_NOTE, "DS_DestroyBuffers: releasing primary buffer\n" );
pDSPBuf->lpVtbl->Release( pDSPBuf );
}
pDSBuf = NULL;
pDSPBuf = NULL;
dma.buffer = NULL;
}
/*
==================
SNDDMA_FreeSound
==================
*/
void SNDDMA_FreeSound( void )
{
if( pDS )
{
DS_DestroyBuffers();
pDS->lpVtbl->Release( pDS );
Sys_FreeLibrary( &dsound_dll );
}
pDS = NULL;
pDSBuf = NULL;
pDSPBuf = NULL;
lpData = NULL;
lpWaveHdr = NULL;
}
/*
==================
SNDDMA_InitDirect
Direct-Sound support
==================
*/
si_state_t SNDDMA_InitDirect( void *hInst )
{
DSCAPS dscaps;
HRESULT hresult;
if( !dsound_dll.link )
{
if( !Sys_LoadLibrary( NULL, &dsound_dll ))
return SIS_FAILURE;
}
MsgDev( D_NOTE, "SNDDMA_InitDirect: creating DS object " );
if(( hresult = iDirectSoundCreate( NULL, &pDS, NULL )) != DS_OK )
{
if( hresult != DSERR_ALLOCATED )
{
MsgDev( D_NOTE, "- failed\n" );
return SIS_FAILURE;
}
MsgDev( D_NOTE, "- failed, hardware already in use\n" );
return SIS_NOTAVAIL;
}
MsgDev( D_NOTE, "- ok\n" );
dscaps.dwSize = sizeof( dscaps );
if( pDS->lpVtbl->GetCaps( pDS, &dscaps ) != DS_OK )
MsgDev( D_ERROR, "SNDDMA_InitDirect: GetCaps failed\n");
if( dscaps.dwFlags & DSCAPS_EMULDRIVER )
{
MsgDev( D_ERROR, "SNDDMA_InitDirect: no DSound driver found\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 )
{
si_state_t stat = SIS_FAILURE; // assume DirectSound won't initialize
// already initialized
if( dma.initialized ) return true;
Mem_Set( &dma, 0, sizeof( dma ));
s_primary = Cvar_Get( "s_primary", "0", CVAR_INIT, "use direct primary buffer" );
// init DirectSound
stat = SNDDMA_InitDirect( hInst );
if( stat == SIS_SUCCESS )
{
if( snd_firsttime )
MsgDev( D_INFO, "Audio: DirectSound\n" );
}
else
{
if( snd_firsttime )
MsgDev( D_ERROR, "SNDDMA_Init: can't initialize sound device\n" );
return false;
}
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;
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();
}
}
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 )
MsgDev( D_WARN, "SNDDMA_BeginPainting: 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 );
// 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 )
{
MsgDev( D_ERROR, "SNDDMA_BeginPainting: lock error '%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_PrintDeviceName
===========
*/
void S_PrintDeviceName( void )
{
Msg( "Audio: DirectSound\n" );
}
/*
===========
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();
}