|
|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
|
|
//
|
|
|
|
// Purpose: Soundscapes.txt resource file processor
|
|
|
|
//
|
|
|
|
// $NoKeywords: $
|
|
|
|
//=============================================================================//
|
|
|
|
|
|
|
|
|
|
|
|
#include "cbase.h"
|
|
|
|
#include <KeyValues.h>
|
|
|
|
#include "engine/IEngineSound.h"
|
|
|
|
#include "filesystem.h"
|
|
|
|
#include "SoundEmitterSystem/isoundemittersystembase.h"
|
|
|
|
#include "soundchars.h"
|
|
|
|
#include "view.h"
|
|
|
|
#include "engine/ivdebugoverlay.h"
|
|
|
|
#include "tier0/icommandline.h"
|
|
|
|
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
|
|
// Only allow recursive references to be 8 levels deep.
|
|
|
|
// This test will flag any circular references and bail.
|
|
|
|
#define MAX_SOUNDSCAPE_RECURSION 8
|
|
|
|
|
|
|
|
const float DEFAULT_SOUND_RADIUS = 36.0f;
|
|
|
|
// Keep an array of all looping sounds so they can be faded in/out
|
|
|
|
// OPTIMIZE: Get a handle/pointer to the engine's sound channel instead
|
|
|
|
// of searching each frame!
|
|
|
|
struct loopingsound_t
|
|
|
|
{
|
|
|
|
Vector position; // position (if !isAmbient)
|
|
|
|
const char *pWaveName; // name of the wave file
|
|
|
|
float volumeTarget; // target volume level (fading towards this)
|
|
|
|
float volumeCurrent; // current volume level
|
|
|
|
soundlevel_t soundlevel; // sound level (if !isAmbient)
|
|
|
|
int pitch; // pitch shift
|
|
|
|
int id; // Used to fade out sounds that don't belong to the most current setting
|
|
|
|
bool isAmbient; // Ambient sounds have no spatialization - they play from everywhere
|
|
|
|
};
|
|
|
|
|
|
|
|
ConVar soundscape_fadetime( "soundscape_fadetime", "3.0", FCVAR_CHEAT, "Time to crossfade sound effects between soundscapes" );
|
|
|
|
|
|
|
|
#include "interval.h"
|
|
|
|
|
|
|
|
struct randomsound_t
|
|
|
|
{
|
|
|
|
Vector position;
|
|
|
|
float nextPlayTime; // time to play a sound from the set
|
|
|
|
interval_t time;
|
|
|
|
interval_t volume;
|
|
|
|
interval_t pitch;
|
|
|
|
interval_t soundlevel;
|
|
|
|
float masterVolume;
|
|
|
|
int waveCount;
|
|
|
|
bool isAmbient;
|
|
|
|
bool isRandom;
|
|
|
|
KeyValues *pWaves;
|
|
|
|
|
|
|
|
void Init()
|
|
|
|
{
|
|
|
|
memset( this, 0, sizeof(*this) );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct subsoundscapeparams_t
|
|
|
|
{
|
|
|
|
int recurseLevel; // test for infinite loops in the script / circular refs
|
|
|
|
float masterVolume;
|
|
|
|
int startingPosition;
|
|
|
|
int positionOverride; // forces all sounds to this position
|
|
|
|
int ambientPositionOverride; // forces all ambient sounds to this position
|
|
|
|
bool allowDSP;
|
|
|
|
bool wroteSoundMixer;
|
|
|
|
bool wroteDSPVolume;
|
|
|
|
};
|
|
|
|
|
|
|
|
class C_SoundscapeSystem : public CBaseGameSystemPerFrame
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
virtual char const *Name() { return "C_SoundScapeSystem"; }
|
|
|
|
|
|
|
|
C_SoundscapeSystem()
|
|
|
|
{
|
|
|
|
m_nRestoreFrame = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
~C_SoundscapeSystem() {}
|
|
|
|
|
|
|
|
void OnStopAllSounds()
|
|
|
|
{
|
|
|
|
m_params.ent.Set( NULL );
|
|
|
|
m_params.soundscapeIndex = -1;
|
|
|
|
m_loopingSounds.Purge();
|
|
|
|
m_randomSounds.Purge();
|
|
|
|
}
|
|
|
|
|
|
|
|
// IClientSystem hooks, not needed
|
|
|
|
virtual void LevelInitPreEntity()
|
|
|
|
{
|
|
|
|
Shutdown();
|
|
|
|
Init();
|
|
|
|
|
|
|
|
TouchSoundFiles();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void LevelInitPostEntity()
|
|
|
|
{
|
|
|
|
if ( !m_pSoundMixerVar )
|
|
|
|
{
|
|
|
|
m_pSoundMixerVar = (ConVar *)cvar->FindVar( "snd_soundmixer" );
|
|
|
|
}
|
|
|
|
if ( !m_pDSPVolumeVar )
|
|
|
|
{
|
|
|
|
m_pDSPVolumeVar = (ConVar *)cvar->FindVar( "dsp_volume" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The level is shutdown in two parts
|
|
|
|
virtual void LevelShutdownPreEntity() {}
|
|
|
|
// Entities are deleted / released here...
|
|
|
|
virtual void LevelShutdownPostEntity()
|
|
|
|
{
|
|
|
|
OnStopAllSounds();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void OnSave() {}
|
|
|
|
virtual void OnRestore()
|
|
|
|
{
|
|
|
|
m_nRestoreFrame = gpGlobals->framecount;
|
|
|
|
}
|
|
|
|
virtual void SafeRemoveIfDesired() {}
|
|
|
|
|
|
|
|
// Called before rendering
|
|
|
|
virtual void PreRender() { }
|
|
|
|
|
|
|
|
// Called after rendering
|
|
|
|
virtual void PostRender() { }
|
|
|
|
|
|
|
|
// IClientSystem hooks used
|
|
|
|
virtual bool Init();
|
|
|
|
virtual void Shutdown();
|
|
|
|
// Gets called each frame
|
|
|
|
virtual void Update( float frametime );
|
|
|
|
|
|
|
|
void PrintDebugInfo()
|
|
|
|
{
|
|
|
|
Msg( "\n------- CLIENT SOUNDSCAPES -------\n" );
|
|
|
|
for ( int i=0; i < m_soundscapes.Count(); i++ )
|
|
|
|
{
|
|
|
|
Msg( "- %d: %s\n", i, m_soundscapes[i]->GetName() );
|
|
|
|
}
|
|
|
|
if ( m_forcedSoundscapeIndex )
|
|
|
|
{
|
|
|
|
Msg( "- PLAYING DEBUG SOUNDSCAPE: %d [%s]\n", m_forcedSoundscapeIndex, SoundscapeNameByIndex(m_forcedSoundscapeIndex) );
|
|
|
|
}
|
|
|
|
Msg( "- CURRENT SOUNDSCAPE: %d [%s]\n", m_params.soundscapeIndex.Get(), SoundscapeNameByIndex(m_params.soundscapeIndex) );
|
|
|
|
Msg( "----------------------------------\n\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// local functions
|
|
|
|
void UpdateAudioParams( audioparams_t &audio );
|
|
|
|
void GetAudioParams( audioparams_t &out ) const { out = m_params; }
|
|
|
|
int GetCurrentSoundscape()
|
|
|
|
{
|
|
|
|
if ( m_forcedSoundscapeIndex >= 0 )
|
|
|
|
return m_forcedSoundscapeIndex;
|
|
|
|
return m_params.soundscapeIndex;
|
|
|
|
}
|
|
|
|
void DevReportSoundscapeName( int index );
|
|
|
|
void UpdateLoopingSounds( float frametime );
|
|
|
|
int AddLoopingAmbient( const char *pSoundName, float volume, int pitch );
|
|
|
|
void UpdateLoopingSound( loopingsound_t &loopSound );
|
|
|
|
void StopLoopingSound( loopingsound_t &loopSound );
|
|
|
|
int AddLoopingSound( const char *pSoundName, bool isAmbient, float volume,
|
|
|
|
soundlevel_t soundLevel, int pitch, const Vector &position );
|
|
|
|
int AddRandomSound( const randomsound_t &sound );
|
|
|
|
void PlayRandomSound( randomsound_t &sound );
|
|
|
|
void UpdateRandomSounds( float gameClock );
|
|
|
|
Vector GenerateRandomSoundPosition();
|
|
|
|
|
|
|
|
void ForceSoundscape( const char *pSoundscapeName, float radius );
|
|
|
|
|
|
|
|
int FindSoundscapeByName( const char *pSoundscapeName );
|
|
|
|
const char *SoundscapeNameByIndex( int index );
|
|
|
|
KeyValues *SoundscapeByIndex( int index );
|
|
|
|
|
|
|
|
// main-level soundscape processing, called on new soundscape
|
|
|
|
void StartNewSoundscape( KeyValues *pSoundscape );
|
|
|
|
void StartSubSoundscape( KeyValues *pSoundscape, subsoundscapeparams_t ¶ms );
|
|
|
|
|
|
|
|
// root level soundscape keys
|
|
|
|
// add a process for each new command here
|
|
|
|
// "dsp"
|
|
|
|
void ProcessDSP( KeyValues *pDSP );
|
|
|
|
// "dsp_player"
|
|
|
|
void ProcessDSPPlayer( KeyValues *pDSPPlayer );
|
|
|
|
// "playlooping"
|
|
|
|
void ProcessPlayLooping( KeyValues *pPlayLooping, const subsoundscapeparams_t ¶ms );
|
|
|
|
// "playrandom"
|
|
|
|
void ProcessPlayRandom( KeyValues *pPlayRandom, const subsoundscapeparams_t ¶ms );
|
|
|
|
// "playsoundscape"
|
|
|
|
void ProcessPlaySoundscape( KeyValues *pPlaySoundscape, subsoundscapeparams_t ¶ms );
|
|
|
|
// "soundmixer"
|
|
|
|
void ProcessSoundMixer( KeyValues *pSoundMixer, subsoundscapeparams_t ¶ms );
|
|
|
|
// "dsp_volume"
|
|
|
|
void ProcessDSPVolume( KeyValues *pKey, subsoundscapeparams_t ¶ms );
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
bool IsBeingRestored() const
|
|
|
|
{
|
|
|
|
return gpGlobals->framecount == m_nRestoreFrame ? true : false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddSoundScapeFile( const char *filename );
|
|
|
|
|
|
|
|
void TouchPlayLooping( KeyValues *pAmbient );
|
|
|
|
void TouchPlayRandom( KeyValues *pPlayRandom );
|
|
|
|
void TouchWaveFiles( KeyValues *pSoundScape );
|
|
|
|
void TouchSoundFile( char const *wavefile );
|
|
|
|
|
|
|
|
void TouchSoundFiles();
|
|
|
|
|
|
|
|
int m_nRestoreFrame;
|
|
|
|
|
|
|
|
CUtlVector< KeyValues * > m_SoundscapeScripts; // The whole script file in memory
|
|
|
|
CUtlVector<KeyValues *> m_soundscapes; // Lookup by index of each root section
|
|
|
|
audioparams_t m_params; // current player audio params
|
|
|
|
CUtlVector<loopingsound_t> m_loopingSounds; // list of currently playing sounds
|
|
|
|
CUtlVector<randomsound_t> m_randomSounds; // list of random sound commands
|
|
|
|
float m_nextRandomTime; // next time to play a random sound
|
|
|
|
int m_loopingSoundId; // marks when the sound was issued
|
|
|
|
int m_forcedSoundscapeIndex;// >= 0 if this a "forced" soundscape? i.e. debug mode?
|
|
|
|
float m_forcedSoundscapeRadius;// distance to spatialized sounds
|
|
|
|
|
|
|
|
static ConVar *m_pDSPVolumeVar;
|
|
|
|
static ConVar *m_pSoundMixerVar;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// singleton system
|
|
|
|
C_SoundscapeSystem g_SoundscapeSystem;
|
|
|
|
ConVar *C_SoundscapeSystem::m_pDSPVolumeVar = NULL;
|
|
|
|
ConVar *C_SoundscapeSystem::m_pSoundMixerVar = NULL;
|
|
|
|
|
|
|
|
IGameSystem *ClientSoundscapeSystem()
|
|
|
|
{
|
|
|
|
return &g_SoundscapeSystem;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Soundscape_OnStopAllSounds()
|
|
|
|
{
|
|
|
|
g_SoundscapeSystem.OnStopAllSounds();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// player got a network update
|
|
|
|
void Soundscape_Update( audioparams_t &audio )
|
|
|
|
{
|
|
|
|
g_SoundscapeSystem.UpdateAudioParams( audio );
|
|
|
|
}
|
|
|
|
|
|
|
|
#define SOUNDSCAPE_MANIFEST_FILE "scripts/soundscapes_manifest.txt"
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::AddSoundScapeFile( const char *filename )
|
|
|
|
{
|
|
|
|
KeyValues *script = new KeyValues( filename );
|
|
|
|
#ifndef _XBOX
|
|
|
|
if ( script->LoadFromFile( filesystem, filename ) )
|
|
|
|
#else
|
|
|
|
if ( filesystem->LoadKeyValues( *script, IFileSystem::TYPE_SOUNDSCAPE, filename, "GAME" ) )
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
// parse out all of the top level sections and save their names
|
|
|
|
KeyValues *pKeys = script;
|
|
|
|
while ( pKeys )
|
|
|
|
{
|
|
|
|
// save pointers to all sections in the root
|
|
|
|
// each one is a soundscape
|
|
|
|
if ( pKeys->GetFirstSubKey() )
|
|
|
|
{
|
|
|
|
m_soundscapes.AddToTail( pKeys );
|
|
|
|
}
|
|
|
|
pKeys = pKeys->GetNextKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Keep pointer around so we can delete it at exit
|
|
|
|
m_SoundscapeScripts.AddToTail( script );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
script->deleteThis();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse the script file, setup index table
|
|
|
|
bool C_SoundscapeSystem::Init()
|
|
|
|
{
|
|
|
|
m_loopingSoundId = 0;
|
|
|
|
|
|
|
|
const char *mapname = MapName();
|
|
|
|
const char *mapSoundscapeFilename = NULL;
|
|
|
|
if ( mapname && *mapname )
|
|
|
|
{
|
|
|
|
mapSoundscapeFilename = VarArgs( "scripts/soundscapes_%s.txt", mapname );
|
|
|
|
}
|
|
|
|
|
|
|
|
KeyValues *manifest = new KeyValues( SOUNDSCAPE_MANIFEST_FILE );
|
|
|
|
if ( filesystem->LoadKeyValues( *manifest, IFileSystem::TYPE_SOUNDSCAPE, SOUNDSCAPE_MANIFEST_FILE, "GAME" ) )
|
|
|
|
{
|
|
|
|
for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() )
|
|
|
|
{
|
|
|
|
if ( !Q_stricmp( sub->GetName(), "file" ) )
|
|
|
|
{
|
|
|
|
// Add
|
|
|
|
AddSoundScapeFile( sub->GetString() );
|
|
|
|
if ( mapSoundscapeFilename && FStrEq( sub->GetString(), mapSoundscapeFilename ) )
|
|
|
|
{
|
|
|
|
mapSoundscapeFilename = NULL; // we've already loaded the map's soundscape
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Warning( "C_SoundscapeSystem::Init: Manifest '%s' with bogus file type '%s', expecting 'file'\n",
|
|
|
|
SOUNDSCAPE_MANIFEST_FILE, sub->GetName() );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( mapSoundscapeFilename && filesystem->FileExists( mapSoundscapeFilename ) )
|
|
|
|
{
|
|
|
|
AddSoundScapeFile( mapSoundscapeFilename );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Error( "Unable to load manifest file '%s'\n", SOUNDSCAPE_MANIFEST_FILE );
|
|
|
|
}
|
|
|
|
|
|
|
|
manifest->deleteThis();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int C_SoundscapeSystem::FindSoundscapeByName( const char *pSoundscapeName )
|
|
|
|
{
|
|
|
|
// UNDONE: Bad perf, linear search!
|
|
|
|
for ( int i = m_soundscapes.Count()-1; i >= 0; --i )
|
|
|
|
{
|
|
|
|
if ( !Q_stricmp( m_soundscapes[i]->GetName(), pSoundscapeName ) )
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
KeyValues *C_SoundscapeSystem::SoundscapeByIndex( int index )
|
|
|
|
{
|
|
|
|
if ( m_soundscapes.IsValidIndex(index) )
|
|
|
|
return m_soundscapes[index];
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *C_SoundscapeSystem::SoundscapeNameByIndex( int index )
|
|
|
|
{
|
|
|
|
if ( index < m_soundscapes.Count() )
|
|
|
|
{
|
|
|
|
return m_soundscapes[index]->GetName();
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::Shutdown()
|
|
|
|
{
|
|
|
|
for ( int i = m_loopingSounds.Count() - 1; i >= 0; --i )
|
|
|
|
{
|
|
|
|
loopingsound_t &sound = m_loopingSounds[i];
|
|
|
|
|
|
|
|
// sound is done, remove from list.
|
|
|
|
StopLoopingSound( sound );
|
|
|
|
}
|
|
|
|
|
|
|
|
// These are only necessary so we can use shutdown/init calls
|
|
|
|
// to flush soundscape data
|
|
|
|
m_loopingSounds.RemoveAll();
|
|
|
|
m_randomSounds.RemoveAll();
|
|
|
|
m_soundscapes.RemoveAll();
|
|
|
|
m_params.ent.Set( NULL );
|
|
|
|
m_params.soundscapeIndex = -1;
|
|
|
|
|
|
|
|
while ( m_SoundscapeScripts.Count() > 0 )
|
|
|
|
{
|
|
|
|
KeyValues *kv = m_SoundscapeScripts[ 0 ];
|
|
|
|
m_SoundscapeScripts.Remove( 0 );
|
|
|
|
kv->deleteThis();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: This will not flush the server side so you cannot add or remove
|
|
|
|
// soundscapes from the list, only change their parameters!!!!
|
|
|
|
CON_COMMAND_F(cl_soundscape_flush, "Flushes the client side soundscapes", FCVAR_SERVER_CAN_EXECUTE|FCVAR_CHEAT)
|
|
|
|
{
|
|
|
|
// save the current soundscape
|
|
|
|
audioparams_t tmp;
|
|
|
|
g_SoundscapeSystem.GetAudioParams( tmp );
|
|
|
|
|
|
|
|
// kill the system
|
|
|
|
g_SoundscapeSystem.Shutdown();
|
|
|
|
|
|
|
|
// restart the system
|
|
|
|
g_SoundscapeSystem.Init();
|
|
|
|
|
|
|
|
// reload the soundscape params from the temp copy
|
|
|
|
Soundscape_Update( tmp );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int SoundscapeCompletion( const char *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] )
|
|
|
|
{
|
|
|
|
int current = 0;
|
|
|
|
|
|
|
|
const char *cmdname = "playsoundscape";
|
|
|
|
char *substring = NULL;
|
|
|
|
int substringLen = 0;
|
|
|
|
if ( Q_strstr( partial, cmdname ) && strlen(partial) > strlen(cmdname) + 1 )
|
|
|
|
{
|
|
|
|
substring = (char *)partial + strlen( cmdname ) + 1;
|
|
|
|
substringLen = strlen(substring);
|
|
|
|
}
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
const char *pSoundscapeName = g_SoundscapeSystem.SoundscapeNameByIndex( i );
|
|
|
|
while ( pSoundscapeName && current < COMMAND_COMPLETION_MAXITEMS )
|
|
|
|
{
|
|
|
|
if ( !substring || !Q_strncasecmp( pSoundscapeName, substring, substringLen ) )
|
|
|
|
{
|
|
|
|
Q_snprintf( commands[ current ], sizeof( commands[ current ] ), "%s %s", cmdname, pSoundscapeName );
|
|
|
|
current++;
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
pSoundscapeName = g_SoundscapeSystem.SoundscapeNameByIndex( i );
|
|
|
|
}
|
|
|
|
|
|
|
|
return current;
|
|
|
|
}
|
|
|
|
|
|
|
|
CON_COMMAND_F_COMPLETION( playsoundscape, "Forces a soundscape to play", FCVAR_CHEAT, SoundscapeCompletion )
|
|
|
|
{
|
|
|
|
if ( args.ArgC() < 2 )
|
|
|
|
{
|
|
|
|
g_SoundscapeSystem.DevReportSoundscapeName( g_SoundscapeSystem.GetCurrentSoundscape() );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const char *pSoundscapeName = args[1];
|
|
|
|
float radius = args.ArgC() > 2 ? atof( args[2] ) : DEFAULT_SOUND_RADIUS;
|
|
|
|
g_SoundscapeSystem.ForceSoundscape( pSoundscapeName, radius );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CON_COMMAND_F( stopsoundscape, "Stops all soundscape processing and fades current looping sounds", FCVAR_CHEAT )
|
|
|
|
{
|
|
|
|
g_SoundscapeSystem.StartNewSoundscape( NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::ForceSoundscape( const char *pSoundscapeName, float radius )
|
|
|
|
{
|
|
|
|
int index = g_SoundscapeSystem.FindSoundscapeByName( pSoundscapeName );
|
|
|
|
if ( index >= 0 )
|
|
|
|
{
|
|
|
|
m_forcedSoundscapeIndex = index;
|
|
|
|
m_forcedSoundscapeRadius = radius;
|
|
|
|
g_SoundscapeSystem.StartNewSoundscape( SoundscapeByIndex(index) );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DevWarning("Can't find soundscape %s\n", pSoundscapeName );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::DevReportSoundscapeName( int index )
|
|
|
|
{
|
|
|
|
const char *pName = "none";
|
|
|
|
if ( index >= 0 && index < m_soundscapes.Count() )
|
|
|
|
{
|
|
|
|
pName = m_soundscapes[index]->GetName();
|
|
|
|
}
|
|
|
|
DevMsg( 1, "Soundscape: %s\n", pName );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This makes all currently playing loops fade toward their target volume
|
|
|
|
void C_SoundscapeSystem::UpdateLoopingSounds( float frametime )
|
|
|
|
{
|
|
|
|
float period = soundscape_fadetime.GetFloat();
|
|
|
|
float amount = frametime;
|
|
|
|
if ( period > 0 )
|
|
|
|
{
|
|
|
|
amount *= 1.0 / period;
|
|
|
|
}
|
|
|
|
|
|
|
|
int fadeCount = m_loopingSounds.Count();
|
|
|
|
while ( fadeCount > 0 )
|
|
|
|
{
|
|
|
|
fadeCount--;
|
|
|
|
loopingsound_t &sound = m_loopingSounds[fadeCount];
|
|
|
|
|
|
|
|
if ( sound.volumeCurrent != sound.volumeTarget )
|
|
|
|
{
|
|
|
|
sound.volumeCurrent = Approach( sound.volumeTarget, sound.volumeCurrent, amount );
|
|
|
|
if ( sound.volumeTarget == 0 && sound.volumeCurrent == 0 )
|
|
|
|
{
|
|
|
|
// sound is done, remove from list.
|
|
|
|
StopLoopingSound( sound );
|
|
|
|
m_loopingSounds.FastRemove( fadeCount );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// tell the engine about the new volume
|
|
|
|
UpdateLoopingSound( sound );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::Update( float frametime )
|
|
|
|
{
|
|
|
|
if ( m_forcedSoundscapeIndex >= 0 )
|
|
|
|
{
|
|
|
|
// generate fake positional sources
|
|
|
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
|
|
|
|
if ( pPlayer )
|
|
|
|
{
|
|
|
|
Vector origin, forward, right;
|
|
|
|
pPlayer->EyePositionAndVectors( &origin, &forward, &right, NULL );
|
|
|
|
|
|
|
|
// put the sound origins at the corners of a box around the player
|
|
|
|
m_params.localSound.Set( 0, origin + m_forcedSoundscapeRadius * (forward-right) );
|
|
|
|
m_params.localSound.Set( 1, origin + m_forcedSoundscapeRadius * (forward+right) );
|
|
|
|
m_params.localSound.Set( 2, origin + m_forcedSoundscapeRadius * (-forward-right) );
|
|
|
|
m_params.localSound.Set( 3, origin + m_forcedSoundscapeRadius * (-forward+right) );
|
|
|
|
m_params.localBits = 0x0007;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// fade out the old sounds over soundscape_fadetime seconds
|
|
|
|
UpdateLoopingSounds( frametime );
|
|
|
|
UpdateRandomSounds( gpGlobals->curtime );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::UpdateAudioParams( audioparams_t &audio )
|
|
|
|
{
|
|
|
|
if ( m_params.soundscapeIndex == audio.soundscapeIndex && m_params.ent.Get() == audio.ent.Get() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_params = audio;
|
|
|
|
m_forcedSoundscapeIndex = -1;
|
|
|
|
if ( audio.ent.Get() && audio.soundscapeIndex >= 0 && audio.soundscapeIndex < m_soundscapes.Count() )
|
|
|
|
{
|
|
|
|
DevReportSoundscapeName( audio.soundscapeIndex );
|
|
|
|
StartNewSoundscape( m_soundscapes[audio.soundscapeIndex] );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// bad index (and the soundscape file actually existed...)
|
|
|
|
if ( audio.ent.Get() != 0 &&
|
|
|
|
audio.soundscapeIndex != -1 )
|
|
|
|
{
|
|
|
|
DevMsg(1, "Error: Bad soundscape!\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Called when a soundscape is activated (leading edge of becoming the active soundscape)
|
|
|
|
void C_SoundscapeSystem::StartNewSoundscape( KeyValues *pSoundscape )
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
// Reset the system
|
|
|
|
// fade out the current loops
|
|
|
|
for ( i = m_loopingSounds.Count()-1; i >= 0; --i )
|
|
|
|
{
|
|
|
|
m_loopingSounds[i].volumeTarget = 0;
|
|
|
|
if ( !pSoundscape )
|
|
|
|
{
|
|
|
|
// if we're cancelling the soundscape, stop the sound immediately
|
|
|
|
m_loopingSounds[i].volumeCurrent = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// update ID
|
|
|
|
m_loopingSoundId++;
|
|
|
|
|
|
|
|
// clear all random sounds
|
|
|
|
m_randomSounds.RemoveAll();
|
|
|
|
m_nextRandomTime = gpGlobals->curtime;
|
|
|
|
|
|
|
|
if ( pSoundscape )
|
|
|
|
{
|
|
|
|
subsoundscapeparams_t params;
|
|
|
|
params.allowDSP = true;
|
|
|
|
params.wroteSoundMixer = false;
|
|
|
|
params.wroteDSPVolume = false;
|
|
|
|
|
|
|
|
params.masterVolume = 1.0;
|
|
|
|
params.startingPosition = 0;
|
|
|
|
params.recurseLevel = 0;
|
|
|
|
params.positionOverride = -1;
|
|
|
|
params.ambientPositionOverride = -1;
|
|
|
|
StartSubSoundscape( pSoundscape, params );
|
|
|
|
|
|
|
|
if ( !params.wroteDSPVolume )
|
|
|
|
{
|
|
|
|
m_pDSPVolumeVar->Revert();
|
|
|
|
}
|
|
|
|
if ( !params.wroteSoundMixer )
|
|
|
|
{
|
|
|
|
m_pSoundMixerVar->Revert();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::StartSubSoundscape( KeyValues *pSoundscape, subsoundscapeparams_t ¶ms )
|
|
|
|
{
|
|
|
|
// Parse/process all of the commands
|
|
|
|
KeyValues *pKey = pSoundscape->GetFirstSubKey();
|
|
|
|
while ( pKey )
|
|
|
|
{
|
|
|
|
if ( !Q_strcasecmp( pKey->GetName(), "dsp" ) )
|
|
|
|
{
|
|
|
|
if ( params.allowDSP )
|
|
|
|
{
|
|
|
|
ProcessDSP( pKey );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "dsp_player" ) )
|
|
|
|
{
|
|
|
|
if ( params.allowDSP )
|
|
|
|
{
|
|
|
|
ProcessDSPPlayer( pKey );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "playlooping" ) )
|
|
|
|
{
|
|
|
|
ProcessPlayLooping( pKey, params );
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "playrandom" ) )
|
|
|
|
{
|
|
|
|
ProcessPlayRandom( pKey, params );
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "playsoundscape" ) )
|
|
|
|
{
|
|
|
|
ProcessPlaySoundscape( pKey, params );
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "Soundmixer" ) )
|
|
|
|
{
|
|
|
|
if ( params.allowDSP )
|
|
|
|
{
|
|
|
|
ProcessSoundMixer( pKey, params );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "dsp_volume" ) )
|
|
|
|
{
|
|
|
|
if ( params.allowDSP )
|
|
|
|
{
|
|
|
|
ProcessDSPVolume( pKey, params );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// add new commands here
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DevMsg( 1, "Soundscape %s:Unknown command %s\n", pSoundscape->GetName(), pKey->GetName() );
|
|
|
|
}
|
|
|
|
pKey = pKey->GetNextKey();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// add a process for each new command here
|
|
|
|
|
|
|
|
// change DSP effect
|
|
|
|
void C_SoundscapeSystem::ProcessDSP( KeyValues *pDSP )
|
|
|
|
{
|
|
|
|
int roomType = pDSP->GetInt();
|
|
|
|
CLocalPlayerFilter filter;
|
|
|
|
enginesound->SetRoomType( filter, roomType );
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : *pDSPPlayer -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void C_SoundscapeSystem::ProcessDSPPlayer( KeyValues *pDSPPlayer )
|
|
|
|
{
|
|
|
|
int dspType = pDSPPlayer->GetInt();
|
|
|
|
CLocalPlayerFilter filter;
|
|
|
|
enginesound->SetPlayerDSP( filter, dspType, false );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::ProcessSoundMixer( KeyValues *pSoundMixer, subsoundscapeparams_t ¶ms )
|
|
|
|
{
|
|
|
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
|
|
|
|
if ( !pPlayer || pPlayer->CanSetSoundMixer() )
|
|
|
|
{
|
|
|
|
m_pSoundMixerVar->SetValue( pSoundMixer->GetString() );
|
|
|
|
params.wroteSoundMixer = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::ProcessDSPVolume( KeyValues *pKey, subsoundscapeparams_t ¶ms )
|
|
|
|
{
|
|
|
|
m_pDSPVolumeVar->SetValue( pKey->GetFloat() );
|
|
|
|
params.wroteDSPVolume = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// start a new looping sound
|
|
|
|
void C_SoundscapeSystem::ProcessPlayLooping( KeyValues *pAmbient, const subsoundscapeparams_t ¶ms )
|
|
|
|
{
|
|
|
|
float volume = 0;
|
|
|
|
soundlevel_t soundlevel = ATTN_TO_SNDLVL(ATTN_NORM);
|
|
|
|
const char *pSoundName = NULL;
|
|
|
|
int pitch = PITCH_NORM;
|
|
|
|
int positionIndex = -1;
|
|
|
|
bool suppress = false;
|
|
|
|
KeyValues *pKey = pAmbient->GetFirstSubKey();
|
|
|
|
while ( pKey )
|
|
|
|
{
|
|
|
|
if ( !Q_strcasecmp( pKey->GetName(), "volume" ) )
|
|
|
|
{
|
|
|
|
volume = params.masterVolume * RandomInterval( ReadInterval( pKey->GetString() ) );
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "pitch" ) )
|
|
|
|
{
|
|
|
|
pitch = RandomInterval( ReadInterval( pKey->GetString() ) );
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "wave" ) )
|
|
|
|
{
|
|
|
|
pSoundName = pKey->GetString();
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "position" ) )
|
|
|
|
{
|
|
|
|
positionIndex = params.startingPosition + pKey->GetInt();
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "attenuation" ) )
|
|
|
|
{
|
|
|
|
soundlevel = ATTN_TO_SNDLVL( RandomInterval( ReadInterval( pKey->GetString() ) ) );
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "soundlevel" ) )
|
|
|
|
{
|
|
|
|
if ( !Q_strncasecmp( pKey->GetString(), "SNDLVL_", strlen( "SNDLVL_" ) ) )
|
|
|
|
{
|
|
|
|
soundlevel = TextToSoundLevel( pKey->GetString() );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
soundlevel = (soundlevel_t)((int)RandomInterval( ReadInterval( pKey->GetString() ) ));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "suppress_on_restore" ) )
|
|
|
|
{
|
|
|
|
suppress = Q_atoi( pKey->GetString() ) != 0 ? true : false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DevMsg( 1, "Ambient %s:Unknown command %s\n", pAmbient->GetName(), pKey->GetName() );
|
|
|
|
}
|
|
|
|
pKey = pKey->GetNextKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( positionIndex < 0 )
|
|
|
|
{
|
|
|
|
positionIndex = params.ambientPositionOverride;
|
|
|
|
}
|
|
|
|
else if ( params.positionOverride >= 0 )
|
|
|
|
{
|
|
|
|
positionIndex = params.positionOverride;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sound is mared as "suppress_on_restore" so don't restart it
|
|
|
|
if ( IsBeingRestored() && suppress )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( volume != 0 && pSoundName != NULL )
|
|
|
|
{
|
|
|
|
if ( positionIndex < 0 )
|
|
|
|
{
|
|
|
|
AddLoopingAmbient( pSoundName, volume, pitch );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( positionIndex > 31 || !(m_params.localBits & (1<<positionIndex) ) )
|
|
|
|
{
|
|
|
|
// suppress sounds if the position isn't available
|
|
|
|
//DevMsg( 1, "Bad position %d\n", positionIndex );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
AddLoopingSound( pSoundName, false, volume, soundlevel, pitch, m_params.localSound[positionIndex] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::TouchSoundFile( char const *wavefile )
|
|
|
|
{
|
|
|
|
filesystem->GetFileTime( VarArgs( "sound/%s", PSkipSoundChars( wavefile ) ), "GAME" );
|
|
|
|
}
|
|
|
|
|
|
|
|
// start a new looping sound
|
|
|
|
void C_SoundscapeSystem::TouchPlayLooping( KeyValues *pAmbient )
|
|
|
|
{
|
|
|
|
KeyValues *pKey = pAmbient->GetFirstSubKey();
|
|
|
|
while ( pKey )
|
|
|
|
{
|
|
|
|
if ( !Q_strcasecmp( pKey->GetName(), "wave" ) )
|
|
|
|
{
|
|
|
|
char const *pSoundName = pKey->GetString();
|
|
|
|
|
|
|
|
// Touch the file
|
|
|
|
TouchSoundFile( pSoundName );
|
|
|
|
}
|
|
|
|
|
|
|
|
pKey = pKey->GetNextKey();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Vector C_SoundscapeSystem::GenerateRandomSoundPosition()
|
|
|
|
{
|
|
|
|
float angle = random->RandomFloat( -180, 180 );
|
|
|
|
float sinAngle, cosAngle;
|
|
|
|
SinCos( angle, &sinAngle, &cosAngle );
|
|
|
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
|
|
|
|
if ( pPlayer )
|
|
|
|
{
|
|
|
|
Vector origin, forward, right;
|
|
|
|
pPlayer->EyePositionAndVectors( &origin, &forward, &right, NULL );
|
|
|
|
return origin + DEFAULT_SOUND_RADIUS * (cosAngle * right + sinAngle * forward);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return CurrentViewOrigin() + DEFAULT_SOUND_RADIUS * (cosAngle * CurrentViewRight() + sinAngle * CurrentViewForward());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::TouchSoundFiles()
|
|
|
|
{
|
|
|
|
if ( !CommandLine()->FindParm( "-makereslists" ) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
int c = m_soundscapes.Count();
|
|
|
|
for ( int i = 0; i < c ; ++i )
|
|
|
|
{
|
|
|
|
TouchWaveFiles( m_soundscapes[ i ] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::TouchWaveFiles( KeyValues *pSoundScape )
|
|
|
|
{
|
|
|
|
KeyValues *pKey = pSoundScape->GetFirstSubKey();
|
|
|
|
while ( pKey )
|
|
|
|
{
|
|
|
|
if ( !Q_strcasecmp( pKey->GetName(), "playlooping" ) )
|
|
|
|
{
|
|
|
|
TouchPlayLooping( pKey );
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "playrandom" ) )
|
|
|
|
{
|
|
|
|
TouchPlayRandom( pKey );
|
|
|
|
}
|
|
|
|
|
|
|
|
pKey = pKey->GetNextKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// puts a recurring random sound event into the queue
|
|
|
|
void C_SoundscapeSystem::TouchPlayRandom( KeyValues *pPlayRandom )
|
|
|
|
{
|
|
|
|
KeyValues *pKey = pPlayRandom->GetFirstSubKey();
|
|
|
|
while ( pKey )
|
|
|
|
{
|
|
|
|
if ( !Q_strcasecmp( pKey->GetName(), "rndwave" ) )
|
|
|
|
{
|
|
|
|
KeyValues *pWaves = pKey->GetFirstSubKey();
|
|
|
|
while ( pWaves )
|
|
|
|
{
|
|
|
|
TouchSoundFile( pWaves->GetString() );
|
|
|
|
|
|
|
|
pWaves = pWaves->GetNextKey();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pKey = pKey->GetNextKey();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// puts a recurring random sound event into the queue
|
|
|
|
void C_SoundscapeSystem::ProcessPlayRandom( KeyValues *pPlayRandom, const subsoundscapeparams_t ¶ms )
|
|
|
|
{
|
|
|
|
randomsound_t sound;
|
|
|
|
sound.Init();
|
|
|
|
sound.masterVolume = params.masterVolume;
|
|
|
|
int positionIndex = -1;
|
|
|
|
bool suppress = false;
|
|
|
|
bool randomPosition = false;
|
|
|
|
KeyValues *pKey = pPlayRandom->GetFirstSubKey();
|
|
|
|
while ( pKey )
|
|
|
|
{
|
|
|
|
if ( !Q_strcasecmp( pKey->GetName(), "volume" ) )
|
|
|
|
{
|
|
|
|
sound.volume = ReadInterval( pKey->GetString() );
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "pitch" ) )
|
|
|
|
{
|
|
|
|
sound.pitch = ReadInterval( pKey->GetString() );
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "attenuation" ) )
|
|
|
|
{
|
|
|
|
interval_t atten = ReadInterval( pKey->GetString() );
|
|
|
|
sound.soundlevel.start = ATTN_TO_SNDLVL( atten.start );
|
|
|
|
sound.soundlevel.range = ATTN_TO_SNDLVL( atten.start + atten.range ) - sound.soundlevel.start;
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "soundlevel" ) )
|
|
|
|
{
|
|
|
|
if ( !Q_strncasecmp( pKey->GetString(), "SNDLVL_", strlen( "SNDLVL_" ) ) )
|
|
|
|
{
|
|
|
|
sound.soundlevel.start = TextToSoundLevel( pKey->GetString() );
|
|
|
|
sound.soundlevel.range = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sound.soundlevel = ReadInterval( pKey->GetString() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "time" ) )
|
|
|
|
{
|
|
|
|
sound.time = ReadInterval( pKey->GetString() );
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "rndwave" ) )
|
|
|
|
{
|
|
|
|
KeyValues *pWaves = pKey->GetFirstSubKey();
|
|
|
|
sound.pWaves = pWaves;
|
|
|
|
sound.waveCount = 0;
|
|
|
|
while ( pWaves )
|
|
|
|
{
|
|
|
|
sound.waveCount++;
|
|
|
|
pWaves = pWaves->GetNextKey();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "position" ) )
|
|
|
|
{
|
|
|
|
if ( !Q_strcasecmp( pKey->GetString(), "random" ) )
|
|
|
|
{
|
|
|
|
randomPosition = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
positionIndex = params.startingPosition + pKey->GetInt();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "suppress_on_restore" ) )
|
|
|
|
{
|
|
|
|
suppress = Q_atoi( pKey->GetString() ) != 0 ? true : false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DevMsg( 1, "Random Sound %s:Unknown command %s\n", pPlayRandom->GetName(), pKey->GetName() );
|
|
|
|
}
|
|
|
|
|
|
|
|
pKey = pKey->GetNextKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( positionIndex < 0 )
|
|
|
|
{
|
|
|
|
positionIndex = params.ambientPositionOverride;
|
|
|
|
}
|
|
|
|
else if ( params.positionOverride >= 0 )
|
|
|
|
{
|
|
|
|
positionIndex = params.positionOverride;
|
|
|
|
randomPosition = false; // override trumps random position
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sound is mared as "suppress_on_restore" so don't restart it
|
|
|
|
if ( IsBeingRestored() && suppress )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( sound.waveCount != 0 )
|
|
|
|
{
|
|
|
|
if ( positionIndex < 0 && !randomPosition )
|
|
|
|
{
|
|
|
|
sound.isAmbient = true;
|
|
|
|
AddRandomSound( sound );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sound.isAmbient = false;
|
|
|
|
if ( randomPosition )
|
|
|
|
{
|
|
|
|
sound.isRandom = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( positionIndex > 31 || !(m_params.localBits & (1<<positionIndex) ) )
|
|
|
|
{
|
|
|
|
// suppress sounds if the position isn't available
|
|
|
|
//DevMsg( 1, "Bad position %d\n", positionIndex );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
sound.position = m_params.localSound[positionIndex];
|
|
|
|
}
|
|
|
|
AddRandomSound( sound );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void C_SoundscapeSystem::ProcessPlaySoundscape( KeyValues *pPlaySoundscape, subsoundscapeparams_t ¶msIn )
|
|
|
|
{
|
|
|
|
subsoundscapeparams_t subParams = paramsIn;
|
|
|
|
|
|
|
|
// sub-soundscapes NEVER set the DSP effects
|
|
|
|
subParams.allowDSP = false;
|
|
|
|
subParams.recurseLevel++;
|
|
|
|
if ( subParams.recurseLevel > MAX_SOUNDSCAPE_RECURSION )
|
|
|
|
{
|
|
|
|
DevMsg( "Error! Soundscape recursion overrun!\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
KeyValues *pKey = pPlaySoundscape->GetFirstSubKey();
|
|
|
|
const char *pSoundscapeName = NULL;
|
|
|
|
while ( pKey )
|
|
|
|
{
|
|
|
|
if ( !Q_strcasecmp( pKey->GetName(), "volume" ) )
|
|
|
|
{
|
|
|
|
subParams.masterVolume = paramsIn.masterVolume * RandomInterval( ReadInterval( pKey->GetString() ) );
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "position" ) )
|
|
|
|
{
|
|
|
|
subParams.startingPosition = paramsIn.startingPosition + pKey->GetInt();
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "positionoverride" ) )
|
|
|
|
{
|
|
|
|
if ( paramsIn.positionOverride < 0 )
|
|
|
|
{
|
|
|
|
subParams.positionOverride = paramsIn.startingPosition + pKey->GetInt();
|
|
|
|
// positionoverride is only ever used to make a whole soundscape come from a point in space
|
|
|
|
// So go ahead and default ambients there too.
|
|
|
|
subParams.ambientPositionOverride = paramsIn.startingPosition + pKey->GetInt();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "ambientpositionoverride" ) )
|
|
|
|
{
|
|
|
|
if ( paramsIn.ambientPositionOverride < 0 )
|
|
|
|
{
|
|
|
|
subParams.ambientPositionOverride = paramsIn.startingPosition + pKey->GetInt();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "name" ) )
|
|
|
|
{
|
|
|
|
pSoundscapeName = pKey->GetString();
|
|
|
|
}
|
|
|
|
else if ( !Q_strcasecmp(pKey->GetName(), "soundlevel") )
|
|
|
|
{
|
|
|
|
DevMsg(1,"soundlevel not supported on sub-soundscapes\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DevMsg( 1, "Playsoundscape %s:Unknown command %s\n", pSoundscapeName ? pSoundscapeName : pPlaySoundscape->GetName(), pKey->GetName() );
|
|
|
|
}
|
|
|
|
pKey = pKey->GetNextKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( pSoundscapeName )
|
|
|
|
{
|
|
|
|
KeyValues *pSoundscapeKeys = SoundscapeByIndex( FindSoundscapeByName( pSoundscapeName ) );
|
|
|
|
if ( pSoundscapeKeys )
|
|
|
|
{
|
|
|
|
StartSubSoundscape( pSoundscapeKeys, subParams );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DevMsg( 1, "Trying to play unknown soundscape %s\n", pSoundscapeName );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// special kind of looping sound with no spatialization
|
|
|
|
int C_SoundscapeSystem::AddLoopingAmbient( const char *pSoundName, float volume, int pitch )
|
|
|
|
{
|
|
|
|
return AddLoopingSound( pSoundName, true, volume, SNDLVL_NORM, pitch, vec3_origin );
|
|
|
|
}
|
|
|
|
|
|
|
|
// add a looping sound to the list
|
|
|
|
// NOTE: will reuse existing entry (fade from current volume) if possible
|
|
|
|
// this prevents pops
|
|
|
|
int C_SoundscapeSystem::AddLoopingSound( const char *pSoundName, bool isAmbient, float volume, soundlevel_t soundlevel, int pitch, const Vector &position )
|
|
|
|
{
|
|
|
|
loopingsound_t *pSoundSlot = NULL;
|
|
|
|
int soundSlot = m_loopingSounds.Count() - 1;
|
|
|
|
bool bForceSoundUpdate = false;
|
|
|
|
while ( soundSlot >= 0 )
|
|
|
|
{
|
|
|
|
loopingsound_t &sound = m_loopingSounds[soundSlot];
|
|
|
|
|
|
|
|
// NOTE: Will always restart/crossfade positional sounds
|
|
|
|
if ( sound.id != m_loopingSoundId &&
|
|
|
|
sound.pitch == pitch &&
|
|
|
|
!Q_strcasecmp( pSoundName, sound.pWaveName ) )
|
|
|
|
{
|
|
|
|
// Ambient sounds can reuse the slots.
|
|
|
|
if ( isAmbient == true &&
|
|
|
|
sound.isAmbient == true )
|
|
|
|
{
|
|
|
|
// reuse this sound
|
|
|
|
pSoundSlot = &sound;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Positional sounds can reuse the slots if the positions are the same.
|
|
|
|
else if ( isAmbient == sound.isAmbient )
|
|
|
|
{
|
|
|
|
if ( VectorsAreEqual( position, sound.position, 0.1f ) )
|
|
|
|
{
|
|
|
|
// reuse this sound
|
|
|
|
pSoundSlot = &sound;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// If it's trying to fade out one positional sound and fade in another, then it gets screwy
|
|
|
|
// because it'll be sending alternating commands to the sound engine, referencing the same sound
|
|
|
|
// (SOUND_FROM_WORLD, CHAN_STATIC, pSoundName). One of the alternating commands will be as
|
|
|
|
// it fades the sound out, and one will be fading the sound in.
|
|
|
|
// Because this will occasionally cause the sound to vanish entirely, we stop the old sound immediately.
|
|
|
|
StopLoopingSound(sound);
|
|
|
|
pSoundSlot = &sound;
|
|
|
|
|
|
|
|
// make a note to update the sound immediately. Otherwise, if its volume happens to be
|
|
|
|
// the same as the old sound's volume, it will never update at all.
|
|
|
|
bForceSoundUpdate = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
soundSlot--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( soundSlot < 0 )
|
|
|
|
{
|
|
|
|
// can't find the sound in the list, make a new one
|
|
|
|
soundSlot = m_loopingSounds.AddToTail();
|
|
|
|
if ( isAmbient )
|
|
|
|
{
|
|
|
|
// start at 0 and fade in
|
|
|
|
enginesound->EmitAmbientSound( pSoundName, 0, pitch );
|
|
|
|
m_loopingSounds[soundSlot].volumeCurrent = 0.0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// non-ambients at 0 volume are culled, so start at 0.05
|
|
|
|
CLocalPlayerFilter filter;
|
|
|
|
|
|
|
|
EmitSound_t ep;
|
|
|
|
ep.m_nChannel = CHAN_STATIC;
|
|
|
|
ep.m_pSoundName = pSoundName;
|
|
|
|
ep.m_flVolume = 0.05;
|
|
|
|
ep.m_SoundLevel = soundlevel;
|
|
|
|
ep.m_nPitch = pitch;
|
|
|
|
ep.m_pOrigin = &position;
|
|
|
|
|
|
|
|
C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep );
|
|
|
|
m_loopingSounds[soundSlot].volumeCurrent = 0.05;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
loopingsound_t &sound = m_loopingSounds[soundSlot];
|
|
|
|
// fill out the slot
|
|
|
|
sound.pWaveName = pSoundName;
|
|
|
|
sound.volumeTarget = volume;
|
|
|
|
sound.pitch = pitch;
|
|
|
|
sound.id = m_loopingSoundId;
|
|
|
|
sound.isAmbient = isAmbient;
|
|
|
|
sound.position = position;
|
|
|
|
sound.soundlevel = soundlevel;
|
|
|
|
|
|
|
|
if (bForceSoundUpdate)
|
|
|
|
{
|
|
|
|
UpdateLoopingSound(sound);
|
|
|
|
}
|
|
|
|
|
|
|
|
return soundSlot;
|
|
|
|
}
|
|
|
|
|
|
|
|
// stop this loop forever
|
|
|
|
void C_SoundscapeSystem::StopLoopingSound( loopingsound_t &loopSound )
|
|
|
|
{
|
|
|
|
if ( loopSound.isAmbient )
|
|
|
|
{
|
|
|
|
enginesound->EmitAmbientSound( loopSound.pWaveName, 0, 0, SND_STOP );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
C_BaseEntity::StopSound( SOUND_FROM_WORLD, CHAN_STATIC, loopSound.pWaveName );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// update with new volume
|
|
|
|
void C_SoundscapeSystem::UpdateLoopingSound( loopingsound_t &loopSound )
|
|
|
|
{
|
|
|
|
if ( loopSound.isAmbient )
|
|
|
|
{
|
|
|
|
enginesound->EmitAmbientSound( loopSound.pWaveName, loopSound.volumeCurrent, loopSound.pitch, SND_CHANGE_VOL );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CLocalPlayerFilter filter;
|
|
|
|
|
|
|
|
EmitSound_t ep;
|
|
|
|
ep.m_nChannel = CHAN_STATIC;
|
|
|
|
ep.m_pSoundName = loopSound.pWaveName;
|
|
|
|
ep.m_flVolume = loopSound.volumeCurrent;
|
|
|
|
ep.m_SoundLevel = loopSound.soundlevel;
|
|
|
|
ep.m_nFlags = SND_CHANGE_VOL;
|
|
|
|
ep.m_nPitch = loopSound.pitch;
|
|
|
|
ep.m_pOrigin = &loopSound.position;
|
|
|
|
|
|
|
|
C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// add a recurring random sound event
|
|
|
|
int C_SoundscapeSystem::AddRandomSound( const randomsound_t &sound )
|
|
|
|
{
|
|
|
|
int index = m_randomSounds.AddToTail( sound );
|
|
|
|
m_randomSounds[index].nextPlayTime = gpGlobals->curtime + 0.5 * RandomInterval( sound.time );
|
|
|
|
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
|
|
|
// play a random sound randomly from this parameterization table
|
|
|
|
void C_SoundscapeSystem::PlayRandomSound( randomsound_t &sound )
|
|
|
|
{
|
|
|
|
Assert( sound.waveCount > 0 );
|
|
|
|
|
|
|
|
int waveId = random->RandomInt( 0, sound.waveCount-1 );
|
|
|
|
KeyValues *pWaves = sound.pWaves;
|
|
|
|
while ( waveId > 0 && pWaves )
|
|
|
|
{
|
|
|
|
pWaves = pWaves->GetNextKey();
|
|
|
|
waveId--;
|
|
|
|
}
|
|
|
|
if ( !pWaves )
|
|
|
|
return;
|
|
|
|
|
|
|
|
const char *pWaveName = pWaves->GetString();
|
|
|
|
|
|
|
|
if ( !pWaveName )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( sound.isAmbient )
|
|
|
|
{
|
|
|
|
enginesound->EmitAmbientSound( pWaveName, sound.masterVolume * RandomInterval( sound.volume ), (int)RandomInterval( sound.pitch ) );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CLocalPlayerFilter filter;
|
|
|
|
|
|
|
|
EmitSound_t ep;
|
|
|
|
ep.m_nChannel = CHAN_STATIC;
|
|
|
|
ep.m_pSoundName = pWaveName;
|
|
|
|
ep.m_flVolume = sound.masterVolume * RandomInterval( sound.volume );
|
|
|
|
ep.m_SoundLevel = (soundlevel_t)(int)RandomInterval( sound.soundlevel );
|
|
|
|
ep.m_nPitch = (int)RandomInterval( sound.pitch );
|
|
|
|
if ( sound.isRandom )
|
|
|
|
{
|
|
|
|
sound.position = GenerateRandomSoundPosition();
|
|
|
|
}
|
|
|
|
ep.m_pOrigin = &sound.position;
|
|
|
|
|
|
|
|
C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// walk the list of random sound commands and update
|
|
|
|
void C_SoundscapeSystem::UpdateRandomSounds( float gameTime )
|
|
|
|
{
|
|
|
|
if ( gameTime < m_nextRandomTime )
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_nextRandomTime = gameTime + 3600; // add some big time to check again (an hour)
|
|
|
|
|
|
|
|
for ( int i = m_randomSounds.Count()-1; i >= 0; i-- )
|
|
|
|
{
|
|
|
|
// time to play?
|
|
|
|
if ( gameTime >= m_randomSounds[i].nextPlayTime )
|
|
|
|
{
|
|
|
|
// UNDONE: add this in to fix range?
|
|
|
|
// float dt = m_randomSounds[i].nextPlayTime - gameTime;
|
|
|
|
PlayRandomSound( m_randomSounds[i] );
|
|
|
|
|
|
|
|
// now schedule the next occurrance
|
|
|
|
// UNDONE: add support for "play once" sounds? FastRemove() here.
|
|
|
|
m_randomSounds[i].nextPlayTime = gameTime + RandomInterval( m_randomSounds[i].time );
|
|
|
|
}
|
|
|
|
|
|
|
|
// update next time to check the queue
|
|
|
|
if ( m_randomSounds[i].nextPlayTime < m_nextRandomTime )
|
|
|
|
{
|
|
|
|
m_nextRandomTime = m_randomSounds[i].nextPlayTime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CON_COMMAND(cl_soundscape_printdebuginfo, "print soundscapes")
|
|
|
|
{
|
|
|
|
g_SoundscapeSystem.PrintDebugInfo();
|
|
|
|
}
|