/*
===========================================================================
Return to Castle Wolfenstein multiplayer GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).
RTCW MP Source Code 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.
RTCW MP Source Code 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 RTCW MP Source Code. If not, see .
In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include
#include "../client/snd_local.h"
#include "win_local.h"
HRESULT ( WINAPI * pDirectSoundCreate )( GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter );
#define iDirectSoundCreate( a,b,c ) pDirectSoundCreate( a,b,c )
#define SECONDARY_BUFFER_SIZE 0x10000
static qboolean dsound_init;
static int sample16;
static DWORD gSndBufSize;
static DWORD locksize;
static LPDIRECTSOUND pDS;
static LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf;
static HINSTANCE hInstDS;
static qboolean snd_iswave = qfalse;
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";
}
HGLOBAL hWaveHdr;
LPWAVEHDR lpWaveHdr;
HWAVEOUT hWaveOut;
WAVEOUTCAPS wavecaps;
DWORD gSndBufSize;
MMTIME mmstarttime;
HANDLE hData;
HPSTR lpData, lpData2;
static qboolean wav_init = qfalse;
// starts at 0 for disabled
static int snd_buffer_count = 0;
static int sample16;
static int snd_sent, snd_completed;
// 64K is > 1 second at 16-bit, 22050 Hz
#define WAV_BUFFERS 64
#define WAV_MASK 0x3F
#define WAV_BUFFER_SIZE 0x0400
extern cvar_t *s_wavonly;
/*
==================
FreeSound
==================
*/
void FreeSound( void ) {
int i;
Com_DPrintf( "Shutting down sound system\n" );
if ( hWaveOut ) {
Com_DPrintf( "...resetting waveOut\n" );
waveOutReset( hWaveOut );
if ( lpWaveHdr ) {
Com_DPrintf( "...unpreparing headers\n" );
for ( i = 0 ; i < WAV_BUFFERS ; i++ )
waveOutUnprepareHeader( hWaveOut, lpWaveHdr + i, sizeof( WAVEHDR ) );
}
Com_DPrintf( "...closing waveOut\n" );
waveOutClose( hWaveOut );
if ( hWaveHdr ) {
Com_DPrintf( "...freeing WAV header\n" );
GlobalUnlock( hWaveHdr );
GlobalFree( hWaveHdr );
}
if ( hData ) {
Com_DPrintf( "...freeing WAV buffer\n" );
GlobalUnlock( hData );
GlobalFree( hData );
}
}
hWaveOut = 0;
hData = 0;
hWaveHdr = 0;
lpData = NULL;
lpWaveHdr = NULL;
wav_init = qfalse;
}
/*
==================
SNDDM_InitWav
Crappy windows multimedia base
==================
*/
qboolean SNDDMA_InitWav( void ) {
WAVEFORMATEX format;
int i;
HRESULT hr;
Com_Printf( "Initializing wave sound\n" );
snd_sent = 0;
snd_completed = 0;
dma.channels = 2;
dma.samplebits = 16;
if ( s_khz->value == 44 ) {
dma.speed = 44100;
}
if ( s_khz->value == 22 ) {
dma.speed = 22050;
} else {
dma.speed = 11025;
}
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 = 0;
format.nAvgBytesPerSec = format.nSamplesPerSec
* format.nBlockAlign;
/* Open a waveform device for output using window callback. */
Com_DPrintf( "...opening waveform device: " );
while ( ( hr = waveOutOpen( (LPHWAVEOUT)&hWaveOut, WAVE_MAPPER,
&format,
0, 0L, CALLBACK_NULL ) ) != MMSYSERR_NOERROR )
{
if ( hr != MMSYSERR_ALLOCATED ) {
Com_Printf( "failed\n" );
return qfalse;
}
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 Wolfenstein with no sound.",
"Sound not available",
MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION ) != IDRETRY ) {
Com_Printf( "hw in use\n" );
return qfalse;
}
}
Com_DPrintf( "ok\n" );
/*
* Allocate and lock memory for the waveform data. The memory
* for waveform data must be globally allocated with
* GMEM_MOVEABLE and GMEM_SHARE flags.
*/
Com_DPrintf( "...allocating waveform buffer: " );
gSndBufSize = WAV_BUFFERS * WAV_BUFFER_SIZE;
hData = GlobalAlloc( GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize );
if ( !hData ) {
Com_Printf( " failed\n" );
FreeSound();
return qfalse;
}
Com_DPrintf( "ok\n" );
Com_DPrintf( "...locking waveform buffer: " );
lpData = GlobalLock( hData );
if ( !lpData ) {
Com_Printf( " failed\n" );
FreeSound();
return qfalse;
}
memset( lpData, 0, gSndBufSize );
Com_DPrintf( "ok\n" );
/*
* Allocate and lock memory for the header. This memory must
* also be globally allocated with GMEM_MOVEABLE and
* GMEM_SHARE flags.
*/
Com_DPrintf( "...allocating waveform header: " );
hWaveHdr = GlobalAlloc( GMEM_MOVEABLE | GMEM_SHARE,
(DWORD) sizeof( WAVEHDR ) * WAV_BUFFERS );
if ( hWaveHdr == NULL ) {
Com_Printf( "failed\n" );
FreeSound();
return qfalse;
}
Com_DPrintf( "ok\n" );
Com_DPrintf( "...locking waveform header: " );
lpWaveHdr = (LPWAVEHDR) GlobalLock( hWaveHdr );
if ( lpWaveHdr == NULL ) {
Com_Printf( "failed\n" );
FreeSound();
return qfalse;
}
memset( lpWaveHdr, 0, sizeof( WAVEHDR ) * WAV_BUFFERS );
Com_DPrintf( "ok\n" );
/* After allocation, set up and prepare headers. */
Com_DPrintf( "...preparing 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 ) {
Com_Printf( "failed\n" );
FreeSound();
return qfalse;
}
}
Com_DPrintf( "ok\n" );
dma.samples = gSndBufSize / ( dma.samplebits / 8 );
dma.samplepos = 0;
dma.submission_chunk = 512;
dma.buffer = (unsigned char *) lpData;
sample16 = ( dma.samplebits / 8 ) - 1;
wav_init = qtrue;
return qtrue;
}
/*
==================
SNDDMA_Shutdown
==================
*/
void SNDDMA_Shutdown( void ) {
Com_DPrintf( "Shutting down sound system\n" );
if ( wav_init ) {
FreeSound();
return;
wav_init = qfalse;
}
if ( pDS ) {
Com_DPrintf( "Destroying DS buffers\n" );
if ( pDS ) {
Com_DPrintf( "...setting NORMAL coop level\n" );
pDS->lpVtbl->SetCooperativeLevel( pDS, g_wv.hWnd, DSSCL_NORMAL );
}
if ( pDSBuf ) {
Com_DPrintf( "...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 ) ) {
Com_DPrintf( "...releasing primary buffer\n" );
pDSPBuf->lpVtbl->Release( pDSPBuf );
}
pDSBuf = NULL;
pDSPBuf = NULL;
dma.buffer = NULL;
Com_DPrintf( "...releasing DS object\n" );
pDS->lpVtbl->Release( pDS );
}
if ( hInstDS ) {
Com_DPrintf( "...freeing DSOUND.DLL\n" );
FreeLibrary( hInstDS );
hInstDS = NULL;
}
pDS = NULL;
pDSBuf = NULL;
pDSPBuf = NULL;
dsound_init = qfalse;
memset( (void *)&dma, 0, sizeof( dma ) );
}
/*
==================
SNDDMA_Init
Initialize direct sound
Returns false if failed
==================
*/
int SNDDMA_Init( void ) {
memset( (void *)&dma, 0, sizeof( dma ) );
dsound_init = 0;
if ( !SNDDMA_InitDS() ) {
// DHM - Nerve :: If DS fails, try Wav
snd_iswave = SNDDMA_InitWav();
if ( snd_iswave ) {
Com_Printf( "Wave sound initialized successfully\n" );
} else {
return 0;
}
} else {
dsound_init = qtrue;
Com_Printf( "DirectSound initialized successfully\n" );
}
return 1;
}
int SNDDMA_InitDS() {
HRESULT hresult;
qboolean pauseTried;
DSBUFFERDESC dsbuf;
DSBCAPS dsbcaps;
WAVEFORMATEX format;
int clear;
if ( s_wavonly->integer ) {
snd_iswave = SNDDMA_InitWav();
if ( snd_iswave ) {
Com_Printf( "Wave sound init succeeded\n" );
}
return 1;
}
Com_Printf( "Initializing DirectSound\n" );
if ( !hInstDS ) {
Com_DPrintf( "...loading dsound.dll: " );
hInstDS = LoadLibrary( "dsound.dll" );
if ( hInstDS == NULL ) {
Com_Printf( "failed\n" );
return 0;
}
Com_DPrintf( "ok\n" );
pDirectSoundCreate = ( long ( __stdcall * )( struct _GUID *,struct IDirectSound **,struct IUnknown * ) )
GetProcAddress( hInstDS,"DirectSoundCreate" );
if ( !pDirectSoundCreate ) {
Com_Printf( "*** couldn't get DS proc addr ***\n" );
return 0;
}
}
Com_DPrintf( "...creating DS object: " );
pauseTried = qfalse;
while ( ( hresult = iDirectSoundCreate( NULL, &pDS, NULL ) ) != DS_OK ) {
if ( hresult != DSERR_ALLOCATED ) {
Com_Printf( "failed\n" );
return 0;
}
if ( pauseTried ) {
Com_Printf( "failed, hardware already in use\n" );
return 0;
}
// first try just waiting five seconds and trying again
// this will handle the case of a sysyem beep playing when the
// game starts
Com_DPrintf( "retrying...\n" );
Sleep( 3000 );
pauseTried = qtrue;
}
Com_DPrintf( "ok\n" );
Com_DPrintf( "...setting DSSCL_NORMAL coop level: " );
if ( DS_OK != pDS->lpVtbl->SetCooperativeLevel( pDS, g_wv.hWnd, DSSCL_NORMAL ) ) {
Com_Printf( "failed\n" );
SNDDMA_Shutdown();
return qfalse;
}
Com_DPrintf( "ok\n" );
// create the secondary buffer we'll actually work with
dma.channels = 2;
dma.samplebits = 16;
if ( s_khz->integer == 44 ) {
dma.speed = 44100;
} else if ( s_khz->integer == 22 ) {
dma.speed = 22050;
} else {
dma.speed = 11025;
}
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 = 0;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
memset( &dsbuf, 0, sizeof( dsbuf ) );
dsbuf.dwSize = sizeof( DSBUFFERDESC );
// Micah: take advantage of 2D hardware.if available.
dsbuf.dwFlags = 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 == pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) ) {
Com_Printf( "locked hardware. ok\n" );
} else {
// Couldn't get hardware, fallback to software.
dsbuf.dwFlags = DSBCAPS_LOCSOFTWARE;
if ( DS_OK != pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) ) {
Com_Printf( "failed\n" );
SNDDMA_Shutdown();
return qfalse;
}
Com_DPrintf( "forced to software. ok\n" );
}
// Make sure mixer is active
if ( DS_OK != pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING ) ) {
Com_Printf( "*** Looped sound play failed ***\n" );
SNDDMA_Shutdown();
return qfalse;
}
// get the returned buffer size
if ( DS_OK != pDSBuf->lpVtbl->GetCaps( pDSBuf, &dsbcaps ) ) {
Com_Printf( "*** GetCaps failed ***\n" );
SNDDMA_Shutdown();
return qfalse;
}
gSndBufSize = dsbcaps.dwBufferBytes;
dma.channels = format.nChannels;
dma.samplebits = format.wBitsPerSample;
dma.speed = format.nSamplesPerSec;
dma.samples = gSndBufSize / ( dma.samplebits / 8 );
dma.submission_chunk = 1;
dma.buffer = NULL; // must be locked first
// DHM - Nerve :: Clear out the buffer in case it has garbage that would loop while menu loads
SNDDMA_BeginPainting();
if ( dma.samplebits == 8 ) {
clear = 0x80;
} else {
clear = 0;
}
if ( dma.buffer ) {
Com_Memset( dma.buffer, clear, dma.samples * dma.samplebits / 8 );
}
SNDDMA_Submit();
// dhm
sample16 = ( dma.samplebits / 8 ) - 1;
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 ( wav_init ) {
s = snd_sent * WAV_BUFFER_SIZE;
s >>= sample16;
s &= ( dma.samples - 1 );
return s;
}
if ( !dsound_init || !pDSBuf ) {
return 0;
}
mmtime.wType = TIME_SAMPLES;
pDSBuf->lpVtbl->GetCurrentPosition( pDSBuf, &mmtime.u.sample, &dwWrite );
s = mmtime.u.sample;
s >>= sample16;
s &= ( dma.samples - 1 );
return s;
}
/*
==============
SNDDMA_BeginPainting
Makes sure dma.buffer is valid
===============
*/
void SNDDMA_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 ( pDSBuf->lpVtbl->GetStatus( pDSBuf, &dwStatus ) != DS_OK ) {
Com_Printf( "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
reps = 0;
dma.buffer = NULL;
while ( ( hresult = pDSBuf->lpVtbl->Lock( pDSBuf, 0, gSndBufSize, &pbuf, &locksize,
&pbuf2, &dwSize2, 0 ) ) != DS_OK )
{
if ( hresult != DSERR_BUFFERLOST ) {
Com_Printf( "SNDDMA_BeginPainting: Lock failed with error '%s'\n", DSoundError( hresult ) );
S_Shutdown();
return;
} else
{
pDSBuf->lpVtbl->Restore( pDSBuf );
}
if ( ++reps > 2 ) {
return;
}
}
dma.buffer = (unsigned char *)pbuf;
}
/*
==============
SNDDMA_Submit
Send sound to device if buffer isn't really the dma buffer
Also unlocks the dsound buffer
===============
*/
void SNDDMA_Submit( void ) {
LPWAVEHDR h;
int wResult;
// unlock the dsound buffer
if ( pDSBuf ) {
pDSBuf->lpVtbl->Unlock( pDSBuf, dma.buffer, locksize, NULL, 0 );
}
if ( !wav_init ) {
return;
}
//
// find which sound blocks have completed
//
while ( 1 )
{
if ( snd_completed == snd_sent ) {
Com_DPrintf( "Sound overrun\n" );
break;
}
if ( !( lpWaveHdr[ snd_completed & WAV_MASK].dwFlags & WHDR_DONE ) ) {
break;
}
snd_completed++; // this buffer has been played
}
//Com_Printf ("completed %i\n", snd_completed);
//
// submit a few new sound blocks
//
while ( ( ( snd_sent - snd_completed ) >> sample16 ) < 8 )
{
h = lpWaveHdr + ( snd_sent & WAV_MASK );
if ( s_paintedtime / 256 <= snd_sent ) {
break; // Com_Printf ("submit overrun\n");
}
//Com_Printf ("send %i\n", snd_sent);
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 ) {
Com_Printf( "Failed to write block to device\n" );
FreeSound();
return;
}
}
}
/*
=================
SNDDMA_Activate
When we change windows we need to do this
=================
*/
void SNDDMA_Activate( void ) {
if ( !pDS ) {
return;
}
if ( DS_OK != pDS->lpVtbl->SetCooperativeLevel( pDS, g_wv.hWnd, DSSCL_PRIORITY ) ) {
Com_Printf( "sound SetCooperativeLevel failed\n" );
SNDDMA_Shutdown();
}
}