mirror of
https://github.com/ptitSeb/Serious-Engine
synced 2024-11-25 11:45:53 +01:00
1757 lines
60 KiB
C++
1757 lines
60 KiB
C++
/* Copyright (c) 2002-2012 Croteam Ltd.
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of version 2 of the GNU General Public License as published by
|
|
the Free Software Foundation
|
|
|
|
|
|
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.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
|
|
|
|
#include "Engine/StdH.h"
|
|
|
|
|
|
// !!! FIXME : rcg12162001 This file really needs to be ripped apart and
|
|
// !!! FIXME : rcg12162001 into platform/driver specific subdirectories.
|
|
|
|
|
|
// !!! FIXME : rcg10132001 what is this file?
|
|
#ifdef PLATFORM_WIN32
|
|
#include "initguid.h"
|
|
#endif
|
|
|
|
|
|
#include <Engine/Engine.h>
|
|
#include <Engine/Sound/SoundLibrary.h>
|
|
#include <Engine/Base/Translation.h>
|
|
|
|
#include <Engine/Base/Shell.h>
|
|
#include <Engine/Base/Memory.h>
|
|
#include <Engine/Base/ErrorReporting.h>
|
|
#include <Engine/Base/ListIterator.inl>
|
|
#include <Engine/Base/Console.h>
|
|
#include <Engine/Base/Console_internal.h>
|
|
#include <Engine/Base/Statistics_Internal.h>
|
|
#include <Engine/Base/IFeel.h>
|
|
|
|
#include <Engine/Sound/SoundProfile.h>
|
|
#include <Engine/Sound/SoundListener.h>
|
|
#include <Engine/Sound/SoundData.h>
|
|
#include <Engine/Sound/SoundObject.h>
|
|
#include <Engine/Sound/SoundDecoder.h>
|
|
#include <Engine/Network/Network.h>
|
|
|
|
#include <Engine/Templates/StaticArray.cpp>
|
|
#include <Engine/Templates/StaticStackArray.cpp>
|
|
|
|
template class CStaticArray<CSoundListener>;
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma comment(lib, "winmm.lib")
|
|
#endif
|
|
|
|
// pointer to global sound library object
|
|
CSoundLibrary *_pSound = NULL;
|
|
|
|
|
|
// console variables
|
|
FLOAT snd_tmMixAhead = 0.2f; // mix-ahead in seconds
|
|
FLOAT snd_fSoundVolume = 1.0f; // master volume for sound playing [0..1]
|
|
FLOAT snd_fMusicVolume = 1.0f; // master volume for music playing [0..1]
|
|
// NOTES:
|
|
// - these 3d sound parameters have been set carefully, take extreme if changing !
|
|
// - ears distance of 20cm causes phase shift of up to 0.6ms which is very noticable
|
|
// and is more than enough, too large values cause too much distorsions in other effects
|
|
// - pan strength needs not to be very strong, since lrfilter has panning-like influence also
|
|
// - if down filter is too large, it makes too much influence even on small elevation changes
|
|
// and messes the situation completely
|
|
FLOAT snd_fDelaySoundSpeed = 1E10; // sound speed used for delay [m/s]
|
|
FLOAT snd_fDopplerSoundSpeed = 330.0f; // sound speed used for doppler [m/s]
|
|
FLOAT snd_fEarsDistance = 0.2f; // distance between listener's ears
|
|
FLOAT snd_fPanStrength = 0.1f; // panning modifier (0=none, 1= full)
|
|
FLOAT snd_fLRFilter = 3.0f; // filter for left-right
|
|
FLOAT snd_fBFilter = 5.0f; // filter for back
|
|
FLOAT snd_fUFilter = 1.0f; // filter for up
|
|
FLOAT snd_fDFilter = 3.0f; // filter for down
|
|
|
|
ENGINE_API INDEX snd_iFormat = 3;
|
|
INDEX snd_bMono = FALSE;
|
|
static INDEX snd_iDevice = -1;
|
|
static INDEX snd_iInterface = 2; // 0=WaveOut, 1=DirectSound, 2=EAX
|
|
static INDEX snd_iMaxOpenRetries = 3;
|
|
static INDEX snd_iMaxExtraChannels = 32;
|
|
static FLOAT snd_tmOpenFailDelay = 0.5f;
|
|
static FLOAT snd_fEAXPanning = 0.0f;
|
|
|
|
static FLOAT snd_fNormalizer = 0.9f;
|
|
static FLOAT _fLastNormalizeValue = 1;
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
extern HWND _hwndMain; // global handle for application window
|
|
static HWND _hwndCurrent = NULL;
|
|
static HINSTANCE _hInstDS = NULL;
|
|
#else
|
|
static CTString snd_strDeviceName;
|
|
#endif
|
|
|
|
static BOOL _bMuted = FALSE;
|
|
static INDEX _iLastEnvType = 1234;
|
|
static FLOAT _fLastEnvSize = 1234;
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
static FLOAT _fLastPanning = 1234;
|
|
static INDEX _iWriteOffset = 0;
|
|
static INDEX _iWriteOffset2 = 0;
|
|
|
|
// TEMP! - for writing mixer buffer to file
|
|
static FILE *_filMixerBuffer;
|
|
static BOOL _bOpened = FALSE;
|
|
#endif
|
|
|
|
#define WAVEOUTBLOCKSIZE 1024
|
|
#define MINPAN (1.0f)
|
|
#define MAXPAN (9.0f)
|
|
|
|
|
|
|
|
/**
|
|
* ----------------------------
|
|
* Sound Library functions
|
|
* ----------------------------
|
|
**/
|
|
|
|
// rcg12162001 Simple Directmedia Layer sound implementation.
|
|
#ifdef PLATFORM_UNIX
|
|
|
|
static Uint8 sdl_silence = 0;
|
|
static volatile SLONG sdl_backbuffer_allocation = 0;
|
|
static Uint8 *sdl_backbuffer = NULL;
|
|
static volatile SLONG sdl_backbuffer_pos = 0;
|
|
static volatile SLONG sdl_backbuffer_remain = 0;
|
|
static SDL_AudioDeviceID sdl_audio_device = 0;
|
|
|
|
static void sdl_audio_callback(void *userdata, Uint8 *stream, int len)
|
|
{
|
|
ASSERT(!_bDedicatedServer);
|
|
ASSERT(sdl_backbuffer != NULL);
|
|
ASSERT(sdl_backbuffer_remain <= sdl_backbuffer_allocation);
|
|
ASSERT(sdl_backbuffer_remain >= 0);
|
|
ASSERT(sdl_backbuffer_pos < sdl_backbuffer_allocation);
|
|
ASSERT(sdl_backbuffer_pos >= 0);
|
|
|
|
// "avail" is just the byte count before the end of the buffer.
|
|
// "cpysize" is how many bytes can actually be copied.
|
|
int avail = sdl_backbuffer_allocation - sdl_backbuffer_pos;
|
|
int cpysize = (len < sdl_backbuffer_remain) ? len : sdl_backbuffer_remain;
|
|
Uint8 *src = sdl_backbuffer + sdl_backbuffer_pos;
|
|
|
|
if (avail < cpysize) // Copy would pass end of ring buffer?
|
|
cpysize = avail;
|
|
|
|
if (cpysize > 0) {
|
|
memcpy(stream, src, cpysize); // move first block to SDL stream.
|
|
sdl_backbuffer_remain -= cpysize;
|
|
ASSERT(sdl_backbuffer_remain >= 0);
|
|
len -= cpysize;
|
|
ASSERT(len >= 0);
|
|
stream += cpysize;
|
|
sdl_backbuffer_pos += cpysize;
|
|
} // if
|
|
|
|
// See if we need to rotate to start of ring buffer...
|
|
ASSERT(sdl_backbuffer_pos <= sdl_backbuffer_allocation);
|
|
if (sdl_backbuffer_pos == sdl_backbuffer_allocation) {
|
|
sdl_backbuffer_pos = 0;
|
|
|
|
// we might need to feed SDL more data now...
|
|
if (len > 0) {
|
|
cpysize = (len < sdl_backbuffer_remain) ? len : sdl_backbuffer_remain;
|
|
if (cpysize > 0) {
|
|
memcpy(stream, sdl_backbuffer, cpysize); // move 2nd block.
|
|
sdl_backbuffer_pos += cpysize;
|
|
ASSERT(sdl_backbuffer_pos < sdl_backbuffer_allocation);
|
|
sdl_backbuffer_remain -= cpysize;
|
|
ASSERT(sdl_backbuffer_remain >= 0);
|
|
len -= cpysize;
|
|
ASSERT(len >= 0);
|
|
stream += cpysize;
|
|
} // if
|
|
} // if
|
|
} // if
|
|
|
|
// SDL _still_ needs more data than we've got! Fill with silence. (*shrug*)
|
|
if (len > 0) {
|
|
ASSERT(sdl_backbuffer_remain == 0);
|
|
memset(stream, sdl_silence, len);
|
|
} // if
|
|
} // sdl_audio_callback
|
|
|
|
|
|
// initialize the SDL audio subsystem.
|
|
|
|
static BOOL StartUp_SDLaudio( CSoundLibrary &sl, BOOL bReport=TRUE)
|
|
{
|
|
bReport=TRUE; // !!! FIXME ...how do you configure this externally?
|
|
|
|
// not using DirectSound (obviously)
|
|
sl.sl_bUsingDirectSound = FALSE;
|
|
sl.sl_bUsingEAX = FALSE;
|
|
snd_iDevice = 0;
|
|
|
|
ASSERT(!_bDedicatedServer);
|
|
if (_bDedicatedServer) {
|
|
CPrintF("Dedicated server; not initializing audio.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if( bReport) CPrintF(TRANSV("SDL audio initialization ...\n"));
|
|
|
|
SDL_AudioSpec desired, obtained;
|
|
SDL_zero(desired);
|
|
SDL_zero(obtained);
|
|
|
|
Sint16 bps = sl.sl_SwfeFormat.wBitsPerSample;
|
|
if (bps <= 8)
|
|
desired.format = AUDIO_U8;
|
|
else if (bps <= 16)
|
|
desired.format = AUDIO_S16LSB;
|
|
else if (bps <= 32)
|
|
desired.format = AUDIO_S32LSB;
|
|
else {
|
|
CPrintF(TRANSV("Unsupported bits-per-sample: %d\n"), bps);
|
|
return FALSE;
|
|
}
|
|
desired.freq = sl.sl_SwfeFormat.nSamplesPerSec;
|
|
|
|
// I dunno if this is the best idea, but I'll give it a try...
|
|
// should probably check a cvar for this...
|
|
if (desired.freq <= 11025)
|
|
desired.samples = 512;
|
|
else if (desired.freq <= 22050)
|
|
desired.samples = 1024;
|
|
else if (desired.freq <= 44100)
|
|
desired.samples = 2048;
|
|
else
|
|
desired.samples = 4096; // (*shrug*)
|
|
|
|
desired.channels = sl.sl_SwfeFormat.nChannels;
|
|
desired.userdata = &sl;
|
|
desired.callback = sdl_audio_callback;
|
|
|
|
// !!! FIXME rcg12162001 We force SDL to convert the audio stream on the
|
|
// !!! FIXME rcg12162001 fly to match sl.sl_SwfeFormat, but I'm curious
|
|
// !!! FIXME rcg12162001 if the Serious Engine can handle it if we changed
|
|
// !!! FIXME rcg12162001 sl.sl_SwfeFormat to match what the audio hardware
|
|
// !!! FIXME rcg12162001 can handle. I'll have to check later.
|
|
sdl_audio_device = SDL_OpenAudioDevice(snd_strDeviceName.IsEmpty() ? NULL : (const char *) snd_strDeviceName, 0, &desired, &obtained, 0);
|
|
if (!sdl_audio_device) {
|
|
CPrintF( TRANSV("SDL_OpenAudioDevice() error: %s\n"), SDL_GetError());
|
|
return FALSE;
|
|
}
|
|
|
|
sdl_silence = obtained.silence;
|
|
sdl_backbuffer_allocation = (obtained.size * 4);
|
|
sdl_backbuffer = (Uint8 *)AllocMemory(sdl_backbuffer_allocation);
|
|
sdl_backbuffer_remain = 0;
|
|
sdl_backbuffer_pos = 0;
|
|
|
|
// report success
|
|
if( bReport) {
|
|
CPrintF( TRANSV(" opened device: %s\n"), "SDL audio stream");
|
|
CPrintF( TRANSV(" %dHz, %dbit, %s\n"),
|
|
sl.sl_SwfeFormat.nSamplesPerSec,
|
|
sl.sl_SwfeFormat.wBitsPerSample,
|
|
SDL_GetCurrentAudioDriver());
|
|
}
|
|
|
|
// determine whole mixer buffer size from mixahead console variable
|
|
sl.sl_slMixerBufferSize = (SLONG)(ceil(snd_tmMixAhead*sl.sl_SwfeFormat.nSamplesPerSec) *
|
|
sl.sl_SwfeFormat.wBitsPerSample/8 * sl.sl_SwfeFormat.nChannels);
|
|
// align size to be next multiply of WAVEOUTBLOCKSIZE
|
|
sl.sl_slMixerBufferSize += WAVEOUTBLOCKSIZE - (sl.sl_slMixerBufferSize % WAVEOUTBLOCKSIZE);
|
|
// decoder buffer always works at 44khz
|
|
sl.sl_slDecodeBufferSize = sl.sl_slMixerBufferSize *
|
|
((44100+sl.sl_SwfeFormat.nSamplesPerSec-1)/sl.sl_SwfeFormat.nSamplesPerSec);
|
|
if( bReport) {
|
|
CPrintF(TRANSV(" parameters: %d Hz, %d bit, stereo, mix-ahead: %gs\n"),
|
|
sl.sl_SwfeFormat.nSamplesPerSec, sl.sl_SwfeFormat.wBitsPerSample, snd_tmMixAhead);
|
|
CPrintF(TRANSV(" output buffers: %d x %d bytes\n"), 2, obtained.size);
|
|
CPrintF(TRANSV(" mpx decode: %d bytes\n"), sl.sl_slDecodeBufferSize);
|
|
}
|
|
|
|
// initialize mixing and decoding buffer
|
|
sl.sl_pslMixerBuffer = (SLONG*)AllocMemory( sl.sl_slMixerBufferSize *2); // (*2 because of 32-bit buffer)
|
|
sl.sl_pswDecodeBuffer = (SWORD*)AllocMemory( sl.sl_slDecodeBufferSize+4); // (+4 because of linear interpolation of last samples)
|
|
|
|
// the audio callback can now safely fill the audio stream with silence
|
|
// until there is actual audio data to mix...
|
|
SDL_PauseAudioDevice(sdl_audio_device, 0);
|
|
|
|
// done
|
|
return TRUE;
|
|
} // StartUp_SDLaudio
|
|
|
|
|
|
// SDL audio shutdown procedure
|
|
static void ShutDown_SDLaudio( CSoundLibrary &sl)
|
|
{
|
|
SDL_PauseAudioDevice(sdl_audio_device, 1);
|
|
|
|
if (sdl_backbuffer != NULL) {
|
|
FreeMemory(sdl_backbuffer);
|
|
sdl_backbuffer = NULL;
|
|
}
|
|
|
|
if (sl.sl_pslMixerBuffer != NULL) {
|
|
FreeMemory( sl.sl_pslMixerBuffer);
|
|
sl.sl_pslMixerBuffer = NULL;
|
|
}
|
|
|
|
if (sl.sl_pswDecodeBuffer != NULL) {
|
|
FreeMemory(sl.sl_pswDecodeBuffer);
|
|
sl.sl_pswDecodeBuffer = NULL;
|
|
}
|
|
|
|
SDL_CloseAudioDevice(sdl_audio_device);
|
|
sdl_audio_device = 0;
|
|
} // ShutDown_SDLaudio
|
|
|
|
|
|
// SDL_LockAudio() must be in effect when calling this!
|
|
// ...and stay in effect until after CopyMixerBuffer_SDLaudio() is called!
|
|
static SLONG PrepareSoundBuffer_SDLaudio( CSoundLibrary &sl)
|
|
{
|
|
ASSERT(sdl_backbuffer_remain >= 0);
|
|
ASSERT(sdl_backbuffer_remain <= sdl_backbuffer_allocation);
|
|
return(sdl_backbuffer_allocation - sdl_backbuffer_remain);
|
|
} // PrepareSoundBuffer_SDLaudio
|
|
|
|
|
|
// SDL_LockAudio() must be in effect when calling this!
|
|
// ...and have been in effect since PrepareSoundBuffer_SDLaudio was called!
|
|
static void CopyMixerBuffer_SDLaudio( CSoundLibrary &sl, SLONG datasize)
|
|
{
|
|
ASSERT((sdl_backbuffer_allocation - sdl_backbuffer_remain) >= datasize);
|
|
|
|
SLONG fillpos = sdl_backbuffer_pos + sdl_backbuffer_remain;
|
|
if (fillpos > sdl_backbuffer_allocation)
|
|
fillpos -= sdl_backbuffer_allocation;
|
|
|
|
SLONG cpysize = datasize;
|
|
if ( (cpysize + fillpos) > sdl_backbuffer_allocation)
|
|
cpysize = sdl_backbuffer_allocation - fillpos;
|
|
|
|
Uint8 *src = sdl_backbuffer + fillpos;
|
|
CopyMixerBuffer_stereo(0, src, cpysize);
|
|
datasize -= cpysize;
|
|
sdl_backbuffer_remain += cpysize;
|
|
if (datasize > 0) { // rotate to start of ring buffer?
|
|
CopyMixerBuffer_stereo(cpysize, sdl_backbuffer, datasize);
|
|
sdl_backbuffer_remain += datasize;
|
|
} // if
|
|
|
|
ASSERT(sdl_backbuffer_remain <= sdl_backbuffer_allocation);
|
|
} // CopyMixerBuffer_SDLaudio
|
|
|
|
|
|
#endif // defined PLATFORM_UNIX (SDL audio implementation)
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Construct uninitialized sound library.
|
|
*/
|
|
CSoundLibrary::CSoundLibrary(void)
|
|
{
|
|
sl_csSound.cs_iIndex = 3000;
|
|
|
|
// access to the list of handlers must be locked
|
|
CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
|
|
// synchronize access to sounds
|
|
CTSingleLock slSounds(&sl_csSound, TRUE);
|
|
|
|
// clear sound format
|
|
memset( &sl_SwfeFormat, 0, sizeof(WAVEFORMATEX));
|
|
sl_EsfFormat = SF_NONE;
|
|
|
|
// reset buffer ptrs
|
|
sl_pslMixerBuffer = NULL;
|
|
sl_pswDecodeBuffer = NULL;
|
|
sl_pubBuffersMemory = NULL;
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
// clear wave out data
|
|
sl_hwoWaveOut = NULL;
|
|
|
|
// clear direct sound data
|
|
_hInstDS = NULL;
|
|
sl_pDS = NULL;
|
|
sl_pKSProperty = NULL;
|
|
sl_pDSPrimary = NULL;
|
|
sl_pDSSecondary = NULL;
|
|
sl_pDSSecondary2 = NULL;
|
|
sl_pDSListener = NULL;
|
|
sl_pDSSourceLeft = NULL;
|
|
sl_pDSSourceRight = NULL;
|
|
#endif
|
|
|
|
sl_bUsingDirectSound = FALSE;
|
|
sl_bUsingEAX = FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Destruct (and clean up).
|
|
*/
|
|
CSoundLibrary::~CSoundLibrary(void)
|
|
{
|
|
// access to the list of handlers must be locked
|
|
CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
|
|
// synchronize access to sounds
|
|
CTSingleLock slSounds(&sl_csSound, TRUE);
|
|
|
|
// clear sound enviroment
|
|
Clear();
|
|
|
|
// clear any installed sound decoders
|
|
CSoundDecoder::EndPlugins();
|
|
}
|
|
|
|
|
|
|
|
// post sound console variables' functions
|
|
|
|
static FLOAT _tmLastMixAhead = 1234;
|
|
static INDEX _iLastFormat = 1234;
|
|
static INDEX _iLastDevice = 1234;
|
|
static INDEX _iLastAPI = 1234;
|
|
|
|
static void SndPostFunc(void *pArgs)
|
|
{
|
|
// clamp variables
|
|
snd_tmMixAhead = Clamp( snd_tmMixAhead, 0.1f, 0.9f);
|
|
snd_iFormat = Clamp( snd_iFormat, (INDEX)CSoundLibrary::SF_NONE, (INDEX)CSoundLibrary::SF_44100_16);
|
|
snd_iDevice = Clamp( snd_iDevice, -1, 15);
|
|
snd_iInterface = Clamp( snd_iInterface, 0, 2);
|
|
// if any variable has been changed
|
|
if( _tmLastMixAhead!=snd_tmMixAhead || _iLastFormat!=snd_iFormat
|
|
|| _iLastDevice!=snd_iDevice || _iLastAPI!=snd_iInterface) {
|
|
// reinit sound format
|
|
_pSound->SetFormat( (enum CSoundLibrary::SoundFormat)snd_iFormat, TRUE);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* some internal functions
|
|
*/
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
// DirectSound shutdown procedure
|
|
static void ShutDown_dsound( CSoundLibrary &sl)
|
|
{
|
|
// free direct sound buffer(s)
|
|
sl.sl_bUsingDirectSound = FALSE;
|
|
sl.sl_bUsingEAX = FALSE;
|
|
|
|
if( sl.sl_pDSSourceRight!=NULL) {
|
|
sl.sl_pDSSourceRight->Release();
|
|
sl.sl_pDSSourceRight = NULL;
|
|
}
|
|
if( sl.sl_pDSSourceLeft != NULL) {
|
|
sl.sl_pDSSourceLeft->Release();
|
|
sl.sl_pDSSourceLeft = NULL;
|
|
}
|
|
if( sl.sl_pDSListener != NULL) {
|
|
sl.sl_pDSListener->Release();
|
|
sl.sl_pDSListener = NULL;
|
|
}
|
|
|
|
if( sl.sl_pDSSecondary2 != NULL) {
|
|
sl.sl_pDSSecondary2->Stop();
|
|
sl.sl_pDSSecondary2->Release();
|
|
sl.sl_pDSSecondary2 = NULL;
|
|
}
|
|
if( sl.sl_pDSSecondary != NULL) {
|
|
sl.sl_pDSSecondary->Stop();
|
|
sl.sl_pDSSecondary->Release();
|
|
sl.sl_pDSSecondary = NULL;
|
|
}
|
|
if( sl.sl_pDSPrimary!=NULL) {
|
|
sl.sl_pDSPrimary->Stop();
|
|
sl.sl_pDSPrimary->Release();
|
|
sl.sl_pDSPrimary = NULL;
|
|
}
|
|
|
|
if( sl.sl_pKSProperty != NULL) {
|
|
sl.sl_pKSProperty->Release();
|
|
sl.sl_pKSProperty = NULL;
|
|
}
|
|
|
|
// free direct sound object
|
|
if( sl.sl_pDS!=NULL) {
|
|
// reset cooperative level
|
|
if( _hwndCurrent!=NULL) sl.sl_pDS->SetCooperativeLevel( _hwndCurrent, DSSCL_NORMAL);
|
|
sl.sl_pDS->Release();
|
|
sl.sl_pDS = NULL;
|
|
}
|
|
// free direct sound library
|
|
if( _hInstDS != NULL) {
|
|
FreeLibrary(_hInstDS);
|
|
_hInstDS = NULL;
|
|
}
|
|
// free memory
|
|
if( sl.sl_pslMixerBuffer!=NULL) {
|
|
FreeMemory( sl.sl_pslMixerBuffer);
|
|
sl.sl_pslMixerBuffer = NULL;
|
|
}
|
|
if( sl.sl_pswDecodeBuffer!=NULL) {
|
|
FreeMemory( sl.sl_pswDecodeBuffer);
|
|
sl.sl_pswDecodeBuffer = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Set wave format from library format
|
|
*/
|
|
static void SetWaveFormat( CSoundLibrary::SoundFormat EsfFormat, WAVEFORMATEX &wfeFormat)
|
|
{
|
|
// change Library Wave Format
|
|
memset( &wfeFormat, 0, sizeof(WAVEFORMATEX));
|
|
wfeFormat.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfeFormat.nChannels = 2;
|
|
wfeFormat.wBitsPerSample = 16;
|
|
switch( EsfFormat) {
|
|
case CSoundLibrary::SF_11025_16: wfeFormat.nSamplesPerSec = 11025; break;
|
|
case CSoundLibrary::SF_22050_16: wfeFormat.nSamplesPerSec = 22050; break;
|
|
case CSoundLibrary::SF_44100_16: wfeFormat.nSamplesPerSec = 44100; break;
|
|
case CSoundLibrary::SF_NONE: ASSERTALWAYS( "Can't set to NONE format"); break;
|
|
default: ASSERTALWAYS( "Unknown Sound format"); break;
|
|
}
|
|
wfeFormat.nBlockAlign = (wfeFormat.wBitsPerSample / 8) * wfeFormat.nChannels;
|
|
wfeFormat.nAvgBytesPerSec = wfeFormat.nSamplesPerSec * wfeFormat.nBlockAlign;
|
|
}
|
|
|
|
|
|
/*
|
|
* Set library format from wave format
|
|
*/
|
|
static void SetLibraryFormat( CSoundLibrary &sl)
|
|
{
|
|
// if library format is none return
|
|
if( sl.sl_EsfFormat == CSoundLibrary::SF_NONE) return;
|
|
|
|
// else check wave format to determine library format
|
|
ULONG ulFormat = sl.sl_SwfeFormat.nSamplesPerSec;
|
|
// find format
|
|
switch( ulFormat) {
|
|
case 11025: sl.sl_EsfFormat = CSoundLibrary::SF_11025_16; break;
|
|
case 22050: sl.sl_EsfFormat = CSoundLibrary::SF_22050_16; break;
|
|
case 44100: sl.sl_EsfFormat = CSoundLibrary::SF_44100_16; break;
|
|
// unknown format
|
|
default:
|
|
ASSERTALWAYS( "Unknown sound format");
|
|
FatalError( TRANS("Unknown sound format"));
|
|
sl.sl_EsfFormat = CSoundLibrary::SF_ILLEGAL;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
static BOOL DSFail( CSoundLibrary &sl, char *strError)
|
|
{
|
|
CPrintF(strError);
|
|
ShutDown_dsound(sl);
|
|
snd_iInterface=1; // if EAX failed -> try DirectSound
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// some helper functions for DirectSound
|
|
static BOOL DSInitSecondary( CSoundLibrary &sl, LPDIRECTSOUNDBUFFER &pBuffer, SLONG slSize)
|
|
{
|
|
// eventuallt adjust for EAX
|
|
DWORD dwFlag3D = NONE;
|
|
if( snd_iInterface==2) {
|
|
dwFlag3D = DSBCAPS_CTRL3D;
|
|
sl.sl_SwfeFormat.nChannels=1; // mono output
|
|
sl.sl_SwfeFormat.nBlockAlign/=2;
|
|
sl.sl_SwfeFormat.nAvgBytesPerSec/=2;
|
|
slSize/=2;
|
|
}
|
|
DSBUFFERDESC dsBuffer;
|
|
memset( &dsBuffer, 0, sizeof(dsBuffer));
|
|
dsBuffer.dwSize = sizeof(DSBUFFERDESC);
|
|
dsBuffer.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | dwFlag3D;
|
|
dsBuffer.dwBufferBytes = slSize;
|
|
dsBuffer.lpwfxFormat = &sl.sl_SwfeFormat;
|
|
HRESULT hResult = sl.sl_pDS->CreateSoundBuffer( &dsBuffer, &pBuffer, NULL);
|
|
if( snd_iInterface==2) {
|
|
// revert back to original wave format (stereo)
|
|
sl.sl_SwfeFormat.nChannels=2;
|
|
sl.sl_SwfeFormat.nBlockAlign*=2;
|
|
sl.sl_SwfeFormat.nAvgBytesPerSec*=2;
|
|
}
|
|
if( hResult != DS_OK) return DSFail( sl, TRANS(" ! DirectSound error: Cannot create secondary buffer.\n"));
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static BOOL DSLockBuffer( CSoundLibrary &sl, LPDIRECTSOUNDBUFFER pBuffer, SLONG slSize, LPVOID &lpData, DWORD &dwSize)
|
|
{
|
|
INDEX ctRetries = 1000; // too much?
|
|
if( sl.sl_bUsingEAX) slSize/=2; // buffer is mono in case of EAX
|
|
FOREVER {
|
|
HRESULT hResult = pBuffer->Lock( 0, slSize, &lpData, &dwSize, NULL, NULL, 0);
|
|
if( hResult==DS_OK && slSize==dwSize) return TRUE;
|
|
if( hResult!=DSERR_BUFFERLOST) return DSFail( sl, TRANS(" ! DirectSound error: Cannot lock sound buffer.\n"));
|
|
if( ctRetries-- == 0) return DSFail( sl, TRANS(" ! DirectSound error: Couldn't restore sound buffer.\n"));
|
|
pBuffer->Restore();
|
|
}
|
|
}
|
|
|
|
|
|
static void DSPlayBuffers( CSoundLibrary &sl)
|
|
{
|
|
DWORD dw;
|
|
BOOL bInitiatePlay = FALSE;
|
|
ASSERT( sl.sl_pDSSecondary!=NULL && sl.sl_pDSPrimary!=NULL);
|
|
if( sl.sl_bUsingEAX && sl.sl_pDSSecondary2->GetStatus(&dw)==DS_OK && !(dw&DSBSTATUS_PLAYING)) bInitiatePlay = TRUE;
|
|
if( sl.sl_pDSSecondary->GetStatus(&dw)==DS_OK && !(dw&DSBSTATUS_PLAYING)) bInitiatePlay = TRUE;
|
|
if( sl.sl_pDSPrimary->GetStatus(&dw)==DS_OK && !(dw&DSBSTATUS_PLAYING)) bInitiatePlay = TRUE;
|
|
|
|
// done if all buffers are already playing
|
|
if( !bInitiatePlay) return;
|
|
|
|
// stop buffers (in case some buffers are playing
|
|
sl.sl_pDSPrimary->Stop();
|
|
sl.sl_pDSSecondary->Stop();
|
|
if( sl.sl_bUsingEAX) sl.sl_pDSSecondary2->Stop();
|
|
|
|
// check sound buffer lock and clear sound buffer(s)
|
|
LPVOID lpData;
|
|
DWORD dwSize;
|
|
if( !DSLockBuffer( sl, sl.sl_pDSSecondary, sl.sl_slMixerBufferSize, lpData, dwSize)) return;
|
|
memset( lpData, 0, dwSize);
|
|
sl.sl_pDSSecondary->Unlock( lpData, dwSize, NULL, 0);
|
|
if( sl.sl_bUsingEAX) {
|
|
if( !DSLockBuffer( sl, sl.sl_pDSSecondary2, sl.sl_slMixerBufferSize, lpData, dwSize)) return;
|
|
memset( lpData, 0, dwSize);
|
|
sl.sl_pDSSecondary2->Unlock( lpData, dwSize, NULL, 0);
|
|
// start playing EAX additional buffer
|
|
sl.sl_pDSSecondary2->Play( 0, 0, DSBPLAY_LOOPING);
|
|
}
|
|
// start playing standard DirectSound buffers
|
|
sl.sl_pDSPrimary->Play( 0, 0, DSBPLAY_LOOPING);
|
|
sl.sl_pDSSecondary->Play( 0, 0, DSBPLAY_LOOPING);
|
|
_iWriteOffset = 0;
|
|
_iWriteOffset2 = 0;
|
|
|
|
// adjust starting offsets for EAX
|
|
if( sl.sl_bUsingEAX) {
|
|
DWORD dwCursor1, dwCursor2;
|
|
SLONG slMinDelta = MAX_SLONG;
|
|
for( INDEX i=0; i<10; i++) { // shoud be enough to screw interrupts
|
|
sl.sl_pDSSecondary->GetCurrentPosition( &dwCursor1, NULL);
|
|
sl.sl_pDSSecondary2->GetCurrentPosition( &dwCursor2, NULL);
|
|
SLONG slDelta1 = dwCursor2-dwCursor1;
|
|
sl.sl_pDSSecondary2->GetCurrentPosition( &dwCursor2, NULL);
|
|
sl.sl_pDSSecondary->GetCurrentPosition( &dwCursor1, NULL);
|
|
SLONG slDelta2 = dwCursor2-dwCursor1;
|
|
SLONG slDelta = (slDelta1+slDelta2) /2;
|
|
if( slDelta<slMinDelta) slMinDelta = slDelta;
|
|
//CPrintF( "D1=%5d, D2=%5d, AD=%5d, MD=%5d\n", slDelta1, slDelta2, slDelta, slMinDelta);
|
|
}
|
|
if( slMinDelta<0) _iWriteOffset = -slMinDelta*2; // 2 because of offset is stereo
|
|
if( slMinDelta>0) _iWriteOffset2 = +slMinDelta*2;
|
|
_iWriteOffset += _iWriteOffset & 3; // round to 4 bytes
|
|
_iWriteOffset2 += _iWriteOffset2 & 3;
|
|
|
|
// assure that first writing offsets are inside buffers
|
|
if( _iWriteOffset >=sl.sl_slMixerBufferSize) _iWriteOffset -= sl.sl_slMixerBufferSize;
|
|
if( _iWriteOffset2>=sl.sl_slMixerBufferSize) _iWriteOffset2 -= sl.sl_slMixerBufferSize;
|
|
ASSERT( _iWriteOffset >=0 && _iWriteOffset <sl.sl_slMixerBufferSize);
|
|
ASSERT( _iWriteOffset2>=0 && _iWriteOffset2<sl.sl_slMixerBufferSize);
|
|
}
|
|
}
|
|
|
|
|
|
// init and set DirectSound format (internal)
|
|
|
|
static BOOL StartUp_dsound( CSoundLibrary &sl, BOOL bReport=TRUE)
|
|
{
|
|
// startup
|
|
sl.sl_bUsingDirectSound = FALSE;
|
|
ASSERT( _hInstDS==NULL && sl.sl_pDS==NULL);
|
|
ASSERT( sl.sl_pDSSecondary==NULL && sl.sl_pDSPrimary==NULL);
|
|
// update window handle (just in case)
|
|
HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter);
|
|
|
|
if( bReport) CPrintF(TRANSV("Direct Sound initialization ...\n"));
|
|
ASSERT( _hInstDS==NULL);
|
|
_hInstDS = LoadLibraryA( "dsound.dll");
|
|
if( _hInstDS==NULL) {
|
|
CPrintF( TRANS(" ! DirectSound error: Cannot load 'DSOUND.DLL'.\n"));
|
|
return FALSE;
|
|
}
|
|
// get main procedure address
|
|
pDirectSoundCreate = (HRESULT(WINAPI *)(GUID FAR *, LPDIRECTSOUND FAR *, IUnknown FAR *))GetProcAddress( _hInstDS, "DirectSoundCreate");
|
|
if( pDirectSoundCreate==NULL) return DSFail( sl, TRANS(" ! DirectSound error: Cannot get procedure address.\n"));
|
|
|
|
// init dsound
|
|
HRESULT hResult;
|
|
hResult = pDirectSoundCreate( NULL, &sl.sl_pDS, NULL);
|
|
if( hResult != DS_OK) return DSFail( sl, TRANS(" ! DirectSound error: Cannot create object.\n"));
|
|
|
|
// get capabilities
|
|
DSCAPS dsCaps;
|
|
dsCaps.dwSize = sizeof(dsCaps);
|
|
hResult = sl.sl_pDS->GetCaps( &dsCaps);
|
|
if( hResult != DS_OK) return DSFail( sl, TRANS(" ! DirectSound error: Cannot determine capabilites.\n"));
|
|
|
|
// fail if in emulation mode
|
|
if( dsCaps.dwFlags & DSCAPS_EMULDRIVER) {
|
|
CPrintF( TRANS(" ! DirectSound error: No driver installed.\n"));
|
|
ShutDown_dsound(sl);
|
|
return FALSE;
|
|
}
|
|
|
|
// set cooperative level to priority
|
|
_hwndCurrent = _hwndMain;
|
|
hResult = sl.sl_pDS->SetCooperativeLevel( _hwndCurrent, DSSCL_PRIORITY);
|
|
if( hResult != DS_OK) return DSFail( sl, TRANS(" ! DirectSound error: Cannot set cooperative level.\n"));
|
|
|
|
// prepare 3D flag if EAX
|
|
DWORD dwFlag3D = NONE;
|
|
if( snd_iInterface==2) dwFlag3D = DSBCAPS_CTRL3D;
|
|
|
|
// create primary sound buffer (must have one)
|
|
DSBUFFERDESC dsBuffer;
|
|
memset( &dsBuffer, 0, sizeof(dsBuffer));
|
|
dsBuffer.dwSize = sizeof(dsBuffer);
|
|
dsBuffer.dwFlags = DSBCAPS_PRIMARYBUFFER | dwFlag3D;
|
|
dsBuffer.dwBufferBytes = 0;
|
|
dsBuffer.lpwfxFormat = NULL;
|
|
hResult = sl.sl_pDS->CreateSoundBuffer( &dsBuffer, &sl.sl_pDSPrimary, NULL);
|
|
if( hResult != DS_OK) return DSFail( sl, TRANS(" ! DirectSound error: Cannot create primary sound buffer.\n"));
|
|
|
|
// set primary buffer format
|
|
WAVEFORMATEX wfx = sl.sl_SwfeFormat;
|
|
hResult = sl.sl_pDSPrimary->SetFormat(&wfx);
|
|
if( hResult != DS_OK) return DSFail( sl, TRANS(" ! DirectSound error: Cannot set primary sound buffer format.\n"));
|
|
|
|
// startup secondary sound buffer(s)
|
|
SLONG slBufferSize = (SLONG)(ceil(snd_tmMixAhead*sl.sl_SwfeFormat.nSamplesPerSec) *
|
|
sl.sl_SwfeFormat.wBitsPerSample/8 * sl.sl_SwfeFormat.nChannels);
|
|
if( !DSInitSecondary( sl, sl.sl_pDSSecondary, slBufferSize)) return FALSE;
|
|
|
|
// set some additionals for EAX
|
|
if( snd_iInterface==2)
|
|
{
|
|
// 2nd secondary buffer
|
|
if( !DSInitSecondary( sl, sl.sl_pDSSecondary2, slBufferSize)) return FALSE;
|
|
// set 3D for all buffers
|
|
HRESULT hr1,hr2,hr3,hr4;
|
|
hr1 = sl.sl_pDSPrimary->QueryInterface( IID_IDirectSound3DListener, (LPVOID*)&sl.sl_pDSListener);
|
|
hr2 = sl.sl_pDSSecondary->QueryInterface( IID_IDirectSound3DBuffer, (LPVOID*)&sl.sl_pDSSourceLeft);
|
|
hr3 = sl.sl_pDSSecondary2->QueryInterface( IID_IDirectSound3DBuffer, (LPVOID*)&sl.sl_pDSSourceRight);
|
|
if( hr1!=DS_OK || hr2!=DS_OK || hr3!=DS_OK) return DSFail( sl, TRANS(" ! DirectSound3D error: Cannot set 3D sound buffer.\n"));
|
|
|
|
hr1 = sl.sl_pDSListener->SetPosition( 0,0,0, DS3D_DEFERRED);
|
|
hr2 = sl.sl_pDSListener->SetOrientation( 0,0,1, 0,1,0, DS3D_DEFERRED);
|
|
hr3 = sl.sl_pDSListener->SetRolloffFactor( 1, DS3D_DEFERRED);
|
|
if( hr1!=DS_OK || hr2!=DS_OK || hr3!=DS_OK) return DSFail( sl, TRANS(" ! DirectSound3D error: Cannot set 3D parameters for listener.\n"));
|
|
hr1 = sl.sl_pDSSourceLeft->SetMinDistance( MINPAN, DS3D_DEFERRED);
|
|
hr2 = sl.sl_pDSSourceLeft->SetMaxDistance( MAXPAN, DS3D_DEFERRED);
|
|
hr3 = sl.sl_pDSSourceRight->SetMinDistance( MINPAN, DS3D_DEFERRED);
|
|
hr4 = sl.sl_pDSSourceRight->SetMaxDistance( MAXPAN, DS3D_DEFERRED);
|
|
if( hr1!=DS_OK || hr2!=DS_OK || hr3!=DS_OK || hr4!=DS_OK) {
|
|
return DSFail( sl, TRANS(" ! DirectSound3D error: Cannot set 3D parameters for sound source.\n"));
|
|
}
|
|
// apply
|
|
hResult = sl.sl_pDSListener->CommitDeferredSettings();
|
|
if( hResult!=DS_OK) return DSFail( sl, TRANS(" ! DirectSound3D error: Cannot apply 3D parameters.\n"));
|
|
// reset EAX parameters
|
|
_fLastPanning = 1234;
|
|
_iLastEnvType = 1234;
|
|
_fLastEnvSize = 1234;
|
|
|
|
// query property interface to EAX
|
|
hResult = sl.sl_pDSSourceLeft->QueryInterface( IID_IKsPropertySet, (LPVOID*)&sl.sl_pKSProperty);
|
|
if( hResult != DS_OK) return DSFail( sl, TRANS(" ! EAX error: Cannot set property interface.\n"));
|
|
// query support
|
|
ULONG ulSupport = 0;
|
|
hResult = sl.sl_pKSProperty->QuerySupport( DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENT, &ulSupport);
|
|
if( hResult != DS_OK || !(ulSupport&KSPROPERTY_SUPPORT_SET)) return DSFail( sl, TRANS(" ! EAX error: Cannot query property support.\n"));
|
|
hResult = sl.sl_pKSProperty->QuerySupport( DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENTSIZE, &ulSupport);
|
|
if( hResult != DS_OK || !(ulSupport&KSPROPERTY_SUPPORT_SET)) return DSFail( sl, TRANS(" ! EAX error: Cannot query property support.\n"));
|
|
// made it - EAX's on!
|
|
sl.sl_bUsingEAX = TRUE;
|
|
}
|
|
|
|
// mark that dsound is operative and set mixer buffer size (decoder buffer always works at 44khz)
|
|
_iWriteOffset = 0;
|
|
_iWriteOffset2 = 0;
|
|
sl.sl_bUsingDirectSound = TRUE;
|
|
sl.sl_slMixerBufferSize = slBufferSize;
|
|
sl.sl_slDecodeBufferSize = sl.sl_slMixerBufferSize *
|
|
((44100+sl.sl_SwfeFormat.nSamplesPerSec-1) /sl.sl_SwfeFormat.nSamplesPerSec);
|
|
// allocate mixing and decoding buffers
|
|
sl.sl_pslMixerBuffer = (SLONG*)AllocMemory( sl.sl_slMixerBufferSize *2); // (*2 because of 32-bit buffer)
|
|
sl.sl_pswDecodeBuffer = (SWORD*)AllocMemory( sl.sl_slDecodeBufferSize+4); // (+4 because of linear interpolation of last samples)
|
|
|
|
// report success
|
|
if( bReport) {
|
|
CTString strDevice = TRANS("default device");
|
|
if( snd_iDevice>=0) strDevice.PrintF( TRANS("device %d"), snd_iDevice);
|
|
CPrintF( TRANS(" %dHz, %dbit, %s, mix-ahead: %gs\n"),
|
|
sl.sl_SwfeFormat.nSamplesPerSec, sl.sl_SwfeFormat.wBitsPerSample, strDevice, snd_tmMixAhead);
|
|
CPrintF(TRANSV(" mixer buffer size: %d KB\n"), sl.sl_slMixerBufferSize /1024);
|
|
CPrintF(TRANSV(" decode buffer size: %d KB\n"), sl.sl_slDecodeBufferSize/1024);
|
|
// EAX?
|
|
CTString strEAX = TRANS("Disabled");
|
|
if( sl.sl_bUsingEAX) strEAX = TRANS("Enabled");
|
|
CPrintF( TRANS(" EAX: %s\n"), strEAX);
|
|
}
|
|
// done
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// set WaveOut format (internal)
|
|
|
|
static INDEX _ctChannelsOpened = 0;
|
|
static BOOL StartUp_waveout( CSoundLibrary &sl, BOOL bReport=TRUE)
|
|
{
|
|
// not using DirectSound (obviously)
|
|
sl.sl_bUsingDirectSound = FALSE;
|
|
sl.sl_bUsingEAX = FALSE;
|
|
if( bReport) CPrintF(TRANSV("WaveOut initialization ...\n"));
|
|
// set maximum total number of retries for device opening
|
|
INDEX ctMaxRetries = snd_iMaxOpenRetries;
|
|
_ctChannelsOpened = 0;
|
|
MMRESULT res;
|
|
// repeat
|
|
FOREVER {
|
|
// try to open wave device
|
|
HWAVEOUT hwo;
|
|
res = waveOutOpen( &hwo, (snd_iDevice<0)?WAVE_MAPPER:snd_iDevice, &sl.sl_SwfeFormat, NULL, NULL, NONE);
|
|
// if opened
|
|
if( res == MMSYSERR_NOERROR) {
|
|
_ctChannelsOpened++;
|
|
// if first one
|
|
if (_ctChannelsOpened==1) {
|
|
// remember as used waveout
|
|
sl.sl_hwoWaveOut = hwo;
|
|
// if extra channel
|
|
} else {
|
|
// remember under extra
|
|
sl.sl_ahwoExtra.Push() = hwo;
|
|
}
|
|
// if no extra channels should be taken
|
|
if (_ctChannelsOpened>=snd_iMaxExtraChannels+1) {
|
|
// no more tries
|
|
break;
|
|
}
|
|
// if cannot open
|
|
} else {
|
|
// decrement retry counter
|
|
ctMaxRetries--;
|
|
// if no more retries
|
|
if (ctMaxRetries<0) {
|
|
// quit trying
|
|
break;
|
|
// if more retries left
|
|
} else {
|
|
// wait a bit (probably sound-scheme is playing)
|
|
Sleep(int(snd_tmOpenFailDelay*1000));
|
|
}
|
|
}
|
|
}
|
|
|
|
// if couldn't set format
|
|
if( _ctChannelsOpened==0 && res != MMSYSERR_NOERROR) {
|
|
// report error
|
|
CTString strError;
|
|
switch (res) {
|
|
case MMSYSERR_ALLOCATED: strError = TRANS("Device already in use."); break;
|
|
case MMSYSERR_BADDEVICEID: strError = TRANS("Bad device number."); break;
|
|
case MMSYSERR_NODRIVER: strError = TRANS("No driver installed."); break;
|
|
case MMSYSERR_NOMEM: strError = TRANS("Memory allocation problem."); break;
|
|
case WAVERR_BADFORMAT: strError = TRANS("Unsupported data format."); break;
|
|
case WAVERR_SYNC: strError = TRANS("Wrong flag?"); break;
|
|
default: strError.PrintF( "%d", res);
|
|
};
|
|
CPrintF( TRANS(" ! WaveOut error: %s\n"), strError);
|
|
return FALSE;
|
|
}
|
|
|
|
// get waveout capabilities
|
|
WAVEOUTCAPS woc;
|
|
memset( &woc, 0, sizeof(woc));
|
|
res = waveOutGetDevCaps((int)sl.sl_hwoWaveOut, &woc, sizeof(woc));
|
|
// report success
|
|
if( bReport) {
|
|
CTString strDevice = TRANS("default device");
|
|
if( snd_iDevice>=0) strDevice.PrintF( TRANS("device %d"), snd_iDevice);
|
|
CPrintF( TRANS(" opened device: %s\n"), woc.szPname);
|
|
CPrintF( TRANS(" %dHz, %dbit, %s\n"),
|
|
sl.sl_SwfeFormat.nSamplesPerSec, sl.sl_SwfeFormat.wBitsPerSample, strDevice);
|
|
}
|
|
|
|
// determine whole mixer buffer size from mixahead console variable
|
|
sl.sl_slMixerBufferSize = (SLONG)(ceil(snd_tmMixAhead*sl.sl_SwfeFormat.nSamplesPerSec) *
|
|
sl.sl_SwfeFormat.wBitsPerSample/8 * sl.sl_SwfeFormat.nChannels);
|
|
// align size to be next multiply of WAVEOUTBLOCKSIZE
|
|
sl.sl_slMixerBufferSize += WAVEOUTBLOCKSIZE - (sl.sl_slMixerBufferSize % WAVEOUTBLOCKSIZE);
|
|
// determine number of WaveOut buffers
|
|
const INDEX ctWOBuffers = sl.sl_slMixerBufferSize / WAVEOUTBLOCKSIZE;
|
|
// decoder buffer always works at 44khz
|
|
sl.sl_slDecodeBufferSize = sl.sl_slMixerBufferSize *
|
|
((44100+sl.sl_SwfeFormat.nSamplesPerSec-1)/sl.sl_SwfeFormat.nSamplesPerSec);
|
|
if( bReport) {
|
|
CPrintF(TRANSV(" parameters: %d Hz, %d bit, stereo, mix-ahead: %gs\n"),
|
|
sl.sl_SwfeFormat.nSamplesPerSec, sl.sl_SwfeFormat.wBitsPerSample, snd_tmMixAhead);
|
|
CPrintF(TRANSV(" output buffers: %d x %d bytes\n"), ctWOBuffers, WAVEOUTBLOCKSIZE),
|
|
CPrintF(TRANSV(" mpx decode: %d bytes\n"), sl.sl_slDecodeBufferSize),
|
|
CPrintF(TRANSV(" extra sound channels taken: %d\n"), _ctChannelsOpened-1);
|
|
}
|
|
|
|
// initialise waveout sound buffers
|
|
sl.sl_pubBuffersMemory = (UBYTE*)AllocMemory( sl.sl_slMixerBufferSize);
|
|
memset( sl.sl_pubBuffersMemory, 0, sl.sl_slMixerBufferSize);
|
|
sl.sl_awhWOBuffers.New(ctWOBuffers);
|
|
for( INDEX iBuffer = 0; iBuffer<sl.sl_awhWOBuffers.Count(); iBuffer++) {
|
|
WAVEHDR &wh = sl.sl_awhWOBuffers[iBuffer];
|
|
wh.lpData = (char*)(sl.sl_pubBuffersMemory + iBuffer*WAVEOUTBLOCKSIZE);
|
|
wh.dwBufferLength = WAVEOUTBLOCKSIZE;
|
|
wh.dwFlags = 0;
|
|
}
|
|
// initialize mixing and decoding buffer
|
|
sl.sl_pslMixerBuffer = (SLONG*)AllocMemory( sl.sl_slMixerBufferSize *2); // (*2 because of 32-bit buffer)
|
|
sl.sl_pswDecodeBuffer = (SWORD*)AllocMemory( sl.sl_slDecodeBufferSize+4); // (+4 because of linear interpolation of last samples)
|
|
|
|
// done
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
* set sound format
|
|
*/
|
|
static void SetFormat_internal( CSoundLibrary &sl, CSoundLibrary::SoundFormat EsfNew, BOOL bReport)
|
|
{
|
|
// access to the list of handlers must be locked
|
|
CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
|
|
// synchronize access to sounds
|
|
CTSingleLock slSounds(&sl.sl_csSound, TRUE);
|
|
|
|
// remember library format
|
|
sl.sl_EsfFormat = EsfNew;
|
|
// release library
|
|
sl.ClearLibrary();
|
|
|
|
// if none skip initialization
|
|
_fLastNormalizeValue = 1;
|
|
if( bReport) CPrintF(TRANSV("Setting sound format ...\n"));
|
|
if( sl.sl_EsfFormat == CSoundLibrary::SF_NONE) {
|
|
if( bReport) CPrintF(TRANSV(" (no sound)\n"));
|
|
return;
|
|
}
|
|
|
|
// set wave format from library format
|
|
SetWaveFormat( EsfNew, sl.sl_SwfeFormat);
|
|
snd_iDevice = Clamp( snd_iDevice, -1, (INDEX)(sl.sl_ctWaveDevices-1));
|
|
snd_tmMixAhead = Clamp( snd_tmMixAhead, 0.1f, 0.9f);
|
|
snd_iInterface = Clamp( snd_iInterface, 0, 2);
|
|
|
|
BOOL bSoundOK = FALSE;
|
|
#ifdef PLATFORM_WIN32
|
|
if( snd_iInterface==2) {
|
|
// if wanted, 1st try to set EAX
|
|
bSoundOK = StartUp_dsound( sl, bReport);
|
|
}
|
|
if( !bSoundOK && snd_iInterface==1) {
|
|
// if wanted, 2nd try to set DirectSound
|
|
bSoundOK = StartUp_dsound( sl, bReport);
|
|
}
|
|
// if DirectSound failed or not wanted
|
|
if( !bSoundOK) {
|
|
// try waveout
|
|
bSoundOK = StartUp_waveout( sl, bReport);
|
|
snd_iInterface = 0; // mark that DirectSound didn't make it
|
|
}
|
|
#else
|
|
bSoundOK = StartUp_SDLaudio(sl, bReport);
|
|
#endif
|
|
|
|
// if didn't make it by now
|
|
if( bReport) CPrintF("\n");
|
|
if( !bSoundOK) {
|
|
// revert to none in case sound init was unsuccessful
|
|
sl.sl_EsfFormat = CSoundLibrary::SF_NONE;
|
|
return;
|
|
}
|
|
// set library format from wave format
|
|
SetLibraryFormat(sl);
|
|
|
|
// add timer handler
|
|
_pTimer->AddHandler(&sl.sl_thTimerHandler);
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialization
|
|
*/
|
|
void CSoundLibrary::Init(void)
|
|
{
|
|
// access to the list of handlers must be locked
|
|
CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
|
|
// synchronize access to sounds
|
|
CTSingleLock slSounds(&sl_csSound, TRUE);
|
|
|
|
_pShell->DeclareSymbol( "void SndPostFunc(INDEX);", (void *) &SndPostFunc);
|
|
|
|
_pShell->DeclareSymbol( " user INDEX snd_bMono;", (void *) &snd_bMono);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fEarsDistance;", (void *) &snd_fEarsDistance);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fDelaySoundSpeed;", (void *) &snd_fDelaySoundSpeed);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fDopplerSoundSpeed;", (void *) &snd_fDopplerSoundSpeed);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fPanStrength;", (void *) &snd_fPanStrength);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fLRFilter;", (void *) &snd_fLRFilter);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fBFilter;", (void *) &snd_fBFilter);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fUFilter;", (void *) &snd_fUFilter);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fDFilter;", (void *) &snd_fDFilter);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fSoundVolume;", (void *) &snd_fSoundVolume);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fMusicVolume;", (void *) &snd_fMusicVolume);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fNormalizer;", (void *) &snd_fNormalizer);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_tmMixAhead post:SndPostFunc;", (void *) &snd_tmMixAhead);
|
|
_pShell->DeclareSymbol( "persistent user INDEX snd_iInterface post:SndPostFunc;", (void *) &snd_iInterface);
|
|
_pShell->DeclareSymbol( "persistent user INDEX snd_iDevice post:SndPostFunc;", (void *) &snd_iDevice);
|
|
_pShell->DeclareSymbol( "persistent user INDEX snd_iFormat post:SndPostFunc;", (void *) &snd_iFormat);
|
|
_pShell->DeclareSymbol( "persistent user INDEX snd_iMaxExtraChannels;", (void *) &snd_iMaxExtraChannels);
|
|
_pShell->DeclareSymbol( "persistent user INDEX snd_iMaxOpenRetries;", (void *) &snd_iMaxOpenRetries);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_tmOpenFailDelay;", (void *) &snd_tmOpenFailDelay);
|
|
_pShell->DeclareSymbol( "persistent user FLOAT snd_fEAXPanning;", (void *) &snd_fEAXPanning);
|
|
|
|
#ifdef PLATFORM_UNIX
|
|
_pShell->DeclareSymbol( "persistent user CTString snd_strDeviceName;", (void *) &snd_strDeviceName);
|
|
#endif
|
|
|
|
// !!! FIXME : rcg12162001 This should probably be done everywhere, honestly.
|
|
#ifdef PLATFORM_UNIX
|
|
if (_bDedicatedServer) {
|
|
CPrintF(TRANSV("Dedicated server; not initializing sound.\n"));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// print header
|
|
CPrintF(TRANSV("Initializing sound...\n"));
|
|
|
|
// initialize sound library and set no-sound format
|
|
SetFormat(SF_NONE);
|
|
|
|
// initialize any installed sound decoders
|
|
|
|
CSoundDecoder::InitPlugins();
|
|
|
|
sl_ctWaveDevices = 0; // rcg11012005 valgrind fix.
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
// get number of devices
|
|
const INDEX ctDevices = waveOutGetNumDevs();
|
|
CPrintF(TRANSV(" Detected devices: %d\n"), ctDevices);
|
|
sl_ctWaveDevices = ctDevices;
|
|
|
|
// for each device
|
|
for(INDEX iDevice=0; iDevice<ctDevices; iDevice++) {
|
|
// get description
|
|
WAVEOUTCAPS woc;
|
|
memset( &woc, 0, sizeof(woc));
|
|
MMRESULT res = waveOutGetDevCaps(iDevice, &woc, sizeof(woc));
|
|
CPrintF(TRANSV(" device %d: %s\n"),
|
|
iDevice, woc.szPname);
|
|
CPrintF(TRANSV(" ver: %d, id: %d.%d\n"),
|
|
woc.vDriverVersion, woc.wMid, woc.wPid);
|
|
CPrintF(TRANSV(" form: 0x%08x, ch: %d, support: 0x%08x\n"),
|
|
woc.dwFormats, woc.wChannels, woc.dwSupport);
|
|
}
|
|
// done
|
|
#else
|
|
const int ctDevices = SDL_GetNumAudioDevices(0);
|
|
CPrintF(TRANSV(" Detected devices: %d\n"), ctDevices);
|
|
sl_ctWaveDevices = ctDevices;
|
|
for (int iDevice = 0; iDevice < ctDevices; iDevice++) {
|
|
CPrintF(TRANSV(" device %d: %s\n"),
|
|
iDevice, SDL_GetAudioDeviceName(iDevice, 0));
|
|
}
|
|
#endif
|
|
|
|
CPrintF("\n");
|
|
}
|
|
|
|
|
|
/*
|
|
* Clear Sound Library
|
|
*/
|
|
void CSoundLibrary::Clear(void)
|
|
{
|
|
// !!! FIXME : rcg12162001 This should probably be done everywhere, honestly.
|
|
#ifdef PLATFORM_UNIX
|
|
if (_bDedicatedServer)
|
|
return;
|
|
#endif
|
|
|
|
// access to the list of handlers must be locked
|
|
CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
|
|
// synchronize access to sounds
|
|
CTSingleLock slSounds(&sl_csSound, TRUE);
|
|
|
|
// clear all sounds and datas buffers
|
|
{FOREACHINLIST(CSoundData, sd_Node, sl_ClhAwareList, itCsdStop) {
|
|
FOREACHINLIST(CSoundObject, so_Node, (itCsdStop->sd_ClhLinkList), itCsoStop) {
|
|
itCsoStop->Stop();
|
|
}
|
|
itCsdStop->ClearBuffer();
|
|
}}
|
|
|
|
// clear wave out data
|
|
ClearLibrary();
|
|
_fLastNormalizeValue = 1;
|
|
}
|
|
|
|
|
|
/* Clear Library WaveOut */
|
|
void CSoundLibrary::ClearLibrary(void)
|
|
{
|
|
// !!! FIXME : rcg12162001 This should probably be done everywhere, honestly.
|
|
#ifdef PLATFORM_UNIX
|
|
if (_bDedicatedServer)
|
|
return;
|
|
#endif
|
|
|
|
// access to the list of handlers must be locked
|
|
CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
|
|
|
|
// synchronize access to sounds
|
|
CTSingleLock slSounds(&sl_csSound, TRUE);
|
|
|
|
// remove timer handler if added
|
|
if (sl_thTimerHandler.th_Node.IsLinked()) {
|
|
_pTimer->RemHandler(&sl_thTimerHandler);
|
|
}
|
|
|
|
sl_bUsingDirectSound = FALSE;
|
|
sl_bUsingEAX = FALSE;
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
// shut down direct sound buffers (if needed)
|
|
ShutDown_dsound(*this);
|
|
|
|
// shut down wave out player buffers (if needed)
|
|
if( sl_hwoWaveOut!=NULL)
|
|
{ // reset wave out play buffers (stop playing)
|
|
MMRESULT res;
|
|
res = waveOutReset(sl_hwoWaveOut);
|
|
ASSERT(res == MMSYSERR_NOERROR);
|
|
// clear buffers
|
|
for( INDEX iBuffer = 0; iBuffer<sl_awhWOBuffers.Count(); iBuffer++) {
|
|
res = waveOutUnprepareHeader( sl_hwoWaveOut, &sl_awhWOBuffers[iBuffer],
|
|
sizeof(sl_awhWOBuffers[iBuffer]));
|
|
ASSERT(res == MMSYSERR_NOERROR);
|
|
}
|
|
sl_awhWOBuffers.Clear();
|
|
// close waveout device
|
|
res = waveOutClose( sl_hwoWaveOut);
|
|
ASSERT(res == MMSYSERR_NOERROR);
|
|
sl_hwoWaveOut = NULL;
|
|
}
|
|
|
|
// for each extra taken channel
|
|
for(INDEX iChannel=0; iChannel<sl_ahwoExtra.Count(); iChannel++) {
|
|
// close its device
|
|
MMRESULT res = waveOutClose( sl_ahwoExtra[iChannel]);
|
|
ASSERT(res == MMSYSERR_NOERROR);
|
|
}
|
|
// free extra channel handles
|
|
sl_ahwoExtra.PopAll();
|
|
|
|
#else
|
|
ShutDown_SDLaudio(*this);
|
|
#endif
|
|
|
|
// free memory
|
|
if( sl_pslMixerBuffer!=NULL) {
|
|
FreeMemory( sl_pslMixerBuffer);
|
|
sl_pslMixerBuffer = NULL;
|
|
}
|
|
if( sl_pswDecodeBuffer!=NULL) {
|
|
FreeMemory( sl_pswDecodeBuffer);
|
|
sl_pswDecodeBuffer = NULL;
|
|
}
|
|
if( sl_pubBuffersMemory!=NULL) {
|
|
FreeMemory( sl_pubBuffersMemory);
|
|
sl_pubBuffersMemory = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// set listener enviroment properties (EAX)
|
|
BOOL CSoundLibrary::SetEnvironment( INDEX iEnvNo, FLOAT fEnvSize/*=0*/)
|
|
{
|
|
if( !sl_bUsingEAX) return FALSE;
|
|
#ifdef PLATFORM_WIN32
|
|
// trim values
|
|
if( iEnvNo<0 || iEnvNo>25) iEnvNo=1;
|
|
if( fEnvSize<1 || fEnvSize>99) fEnvSize=8;
|
|
HRESULT hResult;
|
|
hResult = sl_pKSProperty->Set( DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENT, NULL, 0, &iEnvNo, sizeof(DWORD));
|
|
if( hResult != DS_OK) return DSFail( *this, TRANS(" ! EAX error: Cannot set environment.\n"));
|
|
hResult = sl_pKSProperty->Set( DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENTSIZE, NULL, 0, &fEnvSize, sizeof(FLOAT));
|
|
if( hResult != DS_OK) return DSFail( *this, TRANS(" ! EAX error: Cannot set environment size.\n"));
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// mute all sounds (erase playing buffer(s) and supress mixer)
|
|
void CSoundLibrary::Mute(void)
|
|
{
|
|
ASSERT(this!=NULL);
|
|
// stop all IFeel effects
|
|
IFeel_StopEffect(NULL);
|
|
|
|
// !!! FIXME : rcg12162001 This should probably be done everywhere, honestly.
|
|
#ifdef PLATFORM_UNIX
|
|
if (_bDedicatedServer)
|
|
return;
|
|
#endif
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
// erase direct sound buffer (waveout will shut-up by itself), but skip if there's no more sound library
|
|
if(!sl_bUsingDirectSound) return;
|
|
|
|
// synchronize access to sounds
|
|
CTSingleLock slSounds(&sl_csSound, TRUE);
|
|
|
|
// supress future mixing and erase sound buffer
|
|
_bMuted = TRUE;
|
|
static LPVOID lpData;
|
|
static DWORD dwSize;
|
|
|
|
// flush one secondary buffer
|
|
if( !DSLockBuffer( *this, sl_pDSSecondary, sl_slMixerBufferSize, lpData, dwSize)) return;
|
|
memset( lpData, 0, dwSize);
|
|
sl_pDSSecondary->Unlock( lpData, dwSize, NULL, 0);
|
|
// if EAX is in use
|
|
if( sl_bUsingEAX) {
|
|
// flush right buffer, too
|
|
if( !DSLockBuffer( *this, sl_pDSSecondary2, sl_slMixerBufferSize, lpData, dwSize)) return;
|
|
memset( lpData, 0, dwSize);
|
|
sl_pDSSecondary2->Unlock( lpData, dwSize, NULL, 0);
|
|
}
|
|
|
|
#else
|
|
SDL_LockAudioDevice(sdl_audio_device);
|
|
_bMuted = TRUE;
|
|
sdl_backbuffer_remain = 0; // ditch pending audio data...
|
|
sdl_backbuffer_pos = 0;
|
|
SDL_UnlockAudioDevice(sdl_audio_device);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* set sound format
|
|
*/
|
|
CSoundLibrary::SoundFormat CSoundLibrary::SetFormat( CSoundLibrary::SoundFormat EsfNew, BOOL bReport/*=FALSE*/)
|
|
{
|
|
// !!! FIXME : rcg12162001 Do this for all platforms?
|
|
#ifdef PLATFORM_UNIX
|
|
if (_bDedicatedServer) {
|
|
sl_EsfFormat = SF_NONE;
|
|
return(sl_EsfFormat);
|
|
}
|
|
#endif
|
|
|
|
// access to the list of handlers must be locked
|
|
CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
|
|
// synchronize access to sounds
|
|
CTSingleLock slSounds(&sl_csSound, TRUE);
|
|
|
|
// pause playing all sounds
|
|
{FOREACHINLIST( CSoundData, sd_Node, sl_ClhAwareList, itCsdStop) {
|
|
itCsdStop->PausePlayingObjects();
|
|
}}
|
|
|
|
// change format and keep console variable states
|
|
SetFormat_internal( *this, EsfNew, bReport);
|
|
_tmLastMixAhead = snd_tmMixAhead;
|
|
_iLastFormat = snd_iFormat;
|
|
_iLastDevice = snd_iDevice;
|
|
_iLastAPI = snd_iInterface;
|
|
|
|
// continue playing all sounds
|
|
CListHead lhToReload;
|
|
lhToReload.MoveList(sl_ClhAwareList);
|
|
{FORDELETELIST( CSoundData, sd_Node, lhToReload, itCsdContinue) {
|
|
CSoundData &sd = *itCsdContinue;
|
|
if( !(sd.sd_ulFlags&SDF_ENCODED)) {
|
|
sd.Reload();
|
|
} else {
|
|
sd.sd_Node.Remove();
|
|
sl_ClhAwareList.AddTail(sd.sd_Node);
|
|
}
|
|
sd.ResumePlayingObjects();
|
|
}}
|
|
|
|
// done
|
|
return sl_EsfFormat;
|
|
}
|
|
|
|
|
|
|
|
/* Update all 3d effects and copy internal data. */
|
|
void CSoundLibrary::UpdateSounds(void)
|
|
{
|
|
// !!! FIXME : rcg12162001 This should probably be done everywhere, honestly.
|
|
#ifdef PLATFORM_UNIX
|
|
if (_bDedicatedServer)
|
|
return;
|
|
#endif
|
|
|
|
// see if we have valid handle for direct sound and eventually reinit sound
|
|
#ifdef PLATFORM_WIN32
|
|
if( sl_bUsingDirectSound && _hwndCurrent!=_hwndMain) {
|
|
_hwndCurrent = _hwndMain;
|
|
SetFormat( sl_EsfFormat);
|
|
}
|
|
#endif
|
|
|
|
_bMuted = FALSE; // enable mixer
|
|
_sfStats.StartTimer(CStatForm::STI_SOUNDUPDATE);
|
|
_pfSoundProfile.StartTimer(CSoundProfile::PTI_UPDATESOUNDS);
|
|
|
|
// synchronize access to sounds
|
|
CTSingleLock slSounds( &sl_csSound, TRUE);
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
// make sure that the buffers are playing
|
|
if( sl_bUsingDirectSound) DSPlayBuffers(*this);
|
|
#endif
|
|
|
|
// determine number of listeners and get listener
|
|
INDEX ctListeners=0;
|
|
CSoundListener *sli;
|
|
{FOREACHINLIST( CSoundListener, sli_lnInActiveListeners, _pSound->sl_lhActiveListeners, itsli) {
|
|
sli = itsli;
|
|
ctListeners++;
|
|
}}
|
|
// if there's only one listener environment properties have been changed (in split-screen EAX is not supported)
|
|
if( ctListeners==1 && (_iLastEnvType!=sli->sli_iEnvironmentType || _fLastEnvSize!=sli->sli_fEnvironmentSize)) {
|
|
// keep new properties and eventually update environment (EAX)
|
|
_iLastEnvType = sli->sli_iEnvironmentType;
|
|
_fLastEnvSize = sli->sli_fEnvironmentSize;
|
|
SetEnvironment( _iLastEnvType, _fLastEnvSize);
|
|
}
|
|
// if there are no listeners - reset environment properties
|
|
if( ctListeners<1 && (_iLastEnvType!=1 || _fLastEnvSize!=1.4f)) {
|
|
// keep new properties and update environment
|
|
_iLastEnvType = 1;
|
|
_fLastEnvSize = 1.4f;
|
|
SetEnvironment( _iLastEnvType, _fLastEnvSize);
|
|
}
|
|
|
|
// adjust panning if needed
|
|
#ifdef PLATFORM_WIN32
|
|
snd_fEAXPanning = Clamp( snd_fEAXPanning, -1.0f, +1.0f);
|
|
if( sl_bUsingEAX && _fLastPanning!=snd_fEAXPanning)
|
|
{ // determine new panning
|
|
_fLastPanning = snd_fEAXPanning;
|
|
FLOAT fPanLeft = -1.0f;
|
|
FLOAT fPanRight = +1.0f;
|
|
if( snd_fEAXPanning<0) fPanRight = MINPAN + Abs(snd_fEAXPanning)*MAXPAN; // pan left
|
|
if( snd_fEAXPanning>0) fPanLeft = MINPAN + Abs(snd_fEAXPanning)*MAXPAN; // pan right
|
|
// set and apply
|
|
HRESULT hr1,hr2,hr3;
|
|
hr1 = sl_pDSSourceLeft->SetPosition( fPanLeft, 0,0, DS3D_DEFERRED);
|
|
hr2 = sl_pDSSourceRight->SetPosition( fPanRight,0,0, DS3D_DEFERRED);
|
|
hr3 = sl_pDSListener->CommitDeferredSettings();
|
|
if( hr1!=DS_OK || hr2!=DS_OK || hr3!=DS_OK) DSFail( *this, TRANS(" ! DirectSound3D error: Cannot set 3D position.\n"));
|
|
}
|
|
#endif
|
|
|
|
// for each sound
|
|
{FOREACHINLIST( CSoundData, sd_Node, sl_ClhAwareList, itCsdSoundData) {
|
|
FORDELETELIST( CSoundObject, so_Node, itCsdSoundData->sd_ClhLinkList, itCsoSoundObject) {
|
|
_sfStats.IncrementCounter(CStatForm::SCI_SOUNDSACTIVE);
|
|
itCsoSoundObject->Update3DEffects();
|
|
}
|
|
}}
|
|
|
|
// for each sound
|
|
{FOREACHINLIST( CSoundData, sd_Node, sl_ClhAwareList, itCsdSoundData) {
|
|
FORDELETELIST( CSoundObject, so_Node, itCsdSoundData->sd_ClhLinkList, itCsoSoundObject) {
|
|
CSoundObject &so = *itCsoSoundObject;
|
|
// if sound is playing
|
|
if( so.so_slFlags&SOF_PLAY) {
|
|
// copy parameters
|
|
so.so_sp = so.so_spNew;
|
|
// prepare sound if not prepared already
|
|
if ( !(so.so_slFlags&SOF_PREPARE)) {
|
|
so.PrepareSound();
|
|
so.so_slFlags |= SOF_PREPARE;
|
|
}
|
|
// if it is not playing
|
|
} else {
|
|
// remove it from list
|
|
so.so_Node.Remove();
|
|
}
|
|
}
|
|
}}
|
|
|
|
// remove all listeners
|
|
{FORDELETELIST( CSoundListener, sli_lnInActiveListeners, sl_lhActiveListeners, itsli) {
|
|
itsli->sli_lnInActiveListeners.Remove();
|
|
}}
|
|
|
|
_pfSoundProfile.StopTimer(CSoundProfile::PTI_UPDATESOUNDS);
|
|
_sfStats.StopTimer(CStatForm::STI_SOUNDUPDATE);
|
|
}
|
|
|
|
|
|
/*
|
|
* This is called every TickQuantum seconds.
|
|
*/
|
|
void CSoundTimerHandler::HandleTimer(void)
|
|
{
|
|
// !!! FIXME : rcg12162001 This should probably be done everywhere, honestly.
|
|
#ifdef PLATFORM_UNIX
|
|
if (_bDedicatedServer)
|
|
return;
|
|
#endif
|
|
|
|
/* memory leak checking routines
|
|
ASSERT( _CrtCheckMemory());
|
|
ASSERT( _CrtIsMemoryBlock( (void*)_pSound->sl_pswDecodeBuffer,
|
|
(ULONG)_pSound->sl_slDecodeBufferSize, NULL, NULL, NULL));
|
|
ASSERT( _CrtIsValidPointer( (void*)_pSound->sl_pswDecodeBuffer,
|
|
(ULONG)_pSound->sl_slDecodeBufferSize, TRUE)); */
|
|
// mix all needed sounds
|
|
_pSound->MixSounds();
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* MIXER helper functions
|
|
*/
|
|
|
|
|
|
// copying of mixer buffer to sound buffer(s)
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
static LPVOID _lpData, _lpData2;
|
|
static DWORD _dwSize, _dwSize2;
|
|
|
|
static void CopyMixerBuffer_dsound( CSoundLibrary &sl, SLONG slMixedSize)
|
|
{
|
|
LPVOID lpData;
|
|
DWORD dwSize;
|
|
SLONG slPart1Size, slPart2Size;
|
|
|
|
// if EAX is in use
|
|
if( sl.sl_bUsingEAX)
|
|
{
|
|
// lock left buffer and copy first part of 1st mono block
|
|
if( !DSLockBuffer( sl, sl.sl_pDSSecondary, sl.sl_slMixerBufferSize, lpData, dwSize)) return;
|
|
slPart1Size = Min( sl.sl_slMixerBufferSize-_iWriteOffset, slMixedSize);
|
|
CopyMixerBuffer_mono( 0, ((UBYTE*)lpData)+_iWriteOffset/2, slPart1Size);
|
|
// copy second part of 1st mono block
|
|
slPart2Size = slMixedSize - slPart1Size;
|
|
CopyMixerBuffer_mono( slPart1Size, lpData, slPart2Size);
|
|
_iWriteOffset += slMixedSize;
|
|
if( _iWriteOffset>=sl.sl_slMixerBufferSize) _iWriteOffset -= sl.sl_slMixerBufferSize;
|
|
ASSERT( _iWriteOffset>=0 && _iWriteOffset<sl.sl_slMixerBufferSize);
|
|
sl.sl_pDSSecondary->Unlock( lpData, dwSize, NULL, 0);
|
|
|
|
// lock right buffer and copy first part of 2nd mono block
|
|
if( !DSLockBuffer( sl, sl.sl_pDSSecondary2, sl.sl_slMixerBufferSize, lpData, dwSize)) return;
|
|
slPart1Size = Min( sl.sl_slMixerBufferSize-_iWriteOffset2, slMixedSize);
|
|
CopyMixerBuffer_mono( 2, ((UBYTE*)lpData)+_iWriteOffset2/2, slPart1Size);
|
|
// copy second part of 2nd mono block
|
|
slPart2Size = slMixedSize - slPart1Size;
|
|
CopyMixerBuffer_mono( slPart1Size+2, lpData, slPart2Size);
|
|
_iWriteOffset2 += slMixedSize;
|
|
if( _iWriteOffset2>=sl.sl_slMixerBufferSize) _iWriteOffset2 -= sl.sl_slMixerBufferSize;
|
|
ASSERT( _iWriteOffset2>=0 && _iWriteOffset2<sl.sl_slMixerBufferSize);
|
|
sl.sl_pDSSecondary2->Unlock( lpData, dwSize, NULL, 0);
|
|
}
|
|
// if only standard DSound (no EAX)
|
|
else
|
|
{
|
|
// lock stereo buffer and copy first part of block
|
|
if( !DSLockBuffer( sl, sl.sl_pDSSecondary, sl.sl_slMixerBufferSize, lpData, dwSize)) return;
|
|
slPart1Size = Min( sl.sl_slMixerBufferSize-_iWriteOffset, slMixedSize);
|
|
CopyMixerBuffer_stereo( 0, ((UBYTE*)lpData)+_iWriteOffset, slPart1Size);
|
|
// copy second part of block
|
|
slPart2Size = slMixedSize - slPart1Size;
|
|
CopyMixerBuffer_stereo( slPart1Size, lpData, slPart2Size);
|
|
_iWriteOffset += slMixedSize;
|
|
if( _iWriteOffset>=sl.sl_slMixerBufferSize) _iWriteOffset -= sl.sl_slMixerBufferSize;
|
|
ASSERT( _iWriteOffset>=0 && _iWriteOffset<sl.sl_slMixerBufferSize);
|
|
sl.sl_pDSSecondary->Unlock( lpData, dwSize, NULL, 0);
|
|
}
|
|
}
|
|
|
|
|
|
static void CopyMixerBuffer_waveout( CSoundLibrary &sl)
|
|
{
|
|
MMRESULT res;
|
|
SLONG slOffset = 0;
|
|
for( INDEX iBuffer = 0; iBuffer<sl.sl_awhWOBuffers.Count(); iBuffer++)
|
|
{ // skip prepared buffer
|
|
WAVEHDR &wh = sl.sl_awhWOBuffers[iBuffer];
|
|
if( wh.dwFlags&WHDR_PREPARED) continue;
|
|
// copy part of a mixer buffer to wave buffer
|
|
CopyMixerBuffer_stereo( slOffset, wh.lpData, WAVEOUTBLOCKSIZE);
|
|
slOffset += WAVEOUTBLOCKSIZE;
|
|
// write wave buffer (ready for playing)
|
|
res = waveOutPrepareHeader( sl.sl_hwoWaveOut, &wh, sizeof(wh));
|
|
ASSERT( res==MMSYSERR_NOERROR);
|
|
res = waveOutWrite( sl.sl_hwoWaveOut, &wh, sizeof(wh));
|
|
ASSERT( res==MMSYSERR_NOERROR);
|
|
}
|
|
}
|
|
|
|
|
|
// finds room in sound buffer to copy in next crop of samples
|
|
static SLONG PrepareSoundBuffer_dsound( CSoundLibrary &sl)
|
|
{
|
|
// determine writable block size (difference between write and play pointers)
|
|
HRESULT hr1,hr2;
|
|
DWORD dwCurrentCursor, dwCurrentCursor2;
|
|
SLONG slDataToMix;
|
|
ASSERT( sl.sl_pDSSecondary!=NULL && sl.sl_pDSPrimary!=NULL);
|
|
|
|
// if EAX is in use
|
|
if( sl.sl_bUsingEAX)
|
|
{
|
|
hr1 = sl.sl_pDSSecondary->GetCurrentPosition( &dwCurrentCursor, NULL);
|
|
hr2 = sl.sl_pDSSecondary2->GetCurrentPosition( &dwCurrentCursor2, NULL);
|
|
if( hr1!=DS_OK || hr2!=DS_OK) return DSFail( sl, TRANS(" ! DirectSound error: Cannot obtain sound buffer write position.\n"));
|
|
dwCurrentCursor *=2; // stereo mixer
|
|
dwCurrentCursor2*=2; // stereo mixer
|
|
// store pointers and wrapped block sizes
|
|
SLONG slDataToMix1 = dwCurrentCursor - _iWriteOffset;
|
|
if( slDataToMix1<0) slDataToMix1 += sl.sl_slMixerBufferSize;
|
|
ASSERT( slDataToMix1>=0 && slDataToMix1<=sl.sl_slMixerBufferSize);
|
|
slDataToMix1 = Min( slDataToMix1, sl.sl_slMixerBufferSize);
|
|
SLONG slDataToMix2 = dwCurrentCursor2 - _iWriteOffset2;
|
|
if( slDataToMix2<0) slDataToMix2 += sl.sl_slMixerBufferSize;
|
|
ASSERT( slDataToMix2>=0 && slDataToMix2<=sl.sl_slMixerBufferSize);
|
|
slDataToMix = Min( slDataToMix1, slDataToMix2);
|
|
}
|
|
// if only standard DSound (no EAX)
|
|
else
|
|
{
|
|
hr1 = sl.sl_pDSSecondary->GetCurrentPosition( &dwCurrentCursor, NULL);
|
|
if( hr1!=DS_OK) return DSFail( sl, TRANS(" ! DirectSound error: Cannot obtain sound buffer write position.\n"));
|
|
// store pointer and wrapped block size
|
|
slDataToMix = dwCurrentCursor - _iWriteOffset;
|
|
if( slDataToMix<0) slDataToMix += sl.sl_slMixerBufferSize;
|
|
ASSERT( slDataToMix>=0 && slDataToMix<=sl.sl_slMixerBufferSize);
|
|
slDataToMix = Min( slDataToMix, sl.sl_slMixerBufferSize);
|
|
}
|
|
|
|
// done
|
|
//CPrintF( "LP/LW: %5d / %5d, RP/RW: %5d / %5d, MIX: %5d\n", dwCurrentCursor, _iWriteOffset, dwCurrentCursor2, _iWriteOffset2, slDataToMix); // grgr
|
|
return slDataToMix;
|
|
}
|
|
|
|
|
|
static SLONG PrepareSoundBuffer_waveout( CSoundLibrary &sl)
|
|
{
|
|
// scan waveout buffers to find all that are ready to receive sound data (i.e. not playing)
|
|
SLONG slDataToMix=0;
|
|
for( INDEX iBuffer=0; iBuffer<sl.sl_awhWOBuffers.Count(); iBuffer++)
|
|
{ // if done playing
|
|
WAVEHDR &wh = sl.sl_awhWOBuffers[iBuffer];
|
|
if( wh.dwFlags&WHDR_DONE) {
|
|
// unprepare buffer
|
|
MMRESULT res = waveOutUnprepareHeader( sl.sl_hwoWaveOut, &wh, sizeof(wh));
|
|
ASSERT( res == MMSYSERR_NOERROR);
|
|
}
|
|
// if unprepared
|
|
if( !(wh.dwFlags&WHDR_PREPARED)) {
|
|
// increase mix-in data size
|
|
slDataToMix += WAVEOUTBLOCKSIZE;
|
|
}
|
|
}
|
|
// done
|
|
ASSERT( slDataToMix <= sl.sl_slMixerBufferSize);
|
|
return slDataToMix;
|
|
}
|
|
#endif
|
|
|
|
|
|
/* Update Mixer */
|
|
void CSoundLibrary::MixSounds(void)
|
|
{
|
|
// !!! FIXME : rcg12162001 This should probably be done everywhere, honestly.
|
|
#ifdef PLATFORM_UNIX
|
|
if (_bDedicatedServer)
|
|
return;
|
|
#endif
|
|
|
|
// synchronize access to sounds
|
|
CTSingleLock slSounds( &sl_csSound, TRUE);
|
|
|
|
// do nothing if no sound
|
|
if( sl_EsfFormat==SF_NONE || _bMuted) return;
|
|
|
|
_sfStats.StartTimer(CStatForm::STI_SOUNDMIXING);
|
|
_pfSoundProfile.IncrementAveragingCounter();
|
|
_pfSoundProfile.StartTimer(CSoundProfile::PTI_MIXSOUNDS);
|
|
|
|
// seek available buffer(s) for next crop of samples
|
|
SLONG slDataToMix;
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
if( sl_bUsingDirectSound) { // using direct sound
|
|
slDataToMix = PrepareSoundBuffer_dsound( *this);
|
|
} else { // using wave out
|
|
slDataToMix = PrepareSoundBuffer_waveout(*this);
|
|
}
|
|
#else
|
|
SDL_LockAudioDevice(sdl_audio_device);
|
|
slDataToMix = PrepareSoundBuffer_SDLaudio(*this);
|
|
#endif
|
|
|
|
// skip mixing if all sound buffers are still busy playing
|
|
ASSERT( slDataToMix>=0);
|
|
if( slDataToMix<=0) {
|
|
#ifdef PLATFORM_UNIX
|
|
SDL_UnlockAudioDevice(sdl_audio_device);
|
|
#endif
|
|
_pfSoundProfile.StopTimer(CSoundProfile::PTI_MIXSOUNDS);
|
|
_sfStats.StopTimer(CStatForm::STI_SOUNDMIXING);
|
|
return;
|
|
}
|
|
|
|
// prepare mixer buffer
|
|
_pfSoundProfile.IncrementCounter(CSoundProfile::PCI_MIXINGS, 1);
|
|
ResetMixer( sl_pslMixerBuffer, slDataToMix);
|
|
|
|
BOOL bGamePaused = _pNetwork->IsPaused() || (_pNetwork->IsServer() && _pNetwork->GetLocalPause());
|
|
|
|
// for each sound
|
|
FOREACHINLIST( CSoundData, sd_Node, sl_ClhAwareList, itCsdSoundData) {
|
|
FORDELETELIST( CSoundObject, so_Node, itCsdSoundData->sd_ClhLinkList, itCsoSoundObject) {
|
|
CSoundObject &so = *itCsoSoundObject;
|
|
// if the sound is in-game sound, and the game paused
|
|
if (!(so.so_slFlags&SOF_NONGAME) && bGamePaused) {
|
|
// don't mix it it
|
|
continue;
|
|
}
|
|
// if sound is prepared and playing
|
|
if( so.so_slFlags&SOF_PLAY &&
|
|
so.so_slFlags&SOF_PREPARE &&
|
|
!(so.so_slFlags&SOF_PAUSED)) {
|
|
// mix it
|
|
MixSound(&so);
|
|
}
|
|
}
|
|
}
|
|
|
|
// eventually normalize mixed sounds
|
|
snd_fNormalizer = Clamp( snd_fNormalizer, 0.0f, 1.0f);
|
|
NormalizeMixerBuffer( snd_fNormalizer, slDataToMix, _fLastNormalizeValue);
|
|
|
|
// write mixer buffer to file
|
|
// if( !_bOpened) _filMixerBuffer = fopen( "d:\\MixerBufferDump.raw", "wb");
|
|
// fwrite( (void*)sl_pslMixerBuffer, 1, slDataToMix, _filMixerBuffer);
|
|
// _bOpened = TRUE;
|
|
|
|
// copy mixer buffer to buffers buffer(s)
|
|
#ifdef PLATFORM_WIN32
|
|
if( sl_bUsingDirectSound) { // using direct sound
|
|
CopyMixerBuffer_dsound( *this, slDataToMix);
|
|
} else { // using wave out
|
|
CopyMixerBuffer_waveout(*this);
|
|
}
|
|
#else
|
|
CopyMixerBuffer_SDLaudio(*this, slDataToMix);
|
|
SDL_UnlockAudioDevice(sdl_audio_device);
|
|
#endif
|
|
|
|
// all done
|
|
_pfSoundProfile.StopTimer(CSoundProfile::PTI_MIXSOUNDS);
|
|
_sfStats.StopTimer(CStatForm::STI_SOUNDMIXING);
|
|
}
|
|
|
|
|
|
//
|
|
// Sound mode awareness functions
|
|
//
|
|
|
|
/*
|
|
* Add sound in sound aware list
|
|
*/
|
|
void CSoundLibrary::AddSoundAware(CSoundData &CsdAdd)
|
|
{
|
|
// !!! FIXME : rcg12162001 This should probably be done everywhere, honestly.
|
|
#ifdef PLATFORM_UNIX
|
|
if (_bDedicatedServer)
|
|
return;
|
|
#endif
|
|
|
|
// add sound to list tail
|
|
sl_ClhAwareList.AddTail(CsdAdd.sd_Node);
|
|
};
|
|
|
|
/*
|
|
* Remove a display mode aware object.
|
|
*/
|
|
void CSoundLibrary::RemoveSoundAware(CSoundData &CsdRemove)
|
|
{
|
|
// !!! FIXME : rcg12162001 This should probably be done everywhere, honestly.
|
|
#ifdef PLATFORM_UNIX
|
|
if (_bDedicatedServer)
|
|
return;
|
|
#endif
|
|
|
|
// remove it from list
|
|
CsdRemove.sd_Node.Remove();
|
|
};
|
|
|
|
// listen from this listener this frame
|
|
void CSoundLibrary::Listen(CSoundListener &sl)
|
|
{
|
|
// !!! FIXME : rcg12162001 This should probably be done everywhere, honestly.
|
|
#ifdef PLATFORM_UNIX
|
|
if (_bDedicatedServer)
|
|
return;
|
|
#endif
|
|
|
|
// just add it to list
|
|
if (sl.sli_lnInActiveListeners.IsLinked()) {
|
|
sl.sli_lnInActiveListeners.Remove();
|
|
}
|
|
sl_lhActiveListeners.AddTail(sl.sli_lnInActiveListeners);
|
|
}
|