mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-30 16:11:11 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
1328 lines
38 KiB
C++
1328 lines
38 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 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.
|
|
|
|
Doom 3 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 Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 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 Doom 3 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 "sys/platform.h"
|
|
|
|
#include "sound/snd_local.h"
|
|
|
|
#ifdef ID_DEDICATED
|
|
idCVar idSoundSystemLocal::s_noSound( "s_noSound", "1", CVAR_SOUND | CVAR_BOOL | CVAR_ROM, "" );
|
|
#else
|
|
idCVar idSoundSystemLocal::s_noSound( "s_noSound", "0", CVAR_SOUND | CVAR_BOOL | CVAR_NOCHEAT, "" );
|
|
#endif
|
|
idCVar idSoundSystemLocal::s_quadraticFalloff( "s_quadraticFalloff", "1", CVAR_SOUND | CVAR_BOOL, "" );
|
|
idCVar idSoundSystemLocal::s_drawSounds( "s_drawSounds", "0", CVAR_SOUND | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> );
|
|
idCVar idSoundSystemLocal::s_showStartSound( "s_showStartSound", "0", CVAR_SOUND | CVAR_BOOL, "" );
|
|
idCVar idSoundSystemLocal::s_useOcclusion( "s_useOcclusion", "1", CVAR_SOUND | CVAR_BOOL, "" );
|
|
idCVar idSoundSystemLocal::s_maxSoundsPerShader( "s_maxSoundsPerShader", "0", CVAR_SOUND | CVAR_ARCHIVE, "", 0, 10, idCmdSystem::ArgCompletion_Integer<0,10> );
|
|
idCVar idSoundSystemLocal::s_showLevelMeter( "s_showLevelMeter", "0", CVAR_SOUND | CVAR_BOOL, "" );
|
|
idCVar idSoundSystemLocal::s_constantAmplitude( "s_constantAmplitude", "-1", CVAR_SOUND | CVAR_FLOAT, "" );
|
|
idCVar idSoundSystemLocal::s_minVolume6( "s_minVolume6", "0", CVAR_SOUND | CVAR_FLOAT, "" );
|
|
idCVar idSoundSystemLocal::s_dotbias6( "s_dotbias6", "0.8", CVAR_SOUND | CVAR_FLOAT, "" );
|
|
idCVar idSoundSystemLocal::s_minVolume2( "s_minVolume2", "0.25", CVAR_SOUND | CVAR_FLOAT, "" );
|
|
idCVar idSoundSystemLocal::s_dotbias2( "s_dotbias2", "1.1", CVAR_SOUND | CVAR_FLOAT, "" );
|
|
idCVar idSoundSystemLocal::s_spatializationDecay( "s_spatializationDecay", "2", CVAR_SOUND | CVAR_ARCHIVE | CVAR_FLOAT, "" );
|
|
idCVar idSoundSystemLocal::s_reverse( "s_reverse", "0", CVAR_SOUND | CVAR_ARCHIVE | CVAR_BOOL, "" );
|
|
idCVar idSoundSystemLocal::s_meterTopTime( "s_meterTopTime", "2000", CVAR_SOUND | CVAR_ARCHIVE | CVAR_INTEGER, "" );
|
|
idCVar idSoundSystemLocal::s_volume( "s_volume_dB", "0", CVAR_SOUND | CVAR_ARCHIVE | CVAR_FLOAT, "volume in dB" );
|
|
idCVar idSoundSystemLocal::s_playDefaultSound( "s_playDefaultSound", "1", CVAR_SOUND | CVAR_ARCHIVE | CVAR_BOOL, "play a beep for missing sounds" );
|
|
idCVar idSoundSystemLocal::s_subFraction( "s_subFraction", "0.75", CVAR_SOUND | CVAR_ARCHIVE | CVAR_FLOAT, "volume to subwoofer in 5.1" );
|
|
idCVar idSoundSystemLocal::s_globalFraction( "s_globalFraction", "0.8", CVAR_SOUND | CVAR_ARCHIVE | CVAR_FLOAT, "volume to all speakers when not spatialized" );
|
|
idCVar idSoundSystemLocal::s_doorDistanceAdd( "s_doorDistanceAdd", "150", CVAR_SOUND | CVAR_ARCHIVE | CVAR_FLOAT, "reduce sound volume with this distance when going through a door" );
|
|
idCVar idSoundSystemLocal::s_singleEmitter( "s_singleEmitter", "0", CVAR_SOUND | CVAR_INTEGER, "mute all sounds but this emitter" );
|
|
idCVar idSoundSystemLocal::s_numberOfSpeakers( "s_numberOfSpeakers", "2", CVAR_SOUND | CVAR_ARCHIVE, "number of speakers" );
|
|
idCVar idSoundSystemLocal::s_force22kHz( "s_force22kHz", "0", CVAR_SOUND | CVAR_BOOL, "" );
|
|
idCVar idSoundSystemLocal::s_clipVolumes( "s_clipVolumes", "1", CVAR_SOUND | CVAR_BOOL, "" );
|
|
idCVar idSoundSystemLocal::s_realTimeDecoding( "s_realTimeDecoding", "1", CVAR_SOUND | CVAR_BOOL | CVAR_INIT, "" );
|
|
|
|
idCVar idSoundSystemLocal::s_slowAttenuate( "s_slowAttenuate", "1", CVAR_SOUND | CVAR_BOOL, "slowmo sounds attenuate over shorted distance" );
|
|
idCVar idSoundSystemLocal::s_enviroSuitCutoffFreq( "s_enviroSuitCutoffFreq", "2000", CVAR_SOUND | CVAR_FLOAT, "" );
|
|
idCVar idSoundSystemLocal::s_enviroSuitCutoffQ( "s_enviroSuitCutoffQ", "2", CVAR_SOUND | CVAR_FLOAT, "" );
|
|
idCVar idSoundSystemLocal::s_reverbTime( "s_reverbTime", "1000", CVAR_SOUND | CVAR_FLOAT, "" );
|
|
idCVar idSoundSystemLocal::s_reverbFeedback( "s_reverbFeedback", "0.333", CVAR_SOUND | CVAR_FLOAT, "" );
|
|
idCVar idSoundSystemLocal::s_enviroSuitVolumeScale( "s_enviroSuitVolumeScale", "0.9", CVAR_SOUND | CVAR_FLOAT, "" );
|
|
idCVar idSoundSystemLocal::s_skipHelltimeFX( "s_skipHelltimeFX", "0", CVAR_SOUND | CVAR_BOOL, "" );
|
|
|
|
#if ID_OPENAL
|
|
// off by default. OpenAL DLL gets loaded on-demand
|
|
idCVar idSoundSystemLocal::s_libOpenAL( "s_libOpenAL", "openal32.dll", CVAR_SOUND | CVAR_ARCHIVE, "Deprecated, kept for compability" );
|
|
idCVar idSoundSystemLocal::s_useOpenAL( "s_useOpenAL", "0", CVAR_SOUND | CVAR_BOOL | CVAR_ARCHIVE, "Deprecated, kept for compability" );
|
|
idCVar idSoundSystemLocal::s_useEAXReverb( "s_useEAXReverb", "0", CVAR_SOUND | CVAR_BOOL | CVAR_ARCHIVE, "use EAX reverb" );
|
|
idCVar idSoundSystemLocal::s_muteEAXReverb( "s_muteEAXReverb", "0", CVAR_SOUND | CVAR_BOOL, "mute eax reverb" );
|
|
idCVar idSoundSystemLocal::s_decompressionLimit( "s_decompressionLimit", "6", CVAR_SOUND | CVAR_INTEGER | CVAR_ARCHIVE, "specifies maximum uncompressed sample length in seconds" );
|
|
#else
|
|
idCVar idSoundSystemLocal::s_libOpenAL( "s_libOpenAL", "openal32.dll", CVAR_SOUND | CVAR_ARCHIVE, "OpenAL is not supported in this build" );
|
|
idCVar idSoundSystemLocal::s_useOpenAL( "s_useOpenAL", "0", CVAR_SOUND | CVAR_BOOL | CVAR_ROM, "OpenAL is not supported in this build" );
|
|
idCVar idSoundSystemLocal::s_useEAXReverb( "s_useEAXReverb", "0", CVAR_SOUND | CVAR_BOOL | CVAR_ROM, "EAX not available in this build" );
|
|
idCVar idSoundSystemLocal::s_muteEAXReverb( "s_muteEAXReverb", "0", CVAR_SOUND | CVAR_BOOL | CVAR_ROM, "mute eax reverb" );
|
|
idCVar idSoundSystemLocal::s_decompressionLimit( "s_decompressionLimit", "6", CVAR_SOUND | CVAR_INTEGER | CVAR_ROM, "specifies maximum uncompressed sample length in seconds" );
|
|
#endif
|
|
|
|
bool idSoundSystemLocal::useEAXReverb = false;
|
|
int idSoundSystemLocal::EAXAvailable = -1;
|
|
|
|
idSoundSystemLocal soundSystemLocal;
|
|
idSoundSystem *soundSystem = &soundSystemLocal;
|
|
|
|
/*
|
|
===============
|
|
SoundReloadSounds_f
|
|
|
|
this is called from the main thread
|
|
===============
|
|
*/
|
|
void SoundReloadSounds_f( const idCmdArgs &args ) {
|
|
if ( !soundSystemLocal.soundCache ) {
|
|
return;
|
|
}
|
|
bool force = false;
|
|
if ( args.Argc() == 2 ) {
|
|
force = true;
|
|
}
|
|
soundSystem->SetMute( true );
|
|
soundSystemLocal.soundCache->ReloadSounds( force );
|
|
soundSystem->SetMute( false );
|
|
common->Printf( "sound: changed sounds reloaded\n" );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
ListSounds_f
|
|
|
|
Optional parameter to only list sounds containing that string
|
|
===============
|
|
*/
|
|
void ListSounds_f( const idCmdArgs &args ) {
|
|
int i;
|
|
const char *snd = args.Argv( 1 );
|
|
|
|
if ( !soundSystemLocal.soundCache ) {
|
|
common->Printf( "No sound.\n" );
|
|
return;
|
|
}
|
|
|
|
int totalSounds = 0;
|
|
int totalSamples = 0;
|
|
int totalMemory = 0;
|
|
int totalPCMMemory = 0;
|
|
for( i = 0; i < soundSystemLocal.soundCache->GetNumObjects(); i++ ) {
|
|
const idSoundSample *sample = soundSystemLocal.soundCache->GetObject(i);
|
|
if ( !sample ) {
|
|
continue;
|
|
}
|
|
if ( snd && sample->name.Find( snd, false ) < 0 ) {
|
|
continue;
|
|
}
|
|
|
|
const waveformatex_t &info = sample->objectInfo;
|
|
|
|
const char *stereo = ( info.nChannels == 2 ? "ST" : " " );
|
|
const char *format = ( info.wFormatTag == WAVE_FORMAT_TAG_OGG ) ? "OGG" : "WAV";
|
|
const char *defaulted = ( sample->defaultSound ? "(DEFAULTED)" : sample->purged ? "(PURGED)" : "" );
|
|
|
|
common->Printf( "%s %dkHz %6dms %5dkB %4s %s%s\n", stereo, sample->objectInfo.nSamplesPerSec / 1000,
|
|
soundSystemLocal.SamplesToMilliseconds( sample->LengthIn44kHzSamples() ),
|
|
sample->objectMemSize >> 10, format, sample->name.c_str(), defaulted );
|
|
|
|
if ( !sample->purged ) {
|
|
totalSamples += sample->objectSize;
|
|
if ( info.wFormatTag != WAVE_FORMAT_TAG_OGG )
|
|
totalPCMMemory += sample->objectMemSize;
|
|
if ( !sample->hardwareBuffer )
|
|
totalMemory += sample->objectMemSize;
|
|
}
|
|
totalSounds++;
|
|
}
|
|
common->Printf( "%8d total sounds\n", totalSounds );
|
|
common->Printf( "%8d total samples loaded\n", totalSamples );
|
|
common->Printf( "%8d kB total system memory used\n", totalMemory >> 10 );
|
|
#if ID_OPENAL
|
|
common->Printf( "%8d kB total OpenAL audio memory used\n", ( alGetInteger( alGetEnumValue( "AL_EAX_RAM_SIZE" ) ) - alGetInteger( alGetEnumValue( "AL_EAX_RAM_FREE" ) ) ) >> 10 );
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===============
|
|
ListSoundDecoders_f
|
|
===============
|
|
*/
|
|
void ListSoundDecoders_f( const idCmdArgs &args ) {
|
|
int i, j, numActiveDecoders, numWaitingDecoders;
|
|
idSoundWorldLocal *sw = soundSystemLocal.currentSoundWorld;
|
|
|
|
numActiveDecoders = numWaitingDecoders = 0;
|
|
|
|
for ( i = 0; i < sw->emitters.Num(); i++ ) {
|
|
idSoundEmitterLocal *sound = sw->emitters[i];
|
|
|
|
if ( !sound ) {
|
|
continue;
|
|
}
|
|
|
|
// run through all the channels
|
|
for ( j = 0; j < SOUND_MAX_CHANNELS; j++ ) {
|
|
idSoundChannel *chan = &sound->channels[j];
|
|
|
|
if ( chan->decoder == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
idSoundSample *sample = chan->decoder->GetSample();
|
|
|
|
if ( sample != NULL ) {
|
|
continue;
|
|
}
|
|
|
|
const char *format = ( chan->leadinSample->objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) ? "OGG" : "WAV";
|
|
common->Printf( "%3d waiting %s: %s\n", numWaitingDecoders, format, chan->leadinSample->name.c_str() );
|
|
|
|
numWaitingDecoders++;
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < sw->emitters.Num(); i++ ) {
|
|
idSoundEmitterLocal *sound = sw->emitters[i];
|
|
|
|
if ( !sound ) {
|
|
continue;
|
|
}
|
|
|
|
// run through all the channels
|
|
for ( j = 0; j < SOUND_MAX_CHANNELS; j++ ) {
|
|
idSoundChannel *chan = &sound->channels[j];
|
|
|
|
if ( chan->decoder == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
idSoundSample *sample = chan->decoder->GetSample();
|
|
|
|
if ( sample == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
const char *format = ( sample->objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) ? "OGG" : "WAV";
|
|
|
|
int localTime = soundSystemLocal.GetCurrent44kHzTime() - chan->trigger44kHzTime;
|
|
int sampleTime = sample->LengthIn44kHzSamples() * sample->objectInfo.nChannels;
|
|
int percent;
|
|
if ( localTime > sampleTime ) {
|
|
if ( chan->parms.soundShaderFlags & SSF_LOOPING ) {
|
|
percent = ( localTime % sampleTime ) * 100 / sampleTime;
|
|
} else {
|
|
percent = 100;
|
|
}
|
|
} else {
|
|
percent = localTime * 100 / sampleTime;
|
|
}
|
|
|
|
common->Printf( "%3d decoding %3d%% %s: %s\n", numActiveDecoders, percent, format, sample->name.c_str() );
|
|
|
|
numActiveDecoders++;
|
|
}
|
|
}
|
|
|
|
common->Printf( "%d decoders\n", numWaitingDecoders + numActiveDecoders );
|
|
common->Printf( "%d waiting decoders\n", numWaitingDecoders );
|
|
common->Printf( "%d active decoders\n", numActiveDecoders );
|
|
common->Printf( "%d kB decoder memory in %d blocks\n", idSampleDecoder::GetUsedBlockMemory() >> 10, idSampleDecoder::GetNumUsedBlocks() );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
TestSound_f
|
|
|
|
this is called from the main thread
|
|
===============
|
|
*/
|
|
void TestSound_f( const idCmdArgs &args ) {
|
|
if ( args.Argc() != 2 ) {
|
|
common->Printf( "Usage: testSound <file>\n" );
|
|
return;
|
|
}
|
|
if ( soundSystemLocal.currentSoundWorld ) {
|
|
soundSystemLocal.currentSoundWorld->PlayShaderDirectly( args.Argv( 1 ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
SoundSystemRestart_f
|
|
|
|
restart the sound thread
|
|
|
|
this is called from the main thread
|
|
===============
|
|
*/
|
|
void SoundSystemRestart_f( const idCmdArgs &args ) {
|
|
soundSystem->SetMute( true );
|
|
soundSystemLocal.ShutdownHW();
|
|
soundSystemLocal.InitHW();
|
|
soundSystem->SetMute( false );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSoundSystemLocal::Init
|
|
|
|
initialize the sound system
|
|
===============
|
|
*/
|
|
void idSoundSystemLocal::Init() {
|
|
|
|
common->Printf( "----- Initializing Sound System ------\n" );
|
|
|
|
isInitialized = false;
|
|
muted = false;
|
|
shutdown = false;
|
|
|
|
currentSoundWorld = NULL;
|
|
soundCache = NULL;
|
|
|
|
olddwCurrentWritePos = 0;
|
|
buffers = 0;
|
|
CurrentSoundTime = 0;
|
|
|
|
nextWriteBlock = 0xffffffff;
|
|
|
|
memset( meterTops, 0, sizeof( meterTops ) );
|
|
memset( meterTopsTime, 0, sizeof( meterTopsTime ) );
|
|
|
|
for( int i = -600; i < 600; i++ ) {
|
|
float pt = i * 0.1f;
|
|
volumesDB[i+600] = pow( 2.0f,( pt * ( 1.0f / 6.0f ) ) );
|
|
}
|
|
|
|
// make a 16 byte aligned finalMixBuffer
|
|
finalMixBuffer = (float *) ( ( ( (intptr_t)realAccum ) + 15 ) & ~15 );
|
|
|
|
graph = NULL;
|
|
|
|
if ( !s_noSound.GetBool() ) {
|
|
idSampleDecoder::Init();
|
|
soundCache = new idSoundCache();
|
|
}
|
|
|
|
// set up openal device and context
|
|
common->StartupVariable( "s_useOpenAL", true );
|
|
common->StartupVariable( "s_useEAXReverb", true );
|
|
|
|
common->Printf( "Setup OpenAL device and context... " );
|
|
openalDevice = alcOpenDevice( NULL );
|
|
openalContext = alcCreateContext( openalDevice, NULL );
|
|
alcMakeContextCurrent( openalContext );
|
|
common->Printf( "Done.\n" );
|
|
|
|
#if ID_OPENAL_EAX
|
|
// try to obtain EAX extensions
|
|
if ( idSoundSystemLocal::s_useEAXReverb.GetBool() && alIsExtensionPresent( ID_ALCHAR "EAX4.0" ) ) {
|
|
alEAXSet = (EAXSet)alGetProcAddress( ID_ALCHAR "EAXSet" );
|
|
alEAXGet = (EAXGet)alGetProcAddress( ID_ALCHAR "EAXGet" );
|
|
common->Printf( "OpenAL: found EAX 4.0 extension\n" );
|
|
EAXAvailable = 1;
|
|
} else {
|
|
common->Printf( "OpenAL: EAX 4.0 extension not found\n" );
|
|
idSoundSystemLocal::s_useEAXReverb.SetBool( false );
|
|
alEAXSet = (EAXSet)NULL;
|
|
alEAXGet = (EAXGet)NULL;
|
|
EAXAvailable = 0;
|
|
}
|
|
#else
|
|
common->Printf("OpenAL: EAX 4.0 not supported in this build\n");
|
|
idSoundSystemLocal::s_useEAXReverb.SetBool( false );
|
|
EAXAvailable = 0;
|
|
#endif
|
|
|
|
#if ID_OPENAL_EAX
|
|
// try to obtain EAX-RAM extension - not required for operation
|
|
if ( alIsExtensionPresent( ID_ALCHAR "EAX-RAM" ) == AL_TRUE ) {
|
|
alEAXSetBufferMode = (EAXSetBufferMode)alGetProcAddress( ID_ALCHAR "EAXSetBufferMode" );
|
|
alEAXGetBufferMode = (EAXGetBufferMode)alGetProcAddress( ID_ALCHAR "EAXGetBufferMode" );
|
|
common->Printf( "OpenAL: found EAX-RAM extension, %dkB\\%dkB\n", alGetInteger( alGetEnumValue( ID_ALCHAR "AL_EAX_RAM_FREE" ) ) / 1024, alGetInteger( alGetEnumValue( ID_ALCHAR "AL_EAX_RAM_SIZE" ) ) / 1024 );
|
|
} else {
|
|
alEAXSetBufferMode = (EAXSetBufferMode)NULL;
|
|
alEAXGetBufferMode = (EAXGetBufferMode)NULL;
|
|
common->Printf( "OpenAL: no EAX-RAM extension\n" );
|
|
}
|
|
#endif
|
|
|
|
ALuint handle;
|
|
openalSourceCount = 0;
|
|
|
|
while ( openalSourceCount < 256 ) {
|
|
alGetError();
|
|
alGenSources( 1, &handle );
|
|
if ( alGetError() != AL_NO_ERROR ) {
|
|
break;
|
|
} else {
|
|
// store in source array
|
|
openalSources[openalSourceCount].handle = handle;
|
|
openalSources[openalSourceCount].startTime = 0;
|
|
openalSources[openalSourceCount].chan = NULL;
|
|
openalSources[openalSourceCount].inUse = false;
|
|
openalSources[openalSourceCount].looping = false;
|
|
|
|
// initialise sources
|
|
alSourcef( handle, AL_ROLLOFF_FACTOR, 0.0f );
|
|
|
|
// found one source
|
|
openalSourceCount++;
|
|
}
|
|
}
|
|
|
|
common->Printf( "OpenAL: found %s\n", alcGetString( openalDevice, ALC_DEVICE_SPECIFIER ) );
|
|
common->Printf( "OpenAL: found %d hardware voices\n", openalSourceCount );
|
|
|
|
// adjust source count to allow for at least eight stereo sounds to play
|
|
openalSourceCount -= 8;
|
|
|
|
useEAXReverb = idSoundSystemLocal::s_useEAXReverb.GetBool();
|
|
|
|
cmdSystem->AddCommand( "listSounds", ListSounds_f, CMD_FL_SOUND, "lists all sounds" );
|
|
cmdSystem->AddCommand( "listSoundDecoders", ListSoundDecoders_f, CMD_FL_SOUND, "list active sound decoders" );
|
|
cmdSystem->AddCommand( "reloadSounds", SoundReloadSounds_f, CMD_FL_SOUND|CMD_FL_CHEAT, "reloads all sounds" );
|
|
cmdSystem->AddCommand( "testSound", TestSound_f, CMD_FL_SOUND | CMD_FL_CHEAT, "tests a sound", idCmdSystem::ArgCompletion_SoundName );
|
|
cmdSystem->AddCommand( "s_restart", SoundSystemRestart_f, CMD_FL_SOUND, "restarts the sound system" );
|
|
|
|
common->Printf( "sound system initialized.\n" );
|
|
common->Printf( "--------------------------------------\n" );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSoundSystemLocal::Shutdown
|
|
===============
|
|
*/
|
|
void idSoundSystemLocal::Shutdown() {
|
|
ShutdownHW();
|
|
|
|
// EAX or not, the list needs to be cleared
|
|
EFXDatabase.Clear();
|
|
|
|
efxloaded = false;
|
|
|
|
// adjust source count back up to allow for freeing of all resources
|
|
openalSourceCount += 8;
|
|
|
|
for ( ALsizei i = 0; i < openalSourceCount; i++ ) {
|
|
// stop source
|
|
alSourceStop( openalSources[i].handle );
|
|
alSourcei( openalSources[i].handle, AL_BUFFER, 0 );
|
|
|
|
// delete source
|
|
alDeleteSources( 1, &openalSources[i].handle );
|
|
|
|
// clear entry in source array
|
|
openalSources[i].handle = 0;
|
|
openalSources[i].startTime = 0;
|
|
openalSources[i].chan = NULL;
|
|
openalSources[i].inUse = false;
|
|
openalSources[i].looping = false;
|
|
}
|
|
|
|
// destroy all the sounds (hardware buffers as well)
|
|
delete soundCache;
|
|
soundCache = NULL;
|
|
|
|
// destroy openal device and context
|
|
alcMakeContextCurrent( NULL );
|
|
|
|
alcDestroyContext( openalContext );
|
|
openalContext = NULL;
|
|
|
|
alcCloseDevice( openalDevice );
|
|
openalDevice = NULL;
|
|
|
|
idSampleDecoder::Shutdown();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSoundSystemLocal::InitHW
|
|
===============
|
|
*/
|
|
bool idSoundSystemLocal::InitHW() {
|
|
int numSpeakers = s_numberOfSpeakers.GetInteger();
|
|
|
|
if (numSpeakers != 2 && numSpeakers != 6) {
|
|
common->Warning("invalid value for s_numberOfSpeakers. Use either 2 or 6");
|
|
numSpeakers = 2;
|
|
s_numberOfSpeakers.SetInteger(numSpeakers);
|
|
}
|
|
|
|
if ( s_noSound.GetBool() ) {
|
|
return false;
|
|
}
|
|
|
|
// put the real number in there
|
|
s_numberOfSpeakers.SetInteger(numSpeakers);
|
|
|
|
isInitialized = true;
|
|
shutdown = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSoundSystemLocal::ShutdownHW
|
|
===============
|
|
*/
|
|
bool idSoundSystemLocal::ShutdownHW() {
|
|
if ( !isInitialized ) {
|
|
return false;
|
|
}
|
|
|
|
shutdown = true; // don't do anything at AsyncUpdate() time
|
|
Sys_Sleep( 100 ); // sleep long enough to make sure any async sound talking to hardware has returned
|
|
|
|
common->Printf( "Shutting down sound hardware\n" );
|
|
|
|
isInitialized = false;
|
|
|
|
if ( graph ) {
|
|
Mem_Free( graph );
|
|
graph = NULL;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSoundSystemLocal::GetCurrent44kHzTime
|
|
===============
|
|
*/
|
|
int idSoundSystemLocal::GetCurrent44kHzTime( void ) const {
|
|
if ( isInitialized ) {
|
|
return CurrentSoundTime;
|
|
} else {
|
|
// NOTE: this would overflow 31bits within about 1h20
|
|
//return ( ( Sys_Milliseconds()*441 ) / 10 ) * 4;
|
|
return idMath::FtoiFast( (float)Sys_Milliseconds() * 176.4f );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::AsyncMix
|
|
Mac OSX version. The system uses it's own thread and an IOProc callback
|
|
===================
|
|
*/
|
|
int idSoundSystemLocal::AsyncMix( int soundTime, float *mixBuffer ) {
|
|
int inTime, numSpeakers;
|
|
|
|
if ( !isInitialized || shutdown ) {
|
|
return 0;
|
|
}
|
|
|
|
inTime = Sys_Milliseconds();
|
|
numSpeakers = s_numberOfSpeakers.GetInteger();
|
|
|
|
// let the active sound world mix all the channels in unless muted or avi demo recording
|
|
if ( !muted && currentSoundWorld && !currentSoundWorld->fpa[0] ) {
|
|
currentSoundWorld->MixLoop( soundTime, numSpeakers, mixBuffer );
|
|
}
|
|
|
|
CurrentSoundTime = soundTime;
|
|
|
|
return Sys_Milliseconds() - inTime;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::AsyncUpdate
|
|
called from async sound thread when com_asyncSound == 1 ( Windows )
|
|
===================
|
|
*/
|
|
int idSoundSystemLocal::AsyncUpdate( int inTime ) {
|
|
|
|
if ( !isInitialized || shutdown ) {
|
|
return 0;
|
|
}
|
|
|
|
ulong dwCurrentWritePos;
|
|
dword dwCurrentBlock;
|
|
|
|
// here we do it in samples ( overflows in 27 hours or so )
|
|
dwCurrentWritePos = idMath::Ftol( (float)Sys_Milliseconds() * 44.1f ) % ( MIXBUFFER_SAMPLES * ROOM_SLICES_IN_BUFFER );
|
|
dwCurrentBlock = dwCurrentWritePos / MIXBUFFER_SAMPLES;
|
|
|
|
if ( nextWriteBlock == 0xffffffff ) {
|
|
nextWriteBlock = dwCurrentBlock;
|
|
}
|
|
|
|
if ( dwCurrentBlock != nextWriteBlock ) {
|
|
return 0;
|
|
}
|
|
|
|
soundStats.runs++;
|
|
soundStats.activeSounds = 0;
|
|
|
|
int numSpeakers = s_numberOfSpeakers.GetInteger();
|
|
|
|
nextWriteBlock++;
|
|
nextWriteBlock %= ROOM_SLICES_IN_BUFFER;
|
|
|
|
int newPosition = nextWriteBlock * MIXBUFFER_SAMPLES;
|
|
|
|
if ( newPosition < olddwCurrentWritePos ) {
|
|
buffers++; // buffer wrapped
|
|
}
|
|
|
|
// nextWriteSample is in multi-channel samples inside the buffer
|
|
int nextWriteSamples = nextWriteBlock * MIXBUFFER_SAMPLES;
|
|
|
|
olddwCurrentWritePos = newPosition;
|
|
|
|
// newSoundTime is in multi-channel samples since the sound system was started
|
|
int newSoundTime = ( buffers * MIXBUFFER_SAMPLES * ROOM_SLICES_IN_BUFFER ) + nextWriteSamples;
|
|
|
|
// check for impending overflow
|
|
// FIXME: we don't handle sound wrap-around correctly yet
|
|
if ( newSoundTime > 0x6fffffff ) {
|
|
buffers = 0;
|
|
}
|
|
|
|
if ( (newSoundTime - CurrentSoundTime) > (int)MIXBUFFER_SAMPLES ) {
|
|
soundStats.missedWindow++;
|
|
}
|
|
|
|
// enable audio hardware caching
|
|
alcSuspendContext( openalContext );
|
|
|
|
// let the active sound world mix all the channels in unless muted or avi demo recording
|
|
if ( !muted && currentSoundWorld && !currentSoundWorld->fpa[0] ) {
|
|
currentSoundWorld->MixLoop( newSoundTime, numSpeakers, finalMixBuffer );
|
|
}
|
|
|
|
// disable audio hardware caching (this updates ALL settings since last alcSuspendContext)
|
|
alcProcessContext( openalContext );
|
|
|
|
CurrentSoundTime = newSoundTime;
|
|
|
|
soundStats.timeinprocess = Sys_Milliseconds() - inTime;
|
|
|
|
return soundStats.timeinprocess;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::AsyncUpdateWrite
|
|
sound output using a write API. all the scheduling based on time
|
|
we mix MIXBUFFER_SAMPLES at a time, but we feed the audio device with smaller chunks (and more often)
|
|
called by the sound thread when com_asyncSound is 3 ( Linux )
|
|
===================
|
|
*/
|
|
int idSoundSystemLocal::AsyncUpdateWrite( int inTime ) {
|
|
|
|
if ( !isInitialized || shutdown ) {
|
|
return 0;
|
|
}
|
|
|
|
unsigned int dwCurrentBlock = (unsigned int)( inTime * 44.1f / MIXBUFFER_SAMPLES );
|
|
|
|
if ( nextWriteBlock == 0xffffffff ) {
|
|
nextWriteBlock = dwCurrentBlock;
|
|
}
|
|
|
|
if ( dwCurrentBlock < nextWriteBlock ) {
|
|
return 0;
|
|
}
|
|
|
|
if ( nextWriteBlock != dwCurrentBlock ) {
|
|
Sys_Printf( "missed %d sound updates\n", dwCurrentBlock - nextWriteBlock );
|
|
}
|
|
|
|
int sampleTime = dwCurrentBlock * MIXBUFFER_SAMPLES;
|
|
int numSpeakers = s_numberOfSpeakers.GetInteger();
|
|
|
|
// enable audio hardware caching
|
|
alcSuspendContext( openalContext );
|
|
|
|
// let the active sound world mix all the channels in unless muted or avi demo recording
|
|
if ( !muted && currentSoundWorld && !currentSoundWorld->fpa[0] ) {
|
|
currentSoundWorld->MixLoop( sampleTime, numSpeakers, finalMixBuffer );
|
|
}
|
|
|
|
// disable audio hardware caching (this updates ALL settings since last alcSuspendContext)
|
|
alcProcessContext( openalContext );
|
|
|
|
// only move to the next block if the write was successful
|
|
nextWriteBlock = dwCurrentBlock + 1;
|
|
CurrentSoundTime = sampleTime;
|
|
|
|
return Sys_Milliseconds() - inTime;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::dB2Scale
|
|
===================
|
|
*/
|
|
float idSoundSystemLocal::dB2Scale( const float val ) const {
|
|
if ( val == 0.0f ) {
|
|
return 1.0f; // most common
|
|
} else if ( val <= -60.0f ) {
|
|
return 0.0f;
|
|
} else if ( val >= 60.0f ) {
|
|
return powf( 2.0f, val * ( 1.0f / 6.0f ) );
|
|
}
|
|
int ival = (int)( ( val + 60.0f ) * 10.0f );
|
|
return volumesDB[ival];
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::ImageForTime
|
|
===================
|
|
*/
|
|
cinData_t idSoundSystemLocal::ImageForTime( const int milliseconds, const bool waveform ) {
|
|
cinData_t ret;
|
|
int i, j;
|
|
|
|
if ( !isInitialized ) {
|
|
memset( &ret, 0, sizeof( ret ) );
|
|
return ret;
|
|
}
|
|
|
|
Sys_EnterCriticalSection();
|
|
|
|
if ( !graph ) {
|
|
graph = (dword *)Mem_Alloc( 256*128 * 4);
|
|
}
|
|
memset( graph, 0, 256*128 * 4 );
|
|
float *accum = finalMixBuffer; // unfortunately, these are already clamped
|
|
int time = Sys_Milliseconds();
|
|
|
|
int numSpeakers = s_numberOfSpeakers.GetInteger();
|
|
|
|
if ( !waveform ) {
|
|
for( j = 0; j < numSpeakers; j++ ) {
|
|
int meter = 0;
|
|
for( i = 0; i < MIXBUFFER_SAMPLES; i++ ) {
|
|
float result = idMath::Fabs(accum[i*numSpeakers+j]);
|
|
if ( result > meter ) {
|
|
meter = result;
|
|
}
|
|
}
|
|
|
|
meter /= 256; // 32768 becomes 128
|
|
if ( meter > 128 ) {
|
|
meter = 128;
|
|
}
|
|
int offset;
|
|
int xsize;
|
|
if ( numSpeakers == 6 ) {
|
|
offset = j * 40;
|
|
xsize = 20;
|
|
} else {
|
|
offset = j * 128;
|
|
xsize = 63;
|
|
}
|
|
int x,y;
|
|
dword color = 0xff00ff00;
|
|
for ( y = 0; y < 128; y++ ) {
|
|
for ( x = 0; x < xsize; x++ ) {
|
|
graph[(127-y)*256 + offset + x ] = color;
|
|
}
|
|
#if 0
|
|
if ( y == 80 ) {
|
|
color = 0xff00ffff;
|
|
} else if ( y == 112 ) {
|
|
color = 0xff0000ff;
|
|
}
|
|
#endif
|
|
if ( y > meter ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( meter > meterTops[j] ) {
|
|
meterTops[j] = meter;
|
|
meterTopsTime[j] = time + s_meterTopTime.GetInteger();
|
|
} else if ( time > meterTopsTime[j] && meterTops[j] > 0 ) {
|
|
meterTops[j]--;
|
|
if (meterTops[j]) {
|
|
meterTops[j]--;
|
|
}
|
|
}
|
|
}
|
|
|
|
for( j = 0; j < numSpeakers; j++ ) {
|
|
int meter = meterTops[j];
|
|
|
|
int offset;
|
|
int xsize;
|
|
if ( numSpeakers == 6 ) {
|
|
offset = j*40;
|
|
xsize = 20;
|
|
} else {
|
|
offset = j*128;
|
|
xsize = 63;
|
|
}
|
|
int x,y;
|
|
dword color;
|
|
if ( meter <= 80 ) {
|
|
color = 0xff007f00;
|
|
} else if ( meter <= 112 ) {
|
|
color = 0xff007f7f;
|
|
} else {
|
|
color = 0xff00007f;
|
|
}
|
|
for ( y = meter; y < 128 && y < meter + 4; y++ ) {
|
|
for ( x = 0; x < xsize; x++ ) {
|
|
graph[(127-y)*256 + offset + x ] = color;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
dword colors[] = { 0xff007f00, 0xff007f7f, 0xff00007f, 0xff00ff00, 0xff00ffff, 0xff0000ff };
|
|
|
|
for( j = 0; j < numSpeakers; j++ ) {
|
|
int xx = 0;
|
|
float fmeter;
|
|
int step = MIXBUFFER_SAMPLES / 256;
|
|
for( i = 0; i < MIXBUFFER_SAMPLES; i += step ) {
|
|
fmeter = 0.0f;
|
|
for( int x = 0; x < step; x++ ) {
|
|
float result = accum[(i+x)*numSpeakers+j];
|
|
result = result / 32768.0f;
|
|
fmeter += result;
|
|
}
|
|
fmeter /= 4.0f;
|
|
if ( fmeter < -1.0f ) {
|
|
fmeter = -1.0f;
|
|
} else if ( fmeter > 1.0f ) {
|
|
fmeter = 1.0f;
|
|
}
|
|
int meter = (fmeter * 63.0f);
|
|
graph[ (meter + 64) * 256 + xx ] = colors[j];
|
|
|
|
if ( meter < 0 ) {
|
|
meter = -meter;
|
|
}
|
|
if ( meter > meterTops[xx] ) {
|
|
meterTops[xx] = meter;
|
|
meterTopsTime[xx] = time + 100;
|
|
} else if ( time>meterTopsTime[xx] && meterTops[xx] > 0 ) {
|
|
meterTops[xx]--;
|
|
if ( meterTops[xx] ) {
|
|
meterTops[xx]--;
|
|
}
|
|
}
|
|
xx++;
|
|
}
|
|
}
|
|
for( i = 0; i < 256; i++ ) {
|
|
int meter = meterTops[i];
|
|
for ( int y = -meter; y < meter; y++ ) {
|
|
graph[ (y+64)*256 + i ] = colors[j];
|
|
}
|
|
}
|
|
}
|
|
ret.imageHeight = 128;
|
|
ret.imageWidth = 256;
|
|
ret.image = (unsigned char *)graph;
|
|
|
|
Sys_LeaveCriticalSection();
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::GetSoundDecoderInfo
|
|
===================
|
|
*/
|
|
int idSoundSystemLocal::GetSoundDecoderInfo( int index, soundDecoderInfo_t &decoderInfo ) {
|
|
int i, j, firstEmitter, firstChannel;
|
|
idSoundWorldLocal *sw = soundSystemLocal.currentSoundWorld;
|
|
|
|
if ( index < 0 ) {
|
|
firstEmitter = 0;
|
|
firstChannel = 0;
|
|
} else {
|
|
firstEmitter = index / SOUND_MAX_CHANNELS;
|
|
firstChannel = index - firstEmitter * SOUND_MAX_CHANNELS + 1;
|
|
}
|
|
|
|
for ( i = firstEmitter; i < sw->emitters.Num(); i++ ) {
|
|
idSoundEmitterLocal *sound = sw->emitters[i];
|
|
|
|
if ( !sound ) {
|
|
continue;
|
|
}
|
|
|
|
// run through all the channels
|
|
for ( j = firstChannel; j < SOUND_MAX_CHANNELS; j++ ) {
|
|
idSoundChannel *chan = &sound->channels[j];
|
|
|
|
if ( chan->decoder == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
idSoundSample *sample = chan->decoder->GetSample();
|
|
|
|
if ( sample == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
decoderInfo.name = sample->name;
|
|
decoderInfo.format = ( sample->objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) ? "OGG" : "WAV";
|
|
decoderInfo.numChannels = sample->objectInfo.nChannels;
|
|
decoderInfo.numSamplesPerSecond = sample->objectInfo.nSamplesPerSec;
|
|
decoderInfo.num44kHzSamples = sample->LengthIn44kHzSamples();
|
|
decoderInfo.numBytes = sample->objectMemSize;
|
|
decoderInfo.looping = ( chan->parms.soundShaderFlags & SSF_LOOPING ) != 0;
|
|
decoderInfo.lastVolume = chan->lastVolume;
|
|
decoderInfo.start44kHzTime = chan->trigger44kHzTime;
|
|
decoderInfo.current44kHzTime = soundSystemLocal.GetCurrent44kHzTime();
|
|
|
|
return ( i * SOUND_MAX_CHANNELS + j );
|
|
}
|
|
|
|
firstChannel = 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::AllocSoundWorld
|
|
===================
|
|
*/
|
|
idSoundWorld *idSoundSystemLocal::AllocSoundWorld( idRenderWorld *rw ) {
|
|
idSoundWorldLocal *local = new idSoundWorldLocal;
|
|
|
|
local->Init( rw );
|
|
|
|
return local;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::SetMute
|
|
===================
|
|
*/
|
|
void idSoundSystemLocal::SetMute( bool muteOn ) {
|
|
muted = muteOn;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::SamplesToMilliseconds
|
|
===================
|
|
*/
|
|
int idSoundSystemLocal::SamplesToMilliseconds( int samples ) const {
|
|
return ( samples / (PRIMARYFREQ/1000) );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::SamplesToMilliseconds
|
|
===================
|
|
*/
|
|
int idSoundSystemLocal::MillisecondsToSamples( int ms ) const {
|
|
return ( ms * (PRIMARYFREQ/1000) );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::SetPlayingSoundWorld
|
|
|
|
specifying NULL will cause silence to be played
|
|
===================
|
|
*/
|
|
void idSoundSystemLocal::SetPlayingSoundWorld( idSoundWorld *soundWorld ) {
|
|
currentSoundWorld = static_cast<idSoundWorldLocal *>(soundWorld);
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::GetPlayingSoundWorld
|
|
===================
|
|
*/
|
|
idSoundWorld *idSoundSystemLocal::GetPlayingSoundWorld( void ) {
|
|
return currentSoundWorld;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::BeginLevelLoad
|
|
===================
|
|
*/
|
|
|
|
void idSoundSystemLocal::BeginLevelLoad() {
|
|
if ( !isInitialized ) {
|
|
return;
|
|
}
|
|
soundCache->BeginLevelLoad();
|
|
|
|
if ( efxloaded ) {
|
|
EFXDatabase.UnloadFile();
|
|
efxloaded = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::EndLevelLoad
|
|
===================
|
|
*/
|
|
void idSoundSystemLocal::EndLevelLoad( const char *mapstring ) {
|
|
if ( !isInitialized ) {
|
|
return;
|
|
}
|
|
soundCache->EndLevelLoad();
|
|
|
|
#if ID_OPENAL_EAX
|
|
idStr efxname( "efxs/" );
|
|
idStr mapname( mapstring );
|
|
|
|
mapname.SetFileExtension( ".efx" );
|
|
mapname.StripPath();
|
|
efxname += mapname;
|
|
|
|
efxloaded = EFXDatabase.LoadFile( efxname );
|
|
|
|
if ( efxloaded ) {
|
|
common->Printf("sound: found %s\n", efxname.c_str() );
|
|
} else {
|
|
common->Printf("sound: missing %s\n", efxname.c_str() );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::AllocOpenALSource
|
|
===================
|
|
*/
|
|
ALuint idSoundSystemLocal::AllocOpenALSource( idSoundChannel *chan, bool looping, bool stereo ) {
|
|
int timeOldestZeroVolSingleShot = Sys_Milliseconds();
|
|
int timeOldestZeroVolLooping = Sys_Milliseconds();
|
|
int timeOldestSingle = Sys_Milliseconds();
|
|
int iOldestZeroVolSingleShot = -1;
|
|
int iOldestZeroVolLooping = -1;
|
|
int iOldestSingle = -1;
|
|
int iUnused = -1;
|
|
int index = -1;
|
|
ALsizei i;
|
|
|
|
// Grab current msec time
|
|
int time = Sys_Milliseconds();
|
|
|
|
// Cycle through all sources
|
|
for ( i = 0; i < openalSourceCount; i++ ) {
|
|
// Use any unused source first,
|
|
// Then find oldest single shot quiet source,
|
|
// Then find oldest looping quiet source and
|
|
// Lastly find oldest single shot non quiet source..
|
|
if ( !openalSources[i].inUse ) {
|
|
iUnused = i;
|
|
break;
|
|
} else if ( !openalSources[i].looping && openalSources[i].chan->lastVolume < SND_EPSILON ) {
|
|
if ( openalSources[i].startTime < timeOldestZeroVolSingleShot ) {
|
|
timeOldestZeroVolSingleShot = openalSources[i].startTime;
|
|
iOldestZeroVolSingleShot = i;
|
|
}
|
|
} else if ( openalSources[i].looping && openalSources[i].chan->lastVolume < SND_EPSILON ) {
|
|
if ( openalSources[i].startTime < timeOldestZeroVolLooping ) {
|
|
timeOldestZeroVolLooping = openalSources[i].startTime;
|
|
iOldestZeroVolLooping = i;
|
|
}
|
|
} else if ( !openalSources[i].looping ) {
|
|
if ( openalSources[i].startTime < timeOldestSingle ) {
|
|
timeOldestSingle = openalSources[i].startTime;
|
|
iOldestSingle = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( iUnused != -1 ) {
|
|
index = iUnused;
|
|
} else if ( iOldestZeroVolSingleShot != - 1 ) {
|
|
index = iOldestZeroVolSingleShot;
|
|
} else if ( iOldestZeroVolLooping != -1 ) {
|
|
index = iOldestZeroVolLooping;
|
|
} else if ( iOldestSingle != -1 ) {
|
|
index = iOldestSingle;
|
|
}
|
|
|
|
if ( index != -1 ) {
|
|
// stop the channel that is being ripped off
|
|
if ( openalSources[index].chan ) {
|
|
// stop the channel only when not looping
|
|
if ( !openalSources[index].looping ) {
|
|
openalSources[index].chan->Stop();
|
|
} else {
|
|
openalSources[index].chan->triggered = true;
|
|
}
|
|
|
|
// Free hardware resources
|
|
openalSources[index].chan->ALStop();
|
|
}
|
|
|
|
// Initialize structure
|
|
openalSources[index].startTime = time;
|
|
openalSources[index].chan = chan;
|
|
openalSources[index].inUse = true;
|
|
openalSources[index].looping = looping;
|
|
openalSources[index].stereo = stereo;
|
|
|
|
return openalSources[index].handle;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::FreeOpenALSource
|
|
===================
|
|
*/
|
|
void idSoundSystemLocal::FreeOpenALSource( ALuint handle ) {
|
|
ALsizei i;
|
|
for ( i = 0; i < openalSourceCount; i++ ) {
|
|
if ( openalSources[i].handle == handle ) {
|
|
if ( openalSources[i].chan ) {
|
|
openalSources[i].chan->openalSource = 0;
|
|
}
|
|
#if ID_OPENAL_EAX
|
|
// Reset source EAX ROOM level when freeing stereo source
|
|
if ( openalSources[i].stereo && alEAXSet ) {
|
|
long Room = EAXSOURCE_DEFAULTROOM;
|
|
alEAXSet( &EAXPROPERTYID_EAX_Source, EAXSOURCE_ROOM, openalSources[i].handle, &Room, sizeof(Room));
|
|
}
|
|
#endif
|
|
// Initialize structure
|
|
openalSources[i].startTime = 0;
|
|
openalSources[i].chan = NULL;
|
|
openalSources[i].inUse = false;
|
|
openalSources[i].looping = false;
|
|
openalSources[i].stereo = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============================================================
|
|
SoundFX and misc effects
|
|
============================================================
|
|
*/
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::ProcessSample
|
|
===================
|
|
*/
|
|
void SoundFX_Lowpass::ProcessSample( float* in, float* out ) {
|
|
float c, a1, a2, a3, b1, b2;
|
|
float resonance = idSoundSystemLocal::s_enviroSuitCutoffQ.GetFloat();
|
|
float cutoffFrequency = idSoundSystemLocal::s_enviroSuitCutoffFreq.GetFloat();
|
|
|
|
Initialize();
|
|
|
|
c = 1.0 / idMath::Tan16( idMath::PI * cutoffFrequency / 44100 );
|
|
|
|
// compute coefs
|
|
a1 = 1.0 / ( 1.0 + resonance * c + c * c );
|
|
a2 = 2* a1;
|
|
a3 = a1;
|
|
b1 = 2.0 * ( 1.0 - c * c) * a1;
|
|
b2 = ( 1.0 - resonance * c + c * c ) * a1;
|
|
|
|
// compute output value
|
|
out[0] = a1 * in[0] + a2 * in[-1] + a3 * in[-2] - b1 * out[-1] - b2 * out[-2];
|
|
}
|
|
|
|
void SoundFX_LowpassFast::ProcessSample( float* in, float* out ) {
|
|
// compute output value
|
|
out[0] = a1 * in[0] + a2 * in[-1] + a3 * in[-2] - b1 * out[-1] - b2 * out[-2];
|
|
}
|
|
|
|
void SoundFX_LowpassFast::SetParms( float p1, float p2, float p3 ) {
|
|
float c;
|
|
|
|
// set the vars
|
|
freq = p1;
|
|
res = p2;
|
|
|
|
// precompute the coefs
|
|
c = 1.0 / idMath::Tan( idMath::PI * freq / 44100 );
|
|
|
|
// compute coefs
|
|
a1 = 1.0 / ( 1.0 + res * c + c * c );
|
|
a2 = 2* a1;
|
|
a3 = a1;
|
|
|
|
b1 = 2.0 * ( 1.0 - c * c) * a1;
|
|
b2 = ( 1.0 - res * c + c * c ) * a1;
|
|
}
|
|
|
|
void SoundFX_Comb::Initialize() {
|
|
if ( initialized )
|
|
return;
|
|
|
|
initialized = true;
|
|
maxlen = 50000;
|
|
buffer = new float[maxlen];
|
|
currentTime = 0;
|
|
}
|
|
|
|
void SoundFX_Comb::ProcessSample( float* in, float* out ) {
|
|
float gain = idSoundSystemLocal::s_reverbFeedback.GetFloat();
|
|
int len = idSoundSystemLocal::s_reverbTime.GetFloat() + param;
|
|
|
|
Initialize();
|
|
|
|
// sum up and output
|
|
out[0] = buffer[currentTime];
|
|
buffer[currentTime] = buffer[currentTime] * gain + in[0];
|
|
|
|
// increment current time
|
|
currentTime++;
|
|
if ( currentTime >= len )
|
|
currentTime -= len;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSystemLocal::DoEnviroSuit
|
|
===================
|
|
*/
|
|
void idSoundSystemLocal::DoEnviroSuit( float* samples, int numSamples, int numSpeakers ) {
|
|
float out[10000], *out_p = out + 2;
|
|
float in[10000], *in_p = in + 2;
|
|
|
|
// TODO port to OpenAL
|
|
assert( false );
|
|
|
|
if ( !fxList.Num() ) {
|
|
for ( int i = 0; i < 6; i++ ) {
|
|
SoundFX* fx;
|
|
|
|
// lowpass filter
|
|
fx = new SoundFX_Lowpass();
|
|
fx->SetChannel( i );
|
|
fxList.Append( fx );
|
|
|
|
// comb
|
|
fx = new SoundFX_Comb();
|
|
fx->SetChannel( i );
|
|
fx->SetParameter( i * 100 );
|
|
fxList.Append( fx );
|
|
|
|
// comb
|
|
fx = new SoundFX_Comb();
|
|
fx->SetChannel( i );
|
|
fx->SetParameter( i * 100 + 5 );
|
|
fxList.Append( fx );
|
|
}
|
|
}
|
|
|
|
for ( int i = 0; i < numSpeakers; i++ ) {
|
|
int j;
|
|
|
|
// restore previous samples
|
|
memset( in, 0, 10000 * sizeof( float ) );
|
|
memset( out, 0, 10000 * sizeof( float ) );
|
|
|
|
// fx loop
|
|
for ( int k = 0; k < fxList.Num(); k++ ) {
|
|
SoundFX* fx = fxList[k];
|
|
|
|
// skip if we're not the right channel
|
|
if ( fx->GetChannel() != i )
|
|
continue;
|
|
|
|
// get samples and continuity
|
|
fx->GetContinuitySamples( in_p[-1], in_p[-2], out_p[-1], out_p[-2] );
|
|
for ( j = 0; j < numSamples; j++ ) {
|
|
in_p[j] = samples[j * numSpeakers + i] * s_enviroSuitVolumeScale.GetFloat();
|
|
}
|
|
|
|
// process fx loop
|
|
for ( j = 0; j < numSamples; j++ ) {
|
|
fx->ProcessSample( in_p + j, out_p + j );
|
|
}
|
|
|
|
// store samples and continuity
|
|
fx->SetContinuitySamples( in_p[numSamples-2], in_p[numSamples-3], out_p[numSamples-2], out_p[numSamples-3] );
|
|
|
|
for ( j = 0; j < numSamples; j++ ) {
|
|
samples[j * numSpeakers + i] = out_p[j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idSoundSystemLocal::PrintMemInfo
|
|
=================
|
|
*/
|
|
void idSoundSystemLocal::PrintMemInfo( MemInfo_t *mi ) {
|
|
soundCache->PrintMemInfo( mi );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSoundSystemLocal::IsEAXAvailable
|
|
===============
|
|
*/
|
|
int idSoundSystemLocal::IsEAXAvailable( void ) {
|
|
#if !ID_OPENAL || !ID_OPENAL_EAX
|
|
return -1;
|
|
#else
|
|
ALCdevice *device;
|
|
ALCcontext *context;
|
|
|
|
if ( EAXAvailable != -1 ) {
|
|
return EAXAvailable;
|
|
}
|
|
|
|
// when dynamically loading the OpenAL subsystem, we need to get a context before alIsExtensionPresent would work
|
|
device = alcOpenDevice( NULL );
|
|
context = alcCreateContext( device, NULL );
|
|
alcMakeContextCurrent( context );
|
|
if ( alIsExtensionPresent( ID_ALCHAR "EAX4.0" ) ) {
|
|
alcMakeContextCurrent( NULL );
|
|
alcDestroyContext( context );
|
|
alcCloseDevice( device );
|
|
EAXAvailable = 1;
|
|
return 1;
|
|
}
|
|
alcMakeContextCurrent( NULL );
|
|
alcDestroyContext( context );
|
|
alcCloseDevice( device );
|
|
EAXAvailable = 0;
|
|
return 0;
|
|
#endif
|
|
}
|