diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/shared/soundenvelope.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/shared/soundenvelope.cpp')
| -rw-r--r-- | mp/src/game/shared/soundenvelope.cpp | 1312 |
1 files changed, 1312 insertions, 0 deletions
diff --git a/mp/src/game/shared/soundenvelope.cpp b/mp/src/game/shared/soundenvelope.cpp new file mode 100644 index 00000000..15cba81d --- /dev/null +++ b/mp/src/game/shared/soundenvelope.cpp @@ -0,0 +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<CSoundPatch *> m_soundList;
+ CUtlPriorityQueue<SoundCommand_t *> 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;
+}
|