1424 lines
43 KiB
C++
1424 lines
43 KiB
C++
//========= Copyright © 1996-2005, 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"
|
|
|
|
// 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();
|
|
m_soundOrigin.Init();
|
|
m_soundEntityIndex = -1;
|
|
m_guid = -1;
|
|
|
|
}
|
|
~CSoundPatch()
|
|
{
|
|
g_SoundPatchCount--;
|
|
}
|
|
|
|
void Init( IRecipientFilter *pFilter, CBaseEntity *pEnt, int channel, const char *pSoundName,
|
|
soundlevel_t iSoundLevel, const Vector *pSoundOrigin, float scriptVolume = 1.0f );
|
|
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; };
|
|
#ifdef CLIENT_DLL
|
|
int GetGuid() { return m_guid; };
|
|
float GetElapsedTime( void );
|
|
bool IsStillPlaying( void );
|
|
#endif
|
|
|
|
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; }
|
|
float GetShutdownTime( void ) const { return m_shutdownTime; } // TERROR: debugging
|
|
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;
|
|
|
|
int m_guid;
|
|
soundlevel_t m_soundlevel;
|
|
float m_shutdownTime;
|
|
string_t m_iszSoundName;
|
|
string_t m_iszSoundScriptName;
|
|
EHANDLE m_hEnt;
|
|
int m_entityChannel;
|
|
int m_soundEntityIndex;
|
|
Vector m_soundOrigin;
|
|
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;
|
|
|
|
#ifdef CLIENT_DLL
|
|
CON_COMMAND( cl_report_soundpatch, "reports client-side sound patch count" )
|
|
#else
|
|
CON_COMMAND( report_soundpatch, "reports sound patch count" )
|
|
#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_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, const Vector *pSoundOrigin , float scriptVolume)
|
|
{
|
|
m_hEnt = pEnt;
|
|
if ( pEnt )
|
|
{
|
|
m_soundEntityIndex = pEnt->entindex();
|
|
}
|
|
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;
|
|
|
|
// TERROR: if we say we want CHAN_USER_BASE + N, we mean it!
|
|
if ( m_entityChannel < CHAN_USER_BASE )
|
|
{
|
|
m_entityChannel = params.channel;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
m_iszSoundScriptName = AllocPooledString( pSoundName );
|
|
|
|
m_flScriptVolume = scriptVolume;
|
|
m_soundlevel = soundlevel;
|
|
}
|
|
|
|
m_iszSoundName = AllocPooledString( pSoundName );
|
|
m_volume.SetValue( 0 );
|
|
m_pitch.SetValue( 0 );
|
|
m_isPlaying = false;
|
|
m_shutdownTime = 0;
|
|
m_Filter.Init( pFilter );
|
|
m_baseFlags = 0;
|
|
if( pSoundOrigin )
|
|
{
|
|
m_soundOrigin.x = pSoundOrigin->x;
|
|
m_soundOrigin.y = pSoundOrigin->y;
|
|
m_soundOrigin.z = pSoundOrigin->z;
|
|
}
|
|
|
|
#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();
|
|
}
|
|
|
|
#ifdef CLIENT_DLL
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the playing status of the sound
|
|
// Returns: Sounds playing status from the engine
|
|
//-----------------------------------------------------------------------------
|
|
bool CSoundPatch::IsStillPlaying( void )
|
|
{
|
|
return enginesound->IsSoundStillPlaying(m_guid);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the sound's current elapsed time
|
|
// Returns: Time in seconds
|
|
//-----------------------------------------------------------------------------
|
|
float CSoundPatch::GetElapsedTime( void )
|
|
{
|
|
// convert to seconds
|
|
return enginesound->GetElapsedTimeByGuid(m_guid) * 0.01;
|
|
}
|
|
#endif
|
|
//-----------------------------------------------------------------------------
|
|
// 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 = -1;
|
|
if ( m_hEnt.Get() )
|
|
{
|
|
entIndex = EntIndex();
|
|
}
|
|
else
|
|
{
|
|
// may have deleted the entity after starting the sound, but before stopping the sound, try the saved index
|
|
// this will handle that case so a sound patch doesn't get stuck on
|
|
entIndex = m_soundEntityIndex;
|
|
}
|
|
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() )
|
|
if ( m_flags )
|
|
{
|
|
// 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();
|
|
|
|
// only pass the position if it's coming from the world
|
|
if( EntIndex() == 0 )
|
|
ep.m_pOrigin = &m_soundOrigin;
|
|
|
|
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;
|
|
|
|
// only pass the position if it's coming from the world
|
|
if( EntIndex() == 0 )
|
|
ep.m_pOrigin = &m_soundOrigin;
|
|
|
|
if ( V_stristr( STRING(m_iszSoundName), "music" ) )
|
|
{
|
|
ep.m_nFlags = m_baseFlags;
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
|
|
//#ifdef CLIENT_DLL
|
|
#ifdef ___NOT
|
|
|
|
if ( V_stristr( STRING(m_iszSoundName), "music" ) )
|
|
{
|
|
// Don't play synchronously - we'll get it with the volume adjustments
|
|
//engine->ClientCmd( VarArgs("play %s\n", ep.m_pSoundName) );
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
CBaseEntity::EmitSound( m_Filter, EntIndex(), ep );
|
|
#ifdef CLIENT_DLL
|
|
m_guid = enginesound->GetGuidForLastSoundEmitted();
|
|
#endif
|
|
}
|
|
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();
|
|
|
|
// only pass the position if it's coming from the world
|
|
if( EntIndex() == 0 )
|
|
ep.m_pOrigin = &m_soundOrigin;
|
|
|
|
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();
|
|
|
|
// only pass the position if it's coming from the world
|
|
if( EntIndex() == 0 )
|
|
ep.m_pOrigin = &m_soundOrigin;
|
|
|
|
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, float scriptVolume = 1.0f );
|
|
|
|
CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel, const char *pSoundName,
|
|
float attenuation, const Vector *pSoundOrigin, float scriptVolume = 1.0f );
|
|
|
|
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 );
|
|
#ifdef CLIENT_DLL
|
|
float SoundGetElapsedTime( CSoundPatch *pSound );
|
|
bool SoundIsStillPlaying( CSoundPatch *pSound );
|
|
int SoundGetGuid( CSoundPatch *pSound );
|
|
#endif
|
|
|
|
string_t SoundGetName( CSoundPatch *pSound ) { return pSound->GetName(); }
|
|
string_t SoundGetScriptName( CSoundPatch *pSound ) { return pSound->GetScriptName(); }
|
|
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 )
|
|
{
|
|
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, NULL );
|
|
|
|
return pSound;
|
|
}
|
|
|
|
CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel,
|
|
const char *pSoundName, float attenuation, float scriptVolume )
|
|
{
|
|
CSoundPatch *pSound = new CSoundPatch;
|
|
EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL;
|
|
pSound->Init( &filter, hEnt.Get(), channel, pSoundName, ATTN_TO_SNDLVL( attenuation ), NULL, scriptVolume );
|
|
|
|
return pSound;
|
|
}
|
|
|
|
CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel,
|
|
const char *pSoundName, float attenuation, const Vector *pSoundOrigin, float scriptVolume )
|
|
{
|
|
CSoundPatch *pSound = new CSoundPatch;
|
|
EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL;
|
|
pSound->Init( &filter, hEnt.Get(), channel, pSoundName, ATTN_TO_SNDLVL( attenuation ), pSoundOrigin, scriptVolume );
|
|
|
|
return pSound;
|
|
}
|
|
CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel,
|
|
const char *pSoundName, soundlevel_t soundlevel )
|
|
{
|
|
CSoundPatch *pSound = new CSoundPatch;
|
|
EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL;
|
|
pSound->Init( &filter, hEnt.Get(), channel, pSoundName, soundlevel, NULL );
|
|
|
|
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, es.m_pOrigin );
|
|
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 );
|
|
}
|
|
|
|
#ifdef CLIENT_DLL
|
|
int CSoundControllerImp::SoundGetGuid( CSoundPatch *pSound )
|
|
{
|
|
return pSound->GetGuid();
|
|
}
|
|
float CSoundControllerImp::SoundGetElapsedTime( CSoundPatch *pSound )
|
|
{
|
|
return pSound->GetElapsedTime();
|
|
}
|
|
bool CSoundControllerImp::SoundIsStillPlaying( CSoundPatch *pSound )
|
|
{
|
|
return pSound->IsStillPlaying();
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|