From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/shared/soundenvelope.cpp | 2624 +++++++++++++++++----------------- 1 file changed, 1312 insertions(+), 1312 deletions(-) (limited to 'mp/src/game/shared/soundenvelope.cpp') diff --git a/mp/src/game/shared/soundenvelope.cpp b/mp/src/game/shared/soundenvelope.cpp index 15cba81d..e262aa26 100644 --- a/mp/src/game/shared/soundenvelope.cpp +++ b/mp/src/game/shared/soundenvelope.cpp @@ -1,1312 +1,1312 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "sharedInterface.h" -#include "soundenvelope.h" -#include "engine/IEngineSound.h" -#include "IEffects.h" -#include "isaverestore.h" -#include "saverestore_utlvector.h" -#include "gamestringpool.h" -#include "igamesystem.h" -#include "utlpriorityqueue.h" -#include "mempool.h" -#include "SoundEmitterSystem/isoundemittersystembase.h" -#include "tier0/vprof.h" -#include "gamerules.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -static ConVar soundpatch_captionlength( "soundpatch_captionlength", "2.0", FCVAR_REPLICATED, "How long looping soundpatch captions should display for." ); - -// Envelope -// This is a class that controls a ramp for a sound (pitch / volume / etc) -class CSoundEnvelope -{ -public: - DECLARE_SIMPLE_DATADESC(); - - CSoundEnvelope() - { - m_current = 0.0f; - m_target = 0.0f; - m_rate = 0.0f; - m_forceupdate = false; - } - - void SetTarget( float target, float deltaTime ); - void SetValue( float value ); - bool ShouldUpdate( void ); - void Update( float time ); - inline float Value( void ) { return m_current; } - -private: - float m_current; - float m_target; - float m_rate; - bool m_forceupdate; -}; - - -BEGIN_SIMPLE_DATADESC( CSoundEnvelope ) - DEFINE_FIELD( m_current, FIELD_FLOAT ), - DEFINE_FIELD( m_target, FIELD_FLOAT ), - DEFINE_FIELD( m_rate, FIELD_FLOAT ), - DEFINE_FIELD( m_forceupdate, FIELD_BOOLEAN ), -END_DATADESC() - - -//----------------------------------------------------------------------------- -// Purpose: Set the new target value for this ramp. Reach this target in deltaTime -// seconds from now -// Input : target - new target value -// deltaTime - time to reach target -//----------------------------------------------------------------------------- -void CSoundEnvelope::SetTarget( float target, float deltaTime ) -{ - float deltaValue = target - m_current; - - if ( deltaValue && deltaTime > 0 ) - { - m_target = target; - m_rate = MAX( 0.1, fabs(deltaValue / deltaTime) ); - } - else - { - if ( target != m_current ) - { - m_forceupdate = true; - } - - SetValue( target ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Instantaneously set the value of this ramp -// Input : value - new value -//----------------------------------------------------------------------------- -void CSoundEnvelope::SetValue( float value ) -{ - if ( m_target != value ) - { - m_forceupdate = true; - } - - m_current = m_target = value; - m_rate = 0; -} - - -//----------------------------------------------------------------------------- -// Purpose: Check to see if I need to update this envelope -// Output : Returns true if this envelope is changing -//----------------------------------------------------------------------------- -bool CSoundEnvelope::ShouldUpdate( void ) -{ - if ( m_forceupdate ) - { - m_forceupdate = false; - return true; - } - - if ( m_current != m_target ) - { - return true; - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: Update the envelope for the current frame time -// Input : time - amount of time that has passed -//----------------------------------------------------------------------------- -void CSoundEnvelope::Update( float deltaTime ) -{ - m_current = Approach( m_target, m_current, m_rate * deltaTime ); -} - -class CCopyRecipientFilter : public IRecipientFilter -{ -public: - DECLARE_SIMPLE_DATADESC(); - - CCopyRecipientFilter() : m_Flags(0) {} - - void Init( IRecipientFilter *pSrc ) - { - m_Flags = FLAG_ACTIVE; - if ( pSrc->IsReliable() ) - { - m_Flags |= FLAG_RELIABLE; - } - - if ( pSrc->IsInitMessage() ) - { - m_Flags |= FLAG_INIT_MESSAGE; - } - - for ( int i = 0; i < pSrc->GetRecipientCount(); i++ ) - { - int index = pSrc->GetRecipientIndex( i ); - - if ( index >= 0 ) - m_Recipients.AddToTail( index ); - } - } - - bool IsActive() const - { - return (m_Flags & FLAG_ACTIVE) != 0; - } - - virtual bool IsReliable( void ) const - { - return (m_Flags & FLAG_RELIABLE) != 0; - } - - virtual int GetRecipientCount( void ) const - { - return m_Recipients.Count(); - } - - virtual int GetRecipientIndex( int slot ) const - { - return m_Recipients[ slot ]; - } - - virtual bool IsInitMessage( void ) const - { - return (m_Flags & FLAG_INIT_MESSAGE) != 0; - } - - virtual bool AddRecipient( CBasePlayer *player ) - { - Assert( player ); - - int index = player->entindex(); - - if ( index < 0 ) - return false; - - // Already in list - if ( m_Recipients.Find( index ) != m_Recipients.InvalidIndex() ) - return false; - - m_Recipients.AddToTail( index ); - return true; - } - -private: - enum - { - FLAG_ACTIVE = 0x1, - FLAG_RELIABLE = 0x2, - FLAG_INIT_MESSAGE = 0x4, - }; - - int m_Flags; - CUtlVector< int > m_Recipients; -}; - -BEGIN_SIMPLE_DATADESC( CCopyRecipientFilter ) - - DEFINE_FIELD( m_Flags, FIELD_INTEGER ), - DEFINE_UTLVECTOR( m_Recipients, FIELD_INTEGER ), - -END_DATADESC() - - -#include "tier0/memdbgoff.h" -// This is the a basic sound controller, a "patch" -// It has envelopes for pitch and volume and can manage state changes to those -class CSoundPatch -{ -public: - DECLARE_SIMPLE_DATADESC(); - - static int g_SoundPatchCount; - CSoundPatch() - { - g_SoundPatchCount++; - m_iszSoundName = NULL_STRING; - m_iszSoundScriptName = NULL_STRING; - m_flCloseCaptionDuration = soundpatch_captionlength.GetFloat(); - } - ~CSoundPatch() - { - g_SoundPatchCount--; - } - - void Init( IRecipientFilter *pFilter, CBaseEntity *pEnt, int channel, const char *pSoundName, - soundlevel_t iSoundLevel ); - void ChangePitch( float pitchTarget, float deltaTime ); - void ChangeVolume( float volumeTarget, float deltaTime ); - void FadeOut( float deltaTime, bool destroyOnFadeout ); - float GetPitch( void ); - float GetVolume( void ); - string_t GetName() { return m_iszSoundName; }; - string_t GetScriptName() { return m_iszSoundScriptName; } - // UNDONE: Don't call this, use the controller to shut down - void Shutdown( void ); - bool Update( float time, float deltaTime ); - void Reset( void ); - void StartSound( float flStartTime = 0 ); - void ResumeSound( void ); - int IsPlaying( void ) { return m_isPlaying; } - void AddPlayerPost( CBasePlayer *pPlayer ); - void SetCloseCaptionDuration( float flDuration ) { m_flCloseCaptionDuration = flDuration; } - - void SetBaseFlags( int iFlags ) { m_baseFlags = iFlags; } - - // Returns the ent index - int EntIndex() const; - -private: - // SoundPatches take volumes between 0 & 1, and use that to multiply the sounds.txt specified volume. - // This function is an internal method of accessing the real volume passed into the engine (i.e. post multiply) - float GetVolumeForEngine( void ); - -private: - CSoundEnvelope m_pitch; - CSoundEnvelope m_volume; - - soundlevel_t m_soundlevel; - float m_shutdownTime; - float m_flLastTime; - string_t m_iszSoundName; - string_t m_iszSoundScriptName; - EHANDLE m_hEnt; - int m_entityChannel; - int m_flags; - int m_baseFlags; - int m_isPlaying; - float m_flScriptVolume; // Volume for this sound in sounds.txt - CCopyRecipientFilter m_Filter; - - float m_flCloseCaptionDuration; - -#ifdef _DEBUG - // Used to get the classname of the entity associated with the sound - string_t m_iszClassName; -#endif - - DECLARE_FIXEDSIZE_ALLOCATOR(CSoundPatch); -}; -#include "tier0/memdbgon.h" - -int CSoundPatch::g_SoundPatchCount = 0; - -CON_COMMAND( report_soundpatch, "reports sound patch count" ) -{ -#ifndef CLIENT_DLL - if ( !UTIL_IsCommandIssuedByServerAdmin() ) - return; -#endif - - Msg("Current sound patches: %d\n", CSoundPatch::g_SoundPatchCount ); -} -DEFINE_FIXEDSIZE_ALLOCATOR( CSoundPatch, 64, CUtlMemoryPool::GROW_FAST ); - -BEGIN_SIMPLE_DATADESC( CSoundPatch ) - - DEFINE_EMBEDDED( m_pitch ), - DEFINE_EMBEDDED( m_volume ), - DEFINE_FIELD( m_soundlevel, FIELD_INTEGER ), - DEFINE_FIELD( m_shutdownTime, FIELD_TIME ), - DEFINE_FIELD( m_flLastTime, FIELD_TIME ), - DEFINE_FIELD( m_iszSoundName, FIELD_STRING ), - DEFINE_FIELD( m_iszSoundScriptName, FIELD_STRING ), - DEFINE_FIELD( m_hEnt, FIELD_EHANDLE ), - DEFINE_FIELD( m_entityChannel, FIELD_INTEGER ), - DEFINE_FIELD( m_flags, FIELD_INTEGER ), - DEFINE_FIELD( m_baseFlags, FIELD_INTEGER ), - DEFINE_FIELD( m_isPlaying, FIELD_INTEGER ), - DEFINE_FIELD( m_flScriptVolume, FIELD_FLOAT ), - DEFINE_EMBEDDED( m_Filter ), - DEFINE_FIELD( m_flCloseCaptionDuration, FIELD_FLOAT ), - - // Not saved, it's debug only -// DEFINE_FIELD( m_iszClassName, FIELD_STRING ), - -END_DATADESC() - - - -//----------------------------------------------------------------------------- -// Purpose: Setup the patch -// Input : nEntIndex - index of the edict that owns the sound channel -// channel - This is a sound channel (CHAN_ITEM, CHAN_STATIC) -// *pSoundName - sound script string name -// attenuation - attenuation of this sound (not animated) -//----------------------------------------------------------------------------- -void CSoundPatch::Init( IRecipientFilter *pFilter, CBaseEntity *pEnt, int channel, const char *pSoundName, - soundlevel_t soundlevel ) -{ - m_hEnt = pEnt; - m_entityChannel = channel; - // Get the volume from the script - CSoundParameters params; - if ( !Q_stristr( pSoundName, ".wav" ) && !Q_stristr( pSoundName, ".mp3" ) && - CBaseEntity::GetParametersForSound( pSoundName, params, NULL ) ) - { - m_flScriptVolume = params.volume; - // This has to be the actual .wav because rndwave would cause a bunch of new .wavs to play... bad... - // e.g., when you pitch shift it would start a different wav instead. - - m_iszSoundScriptName = AllocPooledString( pSoundName ); - - pSoundName = params.soundname; - m_soundlevel = params.soundlevel; - - m_entityChannel = params.channel; - } - else - { - - m_iszSoundScriptName = AllocPooledString( pSoundName ); - - m_flScriptVolume = 1.0; - m_soundlevel = soundlevel; - } - - m_iszSoundName = AllocPooledString( pSoundName ); - m_volume.SetValue( 0 ); - m_pitch.SetValue( 0 ); - m_isPlaying = false; - m_shutdownTime = 0; - m_flLastTime = 0; - m_Filter.Init( pFilter ); - m_baseFlags = 0; - -#ifdef _DEBUG - if ( pEnt ) - { - m_iszClassName = AllocPooledString( pEnt->GetClassname() ); - } -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: Ramps the pitch to a new value -// Input : pitchTarget - new value -// deltaTime - seconds to reach the value -//----------------------------------------------------------------------------- -void CSoundPatch::ChangePitch( float pitchTarget, float deltaTime ) -{ - m_flags |= SND_CHANGE_PITCH; - m_pitch.SetTarget( pitchTarget, deltaTime ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Ramps the volume to a new value -// Input : volumeTarget - new volume -// deltaTime - seconds to reach the new volume -//----------------------------------------------------------------------------- -void CSoundPatch::ChangeVolume( float volumeTarget, float deltaTime ) -{ - m_flags |= SND_CHANGE_VOL; - if ( volumeTarget > 1.0 ) - volumeTarget = 1.0; - m_volume.SetTarget( volumeTarget, deltaTime ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Fade volume to zero AND SHUT DOWN THIS SOUND -// Input : deltaTime - seconds before done/shutdown -//----------------------------------------------------------------------------- -void CSoundPatch::FadeOut( float deltaTime, bool destroyOnFadeout ) -{ - ChangeVolume( 0, deltaTime ); - if ( !destroyOnFadeout ) - { - m_shutdownTime = g_pEffects->Time() + deltaTime; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Get the sound's current pitch -//----------------------------------------------------------------------------- -float CSoundPatch::GetPitch( void ) -{ - return m_pitch.Value(); -} - -//----------------------------------------------------------------------------- -// Purpose: Get the sound's current volume -//----------------------------------------------------------------------------- -float CSoundPatch::GetVolume( void ) -{ - return m_volume.Value(); -} - -//----------------------------------------------------------------------------- -// Returns the ent index -//----------------------------------------------------------------------------- -inline int CSoundPatch::EntIndex() const -{ - Assert( !m_hEnt.IsValid() || m_hEnt.Get() ); - return m_hEnt.Get() ? m_hEnt->entindex() : -1; -} - -//----------------------------------------------------------------------------- -// Purpose: SoundPatches take volumes between 0 & 1, and use that to multiply the sounds.txt specified volume. -// This function is an internal method of accessing the real volume passed into the engine (i.e. post multiply) -// Output : float -//----------------------------------------------------------------------------- -float CSoundPatch::GetVolumeForEngine( void ) -{ - return ( m_flScriptVolume * m_volume.Value() ); -} - -//----------------------------------------------------------------------------- -// Purpose: Stop the sound -//----------------------------------------------------------------------------- -void CSoundPatch::Shutdown( void ) -{ -// Msg( "Removing sound %s\n", m_pszSoundName ); - if ( m_isPlaying ) - { - int entIndex = EntIndex(); - Assert( entIndex >= 0 ); - // BUGBUG: Don't crash in release mode - if ( entIndex >= 0 ) - { - CBaseEntity::StopSound( entIndex, m_entityChannel, STRING( m_iszSoundName ) ); - } - m_isPlaying = false; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Update all envelopes and send appropriate data to the client -// Input : time - new global clock -// deltaTime - amount of time that has passed -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CSoundPatch::Update( float time, float deltaTime ) -{ - VPROF( "CSoundPatch::Update" ); - if ( m_shutdownTime && time > m_shutdownTime ) - { - Shutdown(); - return false; - } - - if ( EntIndex() < 0 ) - { - // FIXME: The pointer to this soundpatch is probably leaked since no entity is around to clean it up (ywb) - DevWarning( "CSoundPatch::Update: Removing CSoundPatch (%s) with NULL EHandle\n", STRING(m_iszSoundName) ); - return false; - } - - if ( m_pitch.ShouldUpdate() ) - { - m_pitch.Update( deltaTime ); - m_flags |= SND_CHANGE_PITCH; - } - else - { - m_flags &= ~SND_CHANGE_PITCH; - } - - if ( m_volume.ShouldUpdate() ) - { - m_volume.Update( deltaTime ); - m_flags |= SND_CHANGE_VOL; - } - else - { - m_flags &= ~SND_CHANGE_VOL; - } - - if ( m_flags && m_Filter.IsActive() ) - { - // SoundPatches take volumes between 0 & 1, and use that to multiply the sounds.txt specified volume. - // Because of this, we need to always set the SND_CHANGE_VOL flag when we emit sound, or it'll use the scriptfile's instead. - m_flags |= SND_CHANGE_VOL; - - EmitSound_t ep; - ep.m_nChannel = m_entityChannel; - ep.m_pSoundName = STRING(m_iszSoundName); - ep.m_flVolume = GetVolumeForEngine(); - ep.m_SoundLevel = m_soundlevel; - ep.m_nFlags = m_flags; - ep.m_nPitch = (int)m_pitch.Value(); - - CBaseEntity::EmitSound( m_Filter, EntIndex(), ep ); - - m_flags = 0; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Sound is going to start playing again, clear any shutdown time -//----------------------------------------------------------------------------- -void CSoundPatch::Reset( void ) -{ - m_shutdownTime = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: Start playing the sound - send updates to the client -//----------------------------------------------------------------------------- -void CSoundPatch::StartSound( float flStartTime ) -{ -// Msg( "Start sound %s\n", m_pszSoundName ); - m_flags = 0; - if ( m_Filter.IsActive() ) - { - EmitSound_t ep; - ep.m_nChannel = m_entityChannel; - ep.m_pSoundName = STRING(m_iszSoundName); - ep.m_flVolume = GetVolumeForEngine(); - ep.m_SoundLevel = m_soundlevel; - ep.m_nFlags = (SND_CHANGE_VOL | m_baseFlags); - ep.m_nPitch = (int)m_pitch.Value(); - ep.m_bEmitCloseCaption = false; - - if ( flStartTime ) - { - ep.m_flSoundTime = flStartTime; - } - - CBaseEntity::EmitSound( m_Filter, EntIndex(), ep ); - CBaseEntity::EmitCloseCaption( m_Filter, EntIndex(), STRING( m_iszSoundScriptName ), ep.m_UtlVecSoundOrigin, m_flCloseCaptionDuration, true ); - } - m_isPlaying = true; -} - - -//----------------------------------------------------------------------------- -// Purpose: resumes playing the sound on restore -//----------------------------------------------------------------------------- -void CSoundPatch::ResumeSound( void ) -{ - if ( IsPlaying() && m_Filter.IsActive() ) - { - if ( EntIndex() >= 0 ) - { - EmitSound_t ep; - ep.m_nChannel = m_entityChannel; - ep.m_pSoundName = STRING(m_iszSoundName); - ep.m_flVolume = GetVolumeForEngine(); - ep.m_SoundLevel = m_soundlevel; - ep.m_nFlags = (SND_CHANGE_VOL | SND_CHANGE_PITCH | m_baseFlags); - ep.m_nPitch = (int)m_pitch.Value(); - - CBaseEntity::EmitSound( m_Filter, EntIndex(), ep ); - } - else - { - // FIXME: Lost the entity on restore. It might have been suppressed by the save/restore system. - // This will probably leak the sound patch since there's no one to delete it, but the next - // call to CSoundPatch::Update should at least remove it from the list of sound patches. - DevWarning( "CSoundPatch::ResumeSound: Lost EHAndle on restore - destroy the sound patch in your entity's StopLoopingSounds! (%s)\n", STRING( m_iszSoundName ) ); - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: A new player's entered the game. See if we need to restart our sound. -//----------------------------------------------------------------------------- -void CSoundPatch::AddPlayerPost( CBasePlayer *pPlayer ) -{ - if ( m_Filter.IsActive() && m_Filter.AddRecipient(pPlayer) ) - { - // Alrighty, he's new. We need to restart our sound just to him. - // Create a new filter just to him. - CSingleUserRecipientFilter filter( pPlayer ); - - EmitSound_t ep; - ep.m_nChannel = m_entityChannel; - ep.m_pSoundName = STRING(m_iszSoundName); - ep.m_flVolume = GetVolumeForEngine(); - ep.m_SoundLevel = m_soundlevel; - ep.m_nFlags = (SND_CHANGE_VOL | m_baseFlags); - ep.m_nPitch = (int)m_pitch.Value(); - - CBaseEntity::EmitSound( filter, EntIndex(), ep ); - } -} - -// This is an entry in the command queue. It's used to queue up various pitch and volume changes -// so you can define an envelope without writing timing code in an entity. Existing queued commands -// can be deleted later if the envelope changes dynamically. -#include "tier0/memdbgoff.h" -struct SoundCommand_t -{ - SoundCommand_t( void ) { memset( this, 0, sizeof(*this) ); } - SoundCommand_t( CSoundPatch *pSound, float executeTime, soundcommands_t command, float deltaTime, float value ) : m_pPatch(pSound), m_time(executeTime), m_deltaTime(deltaTime), m_command(command), m_value(value) {} - - CSoundPatch *m_pPatch; - float m_time; - float m_deltaTime; - soundcommands_t m_command; - float m_value; - - SoundCommand_t *m_pNext; - - DECLARE_SIMPLE_DATADESC(); - DECLARE_FIXEDSIZE_ALLOCATOR(SoundCommand_t); -}; -#include "tier0/memdbgon.h" - -DEFINE_FIXEDSIZE_ALLOCATOR( SoundCommand_t, 32, CUtlMemoryPool::GROW_FAST ); - - -BEGIN_SIMPLE_DATADESC( SoundCommand_t ) - -// NOTE: This doesn't need to be saved, sound commands are saved right after the patch -// they are associated with -// DEFINE_FIELD( m_pPatch, FIELD_????? ) - DEFINE_FIELD( m_time, FIELD_TIME ), - DEFINE_FIELD( m_deltaTime, FIELD_FLOAT ), - DEFINE_FIELD( m_command, FIELD_INTEGER ), - DEFINE_FIELD( m_value, FIELD_FLOAT ), -// DEFINE_FIELD( m_pNext, FIELD_????? ) - -END_DATADESC() - -typedef SoundCommand_t *SOUNDCOMMANDPTR; - -bool SoundCommandLessFunc( const SOUNDCOMMANDPTR &lhs, const SOUNDCOMMANDPTR &rhs ) -{ - // NOTE: A greater time means "less" priority - return ( lhs->m_time > rhs->m_time ); -} - - -// This implements the sound controller -class CSoundControllerImp : public CSoundEnvelopeController, public CAutoGameSystemPerFrame -{ - //----------------------------------------------------------------------------- - // internal functions, private to this file - //----------------------------------------------------------------------------- -public: - CSoundControllerImp( void ) : CAutoGameSystemPerFrame( "CSoundControllerImp" ) - { - m_commandList.SetLessFunc( SoundCommandLessFunc ); - } - - void ProcessCommand( SoundCommand_t *pCmd ); - void RemoveFromList( CSoundPatch *pSound ); - void SaveSoundPatch( CSoundPatch *pSound, ISave *pSave ); - void RestoreSoundPatch( CSoundPatch **ppSound, IRestore *pRestore ); - - virtual void OnRestore(); - - //----------------------------------------------------------------------------- - // external interface functions (from CSoundEnvelopeController) - //----------------------------------------------------------------------------- -public: - - // Start this sound playing, or reset if already playing with new volume/pitch - void Play( CSoundPatch *pSound, float volume, float pitch, float flStartTime = 0 ); - void CommandAdd( CSoundPatch *pSound, float executeDeltaTime, soundcommands_t command, float commandTime, float commandValue ); - - void SystemReset( void ); - void SystemUpdate( void ); - void CommandClear( CSoundPatch *pSound ); - void Shutdown( CSoundPatch *pSound ); - - CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, const char *pSoundName ); - CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel, const char *pSoundName, - float attenuation ); - CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel, const char *pSoundName, - soundlevel_t soundlevel ); - CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, const EmitSound_t &es ); - void SoundDestroy( CSoundPatch *pSound ); - void SoundChangePitch( CSoundPatch *pSound, float pitchTarget, float deltaTime ); - void SoundChangeVolume( CSoundPatch *pSound, float volumeTarget, float deltaTime ); - void SoundFadeOut( CSoundPatch *pSound, float deltaTime, bool destroyOnFadeout ); - float SoundGetPitch( CSoundPatch *pSound ); - float SoundGetVolume( CSoundPatch *pSound ); - string_t SoundGetName( CSoundPatch *pSound ) { return pSound->GetName(); } - void SoundSetCloseCaptionDuration( CSoundPatch *pSound, float flDuration ) { pSound->SetCloseCaptionDuration(flDuration); } - - float SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopePoint_t *points, int numPoints ); - float SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopeDescription_t *envelope ); - - void CheckLoopingSoundsForPlayer( CBasePlayer *pPlayer ); - - // Inserts the command into the list, sorted by time - void CommandInsert( SoundCommand_t *pCommand ); - -#ifdef CLIENT_DLL - // CAutoClientSystem - virtual void Update( float frametime ) - { - SystemUpdate(); - } -#else - virtual void PreClientUpdate() - { - SystemUpdate(); - } -#endif - - virtual void LevelShutdownPreEntity() - { - SystemReset(); - } - -private: - CUtlVector m_soundList; - CUtlPriorityQueue m_commandList; - float m_flLastTime; -}; - -// Execute a command from the list -// currently only 3 commands -// UNDONE: Add start command? -void CSoundControllerImp::ProcessCommand( SoundCommand_t *pCmd ) -{ - switch( pCmd->m_command ) - { - case SOUNDCTRL_CHANGE_VOLUME: - pCmd->m_pPatch->ChangeVolume( pCmd->m_value, pCmd->m_deltaTime ); - break; - - case SOUNDCTRL_CHANGE_PITCH: - pCmd->m_pPatch->ChangePitch( pCmd->m_value, pCmd->m_deltaTime ); - break; - - case SOUNDCTRL_STOP: - pCmd->m_pPatch->Shutdown(); - break; - - case SOUNDCTRL_DESTROY: - RemoveFromList( pCmd->m_pPatch ); - delete pCmd->m_pPatch; - pCmd->m_pPatch = NULL; - break; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Remove this sound from the sound list & shutdown (not in external interface) -// Input : *pSound - patch to remove -//----------------------------------------------------------------------------- -void CSoundControllerImp::RemoveFromList( CSoundPatch *pSound ) -{ - m_soundList.FindAndRemove( pSound ); - pSound->Shutdown(); -} - - -//----------------------------------------------------------------------------- -// Start this sound playing, or reset if already playing with new volume/pitch -//----------------------------------------------------------------------------- -void CSoundControllerImp::Play( CSoundPatch *pSound, float volume, float pitch, float flStartTime ) -{ - // reset the vars - pSound->Reset(); - - pSound->ChangeVolume( volume, 0 ); - pSound->ChangePitch( pitch, 0 ); - - if ( pSound->IsPlaying() ) - { - // remove any previous commands in the queue - CommandClear( pSound ); - } - else - { - m_soundList.AddToTail( pSound ); - pSound->StartSound( flStartTime ); - } -} - - -//----------------------------------------------------------------------------- -// Inserts the command into the list, sorted by time -//----------------------------------------------------------------------------- -void CSoundControllerImp::CommandInsert( SoundCommand_t *pCommand ) -{ - m_commandList.Insert( pCommand ); -} - - -//----------------------------------------------------------------------------- -// Purpose: puts a command into the queue -// Input : *pSound - patch this command affects -// executeDeltaTime - relative time to execute this command -// command - command to execute (SOUNDCTRL_*) -// commandTime - commands have 2 parameters, a time and a value -// value - -// Output : void -//----------------------------------------------------------------------------- -void CSoundControllerImp::CommandAdd( CSoundPatch *pSound, float executeDeltaTime, soundcommands_t command, float commandTime, float commandValue ) -{ - SoundCommand_t *pCommand = new SoundCommand_t( pSound, g_pEffects->Time() + executeDeltaTime, command, commandTime, commandValue ); - CommandInsert( pCommand ); -} - -// Reset the whole system (level change, etc.) -void CSoundControllerImp::SystemReset( void ) -{ - for ( int i = m_soundList.Count()-1; i >=0; i-- ) - { - CSoundPatch *pNode = m_soundList[i]; - - // shutdown all active sounds - pNode->Shutdown(); - } - - // clear the list - m_soundList.Purge(); - - // clear the command queue - m_commandList.RemoveAll(); -} - - -//----------------------------------------------------------------------------- -// Purpose: Update the active sounds, dequeue any events and move the ramps -//----------------------------------------------------------------------------- -void CSoundControllerImp::SystemUpdate( void ) -{ - VPROF( "CSoundControllerImp::SystemUpdate" ); - float time = g_pEffects->Time(); - float deltaTime = time - m_flLastTime; - - // handle clock resets - if ( deltaTime < 0 ) - deltaTime = 0; - - m_flLastTime = time; - - { - VPROF( "CSoundControllerImp::SystemUpdate:processcommandlist" ); - while ( m_commandList.Count() ) - { - SoundCommand_t *pCmd = m_commandList.ElementAtHead(); - // Commands are sorted by time. - // process any that should occur by the current time - if ( time >= pCmd->m_time ) - { - m_commandList.RemoveAtHead(); - ProcessCommand( pCmd ); - delete pCmd; - } - else - { - break; - } - } - } - - // NOTE: Because this loop goes from the end to the beginning - // we can fast remove inside it without breaking the indexing - { - VPROF( "CSoundControllerImp::SystemUpdate:removesounds" ); - for ( int i = m_soundList.Count()-1; i >=0; i-- ) - { - CSoundPatch *pNode = m_soundList[i]; - if ( !pNode->Update( time, deltaTime ) ) - { - pNode->Reset(); - m_soundList.FastRemove( i ); - } - } - } -} - -// Remove any envelope commands from the list (dynamically changing envelope) -void CSoundControllerImp::CommandClear( CSoundPatch *pSound ) -{ - for ( int i = m_commandList.Count()-1; i >= 0; i-- ) - { - SoundCommand_t *pCmd = m_commandList.Element( i ); - if ( pCmd->m_pPatch == pSound ) - { - m_commandList.RemoveAt(i); - delete pCmd; - } - } -} - - -//----------------------------------------------------------------------------- -// Saves the sound patch + associated commands -//----------------------------------------------------------------------------- -void CSoundControllerImp::SaveSoundPatch( CSoundPatch *pSoundPatch, ISave *pSave ) -{ - int i; - - // Write out the sound patch - pSave->StartBlock(); - pSave->WriteAll( pSoundPatch ); - pSave->EndBlock(); - - // Count the number of commands that refer to the sound patch - int nCount = 0; - for ( i = m_commandList.Count()-1; i >= 0; i-- ) - { - SoundCommand_t *pCmd = m_commandList.Element( i ); - if ( pCmd->m_pPatch == pSoundPatch ) - { - nCount++; - } - } - - // Write out the number of commands, followed by each command itself - pSave->StartBlock(); - pSave->WriteInt( &nCount ); - - for ( i = m_commandList.Count()-1; i >= 0; i-- ) - { - SoundCommand_t *pCmd = m_commandList.Element( i ); - if ( pCmd->m_pPatch == pSoundPatch ) - { - pSave->StartBlock(); - pSave->WriteAll( pCmd ); - pSave->EndBlock(); - } - } - - pSave->EndBlock(); -} - -//----------------------------------------------------------------------------- -// Restores the sound patch + associated commands -//----------------------------------------------------------------------------- -void CSoundControllerImp::RestoreSoundPatch( CSoundPatch **ppSoundPatch, IRestore *pRestore ) -{ - CSoundPatch *pPatch = new CSoundPatch; - - // read the sound patch data from the memory block - pRestore->StartBlock(); - bool bOk = ( pRestore->ReadAll( pPatch ) != 0 ); - pRestore->EndBlock(); - bOk = (bOk && pPatch->IsPlaying()) ? true : false; - - if (bOk) - { - m_soundList.AddToTail( pPatch ); - } - - // Count the number of commands that refer to the sound patch - pRestore->StartBlock(); - - if ( bOk ) - { - int nCount; - pRestore->ReadInt( &nCount ); - while ( --nCount >= 0 ) - { - SoundCommand_t *pCommand = new SoundCommand_t; - - pRestore->StartBlock(); - if ( pRestore->ReadAll( pCommand ) ) - { - pCommand->m_pPatch = pPatch; - CommandInsert( pCommand ); - } - - pRestore->EndBlock(); - } - } - - pRestore->EndBlock(); - *ppSoundPatch = pPatch; -} - - -//----------------------------------------------------------------------------- -// Purpose: immediately stop playing this sound -// Input : *pSound - Patch to shut down -//----------------------------------------------------------------------------- -void CSoundControllerImp::Shutdown( CSoundPatch *pSound ) -{ - if ( !pSound ) - return; - - pSound->Shutdown(); - CommandClear( pSound ); - RemoveFromList( pSound ); -} - -CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, const char *pSoundName ) -{ -#ifdef CLIENT_DLL - if ( GameRules() ) - { - pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", pSoundName ); - } -#endif - - CSoundPatch *pSound = new CSoundPatch; - - // FIXME: This is done so we don't have to futz with the public interface - EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; - pSound->Init( &filter, hEnt.Get(), CHAN_AUTO, pSoundName, SNDLVL_NORM ); - - return pSound; -} - -CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel, - const char *pSoundName, float attenuation ) -{ -#ifdef CLIENT_DLL - if ( GameRules() ) - { - pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", pSoundName ); - } -#endif - - CSoundPatch *pSound = new CSoundPatch; - EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; - pSound->Init( &filter, hEnt.Get(), channel, pSoundName, ATTN_TO_SNDLVL( attenuation ) ); - - return pSound; -} - -CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel, - const char *pSoundName, soundlevel_t soundlevel ) -{ -#ifdef CLIENT_DLL - if ( GameRules() ) - { - pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", pSoundName ); - } -#endif - - CSoundPatch *pSound = new CSoundPatch; - EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; - pSound->Init( &filter, hEnt.Get(), channel, pSoundName, soundlevel ); - - return pSound; -} - -CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, const EmitSound_t &es ) -{ - CSoundPatch *pSound = new CSoundPatch; - - // FIXME: This is done so we don't have to futz with the public interface - EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; - pSound->Init( &filter, hEnt.Get(), es.m_nChannel, es.m_pSoundName, es.m_SoundLevel ); - pSound->ChangeVolume( es.m_flVolume, 0 ); - pSound->ChangePitch( es.m_nPitch, 0 ); - - if ( es.m_nFlags & SND_SHOULDPAUSE ) - { - pSound->SetBaseFlags( SND_SHOULDPAUSE ); - } - - return pSound; -} - -void CSoundControllerImp::SoundDestroy( CSoundPatch *pSound ) -{ - if ( !pSound ) - return; - - Shutdown( pSound ); - delete pSound; -} - -void CSoundControllerImp::SoundChangePitch( CSoundPatch *pSound, float pitchTarget, float deltaTime ) -{ - pSound->ChangePitch( pitchTarget, deltaTime ); -} - - -void CSoundControllerImp::SoundChangeVolume( CSoundPatch *pSound, float volumeTarget, float deltaTime ) -{ - pSound->ChangeVolume( volumeTarget, deltaTime ); -} - -float CSoundControllerImp::SoundGetPitch( CSoundPatch *pSound ) -{ - return pSound->GetPitch(); -} - -float CSoundControllerImp::SoundGetVolume( CSoundPatch *pSound ) -{ - return pSound->GetVolume(); -} - -void CSoundControllerImp::SoundFadeOut( CSoundPatch *pSound, float deltaTime, bool destroyOnFadeout ) -{ - if ( destroyOnFadeout && (deltaTime == 0.0f) ) - { - SoundDestroy( pSound ); - return; - } - - pSound->FadeOut( deltaTime, destroyOnFadeout ); - if ( destroyOnFadeout ) - { - CommandAdd( pSound, deltaTime, SOUNDCTRL_DESTROY, 0.0f, 0.0f ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Queue a list of envelope points into a sound patch's event list -// Input : *pSound - The sound patch to be operated on -// soundCommand - Type of operation the envelope describes -// *points - List of enevelope points -// numPoints - Number of points provided -// Output : float - Returns the total duration of the envelope -//----------------------------------------------------------------------------- -float CSoundControllerImp::SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopePoint_t *points, int numPoints ) -{ - float amplitude = 0.0f; - float duration = 0.0f; - float totalDuration = 0.0f; - - Assert( points ); - - // Clear out all previously acting commands - CommandClear( pSound ); - - // Evaluate and queue all points - for ( int i = 0; i < numPoints; i++ ) - { - // See if we're keeping our last amplitude for this new point - if ( ( points[i].amplitudeMin != -1.0f ) || ( points[i].amplitudeMax != -1.0f ) ) - { - amplitude = random->RandomFloat( points[i].amplitudeMin, points[i].amplitudeMax ); - } - else if ( i == 0 ) - { - // Can't do this on the first entry - Msg( "Invalid starting amplitude value in envelope! (Cannot be -1)\n" ); - } - - // See if we're keeping our last duration for this new point - if ( ( points[i].durationMin != -1.0f ) || ( points[i].durationMax != -1.0f ) ) - { - duration = random->RandomFloat( points[i].durationMin, points[i].durationMax ); - //duration = points[i].durationMin; - } - else if ( i == 0 ) - { - // Can't do this on the first entry - Msg( "Invalid starting duration value in envelope! (Cannot be -1)\n" ); - } - - // Queue the command - CommandAdd( pSound, totalDuration, soundCommand, duration, amplitude ); - - // Tack this command's duration onto the running duration - totalDuration += duration; - } - - return totalDuration; -} - -//----------------------------------------------------------------------------- -// Purpose: Queue a list of envelope points into a sound patch's event list -// Input : *pSound - The sound patch to be operated on -// soundCommand - Type of operation the envelope describes -// *envelope - The envelope description to be queued -// Output : float - Returns the total duration of the envelope -//----------------------------------------------------------------------------- -float CSoundControllerImp::SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopeDescription_t *envelope ) -{ - return SoundPlayEnvelope( pSound, soundCommand, envelope->pPoints, envelope->nNumPoints ); -} - -//----------------------------------------------------------------------------- -// Purpose: Looping sounds are often started in entity spawn/activate functions. -// In singleplayer, the player's not ready to receive sounds then, so restart -// and SoundPatches that are active and have no receivers. -//----------------------------------------------------------------------------- -void CSoundControllerImp::CheckLoopingSoundsForPlayer( CBasePlayer *pPlayer ) -{ - for ( int i = m_soundList.Count()-1; i >=0; i-- ) - { - CSoundPatch *pNode = m_soundList[i]; - pNode->AddPlayerPost( pPlayer ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Resumes saved soundpatches -//----------------------------------------------------------------------------- -void CSoundControllerImp::OnRestore() -{ - for ( int i = m_soundList.Count()-1; i >=0; i-- ) - { - CSoundPatch *pNode = m_soundList[i]; - if ( pNode && pNode->IsPlaying() ) - { - pNode->ResumeSound(); - } - } -} - - -//----------------------------------------------------------------------------- -// Singleton accessors -//----------------------------------------------------------------------------- -static CSoundControllerImp g_Controller; -CSoundEnvelopeController &CSoundEnvelopeController::GetController( void ) -{ - return g_Controller; -} - - -//----------------------------------------------------------------------------- -// Queues up sound patches to save/load -//----------------------------------------------------------------------------- -class CSoundPatchSaveRestoreOps : public CClassPtrSaveRestoreOps -{ -public: - virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) - { - pSave->StartBlock(); - - int nSoundPatchCount = fieldInfo.pTypeDesc->fieldSize; - CSoundPatch **ppSoundPatch = (CSoundPatch**)fieldInfo.pField; - while ( --nSoundPatchCount >= 0 ) - { - // Write out commands associated with this sound patch - g_Controller.SaveSoundPatch( *ppSoundPatch, pSave ); - ++ppSoundPatch; - } - - pSave->EndBlock(); - } - - virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) - { - pRestore->StartBlock(); - - int nSoundPatchCount = fieldInfo.pTypeDesc->fieldSize; - CSoundPatch **ppSoundPatch = (CSoundPatch**)fieldInfo.pField; - while ( --nSoundPatchCount >= 0 ) - { - // Write out commands associated with this sound patch - g_Controller.RestoreSoundPatch( ppSoundPatch, pRestore ); - ++ppSoundPatch; - } - - pRestore->EndBlock(); - } -}; - -static CSoundPatchSaveRestoreOps s_SoundPatchSaveRestoreOps; -ISaveRestoreOps *GetSoundSaveRestoreOps( ) -{ - return &s_SoundPatchSaveRestoreOps; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "sharedInterface.h" +#include "soundenvelope.h" +#include "engine/IEngineSound.h" +#include "IEffects.h" +#include "isaverestore.h" +#include "saverestore_utlvector.h" +#include "gamestringpool.h" +#include "igamesystem.h" +#include "utlpriorityqueue.h" +#include "mempool.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "tier0/vprof.h" +#include "gamerules.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static ConVar soundpatch_captionlength( "soundpatch_captionlength", "2.0", FCVAR_REPLICATED, "How long looping soundpatch captions should display for." ); + +// Envelope +// This is a class that controls a ramp for a sound (pitch / volume / etc) +class CSoundEnvelope +{ +public: + DECLARE_SIMPLE_DATADESC(); + + CSoundEnvelope() + { + m_current = 0.0f; + m_target = 0.0f; + m_rate = 0.0f; + m_forceupdate = false; + } + + void SetTarget( float target, float deltaTime ); + void SetValue( float value ); + bool ShouldUpdate( void ); + void Update( float time ); + inline float Value( void ) { return m_current; } + +private: + float m_current; + float m_target; + float m_rate; + bool m_forceupdate; +}; + + +BEGIN_SIMPLE_DATADESC( CSoundEnvelope ) + DEFINE_FIELD( m_current, FIELD_FLOAT ), + DEFINE_FIELD( m_target, FIELD_FLOAT ), + DEFINE_FIELD( m_rate, FIELD_FLOAT ), + DEFINE_FIELD( m_forceupdate, FIELD_BOOLEAN ), +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: Set the new target value for this ramp. Reach this target in deltaTime +// seconds from now +// Input : target - new target value +// deltaTime - time to reach target +//----------------------------------------------------------------------------- +void CSoundEnvelope::SetTarget( float target, float deltaTime ) +{ + float deltaValue = target - m_current; + + if ( deltaValue && deltaTime > 0 ) + { + m_target = target; + m_rate = MAX( 0.1, fabs(deltaValue / deltaTime) ); + } + else + { + if ( target != m_current ) + { + m_forceupdate = true; + } + + SetValue( target ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Instantaneously set the value of this ramp +// Input : value - new value +//----------------------------------------------------------------------------- +void CSoundEnvelope::SetValue( float value ) +{ + if ( m_target != value ) + { + m_forceupdate = true; + } + + m_current = m_target = value; + m_rate = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Check to see if I need to update this envelope +// Output : Returns true if this envelope is changing +//----------------------------------------------------------------------------- +bool CSoundEnvelope::ShouldUpdate( void ) +{ + if ( m_forceupdate ) + { + m_forceupdate = false; + return true; + } + + if ( m_current != m_target ) + { + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Update the envelope for the current frame time +// Input : time - amount of time that has passed +//----------------------------------------------------------------------------- +void CSoundEnvelope::Update( float deltaTime ) +{ + m_current = Approach( m_target, m_current, m_rate * deltaTime ); +} + +class CCopyRecipientFilter : public IRecipientFilter +{ +public: + DECLARE_SIMPLE_DATADESC(); + + CCopyRecipientFilter() : m_Flags(0) {} + + void Init( IRecipientFilter *pSrc ) + { + m_Flags = FLAG_ACTIVE; + if ( pSrc->IsReliable() ) + { + m_Flags |= FLAG_RELIABLE; + } + + if ( pSrc->IsInitMessage() ) + { + m_Flags |= FLAG_INIT_MESSAGE; + } + + for ( int i = 0; i < pSrc->GetRecipientCount(); i++ ) + { + int index = pSrc->GetRecipientIndex( i ); + + if ( index >= 0 ) + m_Recipients.AddToTail( index ); + } + } + + bool IsActive() const + { + return (m_Flags & FLAG_ACTIVE) != 0; + } + + virtual bool IsReliable( void ) const + { + return (m_Flags & FLAG_RELIABLE) != 0; + } + + virtual int GetRecipientCount( void ) const + { + return m_Recipients.Count(); + } + + virtual int GetRecipientIndex( int slot ) const + { + return m_Recipients[ slot ]; + } + + virtual bool IsInitMessage( void ) const + { + return (m_Flags & FLAG_INIT_MESSAGE) != 0; + } + + virtual bool AddRecipient( CBasePlayer *player ) + { + Assert( player ); + + int index = player->entindex(); + + if ( index < 0 ) + return false; + + // Already in list + if ( m_Recipients.Find( index ) != m_Recipients.InvalidIndex() ) + return false; + + m_Recipients.AddToTail( index ); + return true; + } + +private: + enum + { + FLAG_ACTIVE = 0x1, + FLAG_RELIABLE = 0x2, + FLAG_INIT_MESSAGE = 0x4, + }; + + int m_Flags; + CUtlVector< int > m_Recipients; +}; + +BEGIN_SIMPLE_DATADESC( CCopyRecipientFilter ) + + DEFINE_FIELD( m_Flags, FIELD_INTEGER ), + DEFINE_UTLVECTOR( m_Recipients, FIELD_INTEGER ), + +END_DATADESC() + + +#include "tier0/memdbgoff.h" +// This is the a basic sound controller, a "patch" +// It has envelopes for pitch and volume and can manage state changes to those +class CSoundPatch +{ +public: + DECLARE_SIMPLE_DATADESC(); + + static int g_SoundPatchCount; + CSoundPatch() + { + g_SoundPatchCount++; + m_iszSoundName = NULL_STRING; + m_iszSoundScriptName = NULL_STRING; + m_flCloseCaptionDuration = soundpatch_captionlength.GetFloat(); + } + ~CSoundPatch() + { + g_SoundPatchCount--; + } + + void Init( IRecipientFilter *pFilter, CBaseEntity *pEnt, int channel, const char *pSoundName, + soundlevel_t iSoundLevel ); + void ChangePitch( float pitchTarget, float deltaTime ); + void ChangeVolume( float volumeTarget, float deltaTime ); + void FadeOut( float deltaTime, bool destroyOnFadeout ); + float GetPitch( void ); + float GetVolume( void ); + string_t GetName() { return m_iszSoundName; }; + string_t GetScriptName() { return m_iszSoundScriptName; } + // UNDONE: Don't call this, use the controller to shut down + void Shutdown( void ); + bool Update( float time, float deltaTime ); + void Reset( void ); + void StartSound( float flStartTime = 0 ); + void ResumeSound( void ); + int IsPlaying( void ) { return m_isPlaying; } + void AddPlayerPost( CBasePlayer *pPlayer ); + void SetCloseCaptionDuration( float flDuration ) { m_flCloseCaptionDuration = flDuration; } + + void SetBaseFlags( int iFlags ) { m_baseFlags = iFlags; } + + // Returns the ent index + int EntIndex() const; + +private: + // SoundPatches take volumes between 0 & 1, and use that to multiply the sounds.txt specified volume. + // This function is an internal method of accessing the real volume passed into the engine (i.e. post multiply) + float GetVolumeForEngine( void ); + +private: + CSoundEnvelope m_pitch; + CSoundEnvelope m_volume; + + soundlevel_t m_soundlevel; + float m_shutdownTime; + float m_flLastTime; + string_t m_iszSoundName; + string_t m_iszSoundScriptName; + EHANDLE m_hEnt; + int m_entityChannel; + int m_flags; + int m_baseFlags; + int m_isPlaying; + float m_flScriptVolume; // Volume for this sound in sounds.txt + CCopyRecipientFilter m_Filter; + + float m_flCloseCaptionDuration; + +#ifdef _DEBUG + // Used to get the classname of the entity associated with the sound + string_t m_iszClassName; +#endif + + DECLARE_FIXEDSIZE_ALLOCATOR(CSoundPatch); +}; +#include "tier0/memdbgon.h" + +int CSoundPatch::g_SoundPatchCount = 0; + +CON_COMMAND( report_soundpatch, "reports sound patch count" ) +{ +#ifndef CLIENT_DLL + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; +#endif + + Msg("Current sound patches: %d\n", CSoundPatch::g_SoundPatchCount ); +} +DEFINE_FIXEDSIZE_ALLOCATOR( CSoundPatch, 64, CUtlMemoryPool::GROW_FAST ); + +BEGIN_SIMPLE_DATADESC( CSoundPatch ) + + DEFINE_EMBEDDED( m_pitch ), + DEFINE_EMBEDDED( m_volume ), + DEFINE_FIELD( m_soundlevel, FIELD_INTEGER ), + DEFINE_FIELD( m_shutdownTime, FIELD_TIME ), + DEFINE_FIELD( m_flLastTime, FIELD_TIME ), + DEFINE_FIELD( m_iszSoundName, FIELD_STRING ), + DEFINE_FIELD( m_iszSoundScriptName, FIELD_STRING ), + DEFINE_FIELD( m_hEnt, FIELD_EHANDLE ), + DEFINE_FIELD( m_entityChannel, FIELD_INTEGER ), + DEFINE_FIELD( m_flags, FIELD_INTEGER ), + DEFINE_FIELD( m_baseFlags, FIELD_INTEGER ), + DEFINE_FIELD( m_isPlaying, FIELD_INTEGER ), + DEFINE_FIELD( m_flScriptVolume, FIELD_FLOAT ), + DEFINE_EMBEDDED( m_Filter ), + DEFINE_FIELD( m_flCloseCaptionDuration, FIELD_FLOAT ), + + // Not saved, it's debug only +// DEFINE_FIELD( m_iszClassName, FIELD_STRING ), + +END_DATADESC() + + + +//----------------------------------------------------------------------------- +// Purpose: Setup the patch +// Input : nEntIndex - index of the edict that owns the sound channel +// channel - This is a sound channel (CHAN_ITEM, CHAN_STATIC) +// *pSoundName - sound script string name +// attenuation - attenuation of this sound (not animated) +//----------------------------------------------------------------------------- +void CSoundPatch::Init( IRecipientFilter *pFilter, CBaseEntity *pEnt, int channel, const char *pSoundName, + soundlevel_t soundlevel ) +{ + m_hEnt = pEnt; + m_entityChannel = channel; + // Get the volume from the script + CSoundParameters params; + if ( !Q_stristr( pSoundName, ".wav" ) && !Q_stristr( pSoundName, ".mp3" ) && + CBaseEntity::GetParametersForSound( pSoundName, params, NULL ) ) + { + m_flScriptVolume = params.volume; + // This has to be the actual .wav because rndwave would cause a bunch of new .wavs to play... bad... + // e.g., when you pitch shift it would start a different wav instead. + + m_iszSoundScriptName = AllocPooledString( pSoundName ); + + pSoundName = params.soundname; + m_soundlevel = params.soundlevel; + + m_entityChannel = params.channel; + } + else + { + + m_iszSoundScriptName = AllocPooledString( pSoundName ); + + m_flScriptVolume = 1.0; + m_soundlevel = soundlevel; + } + + m_iszSoundName = AllocPooledString( pSoundName ); + m_volume.SetValue( 0 ); + m_pitch.SetValue( 0 ); + m_isPlaying = false; + m_shutdownTime = 0; + m_flLastTime = 0; + m_Filter.Init( pFilter ); + m_baseFlags = 0; + +#ifdef _DEBUG + if ( pEnt ) + { + m_iszClassName = AllocPooledString( pEnt->GetClassname() ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Ramps the pitch to a new value +// Input : pitchTarget - new value +// deltaTime - seconds to reach the value +//----------------------------------------------------------------------------- +void CSoundPatch::ChangePitch( float pitchTarget, float deltaTime ) +{ + m_flags |= SND_CHANGE_PITCH; + m_pitch.SetTarget( pitchTarget, deltaTime ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Ramps the volume to a new value +// Input : volumeTarget - new volume +// deltaTime - seconds to reach the new volume +//----------------------------------------------------------------------------- +void CSoundPatch::ChangeVolume( float volumeTarget, float deltaTime ) +{ + m_flags |= SND_CHANGE_VOL; + if ( volumeTarget > 1.0 ) + volumeTarget = 1.0; + m_volume.SetTarget( volumeTarget, deltaTime ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Fade volume to zero AND SHUT DOWN THIS SOUND +// Input : deltaTime - seconds before done/shutdown +//----------------------------------------------------------------------------- +void CSoundPatch::FadeOut( float deltaTime, bool destroyOnFadeout ) +{ + ChangeVolume( 0, deltaTime ); + if ( !destroyOnFadeout ) + { + m_shutdownTime = g_pEffects->Time() + deltaTime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get the sound's current pitch +//----------------------------------------------------------------------------- +float CSoundPatch::GetPitch( void ) +{ + return m_pitch.Value(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the sound's current volume +//----------------------------------------------------------------------------- +float CSoundPatch::GetVolume( void ) +{ + return m_volume.Value(); +} + +//----------------------------------------------------------------------------- +// Returns the ent index +//----------------------------------------------------------------------------- +inline int CSoundPatch::EntIndex() const +{ + Assert( !m_hEnt.IsValid() || m_hEnt.Get() ); + return m_hEnt.Get() ? m_hEnt->entindex() : -1; +} + +//----------------------------------------------------------------------------- +// Purpose: SoundPatches take volumes between 0 & 1, and use that to multiply the sounds.txt specified volume. +// This function is an internal method of accessing the real volume passed into the engine (i.e. post multiply) +// Output : float +//----------------------------------------------------------------------------- +float CSoundPatch::GetVolumeForEngine( void ) +{ + return ( m_flScriptVolume * m_volume.Value() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Stop the sound +//----------------------------------------------------------------------------- +void CSoundPatch::Shutdown( void ) +{ +// Msg( "Removing sound %s\n", m_pszSoundName ); + if ( m_isPlaying ) + { + int entIndex = EntIndex(); + Assert( entIndex >= 0 ); + // BUGBUG: Don't crash in release mode + if ( entIndex >= 0 ) + { + CBaseEntity::StopSound( entIndex, m_entityChannel, STRING( m_iszSoundName ) ); + } + m_isPlaying = false; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Update all envelopes and send appropriate data to the client +// Input : time - new global clock +// deltaTime - amount of time that has passed +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CSoundPatch::Update( float time, float deltaTime ) +{ + VPROF( "CSoundPatch::Update" ); + if ( m_shutdownTime && time > m_shutdownTime ) + { + Shutdown(); + return false; + } + + if ( EntIndex() < 0 ) + { + // FIXME: The pointer to this soundpatch is probably leaked since no entity is around to clean it up (ywb) + DevWarning( "CSoundPatch::Update: Removing CSoundPatch (%s) with NULL EHandle\n", STRING(m_iszSoundName) ); + return false; + } + + if ( m_pitch.ShouldUpdate() ) + { + m_pitch.Update( deltaTime ); + m_flags |= SND_CHANGE_PITCH; + } + else + { + m_flags &= ~SND_CHANGE_PITCH; + } + + if ( m_volume.ShouldUpdate() ) + { + m_volume.Update( deltaTime ); + m_flags |= SND_CHANGE_VOL; + } + else + { + m_flags &= ~SND_CHANGE_VOL; + } + + if ( m_flags && m_Filter.IsActive() ) + { + // SoundPatches take volumes between 0 & 1, and use that to multiply the sounds.txt specified volume. + // Because of this, we need to always set the SND_CHANGE_VOL flag when we emit sound, or it'll use the scriptfile's instead. + m_flags |= SND_CHANGE_VOL; + + EmitSound_t ep; + ep.m_nChannel = m_entityChannel; + ep.m_pSoundName = STRING(m_iszSoundName); + ep.m_flVolume = GetVolumeForEngine(); + ep.m_SoundLevel = m_soundlevel; + ep.m_nFlags = m_flags; + ep.m_nPitch = (int)m_pitch.Value(); + + CBaseEntity::EmitSound( m_Filter, EntIndex(), ep ); + + m_flags = 0; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Sound is going to start playing again, clear any shutdown time +//----------------------------------------------------------------------------- +void CSoundPatch::Reset( void ) +{ + m_shutdownTime = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Start playing the sound - send updates to the client +//----------------------------------------------------------------------------- +void CSoundPatch::StartSound( float flStartTime ) +{ +// Msg( "Start sound %s\n", m_pszSoundName ); + m_flags = 0; + if ( m_Filter.IsActive() ) + { + EmitSound_t ep; + ep.m_nChannel = m_entityChannel; + ep.m_pSoundName = STRING(m_iszSoundName); + ep.m_flVolume = GetVolumeForEngine(); + ep.m_SoundLevel = m_soundlevel; + ep.m_nFlags = (SND_CHANGE_VOL | m_baseFlags); + ep.m_nPitch = (int)m_pitch.Value(); + ep.m_bEmitCloseCaption = false; + + if ( flStartTime ) + { + ep.m_flSoundTime = flStartTime; + } + + CBaseEntity::EmitSound( m_Filter, EntIndex(), ep ); + CBaseEntity::EmitCloseCaption( m_Filter, EntIndex(), STRING( m_iszSoundScriptName ), ep.m_UtlVecSoundOrigin, m_flCloseCaptionDuration, true ); + } + m_isPlaying = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: resumes playing the sound on restore +//----------------------------------------------------------------------------- +void CSoundPatch::ResumeSound( void ) +{ + if ( IsPlaying() && m_Filter.IsActive() ) + { + if ( EntIndex() >= 0 ) + { + EmitSound_t ep; + ep.m_nChannel = m_entityChannel; + ep.m_pSoundName = STRING(m_iszSoundName); + ep.m_flVolume = GetVolumeForEngine(); + ep.m_SoundLevel = m_soundlevel; + ep.m_nFlags = (SND_CHANGE_VOL | SND_CHANGE_PITCH | m_baseFlags); + ep.m_nPitch = (int)m_pitch.Value(); + + CBaseEntity::EmitSound( m_Filter, EntIndex(), ep ); + } + else + { + // FIXME: Lost the entity on restore. It might have been suppressed by the save/restore system. + // This will probably leak the sound patch since there's no one to delete it, but the next + // call to CSoundPatch::Update should at least remove it from the list of sound patches. + DevWarning( "CSoundPatch::ResumeSound: Lost EHAndle on restore - destroy the sound patch in your entity's StopLoopingSounds! (%s)\n", STRING( m_iszSoundName ) ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: A new player's entered the game. See if we need to restart our sound. +//----------------------------------------------------------------------------- +void CSoundPatch::AddPlayerPost( CBasePlayer *pPlayer ) +{ + if ( m_Filter.IsActive() && m_Filter.AddRecipient(pPlayer) ) + { + // Alrighty, he's new. We need to restart our sound just to him. + // Create a new filter just to him. + CSingleUserRecipientFilter filter( pPlayer ); + + EmitSound_t ep; + ep.m_nChannel = m_entityChannel; + ep.m_pSoundName = STRING(m_iszSoundName); + ep.m_flVolume = GetVolumeForEngine(); + ep.m_SoundLevel = m_soundlevel; + ep.m_nFlags = (SND_CHANGE_VOL | m_baseFlags); + ep.m_nPitch = (int)m_pitch.Value(); + + CBaseEntity::EmitSound( filter, EntIndex(), ep ); + } +} + +// This is an entry in the command queue. It's used to queue up various pitch and volume changes +// so you can define an envelope without writing timing code in an entity. Existing queued commands +// can be deleted later if the envelope changes dynamically. +#include "tier0/memdbgoff.h" +struct SoundCommand_t +{ + SoundCommand_t( void ) { memset( this, 0, sizeof(*this) ); } + SoundCommand_t( CSoundPatch *pSound, float executeTime, soundcommands_t command, float deltaTime, float value ) : m_pPatch(pSound), m_time(executeTime), m_deltaTime(deltaTime), m_command(command), m_value(value) {} + + CSoundPatch *m_pPatch; + float m_time; + float m_deltaTime; + soundcommands_t m_command; + float m_value; + + SoundCommand_t *m_pNext; + + DECLARE_SIMPLE_DATADESC(); + DECLARE_FIXEDSIZE_ALLOCATOR(SoundCommand_t); +}; +#include "tier0/memdbgon.h" + +DEFINE_FIXEDSIZE_ALLOCATOR( SoundCommand_t, 32, CUtlMemoryPool::GROW_FAST ); + + +BEGIN_SIMPLE_DATADESC( SoundCommand_t ) + +// NOTE: This doesn't need to be saved, sound commands are saved right after the patch +// they are associated with +// DEFINE_FIELD( m_pPatch, FIELD_????? ) + DEFINE_FIELD( m_time, FIELD_TIME ), + DEFINE_FIELD( m_deltaTime, FIELD_FLOAT ), + DEFINE_FIELD( m_command, FIELD_INTEGER ), + DEFINE_FIELD( m_value, FIELD_FLOAT ), +// DEFINE_FIELD( m_pNext, FIELD_????? ) + +END_DATADESC() + +typedef SoundCommand_t *SOUNDCOMMANDPTR; + +bool SoundCommandLessFunc( const SOUNDCOMMANDPTR &lhs, const SOUNDCOMMANDPTR &rhs ) +{ + // NOTE: A greater time means "less" priority + return ( lhs->m_time > rhs->m_time ); +} + + +// This implements the sound controller +class CSoundControllerImp : public CSoundEnvelopeController, public CAutoGameSystemPerFrame +{ + //----------------------------------------------------------------------------- + // internal functions, private to this file + //----------------------------------------------------------------------------- +public: + CSoundControllerImp( void ) : CAutoGameSystemPerFrame( "CSoundControllerImp" ) + { + m_commandList.SetLessFunc( SoundCommandLessFunc ); + } + + void ProcessCommand( SoundCommand_t *pCmd ); + void RemoveFromList( CSoundPatch *pSound ); + void SaveSoundPatch( CSoundPatch *pSound, ISave *pSave ); + void RestoreSoundPatch( CSoundPatch **ppSound, IRestore *pRestore ); + + virtual void OnRestore(); + + //----------------------------------------------------------------------------- + // external interface functions (from CSoundEnvelopeController) + //----------------------------------------------------------------------------- +public: + + // Start this sound playing, or reset if already playing with new volume/pitch + void Play( CSoundPatch *pSound, float volume, float pitch, float flStartTime = 0 ); + void CommandAdd( CSoundPatch *pSound, float executeDeltaTime, soundcommands_t command, float commandTime, float commandValue ); + + void SystemReset( void ); + void SystemUpdate( void ); + void CommandClear( CSoundPatch *pSound ); + void Shutdown( CSoundPatch *pSound ); + + CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, const char *pSoundName ); + CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel, const char *pSoundName, + float attenuation ); + CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel, const char *pSoundName, + soundlevel_t soundlevel ); + CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, const EmitSound_t &es ); + void SoundDestroy( CSoundPatch *pSound ); + void SoundChangePitch( CSoundPatch *pSound, float pitchTarget, float deltaTime ); + void SoundChangeVolume( CSoundPatch *pSound, float volumeTarget, float deltaTime ); + void SoundFadeOut( CSoundPatch *pSound, float deltaTime, bool destroyOnFadeout ); + float SoundGetPitch( CSoundPatch *pSound ); + float SoundGetVolume( CSoundPatch *pSound ); + string_t SoundGetName( CSoundPatch *pSound ) { return pSound->GetName(); } + void SoundSetCloseCaptionDuration( CSoundPatch *pSound, float flDuration ) { pSound->SetCloseCaptionDuration(flDuration); } + + float SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopePoint_t *points, int numPoints ); + float SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopeDescription_t *envelope ); + + void CheckLoopingSoundsForPlayer( CBasePlayer *pPlayer ); + + // Inserts the command into the list, sorted by time + void CommandInsert( SoundCommand_t *pCommand ); + +#ifdef CLIENT_DLL + // CAutoClientSystem + virtual void Update( float frametime ) + { + SystemUpdate(); + } +#else + virtual void PreClientUpdate() + { + SystemUpdate(); + } +#endif + + virtual void LevelShutdownPreEntity() + { + SystemReset(); + } + +private: + CUtlVector m_soundList; + CUtlPriorityQueue m_commandList; + float m_flLastTime; +}; + +// Execute a command from the list +// currently only 3 commands +// UNDONE: Add start command? +void CSoundControllerImp::ProcessCommand( SoundCommand_t *pCmd ) +{ + switch( pCmd->m_command ) + { + case SOUNDCTRL_CHANGE_VOLUME: + pCmd->m_pPatch->ChangeVolume( pCmd->m_value, pCmd->m_deltaTime ); + break; + + case SOUNDCTRL_CHANGE_PITCH: + pCmd->m_pPatch->ChangePitch( pCmd->m_value, pCmd->m_deltaTime ); + break; + + case SOUNDCTRL_STOP: + pCmd->m_pPatch->Shutdown(); + break; + + case SOUNDCTRL_DESTROY: + RemoveFromList( pCmd->m_pPatch ); + delete pCmd->m_pPatch; + pCmd->m_pPatch = NULL; + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Remove this sound from the sound list & shutdown (not in external interface) +// Input : *pSound - patch to remove +//----------------------------------------------------------------------------- +void CSoundControllerImp::RemoveFromList( CSoundPatch *pSound ) +{ + m_soundList.FindAndRemove( pSound ); + pSound->Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Start this sound playing, or reset if already playing with new volume/pitch +//----------------------------------------------------------------------------- +void CSoundControllerImp::Play( CSoundPatch *pSound, float volume, float pitch, float flStartTime ) +{ + // reset the vars + pSound->Reset(); + + pSound->ChangeVolume( volume, 0 ); + pSound->ChangePitch( pitch, 0 ); + + if ( pSound->IsPlaying() ) + { + // remove any previous commands in the queue + CommandClear( pSound ); + } + else + { + m_soundList.AddToTail( pSound ); + pSound->StartSound( flStartTime ); + } +} + + +//----------------------------------------------------------------------------- +// Inserts the command into the list, sorted by time +//----------------------------------------------------------------------------- +void CSoundControllerImp::CommandInsert( SoundCommand_t *pCommand ) +{ + m_commandList.Insert( pCommand ); +} + + +//----------------------------------------------------------------------------- +// Purpose: puts a command into the queue +// Input : *pSound - patch this command affects +// executeDeltaTime - relative time to execute this command +// command - command to execute (SOUNDCTRL_*) +// commandTime - commands have 2 parameters, a time and a value +// value - +// Output : void +//----------------------------------------------------------------------------- +void CSoundControllerImp::CommandAdd( CSoundPatch *pSound, float executeDeltaTime, soundcommands_t command, float commandTime, float commandValue ) +{ + SoundCommand_t *pCommand = new SoundCommand_t( pSound, g_pEffects->Time() + executeDeltaTime, command, commandTime, commandValue ); + CommandInsert( pCommand ); +} + +// Reset the whole system (level change, etc.) +void CSoundControllerImp::SystemReset( void ) +{ + for ( int i = m_soundList.Count()-1; i >=0; i-- ) + { + CSoundPatch *pNode = m_soundList[i]; + + // shutdown all active sounds + pNode->Shutdown(); + } + + // clear the list + m_soundList.Purge(); + + // clear the command queue + m_commandList.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Update the active sounds, dequeue any events and move the ramps +//----------------------------------------------------------------------------- +void CSoundControllerImp::SystemUpdate( void ) +{ + VPROF( "CSoundControllerImp::SystemUpdate" ); + float time = g_pEffects->Time(); + float deltaTime = time - m_flLastTime; + + // handle clock resets + if ( deltaTime < 0 ) + deltaTime = 0; + + m_flLastTime = time; + + { + VPROF( "CSoundControllerImp::SystemUpdate:processcommandlist" ); + while ( m_commandList.Count() ) + { + SoundCommand_t *pCmd = m_commandList.ElementAtHead(); + // Commands are sorted by time. + // process any that should occur by the current time + if ( time >= pCmd->m_time ) + { + m_commandList.RemoveAtHead(); + ProcessCommand( pCmd ); + delete pCmd; + } + else + { + break; + } + } + } + + // NOTE: Because this loop goes from the end to the beginning + // we can fast remove inside it without breaking the indexing + { + VPROF( "CSoundControllerImp::SystemUpdate:removesounds" ); + for ( int i = m_soundList.Count()-1; i >=0; i-- ) + { + CSoundPatch *pNode = m_soundList[i]; + if ( !pNode->Update( time, deltaTime ) ) + { + pNode->Reset(); + m_soundList.FastRemove( i ); + } + } + } +} + +// Remove any envelope commands from the list (dynamically changing envelope) +void CSoundControllerImp::CommandClear( CSoundPatch *pSound ) +{ + for ( int i = m_commandList.Count()-1; i >= 0; i-- ) + { + SoundCommand_t *pCmd = m_commandList.Element( i ); + if ( pCmd->m_pPatch == pSound ) + { + m_commandList.RemoveAt(i); + delete pCmd; + } + } +} + + +//----------------------------------------------------------------------------- +// Saves the sound patch + associated commands +//----------------------------------------------------------------------------- +void CSoundControllerImp::SaveSoundPatch( CSoundPatch *pSoundPatch, ISave *pSave ) +{ + int i; + + // Write out the sound patch + pSave->StartBlock(); + pSave->WriteAll( pSoundPatch ); + pSave->EndBlock(); + + // Count the number of commands that refer to the sound patch + int nCount = 0; + for ( i = m_commandList.Count()-1; i >= 0; i-- ) + { + SoundCommand_t *pCmd = m_commandList.Element( i ); + if ( pCmd->m_pPatch == pSoundPatch ) + { + nCount++; + } + } + + // Write out the number of commands, followed by each command itself + pSave->StartBlock(); + pSave->WriteInt( &nCount ); + + for ( i = m_commandList.Count()-1; i >= 0; i-- ) + { + SoundCommand_t *pCmd = m_commandList.Element( i ); + if ( pCmd->m_pPatch == pSoundPatch ) + { + pSave->StartBlock(); + pSave->WriteAll( pCmd ); + pSave->EndBlock(); + } + } + + pSave->EndBlock(); +} + +//----------------------------------------------------------------------------- +// Restores the sound patch + associated commands +//----------------------------------------------------------------------------- +void CSoundControllerImp::RestoreSoundPatch( CSoundPatch **ppSoundPatch, IRestore *pRestore ) +{ + CSoundPatch *pPatch = new CSoundPatch; + + // read the sound patch data from the memory block + pRestore->StartBlock(); + bool bOk = ( pRestore->ReadAll( pPatch ) != 0 ); + pRestore->EndBlock(); + bOk = (bOk && pPatch->IsPlaying()) ? true : false; + + if (bOk) + { + m_soundList.AddToTail( pPatch ); + } + + // Count the number of commands that refer to the sound patch + pRestore->StartBlock(); + + if ( bOk ) + { + int nCount; + pRestore->ReadInt( &nCount ); + while ( --nCount >= 0 ) + { + SoundCommand_t *pCommand = new SoundCommand_t; + + pRestore->StartBlock(); + if ( pRestore->ReadAll( pCommand ) ) + { + pCommand->m_pPatch = pPatch; + CommandInsert( pCommand ); + } + + pRestore->EndBlock(); + } + } + + pRestore->EndBlock(); + *ppSoundPatch = pPatch; +} + + +//----------------------------------------------------------------------------- +// Purpose: immediately stop playing this sound +// Input : *pSound - Patch to shut down +//----------------------------------------------------------------------------- +void CSoundControllerImp::Shutdown( CSoundPatch *pSound ) +{ + if ( !pSound ) + return; + + pSound->Shutdown(); + CommandClear( pSound ); + RemoveFromList( pSound ); +} + +CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, const char *pSoundName ) +{ +#ifdef CLIENT_DLL + if ( GameRules() ) + { + pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", pSoundName ); + } +#endif + + CSoundPatch *pSound = new CSoundPatch; + + // FIXME: This is done so we don't have to futz with the public interface + EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; + pSound->Init( &filter, hEnt.Get(), CHAN_AUTO, pSoundName, SNDLVL_NORM ); + + return pSound; +} + +CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel, + const char *pSoundName, float attenuation ) +{ +#ifdef CLIENT_DLL + if ( GameRules() ) + { + pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", pSoundName ); + } +#endif + + CSoundPatch *pSound = new CSoundPatch; + EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; + pSound->Init( &filter, hEnt.Get(), channel, pSoundName, ATTN_TO_SNDLVL( attenuation ) ); + + return pSound; +} + +CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel, + const char *pSoundName, soundlevel_t soundlevel ) +{ +#ifdef CLIENT_DLL + if ( GameRules() ) + { + pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", pSoundName ); + } +#endif + + CSoundPatch *pSound = new CSoundPatch; + EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; + pSound->Init( &filter, hEnt.Get(), channel, pSoundName, soundlevel ); + + return pSound; +} + +CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, const EmitSound_t &es ) +{ + CSoundPatch *pSound = new CSoundPatch; + + // FIXME: This is done so we don't have to futz with the public interface + EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; + pSound->Init( &filter, hEnt.Get(), es.m_nChannel, es.m_pSoundName, es.m_SoundLevel ); + pSound->ChangeVolume( es.m_flVolume, 0 ); + pSound->ChangePitch( es.m_nPitch, 0 ); + + if ( es.m_nFlags & SND_SHOULDPAUSE ) + { + pSound->SetBaseFlags( SND_SHOULDPAUSE ); + } + + return pSound; +} + +void CSoundControllerImp::SoundDestroy( CSoundPatch *pSound ) +{ + if ( !pSound ) + return; + + Shutdown( pSound ); + delete pSound; +} + +void CSoundControllerImp::SoundChangePitch( CSoundPatch *pSound, float pitchTarget, float deltaTime ) +{ + pSound->ChangePitch( pitchTarget, deltaTime ); +} + + +void CSoundControllerImp::SoundChangeVolume( CSoundPatch *pSound, float volumeTarget, float deltaTime ) +{ + pSound->ChangeVolume( volumeTarget, deltaTime ); +} + +float CSoundControllerImp::SoundGetPitch( CSoundPatch *pSound ) +{ + return pSound->GetPitch(); +} + +float CSoundControllerImp::SoundGetVolume( CSoundPatch *pSound ) +{ + return pSound->GetVolume(); +} + +void CSoundControllerImp::SoundFadeOut( CSoundPatch *pSound, float deltaTime, bool destroyOnFadeout ) +{ + if ( destroyOnFadeout && (deltaTime == 0.0f) ) + { + SoundDestroy( pSound ); + return; + } + + pSound->FadeOut( deltaTime, destroyOnFadeout ); + if ( destroyOnFadeout ) + { + CommandAdd( pSound, deltaTime, SOUNDCTRL_DESTROY, 0.0f, 0.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Queue a list of envelope points into a sound patch's event list +// Input : *pSound - The sound patch to be operated on +// soundCommand - Type of operation the envelope describes +// *points - List of enevelope points +// numPoints - Number of points provided +// Output : float - Returns the total duration of the envelope +//----------------------------------------------------------------------------- +float CSoundControllerImp::SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopePoint_t *points, int numPoints ) +{ + float amplitude = 0.0f; + float duration = 0.0f; + float totalDuration = 0.0f; + + Assert( points ); + + // Clear out all previously acting commands + CommandClear( pSound ); + + // Evaluate and queue all points + for ( int i = 0; i < numPoints; i++ ) + { + // See if we're keeping our last amplitude for this new point + if ( ( points[i].amplitudeMin != -1.0f ) || ( points[i].amplitudeMax != -1.0f ) ) + { + amplitude = random->RandomFloat( points[i].amplitudeMin, points[i].amplitudeMax ); + } + else if ( i == 0 ) + { + // Can't do this on the first entry + Msg( "Invalid starting amplitude value in envelope! (Cannot be -1)\n" ); + } + + // See if we're keeping our last duration for this new point + if ( ( points[i].durationMin != -1.0f ) || ( points[i].durationMax != -1.0f ) ) + { + duration = random->RandomFloat( points[i].durationMin, points[i].durationMax ); + //duration = points[i].durationMin; + } + else if ( i == 0 ) + { + // Can't do this on the first entry + Msg( "Invalid starting duration value in envelope! (Cannot be -1)\n" ); + } + + // Queue the command + CommandAdd( pSound, totalDuration, soundCommand, duration, amplitude ); + + // Tack this command's duration onto the running duration + totalDuration += duration; + } + + return totalDuration; +} + +//----------------------------------------------------------------------------- +// Purpose: Queue a list of envelope points into a sound patch's event list +// Input : *pSound - The sound patch to be operated on +// soundCommand - Type of operation the envelope describes +// *envelope - The envelope description to be queued +// Output : float - Returns the total duration of the envelope +//----------------------------------------------------------------------------- +float CSoundControllerImp::SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopeDescription_t *envelope ) +{ + return SoundPlayEnvelope( pSound, soundCommand, envelope->pPoints, envelope->nNumPoints ); +} + +//----------------------------------------------------------------------------- +// Purpose: Looping sounds are often started in entity spawn/activate functions. +// In singleplayer, the player's not ready to receive sounds then, so restart +// and SoundPatches that are active and have no receivers. +//----------------------------------------------------------------------------- +void CSoundControllerImp::CheckLoopingSoundsForPlayer( CBasePlayer *pPlayer ) +{ + for ( int i = m_soundList.Count()-1; i >=0; i-- ) + { + CSoundPatch *pNode = m_soundList[i]; + pNode->AddPlayerPost( pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Resumes saved soundpatches +//----------------------------------------------------------------------------- +void CSoundControllerImp::OnRestore() +{ + for ( int i = m_soundList.Count()-1; i >=0; i-- ) + { + CSoundPatch *pNode = m_soundList[i]; + if ( pNode && pNode->IsPlaying() ) + { + pNode->ResumeSound(); + } + } +} + + +//----------------------------------------------------------------------------- +// Singleton accessors +//----------------------------------------------------------------------------- +static CSoundControllerImp g_Controller; +CSoundEnvelopeController &CSoundEnvelopeController::GetController( void ) +{ + return g_Controller; +} + + +//----------------------------------------------------------------------------- +// Queues up sound patches to save/load +//----------------------------------------------------------------------------- +class CSoundPatchSaveRestoreOps : public CClassPtrSaveRestoreOps +{ +public: + virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) + { + pSave->StartBlock(); + + int nSoundPatchCount = fieldInfo.pTypeDesc->fieldSize; + CSoundPatch **ppSoundPatch = (CSoundPatch**)fieldInfo.pField; + while ( --nSoundPatchCount >= 0 ) + { + // Write out commands associated with this sound patch + g_Controller.SaveSoundPatch( *ppSoundPatch, pSave ); + ++ppSoundPatch; + } + + pSave->EndBlock(); + } + + virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) + { + pRestore->StartBlock(); + + int nSoundPatchCount = fieldInfo.pTypeDesc->fieldSize; + CSoundPatch **ppSoundPatch = (CSoundPatch**)fieldInfo.pField; + while ( --nSoundPatchCount >= 0 ) + { + // Write out commands associated with this sound patch + g_Controller.RestoreSoundPatch( ppSoundPatch, pRestore ); + ++ppSoundPatch; + } + + pRestore->EndBlock(); + } +}; + +static CSoundPatchSaveRestoreOps s_SoundPatchSaveRestoreOps; +ISaveRestoreOps *GetSoundSaveRestoreOps( ) +{ + return &s_SoundPatchSaveRestoreOps; +} -- cgit v1.2.3