mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-02 17:02:17 +00:00
1161 lines
27 KiB
C++
1161 lines
27 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition 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 BFG Edition 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 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition 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 BFG Edition 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.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#pragma hdrstop
|
|
#include "precompiled.h"
|
|
|
|
#include "snd_local.h"
|
|
|
|
idCVar s_singleEmitter( "s_singleEmitter", "0", CVAR_INTEGER, "mute all sounds but this emitter" );
|
|
idCVar s_showStartSound( "s_showStartSound", "0", CVAR_BOOL, "print a message every time a sound starts/stops" );
|
|
idCVar s_useOcclusion( "s_useOcclusion", "1", CVAR_BOOL, "Attenuate sounds based on walls" );
|
|
idCVar s_centerFractionVO( "s_centerFractionVO", "0.75", CVAR_FLOAT, "Portion of VO sounds routed to the center channel" );
|
|
|
|
extern idCVar s_playDefaultSound;
|
|
extern idCVar s_noSound;
|
|
|
|
/*
|
|
================================================================================================
|
|
|
|
idSoundFade
|
|
|
|
================================================================================================
|
|
*/
|
|
|
|
/*
|
|
========================
|
|
idSoundFade::Clear
|
|
========================
|
|
*/
|
|
void idSoundFade::Clear()
|
|
{
|
|
fadeStartTime = 0;
|
|
fadeEndTime = 0;
|
|
fadeStartVolume = 0.0f;
|
|
fadeEndVolume = 0.0f;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundFade::SetVolume
|
|
========================
|
|
*/
|
|
void idSoundFade::SetVolume( float to )
|
|
{
|
|
fadeStartVolume = to;
|
|
fadeEndVolume = to;
|
|
fadeStartTime = 0;
|
|
fadeEndTime = 0;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundFade::Fade
|
|
========================
|
|
*/
|
|
void idSoundFade::Fade( float to, int length, int soundTime )
|
|
{
|
|
int startTime = soundTime;
|
|
// if it is already fading to this volume at this rate, don't change it
|
|
if( fadeEndTime == startTime + length && fadeEndVolume == to )
|
|
{
|
|
return;
|
|
}
|
|
fadeStartVolume = GetVolume( soundTime );
|
|
fadeEndVolume = to;
|
|
fadeStartTime = startTime;
|
|
fadeEndTime = startTime + length;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundFade::GetVolume
|
|
========================
|
|
*/
|
|
float idSoundFade::GetVolume( const int soundTime ) const
|
|
{
|
|
const float fadeDuration = ( fadeEndTime - fadeStartTime );
|
|
const int currentTime = soundTime;
|
|
const float playTime = ( currentTime - fadeStartTime );
|
|
if( fadeDuration <= 0.0f )
|
|
{
|
|
return fadeEndVolume;
|
|
}
|
|
else if( currentTime >= fadeEndTime )
|
|
{
|
|
return fadeEndVolume;
|
|
}
|
|
else if( currentTime > fadeStartTime )
|
|
{
|
|
return fadeStartVolume + ( fadeEndVolume - fadeStartVolume ) * playTime / fadeDuration;
|
|
}
|
|
else
|
|
{
|
|
return fadeStartVolume;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundChannel::idSoundChannel
|
|
========================
|
|
*/
|
|
idSoundChannel::idSoundChannel()
|
|
{
|
|
emitter = NULL;
|
|
hardwareVoice = NULL;
|
|
|
|
startTime = 0;
|
|
endTime = 0;
|
|
leadinSample = NULL;
|
|
loopingSample = NULL;
|
|
logicalChannel = SCHANNEL_ANY;
|
|
allowSlow = false;
|
|
soundShader = NULL;
|
|
|
|
volumeFade.Clear();
|
|
|
|
volumeDB = DB_SILENCE;
|
|
currentAmplitude = 0.0f;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundChannel::~idSoundChannel
|
|
========================
|
|
*/
|
|
idSoundChannel::~idSoundChannel()
|
|
{
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundChannel::CanMute
|
|
Never actually mute VO because we can't restart them precisely enough for lip syncing to not fuck up
|
|
========================
|
|
*/
|
|
bool idSoundChannel::CanMute() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundChannel::Mute
|
|
|
|
A muted sound is considered still running, and can restart when a listener
|
|
gets close enough.
|
|
========================
|
|
*/
|
|
void idSoundChannel::Mute()
|
|
{
|
|
if( hardwareVoice != NULL )
|
|
{
|
|
soundSystemLocal.FreeVoice( hardwareVoice );
|
|
hardwareVoice = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundChannel::IsLooping
|
|
========================
|
|
*/
|
|
bool idSoundChannel::IsLooping() const
|
|
{
|
|
return ( parms.soundShaderFlags & SSF_LOOPING ) != 0;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundChannel::CheckForCompletion
|
|
========================
|
|
*/
|
|
bool idSoundChannel::CheckForCompletion( int currentTime )
|
|
{
|
|
if( leadinSample == NULL )
|
|
{
|
|
return true;
|
|
}
|
|
// endTime of 0 indicates a sound should loop forever
|
|
if( endTime > 0 && endTime < currentTime )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundChannel::UpdateVolume
|
|
========================
|
|
*/
|
|
void idSoundChannel::UpdateVolume( int currentTime )
|
|
{
|
|
idSoundWorldLocal* soundWorld = emitter->soundWorld;
|
|
|
|
volumeDB = DB_SILENCE;
|
|
currentAmplitude = 0.0f;
|
|
|
|
if( leadinSample == NULL )
|
|
{
|
|
return;
|
|
}
|
|
if( startTime > currentTime )
|
|
{
|
|
return;
|
|
}
|
|
if( endTime > 0 && endTime < currentTime )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if you don't want to hear all the beeps from missing sounds
|
|
if( leadinSample->IsDefault() && !s_playDefaultSound.GetBool() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool emitterIsListener = ( emitter->emitterId == soundWorld->listener.id );
|
|
|
|
// if it is a private sound, set the volume to zero unless we match the listener.id
|
|
if( parms.soundShaderFlags & SSF_PRIVATE_SOUND )
|
|
{
|
|
if( !emitterIsListener )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if( parms.soundShaderFlags & SSF_ANTI_PRIVATE_SOUND )
|
|
{
|
|
if( emitterIsListener )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// volume fading
|
|
float newVolumeDB = parms.volume;
|
|
newVolumeDB += volumeFade.GetVolume( currentTime );
|
|
newVolumeDB += soundWorld->volumeFade.GetVolume( currentTime );
|
|
newVolumeDB += soundWorld->pauseFade.GetVolume( currentTime );
|
|
if( parms.soundClass >= 0 && parms.soundClass < SOUND_MAX_CLASSES )
|
|
{
|
|
newVolumeDB += soundWorld->soundClassFade[parms.soundClass].GetVolume( currentTime );
|
|
}
|
|
|
|
bool global = ( parms.soundShaderFlags & SSF_GLOBAL ) != 0;
|
|
|
|
// attenuation
|
|
if( !global && !emitterIsListener )
|
|
{
|
|
float distance = ( parms.soundShaderFlags & SSF_NO_OCCLUSION ) == 0 ? emitter->spatializedDistance : emitter->directDistance;
|
|
float mindist = parms.minDistance;
|
|
float maxdist = parms.maxDistance;
|
|
if( distance >= maxdist )
|
|
{
|
|
newVolumeDB = DB_SILENCE;
|
|
}
|
|
else if( ( distance > mindist ) && ( maxdist > mindist ) )
|
|
{
|
|
float f = ( distance - mindist ) / ( maxdist - mindist );
|
|
newVolumeDB += LinearToDB( Square( 1.0f - f ) );
|
|
}
|
|
}
|
|
|
|
if( soundSystemLocal.musicMuted && ( parms.soundShaderFlags & SSF_MUSIC ) != 0 )
|
|
{
|
|
newVolumeDB = DB_SILENCE;
|
|
}
|
|
|
|
// store the new volume on the channel
|
|
volumeDB = newVolumeDB;
|
|
|
|
// keep track of the maximum volume
|
|
float currentVolumeDB = newVolumeDB;
|
|
if( hardwareVoice != NULL )
|
|
{
|
|
float amplitude = hardwareVoice->GetAmplitude();
|
|
if( amplitude <= 0.0f )
|
|
{
|
|
currentVolumeDB = DB_SILENCE;
|
|
}
|
|
else
|
|
{
|
|
currentVolumeDB += LinearToDB( amplitude );
|
|
}
|
|
currentAmplitude = amplitude;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundChannel::UpdateHardware
|
|
========================
|
|
*/
|
|
void idSoundChannel::UpdateHardware( float volumeAdd, int currentTime )
|
|
{
|
|
idSoundWorldLocal* soundWorld = emitter->soundWorld;
|
|
|
|
if( soundWorld == NULL )
|
|
{
|
|
return;
|
|
}
|
|
if( leadinSample == NULL )
|
|
{
|
|
return;
|
|
}
|
|
if( startTime > currentTime )
|
|
{
|
|
return;
|
|
}
|
|
if( endTime > 0 && endTime < currentTime )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// convert volumes from decibels to linear
|
|
float volume = Max( 0.0f, DBtoLinear( volumeDB + volumeAdd ) );
|
|
|
|
if( ( parms.soundShaderFlags & SSF_UNCLAMPED ) == 0 )
|
|
{
|
|
volume = Min( 1.0f, volume );
|
|
}
|
|
|
|
bool global = ( parms.soundShaderFlags & SSF_GLOBAL ) != 0;
|
|
bool omni = ( parms.soundShaderFlags & SSF_OMNIDIRECTIONAL ) != 0;
|
|
bool emitterIsListener = ( emitter->emitterId == soundWorld->listener.id );
|
|
|
|
int startOffset = 0;
|
|
bool issueStart = false;
|
|
|
|
if( hardwareVoice == NULL )
|
|
{
|
|
if( volume <= 0.00001f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
hardwareVoice = soundSystemLocal.AllocateVoice( leadinSample, loopingSample );
|
|
|
|
if( hardwareVoice == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
issueStart = true;
|
|
startOffset = currentTime - startTime;
|
|
}
|
|
|
|
if( omni || global || emitterIsListener )
|
|
{
|
|
hardwareVoice->SetPosition( vec3_zero );
|
|
}
|
|
else
|
|
{
|
|
hardwareVoice->SetPosition( ( emitter->spatializedOrigin - soundWorld->listener.pos ) * soundWorld->listener.axis.Transpose() );
|
|
}
|
|
if( parms.soundShaderFlags & SSF_VO )
|
|
{
|
|
hardwareVoice->SetCenterChannel( s_centerFractionVO.GetFloat() );
|
|
}
|
|
else
|
|
{
|
|
hardwareVoice->SetCenterChannel( 0.0f );
|
|
}
|
|
|
|
extern idCVar timescale;
|
|
|
|
hardwareVoice->SetGain( volume );
|
|
hardwareVoice->SetInnerRadius( parms.minDistance * METERS_TO_DOOM );
|
|
hardwareVoice->SetPitch( soundWorld->slowmoSpeed * idMath::ClampFloat( 0.2f, 5.0f, timescale.GetFloat() ) );
|
|
|
|
if( soundWorld->enviroSuitActive )
|
|
{
|
|
hardwareVoice->SetOcclusion( 0.5f );
|
|
}
|
|
else
|
|
{
|
|
hardwareVoice->SetOcclusion( 0.0f );
|
|
}
|
|
|
|
if( issueStart )
|
|
{
|
|
hardwareVoice->Start( startOffset, parms.soundShaderFlags | ( parms.shakes == 0.0f ? SSF_NO_FLICKER : 0 ) );
|
|
}
|
|
else
|
|
{
|
|
hardwareVoice->Update();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================================================================================================
|
|
|
|
idSoundEmitterLocal
|
|
|
|
================================================================================================
|
|
*/
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::idSoundEmitterLocal
|
|
========================
|
|
*/
|
|
idSoundEmitterLocal::idSoundEmitterLocal()
|
|
{
|
|
Init( 0, NULL );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::~idSoundEmitterLocal
|
|
========================
|
|
*/
|
|
idSoundEmitterLocal::~idSoundEmitterLocal()
|
|
{
|
|
assert( channels.Num() == 0 );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::Clear
|
|
========================
|
|
*/
|
|
void idSoundEmitterLocal::Init( int i, idSoundWorldLocal* sw )
|
|
{
|
|
index = i;
|
|
soundWorld = sw;
|
|
|
|
// Init should only be called on a freshly constructed sound emitter or in a Reset()
|
|
assert( channels.Num() == 0 );
|
|
|
|
canFree = false;
|
|
origin.Zero();
|
|
emitterId = 0;
|
|
|
|
directDistance = 0.0f;
|
|
lastValidPortalArea = -1;
|
|
spatializedDistance = 0.0f;
|
|
spatializedOrigin.Zero();
|
|
|
|
memset( &parms, 0, sizeof( parms ) );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::Reset
|
|
========================
|
|
*/
|
|
void idSoundEmitterLocal::Reset()
|
|
{
|
|
for( int i = 0; i < channels.Num(); i++ )
|
|
{
|
|
soundWorld->FreeSoundChannel( channels[i] );
|
|
}
|
|
channels.Clear();
|
|
Init( index, soundWorld );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idSoundEmitterLocal::OverrideParms
|
|
==================
|
|
*/
|
|
void idSoundEmitterLocal::OverrideParms( const soundShaderParms_t* base, const soundShaderParms_t* over, soundShaderParms_t* out )
|
|
{
|
|
if( !over )
|
|
{
|
|
*out = *base;
|
|
return;
|
|
}
|
|
if( over->minDistance )
|
|
{
|
|
out->minDistance = over->minDistance;
|
|
}
|
|
else
|
|
{
|
|
out->minDistance = base->minDistance;
|
|
}
|
|
if( over->maxDistance )
|
|
{
|
|
out->maxDistance = over->maxDistance;
|
|
}
|
|
else
|
|
{
|
|
out->maxDistance = base->maxDistance;
|
|
}
|
|
if( over->shakes )
|
|
{
|
|
out->shakes = over->shakes;
|
|
}
|
|
else
|
|
{
|
|
out->shakes = base->shakes;
|
|
}
|
|
if( over->volume )
|
|
{
|
|
out->volume = over->volume;
|
|
}
|
|
else
|
|
{
|
|
out->volume = base->volume;
|
|
}
|
|
if( over->soundClass )
|
|
{
|
|
out->soundClass = over->soundClass;
|
|
}
|
|
else
|
|
{
|
|
out->soundClass = base->soundClass;
|
|
}
|
|
out->soundShaderFlags = base->soundShaderFlags | over->soundShaderFlags;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::CheckForCompletion
|
|
|
|
Checks to see if any of the channels have completed, removing them as they do
|
|
|
|
This will also play any postSounds on the same channel as their owner.
|
|
|
|
Returns true if the emitter should be freed.
|
|
========================
|
|
*/
|
|
bool idSoundEmitterLocal::CheckForCompletion( int currentTime )
|
|
{
|
|
for( int i = channels.Num() - 1; i >= 0 ; i-- )
|
|
{
|
|
idSoundChannel* chan = channels[i];
|
|
|
|
if( chan->CheckForCompletion( currentTime ) )
|
|
{
|
|
channels.RemoveIndex( i );
|
|
soundWorld->FreeSoundChannel( chan );
|
|
}
|
|
}
|
|
return ( canFree && channels.Num() == 0 );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::Update
|
|
========================
|
|
*/
|
|
void idSoundEmitterLocal::Update( int currentTime )
|
|
{
|
|
if( channels.Num() == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
directDistance = ( soundWorld->listener.pos - origin ).LengthFast() * DOOM_TO_METERS;
|
|
|
|
spatializedDistance = directDistance;
|
|
spatializedOrigin = origin;
|
|
|
|
// Initialize all channels to silence
|
|
for( int i = 0; i < channels.Num(); i++ )
|
|
{
|
|
channels[i]->volumeDB = DB_SILENCE;
|
|
}
|
|
|
|
if( s_singleEmitter.GetInteger() > 0 && s_singleEmitter.GetInteger() != index )
|
|
{
|
|
return;
|
|
}
|
|
if( soundWorld->listener.area == -1 )
|
|
{
|
|
// listener is outside the world
|
|
return;
|
|
}
|
|
if( soundSystemLocal.muted || soundWorld != soundSystemLocal.currentSoundWorld )
|
|
{
|
|
return;
|
|
}
|
|
float maxDistance = 0.0f;
|
|
bool maxDistanceValid = false;
|
|
bool useOcclusion = false;
|
|
if( emitterId != soundWorld->listener.id )
|
|
{
|
|
for( int i = 0; i < channels.Num(); i++ )
|
|
{
|
|
idSoundChannel* chan = channels[i];
|
|
if( ( chan->parms.soundShaderFlags & SSF_GLOBAL ) != 0 )
|
|
{
|
|
continue;
|
|
}
|
|
useOcclusion = useOcclusion || ( ( chan->parms.soundShaderFlags & SSF_NO_OCCLUSION ) == 0 );
|
|
maxDistanceValid = true;
|
|
if( maxDistance < channels[i]->parms.maxDistance )
|
|
{
|
|
maxDistance = channels[i]->parms.maxDistance;
|
|
}
|
|
}
|
|
}
|
|
if( maxDistanceValid && directDistance >= maxDistance )
|
|
{
|
|
// too far away to possibly hear it
|
|
return;
|
|
}
|
|
if( useOcclusion && s_useOcclusion.GetBool() )
|
|
{
|
|
// work out virtual origin and distance, which may be from a portal instead of the actual origin
|
|
if( soundWorld->renderWorld != NULL )
|
|
{
|
|
// we have a valid renderWorld
|
|
int soundInArea = soundWorld->renderWorld->PointInArea( origin );
|
|
if( soundInArea == -1 )
|
|
{
|
|
soundInArea = lastValidPortalArea;
|
|
}
|
|
else
|
|
{
|
|
lastValidPortalArea = soundInArea;
|
|
}
|
|
if( soundInArea != -1 && soundInArea != soundWorld->listener.area )
|
|
{
|
|
spatializedDistance = maxDistance * METERS_TO_DOOM;
|
|
soundWorld->ResolveOrigin( 0, NULL, soundInArea, 0.0f, origin, this );
|
|
spatializedDistance *= DOOM_TO_METERS;
|
|
}
|
|
}
|
|
}
|
|
|
|
for( int j = 0; j < channels.Num(); j++ )
|
|
{
|
|
channels[j]->UpdateVolume( currentTime );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::Index
|
|
========================
|
|
*/
|
|
int idSoundEmitterLocal::Index() const
|
|
{
|
|
assert( soundWorld );
|
|
assert( soundWorld->emitters[this->index] == this );
|
|
|
|
return index;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::Free
|
|
|
|
Doesn't free it until the next update.
|
|
========================
|
|
*/
|
|
void idSoundEmitterLocal::Free( bool immediate )
|
|
{
|
|
assert( soundWorld );
|
|
assert( soundWorld->emitters[this->index] == this );
|
|
|
|
if( canFree )
|
|
{
|
|
// Double free
|
|
return;
|
|
}
|
|
if( soundWorld && soundWorld->writeDemo )
|
|
{
|
|
soundWorld->writeDemo->WriteInt( DS_SOUND );
|
|
soundWorld->writeDemo->WriteInt( SCMD_FREE );
|
|
soundWorld->writeDemo->WriteInt( index );
|
|
soundWorld->writeDemo->WriteInt( immediate );
|
|
}
|
|
|
|
if( immediate )
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
canFree = true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::UpdateEmitter
|
|
========================
|
|
*/
|
|
void idSoundEmitterLocal::UpdateEmitter( const idVec3& origin, int listenerId, const soundShaderParms_t* parms )
|
|
{
|
|
assert( soundWorld != NULL );
|
|
assert( soundWorld->emitters[this->index] == this );
|
|
|
|
if( soundWorld && soundWorld->writeDemo )
|
|
{
|
|
soundWorld->writeDemo->WriteInt( DS_SOUND );
|
|
soundWorld->writeDemo->WriteInt( SCMD_UPDATE );
|
|
soundWorld->writeDemo->WriteInt( index );
|
|
soundWorld->writeDemo->WriteVec3( origin );
|
|
soundWorld->writeDemo->WriteInt( listenerId );
|
|
soundWorld->writeDemo->WriteFloat( parms->minDistance );
|
|
soundWorld->writeDemo->WriteFloat( parms->maxDistance );
|
|
soundWorld->writeDemo->WriteFloat( parms->volume );
|
|
soundWorld->writeDemo->WriteFloat( parms->shakes );
|
|
soundWorld->writeDemo->WriteInt( parms->soundShaderFlags );
|
|
soundWorld->writeDemo->WriteInt( parms->soundClass );
|
|
}
|
|
|
|
this->origin = origin;
|
|
this->emitterId = listenerId;
|
|
this->parms = *parms;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::StartSound
|
|
|
|
in most cases play sounds immediately, however
|
|
intercept sounds using SSF_FINITE_SPEED_OF_SOUND
|
|
and schedule them for playback later
|
|
|
|
return: int - the length of the started sound in msec.
|
|
========================
|
|
*/
|
|
int idSoundEmitterLocal::StartSound( const idSoundShader* shader, const s_channelType channel, float diversity, int shaderFlags, bool allowSlow )
|
|
{
|
|
assert( soundWorld != NULL );
|
|
assert( soundWorld->emitters[this->index] == this );
|
|
|
|
if( shader == NULL )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if( soundWorld && soundWorld->writeDemo )
|
|
{
|
|
soundWorld->writeDemo->WriteInt( DS_SOUND );
|
|
soundWorld->writeDemo->WriteInt( SCMD_START );
|
|
soundWorld->writeDemo->WriteInt( index );
|
|
|
|
soundWorld->writeDemo->WriteHashString( shader->GetName() );
|
|
|
|
soundWorld->writeDemo->WriteInt( channel );
|
|
soundWorld->writeDemo->WriteFloat( diversity );
|
|
soundWorld->writeDemo->WriteInt( shaderFlags );
|
|
}
|
|
|
|
if( s_noSound.GetBool() )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int currentTime = soundWorld->GetSoundTime();
|
|
|
|
bool showStartSound = s_showStartSound.GetBool();
|
|
if( showStartSound )
|
|
{
|
|
idLib::Printf( "%dms: StartSound(%d:%d): %s: ", currentTime, index, channel, shader->GetName() );
|
|
}
|
|
|
|
// build the channel parameters by taking the shader parms and optionally overriding
|
|
soundShaderParms_t chanParms;
|
|
chanParms = shader->parms;
|
|
OverrideParms( &chanParms, &parms, &chanParms );
|
|
chanParms.soundShaderFlags |= shaderFlags;
|
|
|
|
if( shader->entries.Num() == 0 )
|
|
{
|
|
if( showStartSound )
|
|
{
|
|
idLib::Printf( S_COLOR_RED "No Entries\n" );
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// PLAY_ONCE sounds will never be restarted while they are running
|
|
if( chanParms.soundShaderFlags & SSF_PLAY_ONCE )
|
|
{
|
|
for( int i = 0; i < channels.Num(); i++ )
|
|
{
|
|
idSoundChannel* chan = channels[i];
|
|
if( chan->soundShader == shader && !chan->CheckForCompletion( currentTime ) )
|
|
{
|
|
if( showStartSound )
|
|
{
|
|
idLib::Printf( S_COLOR_YELLOW "Not started because of playOnce\n" );
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// never play the same sound twice with the same starting time, even
|
|
// if they are on different channels
|
|
for( int i = 0; i < channels.Num(); i++ )
|
|
{
|
|
idSoundChannel* chan = channels[i];
|
|
if( chan->soundShader == shader && chan->startTime == currentTime && chan->endTime != 1 )
|
|
{
|
|
if( showStartSound )
|
|
{
|
|
idLib::Printf( S_COLOR_RED "Already started this frame\n" );
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// kill any sound that is currently playing on this channel
|
|
if( channel != SCHANNEL_ANY )
|
|
{
|
|
for( int i = 0; i < channels.Num(); i++ )
|
|
{
|
|
idSoundChannel* chan = channels[i];
|
|
if( chan->soundShader && chan->logicalChannel == channel )
|
|
{
|
|
if( showStartSound )
|
|
{
|
|
idLib::Printf( S_COLOR_YELLOW "OVERRIDE %s: ", chan->soundShader->GetName() );
|
|
}
|
|
channels.RemoveIndex( i );
|
|
soundWorld->FreeSoundChannel( chan );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
idSoundSample* leadinSample = NULL;
|
|
idSoundSample* loopingSample = NULL;
|
|
|
|
if( shader->leadin && ( chanParms.soundShaderFlags & SSF_LOOPING ) )
|
|
{
|
|
leadinSample = shader->entries[0];
|
|
loopingSample = shader->entries.Num() > 1 ? shader->entries[1] : NULL;
|
|
}
|
|
else
|
|
{
|
|
if( shader->entries.Num() == 1 )
|
|
{
|
|
leadinSample = shader->entries[0];
|
|
}
|
|
else
|
|
{
|
|
int choice;
|
|
if( chanParms.soundShaderFlags & SSF_NO_DUPS )
|
|
{
|
|
// Don't select the most recently played entry
|
|
int mostRecentTime = 0;
|
|
int mostRecent = 0;
|
|
for( int i = 0; i < shader->entries.Num(); i++ )
|
|
{
|
|
int entryTime = shader->entries[i]->GetLastPlayedTime();
|
|
if( entryTime > mostRecentTime )
|
|
{
|
|
mostRecentTime = entryTime;
|
|
mostRecent = i;
|
|
}
|
|
}
|
|
choice = ( int )( diversity * ( shader->entries.Num() - 1 ) );
|
|
if( choice >= mostRecent )
|
|
{
|
|
choice++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// pick a sound from the list based on the passed diversity
|
|
choice = ( int )( diversity * shader->entries.Num() );
|
|
}
|
|
choice = idMath::ClampInt( 0, shader->entries.Num() - 1, choice );
|
|
leadinSample = shader->entries[choice];
|
|
leadinSample->SetLastPlayedTime( soundWorld->GetSoundTime() );
|
|
}
|
|
if( chanParms.soundShaderFlags & SSF_LOOPING )
|
|
{
|
|
loopingSample = leadinSample;
|
|
}
|
|
}
|
|
|
|
// set all the channel parameters here,
|
|
// a hardware voice will be allocated next update if the volume is high enough to be audible
|
|
if( channels.Num() == channels.Max() )
|
|
{
|
|
CheckForCompletion( currentTime ); // as a last chance try to release finished sounds here
|
|
if( channels.Num() == channels.Max() )
|
|
{
|
|
if( showStartSound )
|
|
{
|
|
idLib::Printf( S_COLOR_RED "No free emitter channels!\n" );
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
idSoundChannel* chan = soundWorld->AllocSoundChannel();
|
|
if( chan == NULL )
|
|
{
|
|
if( showStartSound )
|
|
{
|
|
idLib::Printf( S_COLOR_RED "No free global channels!\n" );
|
|
}
|
|
return 0;
|
|
}
|
|
channels.Append( chan );
|
|
chan->emitter = this;
|
|
chan->parms = chanParms;
|
|
chan->soundShader = shader;
|
|
chan->logicalChannel = channel;
|
|
chan->leadinSample = leadinSample;
|
|
chan->loopingSample = loopingSample;
|
|
chan->allowSlow = allowSlow;
|
|
|
|
// return length of sound in milliseconds
|
|
int length = chan->leadinSample->LengthInMsec();
|
|
|
|
// adjust the start time based on diversity for looping sounds, so they don't all start at the same point
|
|
int startOffset = 0;
|
|
|
|
if( chan->IsLooping() && !shader->leadin )
|
|
{
|
|
// looping sounds start at a random point...
|
|
startOffset = soundSystemLocal.random.RandomInt( length );
|
|
}
|
|
|
|
chan->startTime = currentTime - startOffset;
|
|
|
|
if( ( chanParms.soundShaderFlags & SSF_LOOPING ) != 0 )
|
|
{
|
|
// This channel will never end!
|
|
chan->endTime = 0;
|
|
}
|
|
else
|
|
{
|
|
// This channel will automatically end at this time
|
|
chan->endTime = chan->startTime + length + 100;
|
|
}
|
|
if( showStartSound )
|
|
{
|
|
if( loopingSample == NULL || leadinSample == loopingSample )
|
|
{
|
|
idLib::Printf( "Playing %s @ %d\n", leadinSample->GetName(), startOffset );
|
|
}
|
|
else
|
|
{
|
|
idLib::Printf( "Playing %s then looping %s\n", leadinSample->GetName(), loopingSample->GetName() );
|
|
}
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::OnReloadSound
|
|
|
|
This is a shortened version of StartSound, called whenever a sound shader is reloaded.
|
|
If the emitter is currently playing the given sound shader, restart it so
|
|
a change in the sound sample used for a given sound shader will be picked up.
|
|
========================
|
|
*/
|
|
void idSoundEmitterLocal::OnReloadSound( const idDecl* decl )
|
|
{
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::StopSound
|
|
|
|
Can pass SCHANNEL_ANY.
|
|
========================
|
|
*/
|
|
void idSoundEmitterLocal::StopSound( const s_channelType channel )
|
|
{
|
|
assert( soundWorld != NULL );
|
|
assert( soundWorld->emitters[this->index] == this );
|
|
|
|
if( soundWorld && soundWorld->writeDemo )
|
|
{
|
|
soundWorld->writeDemo->WriteInt( DS_SOUND );
|
|
soundWorld->writeDemo->WriteInt( SCMD_STOP );
|
|
soundWorld->writeDemo->WriteInt( index );
|
|
soundWorld->writeDemo->WriteInt( channel );
|
|
}
|
|
|
|
for( int i = 0; i < channels.Num(); i++ )
|
|
{
|
|
idSoundChannel* chan = channels[i];
|
|
|
|
if( channel != SCHANNEL_ANY && chan->logicalChannel != channel )
|
|
{
|
|
continue;
|
|
}
|
|
if( s_showStartSound.GetBool() )
|
|
{
|
|
idLib::Printf( "%dms: StopSound(%d:%d): %s\n", soundWorld->GetSoundTime(), index, channel, chan->soundShader->GetName() );
|
|
}
|
|
|
|
// This forces CheckForCompletion to return true
|
|
chan->endTime = 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::ModifySound
|
|
========================
|
|
*/
|
|
void idSoundEmitterLocal::ModifySound( const s_channelType channel, const soundShaderParms_t* parms )
|
|
{
|
|
assert( soundWorld != NULL );
|
|
assert( soundWorld->emitters[this->index] == this );
|
|
|
|
if( soundWorld && soundWorld->writeDemo )
|
|
{
|
|
soundWorld->writeDemo->WriteInt( DS_SOUND );
|
|
soundWorld->writeDemo->WriteInt( SCMD_MODIFY );
|
|
soundWorld->writeDemo->WriteInt( index );
|
|
soundWorld->writeDemo->WriteInt( channel );
|
|
soundWorld->writeDemo->WriteFloat( parms->minDistance );
|
|
soundWorld->writeDemo->WriteFloat( parms->maxDistance );
|
|
soundWorld->writeDemo->WriteFloat( parms->volume );
|
|
soundWorld->writeDemo->WriteFloat( parms->shakes );
|
|
soundWorld->writeDemo->WriteInt( parms->soundShaderFlags );
|
|
soundWorld->writeDemo->WriteInt( parms->soundClass );
|
|
}
|
|
|
|
for( int i = channels.Num() - 1; i >= 0; i-- )
|
|
{
|
|
idSoundChannel* chan = channels[i];
|
|
if( channel != SCHANNEL_ANY && chan->logicalChannel != channel )
|
|
{
|
|
continue;
|
|
}
|
|
if( s_showStartSound.GetBool() )
|
|
{
|
|
idLib::Printf( "%dms: ModifySound(%d:%d): %s\n", soundWorld->GetSoundTime(), index, channel, chan->soundShader->GetName() );
|
|
}
|
|
OverrideParms( &chan->parms, parms, &chan->parms );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::FadeSound
|
|
========================
|
|
*/
|
|
void idSoundEmitterLocal::FadeSound( const s_channelType channel, float to, float over )
|
|
{
|
|
assert( soundWorld != NULL );
|
|
assert( soundWorld->emitters[this->index] == this );
|
|
|
|
if( soundWorld->writeDemo )
|
|
{
|
|
soundWorld->writeDemo->WriteInt( DS_SOUND );
|
|
soundWorld->writeDemo->WriteInt( SCMD_FADE );
|
|
soundWorld->writeDemo->WriteInt( index );
|
|
soundWorld->writeDemo->WriteInt( channel );
|
|
soundWorld->writeDemo->WriteFloat( to );
|
|
soundWorld->writeDemo->WriteFloat( over );
|
|
}
|
|
|
|
int overMSec = SEC2MS( over );
|
|
|
|
for( int i = 0; i < channels.Num(); i++ )
|
|
{
|
|
idSoundChannel* chan = channels[i];
|
|
|
|
if( channel != SCHANNEL_ANY && chan->logicalChannel != channel )
|
|
{
|
|
continue;
|
|
}
|
|
if( s_showStartSound.GetBool() )
|
|
{
|
|
idLib::Printf( "%dms: FadeSound(%d:%d): %s to %.2fdb over %.2f seconds\n", soundWorld->GetSoundTime(), index, channel, chan->soundShader->GetName(), to, over );
|
|
}
|
|
|
|
// fade it
|
|
chan->volumeFade.Fade( to - chan->parms.volume, overMSec, soundWorld->GetSoundTime() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::CurrentlyPlaying
|
|
========================
|
|
*/
|
|
bool idSoundEmitterLocal::CurrentlyPlaying( const s_channelType channel ) const
|
|
{
|
|
|
|
if( channel == SCHANNEL_ANY )
|
|
{
|
|
return ( channels.Num() > 0 );
|
|
}
|
|
|
|
for( int i = 0; i < channels.Num(); ++i )
|
|
{
|
|
if( channels[i] != NULL && channels[i]->logicalChannel == channel )
|
|
{
|
|
if( channels[i]->endTime == 1 )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSoundEmitterLocal::CurrentAmplitude
|
|
========================
|
|
*/
|
|
float idSoundEmitterLocal::CurrentAmplitude()
|
|
{
|
|
float amplitude = 0.0f;
|
|
int currentTime = soundWorld->GetSoundTime();
|
|
for( int i = 0; i < channels.Num(); i++ )
|
|
{
|
|
idSoundChannel* chan = channels[i];
|
|
if( chan == NULL || currentTime < chan->startTime || ( chan->endTime > 0 && currentTime >= chan->endTime ) )
|
|
{
|
|
continue;
|
|
}
|
|
int relativeTime = currentTime - chan->startTime;
|
|
int leadinLength = chan->leadinSample->LengthInMsec();
|
|
if( relativeTime < leadinLength )
|
|
{
|
|
amplitude = Max( amplitude, chan->leadinSample->GetAmplitude( relativeTime ) );
|
|
}
|
|
else if( chan->loopingSample != NULL )
|
|
{
|
|
amplitude = Max( amplitude, chan->loopingSample->GetAmplitude( ( relativeTime - leadinLength ) % chan->loopingSample->LengthInMsec() ) );
|
|
}
|
|
}
|
|
return amplitude;
|
|
}
|