//========= 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 ( " \n Active 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_minimp3 " ) ;
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 ;
}