summaryrefslogtreecommitdiff
path: root/engine/audio/private/snd_dma.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/audio/private/snd_dma.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'engine/audio/private/snd_dma.cpp')
-rw-r--r--engine/audio/private/snd_dma.cpp8401
1 files changed, 8401 insertions, 0 deletions
diff --git a/engine/audio/private/snd_dma.cpp b/engine/audio/private/snd_dma.cpp
new file mode 100644
index 0000000..137bce7
--- /dev/null
+++ b/engine/audio/private/snd_dma.cpp
@@ -0,0 +1,8401 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Main control for any streaming sound output device.
+//
+//===========================================================================//
+
+#include "audio_pch.h"
+#include "const.h"
+#include "cdll_int.h"
+#include "client_class.h"
+#include "icliententitylist.h"
+#include "tier0/vcrmode.h"
+#include "con_nprint.h"
+#include "tier0/icommandline.h"
+#include "vox_private.h"
+#include "../../traceinit.h"
+#include "../../cmd.h"
+#include "toolframework/itoolframework.h"
+#include "vstdlib/random.h"
+#include "vstdlib/jobthread.h"
+#include "vaudio/ivaudio.h"
+#include "../../client.h"
+#include "../../cl_main.h"
+#include "utldict.h"
+#include "mempool.h"
+#include "../../enginetrace.h" // for traceline
+#include "../../public/bspflags.h" // for traceline
+#include "../../public/gametrace.h" // for traceline
+#include "vphysics_interface.h" // for surface props
+#include "../../ispatialpartitioninternal.h" // for entity enumerator
+#include "../../debugoverlay.h"
+#include "icliententity.h"
+#include "../../cmodel_engine.h"
+#include "../../staticpropmgr.h"
+#include "../../server.h"
+#include "edict.h"
+#include "../../pure_server.h"
+#include "filesystem/IQueuedLoader.h"
+#include "voice.h"
+#if defined( _X360 )
+#include "xbox/xbox_console.h"
+#include "xmp.h"
+#endif
+
+#include "replay/iclientreplaycontext.h"
+#include "replay/ireplaymovierenderer.h"
+
+#include "video/ivideoservices.h"
+extern IVideoServices *g_pVideo;
+
+/*
+#include "gl_model_private.h"
+#include "world.h"
+#include "vphysics_interface.h"
+#include "client_class.h"
+#include "server_class.h"
+*/
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+///////////////////////////////////
+// DEBUGGING
+//
+// Turn this on to print channel output msgs.
+//
+//#define DEBUG_CHANNELS
+
+#define SNDLVL_TO_DIST_MULT( sndlvl ) ( sndlvl ? ((pow( 10.0f, snd_refdb.GetFloat() / 20 ) / pow( 10.0f, (float)sndlvl / 20 )) / snd_refdist.GetFloat()) : 0 )
+#define DIST_MULT_TO_SNDLVL( dist_mult ) (soundlevel_t)(int)( dist_mult ? ( 20 * log10( pow( 10.0f, snd_refdb.GetFloat() / 20 ) / (dist_mult * snd_refdist.GetFloat()) ) ) : 0 )
+
+extern ConVar dsp_spatial;
+extern IPhysicsSurfaceProps *physprop;
+
+extern bool IsReplayRendering();
+
+static void S_Play( const CCommand &args );
+static void S_PlayVol( const CCommand &args );
+void S_SoundList(void);
+static void S_Say ( const CCommand &args );
+void S_Update_(float);
+void S_StopAllSounds(bool clear);
+void S_StopAllSoundsC(void);
+void S_ShutdownMixThread();
+const char *GetClientClassname( SoundSource soundsource );
+
+float SND_GetGainObscured( channel_t *ch, bool fplayersound, bool flooping, bool bAttenuated );
+void DSP_ChangePresetValue( int idsp, int channel, int iproc, float value );
+bool DSP_CheckDspAutoEnabled( void );
+void DSP_SetDspAuto( int dsp_preset );
+float dB_To_Radius ( float db );
+int dsp_room_GetInt ( void );
+
+bool MXR_LoadAllSoundMixers( void );
+void MXR_ReleaseMemory( void );
+int MXR_GetMixGroupListFromDirName( const char *pDirname, byte *pList, int listMax );
+void MXR_GetMixGroupFromSoundsource( channel_t *pchan, SoundSource soundsource, soundlevel_t soundlevel);
+float MXR_GetVolFromMixGroup( int rgmixgroupid[8], int *plast_mixgroupid );
+char *MXR_GetGroupnameFromId( int mixgroupid );
+int MXR_GetMixgroupFromName( const char *pszgroupname );
+void MXR_DebugShowMixVolumes( void );
+#ifdef _DEBUG
+static void MXR_DebugSetMixGroupVolume( const CCommand &args );
+#endif //_DEBUG
+void MXR_UpdateAllDuckerVolumes( void );
+
+void ChannelSetVolTargets( channel_t *pch, int *pvolumes, int ivol_offset, int cvol );
+void ChannelUpdateVolXfade( channel_t *pch );
+void ChannelClearVolumes( channel_t *pch );
+float VOX_GetChanVol(channel_t *ch);
+void ConvertListenerVectorTo2D( Vector *pvforward, Vector *pvright );
+int ChannelGetMaxVol( channel_t *pch );
+
+// Forceably ends voice tweak mode (only occurs during snd_restart
+void VoiceTweak_EndVoiceTweakMode();
+bool VoiceTweak_IsStillTweaking();
+// Only does anything for voice tweak channel so if view entity changes it doesn't fade out to zero volume
+void Voice_Spatialize( channel_t *channel );
+
+// =======================================================================
+// Internal sound data & structures
+// =======================================================================
+
+channel_t channels[MAX_CHANNELS];
+
+int total_channels;
+CActiveChannels g_ActiveChannels;
+static double g_LastSoundFrame = 0.0f; // last full frame of sound
+static double g_LastMixTime = 0.0f; // last time we did mixing
+static float g_EstFrameTime = 0.1f; // estimated frame time running average
+
+// x360 override to fade out game music when the user is playing music through the dashboard
+static float g_DashboardMusicMixValue = 1.0f;
+static float g_DashboardMusicMixTarget = 1.0f;
+const float g_DashboardMusicFadeRate = 0.5f; // Fades one half full-scale volume per second (two seconds for complete fadeout)
+
+// sound mixers
+int g_csoundmixers = 0; // total number of soundmixers found
+int g_cgrouprules = 0; // total number of group rules found
+int g_cgroupclass = 0;
+
+// this is used to enable/disable music playback on x360 when the user selects his own soundtrack to play
+void S_EnableMusic( bool bEnable )
+{
+ if ( bEnable )
+ {
+ g_DashboardMusicMixTarget = 1.0f;
+ }
+ else
+ {
+ g_DashboardMusicMixTarget = 0.0f;
+ }
+}
+
+bool IsSoundSourceLocalPlayer( int soundsource )
+{
+ if ( soundsource == SOUND_FROM_UI_PANEL )
+ return true;
+
+ return ( soundsource == g_pSoundServices->GetViewEntity() );
+}
+
+CThreadMutex g_SndMutex;
+
+#define THREAD_LOCK_SOUND() AUTO_LOCK( g_SndMutex )
+
+const int MASK_BLOCK_AUDIO = CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW;
+
+void CActiveChannels::Add( channel_t *pChannel )
+{
+ Assert( pChannel->activeIndex == 0 );
+ m_list[m_count] = pChannel - channels;
+ m_count++;
+ pChannel->activeIndex = m_count;
+}
+
+void CActiveChannels::Remove( channel_t *pChannel )
+{
+ if ( pChannel->activeIndex == 0 )
+ return;
+ int activeIndex = pChannel->activeIndex - 1;
+ Assert( activeIndex >= 0 && activeIndex < m_count );
+ Assert( pChannel == &channels[m_list[activeIndex]] );
+ m_count--;
+ // Not the last one? Swap the last one with this one and fix its index
+ if ( activeIndex < m_count )
+ {
+ m_list[activeIndex] = m_list[m_count];
+ channels[m_list[activeIndex]].activeIndex = activeIndex+1;
+ }
+ pChannel->activeIndex = 0;
+}
+
+
+void CActiveChannels::GetActiveChannels( CChannelList &list )
+{
+ list.m_count = m_count;
+ if ( m_count )
+ {
+ Q_memcpy( list.m_list, m_list, sizeof(m_list[0])*m_count );
+ }
+
+ for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i )
+ {
+ paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i );
+ if ( pSpecialBuffer->nSpecialDSP != 0 )
+ {
+ list.m_nSpecialDSPs.AddToTail( pSpecialBuffer->nSpecialDSP );
+ }
+ }
+
+ list.m_hasSpeakerChannels = true;
+ list.m_has11kChannels = true;
+ list.m_has22kChannels = true;
+ list.m_has44kChannels = true;
+ list.m_hasDryChannels = true;
+}
+
+void CActiveChannels::Init()
+{
+ m_count = 0;
+}
+
+bool snd_initialized = false;
+
+Vector listener_origin;
+static Vector listener_forward;
+Vector listener_right;
+static Vector listener_up;
+static bool s_bIsListenerUnderwater;
+static vec_t sound_nominal_clip_dist=SOUND_NORMAL_CLIP_DIST;
+
+// @TODO (toml 05-08-02): put this somewhere more reasonable
+vec_t S_GetNominalClipDist()
+{
+ return sound_nominal_clip_dist;
+}
+
+int g_soundtime = 0; // sample PAIRS output since start
+int g_paintedtime = 0; // sample PAIRS mixed since start
+
+float g_ReplaySoundTimeFracAccumulator = 0.0f; // Used by replay
+
+float g_ClockSyncArray[NUM_CLOCK_SYNCS] = {0};
+int g_SoundClockPaintTime[NUM_CLOCK_SYNCS] = {0};
+
+// default 10ms
+ConVar snd_delay_sound_shift("snd_delay_sound_shift","0.01");
+// this forces the clock to resync on the next delayed/sync sound
+void S_SyncClockAdjust( clocksync_index_t syncIndex )
+{
+ g_ClockSyncArray[syncIndex] = 0;
+ g_SoundClockPaintTime[syncIndex] = 0;
+}
+
+float S_ComputeDelayForSoundtime( float soundtime, clocksync_index_t syncIndex )
+{
+ // reset clock and return 0
+ if ( g_ClockSyncArray[syncIndex] == 0 )
+ {
+ // Put the current time marker one tick back to impose a minimum delay on the first sample
+ // this shifts the drift over so the sounds are more likely to delay (rather than skip)
+ // over the burst
+ // NOTE: The first sound after a sync MUST have a non-zero delay for the delay channel
+ // detection logic to work (otherwise we keep resetting the clock)
+ g_ClockSyncArray[syncIndex] = soundtime - host_state.interval_per_tick;
+ g_SoundClockPaintTime[syncIndex] = g_paintedtime;
+ }
+
+ // how much time has passed in the game since we did a clock sync?
+ float gameDeltaTime = soundtime - g_ClockSyncArray[syncIndex];
+
+ // how many samples have been mixed since we did a clock sync?
+ int paintedSamples = g_paintedtime - g_SoundClockPaintTime[syncIndex];
+ int dmaSpeed = g_AudioDevice->DeviceDmaSpeed();
+ int gameSamples = (gameDeltaTime * dmaSpeed);
+ int delaySamples = gameSamples - paintedSamples;
+ float delay = delaySamples / float(dmaSpeed);
+
+ if ( gameDeltaTime < 0 || fabs(delay) > 0.500f )
+ {
+ // Note that the equations assume a correlation between game time and real time
+ // some kind of clock error. This can happen with large host_timescale or when the
+ // framerate hitches drastically (game time is a smaller clamped value wrt real time).
+ // The current sync estimate has probably drifted due to this or some other problem, recompute.
+ //Msg("Clock ERROR!: %.2f %.2f\n", gameDeltaTime, delay);
+ S_SyncClockAdjust(syncIndex);
+ return 0;
+ }
+ return delay + snd_delay_sound_shift.GetFloat();
+}
+
+static int s_buffers = 0;
+static int s_oldsampleOutCount = 0;
+static float s_lastsoundtime = 0.0f;
+
+bool s_bOnLoadScreen = false;
+
+static CClassMemoryPool< CSfxTable > s_SoundPool( MAX_SFX );
+struct SfxDictEntry
+{
+ CSfxTable *pSfx;
+};
+
+static CUtlMap< FileNameHandle_t, SfxDictEntry > s_Sounds( 0, 0, DefLessFunc( FileNameHandle_t ) );
+
+class CDummySfx : public CSfxTable
+{
+public:
+ virtual const char *getname()
+ {
+ return name;
+ }
+
+ void setname( const char *pName )
+ {
+ Q_strncpy( name, pName, sizeof( name ) );
+ OnNameChanged(name);
+ }
+
+private:
+ char name[MAX_PATH];
+};
+
+static CDummySfx dummySfx;
+
+// returns true if ok to procede with TraceRay calls
+bool SND_IsInGame( void )
+{
+ return cl.IsActive();
+}
+
+
+CSfxTable::CSfxTable()
+{
+ m_namePoolIndex = s_Sounds.InvalidIndex();
+ pSource = NULL;
+ m_bUseErrorFilename = false;
+ m_bIsUISound = false;
+ m_bIsLateLoad = false;
+ m_bMixGroupsCached = false;
+ m_pDebugName = NULL;
+}
+
+
+void CSfxTable::SetNamePoolIndex( int index )
+{
+ m_namePoolIndex = index;
+ if ( m_namePoolIndex != s_Sounds.InvalidIndex() )
+ {
+ OnNameChanged(getname());
+ }
+#ifdef _DEBUG
+ m_pDebugName = strdup( getname() );
+#endif
+}
+
+void CSfxTable::OnNameChanged( const char *pName )
+{
+ if ( pName && g_cgrouprules )
+ {
+ char szString[MAX_PATH];
+ Q_strncpy( szString, pName, sizeof(szString) );
+ Q_FixSlashes( szString, '/' );
+ m_mixGroupCount = MXR_GetMixGroupListFromDirName( szString, m_mixGroupList, ARRAYSIZE(m_mixGroupList) );
+ m_bMixGroupsCached = true;
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose: Wrapper for sfxtable->getname()
+// Output : char const
+//-----------------------------------------------------------------------------
+const char *CSfxTable::getname()
+{
+ if ( s_Sounds.InvalidIndex() != m_namePoolIndex )
+ {
+ char* pString = tmpstr512();
+ if ( g_pFileSystem )
+ g_pFileSystem->String( s_Sounds.Key( m_namePoolIndex ), pString, 512 );
+ else
+ {
+ pString[0] = 0;
+ }
+ return pString;
+ }
+
+ return NULL;
+}
+
+FileNameHandle_t CSfxTable::GetFileNameHandle()
+{
+ if ( s_Sounds.InvalidIndex() != m_namePoolIndex )
+ {
+ return s_Sounds.Key( m_namePoolIndex );
+ }
+ return NULL;
+}
+
+const char *CSfxTable::GetFileName()
+{
+ if ( IsX360() && m_bUseErrorFilename )
+ {
+ // Redirecting error sounds to a valid empty wave, prevents a bad loading retry pattern during gameplay
+ // which may event sounds skipped by preload, because they don't exist.
+ return "common/null.wav";
+ }
+
+ const char *pName = getname();
+ return pName ? PSkipSoundChars( pName ) : NULL;
+}
+
+bool CSfxTable::IsPrecachedSound()
+{
+ const char *pName = getname();
+
+ if ( sv.IsActive() )
+ {
+ // Server uses zero to mark invalid sounds
+ return sv.LookupSoundIndex( pName ) != 0 ? true : false;
+ }
+
+ // Client uses -1
+ // WE SHOULD FIX THIS!!!
+ return ( cl.LookupSoundIndex( pName ) != -1 ) ? true : false;
+}
+
+float g_DuckScale = 1.0f;
+
+// Structure used for fading in and out client sound volume.
+typedef struct
+{
+ float initial_percent;
+
+ // How far to adjust client's volume down by.
+ float percent;
+
+ // GetHostTime() when we started adjusting volume
+ float starttime;
+
+ // # of seconds to get to faded out state
+ float fadeouttime;
+ // # of seconds to hold
+ float holdtime;
+ // # of seconds to restore
+ float fadeintime;
+} soundfade_t;
+
+static soundfade_t soundfade; // Client sound fading singleton object
+
+// 0)headphones 2)stereo speakers 4)quad 5)5point1
+// autodetected from windows settings
+ConVar snd_surround( "snd_surround_speakers", "-1", FCVAR_INTERNAL_USE );
+ConVar snd_legacy_surround( "snd_legacy_surround", "0", FCVAR_ARCHIVE );
+ConVar snd_noextraupdate( "snd_noextraupdate", "0" );
+ConVar snd_show( "snd_show", "0", FCVAR_CHEAT, "Show sounds info" );
+ConVar snd_visualize ("snd_visualize", "0", FCVAR_CHEAT, "Show sounds location in world" );
+ConVar snd_pitchquality( "snd_pitchquality", "1", FCVAR_ARCHIVE ); // 1) use high quality pitch shifters
+
+// master volume
+static ConVar volume( "volume", "1.0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Sound volume", true, 0.0f, true, 1.0f );
+// user configurable music volume
+ConVar snd_musicvolume( "snd_musicvolume", "1.0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Music volume", true, 0.0f, true, 1.0f );
+
+ConVar snd_mixahead( "snd_mixahead", "0.1", FCVAR_ARCHIVE );
+ConVar snd_mix_async( "snd_mix_async", "0" );
+#ifdef _DEBUG
+static ConCommand snd_mixvol("snd_mixvol", MXR_DebugSetMixGroupVolume, "Set named Mixgroup to mix volume.");
+#endif
+
+// vaudio DLL
+IVAudio *vaudio = NULL;
+CSysModule *g_pVAudioModule = NULL;
+
+//-----------------------------------------------------------------------------
+// Resource loading for sound
+//-----------------------------------------------------------------------------
+class CResourcePreloadSound : public CResourcePreload
+{
+public:
+ CResourcePreloadSound() : m_SoundNames( 0, 0, true )
+ {
+ }
+
+ virtual bool CreateResource( const char *pName )
+ {
+ CSfxTable *pSfx = S_PrecacheSound( pName );
+ if ( !pSfx )
+ {
+ return false;
+ }
+
+ m_SoundNames.AddString( pSfx->GetFileName() );
+ return true;
+ }
+
+ virtual void PurgeUnreferencedResources()
+ {
+ bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0;
+
+ for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
+ {
+ // the master sound table grows forever
+ // remove sound sources from the master sound table that were not in the preload list
+ CSfxTable *pSfx = s_Sounds[i].pSfx;
+ if ( pSfx && pSfx->pSource )
+ {
+ if ( pSfx->m_bIsUISound )
+ {
+ // never purge ui
+ continue;
+ }
+
+ UtlSymId_t symbol = m_SoundNames.Find( pSfx->GetFileName() );
+ if ( symbol == UTL_INVAL_SYMBOL )
+ {
+ // sound was not part of preload, purge it
+ if ( bSpew )
+ {
+ Msg( "CResourcePreloadSound: Purging: %s\n", pSfx->GetFileName() );
+ }
+
+ pSfx->pSource->CacheUnload();
+ delete pSfx->pSource;
+ pSfx->pSource = NULL;
+ }
+ }
+ }
+
+ m_SoundNames.RemoveAll();
+
+ if ( !g_pQueuedLoader->IsSameMapLoading() )
+ {
+ wavedatacache->Flush();
+ }
+ }
+
+ virtual void PurgeAll()
+ {
+ bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0;
+
+ for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
+ {
+ // the master sound table grows forever
+ // remove sound sources from the master sound table that were not in the preload list
+ CSfxTable *pSfx = s_Sounds[i].pSfx;
+ if ( pSfx && pSfx->pSource )
+ {
+ if ( pSfx->m_bIsUISound )
+ {
+ // never purge ui
+ if ( bSpew )
+ {
+ Msg( "CResourcePreloadSound: Skipping: %s\n", pSfx->GetFileName() );
+ }
+ continue;
+ }
+
+ // sound was not part of preload, purge it
+ if ( bSpew )
+ {
+ Msg( "CResourcePreloadSound: Purging: %s\n", pSfx->GetFileName() );
+ }
+
+ pSfx->pSource->CacheUnload();
+ delete pSfx->pSource;
+ pSfx->pSource = NULL;
+ }
+ }
+
+ m_SoundNames.RemoveAll();
+
+ wavedatacache->Flush();
+ }
+
+private:
+ CUtlSymbolTable m_SoundNames;
+};
+static CResourcePreloadSound s_ResourcePreloadSound;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float S_GetMasterVolume( void )
+{
+ float scale = 1.0f;
+ if ( soundfade.percent != 0 )
+ {
+ scale = clamp( (float)soundfade.percent / 100.0f, 0.0f, 1.0f );
+ scale = 1.0f - scale;
+ }
+ return volume.GetFloat() * scale;
+}
+
+
+void S_SoundInfo_f(void)
+{
+ if ( !g_AudioDevice->IsActive() )
+ {
+ Msg( "Sound system not started\n" );
+ return;
+ }
+
+ Msg( "Sound Device: %s\n", g_AudioDevice->DeviceName() );
+ Msg( " Channels: %d\n", g_AudioDevice->DeviceChannels() );
+ Msg( " Samples: %d\n", g_AudioDevice->DeviceSampleCount() );
+ Msg( " Bits/Sample: %d\n", g_AudioDevice->DeviceSampleBits() );
+ Msg( " Rate: %d\n", g_AudioDevice->DeviceDmaSpeed() );
+ Msg( "total_channels: %d\n", total_channels);
+
+ if ( IsX360() )
+ {
+ // dump a glimpse of the mixing state
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+
+ Msg( "\nActive Channels: (%d)\n", list.Count() );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = list.GetChannel(i);
+ Msg( "%s (Mixer: 0x%p)\n", pChannel->sfx->GetFileName(), pChannel->pMixer );
+ }
+ }
+}
+
+
+/*
+================
+S_Startup
+================
+*/
+
+void S_Startup( void )
+{
+ if ( !snd_initialized )
+ return;
+
+ if ( !g_AudioDevice || g_AudioDevice == Audio_GetNullDevice() )
+ {
+ g_AudioDevice = IAudioDevice::AutoDetectInit( CommandLine()->CheckParm( "-wavonly" ) != 0 );
+ if ( !g_AudioDevice )
+ {
+ Error( "Unable to init audio" );
+ }
+ }
+}
+
+static ConCommand play("play", S_Play, "Play a sound.", FCVAR_SERVER_CAN_EXECUTE );
+static ConCommand playflush( "playflush", S_Play, "Play a sound, reloading from disk in case of changes." );
+static ConCommand playvol( "playvol", S_PlayVol, "Play a sound at a specified volume." );
+static ConCommand speak( "speak", S_Say, "Play a constructed sentence." );
+static ConCommand stopsound( "stopsound", S_StopAllSoundsC, 0, FCVAR_CHEAT); // Marked cheat because it gives an advantage to players minimising ambient noise.
+static ConCommand soundlist( "soundlist", S_SoundList, "List all known sounds." );
+static ConCommand soundinfo( "soundinfo", S_SoundInfo_f, "Describe the current sound device." );
+
+bool IsValidSampleRate( int rate )
+{
+ return rate == SOUND_11k || rate == SOUND_22k || rate == SOUND_44k;
+}
+
+void VAudioInit()
+{
+ if ( IsPC() )
+ {
+ if ( !IsPosix() )
+ {
+ // vaudio_miles.dll will load this...
+ g_pFileSystem->GetLocalCopy( "mss32.dll" );
+ }
+
+ g_pVAudioModule = FileSystem_LoadModule( "vaudio_miles" );
+ if ( g_pVAudioModule )
+ {
+ CreateInterfaceFn vaudioFactory = Sys_GetFactory( g_pVAudioModule );
+ vaudio = (IVAudio *)vaudioFactory( VAUDIO_INTERFACE_VERSION, NULL );
+ }
+ }
+}
+
+/*
+================
+S_Init
+================
+*/
+void S_Init( void )
+{
+ if ( sv.IsDedicated() && !CommandLine()->CheckParm( "-forcesound" ) )
+ return;
+
+ DevMsg( "Sound Initialization: Start\n" );
+
+ // KDB: init sentence array
+ TRACEINIT( VOX_Init(), VOX_Shutdown() );
+
+ VAudioInit();
+
+ if ( CommandLine()->CheckParm( "-nosound" ) )
+ {
+ g_AudioDevice = Audio_GetNullDevice();
+ TRACEINIT( audiosourcecache->Init( host_parms.memsize >> 2 ), audiosourcecache->Shutdown() );
+ return;
+ }
+
+ snd_initialized = true;
+
+ g_ActiveChannels.Init();
+ S_Startup();
+
+ MIX_InitAllPaintbuffers();
+
+ SND_InitScaletable();
+
+ MXR_LoadAllSoundMixers();
+
+ S_StopAllSounds( true );
+
+ TRACEINIT( audiosourcecache->Init( host_parms.memsize >> 2 ), audiosourcecache->Shutdown() );
+
+ AllocDsps( true );
+
+ if ( IsX360() )
+ {
+ g_pQueuedLoader->InstallLoader( RESOURCEPRELOAD_SOUND, &s_ResourcePreloadSound );
+ }
+
+ DevMsg( "Sound Initialization: Finish, Sampling Rate: %i\n", g_AudioDevice->DeviceDmaSpeed() );
+
+#ifdef _X360
+ BOOL bPlaybackControl;
+ // get initial state of the x360 media player
+ if ( XMPTitleHasPlaybackControl( &bPlaybackControl ) == ERROR_SUCCESS )
+ {
+ S_EnableMusic(bPlaybackControl!=0);
+ }
+
+ Assert( g_pVideo != NULL );
+
+ if ( g_pVideo != NULL )
+ {
+ if ( g_pVideo->SoundDeviceCommand( VideoSoundDeviceOperation::HOOK_X_AUDIO, NULL ) != VideoResult::SUCCESS )
+ {
+ Assert( 0 );
+ }
+ }
+#endif
+}
+
+
+// =======================================================================
+// Shutdown sound engine
+// =======================================================================
+void S_Shutdown(void)
+{
+#if !defined( _X360 )
+ if ( VoiceTweak_IsStillTweaking() )
+ {
+ VoiceTweak_EndVoiceTweakMode();
+ }
+#endif
+
+ S_StopAllSounds( true );
+ S_ShutdownMixThread();
+
+ TRACESHUTDOWN( audiosourcecache->Shutdown() );
+
+ SNDDMA_Shutdown();
+
+ for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
+ {
+ if ( s_Sounds[i].pSfx )
+ {
+ delete s_Sounds[i].pSfx->pSource;
+ s_Sounds[i].pSfx->pSource = NULL;
+ }
+ }
+ s_Sounds.RemoveAll();
+ s_SoundPool.Clear();
+
+ // release DSP resources
+ FreeDsps( true );
+
+ MXR_ReleaseMemory();
+
+ // release sentences resources
+ TRACESHUTDOWN( VOX_Shutdown() );
+
+ if ( IsPC() )
+ {
+ // shutdown vaudio
+ if ( vaudio )
+ delete vaudio;
+ FileSystem_UnloadModule( g_pVAudioModule );
+ g_pVAudioModule = NULL;
+ vaudio = NULL;
+ }
+
+ MIX_FreeAllPaintbuffers();
+ snd_initialized = false;
+ g_paintedtime = 0;
+ g_soundtime = 0;
+ g_ReplaySoundTimeFracAccumulator = 0.0f;
+ s_buffers = 0;
+ s_oldsampleOutCount = 0;
+ s_lastsoundtime = 0.0f;
+#if !defined( _X360 )
+ Voice_Deinit();
+#endif
+}
+
+bool S_IsInitted()
+{
+ return snd_initialized;
+}
+
+// =======================================================================
+// Load a sound
+// =======================================================================
+
+//-----------------------------------------------------------------------------
+// Return sfx and set pfInCache to 1 if
+// name is in name cache. Otherwise, alloc
+// a new spot in name cache and return 0
+// in pfInCache.
+//-----------------------------------------------------------------------------
+CSfxTable *S_FindName( const char *szName, int *pfInCache )
+{
+ int i;
+ CSfxTable *sfx = NULL;
+ char szBuff[MAX_PATH];
+ const char *pName;
+
+ if ( !szName )
+ {
+ Error( "S_FindName: NULL\n" );
+ }
+
+ pName = szName;
+ if ( IsX360() )
+ {
+ Q_strncpy( szBuff, pName, sizeof( szBuff ) );
+ int len = Q_strlen( szBuff )-4;
+ if ( len > 0 && !Q_strnicmp( szBuff+len, ".mp3", 4 ) )
+ {
+ // convert unsupported .mp3 to .wav
+ Q_strcpy( szBuff+len, ".wav" );
+ }
+ pName = szBuff;
+
+ if ( pName[0] == CHAR_STREAM )
+ {
+ // streaming (or not) is hardcoded to alternate criteria
+ // prevent the same sound from creating disparate instances
+ pName++;
+ }
+ }
+
+ // see if already loaded
+ FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pName );
+ i = s_Sounds.Find( fnHandle );
+ if ( i != s_Sounds.InvalidIndex() )
+ {
+ sfx = s_Sounds[i].pSfx;
+ Assert( sfx );
+ if ( pfInCache )
+ {
+ // indicate whether or not sound is currently in the cache.
+ *pfInCache = ( sfx->pSource && sfx->pSource->IsCached() ) ? 1 : 0;
+ }
+ return sfx;
+ }
+ else
+ {
+ SfxDictEntry entry;
+ entry.pSfx = ( CSfxTable * )s_SoundPool.Alloc();
+
+ Assert( entry.pSfx );
+
+ i = s_Sounds.Insert( fnHandle, entry );
+ sfx = s_Sounds[i].pSfx;
+
+ sfx->SetNamePoolIndex( i );
+ sfx->pSource = NULL;
+
+ if ( pfInCache )
+ {
+ *pfInCache = 0;
+ }
+ }
+ return sfx;
+}
+
+//-----------------------------------------------------------------------------
+// S_LoadSound
+//
+// Check to see if wave data is in the cache. If so, return pointer to data.
+// If not, allocate cache space for wave data, load wave file into temporary heap
+// space, and dump/convert file data into cache.
+//-----------------------------------------------------------------------------
+double g_flAccumulatedSoundLoadTime = 0.0f;
+CAudioSource *S_LoadSound( CSfxTable *pSfx, channel_t *ch )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ VPROF("S_LoadSound");
+ if ( !pSfx->pSource )
+ {
+ if ( IsX360() )
+ {
+ if ( SND_IsInGame() && !g_pQueuedLoader->IsMapLoading() )
+ {
+ // sound should be present (due to reslists), but NOT allowing a load hitch during gameplay
+ // loading a sound during gameplay is a bad experience, causes a very expensive sync i/o to fetch the header
+ // and in the case of a memory wave, the actual audio data
+ bool bFound = false;
+ if ( !pSfx->m_bIsLateLoad )
+ {
+ if ( pSfx->getname() != PSkipSoundChars( pSfx->getname() ) )
+ {
+ // the sound might already exist as an undecorated audio source
+ FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pSfx->GetFileName() );
+ int i = s_Sounds.Find( fnHandle );
+ if ( i != s_Sounds.InvalidIndex() )
+ {
+ CSfxTable *pOtherSfx = s_Sounds[i].pSfx;
+ Assert( pOtherSfx );
+ CAudioSource *pOtherSource = pOtherSfx->pSource;
+ if ( pOtherSource && pOtherSource->IsCached() )
+ {
+ // Can safely let the "load" continue because the headers are expected to be in the preload
+ // that are now persisted and the wave data cache will find an existing audio buffer match,
+ // so no sync i/o should occur from either.
+ bFound = true;
+ }
+ }
+ }
+
+ if ( !bFound )
+ {
+ // warn once
+ DevWarning( "S_LoadSound: Late load '%s', skipping.\n", pSfx->getname() );
+ pSfx->m_bIsLateLoad = true;
+ }
+ }
+
+ if ( !bFound )
+ {
+ return NULL;
+ }
+ }
+ else if ( pSfx->m_bIsLateLoad )
+ {
+ // outside of gameplay, let the load happen
+ pSfx->m_bIsLateLoad = false;
+ }
+ }
+
+ double st = Plat_FloatTime();
+
+ bool bStream = false;
+ bool bUserVox = false;
+
+ // sound chars can explicitly categorize usage
+ bStream = TestSoundChar( pSfx->getname(), CHAR_STREAM );
+ if ( !bStream )
+ {
+ bUserVox = TestSoundChar( pSfx->getname(), CHAR_USERVOX );
+ }
+
+ // override streaming
+ if ( IsX360() )
+ {
+ const char *s_CriticalSounds[] =
+ {
+ "common",
+ "items",
+ "physics",
+ "player",
+ "ui",
+ "weapons",
+ };
+
+ // stream everything but critical sounds
+ bStream = true;
+ const char *pFileName = pSfx->GetFileName();
+ for ( int i = 0; i<ARRAYSIZE( s_CriticalSounds ); i++ )
+ {
+ size_t len = strlen( s_CriticalSounds[i] );
+ if ( !Q_strnicmp( pFileName, s_CriticalSounds[i], len ) && ( pFileName[len] == '\\' || pFileName[len] == '/' ) )
+ {
+ // never stream these, regardless of sound chars
+ bStream = false;
+ break;
+ }
+ }
+ }
+
+ if ( bStream )
+ {
+ // setup as a streaming resource
+ pSfx->pSource = Audio_CreateStreamedWave( pSfx );
+ }
+ else
+ {
+ if ( bUserVox )
+ {
+ if ( !IsX360() )
+ {
+ pSfx->pSource = Voice_SetupAudioSource( ch->soundsource, ch->entchannel );
+ }
+ else
+ {
+ // not supporting
+ Assert( 0 );
+ }
+ }
+ else
+ {
+ // load all into memory directly
+ pSfx->pSource = Audio_CreateMemoryWave( pSfx );
+ }
+ }
+
+ double ed = Plat_FloatTime();
+ g_flAccumulatedSoundLoadTime += ( ed - st );
+ }
+ else
+ {
+ pSfx->pSource->CheckAudioSourceCache();
+ }
+
+ if ( !pSfx->pSource )
+ {
+ return NULL;
+ }
+
+ // first time to load? Create the mixer
+ if ( ch && !ch->pMixer )
+ {
+ ch->pMixer = pSfx->pSource->CreateMixer( ch->initialStreamPosition );
+ if ( !ch->pMixer )
+ {
+ return NULL;
+ }
+ }
+
+ return pSfx->pSource;
+}
+
+//-----------------------------------------------------------------------------
+// S_PrecacheSound
+//
+// Reserve space for the name of the sound in a global array.
+// Load the data for the non-streaming sound. Streaming sounds
+// defer loading of data until just before playback.
+//-----------------------------------------------------------------------------
+CSfxTable *S_PrecacheSound( const char *name )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ if ( !g_AudioDevice )
+ return NULL;
+
+ if ( !g_AudioDevice->IsActive() )
+ return NULL;
+
+ CSfxTable *sfx = S_FindName( name, NULL );
+ if ( sfx )
+ {
+ // cache sound
+ S_LoadSound( sfx, NULL );
+ }
+ else
+ {
+ Assert( !"S_PrecacheSound: Failed to create sfx" );
+ }
+
+ return sfx;
+}
+
+
+void S_InternalReloadSound( CSfxTable *sfx )
+{
+ if ( !sfx || !sfx->pSource )
+ return;
+
+ sfx->pSource->CacheUnload();
+
+ delete sfx->pSource;
+ sfx->pSource = NULL;
+
+ char pExt[10];
+ Q_ExtractFileExtension( sfx->getname(), pExt, sizeof(pExt) );
+ int nSource = !Q_stricmp( pExt, "mp3" ) ? CAudioSource::AUDIO_SOURCE_MP3 : CAudioSource::AUDIO_SOURCE_WAV;
+// audiosourcecache->RebuildCacheEntry( nSource, sfx->IsPrecachedSound(), sfx );
+ audiosourcecache->GetInfo( nSource, sfx->IsPrecachedSound(), sfx ); // Do a size/date check and rebuild the cache entry if necessary.
+}
+
+
+//-----------------------------------------------------------------------------
+// Refresh a sound in the cache
+//-----------------------------------------------------------------------------
+void S_ReloadSound( const char *name )
+{
+ if ( IsX360() )
+ {
+ // not supporting
+ Assert( 0 );
+ return;
+ }
+
+ if ( !g_AudioDevice )
+ return;
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ CSfxTable *sfx = S_FindName( name, NULL );
+#ifdef _DEBUG
+ if ( sfx )
+ {
+ Assert( Q_stricmp( sfx->getname(), name ) == 0 );
+ }
+#endif
+
+ S_InternalReloadSound( sfx );
+}
+
+
+// See comments on CL_HandlePureServerWhitelist for details of what we're doing here.
+void S_ReloadFilesInList( IFileList *pFilesToReload )
+{
+ if ( !IsPC() )
+ return;
+
+ S_StopAllSounds( true );
+ wavedatacache->Flush();
+ audiosourcecache->ForceRecheckDiskInfo(); // Force all cached audio data to recheck size/date info next time it's accessed.
+
+
+ CUtlVector< CSfxTable * > processed;
+
+ int iLast = s_Sounds.LastInorder();
+ for ( int i = s_Sounds.FirstInorder(); i != iLast; i = s_Sounds.NextInorder( i ) )
+ {
+ FileNameHandle_t fnHandle = s_Sounds.Key( i );
+ char filename[MAX_PATH * 3];
+ if ( !g_pFileSystem->String( fnHandle, filename, sizeof( filename ) ) )
+ {
+ Assert( !"S_HandlePureServerWhitelist - can't get a filename." );
+ continue;
+ }
+
+ // If the file isn't cached in yet, then the filesystem hasn't touched its file, so don't bother.
+ CSfxTable *sfx = s_Sounds[i].pSfx;
+ if ( sfx && ( processed.Find( sfx ) == processed.InvalidIndex() ) )
+ {
+ char fullFilename[MAX_PATH*2];
+ if ( IsSoundChar( filename[0] ) )
+ Q_snprintf( fullFilename, sizeof( fullFilename ), "sound/%s", &filename[1] );
+ else
+ Q_snprintf( fullFilename, sizeof( fullFilename ), "sound/%s", filename );
+
+
+ if ( !pFilesToReload->IsFileInList( fullFilename ) )
+ continue;
+
+ processed.AddToTail( sfx );
+
+ S_InternalReloadSound( sfx );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Unfortunate confusing terminology.
+// Here prefetching means hinting to the audio source (which may be a stream)
+// to get its async data in flight.
+//-----------------------------------------------------------------------------
+void S_PrefetchSound( char const *name, bool bPlayOnce )
+{
+ CSfxTable *sfx;
+
+ if ( !g_AudioDevice )
+ return;
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ sfx = S_FindName( name, NULL );
+ if ( sfx )
+ {
+ // cache sound
+ S_LoadSound( sfx, NULL );
+ }
+
+ if ( !sfx || !sfx->pSource )
+ {
+ return;
+ }
+
+ // hint the sound to start loading
+ sfx->pSource->Prefetch();
+
+ if ( bPlayOnce )
+ {
+ sfx->pSource->SetPlayOnce( true );
+ }
+}
+
+void S_MarkUISound( CSfxTable *pSfx )
+{
+ pSfx->m_bIsUISound = true;
+}
+
+unsigned int RemainingSamples( channel_t *pChannel )
+{
+ if ( !pChannel || !pChannel->sfx || !pChannel->sfx->pSource )
+ return 0;
+
+ unsigned int timeleft = pChannel->sfx->pSource->SampleCount();
+
+ if ( pChannel->sfx->pSource->IsLooped() )
+ {
+ return pChannel->sfx->pSource->SampleRate();
+ }
+
+ if ( pChannel->pMixer )
+ {
+ timeleft -= pChannel->pMixer->GetSamplePosition();
+ }
+
+ return timeleft;
+}
+
+// chooses the voice stealing algorithm
+ConVar voice_steal("voice_steal", "2");
+
+/*
+=================
+SND_StealDynamicChannel
+Select a channel from the dynamic channel allocation area. For the given entity,
+override any other sound playing on the same channel (see code comments below for
+exceptions).
+=================
+*/
+channel_t *SND_StealDynamicChannel(SoundSource soundsource, int entchannel, const Vector &origin, CSfxTable *sfx, float flDelay, bool bDoNotOverwriteExisting)
+{
+ int canSteal[MAX_DYNAMIC_CHANNELS];
+ int canStealCount = 0;
+
+ int sameSoundCount = 0;
+ unsigned int sameSoundRemaining = 0xFFFFFFFF;
+ int sameSoundIndex = -1;
+ int sameVol = 0xFFFF;
+ int availableChannel = -1;
+ bool bDelaySame = false;
+
+ int nExactMatch[MAX_DYNAMIC_CHANNELS];
+ int nExactCount = 0;
+ // first pass to replace sounds on same ent/channel, and search for free or stealable channels otherwise
+ for ( int ch_idx = 0; ch_idx < MAX_DYNAMIC_CHANNELS ; ch_idx++)
+ {
+ channel_t *ch = &channels[ch_idx];
+
+ if ( ch->activeIndex )
+ {
+ // channel CHAN_AUTO never overrides sounds on same channel
+ if ( entchannel != CHAN_AUTO )
+ {
+ int checkChannel = entchannel;
+ if ( checkChannel == -1 )
+ {
+ if ( ch->entchannel != CHAN_STREAM && ch->entchannel != CHAN_VOICE && ch->entchannel != CHAN_VOICE2 )
+ {
+ checkChannel = ch->entchannel;
+ }
+ }
+ if ( ch->soundsource == soundsource && (soundsource != -1) && ch->entchannel == checkChannel )
+ {
+ // we found an exact match for this entity and this channel, but the sound we want to play is considered
+ // low priority so instead of stomping this entry pretend we couldn't find a free slot to play and let
+ // the existing sound keep going
+ if ( bDoNotOverwriteExisting )
+ return NULL;
+
+ if ( ch->flags.delayed_start )
+ {
+ nExactMatch[nExactCount] = ch_idx;
+ nExactCount++;
+ continue;
+ }
+ return ch; // always override sound from same entity
+ }
+ }
+
+ // Never steal the channel of a streaming sound that is currently playing or
+ // voice over IP data that is playing or any sound on CHAN_VOICE( acting )
+ if ( ch->entchannel == CHAN_STREAM || ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE2 )
+ continue;
+
+ // don't let monster sounds override player sounds
+ if ( g_pSoundServices->IsPlayer( ch->soundsource ) && !g_pSoundServices->IsPlayer(soundsource) )
+ continue;
+
+ if ( ch->sfx == sfx )
+ {
+ bDelaySame = ch->flags.delayed_start ? true : bDelaySame;
+ sameSoundCount++;
+ int maxVolume = ChannelGetMaxVol( ch );
+ unsigned int remaining = RemainingSamples(ch);
+ if ( maxVolume < sameVol || (maxVolume == sameVol && remaining < sameSoundRemaining) )
+ {
+ sameSoundIndex = ch_idx;
+ sameVol = maxVolume;
+ sameSoundRemaining = remaining;
+ }
+ }
+ canSteal[canStealCount++] = ch_idx;
+ }
+ else
+ {
+ if ( availableChannel < 0 )
+ {
+ availableChannel = ch_idx;
+ }
+ }
+ }
+
+
+ // coalesce the timeline for this channel
+ if ( nExactCount > 0 )
+ {
+ uint nFreeSampleTime = g_paintedtime + (flDelay * SOUND_DMA_SPEED);
+ channel_t *pReturn = &channels[nExactMatch[0]];
+ uint nMinRemaining = RemainingSamples( pReturn );
+ if ( pReturn->nFreeChannelAtSampleTime == 0 || pReturn->nFreeChannelAtSampleTime > nFreeSampleTime )
+ {
+ pReturn->nFreeChannelAtSampleTime = nFreeSampleTime;
+ }
+ for ( int i = 1; i < nExactCount; i++ )
+ {
+ channel_t *pChannel = &channels[nExactMatch[i]];
+ if ( pChannel->nFreeChannelAtSampleTime == 0 || pChannel->nFreeChannelAtSampleTime > nFreeSampleTime )
+ {
+ pChannel->nFreeChannelAtSampleTime = nFreeSampleTime;
+ }
+ uint nRemain = RemainingSamples( pChannel );
+ if ( nRemain < nMinRemaining )
+ {
+ pReturn = pChannel;
+ nMinRemaining = nRemain;
+ }
+ }
+ // if there's only one, mark it to be freed but don't reuse it.
+ // otherwise mark all others to be freed and use the closest one to being done
+ if ( nExactCount > 1 )
+ {
+ return pReturn;
+ }
+ }
+
+ // Limit the number of times a given sfx/wave can play simultaneously
+ if ( voice_steal.GetInt() > 1 && sameSoundIndex >= 0 )
+ {
+ // if sounds of this type are normally delayed, then add an extra slot for stealing
+ // NOTE: In HL2 these are usually NPC gunshot sounds - and stealing too soon will cut
+ // them off early. This is a safe heuristic to avoid that problem. There's probably a better
+ // long-term solution involving only counting channels that are actually going to play (delay included)
+ // at the same time as this one.
+ int maxSameSounds = bDelaySame ? 5 : 4;
+ float distSqr = 0.0f;
+ if ( sfx->pSource )
+ {
+ distSqr = origin.DistToSqr(listener_origin);
+ if ( sfx->pSource->IsLooped() )
+ {
+ maxSameSounds = 3;
+ }
+ }
+
+ // don't play more than N copies of the same sound, steal the quietest & closest one otherwise
+ if ( sameSoundCount >= maxSameSounds )
+ {
+ channel_t *ch = &channels[sameSoundIndex];
+ // you're already playing a closer version of this sound, don't steal
+ if ( distSqr > 0.0f && ch->origin.DistToSqr(listener_origin) < distSqr && entchannel != CHAN_WEAPON )
+ return NULL;
+
+ //Msg("Sound playing %d copies, stole %s (%d)\n", sameSoundCount, ch->sfx->getname(), sameVol );
+ return ch;
+ }
+ }
+
+ // if there's a free channel, just take that one - don't steal
+ if ( availableChannel >= 0 )
+ return &channels[availableChannel];
+
+ // Still haven't found a suitable channel, so choose the one with the least amount of time left to play
+ float life_left = FLT_MAX;
+ int first_to_die = -1;
+ bool bAllowVoiceSteal = voice_steal.GetBool();
+
+ for ( int i = 0; i < canStealCount; i++ )
+ {
+ int ch_idx = canSteal[i];
+ channel_t *ch = &channels[ch_idx];
+ float timeleft = 0;
+ if ( bAllowVoiceSteal )
+ {
+ int maxVolume = ChannelGetMaxVol( ch );
+ if ( maxVolume < 5 )
+ {
+ //Msg("Sound quiet, stole %s for %s\n", ch->sfx->getname(), sfx->getname() );
+ return ch;
+ }
+
+ if ( ch->sfx && ch->sfx->pSource )
+ {
+ unsigned int sampleCount = RemainingSamples( ch );
+ timeleft = (float)sampleCount / (float)ch->sfx->pSource->SampleRate();
+ }
+ }
+ else
+ {
+ // UNDONE: Kill this when voice_steal 0,1,2 has been tested
+ // UNDONE: This is the old buggy code that we're trying to replace
+ if ( ch->sfx )
+ {
+ // basically steals the first one you come to
+ timeleft = 1; //ch->end - paintedtime
+ }
+ }
+
+ if ( timeleft < life_left )
+ {
+ life_left = timeleft;
+ first_to_die = ch_idx;
+ }
+ }
+ if ( first_to_die >= 0 )
+ {
+ //Msg("Stole %s, timeleft %d\n", channels[first_to_die].sfx->getname(), life_left );
+ return &channels[first_to_die];
+ }
+
+ return NULL;
+}
+
+channel_t *SND_PickDynamicChannel(SoundSource soundsource, int entchannel, const Vector &origin, CSfxTable *sfx, float flDelay, bool bDoNotOverwriteExisting)
+{
+ channel_t *pChannel = SND_StealDynamicChannel( soundsource, entchannel, origin, sfx, flDelay, bDoNotOverwriteExisting );
+ if ( !pChannel )
+ return NULL;
+
+ if (pChannel->sfx)
+ {
+ // Don't restart looping sounds for the same entity
+ CAudioSource *pSource = pChannel->sfx->pSource;
+ if ( pSource )
+ {
+ if ( pSource->IsLooped() )
+ {
+ if ( pChannel->soundsource == soundsource && pChannel->entchannel == entchannel && pChannel->sfx == sfx )
+ {
+ // same looping sound, same ent, same channel, don't restart the sound
+ return NULL;
+ }
+ }
+ }
+ // be sure and release previous channel
+ // if sentence.
+ // ("Stealing channel from %s\n", channels[first_to_die].sfx->getname() );
+ S_FreeChannel(pChannel);
+ }
+
+ return pChannel;
+}
+
+
+
+/*
+=====================
+SND_PickStaticChannel
+=====================
+Pick an empty channel from the static sound area, or allocate a new
+channel. Only fails if we're at max_channels (128!!!) or if
+we're trying to allocate a channel for a stream sound that is
+already playing.
+
+*/
+channel_t *SND_PickStaticChannel(int soundsource, CSfxTable *pSfx)
+{
+ int i;
+ channel_t *ch = NULL;
+
+ // Check for replacement sound, or find the best one to replace
+ for (i = MAX_DYNAMIC_CHANNELS; i<total_channels; i++)
+ if (channels[i].sfx == NULL)
+ break;
+
+ if (i < total_channels)
+ {
+ // reuse an empty static sound channel
+ ch = &channels[i];
+ }
+ else
+ {
+ // no empty slots, alloc a new static sound channel
+ if (total_channels == MAX_CHANNELS)
+ {
+ DevMsg ("total_channels == MAX_CHANNELS\n");
+ return NULL;
+ }
+
+ // get a channel for the static sound
+ ch = &channels[total_channels];
+ total_channels++;
+ }
+
+ return ch;
+}
+
+
+void S_SpatializeChannel( int pVolume[CCHANVOLUMES/2], int master_vol, const Vector *psourceDir, float gain, float mono )
+{
+ float lscale, rscale, scale;
+ vec_t dotRight;
+ Vector sourceDir = *psourceDir;
+
+ dotRight = DotProduct(listener_right, sourceDir);
+
+ // clear volumes
+ for (int i = 0; i < CCHANVOLUMES/2; i++)
+ pVolume[i] = 0;
+
+ if (mono > 0.0)
+ {
+ // sound has radius, within which spatialization becomes mono:
+
+ // mono is 0.0 -> 1.0, from radius 100% to radius 50%
+
+ // at radius * 0.5, dotRight is 0 (ie: sound centered left/right)
+ // at radius * 1.0, dotRight == dotRight
+
+ dotRight *= (1.0 - mono);
+ }
+
+ rscale = 1.0 + dotRight;
+ lscale = 1.0 - dotRight;
+
+ // add in distance effect
+ scale = gain * rscale / 2;
+ pVolume[IFRONT_RIGHT] = (int) (master_vol * scale);
+
+ scale = gain * lscale / 2;
+ pVolume[IFRONT_LEFT] = (int) (master_vol * scale);
+
+ pVolume[IFRONT_RIGHT] = clamp( pVolume[IFRONT_RIGHT], 0, 255 );
+ pVolume[IFRONT_LEFT] = clamp( pVolume[IFRONT_LEFT], 0, 255 );
+
+}
+
+bool S_IsMusic( channel_t *pChannel )
+{
+ if ( !pChannel->flags.bdry )
+ return false;
+
+ CSfxTable *sfx = pChannel->sfx;
+ if ( !sfx )
+ return false;
+
+ CAudioSource *source = sfx->pSource;
+ if ( !source )
+ return false;
+
+ // Don't save restore looping sounds as you can end up with an entity restarting them again and have
+ // them accumulate, etc.
+ if ( source->IsLooped() )
+ return false;
+
+ CAudioMixer *pMixer = pChannel->pMixer;
+ if ( !pMixer )
+ return false;
+
+ for ( int i = 0; i < 8; i++ )
+ {
+ if ( pChannel->mixgroups[i] != -1 )
+ {
+ char *pGroupName = MXR_GetGroupnameFromId( pChannel->mixgroups[i] );
+ if ( !Q_strcmp( pGroupName, "Music" ) )
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: For save/restore of currently playing music
+// Input : list -
+//-----------------------------------------------------------------------------
+void S_GetCurrentlyPlayingMusic( CUtlVector< musicsave_t >& musiclist )
+{
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = &channels[list.GetChannelIndex(i)];
+ if ( !S_IsMusic( pChannel ) )
+ continue;
+
+ musicsave_t song;
+ Q_strncpy( song.songname, pChannel->sfx->getname(), sizeof( song.songname ) );
+ song.sampleposition = pChannel->pMixer->GetPositionForSave();
+ song.master_volume = pChannel->master_vol;
+
+ musiclist.AddToTail( song );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *song -
+//-----------------------------------------------------------------------------
+void S_RestartSong( const musicsave_t *song )
+{
+ Assert( song );
+
+ // Start the song
+ CSfxTable *pSound = S_PrecacheSound( song->songname );
+ if ( pSound )
+ {
+ StartSoundParams_t params;
+ params.staticsound = true;
+ params.soundsource = SOUND_FROM_WORLD;
+ params.entchannel = CHAN_STATIC;
+ params.pSfx = pSound;
+ params.origin = vec3_origin;
+ params.fvol = ( (float)song->master_volume / 255.0f );
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = SND_NOFLAGS;
+ params.pitch = PITCH_NORM;
+ params.initialStreamPosition = song->sampleposition;
+
+ S_StartSound( params );
+
+ if ( IsPC() )
+ {
+ // Now find the channel this went on and skip ahead in the mixer
+ for (int i = 0; i < total_channels; i++)
+ {
+ channel_t *ch = &channels[i];
+
+ if ( !ch->pMixer ||
+ !ch->pMixer->GetSource() )
+ {
+ continue;
+ }
+
+ if ( ch->pMixer->GetSource() != pSound->pSource )
+ {
+ continue;
+ }
+
+ ch->pMixer->SetPositionFromSaved( song->sampleposition );
+ break;
+ }
+ }
+ }
+}
+
+soundlevel_t SND_GetSndlvl ( channel_t *pchannel );
+
+// calculate ammount of sound to be mixed to dsp, based on distance from listener
+
+
+ConVar dsp_dist_min("dsp_dist_min", "0.0", FCVAR_DEMO|FCVAR_CHEAT); // range at which sounds are mixed at dsp_mix_min
+ConVar dsp_dist_max("dsp_dist_max", "1440.0", FCVAR_DEMO|FCVAR_CHEAT); // range at which sounds are mixed at dsp_mix_max
+
+ConVar dsp_mix_min("dsp_mix_min", "0.2", FCVAR_DEMO ); // dsp mix at dsp_dist_min distance "near"
+ConVar dsp_mix_max("dsp_mix_max", "0.8", FCVAR_DEMO ); // dsp mix at dsp_dist_max distance "far"
+ConVar dsp_db_min("dsp_db_min", "80", FCVAR_DEMO ); // sounds with sndlvl below this get dsp_db_mixdrop % less dsp mix
+ConVar dsp_db_mixdrop("dsp_db_mixdrop", "0.5", FCVAR_DEMO ); // sounds with sndlvl below dsp_db_min get dsp_db_mixdrop % less mix
+
+float DSP_ROOM_MIX = 1.0; // mix volume of dsp_room sounds when added back to 'dry' sounds
+float DSP_NOROOM_MIX = 1.0; // mix volume of facing + facing away sounds. added to dsp_room_mix sounds
+
+extern ConVar dsp_off;
+
+// returns 0-1.0 dsp mix value. If sound source is at a range >= DSP_DIST_MAX, return a mix value of
+// DSP_MIX_MAX. This mix value is used later to determine wet/dry mix ratio of sounds.
+
+// This ramp changes with db level of sound source, and is set in the dsp room presets by room size
+// empirical data: 0.78 is nominal mix for sound 100% at far end of room, 0.24 is mix for sound 25% into room
+
+float SND_GetDspMix( channel_t *pchannel, int idist)
+{
+ float mix;
+ float dist = (float)idist;
+ float dist_min = dsp_dist_min.GetFloat();
+ float dist_max = dsp_dist_max.GetFloat();
+ float mix_min;
+ float mix_max;
+
+ // only set dsp mix_min & mix_max when sound is first started
+
+ if ( pchannel->dsp_mix_min < 0 && pchannel->dsp_mix_max < 0 )
+ {
+ mix_min = dsp_mix_min.GetFloat(); // set via dsp_room preset
+ mix_max = dsp_mix_max.GetFloat(); // set via dsp_room preset
+
+ // set mix_min & mix_max based on db level of sound:
+ // sounds below dsp_db_min decrease dsp_mix_min & dsp_mix_max by N%
+ // ie: quiet sounds get less dsp mix than loud sounds
+
+ soundlevel_t sndlvl = SND_GetSndlvl( pchannel );
+ soundlevel_t sndlvl_min = (soundlevel_t)(dsp_db_min.GetInt());
+
+ if (sndlvl <= sndlvl_min)
+ {
+ mix_min *= dsp_db_mixdrop.GetFloat();
+ mix_max *= dsp_db_mixdrop.GetFloat();
+ }
+
+ pchannel->dsp_mix_min = mix_min;
+ pchannel->dsp_mix_max = mix_max;
+ }
+ else
+ {
+ mix_min = pchannel->dsp_mix_min;
+ mix_max = pchannel->dsp_mix_max;
+ }
+
+ // dspmix is 0 (100% mix to facing buffer) if dsp_off
+
+ if ( dsp_off.GetInt() )
+ return 0.0;
+
+ // doppler wavs are mixed dry
+
+ if ( pchannel->wavtype == CHAR_DOPPLER )
+ return 0.0;
+
+ // linear ramp - get dry mix %
+
+ // dist: 0->(max - min)
+
+ dist = clamp( dist, dist_min, dist_max ) - dist_min;
+
+ // dist: 0->1.0
+
+ dist = dist / (dist_max - dist_min);
+
+ // mix: min->max
+
+ mix = ((mix_max - mix_min) * dist) + mix_min;
+
+ return mix;
+}
+
+// calculate crossfade between wav left (close sound) and wav right (far sound) based on
+// distance fron listener
+
+#define DVAR_DIST_MIN (20.0 * 12.0) // play full 'near' sound at 20' or less
+#define DVAR_DIST_MAX (110.0 * 12.0) // play full 'far' sound at 110' or more
+#define DVAR_MIX_MIN 0.0
+#define DVAR_MIX_MAX 1.0
+
+// calculate mixing parameter for CHAR_DISTVAR wavs
+// returns 0 - 1.0, 1.0 is 100% far sound (wav right)
+
+float SND_GetDistanceMix( channel_t *pchannel, int idist)
+{
+ float mix;
+ float dist = (float)idist;
+
+ // doppler wavs are 100% near - their spatialization is calculated later.
+
+ if ( pchannel->wavtype == CHAR_DOPPLER )
+ return 0.0;
+
+ // linear ramp - get dry mix %
+
+ // dist 0->(max - min)
+
+ dist = clamp( dist, (float) DVAR_DIST_MIN, (float) DVAR_DIST_MAX ) - (float) DVAR_DIST_MIN;
+
+ // dist 0->1.0
+
+ dist = dist / (DVAR_DIST_MAX - DVAR_DIST_MIN);
+
+ // mix min->max
+
+ mix = ((DVAR_MIX_MAX - DVAR_MIX_MIN) * dist) + DVAR_MIX_MIN;
+
+ return mix;
+}
+
+// given facing direction of source, and channel,
+// return -1.0 - 1.0, where -1.0 is source facing away from listener
+// and 1.0 is source facing listener
+
+
+float SND_GetFacingDirection( channel_t *pChannel, const QAngle &source_angles )
+{
+ Vector SF; // sound source forward direction unit vector
+ Vector SL; // sound -> listener unit vector
+ float dotSFSL;
+
+ // no facing direction unless wavtyp CHAR_DIRECTIONAL
+
+ if ( pChannel->wavtype != CHAR_DIRECTIONAL )
+ return 1.0;
+
+ VectorSubtract(listener_origin, pChannel->origin, SL);
+ VectorNormalize(SL);
+
+ // compute forward vector for sound entity
+
+ AngleVectors( source_angles, &SF, NULL, NULL );
+
+ // dot source forward unit vector with source to listener unit vector to get -1.0 - 1.0 facing.
+ // ie: projection of SF onto SL
+
+ dotSFSL = DotProduct( SF, SL );
+
+ return dotSFSL;
+}
+
+// calculate point of closest approach - caller must ensure that the
+// forward facing vector of the entity playing this sound points in exactly the direction of
+// travel of the sound. ie: for bullets or tracers, forward vector must point in traceline direction.
+// return true if sound is to be played, false if sound cannot be heard (shot away from player)
+
+bool SND_GetClosestPoint( channel_t *pChannel, QAngle &source_angles, Vector &vnearpoint )
+{
+ // S - sound source origin
+ // L - listener origin
+
+ Vector SF; // sound source forward direction unit vector
+ Vector SL; // sound -> listener vector
+ Vector SD; // sound->closest point vector
+ vec_t dSLSF; // magnitude of project of SL onto SF
+
+ // P = SF (SF . SL) + S
+
+ // only perform this calculation for doppler wavs
+
+ if ( pChannel->wavtype != CHAR_DOPPLER )
+ return false;
+
+ // get vector 'SL' from sound source to listener
+
+ VectorSubtract(listener_origin, pChannel->origin, SL);
+
+ // compute sound->forward vector 'SF' for sound entity
+
+ AngleVectors( source_angles, &SF );
+ VectorNormalize( SF );
+
+ dSLSF = DotProduct( SL, SF );
+
+
+ if ( dSLSF <= 0 && !toolframework->IsToolRecording() )
+ {
+ // source is pointing away from listener, don't play anything
+ // unless we're recording in the tool, since we may play back from in front of the source
+ return false;
+ }
+
+ // project dSLSF along forward unit vector from sound source
+
+ VectorMultiply( SF, dSLSF, SD );
+
+ // output vector - add SD to sound source origin
+
+ VectorAdd( SD, pChannel->origin, vnearpoint );
+
+ return true;
+}
+
+
+// given point of nearest approach and sound source facing angles,
+// return vector pointing into quadrant in which to play
+// doppler left wav (incomming) and doppler right wav (outgoing).
+
+// doppler left is point in space to play left doppler wav
+// doppler right is point in space to play right doppler wav
+
+// Also modifies channel pitch based on distance to nearest approach point
+
+#define DOPPLER_DIST_LEFT_TO_RIGHT (4*12) // separate left/right sounds by 4'
+
+#define DOPPLER_DIST_MAX (20*12) // max distance - causes min pitch
+#define DOPPLER_DIST_MIN (1*12) // min distance - causes max pitch
+#define DOPPLER_PITCH_MAX 1.5 // max pitch change due to distance
+#define DOPPLER_PITCH_MIN 0.25 // min pitch change due to distance
+
+#define DOPPLER_RANGE_MAX (10*12) // don't play doppler wav unless within this range
+ // UNDONE: should be set by caller!
+
+void SND_GetDopplerPoints( channel_t *pChannel, QAngle &source_angles, Vector &vnearpoint, Vector &source_doppler_left, Vector &source_doppler_right)
+{
+ Vector SF; // direction sound source is facing (forward)
+ Vector LN; // vector from listener to closest approach point
+ Vector DL;
+ Vector DR;
+
+ // nearpoint is closest point of approach, when playing CHAR_DOPPLER sounds
+
+ // SF is normalized vector in direction sound source is facing
+
+ AngleVectors( source_angles, &SF );
+ VectorNormalize( SF );
+
+ // source_doppler_left - location in space to play doppler left wav (incomming)
+ // source_doppler_right - location in space to play doppler right wav (outgoing)
+
+ VectorMultiply( SF, -1*DOPPLER_DIST_LEFT_TO_RIGHT, DL );
+ VectorMultiply( SF, DOPPLER_DIST_LEFT_TO_RIGHT, DR );
+
+ VectorAdd( vnearpoint, DL, source_doppler_left );
+ VectorAdd( vnearpoint, DR, source_doppler_right );
+
+ // set pitch of channel based on nearest distance to listener
+
+ // LN is vector from listener to closest approach point
+
+ VectorSubtract(vnearpoint, listener_origin, LN);
+
+ float pitch;
+ float dist = VectorLength( LN );
+
+ // dist varies 0->1
+
+ dist = clamp(dist, (float)DOPPLER_DIST_MIN, (float)DOPPLER_DIST_MAX);
+ dist = (dist - DOPPLER_DIST_MIN) / (DOPPLER_DIST_MAX - DOPPLER_DIST_MIN);
+
+ // pitch varies from max to min
+
+ pitch = DOPPLER_PITCH_MAX - dist * (DOPPLER_PITCH_MAX - DOPPLER_PITCH_MIN);
+
+ pChannel->basePitch = (int)(pitch * 100.0);
+}
+
+// console variables used to construct gain curve - don't change these!
+
+extern ConVar snd_foliage_db_loss;
+extern ConVar snd_gain;
+extern ConVar snd_refdb;
+extern ConVar snd_refdist;
+extern ConVar snd_gain_max;
+extern ConVar snd_gain_min;
+
+ConVar snd_showstart( "snd_showstart", "0", FCVAR_CHEAT ); // showstart always skips info on player footsteps!
+ // 1 - show sound name, channel, volume, time
+ // 2 - show dspmix, distmix, dspface, l/r/f/r vols
+ // 3 - show sound origin coords
+ // 4 - show gain of dsp_room
+ // 5 - show dB loss due to obscured sound
+ // 6 - reserved
+ // 7 - show 2 and total gain & dist in ft. to sound source
+
+#define SND_DB_MAX 140.0 // max db of any sound source
+#define SND_DB_MED 90.0 // db at which compression curve changes
+#define SND_DB_MIN 60.0 // min db of any sound source
+
+#define SND_GAIN_PLAYER_WEAPON_DB 2.0 // increase player weapon gain by N dB
+
+// dB = 20 log (amplitude/32768) 0 to -90.3dB
+// amplitude = 32768 * 10 ^ (dB/20) 0 to +/- 32768
+// gain = amplitude/32768 0 to 1.0
+
+float Gain_To_dB ( float gain )
+{
+ float dB = 20 * log ( gain );
+ return dB;
+}
+
+float dB_To_Gain ( float dB )
+{
+ float gain = powf (10, dB / 20.0);
+ return gain;
+}
+
+float Gain_To_Amplitude ( float gain )
+{
+ return gain * 32768;
+}
+
+float Amplitude_To_Gain ( float amplitude )
+{
+ return amplitude / 32768;
+}
+
+soundlevel_t SND_GetSndlvl ( channel_t *pchannel )
+{
+ return DIST_MULT_TO_SNDLVL( pchannel->dist_mult );
+}
+
+
+// The complete gain calculation, with SNDLVL given in dB is:
+//
+// GAIN = 1/dist * snd_refdist * 10 ^ ( ( SNDLVL - snd_refdb - (dist * snd_foliage_db_loss / 1200)) / 20 )
+//
+// for gain > SND_GAIN_THRESH, start curve smoothing with
+//
+// GAIN = 1 - 1 / (Y * GAIN ^ SND_GAIN_POWER)
+//
+// where Y = -1 / ( (SND_GAIN_THRESH ^ SND_GAIN_POWER) * (SND_GAIN_THRESH - 1) )
+//
+
+float SND_GetGainFromMult( float gain, float dist_mult, vec_t dist );
+
+// gain curve construction
+
+float SND_GetGain( channel_t *ch, bool fplayersound, bool fmusicsound, bool flooping, vec_t dist, bool bAttenuated )
+{
+ VPROF_("SND_GetGain",2,VPROF_BUDGETGROUP_OTHER_SOUND,false,BUDGETFLAG_OTHER);
+ if ( ch->flags.m_bCompatibilityAttenuation )
+ {
+ // Convert to the original attenuation value.
+ soundlevel_t soundlevel = DIST_MULT_TO_SNDLVL( ch->dist_mult );
+ float flAttenuation = SNDLVL_TO_ATTN( soundlevel );
+
+ // Now get the goldsrc dist_mult and use the same calculation it uses in SND_Spatialize.
+ // Straight outta Goldsrc!!!
+ vec_t nominal_clip_dist = 1000.0;
+ float flGoldsrcDistMult = flAttenuation / nominal_clip_dist;
+ dist *= flGoldsrcDistMult;
+ float flReturnValue = 1.0f - dist;
+ flReturnValue = clamp( flReturnValue, 0.f, 1.f );
+ return flReturnValue;
+ }
+ else
+ {
+ float gain = snd_gain.GetFloat();
+
+ if ( fmusicsound )
+ {
+ gain = gain * snd_musicvolume.GetFloat();
+ gain = gain * g_DashboardMusicMixValue;
+ }
+
+ if ( ch->dist_mult )
+ {
+ gain = SND_GetGainFromMult( gain, ch->dist_mult, dist );
+ }
+
+ if ( fplayersound )
+ {
+
+ // player weapon sounds get extra gain - this compensates
+ // for npc distance effect weapons which mix louder as L+R into L,R
+ // Hack.
+
+ if ( ch->entchannel == CHAN_WEAPON )
+ gain = gain * dB_To_Gain( SND_GAIN_PLAYER_WEAPON_DB );
+ }
+
+ // modify gain if sound source not visible to player
+
+ gain = gain * SND_GetGainObscured( ch, fplayersound, flooping, bAttenuated );
+
+ if (snd_showstart.GetInt() == 6)
+ {
+ DevMsg( "(gain %1.3f : dist ft %1.1f) ", gain, (float)dist/12.0 );
+ snd_showstart.SetValue(5); // display once
+ }
+
+ return gain;
+ }
+}
+
+// always ramp channel gain changes over time
+// returns ramped gain, given new target gain
+
+#define SND_GAIN_FADE_TIME 0.25 // xfade seconds between obscuring gain changes
+
+float SND_FadeToNewGain( channel_t *ch, float gain_new )
+{
+
+ if ( gain_new == -1.0 )
+ {
+ // if -1 passed in, just keep fading to existing target
+
+ gain_new = ch->ob_gain_target;
+ }
+
+ // if first time updating, store new gain into gain & target, return
+ // if gain_new is close to existing gain, store new gain into gain & target, return
+
+ if ( ch->flags.bfirstpass || (fabs (gain_new - ch->ob_gain) < 0.01))
+ {
+ ch->ob_gain = gain_new;
+ ch->ob_gain_target = gain_new;
+ ch->ob_gain_inc = 0.0;
+ return gain_new;
+ }
+
+ // set up new increment to new target
+
+ float frametime = g_pSoundServices->GetHostFrametime();
+ float speed;
+ speed = ( frametime / SND_GAIN_FADE_TIME ) * (gain_new - ch->ob_gain);
+
+ ch->ob_gain_inc = fabs(speed);
+
+ // ch->ob_gain_inc = fabs(gain_new - ch->ob_gain) / 10.0;
+
+ ch->ob_gain_target = gain_new;
+
+ // if not hit target, keep approaching
+
+ if ( fabs( ch->ob_gain - ch->ob_gain_target ) > 0.01 )
+ {
+ ch->ob_gain = Approach( ch->ob_gain_target, ch->ob_gain, ch->ob_gain_inc );
+ }
+ else
+ {
+ // close enough, set gain = target
+ ch->ob_gain = ch->ob_gain_target;
+ }
+
+ return ch->ob_gain;
+}
+
+#define SND_TRACE_UPDATE_MAX 2 // max of N channels may be checked for obscured source per frame
+
+static int g_snd_trace_count = 0; // total tracelines for gain obscuring made this frame
+
+// All new sounds must traceline once,
+// but cap the max number of tracelines performed per frame
+// for longer or looping sounds to SND_TRACE_UPDATE_MAX.
+
+bool SND_ChannelOkToTrace( channel_t *ch )
+{
+ // always trace first time sound is spatialized (doesn't update counter)
+
+ if ( ch->flags.bfirstpass )
+ {
+ ch->flags.bTraced = true;
+ return true;
+ }
+
+ // if already traced max channels this frame, return
+
+ if ( g_snd_trace_count >= SND_TRACE_UPDATE_MAX )
+ return false;
+
+ // ok to trace if this sound hasn't yet been traced in this round
+
+ if ( ch->flags.bTraced )
+ return false;
+
+ // set flag - don't traceline this sound again until all others have
+ // been traced
+
+ ch->flags.bTraced = true;
+
+ g_snd_trace_count++; // total traces this frame
+
+ return true;
+}
+
+// determine if we need to reset all flags for traceline limiting -
+// this happens if we hit a frame whein no tracelines occur ie: all currently
+// playing sounds are blocked.
+
+void SND_ChannelTraceReset( void )
+{
+ if ( g_snd_trace_count )
+ return;
+
+ // if no tracelines performed this frame, then reset all
+ // trace flags
+
+ for (int i = 0; i < total_channels; i++)
+ channels[i].flags.bTraced = false;
+}
+
+bool SND_IsLongWave( channel_t *pChannel )
+{
+ CAudioSource *pSource = pChannel->sfx ? pChannel->sfx->pSource : NULL;
+ if ( pSource )
+ {
+ if ( pSource->IsStreaming() )
+ return true;
+
+ // UNDONE: Do this on long wave files too?
+#if 0
+ float length = (float)pSource->SampleCount() / (float)pSource->SampleRate();
+ if ( length > 0.75f )
+ return true;
+#endif
+ }
+
+ return false;
+}
+
+
+ConVar snd_obscured_gain_db( "snd_obscured_gain_dB", "-2.70", FCVAR_CHEAT ); // dB loss due to obscured sound source
+
+// drop gain on channel if sound emitter obscured by
+// world, unbroken windows, closed doors, large solid entities etc.
+
+float SND_GetGainObscured( channel_t *ch, bool fplayersound, bool flooping, bool bAttenuated )
+{
+ float gain = 1.0;
+ int count = 1;
+ float snd_gain_db; // dB loss due to obscured sound source
+
+ // Unattenuated sounds don't get obscured.
+ if ( !bAttenuated )
+ return 1.0f;
+
+ if ( fplayersound )
+ return gain;
+
+ // During signon just apply regular state machine since world hasn't been
+ // created or settled yet...
+
+ if ( !SND_IsInGame() )
+ {
+ if ( !toolframework->InToolMode() )
+ {
+ gain = SND_FadeToNewGain( ch, -1.0 );
+ }
+
+ return gain;
+ }
+
+ // don't do gain obscuring more than once on short one-shot sounds
+
+ if ( !ch->flags.bfirstpass && !ch->flags.isSentence && !flooping && !SND_IsLongWave(ch) )
+ {
+ gain = SND_FadeToNewGain( ch, -1.0 );
+ return gain;
+ }
+
+ snd_gain_db = snd_obscured_gain_db.GetFloat();
+
+ // if long or looping sound, process N channels per frame - set 'processed' flag, clear by
+ // cycling through all channels - this maintains a cap on traces per frame
+
+ if ( !SND_ChannelOkToTrace( ch ) )
+ {
+ // just keep updating fade to existing target gain - no new trace checking
+
+ gain = SND_FadeToNewGain( ch, -1.0 );
+ return gain;
+ }
+ // set up traceline from player eyes to sound emitting entity origin
+
+ Vector endpoint = ch->origin;
+
+ trace_t tr;
+ CTraceFilterWorldOnly filter; // UNDONE: also test for static props?
+ Ray_t ray;
+ ray.Init( MainViewOrigin(), endpoint );
+ g_pEngineTraceClient->TraceRay( ray, MASK_BLOCK_AUDIO, &filter, &tr );
+
+ if (tr.DidHit() && tr.fraction < 0.99)
+ {
+ // can't see center of sound source:
+ // build extents based on dB sndlvl of source,
+ // test to see how many extents are visible,
+ // drop gain by snd_gain_db per extent hidden
+
+ Vector endpoints[4];
+ soundlevel_t sndlvl = DIST_MULT_TO_SNDLVL( ch->dist_mult );
+ float radius;
+ Vector vsrc_forward;
+ Vector vsrc_right;
+ Vector vsrc_up;
+ Vector vecl;
+ Vector vecr;
+ Vector vecl2;
+ Vector vecr2;
+ int i;
+
+ // get radius
+
+ if ( ch->radius > 0 )
+ radius = ch->radius;
+ else
+ radius = dB_To_Radius( sndlvl); // approximate radius from soundlevel
+
+ // set up extent endpoints - on upward or downward diagonals, facing player
+
+ for (i = 0; i < 4; i++)
+ endpoints[i] = endpoint;
+
+ // vsrc_forward is normalized vector from sound source to listener
+
+ VectorSubtract( listener_origin, endpoint, vsrc_forward );
+ VectorNormalize( vsrc_forward );
+ VectorVectors( vsrc_forward, vsrc_right, vsrc_up );
+
+ VectorAdd( vsrc_up, vsrc_right, vecl );
+
+ // if src above listener, force 'up' vector to point down - create diagonals up & down
+
+ if ( endpoint.z > listener_origin.z + (10 * 12) )
+ vsrc_up.z = -vsrc_up.z;
+
+ VectorSubtract( vsrc_up, vsrc_right, vecr );
+ VectorNormalize( vecl );
+ VectorNormalize( vecr );
+
+ // get diagonal vectors from sound source
+
+ vecl2 = radius * vecl;
+ vecr2 = radius * vecr;
+ vecl = (radius / 2.0) * vecl;
+ vecr = (radius / 2.0) * vecr;
+
+ // endpoints from diagonal vectors
+
+ endpoints[0] += vecl;
+ endpoints[1] += vecr;
+ endpoints[2] += vecl2;
+ endpoints[3] += vecr2;
+
+ // drop gain for each point on radius diagonal that is obscured
+
+ for (count = 0, i = 0; i < 4; i++)
+ {
+ // UNDONE: some endpoints are in walls - in this case, trace from the wall hit location
+
+ ray.Init( MainViewOrigin(), endpoints[i] );
+ g_pEngineTraceClient->TraceRay( ray, MASK_BLOCK_AUDIO, &filter, &tr );
+
+ if (tr.DidHit() && tr.fraction < 0.99 && !tr.startsolid )
+ {
+ count++; // skip first obscured point: at least 2 points + center should be obscured to hear db loss
+ if (count > 1)
+ gain = gain * dB_To_Gain( snd_gain_db );
+ }
+ }
+ }
+
+
+ if ( flooping && snd_showstart.GetInt() == 7)
+ {
+ static float g_drop_prev = 0;
+ float drop = (count-1) * snd_gain_db;
+
+ if (drop != g_drop_prev)
+ {
+ DevMsg( "dB drop: %1.4f \n", drop);
+ g_drop_prev = drop;
+ }
+ }
+
+ // crossfade to new gain
+
+ gain = SND_FadeToNewGain( ch, gain );
+
+ return gain;
+}
+
+// convert sound db level to approximate sound source radius,
+// used only for determining how much of sound is obscured by world
+
+#define SND_RADIUS_MAX (20.0 * 12.0) // max sound source radius
+#define SND_RADIUS_MIN (2.0 * 12.0) // min sound source radius
+
+inline float dB_To_Radius ( float db )
+{
+ float radius = SND_RADIUS_MIN + (SND_RADIUS_MAX - SND_RADIUS_MIN) * (db - SND_DB_MIN) / (SND_DB_MAX - SND_DB_MIN);
+
+ return radius;
+}
+
+struct snd_spatial_t
+{
+ int chan; // 0..4 cycles through up to 5 channels
+ int cycle; // 0..2 cycles through 3 vectors per channel
+ int dist[5][3]; // stores last 3 channel distance values [channel][cycle]
+
+ float value_prev[5]; // previous value per channel
+
+ double last_change;
+};
+
+bool g_ssp_init = false;
+snd_spatial_t g_ssp;
+
+// return 0..1 percent difference between a & b
+
+float PercentDifference( float a, float b )
+{
+ float vp;
+
+ if (!(int)a && !(int)b)
+ return 0.0;
+
+ if (!(int)a || !(int)b)
+ return 1.0;
+
+ if (a > b)
+ vp = b / a;
+ else
+ vp = a / b;
+
+ return (1.0 - vp);
+}
+
+// NOTE: Do not change SND_WALL_TRACE_LEN without also changing PRC_MDY6 delay value in snd_dsp.cpp!
+
+#define SND_WALL_TRACE_LEN (100.0*12.0) // trace max of 100' = max of 100 milliseconds of linear delay
+#define SND_SPATIAL_WAIT (0.25) // seconds to wait between traces
+
+// change mod delay value on chan 0..3 to v (inches)
+
+void DSP_SetSpatialDelay( int chan, float v )
+{
+ // remap delay value 0..1200 to 1.0 to -1.0 for modulation
+
+ float value = ( v / SND_WALL_TRACE_LEN) - 1.0; // -1.0...0
+ value = value * 2.0; // -2.0...0
+ value += 1.0; // -1.0...1.0 (0...1200)
+ value *= -1.0; // 1.0...-1.0 (0...1200)
+
+ // assume first processor in dsp_spatial is the modulating delay unit for DSP_ChangePresetValue
+
+ int iproc = 0;
+
+ DSP_ChangePresetValue( idsp_spatial, chan, iproc, value );
+/*
+
+ if (chan & 0x01)
+ DevMsg("RDly: %3.0f \n", v/12 );
+ else
+ DevMsg("LDly: %3.0f \n", v/12 );
+*/
+}
+
+// use non-feedback delay to stereoize (or make quad, or quad + center) the mono dsp_room fx,
+// This simulates the average sum of delays caused by reflections
+// from the left and right walls relative to the player. The average delay
+// difference between left & right wall is (l + r)/2. This becomes the average
+// delay difference between left & right ear.
+// call at most once per frame to update player->wall spatial delays
+
+void SND_SetSpatialDelays()
+{
+ VPROF("SoundSpatialDelays");
+ float dist, v, vp;
+ Vector v_dir, v_dir2;
+ int chan_max = (g_AudioDevice->IsSurround() ? 4 : 2) + (g_AudioDevice->IsSurroundCenter() ? 1 : 0); // 2, 4, 5 channels
+
+ // use listener_forward2d, which doesn't change when player looks up/down.
+
+ Vector listener_forward2d;
+
+ ConvertListenerVectorTo2D( &listener_forward2d, &listener_right );
+
+ // init struct if 1st time through
+
+ if ( !g_ssp_init )
+ {
+ Q_memset(&g_ssp, 0, sizeof(snd_spatial_t));
+ g_ssp_init = true;
+ }
+
+ // return if dsp_spatial is 0
+
+ if ( !dsp_spatial.GetInt() )
+ return;
+
+ // if listener has not been updated, do nothing
+
+ if ((listener_origin == vec3_origin) &&
+ (listener_forward == vec3_origin) &&
+ (listener_right == vec3_origin) &&
+ (listener_up == vec3_origin) )
+ return;
+
+ if ( !SND_IsInGame() )
+ return;
+
+ // get time
+
+ double dtime = g_pSoundServices->GetHostTime();
+
+ // compare to previous time - if starting new check - don't check for new room until timer expires
+
+ if (!g_ssp.chan && !g_ssp.cycle)
+ {
+ if (fabs(dtime - g_ssp.last_change) < SND_SPATIAL_WAIT)
+ return;
+ }
+
+ // cycle through forward, left, rearward vectors, averaging to get left/right delay
+ // count[chan][cycle] 0,1 0,2 0,3 1,1 1,2 1,3 2,1 2,2 2,3 ...
+
+ g_ssp.cycle++;
+
+ if (g_ssp.cycle == 3)
+ {
+ g_ssp.cycle = 0;
+
+ // cycle through front left, front right, rear left, rear right, front center delays
+
+ g_ssp.chan++;
+
+ if (g_ssp.chan >= chan_max )
+ g_ssp.chan = 0;
+ }
+
+ // set up traceline from player eyes to surrounding walls
+
+ switch( g_ssp.chan )
+ {
+ default:
+ case 0: // front left: trace max 100' 'cone' to player's left
+ if ( g_AudioDevice->IsSurround() )
+ {
+ // 4-5 speaker case - front left
+ v_dir = (-listener_right + listener_forward2d) / 2.0;
+ v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? -listener_right * 0.5: listener_forward2d * 0.5) : v_dir;
+ }
+ else
+ {
+ // 2 speaker case - left
+ v_dir = listener_right * -1.0;
+ v_dir2 = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_forward2d * 0.5 : -listener_forward2d * 0.5) : v_dir;
+ v_dir = (v_dir + v_dir2) / 2.0;
+ }
+ break;
+
+ case 1: // front right: trace max 100' 'cone' to player's right
+ if ( g_AudioDevice->IsSurround() )
+ {
+ // 4-5 speaker case - front right
+ v_dir = (listener_right + listener_forward2d) / 2.0;
+ v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_right * 0.5: listener_forward2d * 0.5) : v_dir;
+ }
+ else
+ {
+ // 2 speaker case - right
+ v_dir = listener_right;
+ v_dir2 = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_forward2d * 0.5 : -listener_forward2d * 0.5) : v_dir;
+ v_dir = (v_dir + v_dir2) / 2.0;
+ }
+ break;
+
+ case 2: // rear left: trace max 100' 'cone' to player's rear left
+ v_dir = (listener_right + listener_forward2d) / -2.0;
+ v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? -listener_right * 0.5 : -listener_forward2d * 0.5) : v_dir;
+ break;
+
+ case 3: // rear right: trace max 100' 'cone' to player's rear right
+ v_dir = (listener_right - listener_forward2d) / 2.0;
+ v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_right * 0.5: -listener_forward2d * 0.5) : v_dir;
+ break;
+
+ case 4: // front center: trace max 100' 'cone' to player's front
+ v_dir = listener_forward2d;
+ v_dir2 = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_right * 0.15 : -listener_right * 0.15) : v_dir;
+ v_dir = (v_dir + v_dir2);
+ break;
+ }
+
+ Vector endpoint;
+ trace_t tr;
+ CTraceFilterWorldOnly filter;
+
+ endpoint = MainViewOrigin() + v_dir * SND_WALL_TRACE_LEN;
+ Ray_t ray;
+ ray.Init( MainViewOrigin(), endpoint );
+ g_pEngineTraceClient->TraceRay( ray, MASK_BLOCK_AUDIO, &filter, &tr );
+
+ dist = SND_WALL_TRACE_LEN;
+
+ if ( tr.DidHit() )
+ {
+ dist = VectorLength( tr.endpos - MainViewOrigin() );
+ }
+
+ g_ssp.dist[g_ssp.chan][g_ssp.cycle] = dist;
+
+ // set new result in dsp_spatial delay params when all delay values have been filled in
+
+ if (!g_ssp.cycle && !g_ssp.chan)
+ {
+ // update delay for each channel
+
+ for (int chan = 0; chan < chan_max; chan++)
+ {
+ // compute average of 3 traces per channel
+
+ v = (g_ssp.dist[chan][0] + g_ssp.dist[chan][1] + g_ssp.dist[chan][2]) / 3.0;
+ vp = g_ssp.value_prev[chan];
+
+ // only change if 10% difference from previous
+
+ if ((vp != v) && int(v) && (PercentDifference( v, vp ) >= 0.1))
+ {
+ // update when we have data for all L/R && RL/RR channels...
+
+ if (chan & 0x1)
+ {
+ float vr = fpmin( v, (50*12.0f) );
+ float vl = fpmin(g_ssp.value_prev[chan-1], (50*12.0f));
+
+/* UNDONE: not needed, now that this applies only to dsp 'room' buffer
+
+ // ensure minimum separation = average distance to walls
+
+ float dmin = (vl + vr) / 2.0; // average distance to walls
+ float d = vl - vr; // l/r separation
+
+ // if separation is less than average, increase min
+
+ if (abs(d) < dmin/2)
+ {
+ if (vl > vr)
+ vl += dmin/2 - d;
+ else
+ vr += dmin/2 - d;
+ }
+*/
+ DSP_SetSpatialDelay(chan-1, vl);
+ DSP_SetSpatialDelay(chan, vr);
+ }
+
+ // update center chan
+
+ if (chan == 4)
+ {
+ float vl = fpmin( v, (50*12.0f) );
+ DSP_SetSpatialDelay(chan, vl);
+ }
+ }
+
+ g_ssp.value_prev[chan] = v;
+
+ }
+
+ // update wait timer now that all values have been checked
+
+ g_ssp.last_change = dtime;
+ }
+}
+
+// Dsp Automatic Selection:
+
+// a) enabled by setting dsp_room to DSP_AUTOMATIC. Subsequently, dsp_automatic is the actual dsp value for dsp_room.
+// b) disabled by setting dsp_room to anything else
+
+// c) while enabled, detection nodes are placed as player moves into a new space
+// i. at each node, a new dsp setting is calculated and dsp_automatic is set to an appropriate preset
+// ii. new nodes are set when player moves out of sight of previous node
+// iii. moving into line of sight of a detection node causes closest node to player to set dsp_automatic
+
+// see void DAS_CheckNewRoomDSP( ) for main entrypoint
+
+ConVar das_debug( "adsp_debug", "0", FCVAR_ARCHIVE );
+ // >0: draw blue dsp detection node location
+ // >1: draw green room trace height detection bars
+ // 3: draw yellow horizontal trace bars for room width/depth detection
+ // 4: draw yellow upward traces for height detection
+ // 5: draw teal box around all props around player
+ // 6: draw teal box around room as detected
+
+#define DAS_CWALLS 20 // # of wall traces to save for calculating room dimensions
+#define DAS_ROOM_TRACE_LEN (400.0*12.0) // max size of trace to check for room dimensions
+
+#define DAS_AUTO_WAIT 0.25 // wait min of n seconds between dsp_room changes and update checks
+
+#define DAS_WIDTH_MIN 0.4 // min % change in avg width of any wall pair to cause new dsp
+#define DAS_REFL_MIN 0.5 // min % change in avg refl of any wall to cause new dsp
+#define DAS_SKYHIT_MIN 0.8 // min % change in # of sky hits per wall
+
+#define DAS_DIST_MIN (4.0 * 12.0) // min distance between room dsp changes
+#define DAS_DIST_MAX (40.0 * 12.0) // max distance to preserve room dsp changes
+
+#define DAS_DIST_MIN_OUTSIDE (6.0 * 12.0) // min distance between room dsp changes outside
+#define DAS_DIST_MAX_OUTSIDE (100.0 * 12.0) // max distance to preserve room dsp changes outside
+
+#define IVEC_DIAG_UP 8 // start of diagonal up vectors
+#define IVEC_UP 18 // up vector
+#define IVEC_DOWN 19 // down vector
+
+#define DAS_REFLECTIVITY_NORM 0.5
+#define DAS_REFLECTIVITY_SKY 0.0
+
+// auto dsp room struct
+
+struct das_room_t
+{
+ int dist[DAS_CWALLS]; // distance in units from player to axis aligned and diagonal walls
+ float reflect[DAS_CWALLS]; // acoustic reflectivity per wall
+ float skyhits[DAS_CWALLS]; // every sky hit adds 0.1
+ Vector hit[DAS_CWALLS]; // location of trace hit on wall - used for calculating average centers
+ Vector norm[DAS_CWALLS]; // wall normal at hit location
+
+ Vector vplayer; // 'frozen' location above player's head
+
+ Vector vplayer_eyes; // 'frozen' location player's eyes
+
+ int width_max; // max width
+ int length_max; // max length
+ int height_max; // max height
+
+ float refl_avg; // running average of reflectivity of all walls
+ float refl_walls[6]; // left,right,front,back,ceiling,floor reflectivities
+
+ float sky_pct; // percent of sky hits
+
+ Vector room_mins; // room bounds
+ Vector room_maxs;
+
+ double last_dsp_change; // time since last dsp change
+
+ float diffusion; // 0..1.0 check radius (avg of width_avg) for # of props - scale diffusion based on # found
+ short iwall; // cycles through walls 0..5, ensuring only one trace per frame
+ short ent_count; // count of entities found in radius
+ bool bskyabove; // true if sky found above player (ie: outside)
+ bool broomready; // true if all distances are filled in and room is ready to check
+ short lowceiling; // if non-zero, ceiling directly above player if < 112 units
+};
+
+// dsp detection node
+
+struct das_node_t
+{
+ Vector vplayer; // position
+
+ bool fused; // true if valid node
+ bool fseesplayer; // true if node sees player on last check
+ short dsp_preset; // preset
+
+ int range_min; // min,max detection ranges
+ int range_max;
+
+ int dist; // last distance to player
+
+ // room parameters when node was created:
+
+ das_room_t room;
+};
+
+#define DAS_CNODES 40 // keep around last n nodes - must be same as DSP_CAUTO_PRESETS!!!
+
+das_node_t g_das_nodes[DAS_CNODES]; // all dsp detection nodes
+das_node_t *g_pdas_last_node = NULL; // last node that saw player
+
+int g_das_check_next; // next node to check
+int g_das_store_next; // next place to store node
+bool g_das_all_checked; // true if all nodes checked
+int g_das_checked_count; // count of nodes checked in latest pass
+
+das_room_t g_das_room; // room detector
+
+bool g_bdas_room_init = 0;
+bool g_bdas_init_nodes = 0;
+bool g_bdas_create_new_node = 0;
+
+bool DAS_TraceNodeToPlayer( das_room_t *proom, das_node_t *pnode );
+void DAS_InitAutoRoom( das_room_t *proom);
+void DAS_DebugDrawTrace ( trace_t *ptr, int r, int g, int b, float duration, int imax );
+
+Vector g_das_vec3[DAS_CWALLS]; // trace vectors to walls, ceiling, floor
+
+void DAS_InitNodes( void )
+{
+ Q_memset(g_das_nodes, 0, sizeof(das_node_t) * DAS_CNODES);
+ g_das_check_next = 0;
+ g_das_store_next = 0;
+ g_das_all_checked = 0;
+ g_das_checked_count = 0;
+
+ // init all rooms
+
+ for (int i = 0; i < DAS_CNODES; i++)
+ DAS_InitAutoRoom( &(g_das_nodes[i].room) );
+
+ // init trace vectors
+ // set up trace vectors for max, min width
+ float vl = DAS_ROOM_TRACE_LEN;
+ float vlu = DAS_ROOM_TRACE_LEN * 0.52;
+ float vlu2 = DAS_ROOM_TRACE_LEN * 0.48; // don't use 'perfect' diagonals
+
+ g_das_vec3[0].Init(vl, 0.0, 0.0); // x left
+ g_das_vec3[1].Init(-vl, 0.0, 0.0); // x right
+
+ g_das_vec3[2].Init(0.0, vl, 0.0); // y front
+ g_das_vec3[3].Init(0.0, -vl, 0.0); // y back
+
+ g_das_vec3[4].Init(-vlu, vlu2, 0.0); // diagonal front left
+ g_das_vec3[5].Init(vlu, -vlu2, 0.0); // diagonal rear right
+
+ g_das_vec3[6].Init(vlu, vlu2, 0.0); // diagonal front right
+ g_das_vec3[7].Init(-vlu, -vlu2, 0.0); // diagonal rear left
+
+ // set up trace vectors for max height - on x=y diagonal
+
+ g_das_vec3[8].Init(vlu, vlu2, vlu/2.0); // front right up A x,y,z/2 (IVEC_DIAG_UP)
+ g_das_vec3[9].Init(vlu, vlu2, vlu); // front right up B x,y,z
+ g_das_vec3[10].Init(vlu/2.0, vlu2/2.0, vlu); // front right up C x/2,y/2,z
+
+ g_das_vec3[11].Init(-vlu, -vlu2, vlu/2.0); // rear left up A -x,-y,z/2
+ g_das_vec3[12].Init(-vlu, -vlu2, vlu); // rear left up B -x,-y,z
+ g_das_vec3[13].Init(-vlu/2.0, -vlu2/2.0, vlu); // rear left up C -x/2,-y/2,z
+
+ // set up trace vectors for max height - on x axis & y axis
+
+ g_das_vec3[14].Init(-vlu, 0, vlu); // left up B -x,0,z
+ g_das_vec3[15].Init(0, vlu/2.0, vlu); // front up C -x/2,0,z
+
+ g_das_vec3[16].Init(0, -vlu, vlu); // rear up B x,0,z
+ g_das_vec3[17].Init(vlu/2.0, 0, vlu); // right up C x/2,0,z
+
+ g_das_vec3[18].Init(0.0, 0.0, vl); // up (IVEC_UP)
+ g_das_vec3[19].Init(0.0, 0.0, -vl); // down (IVEC_DOWN)
+}
+
+void DAS_InitAutoRoom( das_room_t *proom)
+{
+ Q_memset(proom, 0, sizeof (das_room_t));
+}
+
+// reset all nodes for next round of visibility checks between player & nodes
+
+void DAS_ResetNodes( void )
+{
+ for (int i = 0; i < DAS_CNODES; i++)
+ {
+ g_das_nodes[i].fseesplayer = false;
+ g_das_nodes[i].dist = 0;
+ }
+
+ g_das_all_checked = false;
+ g_das_checked_count = 0;
+ g_bdas_create_new_node = false;
+}
+
+// utility function - return next index, wrap at max
+
+int DAS_GetNextIndex( int *pindex, int max )
+{
+ int i = *pindex;
+ int j;
+
+ j = i+1;
+ if ( j >= max )
+ j = 0;
+
+ *pindex = j;
+
+ return i;
+}
+
+// returns true if dsp node is within range of player
+
+bool DAS_NodeInRange( das_room_t *proom, das_node_t *pnode )
+{
+ float dist;
+
+ dist = VectorLength( proom->vplayer - pnode->vplayer );
+
+ // player can still see previous room selection point, and it's less than n feet away,
+ // then flag this node as visible
+
+ pnode->dist = dist;
+
+ return ( dist <= pnode->range_max );
+}
+
+// update next valid node - set up internal node state if it can see player
+// called once per frame
+// returns true if all nodes have been checked
+
+bool DAS_CheckNextNode( das_room_t *proom )
+{
+ int i, j;
+
+ if ( g_das_all_checked )
+ return true;
+
+ // find next valid node
+
+ for (j = 0; j < DAS_CNODES; j++)
+ {
+ // track number of nodes checked
+
+ g_das_checked_count++;
+
+ // get next node in range to check
+
+ i = DAS_GetNextIndex( &g_das_check_next, DAS_CNODES );
+
+ if ( g_das_nodes[i].fused && DAS_NodeInRange( proom, &(g_das_nodes[i]) ) )
+ {
+ // trace to see if player can still see node,
+ // if so stop checking
+
+ if ( DAS_TraceNodeToPlayer( proom, &(g_das_nodes[i]) ))
+ goto checknode_exit;
+ }
+ }
+
+checknode_exit:
+
+ // flag that all nodes have been checked
+
+ if ( g_das_checked_count >= DAS_CNODES )
+ g_das_all_checked = true;
+
+ return g_das_all_checked;
+}
+
+
+int DAS_GetNextNodeIndex()
+{
+ return g_das_store_next;
+}
+// store new node for room
+
+void DAS_StoreNode( das_room_t *proom, int dsp_preset)
+{
+ // overwrite node in cyclic list
+
+ int i = DAS_GetNextIndex( &g_das_store_next, DAS_CNODES );
+
+ g_das_nodes[i].dsp_preset = dsp_preset;
+ g_das_nodes[i].fused = true;
+ g_das_nodes[i].vplayer = proom->vplayer;
+
+ // calculate node scanning range_max based on room size
+
+ if ( !proom->bskyabove )
+ {
+ // inside range - halls & tunnels have nodes every 5*width
+ g_das_nodes[i].range_max = fpmin((int)DAS_DIST_MAX, min(proom->width_max * 5, proom->length_max) );
+ g_das_nodes[i].range_min = DAS_DIST_MIN;
+ }
+ else
+ {
+ // outside range
+ g_das_nodes[i].range_max = DAS_DIST_MAX_OUTSIDE;
+ g_das_nodes[i].range_min = DAS_DIST_MIN_OUTSIDE;
+ }
+
+ g_das_nodes[i].fseesplayer = false;
+ g_das_nodes[i].dist = 0;
+
+ g_das_nodes[i].room = *proom;
+
+ // update last node visible as this node
+
+ g_pdas_last_node = &(g_das_nodes[i]);
+}
+
+// check all updated nodes,
+// return dsp_preset of largest node (by area) that can see player
+// return -1 if no preset found
+
+// NOTE: outside nodes can't see player if player is inside and vice versa
+// foutside is true if player is outside
+
+int DAS_GetDspPreset( bool foutside )
+{
+ int dsp_preset = -1;
+
+ int i;
+ // int dist_min = 100000;
+ int area_max = 0;
+ int area;
+
+ // find node that represents room with greatest floor area, return its preset.
+
+ for (i = 0; i < DAS_CNODES; i++)
+ {
+ if (g_das_nodes[i].fused && g_das_nodes[i].fseesplayer)
+ {
+ area = (g_das_nodes[i].room.width_max * g_das_nodes[i].room.length_max);
+
+ if ( g_das_nodes[i].room.bskyabove == foutside )
+ {
+ if (area > area_max)
+ {
+ area_max = area;
+ dsp_preset = g_das_nodes[i].dsp_preset;
+
+ // save pointer to last node that saw player
+
+ g_pdas_last_node = &(g_das_nodes[i]);
+ }
+ }
+/*
+
+ // find nearest node, return its preset
+
+ if (g_das_nodes[i].dist < dist_min)
+ {
+ if ( g_das_nodes[i].room.bskyabove == foutside )
+ {
+ dist_min = g_das_nodes[i].dist;
+ dsp_preset = g_das_nodes[i].dsp_preset;
+
+ // save pointer to last node that saw player
+
+ g_pdas_last_node = &(g_das_nodes[i]);
+
+ }
+ }
+*/
+ }
+ }
+
+ return dsp_preset;
+}
+
+// custom trace filter:
+// a) never hit player or monsters or entities
+// b) always hit world, or moveables or static props
+
+class CTraceFilterDAS : public ITraceFilter
+{
+public:
+ bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+ {
+ IClientUnknown *pUnk = static_cast<IClientUnknown*>(pHandleEntity);
+ IClientEntity *pEntity;
+
+ if ( !pUnk )
+ return false;
+
+ // don't hit non-collideable props
+
+ if ( StaticPropMgr()->IsStaticProp( pHandleEntity ) )
+ {
+
+ ICollideable *pCollide = StaticPropMgr()->GetStaticProp( pHandleEntity);
+ if (!pCollide)
+ return false;
+ }
+
+ // don't hit any ents
+
+ pEntity = pUnk->GetIClientEntity();
+
+ if ( pEntity )
+ return false;
+
+ return true;
+ }
+
+ virtual TraceType_t GetTraceType() const
+ {
+ return TRACE_EVERYTHING_FILTER_PROPS;
+ }
+};
+
+#define DAS_TRACE_MASK (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW)
+
+// returns true if clear line exists between node and player
+// if node can see player, sets up node distance and flag fseesplayer
+
+bool DAS_TraceNodeToPlayer( das_room_t *proom, das_node_t *pnode )
+{
+ trace_t trP;
+ CTraceFilterDAS filterP;
+ bool fseesplayer = false;
+ float dist;
+ Ray_t ray;
+ ray.Init( proom->vplayer, pnode->vplayer );
+
+ g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterP, &trP );
+ dist = VectorLength( proom->vplayer - pnode->vplayer );
+
+ // player can still see previous room selection point, and it's less than n feet away,
+ // then flag this node as visible
+
+ if ( !trP.DidHit() && (dist <= DAS_DIST_MAX) )
+ {
+ fseesplayer = true;
+ pnode->dist = dist;
+ }
+
+ pnode->fseesplayer = fseesplayer;
+
+ return fseesplayer;
+}
+
+// update room boundary maxs, mins
+
+void DAS_SetRoomBounds( das_room_t *proom, Vector &hit, bool bheight )
+{
+ Vector maxs, mins;
+
+ maxs = proom->room_maxs;
+ mins = proom->room_mins;
+
+ if (!bheight)
+ {
+ if (hit.x > maxs.x)
+ maxs.x = hit.x;
+
+ if (hit.x < mins.x)
+ mins.x = hit.x;
+
+ if (hit.z > maxs.z)
+ maxs.z = hit.z;
+
+ if (hit.z < mins.z)
+ mins.z = hit.z;
+ }
+
+ if (bheight)
+ {
+ if (hit.y > maxs.y)
+ maxs.y = hit.y;
+
+ if (hit.y < mins.y)
+ mins.y = hit.y;
+ }
+
+ proom->room_maxs = maxs;
+ proom->room_mins = mins;
+}
+
+// when all walls are updated, calculate max length, width, height, reflectivity, sky hit%, room center
+// returns true if room parameters are in good location to place a node
+// returns false if room parameters are not in good location to place a node
+// note: false occurs if up vector doesn't hit sky, but one or more up diagonal vectors do hit sky
+
+bool DAS_CalcRoomProps( das_room_t *proom )
+{
+ int length_max = 0;
+ int width_max = 0;
+ int height_max = 0;
+ int dist[4];
+ float area1, area2;
+ int height;
+ int i;
+ int j;
+ int k;
+ bool b_diaghitsky = false;
+
+ // reject this location if up vector doesn't hit sky, but
+ // one or more up diagonals do hit sky -
+ // in this case, player is under a slight overhang, narrow bridge, or
+ // standing just inside a window or doorway. keep looking for better node location
+
+ for (i = IVEC_DIAG_UP; i < IVEC_UP; i++)
+ {
+ if (proom->skyhits[i] > 0.0)
+ b_diaghitsky = true;
+ }
+
+ if (b_diaghitsky && !(proom->skyhits[IVEC_UP] > 0.0))
+ return false;
+
+ // get all distance pairs
+
+ for (i = 0; i < IVEC_DIAG_UP; i+=2)
+ dist[i/2] = proom->dist[i] + proom->dist[i+1]; // 1st pair is width
+
+ // if areas differ by more than 25%
+ // select the pair with the greater area
+
+ // if areas do not differ by more than 25%, select the pair with the
+ // longer measured distance. Filters incorrect selection due to diagonals.
+
+ area1 = (float)(dist[0] * dist[1]);
+ area2 = (float)(dist[2] * dist[3]);
+
+ area1 = (int)area1 == 0 ? 1.0 : area1;
+ area2 = (int)area2 == 0 ? 1.0 : area2;
+
+ if ( PercentDifference(area1, area2) > 0.25 )
+ {
+ // areas are more than 25% different - select pair with greater area
+
+ j = area1 > area2 ? 0 : 2;
+ }
+ else
+ {
+ // select pair with longer measured distance
+
+ int iMaxDist = 0; // index to max dist
+ int dmax = 0;
+
+ for (i = 0; i < 4; i++)
+ {
+ if (dist[i] > dmax)
+ {
+ dmax = dist[i];
+ iMaxDist = i;
+ }
+ }
+
+ j = iMaxDist > 1 ? 2 : 0;
+ }
+
+
+ // width is always the smaller of the dimensions
+
+ width_max = min (dist[j], dist[j+1]);
+ length_max = max (dist[j], dist[j+1]);
+
+ // get max height
+
+ for (i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
+ {
+ height = proom->dist[i];
+
+ if (height > height_max)
+ height_max = height;
+ }
+
+ proom->length_max = length_max;
+ proom->width_max = width_max;
+ proom->height_max = height_max;
+
+ // get room max,min from chosen width, depth
+ // 0..3 or 4..7
+
+ for ( i = j*2; i < 4+(j*2); i++)
+ DAS_SetRoomBounds( proom, proom->hit[i], false );
+
+ // get room height min from down trace
+
+ proom->room_mins.z = proom->hit[IVEC_DOWN].z;
+
+ // reset room height max to player trace height
+
+ proom->room_maxs.z = proom->vplayer.z;
+
+ // draw box around room max,min
+
+ if (das_debug.GetInt() == 6)
+ {
+ // draw box around all objects detected
+ Vector maxs = proom->room_maxs;
+ Vector mins = proom->room_mins;
+ Vector orig = (maxs + mins) / 2.0;
+ Vector absMax = maxs - orig;
+ Vector absMin = mins - orig;
+
+ CDebugOverlay::AddBoxOverlay( orig, absMax, absMin, vec3_angle, 255, 0, 255, 0, 60.0f );
+ }
+ // calculate average reflectivity
+
+ float refl = 0.0;
+
+ // average reflectivity for walls
+
+ // 0..3 or 4..7
+
+ for ( k = 0, i = j*2; i < 4+(j*2); i++, k++)
+ {
+ refl += proom->reflect[i];
+ proom->refl_walls[k] = proom->reflect[i];
+ }
+
+ // assume ceiling is open
+
+ proom->refl_walls[4] = 0.0;
+
+ // get ceiling reflectivity, if any non zero
+
+ for ( i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
+ {
+ if (proom->reflect[i] == 0.0)
+ {
+ // if any upward trace hit sky, exit;
+ // ceiling reflectivity is 0.0
+
+ proom->refl_walls[4] = 0.0;
+
+ i = IVEC_DOWN; // exit loop
+ }
+ else
+ {
+
+ // upward trace didn't hit sky, keep checking
+
+ proom->refl_walls[4] = proom->reflect[i];
+ }
+ }
+
+ // add in ceiling reflectivity, if any
+
+ refl += proom->refl_walls[4];
+
+ // get floor reflectivity
+
+ refl += proom->reflect[IVEC_DOWN];
+ proom->refl_walls[5] = proom->reflect[IVEC_DOWN];
+
+ proom->refl_avg = refl / 6.0;
+
+ // calculate sky hit percent for this wall
+
+ float sky_pct = 0.0;
+
+ // 0..3 or 4..7
+
+ for ( i = j*2; i < 4+(j*2); i++)
+ sky_pct += proom->skyhits[i];
+
+ for ( i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
+ {
+ if (proom->skyhits[i] > 0.0)
+ {
+ // if any upward trace hit sky, exit loop
+ sky_pct += proom->skyhits[i];
+ i = IVEC_DOWN;
+ }
+ }
+
+ // get floor skyhit
+
+ sky_pct += proom->skyhits[IVEC_DOWN];
+
+ proom->sky_pct = sky_pct;
+
+ // check for sky above
+ proom->bskyabove = false;
+
+ for (i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
+ {
+ if (proom->skyhits[i] > 0.0)
+ proom->bskyabove = true;
+ }
+
+ return true;
+}
+
+// return true if trace hit solid
+// return false if trace hit sky or didn't hit anything
+
+bool DAS_HitSolid( trace_t *ptr )
+{
+ // if hit nothing return false
+
+ if (!ptr->DidHit())
+ return false;
+
+ // if hit sky, return false (not solid)
+ if (ptr->surface.flags & SURF_SKY)
+ return false;
+
+ return true;
+}
+
+// returns true if trace hit sky
+
+bool DAS_HitSky( trace_t *ptr )
+{
+ if (ptr->DidHit() && (ptr->surface.flags & SURF_SKY))
+ return true;
+ if (!ptr->DidHit() )
+ {
+ float dz = ptr->endpos.z - ptr->startpos.z;
+ if ( dz > 200*12.0f )
+ return true;
+ }
+ return false;
+}
+
+
+bool DAS_ScanningForHeight( das_room_t *proom )
+{
+ return (proom->iwall >= IVEC_DIAG_UP);
+}
+
+bool DAS_ScanningForWidth( das_room_t *proom )
+{
+ return (proom->iwall < IVEC_DIAG_UP);
+}
+
+bool DAS_ScanningForFloor( das_room_t *proom )
+{
+ return (proom->iwall == IVEC_DOWN);
+}
+
+ConVar das_door_height("adsp_door_height", "112"); // standard door height hl2
+ConVar das_wall_height("adsp_wall_height", "128"); // standard wall height hl2
+ConVar das_low_ceiling("adsp_low_ceiling", "108"); // low ceiling height hl2
+
+
+// set origin for tracing out to walls to point above player's head
+// allows calculations over walls and floor obstacles, and above door openings
+
+// WARNING: the current settings are optimal for skipping floor and ceiling clutter,
+// and for detecting rooms without 'looking' through doors or windows. Don't change these cvars for hl2!
+
+void DAS_SetTraceHeight( das_room_t *proom, trace_t *ptrU, trace_t *ptrD )
+{
+ // NOTE: when tracing down through player's box, endpos and startpos are reversed and
+ // startsolid and allsolid are true.
+
+ int zup = abs(ptrU->endpos.z - ptrU->startpos.z); // height above player's head
+ int zdown = abs(ptrD->endpos.z - ptrD->startpos.z); // distance to floor from player's head
+ int h;
+ h = zup + zdown;
+
+ int door_height = das_door_height.GetInt();
+ int wall_height = das_wall_height.GetInt();
+ int low_ceiling = das_low_ceiling.GetInt();
+
+ if (h > low_ceiling && h <= wall_height)
+ {
+ // low ceiling - trace out just above standard door height @ 112
+ if (h > door_height)
+ proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + door_height + 1;
+ else
+ proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + h - 1;
+ }
+ else if ( h > wall_height )
+ {
+ // tall ceiling - trace out over standard walls @ 128
+
+ proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + wall_height + 1;
+ }
+ else
+ {
+ // very low ceiling, trace out from just below ceiling
+ proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + h - 1;
+ proom->lowceiling = h;
+ }
+
+ Assert (proom->vplayer.z <= ptrU->endpos.z);
+
+ if (das_debug.GetInt() > 1)
+ {
+ // draw line to height, and between floor and ceiling
+
+ CDebugOverlay::AddLineOverlay( ptrD->endpos, ptrU->endpos, 0, 255, 0, 255, false, 20 );
+
+ Vector mins;
+ Vector maxs;
+ mins.Init(-1,-1,-2.0);
+ maxs.Init(1,1,0);
+
+ CDebugOverlay::AddBoxOverlay( proom->vplayer, mins, maxs, vec3_angle, 255, 0, 0, 0, 20 );
+
+ CDebugOverlay::AddBoxOverlay( ptrU->endpos, mins, maxs, vec3_angle, 0, 255, 0, 0, 20 );
+ CDebugOverlay::AddBoxOverlay( ptrD->endpos, mins, maxs, vec3_angle, 0, 255, 0, 0, 20 );
+
+ }
+}
+
+// prepare room struct for new round of checks:
+// clear out struct,
+// init trace height origin by finding space above player's head
+// returns true if player is in valid position to begin checks from
+
+bool DAS_StartTraceChecks( das_room_t *proom )
+{
+ // starting new check: store player position, init maxs, mins
+
+ proom->vplayer_eyes = MainViewOrigin();
+ proom->vplayer = MainViewOrigin();
+
+ proom->height_max = 0;
+ proom->width_max = 0;
+ proom->length_max = 0;
+ proom->room_maxs.Init (0.0, 0.0, 0.0);
+ proom->room_mins.Init (10000.0, 10000.0, 10000.0);
+
+ proom->lowceiling = 0;
+
+ // find point between player's head and ceiling - trace out to walls from here
+
+ trace_t trU, trD;
+ CTraceFilterDAS filterU, filterD;
+
+ Vector v_dir = g_das_vec3[IVEC_DOWN]; // down - find floor
+
+ Vector endpoint = proom->vplayer + v_dir;
+
+ Ray_t ray;
+ ray.Init( proom->vplayer, endpoint );
+
+ g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterD, &trD );
+
+ // if player jumping or in air, don't continue
+
+ if (trD.DidHit() && abs(trD.endpos.z - trD.startpos.z) > 72)
+ return false;
+
+ v_dir = g_das_vec3[IVEC_UP]; // up - find ceiling
+
+ endpoint = proom->vplayer + v_dir;
+
+ ray.Init( proom->vplayer, endpoint );
+
+ g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterU, &trU );
+
+ // if down trace hits floor, set trace height, otherwise default is player eye location
+
+ if ( DAS_HitSolid( &trD) )
+ DAS_SetTraceHeight( proom, &trU, &trD );
+
+ return true;
+}
+
+void DAS_DebugDrawTrace ( trace_t *ptr, int r, int g, int b, float duration, int imax)
+{
+
+ // das_debug == 3: draw horizontal trace bars for room width/depth detection
+ // das_debug == 4: draw upward traces for height detection
+
+ if (das_debug.GetInt() != imax)
+ return;
+
+ CDebugOverlay::AddLineOverlay( ptr->startpos, ptr->endpos, r, g, b, 255, false, duration );
+
+ Vector mins;
+ Vector maxs;
+ mins.Init(-1,-1,-2.0);
+ maxs.Init(1,1,0);
+
+ CDebugOverlay::AddBoxOverlay( ptr->endpos, mins, maxs, vec3_angle, r, g, b, 0, duration );
+
+}
+
+// wall surface data
+
+struct das_surfdata_t
+{
+ float dist; // distance to player
+ float reflectivity; // acoustic reflectivity of material on surface
+ Vector hit; // trace hit location
+ Vector norm; // wall normal at hit location
+};
+
+// trace hit wall surface, get info about surface and store in surfdata struct
+// if scanning for height, bounce a second trace off of ceiling and get dist to floor
+
+void DAS_GetSurfaceData( das_room_t *proom, trace_t *ptr, das_surfdata_t *psurfdata )
+{
+
+ float dist; // distance to player
+ float reflectivity; // acoustic reflectivity of material on surface
+ Vector hit; // trace hit location
+ Vector norm; // wall normal at hit location
+ surfacedata_t *psurf;
+
+ psurf = physprop->GetSurfaceData( ptr->surface.surfaceProps );
+
+ reflectivity = psurf ? psurf->audio.reflectivity : DAS_REFLECTIVITY_NORM;
+
+ // keep wall hit location and normal, to calc room bounds and center
+
+ norm = ptr->plane.normal;
+
+ // get length to hit location
+
+ dist = VectorLength(ptr->endpos - ptr->startpos);
+
+ // if started tracing from within player box, startpos & endpos may be flipped
+
+ if (ptr->endpos.z >= ptr->startpos.z)
+ hit = ptr->endpos;
+ else
+ hit = ptr->startpos;
+
+ // if checking for max height by bouncing several vectors off of ceiling:
+ // ignore returned normal from 1st bounce, just search straight down from trace hit location
+
+ if ( DAS_ScanningForHeight( proom ) && !DAS_ScanningForFloor( proom ) )
+ {
+ trace_t tr2;
+ CTraceFilterDAS filter2;
+
+ norm.Init(0.0, 0.0, -1.0);
+
+ Vector endpoint = hit + ( norm * DAS_ROOM_TRACE_LEN );
+
+ Ray_t ray;
+ ray.Init( hit, endpoint );
+
+ g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filter2, &tr2 );
+
+ //DAS_DebugDrawTrace( &tr2, 255, 255, 0, 10, 1);
+
+ if (tr2.DidHit())
+ {
+ // get distance between surfaces
+
+ dist = VectorLength(tr2.endpos - tr2.startpos);
+ }
+ }
+
+ // set up surface struct and return
+
+ psurfdata->dist = dist;
+ psurfdata->hit = hit;
+ psurfdata->norm = norm;
+ psurfdata->reflectivity = reflectivity;
+
+}
+
+
+// algorithm for detecting approximate size of space around player. Handles player in corner & non-axis aligned rooms.
+// also handles player on catwalk or player under small bridge/overhang.
+// The goal is to only change the dsp room description if the the player moves into
+// a space which is SIGNIFICANTLY different from the previously set dsp space.
+
+// save player position. find a point above player's head and trace out from here.
+
+// from player position, get max width and max length:
+
+// from player position,
+// a) trace x,-x, y,-y axes
+// b) trace xy, -xy, x-y, -x-y diagonals
+// c) select largest room size detected from max width, max length
+
+
+// from player position, get height
+// a) trace out along front-up (or left-up, back-up, right-up), save hit locations
+// b) trace down -z from hit locations
+// c) save max height
+
+// when max width, max length, max height all updated, get new player position
+
+// get average room size & wall materials:
+// update averages with one traceline per frame only
+// returns true if room is fully updated and ready to check
+
+bool DAS_UpdateRoomSize( das_room_t *proom )
+{
+ Vector endpoint;
+ Vector startpoint;
+ Vector v_dir;
+ int iwall;
+ bool bskyhit = false;
+ das_surfdata_t surfdata;
+
+ // do nothing if room already fully checked
+
+ if ( proom->broomready )
+ return true;
+
+ // cycle through all walls, floor, ceiling
+ // get wall index
+
+ iwall = proom->iwall;
+
+ // get height above player and init proom for new round of checks
+
+ if (iwall == 0)
+ {
+ if (!DAS_StartTraceChecks( proom ))
+ return false; // bad location to check room - player is jumping etc.
+ }
+
+ // get trace vector
+
+ v_dir = g_das_vec3[iwall];
+
+ // trace out from trace origin, in axis-aligned direction or along diagonals
+
+ // if looking for max height, trace from top of player's eyes
+
+ if ( DAS_ScanningForHeight( proom ) )
+ {
+ startpoint = proom->vplayer_eyes;
+ endpoint = proom->vplayer_eyes + v_dir;
+ }
+ else
+ {
+ startpoint = proom->vplayer;
+ endpoint = proom->vplayer + v_dir;
+ }
+
+ // try less expensive world-only trace first (no props, no ents - just try to hit walls)
+
+ trace_t tr;
+ CTraceFilterWorldOnly filter;
+
+ Ray_t ray;
+ ray.Init( startpoint, endpoint );
+
+ g_pEngineTraceClient->TraceRay( ray, CONTENTS_SOLID, &filter, &tr );
+
+ // if didn't hit world, or we hit sky when looking horizontally,
+ // retrace, this time including props
+
+ if ( !DAS_HitSolid( &tr ) && DAS_ScanningForWidth( proom ) )
+ {
+ CTraceFilterDAS filterDas;
+
+ ray.Init( startpoint, endpoint );
+ g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterDas, &tr );
+ }
+
+ if (das_debug.GetInt() > 2)
+ {
+ // draw trace lines
+
+ if ( DAS_HitSolid( &tr ) )
+ DAS_DebugDrawTrace( &tr, 0, 255, 255, 10, DAS_ScanningForHeight( proom ) + 3);
+ else
+ DAS_DebugDrawTrace( &tr, 255, 0, 0, 10, DAS_ScanningForHeight( proom ) + 3); // red lines if sky hit or no hit
+ }
+
+ // init surface data with defaults, in case we didn't hit world
+
+ surfdata.dist = DAS_ROOM_TRACE_LEN;
+ surfdata.reflectivity = DAS_REFLECTIVITY_SKY; // assume sky or open area
+ surfdata.hit = endpoint; // trace hit location
+ surfdata.norm = -v_dir;
+
+ // check for sky hits
+
+ if ( DAS_HitSky( &tr ) )
+ {
+ bskyhit = true;
+
+ if ( DAS_ScanningForWidth( proom ) )
+ // ignore horizontal sky hits for distance calculations
+ surfdata.dist = 1.0;
+ else
+ surfdata.dist = surfdata.dist; // debug
+ }
+
+ // get length of trace if it hit world
+
+ // if hit solid and not sky (tr.DidHit() && !bskyhit)
+ // get surface information
+
+ if ( DAS_HitSolid( &tr) )
+ DAS_GetSurfaceData( proom, &tr, &surfdata );
+
+ // store surface data
+
+ proom->dist[iwall] = surfdata.dist;
+ proom->reflect[iwall] = clamp(surfdata.reflectivity, 0.0f, 1.0f);
+ proom->skyhits[iwall] = bskyhit ? 0.1 : 0.0;
+ proom->hit[iwall] = surfdata.hit;
+ proom->norm[iwall] = surfdata.norm;
+
+ // update wall counter
+
+ proom->iwall++;
+
+ if (proom->iwall == DAS_CWALLS)
+ {
+ bool b_good_node_location;
+
+ // calculate room mins, maxs, reflectivity etc
+
+ b_good_node_location = DAS_CalcRoomProps( proom );
+
+ // reset wall counter
+
+ proom->iwall = 0;
+ proom->broomready = b_good_node_location; // room ready to check if good node location
+
+ return b_good_node_location;
+ }
+
+ return false; // room not yet fully updated
+}
+
+// create entity enumerator for counting ents & summing volume of ents in room
+
+class CDasEntEnum : public IPartitionEnumerator
+{
+ public:
+ int m_count; // # of ents in space
+ float m_volume; // space occupied by ents
+
+ public:
+
+ void Reset()
+ {
+ m_count = 0;
+ m_volume = 0.0;
+ }
+
+ // called with each handle...
+
+ IterationRetval_t EnumElement( IHandleEntity *pHandleEntity )
+ {
+ float vol;
+
+ // get bounding box of entity
+ // Generate a collideable
+
+ ICollideable *pCollideable = g_pEngineTraceClient->GetCollideable( pHandleEntity );
+
+ if ( !pCollideable )
+ return ITERATION_CONTINUE;
+
+ // Check for solid
+
+ if ( !IsSolid( pCollideable->GetSolid(), pCollideable->GetSolidFlags() ) )
+ return ITERATION_CONTINUE;
+
+ m_count++;
+
+ // compute volume of space occupied by entity
+ Vector mins = pCollideable->OBBMins();
+ Vector maxs = pCollideable->OBBMaxs();
+
+ vol = fabs((maxs.x - mins.x) * (maxs.y - mins.y) * (maxs.z - mins.z));
+
+ m_volume += vol; // add to total vol
+
+ if (das_debug.GetInt() == 5)
+ {
+ // draw box around all objects detected
+
+ Vector orig = pCollideable->GetCollisionOrigin();
+ CDebugOverlay::AddBoxOverlay( orig, mins, maxs, pCollideable->GetCollisionAngles(), 255, 0, 255, 0, 60.0f );
+ }
+
+ return ITERATION_CONTINUE;
+ }
+};
+
+// determine # of solid ents/props within detected room boundaries
+// and set diffusion based on count of ents and spatial volume of ents
+
+void DAS_SetDiffusion( das_room_t *proom )
+{
+ // BRJ 7/12/05
+ // This was commented out because the y component of proom->room_mins, proom->room_maxs was never
+ // being computed, causing a bogus box to be sent to the partition system. The results of
+ // this computation (namely the diffusion + ent_count fields of das_room_t) were never being used.
+ // Therefore, we'll avoid the enumeration altogether
+
+ proom->diffusion = 0.0f;
+ proom->ent_count = 0;
+
+ /*
+ CDasEntEnum enumerator;
+ SpatialPartitionListMask_t mask = PARTITION_CLIENT_SOLID_EDICTS; // count only solid ents in room
+ int count;
+ float vol;
+ float volroom;
+ float dfn;
+
+ enumerator.Reset();
+
+ SpatialPartition()->EnumerateElementsInBox(mask, proom->room_mins, proom->room_maxs, true, &enumerator );
+
+ count = enumerator.m_count;
+ vol = enumerator.m_volume;
+
+ // compute diffusion from volume
+
+ // how much space around player is filled with props?
+
+ volroom = (proom->room_maxs.x - proom->room_mins.x) * (proom->room_maxs.y - proom->room_mins.y) * (proom->room_maxs.z - proom->room_mins.z);
+ volroom = fabs(volroom);
+
+ if ( !(int)volroom )
+ volroom = 1.0;
+
+ dfn = vol / volroom; // % of total volume occupied by props
+
+ dfn = clamp (dfn, 0.0, 1.0);
+
+ proom->diffusion = dfn;
+ proom->ent_count = count;
+ */
+}
+
+// debug routine to display current room params
+
+void DAS_DisplayRoomDEBUG( das_room_t *proom, bool fnew, float preset )
+{
+ float dx,dy,dz;
+ Vector ctr;
+ float count;
+
+ if (das_debug.GetInt() == 0)
+ return;
+
+ dx = proom->length_max / 12.0;
+ dy = proom->width_max / 12.0;
+ dz = proom->height_max / 12.0;
+
+ float refl = proom->refl_avg;
+
+ count = (float)(proom->ent_count);
+ float fsky = (proom->bskyabove ? 1.0 : 0.0);
+
+ if (fnew)
+ DevMsg( "NEW DSP NODE: size:(%.0f,%.0f) height:(%.0f) dif %.4f : refl %.4f : cobj: %.0f : sky %.0f \n", dx, dy, dz, proom->diffusion, refl, count, fsky);
+
+ if (!fnew && preset < 0.0)
+ return;
+
+ if (preset >= 0.0)
+ {
+ if (proom == NULL)
+ return;
+
+ DevMsg( "DSP PRESET: %.0f size:(%.0f,%.0f) height:(%.0f) dif %.4f : refl %.4f : cobj: %.0f : sky %.0f \n", preset, dx, dy, dz, proom->diffusion, refl, count, fsky);
+ return;
+ }
+
+ // draw box around new node location
+
+ Vector mins;
+ Vector maxs;
+ mins.Init(-8,-8,-16);
+ maxs.Init(8,8,0);
+
+ CDebugOverlay::AddBoxOverlay( proom->vplayer, mins, maxs, vec3_angle, 0, 0, 255, 0, 1000.0f );
+
+ // draw red box around node origin
+
+ mins.Init(-0.5,-0.5,-1.0);
+ maxs.Init(0.5,0.5,0);
+
+ CDebugOverlay::AddBoxOverlay( proom->vplayer, mins, maxs, vec3_angle, 255, 0, 0, 0, 1000.0f );
+
+ CDebugOverlay::AddTextOverlay( proom->vplayer, 0, 10, 1.0, "DSP NODE" );
+}
+
+// check newly calculated room parameters against current stored params.
+// if different, return true.
+// NOTE: only call when all proom params have been calculated.
+// return false if this is not a good location for creating a new node
+
+bool DAS_CheckNewRoom( das_room_t *proom )
+{
+ bool bnewroom;
+ float dw,dw2,dr,ds,dh;
+ int cchanged = 0;
+ das_room_t *proom_prev = NULL;
+ Vector2D v2d;
+ Vector v3d;
+ float dist;
+
+ // player can't see previous node, determine if this is a good place to lay down
+ // a new node. Get room at last seen node for comparison
+
+ if (g_pdas_last_node)
+ proom_prev = &(g_pdas_last_node->room);
+
+ // no previous room node saw player, go create new room node
+
+ if (!proom_prev)
+ {
+ bnewroom = true;
+ goto check_ret;
+ }
+
+ // if player not at least n feet from last node, return false
+
+ v3d = proom->vplayer - proom_prev->vplayer;
+ v2d.Init(v3d.x, v3d.y);
+
+ dist = Vector2DLength(v2d);
+
+ if (dist <= DAS_DIST_MIN)
+ return false;
+
+ // see if room size has changed significantly since last node
+
+ bnewroom = true;
+
+ dw = 0.0;
+ dw2 = 0.0;
+ dh = 0.0;
+ dr = 0.0;
+
+ if ( proom_prev->width_max != 0 )
+ dw = (float)proom->width_max / (float)proom_prev->width_max; // max width delta
+
+ if ( proom_prev->length_max != 0 )
+ dw2 = (float)proom->length_max / (float)proom_prev->length_max; // max length delta
+
+ if ( proom_prev->height_max != 0 )
+ dh = (float)proom->height_max / (float)proom_prev->height_max; // max height delta
+
+ if ( proom_prev->refl_avg != 0.0 )
+ dr = proom->refl_avg / proom_prev->refl_avg; // reflectivity delta
+
+ ds = fabs( proom->sky_pct - proom_prev->sky_pct); // sky hits delta
+
+ if (dw > 1.0) dw = 1.0 / dw;
+ if (dw2 > 1.0) dw = 1.0 / dw2;
+ if (dh > 1.0) dh = 1.0 / dh;
+ if (dr > 1.0) dr = 1.0 / dr;
+
+ if ( (1.0 - dw) >= DAS_WIDTH_MIN )
+ cchanged++;
+
+ if ( (1.0 - dw2) >= DAS_WIDTH_MIN )
+ cchanged++;
+
+// if ( (1.0 - dh) >= DAS_WIDTH_MIN ) // don't change room based on height change
+// cchanged++;
+
+ // new room only if at least 1 changed
+
+ if (cchanged >= 1)
+ goto check_ret;
+
+// if ( (1.0 - dr) >= DAS_REFL_MIN ) // don't change room based on reflectivity change
+// goto check_ret;
+
+// if (ds >= DAS_SKYHIT_MIN )
+// goto check_ret;
+
+ // new room if sky above changes state
+
+ if (proom->bskyabove != proom_prev->bskyabove)
+ goto check_ret;
+
+ // room didn't change significantly, return false
+
+ bnewroom = false;
+
+check_ret:
+
+ if ( bnewroom )
+ {
+ // if low ceiling detected < 112 units, and max height is > low ceiling height by 20%, discard - no change
+ // this detects player in doorway, under pipe or narrow bridge
+
+ if ( proom->lowceiling && (proom->lowceiling < proom->height_max))
+ {
+ float h = (float)(proom->lowceiling) / (float)proom->height_max;
+
+ if (h < 0.8)
+ return false;
+ }
+
+ DAS_SetDiffusion( proom );
+ }
+
+ DAS_DisplayRoomDEBUG( proom, bnewroom, -1.0 );
+
+ return bnewroom;
+}
+
+
+extern int DSP_ConstructPreset( bool bskyabove, int width, int length, int height, float fdiffusion, float freflectivity, float *psurf_refl, int inode, int cnodes );
+
+// select new dsp_room based on size, wall materials
+// (or modulate params for current dsp)
+// returns new preset # for dsp_automatic
+
+int DAS_GetRoomDSP( das_room_t *proom, int inode )
+{
+
+ // preset constructor
+ // call dsp module with params, get dsp preset back
+
+ bool bskyabove = proom->bskyabove;
+ int width = proom->width_max;
+ int length = proom->length_max;
+ int height = proom->height_max;
+ float fdiffusion = proom->diffusion;
+ float freflectivity = proom->refl_avg;
+ float surf_refl[6];
+
+ // fill array of surface reflectivities - for left,right,front,back,ceiling,floor
+
+ for (int i = 0; i < 6; i++)
+ surf_refl[i] = proom->refl_walls[i];
+
+ return DSP_ConstructPreset( bskyabove, width, length, height, fdiffusion, freflectivity, surf_refl, inode, DAS_CNODES );
+
+}
+
+
+// main entry point: call once per frame to update dsp_automatic
+// for automatic room detection. dsp_room must be set to DSP_AUTOMATIC to enable.
+// NOTE: this routine accumulates traceline information over several frames - it
+// never traces more than 3 times per call, and normally just once per call.
+
+void DAS_CheckNewRoomDSP( )
+{
+ VPROF("DAS_CheckNewRoomDSP");
+ das_room_t *proom = &g_das_room;
+ int dsp_preset;
+ bool bRoom_ready = false;
+
+ // if listener has not been updated, do nothing
+
+ if ((listener_origin == vec3_origin) &&
+ (listener_forward == vec3_origin) &&
+ (listener_right == vec3_origin) &&
+ (listener_up == vec3_origin) )
+ return;
+
+ if ( !SND_IsInGame() )
+ return;
+
+ // make sure we init nodes & vectors first time this is called
+
+ if ( !g_bdas_init_nodes )
+ {
+ g_bdas_init_nodes = 1;
+ DAS_InitNodes();
+ }
+
+ if ( !DSP_CheckDspAutoEnabled())
+ {
+ // make sure room params are reinitialized each time autoroom is selected
+
+ g_bdas_room_init = 0;
+ return;
+ }
+
+ if ( !g_bdas_room_init )
+ {
+ g_bdas_room_init = 1;
+
+ DAS_InitAutoRoom( proom );
+ }
+
+ // get time
+
+ double dtime = g_pSoundServices->GetHostTime();
+
+ // compare to previous time - don't check for new room until timer expires
+ // ie: wait at least DAS_AUTO_WAIT seconds between preset changes
+
+ if ( fabs(dtime - proom->last_dsp_change) < DAS_AUTO_WAIT )
+ return;
+
+ // first, update room size parameters, see if room is ready to check - if room is updated, return true right away
+
+ // 3 traces per frame while accumulating room size info
+
+ for (int i = 0 ; i < 3; i++)
+ bRoom_ready = DAS_UpdateRoomSize( proom );
+
+ if (!bRoom_ready)
+ return;
+
+
+ if ( !g_bdas_create_new_node )
+ {
+ // next, check all nodes for line of sight to player - if all checked, return true right away
+
+ if ( !DAS_CheckNextNode( proom ) )
+ {
+ // check all nodes first
+
+ return;
+ }
+
+ // find out if any previously stored nodes can see player,
+ // if so, get closest node's dsp preset
+
+ dsp_preset = DAS_GetDspPreset( proom->bskyabove );
+
+ if (dsp_preset != -1)
+ {
+ // an existing node can see player - just set preset and return
+
+ if (dsp_preset != dsp_room_GetInt())
+ {
+ // changed preset, so update timestamp
+
+ proom->last_dsp_change = g_pSoundServices->GetHostTime();
+
+ if (g_pdas_last_node)
+ DAS_DisplayRoomDEBUG( &(g_pdas_last_node->room), false, (float)dsp_preset );
+ }
+
+ DSP_SetDspAuto( dsp_preset );
+
+ goto check_new_room_exit;
+ }
+ }
+
+ g_bdas_create_new_node = true;
+
+ // no nodes can see player, need to try to create a new one
+
+ // check for 'new' room around player
+
+ if ( DAS_CheckNewRoom( proom ) )
+ {
+ // new room found - update dsp_automatic
+
+ dsp_preset = DAS_GetRoomDSP( proom, DAS_GetNextNodeIndex() );
+
+ DSP_SetDspAuto( dsp_preset );
+
+ // changed preset, so update timestamp
+
+ proom->last_dsp_change = g_pSoundServices->GetHostTime();
+
+ // save room as new node
+
+ DAS_StoreNode( proom, dsp_preset );
+
+ goto check_new_room_exit;
+ }
+
+check_new_room_exit:
+
+ // reset new node creation flag - start checking for visible nodes again
+
+ g_bdas_create_new_node = false;
+
+ // reset room checking flag - start checking room around player again
+
+ proom->broomready = false;
+
+ // reset node checking flag - start checking nodes around player again
+
+ DAS_ResetNodes();
+
+ return;
+}
+
+// remap contents of volumes[] arrary if sound originates from player, or is music, and is 100% 'mono'
+// ie: same volume in all channels
+
+void RemapPlayerOrMusicVols( channel_t *ch, int volumes[CCHANVOLUMES/2], bool fplayersound, bool fmusicsound, float mono )
+{
+ VPROF_("RemapPlayerOrMusicVols", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+
+ if ( !fplayersound && !fmusicsound )
+ return; // no remapping
+
+ if ( ch->flags.bSpeaker )
+ return; // don't remap speaker sounds rebroadcast on player
+
+ // get total volume
+
+ float vol_total = 0.0;
+ int k;
+
+ for (k = 0; k < CCHANVOLUMES/2; k++)
+ vol_total += (float)volumes[k];
+
+ if ( !g_AudioDevice->IsSurround() )
+ {
+ if (mono < 1.0)
+ return;
+
+ // remap 2 chan non-spatialized versions of player and music sounds
+ // note: this is required to keep volumes same as 4 & 5 ch cases!
+
+ float vol_dist_music[] = {1.0, 1.0}; // FL, FR music volumes
+ float vol_dist_player[] = {1.0, 1.0}; // FL, FR player volumes
+ float *pvol_dist;
+
+ pvol_dist = (fplayersound ? vol_dist_player : vol_dist_music);
+
+ for (k = 0; k < 2; k++)
+ volumes[k] = clamp((int)(vol_total * pvol_dist[k]), 0, 255);
+
+ return;
+ }
+
+ // surround sound configuration...
+
+ if ( fplayersound ) // && (ch->bstereowav && ch->wavtype != CHAR_DIRECTIONAL && ch->wavtype != CHAR_DISTVARIANT) )
+ {
+ // NOTE: player sounds also get n% overall volume boost.
+
+ //float vol_dist5[] = {0.29, 0.29, 0.09, 0.09, 0.63}; // FL, FR, RL, RR, FC - 5 channel (mono source) volume distribution
+ //float vol_dist5st[] = {0.29, 0.29, 0.09, 0.09, 0.63}; // FL, FR, RL, RR, FC - 5 channel (stereo source) volume distribution
+
+ float vol_dist5[] = {0.30, 0.30, 0.09, 0.09, 0.59}; // FL, FR, RL, RR, FC - 5 channel (mono source) volume distribution
+ float vol_dist5st[] = {0.30, 0.30, 0.09, 0.09, 0.59}; // FL, FR, RL, RR, FC - 5 channel (stereo source) volume distribution
+
+ float vol_dist4[] = {0.50, 0.50, 0.15, 0.15, 0.00}; // FL, FR, RL, RR, 0 - 4 channel (mono source) volume distribution
+ float vol_dist4st[] = {0.50, 0.50, 0.15, 0.15, 0.00}; // FL, FR, RL, RR, 0 - 4 channel (stereo source)volume distribution
+
+ float *pvol_dist;
+
+ if ( ch->flags.bstereowav && (ch->wavtype == CHAR_OMNI || ch->wavtype == CHAR_SPATIALSTEREO || ch->wavtype == 0))
+ {
+ pvol_dist = (g_AudioDevice->IsSurroundCenter() ? vol_dist5st : vol_dist4st);
+ }
+ else
+ {
+ pvol_dist = (g_AudioDevice->IsSurroundCenter() ? vol_dist5 : vol_dist4);
+ }
+
+ for (k = 0; k < 5; k++)
+ volumes[k] = clamp((int)(vol_total * pvol_dist[k]), 0, 255);
+
+ return;
+ }
+
+ // Special case for music in surround mode
+
+ if ( fmusicsound )
+ {
+ float vol_dist5[] = {0.5, 0.5, 0.25, 0.25, 0.0}; // FL, FR, RL, RR, FC - 5 channel distribution
+ float vol_dist4[] = {0.5, 0.5, 0.25, 0.25, 0.0}; // FL, FR, RL, RR, 0 - 4 channel distribution
+ float *pvol_dist;
+
+ pvol_dist = (g_AudioDevice->IsSurroundCenter() ? vol_dist5 : vol_dist4);
+
+ for (k = 0; k < 5; k++)
+ volumes[k] = clamp((int)(vol_total * pvol_dist[k]), 0, 255);
+
+ return;
+ }
+
+ return;
+}
+
+static int s_nSoundGuid = 0;
+
+void SND_ActivateChannel( channel_t *pChannel )
+{
+ Q_memset( pChannel, 0, sizeof(*pChannel) );
+ g_ActiveChannels.Add( pChannel );
+ pChannel->guid = ++s_nSoundGuid;
+}
+
+/*
+=================
+SND_Spatialize
+=================
+*/
+void SND_Spatialize(channel_t *ch)
+{
+ VPROF("SND_Spatialize");
+
+ vec_t dist;
+ Vector source_vec;
+ Vector source_vec_DL;
+ Vector source_vec_DR;
+ Vector source_doppler_left;
+ Vector source_doppler_right;
+
+ bool fdopplerwav = false;
+ bool fplaydopplerwav = false;
+ bool fvalidentity;
+ float gain;
+ float scale = 1.0;
+ bool fplayersound = false;
+ bool fmusicsound = false;
+ float mono = 0.0;
+ bool bAttenuated = true;
+
+ ch->dspface = 1.0; // default facing direction: always facing player
+ ch->dspmix = 0; // default mix 0% dsp_room fx
+ ch->distmix = 0; // default 100% left (near) wav
+
+#if !defined( _X360 )
+ if ( ch->sfx &&
+ ch->sfx->pSource &&
+ ch->sfx->pSource->GetType() == CAudioSource::AUDIO_SOURCE_VOICE )
+ {
+ Voice_Spatialize( ch );
+ }
+#endif
+
+ if ( IsSoundSourceLocalPlayer( ch->soundsource ) && !toolframework->InToolMode() )
+ {
+ // sounds coming from listener actually come from a short distance directly in front of listener
+ // in tool mode however, the view entity is meaningless, since we're viewing from arbitrary locations in space
+ fplayersound = true;
+ }
+
+ // assume 'dry', playeverwhere sounds are 'music' or 'voiceover'
+
+ if ( ch->flags.bdry && ch->dist_mult <= 0 )
+ {
+ fmusicsound = true;
+ fplayersound = false;
+ }
+
+ // update channel's position in case ent that made the sound is moving.
+ QAngle source_angles;
+ source_angles.Init(0.0, 0.0, 0.0);
+ Vector entOrigin = ch->origin;
+
+ bool looping = false;
+
+ CAudioSource *pSource = ch->sfx ? ch->sfx->pSource : NULL;
+ if ( pSource )
+ {
+ looping = pSource->IsLooped();
+ }
+
+ SpatializationInfo_t si;
+ si.info.Set(
+ ch->soundsource,
+ ch->entchannel,
+ ch->sfx ? ch->sfx->getname() : "",
+ ch->origin,
+ ch->direction,
+ ch->master_vol,
+ DIST_MULT_TO_SNDLVL( ch->dist_mult ),
+ looping,
+ ch->pitch,
+ listener_origin,
+ ch->speakerentity );
+
+ si.type = SpatializationInfo_t::SI_INSPATIALIZATION;
+ si.pOrigin = &entOrigin;
+ si.pAngles = &source_angles;
+ si.pflRadius = NULL;
+ if ( ch->soundsource != 0 && ch->radius == 0 )
+ {
+ si.pflRadius = &ch->radius;
+ }
+
+ {
+ VPROF_("SoundServices->GetSoundSpatializtion", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+ fvalidentity = g_pSoundServices->GetSoundSpatialization( ch->soundsource, si );
+ }
+
+ if ( ch->flags.bUpdatePositions )
+ {
+ AngleVectors( source_angles, &ch->direction );
+ ch->origin = entOrigin;
+ }
+ else
+ {
+ VectorAngles( ch->direction, source_angles );
+ }
+
+ if ( ch->userdata != 0 )
+ {
+ g_pSoundServices->GetToolSpatialization( ch->userdata, ch->guid, si );
+ if ( ch->flags.bUpdatePositions )
+ {
+ AngleVectors( source_angles, &ch->direction );
+ ch->origin = entOrigin;
+ }
+ }
+
+#if 0
+ // !!!UNDONE - above code assumes the ENT hasn't been removed or respawned as another ent!
+ // !!!UNDONE - fix this by flagging some entities (ie: glass) as immobile. Don't spatialize them.
+ if ( !fvalidendity)
+ {
+ // Turn off the sound while the entity doesn't exist or is not in the PVS.
+ goto ClearAllVolumes;
+ }
+#endif // 0
+
+
+ fdopplerwav = ((ch->wavtype == CHAR_DOPPLER) && !fplayersound);
+ if ( fdopplerwav )
+ {
+ VPROF_("SND_Spatialize doppler", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+ Vector vnearpoint; // point of closest approach to listener,
+ // along sound source forward direction (doppler wavs)
+
+ vnearpoint = ch->origin; // default nearest sound approach point
+
+ // calculate point of closest approach for CHAR_DOPPLER wavs, replace source_vec
+
+ fplaydopplerwav = SND_GetClosestPoint( ch, source_angles, vnearpoint );
+
+ // if doppler sound was 'shot' away from listener, don't play it
+
+ if ( !fplaydopplerwav )
+ goto ClearAllVolumes;
+
+ // find location of doppler left & doppler right points
+
+ SND_GetDopplerPoints( ch, source_angles, vnearpoint, source_doppler_left, source_doppler_right);
+
+ // source_vec_DL is vector from listener to doppler left point
+ // source_vec_DR is vector from listener to doppler right point
+
+ VectorSubtract(source_doppler_left, listener_origin, source_vec_DL );
+ VectorSubtract(source_doppler_right, listener_origin, source_vec_DR );
+
+ // normalized vectors to left and right doppler locations
+
+ dist = VectorNormalize( source_vec_DL );
+ VectorNormalize( source_vec_DR );
+
+ // don't play doppler if out of range
+ // unless recording in the tool, since we may play back in range
+ if ( dist > DOPPLER_RANGE_MAX && !toolframework->IsToolRecording() )
+ goto ClearAllVolumes;
+ }
+ else
+ {
+ // source_vec is vector from listener to sound source
+
+ if ( fplayersound )
+ {
+ // get 2d forward direction vector, ignoring pitch angle
+ Vector listener_forward2d;
+
+ ConvertListenerVectorTo2D( &listener_forward2d, &listener_right );
+
+ // player sounds originate from 1' in front of player, 2d
+
+ VectorMultiply(listener_forward2d, 12.0, source_vec );
+ }
+ else
+ {
+ VectorSubtract(ch->origin, listener_origin, source_vec);
+ }
+
+ // normalize source_vec and get distance from listener to source
+
+ dist = VectorNormalize( source_vec );
+ }
+
+ // calculate dsp mix based on distance to listener & sound level (linear approximation)
+
+ ch->dspmix = SND_GetDspMix( ch, dist );
+
+ // calculate sound source facing direction for CHAR_DIRECTIONAL wavs
+
+ if ( !fplayersound )
+ {
+ ch->dspface = SND_GetFacingDirection( ch, source_angles );
+
+ // calculate mixing parameter for CHAR_DISTVAR wavs
+
+ ch->distmix = SND_GetDistanceMix( ch, dist );
+ }
+
+ // for sounds with a radius, spatialize left/right/front/rear evenly within the radius
+
+ if ( ch->radius > 0 && dist < ch->radius && !fdopplerwav )
+ {
+ float interval = ch->radius * 0.5;
+ mono = dist - interval;
+ if ( mono < 0.0 )
+ mono = 0.0;
+ mono /= interval;
+
+ mono = 1.0 - mono;
+
+ // mono is 0.0 -> 1.0 from radius 100% to radius 50%
+ }
+
+ // don't pan sounds with no attenuation
+ if ( ch->dist_mult <= 0 && !fdopplerwav )
+ {
+ // sound is centered left/right/front/back
+
+ mono = 1.0;
+ bAttenuated = false;
+ }
+
+ if ( ch->wavtype == CHAR_OMNI )
+ {
+ // omni directional sound sources are mono mix, all speakers
+ // ie: they only attenuate by distance, not by source direction.
+
+ mono = 1.0;
+ bAttenuated = false;
+ }
+
+ // calculate gain based on distance, atmospheric attenuation, interposed objects
+ // perform compression as gain approaches 1.0
+
+ gain = SND_GetGain( ch, fplayersound, fmusicsound, looping, dist, bAttenuated );
+
+ // map gain through global mixer by soundtype
+
+ // gain *= SND_GetVolFromSoundtype( ch->soundtype );
+ int last_mixgroupid;
+
+ gain *= MXR_GetVolFromMixGroup( ch->mixgroups, &last_mixgroupid );
+
+ // if playing a word, get volume scale of word - scale gain
+
+ scale = VOX_GetChanVol(ch);
+
+ gain *= scale;
+
+ // save spatialized volume and mixgroupid for display later
+
+ ch->last_mixgroupid = last_mixgroupid;
+
+ if ( fdopplerwav )
+ {
+ VPROF_("SND_Spatialize doppler", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+ // fill out channel volumes for both doppler sound source locations
+ int volumes[CCHANVOLUMES/2];
+
+ // left doppler location
+
+ g_AudioDevice->SpatializeChannel( volumes, ch->master_vol, source_vec_DL, gain, mono );
+
+ // load volumes into channel as crossfade targets
+
+ ChannelSetVolTargets( ch, volumes, IFRONT_LEFT, CCHANVOLUMES/2 );
+
+ // right doppler location
+
+ g_AudioDevice->SpatializeChannel( volumes, ch->master_vol, source_vec_DR, gain, mono );
+
+ // load volumes into channel as crossfade targets
+
+ ChannelSetVolTargets( ch, volumes, IFRONT_LEFTD, CCHANVOLUMES/2 );
+ }
+ else
+ {
+ // fill out channel volumes for single sound source location
+ int volumes[CCHANVOLUMES/2];
+
+ g_AudioDevice->SpatializeChannel( volumes, ch->master_vol, source_vec, gain, mono );
+
+ // Special case for stereo sounds originating from player in surround mode
+ // and special case for musci: remap volumes directly to channels.
+
+ RemapPlayerOrMusicVols( ch, volumes, fplayersound, fmusicsound, mono );
+
+ // load volumes into channel as crossfade volume targets
+
+ ChannelSetVolTargets( ch, volumes, IFRONT_LEFT, CCHANVOLUMES/2 );
+ }
+
+
+ // prevent left/right/front/rear/center volumes from changing too quickly & producing pops
+
+ ChannelUpdateVolXfade( ch );
+
+ // end of first time spatializing sound
+
+ if ( SND_IsInGame() || toolframework->InToolMode() )
+ {
+ ch->flags.bfirstpass = false;
+ }
+
+ // calculate total volume for display later
+ ch->last_vol = gain * (ch->master_vol/255.0);
+
+ return;
+
+ClearAllVolumes:
+
+ // Clear all volumes and return.
+ // This shuts the sound off permanently.
+
+ ChannelClearVolumes( ch );
+
+ // end of first time spatializing sound
+
+ ch->flags.bfirstpass = false;
+}
+
+ConVar snd_defer_trace("snd_defer_trace","1");
+void SND_SpatializeFirstFrameNoTrace( channel_t *pChannel)
+{
+ if ( snd_defer_trace.GetBool() )
+ {
+ // set up tracing state to be non-obstructed
+ pChannel->flags.bfirstpass = false;
+ pChannel->flags.bTraced = true;
+ pChannel->ob_gain = 1.0;
+ pChannel->ob_gain_inc = 1.0;
+ pChannel->ob_gain_target = 1.0;
+ // now spatialize without tracing
+ SND_Spatialize(pChannel);
+ // now reset tracing state to firstpass so the trace gets done on next spatialize
+ pChannel->ob_gain = 0.0;
+ pChannel->ob_gain_inc = 0.0;
+ pChannel->ob_gain_target = 0.0;
+ pChannel->flags.bfirstpass = true;
+ pChannel->flags.bTraced = false;
+ }
+ else
+ {
+ pChannel->ob_gain = 0.0;
+ pChannel->ob_gain_inc = 0.0;
+ pChannel->ob_gain_target = 0.0;
+ pChannel->flags.bfirstpass = true;
+ pChannel->flags.bTraced = false;
+ SND_Spatialize(pChannel);
+ }
+}
+
+
+// search through all channels for a channel that matches this
+// soundsource, entchannel and sfx, and perform alteration on channel
+// as indicated by 'flags' parameter. If shut down request and
+// sfx contains a sentence name, shut off the sentence.
+// returns TRUE if sound was altered,
+// returns FALSE if sound was not found (sound is not playing)
+
+int S_AlterChannel( int soundsource, int entchannel, CSfxTable *sfx, int vol, int pitch, int flags )
+{
+ THREAD_LOCK_SOUND();
+ int ch_idx;
+
+ const char *name = sfx->getname();
+ if ( name && TestSoundChar( name, CHAR_SENTENCE ) )
+ {
+ // This is a sentence name.
+ // For sentences: assume that the entity is only playing one sentence
+ // at a time, so we can just shut off
+ // any channel that has ch->isentence >= 0 and matches the
+ // soundsource.
+
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ ch_idx = list.GetChannelIndex(i);
+ if (channels[ch_idx].soundsource == soundsource
+ && channels[ch_idx].entchannel == entchannel
+ && channels[ch_idx].sfx != NULL )
+ {
+
+ if (flags & SND_CHANGE_PITCH)
+ channels[ch_idx].basePitch = pitch;
+
+ if (flags & SND_CHANGE_VOL)
+ channels[ch_idx].master_vol = vol;
+
+ if (flags & SND_STOP)
+ {
+ S_FreeChannel(&channels[ch_idx]);
+ }
+
+ return TRUE;
+ }
+ }
+ // channel not found
+ return FALSE;
+
+ }
+
+ // regular sound or streaming sound
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+
+ bool bSuccess = false;
+
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ ch_idx = list.GetChannelIndex(i);
+ if ( channels[ch_idx].soundsource == soundsource &&
+ ( ( flags & SND_IGNORE_NAME ) ||
+ ( channels[ch_idx].entchannel == entchannel && channels[ch_idx].sfx == sfx ) ) )
+ {
+ if (flags & SND_CHANGE_PITCH)
+ channels[ch_idx].basePitch = pitch;
+
+ if (flags & SND_CHANGE_VOL)
+ channels[ch_idx].master_vol = vol;
+
+ if (flags & SND_STOP)
+ {
+ S_FreeChannel(&channels[ch_idx]);
+ }
+
+ if ( ( flags & SND_IGNORE_NAME ) == 0 )
+ return TRUE;
+ else
+ bSuccess = true;
+ }
+ }
+
+ return ( bSuccess ) ? ( TRUE ) : ( FALSE );
+}
+
+// set channel flags during initialization based on
+// source name
+
+void S_SetChannelWavtype( channel_t *target_chan, CSfxTable *pSfx )
+{
+ // if 1st or 2nd character of name is CHAR_DRYMIX, sound should be mixed dry with no dsp (ie: music)
+
+ if ( TestSoundChar(pSfx->getname(), CHAR_DRYMIX) )
+ target_chan->flags.bdry = true;
+ else
+ target_chan->flags.bdry = false;
+
+ if ( TestSoundChar(pSfx->getname(), CHAR_FAST_PITCH) )
+ target_chan->flags.bfast_pitch = true;
+ else
+ target_chan->flags.bfast_pitch = false;
+
+ // get sound spatialization encoding
+
+ target_chan->wavtype = 0;
+
+ if ( TestSoundChar( pSfx->getname(), CHAR_DOPPLER ))
+ target_chan->wavtype = CHAR_DOPPLER;
+
+ if ( TestSoundChar( pSfx->getname(), CHAR_DIRECTIONAL ))
+ target_chan->wavtype = CHAR_DIRECTIONAL;
+
+ if ( TestSoundChar( pSfx->getname(), CHAR_DISTVARIANT ))
+ target_chan->wavtype = CHAR_DISTVARIANT;
+
+ if ( TestSoundChar( pSfx->getname(), CHAR_OMNI ))
+ target_chan->wavtype = CHAR_OMNI;
+
+ if ( TestSoundChar( pSfx->getname(), CHAR_SPATIALSTEREO ))
+ target_chan->wavtype = CHAR_SPATIALSTEREO;
+}
+
+
+// Sets bstereowav flag in channel if source is true stere wav
+// sets default wavtype for stereo wavs to CHAR_DISTVARIANT -
+// ie: sound varies with distance (left is close, right is far)
+// Must be called after S_SetChannelWavtype
+
+void S_SetChannelStereo( channel_t *target_chan, CAudioSource *pSource )
+{
+ if ( !pSource )
+ {
+ target_chan->flags.bstereowav = false;
+ return;
+ }
+
+ // returns true only if source data is a stereo wav file.
+ // ie: mp3, voice, sentence are all excluded.
+
+ target_chan->flags.bstereowav = pSource->IsStereoWav();
+
+ // Default stereo wavtype:
+
+ // just player standard stereo wavs on player entity - no override.
+
+ if ( IsSoundSourceLocalPlayer( target_chan->soundsource ) )
+ return;
+
+ // default wavtype for stereo wavs is OMNI - except for drymix or sounds with 0 attenuation
+
+ if ( target_chan->flags.bstereowav && !target_chan->wavtype && !target_chan->flags.bdry && target_chan->dist_mult )
+ // target_chan->wavtype = CHAR_DISTVARIANT;
+ target_chan->wavtype = CHAR_OMNI;
+}
+
+// =======================================================================
+// Channel volume management routines:
+
+// channel volumes crossfade between values over time
+// to prevent pops due to rapid spatialization changes
+// =======================================================================
+
+// return true if all volumes and target volumes for channel are less/equal to 'vol'
+
+bool BChannelLowVolume( channel_t *pch, int vol_min )
+{
+ int max = -1;
+ int max_target = -1;
+ int vol;
+ int vol_target;
+
+ for (int i = 0; i < CCHANVOLUMES; i++)
+ {
+ vol = (int)(pch->fvolume[i]);
+ vol_target = (int)(pch->fvolume_target[i]);
+
+ if (vol > max)
+ max = vol;
+
+ if (vol_target > max_target)
+ max_target = vol_target;
+ }
+
+ return (max <= vol_min && max_target <= vol_min);
+}
+
+// Get the loudest actual volume for a channel (not counting targets).
+float ChannelLoudestCurVolume( const channel_t * RESTRICT pch )
+{
+ float loudest = pch->fvolume[0];
+ for (int i = 1; i < CCHANVOLUMES; i++)
+ {
+ loudest = fpmax(loudest, pch->fvolume[i]);
+ }
+ return loudest;
+}
+
+// clear all volumes, targets, crossfade increments
+
+void ChannelClearVolumes( channel_t *pch )
+{
+ for (int i = 0; i < CCHANVOLUMES; i++)
+ {
+ pch->fvolume[i] = 0.0;
+ pch->fvolume_target[i] = 0.0;
+ pch->fvolume_inc[i] = 0.0;
+ }
+}
+
+// return current volume as integer
+
+int ChannelGetVol( channel_t *pch, int ivol )
+{
+ Assert(ivol < CCHANVOLUMES);
+ return (int)(pch->fvolume[ivol]);
+}
+
+// return maximum current output volume
+
+int ChannelGetMaxVol( channel_t *pch )
+{
+ float max = 0.0;
+
+ for (int i = 0; i < CCHANVOLUMES; i++)
+ {
+ if (pch->fvolume[i] > max)
+ max = pch->fvolume[i];
+ }
+
+ return (int)max;
+}
+
+// set current volume (clears crossfading - instantaneous value change)
+
+void ChannelSetVol( channel_t *pch, int ivol, int vol )
+{
+ Assert(ivol < CCHANVOLUMES);
+
+ pch->fvolume[ivol] = (float)(clamp(vol, 0, 255));
+
+ pch->fvolume_target[ivol] = pch->fvolume[ivol];
+ pch->fvolume_inc[ivol] = 0.0;
+}
+
+// copy current channel volumes into target array, starting at ivol, copying cvol entries
+
+void ChannelCopyVolumes( channel_t *pch, int *pvolume_dest, int ivol_start, int cvol )
+{
+ Assert (ivol_start < CCHANVOLUMES);
+ Assert (ivol_start + cvol <= CCHANVOLUMES);
+
+ for (int i = 0; i < cvol; i++)
+ pvolume_dest[i] = (int)(pch->fvolume[i + ivol_start]);
+}
+
+// volume has hit target, shut off crossfading increment
+
+inline void ChannelStopVolXfade( channel_t *pch, int ivol )
+{
+ pch->fvolume[ivol] = pch->fvolume_target[ivol];
+ pch->fvolume_inc[ivol] = 0.0;
+}
+
+#define VOL_XFADE_TIME 0.070 // channel volume crossfade time in seconds
+
+#define VOL_INCR_MAX 20.0 // never change volume by more than +/-N units per frame
+
+// set volume target and volume increment (for crossfade) for channel & speaker
+
+void ChannelSetVolTarget( channel_t *pch, int ivol, int volume_target )
+{
+ float frametime = g_pSoundServices->GetHostFrametime();
+ float speed;
+ float vol_target = (float)(clamp(volume_target, 0, 255));
+ float vol_current;
+
+ Assert(ivol < CCHANVOLUMES);
+
+ // set volume target
+
+ pch->fvolume_target[ivol] = vol_target;
+
+ // current volume
+
+ vol_current = pch->fvolume[ivol];
+
+ // if first time spatializing, set target = volume with no crossfade
+ // if current & target volumes are close - don't bother crossfading
+
+ if ( pch->flags.bfirstpass || (fabs(vol_target - vol_current) < 5.0))
+ {
+ // set current volume = target, no increment
+
+ ChannelStopVolXfade( pch, ivol);
+ return;
+ }
+
+ // get crossfade increment 'speed' (volume change per frame)
+
+ speed = ( frametime / VOL_XFADE_TIME ) * (vol_target - vol_current);
+
+ // make sure we never increment by more than +/- VOL_INCR_MAX volume units per frame
+
+ speed = clamp(speed, (float) -VOL_INCR_MAX, (float) VOL_INCR_MAX);
+
+ pch->fvolume_inc[ivol] = speed;
+}
+
+// set volume targets, using array pvolume as source volumes.
+// set into channel volumes starting at ivol_offset index
+// set cvol volumes
+
+void ChannelSetVolTargets( channel_t *pch, int *pvolumes, int ivol_offset, int cvol )
+{
+ int volume_target;
+
+ Assert(ivol_offset + cvol <= CCHANVOLUMES);
+
+ for (int i = 0; i < cvol; i++)
+ {
+ volume_target = pvolumes[i];
+
+ ChannelSetVolTarget( pch, ivol_offset + i, volume_target );
+ }
+}
+
+
+// Call once per frame, per channel:
+// update all volume crossfades, from fvolume -> fvolume_target
+// if current volume reaches target, set increment to 0
+
+void ChannelUpdateVolXfade( channel_t *pch )
+{
+ float fincr;
+
+ for (int i = 0; i < CCHANVOLUMES; i++)
+ {
+ fincr = pch->fvolume_inc[i];
+
+ if (fincr != 0.0)
+ {
+ pch->fvolume[i] += fincr;
+
+ // test for hit target
+
+ if (fincr > 0.0)
+ {
+ if (pch->fvolume[i] >= pch->fvolume_target[i])
+ ChannelStopVolXfade( pch, i );
+ }
+ else
+ {
+ if (pch->fvolume[i] <= pch->fvolume_target[i])
+ ChannelStopVolXfade( pch, i );
+ }
+ }
+ }
+}
+
+// =======================================================================
+// S_StartDynamicSound
+// =======================================================================
+// Start a sound effect for the given entity on the given channel (ie; voice, weapon etc).
+// Try to grab a channel out of the 8 dynamic spots available.
+// Currently used for looping sounds, streaming sounds, sentences, and regular entity sounds.
+// NOTE: volume is 0.0 - 1.0 and attenuation is 0.0 - 1.0 when passed in.
+// Pitch changes playback pitch of wave by % above or below 100. Ignored if pitch == 100
+
+// NOTE: it's not a good idea to play looping sounds through StartDynamicSound, because
+// if the looping sound starts out of range, or is bumped from the buffer by another sound
+// it will never be restarted. Use StartStaticSound (pass CHAN_STATIC to EMIT_SOUND or
+// SV_StartSound.
+
+int S_StartDynamicSound( StartSoundParams_t& params )
+{
+ Assert( params.staticsound == false );
+
+ channel_t *target_chan;
+ int vol;
+
+ if ( !g_AudioDevice || !g_AudioDevice->IsActive())
+ return 0;
+
+ if (!params.pSfx)
+ return 0;
+
+ // For debugging to see the actual name of the sound...
+ char sndname[ MAX_OSPATH ];
+ Q_strncpy( sndname, params.pSfx->getname(), sizeof( sndname ) );
+
+ // Msg("Start sound %s\n", pSfx->getname() );
+
+ // override the entchannel to CHAN_STREAM if this is a
+ // non-voice stream sound.
+ if ( TestSoundChar(sndname, CHAR_STREAM ) && params.entchannel != CHAN_VOICE && params.entchannel != CHAN_VOICE2 )
+ params.entchannel = CHAN_STREAM;
+
+ vol = params.fvol*255;
+
+ if (vol > 255)
+ {
+ DevMsg("S_StartDynamicSound: %s volume > 255", sndname );
+ vol = 255;
+ }
+
+ THREAD_LOCK_SOUND();
+
+ if ( params.flags & (SND_STOP|SND_CHANGE_VOL|SND_CHANGE_PITCH) )
+ {
+ if ( S_AlterChannel( params.soundsource, params.entchannel, params.pSfx, vol, params.pitch, params.flags) )
+ return 0;
+ if ( params.flags & SND_STOP )
+ return 0;
+ // fall through - if we're not trying to stop the sound,
+ // and we didn't find it (it's not playing), go ahead and start it up
+ }
+
+ if (params.pitch == 0)
+ {
+ DevMsg ("Warning: S_StartDynamicSound (%s) Ignored, called with pitch 0\n", sndname );
+ return 0;
+ }
+
+ // pick a channel to play on
+ target_chan = SND_PickDynamicChannel(params.soundsource, params.entchannel, params.origin, params.pSfx, params.delay, (params.flags & SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL) != 0 );
+ if ( !target_chan )
+ return 0;
+
+ int channelIndex = (int)( target_chan - channels );
+ g_AudioDevice->ChannelReset( params.soundsource, channelIndex, target_chan->dist_mult );
+
+#ifdef DEBUG_CHANNELS
+ {
+ char szTmp[128];
+ Q_snprintf(szTmp, sizeof( szTmp ), "Sound %s playing on Dynamic game channel %d\n", sndname, IWavstreamOfCh(target_chan));
+ Plat_DebugString(szTmp);
+ }
+#endif
+
+ bool bIsSentence = TestSoundChar( sndname, CHAR_SENTENCE );
+
+ SND_ActivateChannel( target_chan );
+ ChannelClearVolumes( target_chan );
+
+ target_chan->userdata = params.userdata;
+ target_chan->initialStreamPosition = params.initialStreamPosition;
+
+ VectorCopy(params.origin, target_chan->origin);
+ VectorCopy(params.direction, target_chan->direction);
+
+ // never update positions if source entity is 0
+ target_chan->flags.bUpdatePositions = params.bUpdatePositions && (params.soundsource == 0 ? 0 : 1);
+
+ // reference_dist / (reference_power_level / actual_power_level)
+ target_chan->flags.m_bCompatibilityAttenuation = SNDLEVEL_IS_COMPATIBILITY_MODE( params.soundlevel );
+ if ( target_chan->flags.m_bCompatibilityAttenuation )
+ {
+ // Translate soundlevel from its 'encoded' value to a real soundlevel that we can use in the sound system.
+ params.soundlevel = SNDLEVEL_FROM_COMPATIBILITY_MODE( params.soundlevel );
+ }
+
+ target_chan->dist_mult = SNDLVL_TO_DIST_MULT( params.soundlevel );
+
+ S_SetChannelWavtype( target_chan, params.pSfx );
+
+ target_chan->master_vol = vol;
+ target_chan->soundsource = params.soundsource;
+ target_chan->entchannel = params.entchannel;
+ target_chan->basePitch = params.pitch;
+ target_chan->flags.isSentence = false;
+ target_chan->radius = 0;
+ target_chan->sfx = params.pSfx;
+ target_chan->special_dsp = params.specialdsp;
+ target_chan->flags.fromserver = params.fromserver;
+ target_chan->flags.bSpeaker = (params.flags & SND_SPEAKER) ? 1 : 0;
+ target_chan->speakerentity = params.speakerentity;
+
+ target_chan->flags.m_bShouldPause = (params.flags & SND_SHOULDPAUSE) ? 1 : 0;
+
+ // initialize dsp room mixing params
+ target_chan->dsp_mix_min = -1;
+ target_chan->dsp_mix_max = -1;
+
+ CAudioSource *pSource = NULL;
+
+ if ( bIsSentence )
+ {
+ // this is a sentence
+ // link all words and load the first word
+
+ // NOTE: sentence names stored in the cache lookup are
+ // prepended with a '!'. Sentence names stored in the
+ // sentence file do not have a leading '!'.
+ VOX_LoadSound( target_chan, PSkipSoundChars( sndname ) );
+ }
+ else
+ {
+ // regular or streamed sound fx
+ pSource = S_LoadSound( params.pSfx, target_chan );
+ if ( pSource && !IsValidSampleRate( pSource->SampleRate() ) )
+ {
+ Warning( "*** Invalid sample rate (%d) for sound '%s'.\n", pSource->SampleRate(), sndname );
+ }
+
+ if ( !pSource && !params.pSfx->m_bIsLateLoad )
+ {
+ Warning( "Failed to load sound \"%s\", file probably missing from disk/repository\n", sndname );
+ }
+
+ }
+
+ if (!target_chan->pMixer)
+ {
+ // couldn't load the sound's data, or sentence has 0 words (this is not an error)
+ S_FreeChannel( target_chan );
+ return 0;
+ }
+
+ int nSndShowStart = snd_showstart.GetInt();
+
+ // TODO: Support looping sounds through speakers.
+ // If the sound is from a speaker, and it's looping, ignore it.
+ if ( target_chan->flags.bSpeaker )
+ {
+ if ( params.pSfx->pSource && params.pSfx->pSource->IsLooped() )
+ {
+ if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
+ {
+ DevMsg("DynamicSound : Speaker ignored looping sound: %s\n", sndname );
+ }
+
+ S_FreeChannel( target_chan );
+ return 0;
+ }
+ }
+
+ S_SetChannelStereo( target_chan, pSource );
+
+ if (nSndShowStart == 5)
+ {
+ snd_showstart.SetValue(6); // debug: show gain for next spatialize only
+ nSndShowStart = 6;
+ }
+
+ // get sound type before we spatialize
+ MXR_GetMixGroupFromSoundsource( target_chan, params.soundsource, params.soundlevel );
+
+ // skip the trace on the first spatialization. This channel may be stolen
+ // by another sound played this frame. Defer the trace to the mix loop
+ SND_SpatializeFirstFrameNoTrace(target_chan);
+
+ if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
+ {
+ channel_t *pTargetChan = target_chan;
+
+ DevMsg( "DynamicSound %s : src %d : channel %d : %d dB : vol %.2f : time %.3f\n", sndname, params.soundsource, params.entchannel, params.soundlevel, params.fvol, g_pSoundServices->GetHostTime() );
+ if (nSndShowStart == 2 || nSndShowStart == 5)
+ DevMsg( "\t dspmix %1.2f : distmix %1.2f : dspface %1.2f : lvol %1.2f : cvol %1.2f : rvol %1.2f : rlvol %1.2f : rrvol %1.2f\n",
+ pTargetChan->dspmix, pTargetChan->distmix, pTargetChan->dspface,
+ pTargetChan->fvolume[IFRONT_LEFT], pTargetChan->fvolume[IFRONT_CENTER], pTargetChan->fvolume[IFRONT_RIGHT], pTargetChan->fvolume[IREAR_LEFT], pTargetChan->fvolume[IREAR_RIGHT] );
+ if (nSndShowStart == 3)
+ DevMsg( "\t x: %4f y: %4f z: %4f\n", pTargetChan->origin.x, pTargetChan->origin.y, pTargetChan->origin.z );
+
+ if ( snd_visualize.GetInt() )
+ {
+ CDebugOverlay::AddTextOverlay( pTargetChan->origin, 2.0f, sndname );
+ }
+ }
+
+ // If a client can't hear a sound when they FIRST receive the StartSound message,
+ // the client will never be able to hear that sound. This is so that out of
+ // range sounds don't fill the playback buffer. For streaming sounds, we bypass this optimization.
+
+ if ( BChannelLowVolume( target_chan, 0 ) && !toolframework->IsToolRecording() )
+ {
+ // Looping sounds don't use this optimization because they should stick around until they're killed.
+ // Also bypass for speech (GetSentence)
+ if ( !params.pSfx->pSource || (!params.pSfx->pSource->IsLooped() && !params.pSfx->pSource->GetSentence()) )
+ {
+ // if this is long sound, play the whole thing.
+ if (!SND_IsLongWave( target_chan ))
+ {
+ // DevMsg("S_StartDynamicSound: spatialized to 0 vol & ignored %s", sndname);
+ S_FreeChannel( target_chan );
+ return 0; // not audible at all
+ }
+ }
+ }
+
+ // Init client entity mouth movement vars
+ target_chan->flags.m_bIgnorePhonemes = ( params.flags & SND_IGNORE_PHONEMES ) != 0;
+ SND_InitMouth(target_chan);
+
+ if ( IsX360() && params.delay < 0 )
+ {
+ params.delay = 0;
+ target_chan->flags.delayed_start = true;
+ }
+
+ // Pre-startup delay. Compute # of samples over which to mix in zeros from data source before
+ // actually reading first set of samples
+ if ( params.delay != 0.0f )
+ {
+ Assert( target_chan->sfx );
+ Assert( target_chan->sfx->pSource );
+
+ // delay count is computed at the sampling rate of the source because the output rate will
+ // match the source rate when the sound is mixed
+ float rate = target_chan->sfx->pSource->SampleRate();
+ int delaySamples = (int)( params.delay * rate );
+
+ if ( params.delay > 0 )
+ {
+ target_chan->pMixer->SetStartupDelaySamples( delaySamples );
+ target_chan->flags.delayed_start = true;
+ }
+ else
+ {
+ int skipSamples = -delaySamples;
+ int totalSamples = target_chan->sfx->pSource->SampleCount();
+ if ( target_chan->sfx->pSource->IsLooped() )
+ {
+ skipSamples = skipSamples % totalSamples;
+ }
+ if ( skipSamples >= totalSamples )
+ {
+ S_FreeChannel( target_chan );
+ return 0;
+ }
+ target_chan->pitch = target_chan->basePitch * 0.01f;
+ target_chan->pMixer->SkipSamples( target_chan, skipSamples, rate, 0 );
+ target_chan->ob_gain_target = 1.0f;
+ target_chan->ob_gain = 1.0f;
+ target_chan->ob_gain_inc = 0.0;
+ target_chan->flags.bfirstpass = false;
+ target_chan->flags.delayed_start = true;
+ }
+ }
+
+ g_pSoundServices->OnSoundStarted( target_chan->guid, params, sndname );
+ return target_chan->guid;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *name -
+// Output : CSfxTable
+//-----------------------------------------------------------------------------
+CSfxTable *S_DummySfx( const char *name )
+{
+ dummySfx.setname( name );
+ return &dummySfx;
+}
+
+/*
+=================
+S_StartStaticSound
+=================
+Start playback of a sound, loaded into the static portion of the channel array.
+Currently, this should be used for looping ambient sounds, looping sounds
+that should not be interrupted until complete, non-creature sentences,
+and one-shot ambient streaming sounds. Can also play 'regular' sounds one-shot,
+in case designers want to trigger regular game sounds.
+Pitch changes playback pitch of wave by % above or below 100. Ignored if pitch == 100
+
+ NOTE: volume is 0.0 - 1.0 and attenuation is 0.0 - 1.0 when passed in.
+*/
+
+int S_StartStaticSound( StartSoundParams_t& params )
+{
+ Assert( params.staticsound == true );
+
+ channel_t *ch;
+ CAudioSource *pSource = NULL;
+
+ if ( !g_AudioDevice->IsActive() )
+ return 0;
+
+ if ( !params.pSfx )
+ return 0;
+
+ // For debugging to see the actual name of the sound...
+ char sndname[ MAX_OSPATH ];
+ Q_strncpy( sndname, params.pSfx->getname(), sizeof( sndname ) );
+// Msg("Start static sound %s\n", pSfx->getname() );
+
+ int vol = params.fvol * 255;
+ if ( vol > 255 )
+ {
+ DevMsg( "S_StartStaticSound: %s volume > 255", sndname );
+ vol = 255;
+ }
+
+ int nSndShowStart = snd_showstart.GetInt();
+
+ if ((params.flags & SND_STOP) && nSndShowStart > 0)
+ DevMsg("S_StartStaticSound: %s Stopped.\n", sndname);
+
+ if ((params.flags & SND_STOP) || (params.flags & SND_CHANGE_VOL) || (params.flags & SND_CHANGE_PITCH))
+ {
+ if (S_AlterChannel(params.soundsource, params.entchannel, params.pSfx, vol, params.pitch, params.flags) || (params.flags & SND_STOP))
+ return 0;
+ }
+
+ if ( params.pitch == 0 )
+ {
+ DevMsg( "Warning: S_StartStaticSound Ignored, called with pitch 0\n");
+ return 0;
+ }
+
+ // First, make sure the sound source entity is even in the PVS.
+ float flSoundRadius = 0.0f;
+
+ bool looping = false;
+
+ /*
+ CAudioSource *pSource = pSfx ? pSfx->pSource : NULL;
+ if ( pSource )
+ {
+ looping = pSource->IsLooped();
+ }
+ */
+
+ SpatializationInfo_t si;
+ si.info.Set(
+ params.soundsource,
+ params.entchannel,
+ params.pSfx ? sndname : "",
+ params.origin,
+ params.direction,
+ vol,
+ params.soundlevel,
+ looping,
+ params.pitch,
+ listener_origin,
+ params.speakerentity );
+
+ si.type = SpatializationInfo_t::SI_INCREATION;
+
+ si.pOrigin = NULL;
+ si.pAngles = NULL;
+ si.pflRadius = &flSoundRadius;
+
+ g_pSoundServices->GetSoundSpatialization( params.soundsource, si );
+
+ // pick a channel to play on from the static area
+ THREAD_LOCK_SOUND();
+
+ ch = SND_PickStaticChannel(params.soundsource, params.pSfx); // Autolooping sounds are always fixed origin(?)
+ if ( !ch )
+ return 0;
+
+ SND_ActivateChannel( ch );
+ ChannelClearVolumes( ch );
+
+ ch->userdata = params.userdata;
+ ch->initialStreamPosition = params.initialStreamPosition;
+
+ if ( ch->userdata != 0 )
+ {
+ g_pSoundServices->GetToolSpatialization( ch->userdata, ch->guid, si );
+ }
+
+ int channelIndex = ch - channels;
+ g_AudioDevice->ChannelReset( params.soundsource, channelIndex, ch->dist_mult );
+
+#ifdef DEBUG_CHANNELS
+ {
+ char szTmp[128];
+ Q_snprintf(szTmp, sizeof( szTmp ), "Sound %s playing on Static game channel %d\n", sfxin->name, IWavstreamOfCh(ch));
+ Plat_DebugString(szTmp);
+ }
+#endif
+
+ if ( TestSoundChar(sndname, CHAR_SENTENCE) )
+ {
+ // this is a sentence. link words to play in sequence.
+
+ // NOTE: sentence names stored in the cache lookup are
+ // prepended with a '!'. Sentence names stored in the
+ // sentence file do not have a leading '!'.
+
+ // link all words and load the first word
+ VOX_LoadSound( ch, PSkipSoundChars(sndname) );
+ }
+ else
+ {
+ // load regular or stream sound
+ pSource = S_LoadSound( params.pSfx, ch );
+ if ( pSource && !IsValidSampleRate( pSource->SampleRate() ) )
+ {
+ Warning( "*** Invalid sample rate (%d) for sound '%s'.\n", pSource->SampleRate(), sndname );
+ }
+
+ if ( !pSource && !params.pSfx->m_bIsLateLoad )
+ {
+ Warning( "Failed to load sound \"%s\", file probably missing from disk/repository\n", sndname );
+ }
+
+ ch->sfx = params.pSfx;
+ ch->flags.isSentence = false;
+ }
+
+ if ( !ch->pMixer )
+ {
+ // couldn't load sounds' data, or sentence has 0 words (not an error)
+ S_FreeChannel( ch );
+ return 0;
+ }
+
+ VectorCopy (params.origin, ch->origin);
+ VectorCopy (params.direction, ch->direction);
+
+ // never update positions if source entity is 0
+ ch->flags.bUpdatePositions = params.bUpdatePositions && (params.soundsource == 0 ? 0 : 1);
+
+ ch->master_vol = vol;
+
+ ch->flags.m_bCompatibilityAttenuation = SNDLEVEL_IS_COMPATIBILITY_MODE( params.soundlevel );
+ if ( ch->flags.m_bCompatibilityAttenuation )
+ {
+ // Translate soundlevel from its 'encoded' value to a real soundlevel that we can use in the sound system.
+ params.soundlevel = SNDLEVEL_FROM_COMPATIBILITY_MODE( params.soundlevel );
+ }
+
+ ch->dist_mult = SNDLVL_TO_DIST_MULT( params.soundlevel );
+
+ S_SetChannelWavtype( ch, params.pSfx );
+
+ ch->basePitch = params.pitch;
+ ch->soundsource = params.soundsource;
+ ch->entchannel = params.entchannel;
+ ch->special_dsp = params.specialdsp;
+ ch->flags.fromserver = params.fromserver;
+ ch->flags.bSpeaker = (params.flags & SND_SPEAKER) ? 1 : 0;
+ ch->speakerentity = params.speakerentity;
+
+ ch->flags.m_bShouldPause = (params.flags & SND_SHOULDPAUSE) ? 1 : 0;
+
+ // TODO: Support looping sounds through speakers.
+ // If the sound is from a speaker, and it's looping, ignore it.
+ if ( ch->flags.bSpeaker )
+ {
+ if ( params.pSfx->pSource && params.pSfx->pSource->IsLooped() )
+ {
+ if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
+ {
+ DevMsg("StaticSound : Speaker ignored looping sound: %s\n", sndname);
+ }
+
+ S_FreeChannel( ch );
+ return 0;
+ }
+ }
+
+ // set the default radius
+ ch->radius = flSoundRadius;
+
+ S_SetChannelStereo( ch, pSource );
+
+ // initialize dsp room mixing params
+ ch->dsp_mix_min = -1;
+ ch->dsp_mix_max = -1;
+
+ if (nSndShowStart == 5)
+ {
+ snd_showstart.SetValue(6); // display gain once only
+ nSndShowStart = 6;
+ }
+
+ // get sound type before we spatialize
+
+ MXR_GetMixGroupFromSoundsource( ch, params.soundsource, params.soundlevel );
+
+ // skip the trace on the first spatialization. This channel may be stolen
+ // by another sound played this frame. Defer the trace to the mix loop
+ SND_SpatializeFirstFrameNoTrace(ch);
+
+ // Init client entity mouth movement vars
+ ch->flags.m_bIgnorePhonemes = ( params.flags & SND_IGNORE_PHONEMES ) != 0;
+ SND_InitMouth( ch );
+
+ if ( IsX360() && params.delay < 0 )
+ {
+ // X360TEMP: Can't support yet, but going to.
+ params.delay = 0;
+ }
+
+ // Pre-startup delay. Compute # of samples over which to mix in zeros from data source before
+ // actually reading first set of samples
+ if ( params.delay != 0.0f )
+ {
+ Assert( ch->sfx );
+ Assert( ch->sfx->pSource );
+
+ float rate = ch->sfx->pSource->SampleRate();
+
+ int delaySamples = (int)( params.delay * rate * params.pitch * 0.01f );
+
+ ch->pMixer->SetStartupDelaySamples( delaySamples );
+
+ if ( params.delay > 0 )
+ {
+ ch->pMixer->SetStartupDelaySamples( delaySamples );
+ ch->flags.delayed_start = true;
+ }
+ else
+ {
+ int skipSamples = -delaySamples;
+ int totalSamples = ch->sfx->pSource->SampleCount();
+
+ if ( ch->sfx->pSource->IsLooped() )
+ {
+ skipSamples = skipSamples % totalSamples;
+ }
+
+ if ( skipSamples >= totalSamples )
+ {
+ S_FreeChannel( ch );
+ return 0;
+ }
+
+ ch->pitch = ch->basePitch * 0.01f;
+ ch->pMixer->SkipSamples( ch, skipSamples, rate, 0 );
+ ch->ob_gain_target = 1.0f;
+ ch->ob_gain = 1.0f;
+ ch->ob_gain_inc = 0.0f;
+ ch->flags.bfirstpass = false;
+ }
+ }
+
+ if ( S_IsMusic( ch ) )
+ {
+ // See if we have "music" of same name playing from "world" which means we save/restored this sound already. If so,
+ // kill the new version and update the soundsource
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = list.GetChannel(i);
+ // Don't mess with the channel we just created, of course
+ if ( ch == pChannel )
+ continue;
+ if ( ch->sfx != pChannel->sfx )
+ continue;
+ if ( pChannel->soundsource != SOUND_FROM_WORLD )
+ continue;
+ if ( !S_IsMusic( pChannel ) )
+ continue;
+
+ DevMsg( 1, "Hooking duplicate restored song track %s\n", sndname );
+
+ // the new channel will have an updated soundsource and probably
+ // has an updated pitch or volume since we are receiving this sound message
+ // after the sound has started playing (usually a volume change)
+ // copy that data out of the source
+ pChannel->soundsource = ch->soundsource;
+ pChannel->master_vol = ch->master_vol;
+ pChannel->basePitch = ch->basePitch;
+ pChannel->pitch = ch->pitch;
+ S_FreeChannel( ch );
+
+ return 0;
+ }
+ }
+
+ g_pSoundServices->OnSoundStarted( ch->guid, params, sndname );
+
+ if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
+ {
+ DevMsg( "StaticSound %s : src %d : channel %d : %d dB : vol %.2f : radius %.0f : time %.3f\n", sndname, params.soundsource, params.entchannel, params.soundlevel, params.fvol, flSoundRadius, g_pSoundServices->GetHostTime() );
+ if (nSndShowStart == 2 || nSndShowStart == 5)
+ DevMsg( "\t dspmix %1.2f : distmix %1.2f : dspface %1.2f : lvol %1.2f : cvol %1.2f : rvol %1.2f : rlvol %1.2f : rrvol %1.2f\n",
+ ch->dspmix, ch->distmix, ch->dspface,
+ ch->fvolume[IFRONT_LEFT], ch->fvolume[IFRONT_CENTER], ch->fvolume[IFRONT_RIGHT], ch->fvolume[IREAR_LEFT], ch->fvolume[IREAR_RIGHT] );
+ if (nSndShowStart == 3)
+ DevMsg( "\t x: %4f y: %4f z: %4f\n", ch->origin.x, ch->origin.y, ch->origin.z );
+ }
+
+ return ch->guid;
+}
+
+#ifdef STAGING_ONLY
+static ConVar snd_filter( "snd_filter", "", FCVAR_CHEAT );
+#endif // STAGING_ONLY
+
+int S_StartSound( StartSoundParams_t& params )
+{
+
+ if( ! params.pSfx )
+ {
+ return 0;
+ }
+
+#ifdef STAGING_ONLY
+ if ( snd_filter.GetString()[ 0 ] && !Q_stristr( params.pSfx->getname(), snd_filter.GetString() ) )
+ {
+ return 0;
+ }
+#endif // STAGING_ONLY
+
+ if ( IsX360() && params.delay < 0 && !params.initialStreamPosition && params.pSfx )
+ {
+ // calculate an initial stream position from the expected sample position
+ float rate = params.pSfx->pSource->SampleRate();
+ int samplePosition = (int)( -params.delay * rate * params.pitch * 0.01f );
+ params.initialStreamPosition = params.pSfx->pSource->SampleToStreamPosition( samplePosition );
+ }
+
+ if ( params.staticsound )
+ {
+ VPROF_( "StartStaticSound", 0, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+ return S_StartStaticSound( params );
+ }
+ else
+ {
+ VPROF_( "StartDynamicSound", 0, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+ return S_StartDynamicSound( params );
+ }
+}
+
+// Restart all the sounds on the specified channel
+inline bool IsChannelLooped( int iChannel )
+{
+ return (channels[iChannel].sfx &&
+ channels[iChannel].sfx->pSource &&
+ channels[iChannel].sfx->pSource->IsLooped() );
+}
+
+int S_GetCurrentStaticSounds( SoundInfo_t *pResult, int nSizeResult, int entchannel )
+{
+ int nSpaceRemaining = nSizeResult;
+ for (int i = MAX_DYNAMIC_CHANNELS; i < total_channels && nSpaceRemaining; i++)
+ {
+ if ( channels[i].entchannel == entchannel && channels[i].sfx )
+ {
+ pResult->Set( channels[i].soundsource,
+ channels[i].entchannel,
+ channels[i].sfx->getname(),
+ channels[i].origin,
+ channels[i].direction,
+ ( (float)channels[i].master_vol / 255.0 ),
+ DIST_MULT_TO_SNDLVL( channels[i].dist_mult ),
+ IsChannelLooped( i ),
+ channels[i].basePitch,
+ listener_origin,
+ channels[i].speakerentity );
+ pResult++;
+ nSpaceRemaining--;
+ }
+ }
+ return (nSizeResult - nSpaceRemaining);
+}
+
+
+// Stop all sounds for entity on a channel.
+void S_StopSound(int soundsource, int entchannel)
+{
+ THREAD_LOCK_SOUND();
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = list.GetChannel(i);
+ if (pChannel->soundsource == soundsource
+ && pChannel->entchannel == entchannel)
+ {
+ S_FreeChannel( pChannel );
+ }
+ }
+}
+
+channel_t *S_FindChannelByGuid( int guid )
+{
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = list.GetChannel(i);
+ if ( pChannel->guid == guid )
+ {
+ return pChannel;
+ }
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : guid -
+//-----------------------------------------------------------------------------
+void S_StopSoundByGuid( int guid )
+{
+ THREAD_LOCK_SOUND();
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ if ( pChannel )
+ {
+ S_FreeChannel( pChannel );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : guid -
+//-----------------------------------------------------------------------------
+float S_SoundDurationByGuid( int guid )
+{
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ if ( !pChannel || !pChannel->sfx )
+ return 0.0f;
+
+ // NOTE: Looping sounds will return the length of a single loop
+ // Use S_IsLoopingSoundByGuid to see if they are looped
+ float flRate = pChannel->sfx->pSource->SampleRate() * pChannel->basePitch * 0.01f;
+ int nTotalSamples = pChannel->sfx->pSource->SampleCount();
+ return (flRate != 0.0f) ? nTotalSamples / flRate : 0.0f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Is this sound a looping sound?
+//-----------------------------------------------------------------------------
+bool S_IsLoopingSoundByGuid( int guid )
+{
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ if ( !pChannel || !pChannel->sfx )
+ return false;
+
+ return( pChannel->sfx->pSource->IsLooped() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Note that the guid is preincremented, so we can just return the current value as the "last sound" indicator
+// Input : -
+// Output : int
+//-----------------------------------------------------------------------------
+int S_GetGuidForLastSoundEmitted()
+{
+ return s_nSoundGuid;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : guid -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool S_IsSoundStillPlaying( int guid )
+{
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ return pChannel != NULL ? true : false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : guid -
+// fvol -
+//-----------------------------------------------------------------------------
+void S_SetVolumeByGuid( int guid, float fvol )
+{
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ pChannel->master_vol = 255.0f * clamp( fvol, 0.0f, 1.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : guid -
+// Output : float
+//-----------------------------------------------------------------------------
+float S_GetElapsedTimeByGuid( int guid )
+{
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ if ( !pChannel )
+ return 0.0f;
+
+ CAudioMixer *mixer = pChannel->pMixer;
+ if ( !mixer )
+ return 0.0f;
+
+ float elapsed = mixer->GetSamplePosition() / ( mixer->GetSource()->SampleRate() * pChannel->pitch * 0.01f );
+ return elapsed;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : sndlist -
+//-----------------------------------------------------------------------------
+void S_GetActiveSounds( CUtlVector< SndInfo_t >& sndlist )
+{
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *ch = list.GetChannel(i);
+
+ SndInfo_t info;
+
+ info.m_nGuid = ch->guid;
+ info.m_filenameHandle = ch->sfx ? ch->sfx->GetFileNameHandle() : NULL;
+ info.m_nSoundSource = ch->soundsource;
+ info.m_nChannel = ch->entchannel;
+ // If a sound is being played through a speaker entity (e.g., on a monitor,), this is the
+ // entity upon which to show the lips moving, if the sound has sentence data
+ info.m_nSpeakerEntity = ch->speakerentity;
+ info.m_flVolume = (float)ch->master_vol / 255.0f;
+ info.m_flLastSpatializedVolume = ch->last_vol;
+ // Radius of this sound effect (spatialization is different within the radius)
+ info.m_flRadius = ch->radius;
+ info.m_nPitch = ch->basePitch;
+ info.m_pOrigin = &ch->origin;
+ info.m_pDirection = &ch->direction;
+
+ // if true, assume sound source can move and update according to entity
+ info.m_bUpdatePositions = ch->flags.bUpdatePositions;
+ // true if playing linked sentence
+ info.m_bIsSentence = ch->flags.isSentence;
+ // if true, bypass all dsp processing for this sound (ie: music)
+ info.m_bDryMix = ch->flags.bdry;
+ // true if sound is playing through in-game speaker entity.
+ info.m_bSpeaker = ch->flags.bSpeaker;
+ // true if sound is using special DSP effect
+ info.m_bSpecialDSP = ( ch->special_dsp != 0 );
+ // for snd_show, networked sounds get colored differently than local sounds
+ info.m_bFromServer = ch->flags.fromserver;
+
+ sndlist.AddToTail( info );
+ }
+}
+
+void S_StopAllSounds( bool bClear )
+{
+ THREAD_LOCK_SOUND();
+ int i;
+
+ if ( !g_AudioDevice )
+ return;
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ total_channels = MAX_DYNAMIC_CHANNELS; // no statics
+
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = list.GetChannel(i);
+ if ( channels[i].sfx )
+ {
+ DevMsg( 1, "%2d:Stopped sound %s\n", i, channels[i].sfx->getname() );
+ }
+ S_FreeChannel( pChannel );
+ }
+
+ Q_memset( channels, 0, MAX_CHANNELS * sizeof(channel_t) );
+
+ if ( bClear )
+ {
+ S_ClearBuffer();
+ }
+
+ // Clear any remaining soundfade
+ memset( &soundfade, 0, sizeof( soundfade ) );
+
+ g_AudioDevice->StopAllSounds();
+ Assert( g_ActiveChannels.GetActiveCount() == 0 );
+}
+
+void S_StopAllSoundsC( void )
+{
+ S_StopAllSounds( true );
+}
+
+void S_OnLoadScreen( bool value )
+{
+ s_bOnLoadScreen = value;
+}
+
+void S_ClearBuffer( void )
+{
+ if ( !g_AudioDevice )
+ return;
+
+ g_AudioDevice->ClearBuffer();
+ DSP_ClearState();
+ MIX_ClearAllPaintBuffers( PAINTBUFFER_SIZE, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : percent -
+// holdtime -
+// intime -
+// outtime -
+//-----------------------------------------------------------------------------
+void S_SoundFade( float percent, float holdtime, float intime, float outtime )
+{
+ soundfade.starttime = g_pSoundServices->GetHostTime();
+
+ soundfade.initial_percent = percent;
+ soundfade.fadeouttime = outtime;
+ soundfade.holdtime = holdtime;
+ soundfade.fadeintime = intime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Modulates sound volume on the client.
+//-----------------------------------------------------------------------------
+void S_UpdateSoundFade(void)
+{
+ float totaltime;
+ float f;
+ // Determine current fade value.
+
+ // Assume no fading remains
+ soundfade.percent = 0;
+
+ totaltime = soundfade.fadeouttime + soundfade.fadeintime + soundfade.holdtime;
+
+ float elapsed = g_pSoundServices->GetHostTime() - soundfade.starttime;
+
+ // Clock wrapped or reset (BUG) or we've gone far enough
+ if ( elapsed < 0.0f || elapsed >= totaltime || totaltime <= 0.0f )
+ {
+ return;
+ }
+
+ // We are in the fade time, so determine amount of fade.
+ if ( soundfade.fadeouttime > 0.0f && ( elapsed < soundfade.fadeouttime ) )
+ {
+ // Ramp up
+ f = elapsed / soundfade.fadeouttime;
+ }
+ // Inside the hold time
+ else if ( elapsed <= ( soundfade.fadeouttime + soundfade.holdtime ) )
+ {
+ // Stay
+ f = 1.0f;
+ }
+ else
+ {
+ // Ramp down
+ f = ( elapsed - ( soundfade.fadeouttime + soundfade.holdtime ) ) / soundfade.fadeintime;
+ // backward interpolated...
+ f = 1.0f - f;
+ }
+
+ // Spline it.
+ f = SimpleSpline( f );
+ f = clamp( f, 0.0f, 1.0f );
+
+ soundfade.percent = soundfade.initial_percent * f;
+}
+
+
+//=============================================================================
+
+// Global Voice Ducker - enabled in vcd scripts, when characters deliver important dialog. Overrides all
+// other mixer ducking, and ducks all other sounds except dialog.
+
+ConVar snd_ducktovolume( "snd_ducktovolume", "0.55", FCVAR_ARCHIVE );
+ConVar snd_duckerattacktime( "snd_duckerattacktime", "0.5", FCVAR_ARCHIVE );
+ConVar snd_duckerreleasetime( "snd_duckerreleasetime", "2.5", FCVAR_ARCHIVE );
+ConVar snd_duckerthreshold("snd_duckerthreshold", "0.15", FCVAR_ARCHIVE );
+
+static void S_UpdateVoiceDuck( int voiceChannelCount, int voiceChannelMaxVolume, float frametime )
+{
+ float volume_when_ducked = snd_ducktovolume.GetFloat();
+ int volume_threshold = (int)(snd_duckerthreshold.GetFloat() * 255.0);
+
+ float duckTarget = 1.0;
+ if ( voiceChannelCount > 0 )
+ {
+ voiceChannelMaxVolume = clamp(voiceChannelMaxVolume, 0, 255);
+
+ // duckTarget = RemapVal( voiceChannelMaxVolume, 0, 255, 1.0, volume_when_ducked );
+
+ // KB: Change: ducker now active if any character is speaking above threshold volume.
+ // KB: Active ducker drops all volumes to volumes * snd_duckvolume
+
+ if ( voiceChannelMaxVolume > volume_threshold )
+ duckTarget = volume_when_ducked;
+ }
+ float rate = ( duckTarget < g_DuckScale ) ? snd_duckerattacktime.GetFloat() : snd_duckerreleasetime.GetFloat();
+ g_DuckScale = Approach( duckTarget, g_DuckScale, frametime * ((1-volume_when_ducked) / rate) );
+}
+
+// set 2d forward vector, given 3d right vector.
+// NOTE: this should only be used for a listener forward
+// vector from a listener right vector. It is not a general use routine.
+
+void ConvertListenerVectorTo2D( Vector *pvforward, Vector *pvright )
+{
+ // get 2d forward direction vector, ignoring pitch angle
+ QAngle angles2d;
+ Vector source2d;
+ Vector listener_forward2d;
+
+ source2d = *pvright;
+ source2d.z = 0.0;
+
+ VectorNormalize(source2d);
+
+ // convert right vector to euler angles (yaw & pitch)
+
+ VectorAngles(source2d, angles2d);
+
+ // get forward angle of listener
+
+ angles2d[PITCH] = 0;
+ angles2d[YAW] += 90; // rotate 90 ccw
+ angles2d[ROLL] = 0;
+
+ if (angles2d[YAW] >= 360)
+ angles2d[YAW] -= 360;
+
+ AngleVectors(angles2d, &listener_forward2d);
+
+ VectorNormalize(listener_forward2d);
+
+ *pvforward = listener_forward2d;
+}
+
+// If this is nonzero, we will only spatialize some of the static
+// channels each frame. The round robin will spatialize 1 / (2 ^ x)
+// of the spatial channels each frame.
+ConVar snd_spatialize_roundrobin( "snd_spatialize_roundrobin", "0", FCVAR_ALLOWED_IN_COMPETITIVE, "Lowend optimization: if nonzero, spatialize only a fraction of sound channels each frame. 1/2^x of channels will be spatialized per frame." );
+/*
+============
+S_Update
+
+Called once each time through the main loop
+============
+*/
+void S_Update( const AudioState_t *pAudioState )
+{
+ VPROF("S_Update");
+ channel_t *ch;
+ channel_t *combine;
+ static unsigned int s_roundrobin = 0 ; ///< number of times this function is called.
+ ///< used instead of host_frame because that number
+ ///< isn't necessarily available here (sez Yahn).
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ g_SndMutex.Lock();
+
+ // Update any client side sound fade
+ S_UpdateSoundFade();
+
+ if ( pAudioState )
+ {
+ VectorCopy( pAudioState->m_Origin, listener_origin );
+ AngleVectors( pAudioState->m_Angles, &listener_forward, &listener_right, &listener_up );
+ s_bIsListenerUnderwater = pAudioState->m_bIsUnderwater;
+ }
+ else
+ {
+ VectorCopy( vec3_origin, listener_origin );
+ VectorCopy( vec3_origin, listener_forward );
+ VectorCopy( vec3_origin, listener_right );
+ VectorCopy( vec3_origin, listener_up );
+ s_bIsListenerUnderwater = false;
+ }
+
+ g_AudioDevice->UpdateListener( listener_origin, listener_forward, listener_right, listener_up );
+
+ combine = NULL;
+
+ int voiceChannelCount = 0;
+ int voiceChannelMaxVolume = 0;
+
+ // reset traceline counter for this frame
+ g_snd_trace_count = 0;
+
+ // calculate distance to nearest walls, update dsp_spatial
+ // updates one wall only per frame (one trace per frame)
+ SND_SetSpatialDelays();
+
+ // updates dsp_room if automatic room detection enabled
+ DAS_CheckNewRoomDSP();
+
+ // update spatialization for static and dynamic sounds
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+
+ if (snd_spatialize_roundrobin.GetInt() == 0)
+ {
+ // spatialize each channel each time
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ ch = list.GetChannel(i);
+ Assert(ch->sfx);
+ Assert(ch->activeIndex > 0);
+
+ SND_Spatialize(ch); // respatialize channel
+
+ if ( ch->sfx->pSource && ch->sfx->pSource->IsVoiceSource() )
+ {
+ voiceChannelCount++;
+ voiceChannelMaxVolume = max(voiceChannelMaxVolume, ChannelGetMaxVol( ch) );
+ }
+ }
+ }
+ else // lowend performance improvement: spatialize only some channels each frame.
+ {
+ unsigned int robinmask = (1 << snd_spatialize_roundrobin.GetInt()) - 1;
+
+ // now do static channels
+ for ( int i = 0 ; i < list.Count() ; ++i )
+ {
+ ch = list.GetChannel(i);
+ Assert(ch->sfx);
+ Assert(ch->activeIndex > 0);
+
+ // need to check bfirstpass because sound tracing may have been deferred
+ if ( ch->flags.bfirstpass || (robinmask & s_roundrobin) == ( i & robinmask ) )
+ {
+ SND_Spatialize(ch); // respatialize channel
+ }
+
+ if ( ch->sfx->pSource && ch->sfx->pSource->IsVoiceSource() )
+ {
+ voiceChannelCount++;
+ voiceChannelMaxVolume = max( voiceChannelMaxVolume, ChannelGetMaxVol( ch) );
+ }
+ }
+
+ ++s_roundrobin;
+ }
+
+
+
+ SND_ChannelTraceReset();
+
+ // set new target for voice ducking
+ float frametime = g_pSoundServices->GetHostFrametime();
+ S_UpdateVoiceDuck( voiceChannelCount, voiceChannelMaxVolume, frametime );
+
+ // update x360 music volume
+ g_DashboardMusicMixValue = Approach( g_DashboardMusicMixTarget, g_DashboardMusicMixValue, g_DashboardMusicFadeRate * frametime );
+
+ //
+ // debugging output
+ //
+ if (snd_show.GetInt())
+ {
+ con_nprint_t np;
+ np.time_to_live = 2.0f;
+ np.fixed_width_font = true;
+
+ int total = 0;
+
+ CChannelList activeChannels;
+ g_ActiveChannels.GetActiveChannels( activeChannels );
+ for ( int i = 0; i < activeChannels.Count(); i++ )
+ {
+ channel_t *channel = activeChannels.GetChannel(i);
+ if ( !channel->sfx )
+ continue;
+
+ np.index = total + 2;
+ if ( channel->flags.fromserver )
+ {
+ np.color[0] = 1.0;
+ np.color[1] = 0.8;
+ np.color[2] = 0.1;
+ }
+ else
+ {
+ np.color[0] = 0.1;
+ np.color[1] = 0.9;
+ np.color[2] = 1.0;
+ }
+
+ unsigned int sampleCount = RemainingSamples( channel );
+ float timeleft = (float)sampleCount / (float)channel->sfx->pSource->SampleRate();
+ bool bLooping = channel->sfx->pSource->IsLooped();
+
+ if (snd_surround.GetInt() < 4)
+ {
+ Con_NXPrintf ( &np, "%02i l(%03d) r(%03d) vol(%03d) ent(%03d) pos(%6d %6d %6d) timeleft(%f) looped(%d) %50s",
+ total+ 1,
+ (int)channel->fvolume[IFRONT_LEFT],
+ (int)channel->fvolume[IFRONT_RIGHT],
+ channel->master_vol,
+ channel->soundsource,
+ (int)channel->origin[0],
+ (int)channel->origin[1],
+ (int)channel->origin[2],
+ timeleft,
+ bLooping,
+ channel->sfx->getname());
+ }
+ else
+ {
+ Con_NXPrintf ( &np, "%02i l(%03d) c(%03d) r(%03d) rl(%03d) rr(%03d) vol(%03d) ent(%03d) pos(%6d %6d %6d) timeleft(%f) looped(%d) %50s",
+ total+ 1,
+ (int)channel->fvolume[IFRONT_LEFT],
+ (int)channel->fvolume[IFRONT_CENTER],
+ (int)channel->fvolume[IFRONT_RIGHT],
+ (int)channel->fvolume[IREAR_LEFT],
+ (int)channel->fvolume[IREAR_RIGHT],
+ channel->master_vol,
+ channel->soundsource,
+ (int)channel->origin[0],
+ (int)channel->origin[1],
+ (int)channel->origin[2],
+ timeleft,
+ bLooping,
+ channel->sfx->getname());
+ }
+
+ if ( snd_visualize.GetInt() )
+ {
+ CDebugOverlay::AddTextOverlay( channel->origin, 0.05f, channel->sfx->getname() );
+ }
+
+ total++;
+ }
+
+ while ( total <= 128 )
+ {
+ Con_NPrintf( total + 2, "" );
+ total++;
+ }
+ }
+
+ g_SndMutex.Unlock();
+
+ if ( s_bOnLoadScreen )
+ return;
+
+ // not time to update yet?
+ double tNow = Plat_FloatTime();
+ // this is the last time we ran a sound frame
+ g_LastSoundFrame = tNow;
+ // this is the last time we did mixing (extraupdate also advances this if it mixes)
+ g_LastMixTime = tNow;
+ // mix some sound
+ // try to stay at least one frame + mixahead ahead in the mix.
+ g_EstFrameTime = (g_EstFrameTime * 0.9f) + (g_pSoundServices->GetHostFrametime() * 0.1f);
+ S_Update_( g_EstFrameTime + snd_mixahead.GetFloat() );
+}
+
+CON_COMMAND( snd_dumpclientsounds, "Dump sounds to VXConsole" )
+{
+ con_nprint_t np;
+ np.time_to_live = 2.0f;
+ np.fixed_width_font = true;
+
+ int total = 0;
+
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *ch = list.GetChannel(i);
+ if ( !ch->sfx )
+ continue;
+
+ unsigned int sampleCount = RemainingSamples( ch );
+ float timeleft = (float)sampleCount / (float)ch->sfx->pSource->SampleRate();
+ bool bLooping = ch->sfx->pSource->IsLooped();
+ const char *pszclassname = GetClientClassname(ch->soundsource);
+
+ Msg( "%02i %s l(%03d) c(%03d) r(%03d) rl(%03d) rr(%03d) vol(%03d) pos(%6d %6d %6d) timeleft(%f) looped(%d) %50s chan:%d ent(%03d):%s\n",
+ total+ 1,
+ ch->flags.fromserver ? "SERVER" : "CLIENT",
+ (int)ch->fvolume[IFRONT_LEFT],
+ (int)ch->fvolume[IFRONT_CENTER],
+ (int)ch->fvolume[IFRONT_RIGHT],
+ (int)ch->fvolume[IREAR_LEFT],
+ (int)ch->fvolume[IREAR_RIGHT],
+ ch->master_vol,
+ (int)ch->origin[0],
+ (int)ch->origin[1],
+ (int)ch->origin[2],
+ timeleft,
+ bLooping,
+ ch->sfx->getname(),
+ ch->entchannel,
+ ch->soundsource,
+ pszclassname ? pszclassname : "NULL" );
+
+ total++;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Set g_soundtime to number of full samples that have been transfered out to hardware
+// since start.
+//-----------------------------------------------------------------------------
+void GetSoundTime(void)
+{
+ int fullsamples;
+ int sampleOutCount;
+
+ // size of output buffer in *full* 16 bit samples
+ // A 2 channel device has a *full* sample consisting of a 16 bit LR pair.
+ // A 1 channel device has a *full* sample consiting of a 16 bit single sample.
+ fullsamples = g_AudioDevice->DeviceSampleCount() / g_AudioDevice->DeviceChannels();
+
+ // NOTE: it is possible to miscount buffers if it has wrapped twice between
+ // calls to S_Update. However, since the output buffer size is > 1 second of sound,
+ // this should only occur for framerates lower than 1hz
+
+ // sampleOutCount is counted in 16 bit *full* samples, of number of samples output to hardware
+ // for current output buffer
+ sampleOutCount = g_AudioDevice->GetOutputPosition();
+ if ( sampleOutCount < s_oldsampleOutCount )
+ {
+ // buffer wrapped
+ s_buffers++;
+ if ( g_paintedtime > 0x70000000 )
+ {
+ // time to chop things off to avoid 32 bit limits
+ s_buffers = 0;
+ g_paintedtime = fullsamples;
+ S_StopAllSounds( true );
+ }
+ }
+
+ s_oldsampleOutCount = sampleOutCount;
+
+ if ( cl_movieinfo.IsRecording() || IsReplayRendering() )
+ {
+ // when recording a replay, we look at the record frame rate, not the engine frame rate
+
+#if defined( REPLAY_ENABLED )
+ extern IClientReplayContext *g_pClientReplayContext;
+ if ( IsReplayRendering() )
+ {
+ IReplayMovieRenderer *pMovieRenderer = (g_pClientReplayContext != NULL) ? g_pClientReplayContext->GetMovieRenderer() : NULL;
+
+ if ( pMovieRenderer && pMovieRenderer->IsAudioSyncFrame() )
+ {
+ float t = g_pSoundServices->GetHostTime();
+ if ( s_lastsoundtime != t )
+ {
+ float frameTime = pMovieRenderer->GetRecordingFrameDuration();
+ float fSamples = frameTime * (float) g_AudioDevice->DeviceDmaSpeed() + g_ReplaySoundTimeFracAccumulator;
+
+ float intPart = (float) floor( fSamples );
+ g_ReplaySoundTimeFracAccumulator = fSamples - intPart;
+
+ g_soundtime += (int) intPart;
+ s_lastsoundtime = t;
+ }
+ }
+
+ }
+ else // cl_movieinfo.IsRecording()
+ // in movie, just mix one frame worth of sound
+#endif
+ {
+
+ float t = g_pSoundServices->GetHostTime();
+ if ( s_lastsoundtime != t )
+ {
+ g_soundtime += g_pSoundServices->GetHostFrametime() * g_AudioDevice->DeviceDmaSpeed();
+
+ s_lastsoundtime = t;
+ }
+ }
+ }
+ else
+ {
+ // g_soundtime indicates how many *full* samples have actually been
+ // played out to dma
+ g_soundtime = s_buffers*fullsamples + sampleOutCount;
+ }
+}
+
+void S_ExtraUpdate( void )
+{
+ if ( !g_AudioDevice || !g_pSoundServices )
+ return;
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ if ( s_bOnLoadScreen )
+ return;
+
+ if ( snd_noextraupdate.GetInt() || cl_movieinfo.IsRecording() || IsReplayRendering() )
+ return; // don't pollute timings
+
+ // If listener position and orientation has not yet been updated (ie: no call to S_Update since level load)
+ // then don't mix. Important - mixing with listener at 'false' origin causes
+ // some sounds to incorrectly spatialize to 0 volume, killing them before they can play.
+
+ if ((listener_origin == vec3_origin) &&
+ (listener_forward == vec3_origin) &&
+ (listener_right == vec3_origin) &&
+ (listener_up == vec3_origin) )
+ return;
+
+ // Only mix if you have used up 90% of the mixahead buffer
+ double tNow = Plat_FloatTime();
+ float delta = (tNow - g_LastMixTime);
+ // we know we were at least snd_mixahead seconds ahead of the output the last time we did mixing
+ // if we're not close to running out just exit to avoid small mix batches
+ if ( delta > 0 && delta < (snd_mixahead.GetFloat() * 0.9f) )
+ return;
+ g_LastMixTime = tNow;
+
+ g_pSoundServices->OnExtraUpdate();
+ // Shouldn't have to do any work here if your framerate hasn't dropped
+ S_Update_( snd_mixahead.GetFloat() );
+}
+
+extern void DEBUG_StartSoundMeasure(int type, int samplecount );
+extern void DEBUG_StopSoundMeasure(int type, int samplecount );
+
+void S_Update_Guts( float mixAheadTime )
+{
+ VPROF( "S_Update_Guts" );
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ DEBUG_StartSoundMeasure(4, 0);
+
+ // Update our perception of audio time.
+ // 'g_soundtime' tells how many samples have
+ // been played out of the dma buffer since sound system startup.
+ // 'g_paintedtime' indicates how many samples we've actually mixed
+ // and sent to the dma buffer since sound system startup.
+ GetSoundTime();
+
+// if ( g_soundtime > g_paintedtime )
+// {
+// // if soundtime > paintedtime, then the dma buffer
+// // has played out more sound than we've actually
+// // mixed. We need to call S_Update_ more often.
+//
+// DevMsg ("S_Update_ : Underflow\n");
+// paintedtime = g_soundtime;
+// }
+// (kdb) above code doesn't handle underflow correctly
+// should actually zero out the paintbuffer to advance to the new
+// time.
+
+ // mix ahead of current position
+ unsigned endtime = g_AudioDevice->PaintBegin( mixAheadTime, g_soundtime, g_paintedtime );
+
+ int samples = endtime - g_paintedtime;
+ samples = samples < 0 ? 0 : samples;
+ if ( samples )
+ {
+ THREAD_LOCK_SOUND();
+
+ DEBUG_StartSoundMeasure( 2, samples );
+
+ MIX_PaintChannels( endtime, s_bIsListenerUnderwater );
+
+ MXR_DebugShowMixVolumes();
+
+ MXR_UpdateAllDuckerVolumes();
+
+ DEBUG_StopSoundMeasure( 2, 0 );
+ }
+
+ g_AudioDevice->PaintEnd();
+ DEBUG_StopSoundMeasure( 4, samples );
+}
+
+#if !defined( _X360 )
+#define THREADED_MIX_TIME 33
+#else
+#define THREADED_MIX_TIME XMA_POLL_RATE
+#endif
+
+ConVar snd_ShowThreadFrameTime( "snd_ShowThreadFrameTime", "0" );
+
+bool g_bMixThreadExit;
+ThreadHandle_t g_hMixThread;
+void S_Update_Thread()
+{
+ float frameTime = THREADED_MIX_TIME * 0.001f;
+ double lastFrameTime = Plat_FloatTime();
+
+ while ( !g_bMixThreadExit )
+ {
+ // mixing (for 360) needs to be updated at a steady rate
+ // large update times causes the mixer to demand more audio data
+ // the 360 decoder has finite latency and cannot fulfill spike requests
+ float t0 = Plat_FloatTime();
+ S_Update_Guts( frameTime + snd_mixahead.GetFloat() );
+ int updateTime = ( Plat_FloatTime() - t0 ) * 1000.0f;
+
+ // try to maintain a steadier rate by compensating for fluctuating mix times
+ int sleepTime = THREADED_MIX_TIME - updateTime;
+ if ( sleepTime > 0 )
+ {
+ ThreadSleep( sleepTime );
+ }
+
+ // mimic a frametime needed for sound update
+ double t1 = Plat_FloatTime();
+ frameTime = t1 - lastFrameTime;
+ lastFrameTime = t1;
+
+ if ( snd_ShowThreadFrameTime.GetBool() )
+ {
+ Msg( "S_Update_Thread: frameTime: %d ms\n", (int)( frameTime * 1000.0f ) );
+ }
+ }
+}
+
+void S_ShutdownMixThread()
+{
+ if ( g_hMixThread )
+ {
+ g_bMixThreadExit = true;
+ ThreadJoin( g_hMixThread );
+ ReleaseThreadHandle( g_hMixThread );
+ g_hMixThread = NULL;
+ }
+}
+
+void S_Update_( float mixAheadTime )
+{
+ if ( !IsConsole() || !snd_mix_async.GetBool() )
+ {
+ S_ShutdownMixThread();
+ S_Update_Guts( mixAheadTime );
+ }
+ else
+ {
+ if ( !g_hMixThread )
+ {
+ g_bMixThreadExit = false;
+ g_hMixThread = ThreadExecuteSolo( "SndMix", S_Update_Thread );
+ if ( IsX360() )
+ {
+ ThreadSetAffinity( g_hMixThread, XBOX_PROCESSOR_5 );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Threaded mixing enable. Purposely hiding enable/disable details.
+//-----------------------------------------------------------------------------
+void S_EnableThreadedMixing( bool bEnable )
+{
+ if ( snd_mix_async.GetBool() != bEnable )
+ {
+ snd_mix_async.SetValue( bEnable );
+ }
+}
+
+/*
+===============================================================================
+
+console functions
+
+===============================================================================
+*/
+extern void DSP_DEBUGSetParams(int ipreset, int iproc, float *pvalues, int cparams);
+extern void DSP_DEBUGReloadPresetFile( void );
+
+void S_DspParms( const CCommand &args )
+{
+ if ( args.ArgC() == 1)
+ {
+ // if dsp_parms with no arguments, reload entire preset file
+
+ DSP_DEBUGReloadPresetFile();
+
+ return;
+ }
+
+ if ( args.ArgC() < 4 )
+ {
+ Msg( "Usage: dsp_parms PRESET# PROC# param0 param1 ...up to param15 \n" );
+ return;
+ }
+
+ int cparam = min( args.ArgC() - 4, 16);
+
+ float params[16];
+ Q_memset( params, 0, sizeof(float) * 16 );
+
+ // get preset & proc
+ int idsp, iproc;
+ idsp = Q_atof( args[1] );
+ iproc = Q_atof( args[2] );
+
+ // get params
+ for (int i = 0; i < cparam; i++)
+ {
+ params[i] = Q_atof( args[i+4] );
+ }
+
+ // set up params & switch preset
+ DSP_DEBUGSetParams(idsp, iproc, params, cparam);
+}
+
+static ConCommand dsp_parm("dsp_reload", S_DspParms );
+
+void S_Play( const char *pszName, bool flush = false )
+{
+ int inCache;
+ char szName[256];
+ CSfxTable *pSfx;
+
+ Q_strncpy( szName, pszName, sizeof( szName ) );
+ if ( !Q_strrchr( pszName, '.' ) )
+ {
+ Q_strncat( szName, ".wav", sizeof( szName ), COPY_ALL_CHARACTERS );
+ }
+
+ pSfx = S_FindName( szName, &inCache );
+ if ( inCache && flush )
+ {
+ pSfx->pSource->CacheUnload();
+ }
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = g_pSoundServices->GetViewEntity();
+ params.entchannel = CHAN_REPLACE;
+ params.pSfx = pSfx;
+ params.origin = listener_origin;
+ params.fvol = 1.0f;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+
+ S_StartSound( params );
+}
+
+static void S_Play( const CCommand &args )
+{
+ bool bFlush = !Q_stricmp( args[0], "playflush" );
+ for ( int i = 1; i < args.ArgC(); ++i )
+ {
+ S_Play( args[i], bFlush );
+ }
+}
+
+static void S_PlayVol( const CCommand &args )
+{
+ static int hash=543;
+ float vol;
+ char name[256];
+ CSfxTable *pSfx;
+
+ for ( int i = 1; i<args.ArgC(); i += 2 )
+ {
+ if ( !Q_strrchr( args[i], '.') )
+ {
+ Q_strncpy( name, args[i], sizeof( name ) );
+ Q_strncat( name, ".wav", sizeof( name ), COPY_ALL_CHARACTERS );
+ }
+ else
+ {
+ Q_strncpy( name, args[i], sizeof( name ) );
+ }
+
+ pSfx = S_PrecacheSound( name );
+ vol = Q_atof( args[i+1] );
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = hash++;
+ params.entchannel = CHAN_AUTO;
+ params.pSfx = pSfx;
+ params.origin = listener_origin;
+ params.fvol = vol;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+
+ S_StartDynamicSound( params );
+ }
+}
+
+static void S_PlayDelay( const CCommand &args )
+{
+ if ( args.ArgC() != 3 )
+ {
+ Msg( "Usage: sndplaydelay delay_in_sec (negative to skip ahead) soundname\n" );
+ return;
+ }
+
+ char szName[256];
+ CSfxTable *pSfx;
+
+ float delay = Q_atof( args[ 1 ] );
+
+ Q_strncpy(szName, args[ 2 ], sizeof( szName ) );
+ if ( !Q_strrchr( args[ 2 ], '.' ) )
+ {
+ Q_strncat( szName, ".wav", sizeof( szName ), COPY_ALL_CHARACTERS );
+ }
+
+ pSfx = S_FindName( szName, NULL );
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = g_pSoundServices->GetViewEntity();
+ params.entchannel = CHAN_REPLACE;
+ params.pSfx = pSfx;
+ params.origin = listener_origin;
+ params.fvol = 1.0f;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+ params.delay = delay;
+
+ S_StartSound( params );
+
+}
+static ConCommand sndplaydelay( "sndplaydelay", S_PlayDelay, "Usage: sndplaydelay delay_in_sec (negative to skip ahead) soundname", FCVAR_SERVER_CAN_EXECUTE );
+
+static bool SortByNameLessFunc( const int &lhs, const int &rhs )
+{
+ CSfxTable *pSfx1 = s_Sounds[lhs].pSfx;
+ CSfxTable *pSfx2 = s_Sounds[rhs].pSfx;
+
+ return CaselessStringLessThan( pSfx1->getname(), pSfx2->getname() );
+}
+
+void S_SoundList(void)
+{
+ CSfxTable *sfx;
+ CAudioSource *pSource;
+ int size, total;
+
+ total = 0;
+ for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
+ {
+ sfx = s_Sounds[i].pSfx;
+
+ pSource = sfx->pSource;
+ if ( !pSource || !pSource->IsCached() )
+ continue;
+
+ size = pSource->SampleSize() * pSource->SampleCount();
+ total += size;
+
+ if ( pSource->IsLooped() )
+ Msg ("L");
+ else
+ Msg (" ");
+ Msg("(%2db) %6i : %s\n", pSource->SampleSize(), size, sfx->getname());
+ }
+ Msg( "Total resident: %i\n", total );
+}
+
+#if defined( _X360 )
+CON_COMMAND( vx_soundlist, "Dump sounds to VXConsole" )
+{
+ CSfxTable *sfx;
+ CAudioSource *pSource;
+ int dataSize;
+ char *pFormatStr;
+ int sampleRate;
+ int sampleBits;
+ int streamed;
+ int looped;
+ int channels;
+ int numSamples;
+
+ int numSounds = s_Sounds.Count();
+ xSoundList_t* pSoundList = new xSoundList_t[numSounds];
+
+ int i = 0;
+ for ( int iSrcSound=s_Sounds.FirstInorder(); iSrcSound != s_Sounds.InvalidIndex(); iSrcSound = s_Sounds.NextInorder( iSrcSound ) )
+ {
+ dataSize = -1;
+ sampleRate = -1;
+ sampleBits = -1;
+ pFormatStr = "???";
+ streamed = -1;
+ looped = -1;
+ channels = -1;
+ numSamples = -1;
+
+ sfx = s_Sounds[iSrcSound].pSfx;
+ pSource = sfx->pSource;
+ if ( pSource && pSource->IsCached() )
+ {
+ numSamples = pSource->SampleCount();
+ dataSize = pSource->DataSize();
+ sampleRate = pSource->SampleRate();
+ streamed = pSource->IsStreaming();
+ looped = pSource->IsLooped();
+ channels = pSource->IsStereoWav() ? 2 : 1;
+
+ if ( pSource->Format() == WAVE_FORMAT_ADPCM )
+ {
+ pFormatStr = "ADPCM";
+ sampleBits = 16;
+ }
+ else if ( pSource->Format() == WAVE_FORMAT_PCM )
+ {
+ pFormatStr = "PCM";
+ sampleBits = (pSource->SampleSize() * 8)/channels;
+ }
+ else if ( pSource->Format() == WAVE_FORMAT_XMA )
+ {
+ pFormatStr = "XMA";
+ sampleBits = 16;
+ }
+ }
+
+ V_strncpy( pSoundList[i].name, sfx->getname(), sizeof( pSoundList[i].name ) );
+ V_strncpy( pSoundList[i].formatName, pFormatStr, sizeof( pSoundList[i].formatName ) );
+ pSoundList[i].rate = sampleRate;
+ pSoundList[i].bits = sampleBits;
+ pSoundList[i].channels = channels;
+ pSoundList[i].looped = looped;
+ pSoundList[i].dataSize = dataSize;
+ pSoundList[i].numSamples = numSamples;
+ pSoundList[i].streamed = streamed;
+ ++i;
+ }
+
+ XBX_rSoundList( numSounds, pSoundList );
+ delete [] pSoundList;
+}
+#endif
+
+extern unsigned g_snd_time_debug;
+extern unsigned g_snd_call_time_debug;
+extern unsigned g_snd_count_debug;
+extern unsigned g_snd_samplecount;
+extern unsigned g_snd_frametime;
+extern unsigned g_snd_frametime_total;
+extern int g_snd_profile_type;
+
+// start measuring sound perf, 100 reps
+// type 1 - dsp, 2 - mix, 3 - load sound, 4 - all sound
+// set type via ConVar snd_profile
+
+void DEBUG_StartSoundMeasure(int type, int samplecount )
+{
+ if (type != g_snd_profile_type)
+ return;
+
+ if (samplecount)
+ g_snd_samplecount += samplecount;
+
+ g_snd_call_time_debug = Plat_MSTime();
+}
+
+// show sound measurement after 25 reps - show as % of total frame
+// type 1 - dsp, 2 - mix, 3 - load sound, 4 - all sound
+
+// BUGBUG: snd_profile 4 reports a lower average because it's average cost
+// PER CALL and most calls (via SoundExtraUpdate()) don't do any work and
+// bring the average down. If you want an average PER FRAME instead, it's generally higher.
+void DEBUG_StopSoundMeasure(int type, int samplecount )
+{
+ if (type != g_snd_profile_type)
+ return;
+
+ if (samplecount)
+ g_snd_samplecount += samplecount;
+
+ // add total time since last frame
+
+ g_snd_frametime_total += Plat_MSTime() - g_snd_frametime;
+
+ // performance timing
+
+ g_snd_time_debug += Plat_MSTime() - g_snd_call_time_debug;
+
+ if (++g_snd_count_debug >= 100)
+ {
+ switch (g_snd_profile_type)
+ {
+ case 1:
+ Msg("dsp: (%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
+ Msg("(%2.2f) pct of frame \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total));
+ break;
+ case 2:
+ Msg("mix+dsp:(%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
+ Msg("(%2.2f) pct of frame \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total));
+ break;
+ case 3:
+ //if ( (((float)g_snd_time_debug) / 100.0) < 0.01 )
+ // break;
+ Msg("snd load: (%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
+ Msg("(%2.2f) pct of frame \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total));
+ break;
+ case 4:
+ Msg("sound: (%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
+ Msg("(%2.2f) pct of frame (%d samples) \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total), g_snd_samplecount);
+ break;
+ }
+
+ g_snd_count_debug = 0;
+ g_snd_time_debug = 0;
+ g_snd_samplecount = 0;
+ g_snd_frametime_total = 0;
+ }
+
+ g_snd_frametime = Plat_MSTime();
+}
+
+// speak a sentence from console; works by passing in "!sentencename"
+// or "sentence"
+
+extern ConVar dsp_room;
+
+static void S_Say( const CCommand &args )
+{
+ CSfxTable *pSfx;
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ char sound[256];
+ Q_strncpy( sound, args[1], sizeof( sound ) );
+
+ // DEBUG - test performance of dsp code
+ if ( !Q_stricmp( sound, "dsp" ) )
+ {
+ unsigned time;
+ int i;
+ int count = 10000;
+ int idsp;
+
+ for (i = 0; i < PAINTBUFFER_SIZE; i++)
+ {
+ g_paintbuffer[i].left = RandomInt(0,2999);
+ g_paintbuffer[i].right = RandomInt(0,2999);
+ }
+
+ Msg ("Start profiling 10,000 calls to DSP\n");
+
+ idsp = dsp_room.GetInt();
+
+ // get system time
+
+ time = Plat_MSTime();
+
+ for (i = 0; i < count; i++)
+ {
+ // SX_RoomFX(PAINTBUFFER_SIZE, TRUE, TRUE);
+
+ DSP_Process(idsp, g_paintbuffer, NULL, NULL, PAINTBUFFER_SIZE);
+
+ }
+ // display system time delta
+ Msg("%d milliseconds \n", Plat_MSTime() - time);
+ return;
+ }
+
+ if ( !Q_stricmp(sound, "paint") )
+ {
+ unsigned time;
+ int count = 10000;
+ static int hash=543;
+ int psav = g_paintedtime;
+
+ Msg ("Start profiling MIX_PaintChannels\n");
+
+ pSfx = S_PrecacheSound("ambience/labdrone1.wav");
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = hash++;
+ params.entchannel = CHAN_AUTO;
+ params.pSfx = pSfx;
+ params.origin = listener_origin;
+ params.fvol = 1.0f;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+
+ S_StartDynamicSound( params );
+
+ // get system time
+ time = Plat_MSTime();
+
+ // paint a boatload of sound
+
+ MIX_PaintChannels( g_paintedtime + 512*count, s_bIsListenerUnderwater );
+
+ // display system time delta
+ Msg("%d milliseconds \n", Plat_MSTime() - time);
+ g_paintedtime = psav;
+ return;
+ }
+
+ // DEBUG
+ if ( !TestSoundChar( sound, CHAR_SENTENCE ) )
+ {
+ // build a fake sentence name, then play the sentence text
+
+ Q_strncpy(sound, "xxtestxx ", sizeof( sound ) );
+ Q_strncat(sound, args[1], sizeof( sound ), COPY_ALL_CHARACTERS );
+
+ int addIndex = g_Sentences.AddToTail();
+ sentence_t *pSentence = &g_Sentences[addIndex];
+ pSentence->pName = sound;
+ pSentence->length = 0;
+
+ // insert null terminator after sentence name
+ sound[8] = 0;
+
+ pSfx = S_PrecacheSound ("!xxtestxx");
+ if (!pSfx)
+ {
+ Msg ("S_Say: can't cache %s\n", sound);
+ return;
+ }
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = g_pSoundServices->GetViewEntity();
+ params.entchannel = CHAN_REPLACE;
+ params.pSfx = pSfx;
+ params.origin = vec3_origin;
+ params.fvol = 1.0f;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+
+ S_StartDynamicSound ( params );
+
+ // remove last
+ g_Sentences.Remove( g_Sentences.Size() - 1 );
+ }
+ else
+ {
+ pSfx = S_FindName(sound, NULL);
+ if (!pSfx)
+ {
+ Msg ("S_Say: can't find sentence name %s\n", sound);
+ return;
+ }
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = g_pSoundServices->GetViewEntity();
+ params.entchannel = CHAN_REPLACE;
+ params.pSfx = pSfx;
+ params.origin = vec3_origin;
+ params.fvol = 1.0f;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+
+ S_StartDynamicSound( params );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+//
+// Sound Mixers
+//
+// Sound mixers are referenced by name from Soundscapes, and are used to provide
+// custom volume control over various sound categories, called 'mix groups'
+//
+// see scripts/soundmixers.txt for data format
+//------------------------------------------------------------------------------
+
+#define CMXRGROUPMAX 64 // up to n mixgroups
+#define CMXRGROUPRULESMAX (CMXRGROUPMAX + 16) // max number of group rules
+#define CMXRSOUNDMIXERSMAX 32 // up to n sound mixers per project
+
+// mix groups - these equivalent to submixes on an audio mixer
+
+// list of rules for determining sound membership in mix groups.
+// All conditions which are not null are ANDed together
+#define CMXRCLASSMAX 16
+#define CMXRNAMEMAX 32
+
+struct classlistelem_t
+{
+ char szclassname[CMXRNAMEMAX]; // name of entities' class, such as CAI_BaseNPC or CHL2_Player
+};
+
+
+struct grouprule_t
+{
+ char szmixgroup[CMXRNAMEMAX]; // mix group name
+ int mixgroupid; // mix group unique id
+ char szdir[CMXRNAMEMAX]; // substring to search for in ch->sfx
+ int classId; // index of classname
+ int chantype; // channel type (CHAN_WEAPON, etc)
+ int soundlevel_min; // min soundlevel
+ int soundlevel_max; // max soundlevel
+
+ int priority; // 0..100 higher priority sound groups duck all lower pri groups if enabled
+ int is_ducked; // if 1, sound group is ducked by all higher priority 'causes_duck" sounds
+ int causes_ducking; // if 1, sound group ducks other 'is_ducked' sounds of lower priority
+ float duck_target_pct; // if sound group is ducked, target percent of original volume
+
+ float total_vol; // total volume of all sounds in this group, if group can cause ducking
+ float ducker_threshold; // ducking is caused by this group if total_vol > ducker_threshold
+ // and causes_ducking is enabled.
+ float duck_target_vol; // target volume while ducking
+ float duck_ramp_val; // current value of ramp - moves towards duck_target_vol
+};
+
+// sound mixer
+
+struct soundmixer_t
+{
+ char szsoundmixer[CMXRNAMEMAX]; // name of this soundmixer
+ float mapMixgroupidToValue[CMXRGROUPMAX]; // sparse array of mix group values for this soundmixer
+};
+
+int g_mapMixgroupidToGrouprulesid[CMXRGROUPMAX]; // map mixgroupid (one per unique group name)
+ // back to 1st entry of this name in g_grouprules
+
+// sound mixer globals
+
+classlistelem_t g_groupclasslist[CMXRCLASSMAX];
+soundmixer_t g_soundmixers[CMXRSOUNDMIXERSMAX]; // all sound mixers
+grouprule_t g_grouprules[CMXRGROUPRULESMAX]; // all rules for determining mix group membership
+
+
+// set current soundmixer index g_isoundmixer, search for match in soundmixers
+// Only change current soundmixer if new name is different from current name.
+
+int g_isoundmixer = -1; // index of current sound mixer
+char g_szsoundmixer_cur[64]; // current soundmixer name
+
+ConVar snd_soundmixer("snd_soundmixer", "Default_Mix"); // current soundmixer name
+
+
+void MXR_SetCurrentSoundMixer( const char *szsoundmixer )
+{
+ // if soundmixer name is not different from current name, return
+
+ if ( !Q_stricmp(szsoundmixer, g_szsoundmixer_cur) )
+ {
+ return;
+ }
+
+ for (int i = 0; i < g_csoundmixers; i++)
+ {
+ if ( !Q_stricmp(g_soundmixers[i].szsoundmixer, szsoundmixer) )
+ {
+ g_isoundmixer = i;
+
+ // save new current sound mixer name
+ V_strcpy_safe(g_szsoundmixer_cur, szsoundmixer);
+
+ return;
+ }
+ }
+}
+
+ConVar snd_showclassname("snd_showclassname", "0"); // if 1, show classname of ent making sound
+ // if 2, show all mixgroup matches
+ // if 3, show all mixgroup matches with current soundmixer for ent
+// get the client class name if an entity was specified
+const char *GetClientClassname( SoundSource soundsource )
+{
+ IClientEntity *pClientEntity = NULL;
+ if ( entitylist )
+ {
+ pClientEntity = entitylist->GetClientEntity( soundsource );
+ if ( pClientEntity )
+ {
+ ClientClass *pClientClass = pClientEntity->GetClientClass();
+ // check npc sounds
+ if ( pClientClass )
+ {
+ return pClientClass->GetName();
+ }
+ }
+ }
+
+ return NULL;
+}
+
+// builds a cached list of rules that match the directory name on the sound
+int MXR_GetMixGroupListFromDirName( const char *pDirname, byte *pList, int listMax )
+{
+ // if we call this before the groups are parsed we'll get bad data
+ Assert(g_cgrouprules>0);
+ int count = 0;
+ for ( int i = 0; i < listMax; i++ )
+ {
+ pList[i] = 255;
+ }
+
+ for ( int i = 0; i < g_cgrouprules; i++ )
+ {
+ grouprule_t *prule = &g_grouprules[i];
+ if ( prule->szdir[ 0 ] && Q_stristr( pDirname, prule->szdir ) )
+ {
+ pList[count] = i;
+ count++;
+ if ( count >= listMax )
+ return count;
+ }
+ }
+ return count;
+}
+
+
+// determine which mixgroups sound is in, and save those mixgroupids in sound.
+// use current soundmixer indicated with g_isoundmixer, and contents of g_rgpgrouprules.
+// Algorithm:
+// 1. all conditions in a row are AND conditions,
+// 2. all rows sharing the same groupname are OR conditions.
+// so - if a sound matches all conditions of a row, it is given that row's mixgroup id
+// if a sound doesn't match all conditions of a row, the next row is checked.
+
+// returns 0, default mixgroup if no match
+void MXR_GetMixGroupFromSoundsource( channel_t *pchan, SoundSource soundsource, soundlevel_t soundlevel)
+{
+ int i;
+ grouprule_t *prule;
+ bool fmatch;
+ bool classMatch[CMXRCLASSMAX];
+
+ // init all mixgroups for channel
+ for ( i = 0; i < 8; i++ )
+ {
+ pchan->mixgroups[i] = -1;
+ }
+
+ char sndname[MAX_OSPATH];
+ Q_strncpy( sndname, pchan->sfx->getname(), sizeof( sndname ) );
+ // Use forward slashes here
+ Q_FixSlashes( sndname, '/' );
+ const char *pszclassname = GetClientClassname(soundsource);
+
+ for ( i = 0; i < g_cgroupclass; i++ )
+ {
+ classMatch[i] = false;
+ if ( pszclassname && Q_stristr(pszclassname, g_groupclasslist[i].szclassname ) )
+ {
+ classMatch[i] = true;
+ }
+ }
+
+ if ( snd_showclassname.GetInt() == 1)
+ {
+ // utility: show classname of ent making sound
+
+ if (pszclassname)
+ {
+ DevMsg("(%s:%s) \n", pszclassname, sndname);
+ }
+ }
+
+ // check all group rules for a match, save
+ // up to 8 matches in channel mixgroup.
+
+ int cmixgroups = 0;
+ if (!pchan->sfx->m_bMixGroupsCached)
+ {
+ pchan->sfx->OnNameChanged( pchan->sfx->getname() );
+ }
+
+ // since this is a sorted list (in group rule order) we only need to test against the next matching rule
+ // this avoids a search inside the loop
+ int currentDirRuleIndex = 0;
+ int currentDirRule = pchan->sfx->m_mixGroupList[0];
+
+ for (i = 0; i < g_cgrouprules; i++)
+ {
+ prule = &g_grouprules[i];
+ fmatch = true;
+
+ // check directory or name substring
+#if _DEBUG
+ // check dir table is correct in CSfxTable cache
+ if ( prule->szdir[ 0 ] && Q_stristr( sndname, prule->szdir ) )
+ {
+ Assert(currentDirRule == i);
+ }
+ else
+ {
+ Assert(currentDirRule != i);
+ }
+ if ( prule->classId >= 0 )
+ {
+ // rule has a valid class id and table is correct
+ Assert(prule->classId < g_cgroupclass);
+ if ( pszclassname && Q_stristr(pszclassname, g_groupclasslist[prule->classId].szclassname) )
+ {
+ Assert(classMatch[prule->classId] == true);
+ }
+ else
+ {
+ Assert(classMatch[prule->classId] == false);
+ }
+ }
+#endif
+ // this is the next matching dir for this sound, no need to search
+ // becuse the list is sorted and we visit all elements
+ if ( currentDirRule == i )
+ {
+ Assert(prule->szdir[0]);
+ currentDirRuleIndex++;
+ currentDirRule = 255;
+ if ( currentDirRuleIndex < pchan->sfx->m_mixGroupCount )
+ {
+ currentDirRule = pchan->sfx->m_mixGroupList[currentDirRuleIndex];
+ }
+ }
+ else if ( prule->szdir[ 0 ] )
+ {
+ fmatch = false; // substring doesn't match, keep looking
+ }
+
+ // check class name
+
+ if ( fmatch && prule->classId >= 0 )
+ {
+ fmatch = classMatch[prule->classId];
+ }
+
+ // check channel type
+
+ if ( fmatch && prule->chantype >= 0)
+ {
+ if ( pchan->entchannel != prule->chantype )
+ fmatch = false; // channel type doesn't match, keep looking
+ }
+
+ // check sndlvlmin/max
+
+ if ( fmatch && prule->soundlevel_min >= 0)
+ {
+ if ( soundlevel < prule->soundlevel_min )
+ fmatch = false; // soundlevel is less than min, keep looking
+ }
+
+ if ( fmatch && prule->soundlevel_max >= 0)
+ {
+ if ( soundlevel > prule->soundlevel_max )
+ fmatch = false; // soundlevel is greater than max, keep looking
+ }
+
+ if ( fmatch )
+ {
+ pchan->mixgroups[cmixgroups] = prule->mixgroupid;
+ cmixgroups++;
+ if (cmixgroups >= 8)
+ return; // too many matches, stop looking
+ }
+
+ if (fmatch && snd_showclassname.GetInt() >= 2)
+ {
+ // show all mixgroups for this sound
+ if (cmixgroups == 1)
+ {
+ DevMsg("\n%s:%s: ", g_szsoundmixer_cur, sndname);
+ }
+ if (prule->szmixgroup[0])
+ {
+ // int rgmixgroupid[8];
+ // for (int i = 0; i < 8; i++)
+ // rgmixgroupid[i] = -1;
+ // rgmixgroupid[0] = prule->mixgroupid;
+ // float vol = MXR_GetVolFromMixGroup( rgmixgroupid );
+ // DevMsg("%s(%1.2f) ", prule->szmixgroup, vol);
+ DevMsg("%s ", prule->szmixgroup);
+ }
+ }
+ }
+}
+
+struct debug_showvols_t
+{
+ char *psz; // group name
+ int mixgroupid; // groupid
+ float vol; // group volume
+ float totalvol; // total volume of all sounds playing in this group
+};
+
+
+// display routine for MXR_DebugShowMixVolumes
+
+#define MXR_DEBUG_INCY (1.0/40.0) // vertical text spacing
+#define MXR_DEBUG_GREENSTART 0.3 // start position on screen of bar
+
+#define MXR_DEBUG_MAXVOL 1.0 // max volume scale
+#define MXR_DEBUG_REDLIMIT 1.0 // volume limit into yellow
+#define MXR_DEBUG_YELLOWLIMIT 0.7 // volume limit into red
+
+#define MXR_DEBUG_VOLSCALE 48 // length of graph in characters
+#define MXR_DEBUG_CHAR '-' // bar character
+
+extern ConVar dsp_volume;
+int g_debug_mxr_displaycount = 0;
+
+void MXR_DebugGraphMixVolumes( debug_showvols_t *groupvols, int cgroups)
+{
+ float flXpos, flYpos, flXposBar, duration;
+ int r,g,b,a;
+ int rb, gb, bb, ab;
+ flXpos = 0;
+ flYpos = 0;
+ char text[128];
+ char bartext[MXR_DEBUG_VOLSCALE*3];
+
+ duration = 0.01;
+
+ g_debug_mxr_displaycount++;
+
+ if (!(g_debug_mxr_displaycount % 10))
+ return; // only display every 10 frames
+
+
+ r = 96; g = 86; b = 226; a = 255; ab = 255;
+
+ // show volume, dsp_volume
+
+ Q_snprintf( text, 128, "Game Volume: %1.2f", volume.GetFloat());
+ CDebugOverlay::AddScreenTextOverlay(flXpos, flYpos, duration, r, g, b,a, text);
+ flYpos += MXR_DEBUG_INCY;
+
+ Q_snprintf( text, 128, "DSP Volume: %1.2f", dsp_volume.GetFloat());
+ CDebugOverlay::AddScreenTextOverlay(flXpos, flYpos, duration, r, g, b,a, text);
+ flYpos += MXR_DEBUG_INCY;
+
+ for (int i = 0; i < cgroups; i++)
+ {
+ // r += 64; g += 64; b += 16;
+
+ r = r % 255; g = g % 255; b = b % 255;
+
+ Q_snprintf( text, 128, "%s: %1.2f (%1.2f)", groupvols[i].psz,
+ groupvols[i].vol * g_DuckScale, groupvols[i].totalvol * g_DuckScale);
+
+ CDebugOverlay::AddScreenTextOverlay(flXpos, flYpos, duration, r, g, b,a, text);
+
+ // draw volume bar graph
+
+ float vol = (groupvols[i].totalvol * g_DuckScale) / MXR_DEBUG_MAXVOL;
+
+ // draw first 70% green
+ float vol1 = 0.0;
+ float vol2 = 0.0;
+ float vol3 = 0.0;
+ int cbars;
+
+ vol1 = clamp(vol, 0.0f, 0.7f);
+ vol2 = clamp(vol, 0.0f, 0.95f);
+ vol3 = vol;
+
+ flXposBar = flXpos + MXR_DEBUG_GREENSTART;
+
+ if (vol1 > 0.0)
+ {
+ //flXposBar = flXpos + MXR_DEBUG_GREENSTART;
+
+ rb = 0; gb= 255; bb = 0; // green bar
+ Q_memset(bartext, 0, sizeof(bartext));
+
+ cbars = (int)((float)vol1 * (float)MXR_DEBUG_VOLSCALE);
+ cbars = clamp(cbars, 0, MXR_DEBUG_VOLSCALE*3-1);
+ Q_memset(bartext, MXR_DEBUG_CHAR, cbars);
+
+ CDebugOverlay::AddScreenTextOverlay(flXposBar, flYpos, duration, rb, gb, bb,ab, bartext);
+ }
+
+
+ // yellow bar
+ if (vol2 > MXR_DEBUG_YELLOWLIMIT)
+ {
+ rb = 255; gb = 255; bb = 0;
+ Q_memset(bartext, 0, sizeof(bartext));
+
+ cbars = (int)((float)vol2 * (float)MXR_DEBUG_VOLSCALE);
+ cbars = clamp(cbars, 0, MXR_DEBUG_VOLSCALE*3-1);
+ Q_memset(bartext, MXR_DEBUG_CHAR, cbars);
+
+ CDebugOverlay::AddScreenTextOverlay(flXposBar, flYpos, duration, rb, gb, bb,ab, bartext);
+ }
+
+ // red bar
+ if (vol3 > MXR_DEBUG_REDLIMIT)
+ {
+ //flXposBar = flXpos + MXR_DEBUG_REDSTART;
+ rb = 255; gb = 0; bb = 0;
+ Q_memset(bartext, 0, sizeof(bartext));
+
+ cbars = (int)((float)vol3 * (float)MXR_DEBUG_VOLSCALE);
+ cbars = clamp(cbars, 0, MXR_DEBUG_VOLSCALE*3-1);
+ Q_memset(bartext, MXR_DEBUG_CHAR, cbars);
+
+ CDebugOverlay::AddScreenTextOverlay(flXposBar, flYpos, duration, rb, gb, bb,ab, bartext);
+ }
+
+ flYpos += MXR_DEBUG_INCY;
+ }
+}
+
+ConVar snd_disable_mixer_duck("snd_disable_mixer_duck", "0"); // if 1, soundmixer ducking is disabled
+
+// given mix group id, return current duck volume
+
+float MXR_GetDuckVolume( int mixgroupid )
+{
+
+ if ( snd_disable_mixer_duck.GetInt() )
+ return 1.0;
+
+ Assert ( mixgroupid < g_cgrouprules );
+
+ int grouprulesid = g_mapMixgroupidToGrouprulesid[mixgroupid];
+
+ // if this mixgroup is not ducked, return 1.0
+
+ if ( !g_grouprules[grouprulesid].is_ducked )
+ return 1.0;
+
+ // return current duck value for this group, scaled by current fade in/out ramp
+
+ return g_grouprules[grouprulesid].duck_ramp_val;
+
+}
+
+#define SND_DUCKER_UPDATETIME 0.1 // seconds to wait between ducker updates
+
+double g_mxr_ducktime = 0.0; // time of last update to ducker
+
+// Get total volume currently playing in all groups,
+// process duck volumes for all groups
+// Call once per frame - updates occur at 10hz
+
+void MXR_UpdateAllDuckerVolumes( void )
+{
+ if ( snd_disable_mixer_duck.GetInt() )
+ return;
+
+ // check timer since last update, only update at 10hz
+
+ int i;
+ double dtime = g_pSoundServices->GetHostTime();
+
+ // don't update until timer expires
+
+ if (fabs(dtime - g_mxr_ducktime) < SND_DUCKER_UPDATETIME)
+ return;
+
+ g_mxr_ducktime = dtime;
+
+ // clear out all total volume values for groups
+
+ for ( i = 0; i < g_cgrouprules; i++)
+ g_grouprules[i].total_vol = 0.0;
+
+ // for every channel in a mix group which can cause ducking:
+ // get total volume, store total in grouprule:
+
+ CChannelList list;
+ int ch_idx;
+
+ channel_t *pchan;
+ bool b_found_ducked_channel = false;
+
+ g_ActiveChannels.GetActiveChannels( list );
+
+ for ( i = 0; i < list.Count(); i++ )
+ {
+ ch_idx = list.GetChannelIndex(i);
+ pchan = &channels[ch_idx];
+
+ if (pchan->last_vol > 0.0)
+ {
+ // account for all mix groups this channel belongs to...
+
+ for (int j = 0; j < 8; j++)
+ {
+ int imixgroup = pchan->mixgroups[j];
+
+ if (imixgroup < 0)
+ continue;
+
+ int grouprulesid = g_mapMixgroupidToGrouprulesid[imixgroup];
+
+ if (g_grouprules[grouprulesid].causes_ducking)
+ g_grouprules[grouprulesid].total_vol += pchan->last_vol;
+
+ if (g_grouprules[grouprulesid].is_ducked)
+ b_found_ducked_channel = true;
+ }
+ }
+ }
+
+ // if no channels playing which may be ducked, do nothing
+
+ if ( !b_found_ducked_channel )
+ return;
+
+ // for all groups that can be ducked:
+ // see if a higher priority sound group has a volume > threshold,
+ // if so, then duck this group by setting duck_target_vol to duck_target_pct.
+ // if no sound group is causing ducking in this group, reset duck_target_vol to 1.0
+
+ for (i = 0; i < g_cgrouprules; i++)
+ {
+ if (g_grouprules[i].is_ducked)
+ {
+ int priority = g_grouprules[i].priority;
+
+ float duck_volume = 1.0; // clear to 1.0 if no channel causing ducking
+
+ // make sure we interact appropriately with global voice ducking...
+ // if global voice ducking is active, skip sound group ducking and just set duck_volume target to 1.0
+
+ if ( g_DuckScale >= 1.0 )
+ {
+ // check all sound groups for higher priority duck trigger
+
+ for (int j = 0; j < g_cgrouprules; j++)
+ {
+ if (g_grouprules[j].priority > priority &&
+ g_grouprules[j].causes_ducking &&
+ g_grouprules[j].total_vol > g_grouprules[j].ducker_threshold)
+ {
+ // a higher priority group is causing this group to be ducked
+ // set duck volume target to the ducked group's duck target percent
+ // and break
+
+ duck_volume = g_grouprules[i].duck_target_pct;
+
+ // UNDONE: to prevent edge condition caused by crossing threshold, may need to have secondary
+ // UNDONE: timer which allows ducking at 0.2 hz
+
+ break;
+ }
+ }
+ }
+
+ g_grouprules[i].duck_target_vol = duck_volume;
+ }
+ }
+
+ // update all ducker ramps if current duck value is not target
+ // if ramp is greater than duck_volume, approach at 'attack rate'
+ // if ramp is less than duck_volume, approach at 'decay rate'
+
+ for (i = 0; i < g_cgrouprules; i++)
+ {
+ float target = g_grouprules[i].duck_target_vol;
+ float current = g_grouprules[i].duck_ramp_val;
+
+ if (g_grouprules[i].is_ducked && (current != target))
+ {
+
+ float ramptime = target < current ? snd_duckerattacktime.GetFloat() : snd_duckerreleasetime.GetFloat();
+
+ // delta is volume change per update (we can do this
+ // since we run at an approximate fixed update rate of 10hz)
+
+ float delta = (1.0 - g_grouprules[i].duck_target_pct);
+
+ delta *= ( SND_DUCKER_UPDATETIME / ramptime );
+
+ if (current > target)
+ delta = -delta;
+
+ // update ramps
+
+ current += delta;
+
+ if (current < target && delta < 0)
+ current = target;
+ if (current > target && delta > 0)
+ current = target;
+
+ g_grouprules[i].duck_ramp_val = current;
+ }
+ }
+
+}
+
+ConVar snd_showmixer("snd_showmixer", "0"); // set to 1 to show mixer every frame
+
+// show the current soundmixer output
+
+void MXR_DebugShowMixVolumes( void )
+{
+ if (snd_showmixer.GetInt() == 0)
+ return;
+
+ // for the current soundmixer:
+ // make a totalvolume bucket for each mixgroup type in the soundmixer.
+ // for every active channel, add its spatialized volume to
+ // totalvolume bucket for that channel's selected mixgroup
+
+ // display all mixgroup/volume/totalvolume values as horizontal bars
+
+ debug_showvols_t groupvols[CMXRGROUPMAX];
+
+ int i;
+ int cgroups = 0;
+
+ if (g_isoundmixer < 0)
+ {
+ DevMsg("No sound mixer selected!");
+ return;
+ }
+
+ soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
+
+ // for every entry in mapMixgroupidToValue which is not -1,
+ // set up groupvols
+
+ for (i = 0; i < CMXRGROUPMAX; i++)
+ {
+ if (pmixer->mapMixgroupidToValue[i] >= 0)
+ {
+ groupvols[cgroups].mixgroupid = i;
+ groupvols[cgroups].psz = MXR_GetGroupnameFromId( i );
+ groupvols[cgroups].totalvol = 0.0;
+ groupvols[cgroups].vol = pmixer->mapMixgroupidToValue[i];
+ cgroups++;
+ }
+ }
+
+ // for every active channel, get its volume and
+ // the selected mixgroupid, add to groupvols totalvol
+
+ CChannelList list;
+ int ch_idx;
+ channel_t *pchan;
+
+ g_ActiveChannels.GetActiveChannels( list );
+
+ for ( i = 0; i < list.Count(); i++ )
+ {
+ ch_idx = list.GetChannelIndex(i);
+ pchan = &channels[ch_idx];
+ if (pchan->last_vol > 0.0)
+ {
+ // find entry in groupvols
+ for (int j = 0; j < CMXRGROUPMAX; j++)
+ {
+ if (pchan->last_mixgroupid == groupvols[j].mixgroupid)
+ {
+ groupvols[j].totalvol += pchan->last_vol;
+ break;
+ }
+ }
+ }
+ }
+
+ // groupvols is now fully initialized - just display it
+
+ MXR_DebugGraphMixVolumes( groupvols, cgroups);
+}
+
+#ifdef _DEBUG
+
+// set the named mixgroup volume to vol for the current soundmixer
+static void MXR_DebugSetMixGroupVolume( const CCommand &args )
+{
+ if ( args.ArgC() != 3 )
+ {
+ DevMsg("Parameters: mix group name, volume");
+ return;
+ }
+
+ const char *szgroupname = args[1];
+ float vol = atof( args[2] );
+
+ int imixgroup = MXR_GetMixgroupFromName( szgroupname );
+
+ if ( g_isoundmixer < 0 )
+ return;
+
+ soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
+
+ pmixer->mapMixgroupidToValue[imixgroup] = vol;
+}
+
+#endif //_DEBUG
+
+// given array of groupids (ie: the sound is in these groups),
+// return a mix volume.
+
+// return first mixgroup id in the provided array
+// which maps to a non -1 volume value for this
+// sound mixer
+
+float MXR_GetVolFromMixGroup( int rgmixgroupid[8], int *plast_mixgroupid )
+{
+
+ // if no soundmixer currently set, return 1.0 volume
+
+ if (g_isoundmixer < 0)
+ {
+ *plast_mixgroupid = 0;
+ return 1.0;
+ }
+
+ float duckgain = 1.0;
+
+ if (g_csoundmixers)
+ {
+ soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
+
+ if (pmixer)
+ {
+ // search mixgroupid array, return first match (non -1)
+
+ for (int i = 0; i < 8; i++)
+ {
+ int imixgroup = rgmixgroupid[i];
+
+ if (imixgroup < 0)
+ continue;
+
+ // save lowest duck gain value for any of the mix groups this sound is in
+
+ float duckgain_new = MXR_GetDuckVolume( imixgroup );
+
+ if ( duckgain_new < duckgain)
+ duckgain = duckgain_new;
+
+ Assert(imixgroup < CMXRGROUPMAX);
+
+ // return first mixgroup id in the passed in array
+ // that maps to a non -1 volume value for this
+ // sound mixer
+
+ if ( pmixer->mapMixgroupidToValue[imixgroup] >= 0)
+ {
+ *plast_mixgroupid = imixgroup;
+
+ // get gain due to mixer settings
+
+ float gain = pmixer->mapMixgroupidToValue[imixgroup];
+
+ // modify gain with ducker settings for this group
+
+ return gain * duckgain;
+ }
+ }
+ }
+ }
+
+ *plast_mixgroupid = 0;
+ return duckgain;
+}
+
+// get id of mixgroup name
+
+int MXR_GetMixgroupFromName( const char *pszgroupname )
+{
+ // scan group rules for mapping from name to id
+ if ( !pszgroupname )
+ return -1;
+
+ if ( Q_strlen(pszgroupname) == 0 )
+ return -1;
+
+ for (int i = 0; i < g_cgrouprules; i++)
+ {
+ if ( !Q_stricmp(g_grouprules[i].szmixgroup, pszgroupname ) )
+ return g_grouprules[i].mixgroupid;
+ }
+
+ return -1;
+}
+
+// get mixgroup name from id
+char *MXR_GetGroupnameFromId( int mixgroupid)
+{
+ // scan group rules for mapping from name to id
+ if (mixgroupid < 0)
+ return NULL;
+
+ for (int i = 0; i < g_cgrouprules; i++)
+ {
+ if ( g_grouprules[i].mixgroupid == mixgroupid)
+ return g_grouprules[i].szmixgroup;
+ }
+
+ return NULL;
+}
+
+// assign a unique mixgroup id to each unique named mix group
+// within grouprules. Note: all mixgroupids in grouprules must be -1
+// when this routine starts.
+
+void MXR_AssignGroupIds( void )
+{
+ int cmixgroupid = 0;
+
+ for (int i = 0; i < g_cgrouprules; i++)
+ {
+ int mixgroupid = MXR_GetMixgroupFromName( g_grouprules[i].szmixgroup );
+
+ if (mixgroupid == -1)
+ {
+ // groupname is not yet assigned, provide a unique mixgroupid.
+
+ g_grouprules[i].mixgroupid = cmixgroupid;
+
+ // save reverse mapping, from mixgroupid to the first grouprules entry for this name
+
+ g_mapMixgroupidToGrouprulesid[cmixgroupid] = i;
+
+ cmixgroupid++;
+ }
+ }
+}
+
+int MXR_AddClassname( const char *pName )
+{
+ char szclassname[CMXRNAMEMAX];
+ Q_strncpy( szclassname, pName, CMXRNAMEMAX );
+ for ( int i = 0; i < g_cgroupclass; i++ )
+ {
+ if ( !Q_stricmp( szclassname, g_groupclasslist[i].szclassname ) )
+ return i;
+ }
+ if ( g_cgroupclass >= CMXRCLASSMAX )
+ {
+ Assert(g_cgroupclass < CMXRCLASSMAX);
+ return -1;
+ }
+ Q_memcpy(g_groupclasslist[g_cgroupclass].szclassname, pName, min((size_t)CMXRNAMEMAX-1, strlen(pName)));
+ g_cgroupclass++;
+ return g_cgroupclass-1;
+}
+
+#define CHAR_LEFT_PAREN '{'
+#define CHAR_RIGHT_PAREN '}'
+
+// load group rules and sound mixers from file
+
+bool MXR_LoadAllSoundMixers( void )
+{
+ // init soundmixer globals
+
+ g_isoundmixer = -1;
+ g_szsoundmixer_cur[0] = 0;
+
+ g_csoundmixers = 0; // total number of soundmixers found
+ g_cgrouprules = 0; // total number of group rules found
+
+ Q_memset(g_soundmixers, 0, sizeof(g_soundmixers));
+ Q_memset(g_grouprules, 0, sizeof(g_grouprules));
+
+ // load file
+
+ // build rules
+
+ // build array of sound mixers
+
+ char szFile[MAX_OSPATH];
+ const char *pstart;
+ bool bResult = false;
+ char *pbuffer;
+
+ Q_snprintf( szFile, sizeof( szFile ), "scripts/soundmixers.txt" );
+
+ pbuffer = (char *)COM_LoadFile( szFile, 5, NULL ); // Use malloc - free at end of this routine
+ if ( !pbuffer )
+ {
+ Error( "MXR_LoadAllSoundMixers: unable to open '%s'\n", szFile );
+ return bResult;
+ }
+
+ pstart = pbuffer;
+
+ // first pass: load g_grouprules[]
+
+ // starting at top of file,
+ // scan for first '{', skipping all comment lines
+ // get strings for: groupname, directory, classname, chan, sndlvl_min, sndlvl_max
+ // convert chan to CHAN_ lookup
+ // convert sndlvl_min, sndl_max to ints
+ // store all in g_grouprules, update g_cgrouprules;
+ // get next line
+ // when hit '}' we're done with grouprules
+
+ // check for first CHAR_LEFT_PAREN
+
+ while (1)
+ {
+ pstart = COM_Parse( pstart );
+
+ if ( strlen(com_token) <= 0)
+ break; // eof
+
+ if ( com_token[0] != CHAR_LEFT_PAREN )
+ continue;
+
+ break;
+ }
+
+ while (1)
+ {
+ pstart = COM_Parse( pstart );
+ if (com_token[0] == CHAR_RIGHT_PAREN)
+ break;
+
+ grouprule_t *pgroup = &g_grouprules[g_cgrouprules];
+
+ // copy mixgroup name, directory, classname
+ // if no value specified, set to 0 length string
+
+ if (com_token[0])
+ Q_memcpy(pgroup->szmixgroup, com_token, min((size_t)CMXRNAMEMAX-1, strlen(com_token)));
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ Q_memcpy(pgroup->szdir, com_token, min((size_t)CMXRNAMEMAX-1, strlen(com_token)));
+
+ pgroup->classId = -1;
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ {
+ pgroup->classId = MXR_AddClassname( com_token );
+ }
+
+ // make sure all copied strings are null terminated
+ pgroup->szmixgroup[CMXRNAMEMAX-1] = 0;
+ pgroup->szdir[CMXRNAMEMAX-1] = 0;
+
+ // lookup chan
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ {
+ if (!Q_stricmp(com_token, "CHAN_STATIC"))
+ pgroup->chantype = CHAN_STATIC;
+ else if (!Q_stricmp(com_token, "CHAN_WEAPON"))
+ pgroup->chantype = CHAN_WEAPON;
+ else if (!Q_stricmp(com_token, "CHAN_VOICE"))
+ pgroup->chantype = CHAN_VOICE;
+ else if (!Q_stricmp(com_token, "CHAN_VOICE2"))
+ pgroup->chantype = CHAN_VOICE2;
+ else if (!Q_stricmp(com_token, "CHAN_BODY"))
+ pgroup->chantype = CHAN_BODY;
+ else if (!Q_stricmp(com_token, "CHAN_ITEM"))
+ pgroup->chantype = CHAN_ITEM;
+ }
+ else
+ pgroup->chantype = -1;
+
+ // get sndlvls
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->soundlevel_min = atoi(com_token);
+ else
+ pgroup->soundlevel_min = -1;
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->soundlevel_max = atoi(com_token);
+ else
+ pgroup->soundlevel_max = -1;
+
+ // get duck priority, IsDucked, Causes_ducking, duck_target_pct
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->priority = atoi(com_token);
+ else
+ pgroup->priority = 50;
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->is_ducked = atoi(com_token);
+ else
+ pgroup->is_ducked = 0;
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->causes_ducking = atoi(com_token);
+ else
+ pgroup->causes_ducking = 0;
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->duck_target_pct = ((float)(atoi(com_token))) / 100.0f;
+ else
+ pgroup->duck_target_pct = 0.5f;
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->ducker_threshold = ((float)(atoi(com_token))) / 100.0f;
+ else
+ pgroup->ducker_threshold = 0.5f;
+
+ pgroup->duck_ramp_val = 1.0;
+ pgroup->duck_target_vol = 1.0;
+ pgroup->total_vol = 0.0;
+
+ // set mixgroup id to -1
+ pgroup->mixgroupid = -1;
+
+ // update rule count
+
+ g_cgrouprules++;
+
+ if (g_cgrouprules >= CMXRGROUPRULESMAX)
+ {
+ // UNDONE: error! too many rules
+ break;
+ }
+ }
+
+ // now process all groupids in groups, such that
+ // each mixgroup gets a unique id.
+
+ MXR_AssignGroupIds();
+
+ // now load g_soundmixers
+
+ // while not at end of file...
+ // scan for "<name>", if found save as new soundmixer name
+ // while not '}'
+ // scan for "<name>", save as groupname
+ // scan for "<num>", save as mix value
+
+ while(1)
+ {
+ pstart = COM_Parse( pstart );
+
+ if ( strlen(com_token) <= 0)
+ break; // eof
+
+ // save name in soundmixer
+
+ soundmixer_t *pmixer = &g_soundmixers[g_csoundmixers];
+
+ Q_memcpy(pmixer->szsoundmixer, com_token, min((size_t)CMXRNAMEMAX-1, strlen(com_token)));
+
+ // init all mixer values to -1.
+
+ for (int j = 0; j < CMXRGROUPMAX; j++)
+ {
+ pmixer->mapMixgroupidToValue[j] = -1.0;
+ }
+
+ // load all groupnames for this soundmixer
+
+ while (1)
+ {
+ pstart = COM_Parse( pstart );
+
+ if (com_token[0] == CHAR_LEFT_PAREN)
+ continue; // skip {
+
+ if (com_token[0] == CHAR_RIGHT_PAREN)
+ break; // finished with this sounmixer
+
+ // lookup mixgroupid for groupname
+ int mixgroupid = MXR_GetMixgroupFromName( com_token );
+ float value;
+
+ // get mix value
+ pstart = COM_Parse( pstart );
+ value = atof( com_token );
+
+ // store value for mixgroupid
+ Assert(mixgroupid <= CMXRGROUPMAX);
+
+ pmixer->mapMixgroupidToValue[mixgroupid] = value;
+ }
+
+ g_csoundmixers++;
+ if (g_csoundmixers >= CMXRSOUNDMIXERSMAX)
+ {
+ // UNDONE: error! to many sound mixers
+ break;
+ }
+ }
+
+ bResult = true;
+
+// loadmxr_exit:
+ free( pbuffer );
+ return bResult;
+}
+
+void MXR_ReleaseMemory( void )
+{
+ // free all resources
+}
+
+float S_GetMono16Samples( const char *pszName, CUtlVector< short >& sampleList )
+{
+ CSfxTable *pSfx = S_PrecacheSound( PSkipSoundChars( pszName ) );
+ if ( !pSfx )
+ return 0.0f;
+
+ CAudioSource *pWave = pSfx->pSource;
+ if ( !pWave )
+ return 0.0f;
+
+ int nType = pWave->GetType();
+ if ( nType != CAudioSource::AUDIO_SOURCE_WAV )
+ return 0.0f;
+
+ CAudioMixer *pMixer = pWave->CreateMixer();
+ if ( !pMixer )
+ return 0.0f;
+
+ float duration = AudioSource_GetSoundDuration( pSfx );
+
+ // Determine start/stop positions
+ int totalsamples = (int)( duration * pWave->SampleRate() );
+ if ( totalsamples <= 0 )
+ return 0;
+
+ bool bStereo = pWave->IsStereoWav();
+ int mix_sample_size = pMixer->GetMixSampleSize();
+ int nNumChannels = bStereo ? 2 : 1;
+
+ char *pData = NULL;
+
+ int pos = 0;
+ int remaining = totalsamples;
+ while ( remaining > 0 )
+ {
+ int blockSize = min( remaining, 1000 );
+
+ char copyBuf[AUDIOSOURCE_COPYBUF_SIZE];
+ int copied = pWave->GetOutputData( (void **)&pData, pos, blockSize, copyBuf );
+ if ( !copied )
+ {
+ break;
+ }
+
+ remaining -= copied;
+ pos += copied;
+
+ // Now get samples out of output data
+ switch ( nNumChannels )
+ {
+ default:
+ case 1:
+ {
+ for ( int i = 0; i < copied; ++i )
+ {
+ int offset = i * mix_sample_size;
+
+ short sample = 0;
+ if ( mix_sample_size == 1 )
+ {
+ char s = *( char * )( pData + offset );
+ // Upscale it to fit into a short
+ sample = s << 8;
+ }
+ else if ( mix_sample_size == 2 )
+ {
+ sample = *( short * )( pData + offset );
+ }
+ else if ( mix_sample_size == 4 )
+ {
+ // Not likely to have 4 bytes mono!!!
+ Assert( 0 );
+
+ int s = *( int * )( pData + offset );
+ sample = s >> 16;
+ }
+ else
+ {
+ Assert( 0 );
+ }
+
+ sampleList.AddToTail( sample );
+ }
+ }
+ break;
+
+ case 2:
+ {
+ for ( int i = 0; i < copied; ++i )
+ {
+ int offset = i * mix_sample_size;
+
+ short left = 0;
+ short right = 0;
+
+ if ( mix_sample_size == 1 )
+ {
+ // Not possible!!!, must be at least 2 bytes!!!
+ Assert( 0 );
+
+ char v = *( char * )( pData + offset );
+ left = right = ( v << 8 );
+ }
+ else if ( mix_sample_size == 2 )
+ {
+ // One byte per channel
+ left = (short)( ( *(char *)( pData + offset ) ) << 8 );
+ right = (short)( ( *(char *)( pData + offset + 1 ) ) << 8 );
+ }
+ else if ( mix_sample_size == 4 )
+ {
+ // 2 bytes per channel
+ left = *( short * )( pData + offset );
+ right = *( short * )( pData + offset + 2 );
+ }
+ else
+ {
+ Assert( 0 );
+ }
+
+ short sample = ( left + right ) >> 1;
+ sampleList.AddToTail( sample );
+ }
+ }
+ break;
+ }
+ }
+
+ delete pMixer;
+
+ return duration;
+}