You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1423 lines
42 KiB
1423 lines
42 KiB
//========= 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; |
|
}
|
|
|