/* =========================================================================== 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 . 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; }