Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2751 lines
81 KiB

//===== Copyright � 1996-2005, 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 "sound.h"
#include "client_class.h"
#include "icliententitylist.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"
#include "snd_dma.h"
#include "snd_mixgroups.h"
#if defined( _X360 )
#include "xbox/xbox_console.h"
#include "xmp.h"
#include "avi/ibik.h"
extern IBik *bik;
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern ConVar dsp_volume;
// extern ConVar volume;
extern ConVar snd_duckerattacktime;
extern ConVar snd_duckerreleasetime;
//--------------------------------------------------------------------------------------------------------------
// 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;
int g_cmixlayers = 0;
// these are temporary until I can put them into the mixer proper
ConVar snd_mixerMasterLevel("snd_mixer_master_level", "1.0", FCVAR_CHEAT );
ConVar snd_mixerMasterDSP("snd_mixer_master_dsp", "1.0", FCVAR_CHEAT );
ConVar snd_showclassname("snd_showclassname", "0", FCVAR_CHEAT ); // 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
ConVar snd_list( "snd_list", "", FCVAR_CHEAT ); // lists all sounds played that match substring filter arg. "" = none, "*" = all
ConVar snd_showmixer("snd_showmixer", "0", FCVAR_CHEAT ); // set to 1 to show mixer every frame
static ConVar snd_disable_mixer_solo("snd_disable_mixer_solo", "0", FCVAR_CHEAT ); // for finding soloing bugs
//------------------------------------------------------------------------------
//
// 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 128 // up to n mixgroups
#define CMXRGROUPRULESMAX (CMXRGROUPMAX + 16) // max number of group rules
#define CMXRSOUNDMIXERSMAX 32 // up to n sound mixers per project
#define CMXRMIXLAYERSMAX 16 // up to n mix layers per project
// mix groups - these equivalent to submixes on an audio mixer
#define CMXRMATCHMAX 8
// 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
short is_ducked; // if 1, sound group is ducked by all higher priority 'causes_duck" sounds
short is_voice;
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
float trigger_vol; // total volume of all sounds in this group, use for triggers
// 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
{
float mixAmount;
char szsoundmixer[CMXRNAMEMAX]; // name of this soundmixer
float ALIGN128 mapMixgroupidToVolume[CMXRGROUPMAX]; // sparse array of mix group volume values for this soundmixer
float ALIGN128 mapMixgroupidToLevel[CMXRGROUPMAX]; // sparse array of mix group volume values for this soundmixer
float ALIGN128 mapMixgroupidToDsp[CMXRGROUPMAX]; // sparse array of mix group volume values for this soundmixer
float ALIGN128 mapMixgroupidToSolo[CMXRGROUPMAX]; // sparse array of mix group solo values for this soundmixer
float ALIGN128 mapMixgroupidToMute[CMXRGROUPMAX]; // sparse array of mix group mute values for this soundmixer
};
#define CMXRTRIGGEREDLAYERMAX 16
/*struct layertrigger_t
{
int cmixlayers; // number of layers
int imixlayer[CMXRTRIGGEREDLAYERMAX]; // triggering mix group
float fthreshold[CMXRTRIGGEREDLAYERMAX]; //
float fmixamount[CMXRTRIGGEREDLAYERMAX]; //
float fattack[CMXRTRIGGEREDLAYERMAX]; //
float frelease[CMXRTRIGGEREDLAYERMAX]; //
};*/
struct layertrigger_t
{
bool bhastrigger;
bool bistrigger[CMXRGROUPMAX];
float fthreshold[CMXRGROUPMAX]; //
float fmixamount[CMXRGROUPMAX]; //
float fattack[CMXRGROUPMAX]; //
float frelease[CMXRGROUPMAX]; //
};
float g_soloActive = 0.0; // are any soundmixers solo'd?
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
soundmixer_t g_mixlayers[CMXRMIXLAYERSMAX]; // all mix layers
soundmixer_t g_mastermixlayer;
grouprule_t g_grouprules[CMXRGROUPRULESMAX]; // all rules for determining mix group membership
layertrigger_t g_layertriggers[CMXRMIXLAYERSMAX]; //
// 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;
}
}
}
static void MXR_AccumulateMasterMixLayer(void)
{
// we are doing a weighted average in the case of multiple entries,
// but we only want it averaged by the number of entries per mix group
// if there is no mix group entry the defaults give zero difference
// this whole thing could be optimized for memory but it's insignificant
float totalAmount[CMXRGROUPMAX];
for (int j = 0; j < CMXRGROUPMAX; j++)
{
// defaults
g_mastermixlayer.mapMixgroupidToVolume[j] = 1.0;
g_mastermixlayer.mapMixgroupidToLevel[j] = 1.0;
g_mastermixlayer.mapMixgroupidToDsp[j] = 1.0;
g_mastermixlayer.mapMixgroupidToSolo[j] = 0.0;
g_mastermixlayer.mapMixgroupidToMute[j] = 0.0;
totalAmount[j] = 0.0;
for (int i = 0; i < CMXRMIXLAYERSMAX; i++)
{
// mix layers
soundmixer_t *pmixlayer = &g_mixlayers[i];
// volume entry < 0 = no entry
if(pmixlayer->mapMixgroupidToVolume[j] >= 0.0)
totalAmount[j] += pmixlayer->mixAmount;
}
}
// using the accumulated "amounts" we can do a weighted average of the actual layer values
for (int i = 0; i < CMXRMIXLAYERSMAX; i++)
{
// mix layers
soundmixer_t *pmixlayer = &g_mixlayers[i];
for (int j = 0; j < CMXRGROUPMAX; j++)
{
if(!(totalAmount[j] > 0.0))
continue;
float amount = pmixlayer->mixAmount * (pmixlayer->mixAmount / totalAmount[j]);
// -1 entry volume = no entry
if(pmixlayer->mapMixgroupidToVolume[j] < 0.0)
continue;
g_mastermixlayer.mapMixgroupidToVolume[j] = clamp(g_mastermixlayer.mapMixgroupidToVolume[j] + ((pmixlayer->mapMixgroupidToVolume[j] - 1.0) * amount), 0.0, 1.0);
g_mastermixlayer.mapMixgroupidToLevel[j] = clamp(g_mastermixlayer.mapMixgroupidToLevel[j] + ((pmixlayer->mapMixgroupidToLevel[j] - 1.0) * amount), 0.0, 1.0);
g_mastermixlayer.mapMixgroupidToDsp[j] = clamp(g_mastermixlayer.mapMixgroupidToDsp[j] + ((pmixlayer->mapMixgroupidToDsp[j] - 1.0) * amount), 0.0, 1.0);
g_mastermixlayer.mapMixgroupidToSolo[j] = clamp(g_mastermixlayer.mapMixgroupidToSolo[j] + (pmixlayer->mapMixgroupidToSolo[j] * amount), 0.0, 1.0);
g_mastermixlayer.mapMixgroupidToMute[j] = clamp(g_mastermixlayer.mapMixgroupidToMute[j] + (pmixlayer->mapMixgroupidToMute[j] * amount), 0.0, 1.0);
}
}
}
#if defined( _X360 )
// 360 SIMD version of function above.
// could be squeezed a little more with some hardcoded registers.
static void MXR_AccumulateMasterMixLayerVMX(void)
{
// we are doing a weighted average in the case of multiple entries,
// but we only want it averaged by the number of entries per mix group
// if there is no mix group entry the defaults give zero difference.
// make sure these constants are divisible by sixteen floats (the optimizations
// below depend on that, but they can be rewritten if necessary)
COMPILE_TIME_ASSERT( CMXRGROUPMAX % 16 == 0 );
COMPILE_TIME_ASSERT( CMXRMIXLAYERSMAX % 16 == 0 );
// g_mastermixlayer must be simd-aligned.
AssertMsg( (((unsigned int)((char *)g_mastermixlayer.mapMixgroupidToVolume)) & 0x7F) == 0 &&
(((unsigned int)((char *)g_mastermixlayer.mapMixgroupidToLevel)) & 0x7F) == 0 &&
(((unsigned int)((char *)g_mastermixlayer.mapMixgroupidToDsp)) & 0x7F) == 0 &&
(((unsigned int)((char *)g_mastermixlayer.mapMixgroupidToSolo)) & 0x7F) == 0 &&
(((unsigned int)((char *)g_mastermixlayer.mapMixgroupidToMute)) & 0x7F) == 0,
"Float vectors in g_mastermixlayer not 128-byte aligned!" );
// // Initialize cache.
// The results will be written into the g_mastermixlayer array; first, zero it out
// to haul it into cache, then initialize.
// We do the first output layer here, but the rest are interleaved with the
// layer total weight computation below (to spread out the memory bandwidth)
//for ( int mixgroup = 0 ; mixgroup < CMXRGROUPMAX ; mixgroup += 16 )
{
const int mixgroup = 0;
// use dcbz, which brings memory into cache,
// but also zeroes it out -- that way you don't
// have to wait for it to actually load; you
// just mark it dirty.
// __dcbz128( int offset, void * base );
__dcbz128( 0, g_mastermixlayer.mapMixgroupidToVolume + mixgroup );
__dcbz128( 0, g_mastermixlayer.mapMixgroupidToLevel + mixgroup );
__dcbz128( 0, g_mastermixlayer.mapMixgroupidToDsp + mixgroup );
__dcbz128( 0, g_mastermixlayer.mapMixgroupidToSolo + mixgroup );
__dcbz128( 0, g_mastermixlayer.mapMixgroupidToMute + mixgroup );
}
const fltx4 Ones = Four_Ones;
const fltx4 Zeroes = Four_Zeros;
float ALIGN128 totalAmount[CMXRGROUPMAX];
// // compute the total weights for each mix layer.
// we do one loop by hand, and then iterate the rest
// int layer = 0 -- implicit initialization of total to zero
{
soundmixer_t * RESTRICT pmixlayer = &g_mixlayers[ 0 ];
soundmixer_t * RESTRICT pNextLayer = &g_mixlayers[ 1 ]; // prefetch the next layer
AssertMsg( (((unsigned int) ((char *)(&pmixlayer->mapMixgroupidToVolume))) & 0x07) == 0,
"Float members of mixlayers are not SIMD-aligned." );
fltx4 mixAmount = ReplicateX4(&pmixlayer->mixAmount); // pull the mixamount into all four of a fltx4
for ( int group = 0 ; group < CMXRGROUPMAX ; group+=4 ) // do groups four at a time
{
if ( (group & 0x07) == 0 )
{
__dcbt( 0, &pNextLayer->mapMixgroupidToVolume[group] );
// blow out the output vectors
__dcbz128( 0, g_mastermixlayer.mapMixgroupidToVolume + group );
__dcbz128( 0, g_mastermixlayer.mapMixgroupidToLevel + group );
__dcbz128( 0, g_mastermixlayer.mapMixgroupidToDsp + group );
__dcbz128( 0, g_mastermixlayer.mapMixgroupidToSolo + group );
__dcbz128( 0, g_mastermixlayer.mapMixgroupidToMute + group );
}
fltx4 mgidToVolume = LoadAlignedSIMD( &pmixlayer->mapMixgroupidToVolume[group] );
fltx4 total;
fltx4 mgidToVolumeIsGreaterThanZero = CmpGeSIMD( mgidToVolume, Zeroes );
// if greater than zero, accumulate the mixamount into TotalAmount.
// volume entry < 0 means do not accumulate.
total = MaskedAssign( mgidToVolumeIsGreaterThanZero, // if vol >=0 ..
mixAmount, // total = mixamount
Zeroes ); // total = 0
// save total back out
StoreAlignedSIMD( totalAmount+group, total );
}
}
for ( int layer = 1; layer < CMXRMIXLAYERSMAX ; layer++ )
{
soundmixer_t * RESTRICT pmixlayer = &g_mixlayers[ layer ];
soundmixer_t * RESTRICT pNextLayer = &g_mixlayers[ layer+1 ]; // prefetch the next layer
const fltx4 mixAmount = ReplicateX4(&pmixlayer->mixAmount); // pull the mixamount into all four of a fltx4
for ( int group = 0 ; group < CMXRGROUPMAX ; group+=16 ) // do groups SIXTEEN at a time (to hide the long VMX pipeline)
{
__dcbt( 0, &pNextLayer->mapMixgroupidToVolume[group] ); // prefetch volumes for next layer
// unroll the loop a little by hand. This is different from iterating because
// an explicitly unrolled loop like this will put each element of mgidToVolume[4]
// on its own register, rather than in an array; that way we can have four operations
// in flight simultaneously.
fltx4 mgidToVolume[4];
mgidToVolume[0] = LoadAlignedSIMD( &pmixlayer->mapMixgroupidToVolume[group + 0] ); // each of these
mgidToVolume[1] = LoadAlignedSIMD( &pmixlayer->mapMixgroupidToVolume[group + 4] ); // loads takes
mgidToVolume[2] = LoadAlignedSIMD( &pmixlayer->mapMixgroupidToVolume[group + 8] ); // about 12 ops
mgidToVolume[3] = LoadAlignedSIMD( &pmixlayer->mapMixgroupidToVolume[group + 12] ); // to finish.
fltx4 total[4];
total[0] = LoadAlignedSIMD( totalAmount + group + 0 );
total[1] = LoadAlignedSIMD( totalAmount + group + 4 );
total[2] = LoadAlignedSIMD( totalAmount + group + 8 );
total[3] = LoadAlignedSIMD( totalAmount + group + 12 );
// volume entry < 0 means no entry.
fltx4 mgidToVolumeIsGreaterThanZero[4];
mgidToVolumeIsGreaterThanZero[0] = CmpGeSIMD( mgidToVolume[0], Zeroes ); // four bools.
mgidToVolumeIsGreaterThanZero[1] = CmpGeSIMD( mgidToVolume[1], Zeroes );
mgidToVolumeIsGreaterThanZero[2] = CmpGeSIMD( mgidToVolume[2], Zeroes );
mgidToVolumeIsGreaterThanZero[3] = CmpGeSIMD( mgidToVolume[3], Zeroes );
// if greater than zero, accumulate the mixamount into TotalAmount.
// volume entry < 0 means do not accumulate.
total[0] = MaskedAssign( mgidToVolumeIsGreaterThanZero[0], // if vol >=0 ..
AddSIMD( total[0], mixAmount ), // total+= mixamount
total[0] ); // total = total
total[1] = MaskedAssign( mgidToVolumeIsGreaterThanZero[1], // if vol >=0 ..
AddSIMD( total[1], mixAmount ), // total+= mixamount
total[1] ); // total = total
total[2] = MaskedAssign( mgidToVolumeIsGreaterThanZero[2], // if vol >=0 ..
AddSIMD( total[2], mixAmount ), // total+= mixamount
total[2] ); // total = total
total[3] = MaskedAssign( mgidToVolumeIsGreaterThanZero[3], // if vol >=0 ..
AddSIMD( total[3], mixAmount ), // total+= mixamount
total[3] ); // total = total
// save total back out
StoreAlignedSIMD( totalAmount + group + 0, total[0] );
StoreAlignedSIMD( totalAmount + group + 4, total[1] );
StoreAlignedSIMD( totalAmount + group + 8, total[2] );
StoreAlignedSIMD( totalAmount + group + 12, total[3] );
}
}
// // using the accumulated "amounts" we can do a weighted average of the actual layer values
// first compute reciprocals of all the weights. It's okay to divide by zero -- in this case
// we'll replace the reciprocal with -1, to indicate that this group should be skipped.
// we work the groups four at a time, or the individual floats sixteen at a time, because
// of the latency of the reciprocal operation.
fltx4 totalAmountRecip4s[CMXRGROUPMAX / 4];
for ( int i = 0 ; i < (CMXRGROUPMAX / 4) ; i += 4 )
{
fltx4 total[4];
const int iTimesFour = i << 2; // shift is cheap
total[0] = LoadAlignedSIMD( totalAmount + iTimesFour + 0 );
total[1] = LoadAlignedSIMD( totalAmount + iTimesFour + 4 );
total[2] = LoadAlignedSIMD( totalAmount + iTimesFour + 8 );
total[3] = LoadAlignedSIMD( totalAmount + iTimesFour + 12 );
fltx4 totalRecip[4];
totalRecip[0] = ReciprocalSIMD( total[0] );
totalRecip[1] = ReciprocalSIMD( total[1] );
totalRecip[2] = ReciprocalSIMD( total[2] );
totalRecip[3] = ReciprocalSIMD( total[3] );
fltx4 totalIsGreaterThanZero[4];
totalIsGreaterThanZero[0] = CmpGtSIMD( total[0], Zeroes );
totalIsGreaterThanZero[1] = CmpGtSIMD( total[1], Zeroes );
totalIsGreaterThanZero[2] = CmpGtSIMD( total[2], Zeroes );
totalIsGreaterThanZero[3] = CmpGtSIMD( total[3], Zeroes );
totalAmountRecip4s[ i + 0 ] = MaskedAssign( totalIsGreaterThanZero[0], totalRecip[0], Four_NegativeOnes );
totalAmountRecip4s[ i + 1 ] = MaskedAssign( totalIsGreaterThanZero[1], totalRecip[1], Four_NegativeOnes );
totalAmountRecip4s[ i + 2 ] = MaskedAssign( totalIsGreaterThanZero[2], totalRecip[2], Four_NegativeOnes );
totalAmountRecip4s[ i + 3 ] = MaskedAssign( totalIsGreaterThanZero[3], totalRecip[3], Four_NegativeOnes );
// oh, and meanwhile, prefetch the first mix layer that we're going to process below.
soundmixer_t * RESTRICT pnextlayer = &g_mixlayers[0];
unsigned int offset = sizeof(float) * iTimesFour;
// __dcbt( 0, pnextlayer->mapMixgroupidToVolume + iTimesFour ); // volumes are already in cache
__dcbt( offset, pnextlayer->mapMixgroupidToLevel );
__dcbt( offset, pnextlayer->mapMixgroupidToDsp );
__dcbt( offset, pnextlayer->mapMixgroupidToSolo );
__dcbt( offset, pnextlayer->mapMixgroupidToMute );
}
// // with the reciprocals computed, now work out the mix levels.
// We cook the groups four at a time.
// do one loop to write in the initialized default values ..
{
const int layer = 0;
// mix layers
soundmixer_t * RESTRICT pmixlayer = &g_mixlayers[layer];
fltx4 mixLayerAmountSq = ReplicateX4( pmixlayer->mixAmount ); // pmixplayer->mixAmount * pmixplayer->mixAmount
mixLayerAmountSq = MulSIMD( mixLayerAmountSq, mixLayerAmountSq );
// prefetch the groups for the next layer
soundmixer_t * RESTRICT pnextlayer = &g_mixlayers[layer+1];
for ( unsigned int group = 0; group < CMXRGROUPMAX; group+=4 )
{
// once per 16 floats (128 bytes)...
if ((group & 0x07) == 0)
{
// __dcbt( 0, pnextlayer->mapMixgroupidToVolume + group ); // volumes have already been fetched
unsigned int offset = sizeof(float) * group;
__dcbt( offset, pnextlayer->mapMixgroupidToLevel );
__dcbt( offset, pnextlayer->mapMixgroupidToDsp );
__dcbt( offset, pnextlayer->mapMixgroupidToSolo );
__dcbt( offset, pnextlayer->mapMixgroupidToMute );
}
// we only write groups where the total weight > 0
// and pmixlayer->mapMixGroupidToVolume[n] >= 0
fltx4 gidToVolume = LoadAlignedSIMD( pmixlayer->mapMixgroupidToVolume + group );
fltx4 bShouldTouch;
const unsigned int groupDivFour = group >> 2;
bShouldTouch = CmpGtSIMD( totalAmountRecip4s[groupDivFour], Zeroes );
fltx4 amount = MulSIMD( mixLayerAmountSq, totalAmountRecip4s[groupDivFour] ); // pmixplayer->mixAmount * pmixplayer->mixAmount / totalAmount[group]
bShouldTouch = AndSIMD( bShouldTouch, CmpGeSIMD( gidToVolume, Zeroes ) ); //bShouldTouch[x] = totalAmountRecip4s[x] > 0 && gidToVolume[x] >=0
fltx4 gidToLevel = LoadAlignedSIMD( pmixlayer->mapMixgroupidToLevel + group );
fltx4 gidToDsp = LoadAlignedSIMD( pmixlayer->mapMixgroupidToDsp + group );
fltx4 gidToSolo = LoadAlignedSIMD( pmixlayer->mapMixgroupidToSolo + group );
fltx4 gidToMute = LoadAlignedSIMD( pmixlayer->mapMixgroupidToMute + group );
// the master mix values, which are also the output.
// start with the defaults.
fltx4 mastergidToVolume = Ones;
fltx4 mastergidToLevel = Ones;
fltx4 mastergidToDsp = Ones;
fltx4 mastergidToSolo = Zeroes;
fltx4 mastergidToMute = Zeroes;
gidToVolume = SubSIMD(gidToVolume, Ones); // pmixlayer->mapMixgroupidToVolume[j] - 1.0f
gidToLevel = SubSIMD(gidToLevel, Ones); // pmixlayer->mapMixgroupidToLevel[j] - 1.0f
gidToDsp = SubSIMD(gidToDsp, Ones); // pmixlayer->mapMixgroupidToDsp[j] - 1.0f
// let the subs cook for a little bit...
gidToSolo = MaddSIMD( amount, gidToSolo, mastergidToSolo ); // pmixlayer->mapMixgroupidToSolo[j] * amount + g_mastermixlayer.mapMixgroupidToSolo[j]
gidToMute = MaddSIMD( amount, gidToMute, mastergidToMute );
gidToVolume = MaddSIMD( amount, gidToVolume, mastergidToVolume );
gidToLevel = MaddSIMD( amount, gidToLevel, mastergidToLevel );
gidToDsp = MaddSIMD( amount, gidToDsp, mastergidToDsp );
// clamp to between zero and one
gidToSolo = ClampVectorSIMD( gidToSolo, Zeroes, Ones );
gidToMute = ClampVectorSIMD( gidToMute, Zeroes, Ones );
gidToVolume = ClampVectorSIMD( gidToVolume, Zeroes, Ones );
gidToLevel = ClampVectorSIMD( gidToLevel, Zeroes, Ones );
gidToDsp = ClampVectorSIMD( gidToDsp, Zeroes, Ones );
// write out the appropriate groups
mastergidToSolo = MaskedAssign( bShouldTouch, gidToSolo, mastergidToSolo );
mastergidToMute = MaskedAssign( bShouldTouch, gidToMute, mastergidToMute );
mastergidToVolume = MaskedAssign( bShouldTouch, gidToVolume, mastergidToVolume );
mastergidToLevel = MaskedAssign( bShouldTouch, gidToLevel, mastergidToLevel );
mastergidToDsp = MaskedAssign( bShouldTouch, gidToDsp, mastergidToDsp );
StoreAlignedSIMD( g_mastermixlayer.mapMixgroupidToSolo + group, mastergidToSolo );
StoreAlignedSIMD( g_mastermixlayer.mapMixgroupidToMute + group, mastergidToMute );
StoreAlignedSIMD( g_mastermixlayer.mapMixgroupidToVolume + group, mastergidToVolume );
StoreAlignedSIMD( g_mastermixlayer.mapMixgroupidToLevel + group, mastergidToLevel );
StoreAlignedSIMD( g_mastermixlayer.mapMixgroupidToDsp + group, mastergidToDsp );
}
}
// iterate over the remaining layers.
for ( int layer = 1; layer < CMXRMIXLAYERSMAX; layer++ )
{
// mix layers
soundmixer_t * RESTRICT pmixlayer = &g_mixlayers[layer];
fltx4 mixLayerAmountSq = ReplicateX4( &pmixlayer->mixAmount ); // pmixplayer->mixAmount * pmixplayer->mixAmount
mixLayerAmountSq = MulSIMD( mixLayerAmountSq, mixLayerAmountSq );
// prefetch the groups for the next layer
soundmixer_t * RESTRICT pnextlayer = &g_mixlayers[layer+1];
for ( unsigned int group = 0; group < CMXRGROUPMAX; group+=4 )
{
// once per 16 floats (128 bytes)...
if ((group & 0x07) == 0)
{
// __dcbt( 0, pnextlayer->mapMixgroupidToVolume + group ); // volumes already fetched.
unsigned int offset = group * sizeof(float);
__dcbt( offset, pnextlayer->mapMixgroupidToLevel );
__dcbt( offset, pnextlayer->mapMixgroupidToDsp );
__dcbt( offset, pnextlayer->mapMixgroupidToSolo );
__dcbt( offset, pnextlayer->mapMixgroupidToMute );
}
// we only write groups where the total weight > 0
// and pmixlayer->mapMixGroupidToVolume[n] >= 0
fltx4 gidToVolume = LoadAlignedSIMD( pmixlayer->mapMixgroupidToVolume + group );
fltx4 bShouldTouch;
const unsigned int groupDivFour = group >> 2;
bShouldTouch = CmpGtSIMD( totalAmountRecip4s[groupDivFour], Zeroes );
fltx4 amount = MulSIMD( mixLayerAmountSq, totalAmountRecip4s[groupDivFour] ); // pmixplayer->mixAmount * pmixplayer->mixAmount / totalAmount[group]
bShouldTouch = AndSIMD( bShouldTouch, CmpGeSIMD( gidToVolume, Zeroes ) ); //bShouldTouch[x] = totalAmountRecip4s[x] > 0 && gidToVolume[x] >=0
fltx4 gidToLevel = LoadAlignedSIMD( pmixlayer->mapMixgroupidToLevel + group );
fltx4 gidToDsp = LoadAlignedSIMD( pmixlayer->mapMixgroupidToDsp + group );
fltx4 gidToSolo = LoadAlignedSIMD( pmixlayer->mapMixgroupidToSolo + group );
fltx4 gidToMute = LoadAlignedSIMD( pmixlayer->mapMixgroupidToMute + group );
// the master mix values, which are also the output
fltx4 mastergidToVolume = LoadAlignedSIMD( g_mastermixlayer.mapMixgroupidToVolume + group );
fltx4 mastergidToLevel = LoadAlignedSIMD( g_mastermixlayer.mapMixgroupidToLevel + group );
fltx4 mastergidToDsp = LoadAlignedSIMD( g_mastermixlayer.mapMixgroupidToDsp + group );
fltx4 mastergidToSolo = LoadAlignedSIMD( g_mastermixlayer.mapMixgroupidToSolo + group );
fltx4 mastergidToMute = LoadAlignedSIMD( g_mastermixlayer.mapMixgroupidToMute + group );
gidToVolume = SubSIMD(gidToVolume, Ones); // pmixlayer->mapMixgroupidToVolume[j] - 1.0f
gidToLevel = SubSIMD(gidToLevel, Ones); // pmixlayer->mapMixgroupidToLevel[j] - 1.0f
gidToDsp = SubSIMD(gidToDsp, Ones); // pmixlayer->mapMixgroupidToDsp[j] - 1.0f
// let the subs cook for a little bit...
gidToSolo = MaddSIMD( amount, gidToSolo, mastergidToSolo ); // pmixlayer->mapMixgroupidToSolo[j] * amount + g_mastermixlayer.mapMixgroupidToSolo[j]
gidToMute = MaddSIMD( amount, gidToMute, mastergidToMute );
gidToVolume = MaddSIMD( amount, gidToVolume, mastergidToVolume );
gidToLevel = MaddSIMD( amount, gidToLevel, mastergidToLevel );
gidToDsp = MaddSIMD( amount, gidToDsp, mastergidToDsp );
// clamp to between zero and one
gidToSolo = ClampVectorSIMD( gidToSolo, Zeroes, Ones );
gidToMute = ClampVectorSIMD( gidToMute, Zeroes, Ones );
gidToVolume = ClampVectorSIMD( gidToVolume, Zeroes, Ones );
gidToLevel = ClampVectorSIMD( gidToLevel, Zeroes, Ones );
gidToDsp = ClampVectorSIMD( gidToDsp, Zeroes, Ones );
// write out the appropriate groups
mastergidToSolo = MaskedAssign( bShouldTouch, gidToSolo, mastergidToSolo );
mastergidToMute = MaskedAssign( bShouldTouch, gidToMute, mastergidToMute );
mastergidToVolume = MaskedAssign( bShouldTouch, gidToVolume, mastergidToVolume );
mastergidToLevel = MaskedAssign( bShouldTouch, gidToLevel, mastergidToLevel );
mastergidToDsp = MaskedAssign( bShouldTouch, gidToDsp, mastergidToDsp );
StoreAlignedSIMD( g_mastermixlayer.mapMixgroupidToSolo + group, mastergidToSolo );
StoreAlignedSIMD( g_mastermixlayer.mapMixgroupidToMute + group, mastergidToMute );
StoreAlignedSIMD( g_mastermixlayer.mapMixgroupidToVolume + group, mastergidToVolume );
StoreAlignedSIMD( g_mastermixlayer.mapMixgroupidToLevel + group, mastergidToLevel );
StoreAlignedSIMD( g_mastermixlayer.mapMixgroupidToDsp + group, mastergidToDsp );
}
}
}
ConVar snd_use_vmx("snd_use_vmx", "1");
#endif
// Check in advance if ANY groups are solo'd
void MXR_SetSoloActive(void)
{
#ifdef _X360
if (snd_use_vmx.GetBool())
MXR_AccumulateMasterMixLayerVMX();
else
MXR_AccumulateMasterMixLayer();
#else
MXR_AccumulateMasterMixLayer();
#endif
g_soloActive = 0.0;
if ( !snd_disable_mixer_solo.GetBool() )
{
soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
// for every entry in mapMixgroupidToSolo which is not 0
for (int i = 0; i < CMXRGROUPMAX; i++)
{
g_soloActive = MAX(g_soloActive, pmixer->mapMixgroupidToSolo[i]);
g_soloActive = MAX(g_soloActive, g_mastermixlayer.mapMixgroupidToSolo[i]);
}
}
}
ClientClass *GetClientClass( SoundSource soundsource )
{
IClientEntity *pClientEntity = NULL;
if ( entitylist )
{
pClientEntity = entitylist->GetClientEntity( soundsource );
if ( pClientEntity )
{
ClientClass *pClientClass = pClientEntity->GetClientClass();
// check npc sounds
return pClientClass;
}
}
return NULL;
}
// 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 ] && V_strstr( pDirname, prule->szdir ) )
{
pList[count] = i;
count++;
if ( count >= listMax )
return count;
}
}
return count;
}
bool MXR_IsMusicGroup( int ruleIndex )
{
if ( ruleIndex != 255 && ruleIndex >= 0 && ruleIndex < g_cgrouprules )
{
grouprule_t *prule = &g_grouprules[ruleIndex];
if ( Q_stristr(prule->szmixgroup, "music") )
return true;
}
return false;
}
// 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 )
{
grouprule_t *prule;
bool fmatch;
bool classMatch[CMXRCLASSMAX];
// init all mixgroups for channel
for ( int i = 0; i < ARRAYSIZE( pchan->mixgroups ); i++ )
{
pchan->mixgroups[i] = -1;
}
char sndname[MAX_PATH];
pchan->sfx->getname(sndname, sizeof(sndname));
// Use forward slashes here
Q_FixSlashes( sndname, '/' );
const char *pszclassname = GetClientClassname( pchan->soundsource );
if ( snd_showclassname.GetInt() == 1 && pszclassname )
{
// utility: show classname of ent making sound
DevMsg( "(%s:%s) \n", pszclassname, sndname);
}
// check for player
bool bIsPlayer = g_pSoundServices->IsPlayer( pchan->soundsource );
for ( int i = 0; i < g_cgroupclass; i++ )
{
classMatch[i] = ( pszclassname && Q_stristr( pszclassname, g_groupclasslist[i].szclassname ) ||
( bIsPlayer && !Q_strcmp( g_groupclasslist[i].szclassname, "localPlayer") ) );
}
// check all group rules for a match, save
// up to CMXRMATCHMAX matches in channel mixgroup.
int cmixgroups = 0;
if ( !pchan->sfx->m_bMixGroupsCached )
{
pchan->sfx->OnNameChanged( sndname );
}
// 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 ( int i = 0; i < g_cgrouprules; i++)
{
prule = &g_grouprules[i];
fmatch = true;
// check directory or name substring
#ifdef _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);
bool bShouldBeTrue = ( pszclassname && Q_stristr(pszclassname, g_groupclasslist[prule->classId].szclassname) ) ||
( !Q_strcmp( g_groupclasslist[prule->classId].szclassname, "localPlayer" ) && bIsPlayer );
if ( bShouldBeTrue )
{
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
}
float soundlevel = pchan->m_flSoundLevel;
// 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++;
// only print the first match
if ( cmixgroups == 1 )
{
// filtered listing of sounds
const char *filter = snd_list.GetString();
if ( filter[0] )
{
// utility: show classname of ent making sound
if ( Q_stristr( sndname, filter ))
{
DevMsg( "%s", sndname );
// show main mixgroup for this sound
mixervalues_t mValues;
int lastMixGroup;
MXR_GetVolFromMixGroup( pchan, &mValues, &lastMixGroup );
if ( prule->szmixgroup[0] )
{
DevMsg(" : %s : vol: %4.2f, sndlvl: %4.2f \n", prule->szmixgroup, mValues.volume, soundlevel);
}
}
}
}
// a member of CMXRMATCHMAX groups?
if ( cmixgroups >= CMXRMATCHMAX )
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[CMXRMATCHMAX];
// for (int i = 0; i < CMXRMATCHMAX; i++)
// rgmixgroupid[i] = -1;
// rgmixgroupid[0] = prule->mixgroupid;
// float vol = MXR_GetVolFromMixGroup( rgmixgroupid );
// DevMsg("%s(%1.2f) ", prule->szmixgroup, vol);
DevMsg("%s ", prule->szmixgroup);
}
}
}
}
ConVar snd_disable_mixer_duck("snd_disable_mixer_duck", "0", FCVAR_CHEAT ); // 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 );
Assert( mixgroupid >= 0 && mixgroupid < ARRAYSIZE(g_mapMixgroupidToGrouprulesid) );
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
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 ( int i = 0; i < g_cgrouprules; i++)
{
g_grouprules[i].total_vol = 0.0;
g_grouprules[i].trigger_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 ( int 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 < CMXRMATCHMAX; 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;
g_grouprules[grouprulesid].trigger_vol += pchan->last_vol;
if (g_grouprules[grouprulesid].is_ducked)
b_found_ducked_channel = true;
}
}
}
// we're going to hanld triggers here because it's convenient
// this is all a bit messy and should be cleaned up at some point
// layer trigger defaults
for ( int i = 0; i < CMXRMIXLAYERSMAX; i++ )
{
layertrigger_t *playertriggers = &g_layertriggers[i];
if(!playertriggers->bhastrigger)
continue;
soundmixer_t *pmixlayer = &g_mixlayers[i];
float curMixLevel = pmixlayer->mixAmount;
float maxNewLevel = 0.0;
float maxTriggerLevel = 0.0;
float maxAttack = 0.0;
float maxRelease = 0.0;
for( int j = 0; j < CMXRGROUPMAX; j++)
{
if(!playertriggers->bistrigger[j])
continue;
int grouprulesid = g_mapMixgroupidToGrouprulesid[j];
maxTriggerLevel = MAX(maxTriggerLevel, playertriggers->fmixamount[j]);
if(g_grouprules[grouprulesid].trigger_vol > playertriggers->fthreshold[j])
{
maxNewLevel = MAX(maxNewLevel, playertriggers->fmixamount[j]);
}
maxAttack = MAX(playertriggers->fattack[j], maxAttack);
maxRelease = MAX(playertriggers->frelease[j], maxRelease);
}
if(maxNewLevel != curMixLevel)
{
float ramptime = (maxNewLevel > curMixLevel) ? maxAttack : maxRelease;
// delta is volume change per update (we can do this
// since we run at an approximate fixed update rate of 10hz)
// only if we have a fade
if(ramptime > 0.0)
{
float delta = maxTriggerLevel;
delta *= ( SND_DUCKER_UPDATETIME / ramptime );
if (curMixLevel > maxNewLevel)
delta = -delta;
// update ramps
float updatedMixLevel = curMixLevel + delta;
if (updatedMixLevel < maxNewLevel && delta < 0)
updatedMixLevel = maxNewLevel;
if (updatedMixLevel > maxNewLevel && delta > 0)
updatedMixLevel = maxNewLevel;
maxNewLevel = updatedMixLevel;
}
}
pmixlayer->mixAmount = maxNewLevel;
}
// TODO: THIS IS DESIGNED FOR MULTIPLE TRIGGERS (MAX(a,b) IS PROBABLY BEST)
/* for (int i = 0; i < CMXRGROUPMAX; i++)
{
int grouprulesid = g_mapMixgroupidToGrouprulesid[i];
layertrigger_t *playertrigger = &g_layertriggers[i];
for(int j = 0; j < playertrigger->cmixlayers; j++)
{
int layergroupid = playertrigger->imixlayer[j];
if(layergroupid > -1 && layergroupid < g_cmixlayers)
{
soundmixer_t *pmixlayer = &g_mixlayers[layergroupid];
bool trig = false;
float mixAmount = 0.0;
if(g_grouprules[grouprulesid].trigger_vol > playertrigger->fthreshold[j])
{
mixAmount = playertrigger->fmixamount[j];
DevMsg("***LAYERTRIGGER!!!\n");
trig = true;
}
float ramptime = (mixAmount >= pmixlayer->mixAmount) ? playertrigger->fattack[j] : playertrigger->frelease[j];
// delta is volume change per update (we can do this
// since we run at an approximate fixed update rate of 10hz)
float delta = playertrigger->fmixamount[j];
delta *= ( SND_DUCKER_UPDATETIME / ramptime );
if (pmixlayer->mixAmount > mixAmount)
delta = -delta;
// update ramps
pmixlayer->mixAmount += delta;
if (pmixlayer->mixAmount < mixAmount && delta < 0)
pmixlayer->mixAmount = mixAmount;
if (pmixlayer->mixAmount > mixAmount && delta > 0)
pmixlayer->mixAmount = mixAmount;
if(trig)
DevMsg("%f\n", pmixlayer->mixAmount);
}
}
}
*/
// 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 (int 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 ( int 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;
}
}
}
//-----------------------------------------------------------------
//
// Setting mixer values
//
//-----------------------------------------------------------------
bool bPrintSetMixerDebug = false;
// this will set every mix group who's name contains the passed string
void S_SetIndexedMixGroupOfMixer( int imixgroup, const char *szparam, float val, soundmixer_t *pmixer, int setMixerType )
{
// TODO: need to lose these string compares for cdllint as well, make int enums!!
if(imixgroup >= 0)
{
if(!Q_stricmp("vol", szparam))
{
pmixer->mapMixgroupidToVolume[imixgroup] = val;
}
else if(!Q_stricmp("level", szparam))
{
pmixer->mapMixgroupidToLevel[imixgroup] = val;
}
else if(!Q_stricmp("dsp", szparam))
{
pmixer->mapMixgroupidToDsp[imixgroup] = val;
}
else if(!Q_stricmp("mute", szparam))
{
pmixer->mapMixgroupidToMute[imixgroup] = val;
}
else if(!Q_stricmp("solo", szparam))
{
pmixer->mapMixgroupidToSolo[imixgroup] = val;
}
else if(!Q_stricmp("mix", szparam))
{
pmixer->mixAmount = val;
}
}
}
void S_SetIndexedMixGroupOfMixer( int imixgroup, MXRMixGroupFields_t nMixGroupField, float val, soundmixer_t *pmixer )
{
if(imixgroup >= 0)
{
switch( nMixGroupField )
{
case MXR_MIXGROUP_VOL:
pmixer->mapMixgroupidToVolume[imixgroup] = val;
break;
case MXR_MIXGROUP_LEVEL:
pmixer->mapMixgroupidToLevel[imixgroup] = val;
break;
case MXR_MIXGROUP_DSP:
pmixer->mapMixgroupidToDsp[imixgroup] = val;
break;
case MXR_MIXGROUP_SOLO:
pmixer->mapMixgroupidToSolo[imixgroup] = val;
break;
case MXR_MIXGROUP_MUTE:
pmixer->mapMixgroupidToMute[imixgroup] = val;
break;
}
}
}
void S_SetMixGroupOfMixer( const char *szgroupname, const char *szparam, float val, soundmixer_t *pmixer, int setMixerType )
{
if ( !szgroupname )
return;
if ( Q_strlen(szgroupname) == 0 )
return;
// scan group rules for mapping from name to id
for (int i = 0; i < g_cgrouprules; i++)
{
// if the mix groups name contains the string we set it
if ( Q_stristr( g_grouprules[i].szmixgroup, szgroupname ) )
{
if(bPrintSetMixerDebug)
DevMsg("Setting Mixer %s: MixGroup %s: %s : %f\n", pmixer->szsoundmixer, g_grouprules[i].szmixgroup, szparam, val );
S_SetIndexedMixGroupOfMixer(g_grouprules[i].mixgroupid, szparam, val, pmixer, setMixerType );
}
}
}
// this will set every mix group who's name contains the passed string
void S_SetMixGroupOfCurrentMixer( const char *szgroupname, const char *szparam, float val, int setMixerType )
{
// get current mixer
if ( g_isoundmixer < 0 )
return;
soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
S_SetMixGroupOfMixer( szgroupname, szparam, val, pmixer, setMixerType );
}
// this will set every mix group who's name contains the passed string
void S_SetMixGroupOfMixLayer( int nMixGroupIndex, int nMixLayerIndex, MXRMixGroupFields_t nMixGroupField, float flValue )
{
soundmixer_t *pMixLayer = &g_mixlayers[ nMixLayerIndex ];
S_SetIndexedMixGroupOfMixer( nMixGroupIndex, nMixGroupField, flValue, pMixLayer );
}
int S_GetMixGroupIndex( const char *pMixGroupName )
{
int imixgroupid = MXR_GetMixgroupFromName( pMixGroupName );
if( imixgroupid < 0 || imixgroupid >= CMXRGROUPMAX )
{
DevWarning( "Error: MixGroup %s cannot be resolved!\n", pMixGroupName );
return -1;
}
return imixgroupid;
}
int MXR_GetMixLayerIndexFromName( const char *szmixlayername )
{
for (int i = 0; i < CMXRMIXLAYERSMAX; i++)
{
// sound mixers
soundmixer_t *pmixer = &g_mixlayers[i];
if ( !Q_stricmp( pmixer->szsoundmixer, szmixlayername ) )
{
return i;
}
}
return -1;
}
int S_GetMixLayerIndex(const char *szmixlayername)
{
return MXR_GetMixLayerIndexFromName(szmixlayername);
}
void S_SetMixLayerLevel(int index, float level)
{
soundmixer_t *pmixlayer = &g_mixlayers[index];
pmixlayer->mixAmount = level;
}
void S_SetMixLayerTriggerFactor( int nMixLayerIndex, int nMixGroupIndex, float flFactor )
{
Assert( nMixLayerIndex != -1 );
layertrigger_t *playertriggers = &g_layertriggers[ nMixLayerIndex ];
if( nMixGroupIndex < 0 || nMixGroupIndex >= CMXRGROUPMAX)
{
DevMsg("Error: MixGroup %i, in LayerTriggers cannot be resolved!\n", nMixGroupIndex );
return;
}
if( ! playertriggers->bistrigger[ nMixGroupIndex ] || !playertriggers->bhastrigger )
{
// error here
return;
}
playertriggers->fmixamount[ nMixGroupIndex ] = flFactor;
}
void S_SetMixLayerTriggerFactor( const char *pMixLayerName, const char *pMixGroupName, float flFactor )
{
int nMixLayerIndex = MXR_GetMixLayerIndexFromName( pMixLayerName );
int nMixGroupIndex = MXR_GetMixgroupFromName( pMixGroupName );
if( nMixGroupIndex < 0 || nMixGroupIndex >= CMXRGROUPMAX)
{
DevMsg("Error: MixGroup %s, in LayerTriggers cannot be resolved!\n", pMixGroupName );
return;
}
S_SetMixLayerTriggerFactor( nMixLayerIndex, nMixGroupIndex, flFactor );
}
//-----------------------------------------------------------------------
//
// ConCommands to set mixer values
//
//-----------------------------------------------------------------------
static void MXR_SetSoundMixer( const CCommand &args )
{
if ( args.ArgC() != 4 )
{
DevMsg("Parameters: mix group name, [vol, mute, solo], value");
return;
}
const char *szgroupname = args[1];
const char *szparam = args[2];
float val = atof( args[3] );
bPrintSetMixerDebug = true;
S_SetMixGroupOfCurrentMixer(szgroupname, szparam, val, MIXER_SET );
bPrintSetMixerDebug = false;
}
static ConCommand snd_setmixer("snd_setmixer", MXR_SetSoundMixer, "Set named Mixgroup of current mixer to mix vol, mute, solo.", FCVAR_CHEAT );
// set the named mixgroup volume to vol for the current soundmixer
static void MXR_SetMixLayer( const CCommand &args )
{
if ( args.ArgC() != 5 )
{
DevMsg("Parameters: mix group name, layer name, [vol, mute, solo], value, amount");
return;
}
const char *szlayername = args[1];
const char *szgroupname = args[2];
const char *szparam = args[3];
float val = atof( args[4] );
bPrintSetMixerDebug = true;
for( int i = 0; i < g_cmixlayers; i++)
{
soundmixer_t *pmixlayer = &g_mixlayers[i];
if(!Q_stricmp(pmixlayer->szsoundmixer, szlayername))
{
DevMsg("Setting MixLayer %s\n", pmixlayer->szsoundmixer);
S_SetMixGroupOfMixer(szgroupname, szparam, val, pmixlayer, MIXER_SET );
}
}
bPrintSetMixerDebug = false;
}
static ConCommand snd_setmixlayer("snd_setmixlayer", MXR_SetMixLayer, "Set named Mixgroup of named mix layer to mix vol, mute, solo.", FCVAR_CHEAT );
static void MXR_SetMixLayerAmount( const CCommand &args )
{
if ( args.ArgC() != 3 )
{
DevMsg("Parameters: mixer name, mix amount");
return;
}
const char *szlayername = args[1];
float val = atof( args[2] );
for( int i = 0; i < g_cmixlayers; i++)
{
soundmixer_t *pmixlayer = &g_mixlayers[i];
if(!Q_stricmp(pmixlayer->szsoundmixer, szlayername))
{
DevMsg("Setting MixLayer %s : mix %f\n", pmixlayer->szsoundmixer, val);
pmixlayer->mixAmount = val;
break;
}
}
}
static ConCommand snd_setmixlayeramount("snd_setmixlayer_amount", MXR_SetMixLayerAmount, "Set named mix layer mix amount.", FCVAR_CHEAT );
static void MXR_SetMixLayerTriggerFactor( const CCommand &args )
{
if ( args.ArgC() != 4 )
{
DevMsg("Parameters: mix layer name, mix group name, trigger amount");
return;
}
const char *szlayername = args[1];
const char *szgroupname = args[2];
float val = atof( args[3] );
S_SetMixLayerTriggerFactor( szlayername, szgroupname, val );
}
static ConCommand snd_soundmixer_set_trigger_factor("snd_soundmixer_set_trigger_factor", MXR_SetMixLayerTriggerFactor, "Set named mix layer / mix group, trigger amount.", FCVAR_CHEAT );
int MXR_GetFirstValidMixGroup( channel_t *pChannel )
{
for (int i = 0; i < CMXRMATCHMAX; i++)
{
int imixgroup = pChannel->mixgroups[i];
//rgmixgroupid[i];
if (imixgroup < 0)
continue;
return imixgroup;
}
return -1;
}
// ---------------------------------------------------------------------
// 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
// ---------------------------------------------------------------------
soundmixer_t *MXR_GetCurrentMixer( )
{
// if no soundmixer currently set, return 1.0 volume
if (g_isoundmixer < 0)
{
return NULL;
}
if (g_csoundmixers)
{
soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
if (pmixer)
{
return pmixer;
}
}
return NULL;
}
void MXR_GetValuesFromMixGroupIndex( mixervalues_t *mixValues, int imixgroup )
{
// save lowest duck gain value for any of the mix groups this sound is in
Assert(imixgroup < CMXRGROUPMAX);
soundmixer_t *pmixer = MXR_GetCurrentMixer( );
if( !pmixer )
{
// NEEDS ERROR!
return;
}
if ( pmixer->mapMixgroupidToVolume[imixgroup] >= 0)
{
// level
mixValues->level = pmixer->mapMixgroupidToLevel[imixgroup] * g_mastermixlayer.mapMixgroupidToLevel[imixgroup];
mixValues->level *= snd_mixerMasterLevel.GetFloat();
// dsp
mixValues->dsp = pmixer->mapMixgroupidToDsp[imixgroup] * g_mastermixlayer.mapMixgroupidToDsp[imixgroup];
mixValues->dsp *= snd_mixerMasterDSP.GetFloat();
// modify gain with ducker settings for this group
mixValues->volume = pmixer->mapMixgroupidToVolume[imixgroup] * g_mastermixlayer.mapMixgroupidToVolume[imixgroup];
// check for muting
mixValues->volume *= (1.0 - (MAX(pmixer->mapMixgroupidToMute[imixgroup], g_mastermixlayer.mapMixgroupidToMute[imixgroup])));
// If any group is solo'd && not this one, mute.
if(g_soloActive > 0.0)
{
// by definition current solo value is less than g_soloActive (max of mixer)
// not positive this math is the right approach
float factor = 1.0 - ((1.0 - (MAX(pmixer->mapMixgroupidToSolo[imixgroup], g_mastermixlayer.mapMixgroupidToSolo[imixgroup]) / g_soloActive)) * g_soloActive);
mixValues->volume *= factor;
}
}
}
void MXR_GetVolFromMixGroup( channel_t *ch, mixervalues_t *mixValues, int *plast_mixgroupid )
{
soundmixer_t *pmixer = MXR_GetCurrentMixer( );
if( !pmixer )
{
// NEEDS ERROR!
*plast_mixgroupid = 0;
mixValues->volume = 1.0;
return;
}
float duckgain = 1.0;
// search mixgroupid array, return first match (non -1)
for (int i = 0; i < CMXRMATCHMAX; i++)
{
int imixgroup = ch->mixgroups[i];
//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->mapMixgroupidToVolume[imixgroup] >= 0)
{
*plast_mixgroupid = imixgroup;
MXR_GetValuesFromMixGroupIndex( mixValues, imixgroup );
// apply ducking although this isn't fully working because it doesn't collect up the lowest duck amount
// Did this get broken on L4D1?
mixValues->volume *= duckgain;
return;
}
}
*plast_mixgroupid = 0;
mixValues->volume = duckgain;
return;
}
// 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++;
}
else
{
g_grouprules[i].mixgroupid = mixgroupid;
}
}
}
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(CMXRNAMEMAX-1, strlen(pName)));
g_cgroupclass++;
return g_cgroupclass-1;
}
ConVar snd_soundmixer_version("snd_soundmixer_version", "2" );
#define CHAR_LEFT_PAREN '{'
#define CHAR_RIGHT_PAREN '}'
// load group rules and sound mixers from file
void S_FlushMixers( const CCommand &args )
{
MXR_LoadAllSoundMixers();
}
static ConCommand SoundMixersFlush("snd_soundmixer_flush", S_FlushMixers, "Reload soundmixers.txt file.", FCVAR_CHEAT );
enum
{
MXRPARSE_NONE,
MXRPARSE_MIXGROUPS,
MXRPARSE_SOUNDMIXERS,
MXRPARSE_SOUNDMIXERGROUPS,
MXRPARSE_MIXLAYERS,
MXRPARSE_MIXLAYERGROUPS,
MXRPARSE_LAYERTRIGGERS
};
#define MIXGROUPS_STRING "MixGroups"
#define SOUNDMIXERS_STRING "SoundMixers"
#define MIXLAYERS_STRING "MixLayers"
#define LAYERTRIGGERS_STRING "LayerTriggers"
ConVar DebugMXRParse("snd_soundmixer_parse_debug", "0");
const char *MXR_ParseMixGroup(const char *pstart)
{
int parse_debug = DebugMXRParse.GetInt();
grouprule_t *pgroup = &g_grouprules[g_cgrouprules];
// copy mixgroup name, directory, classname
// if no value specified, set to 0 length string
if(parse_debug)
DevMsg("MixGroup %s:\n", com_token);
Q_memcpy(pgroup->szmixgroup, com_token, MIN(CMXRNAMEMAX-1, strlen(com_token)));
// make sure all copied strings are null terminated
pgroup->szmixgroup[CMXRNAMEMAX-1] = 0;
// path rule string
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
{
Q_memcpy(pgroup->szdir, com_token, MIN(CMXRNAMEMAX-1, strlen(com_token)));
V_strlower( pgroup->szdir );
// HACK to find group that affects voice channels
if ( V_strstr(pgroup->szdir, "?voice") )
{
pgroup->is_voice = 1;
}
}
}
else
DevMsg("Error: Parsing soundmixers.txt, mixgroup rules incomplete!\n");
// make sure all copied strings are null terminated
pgroup->szdir[CMXRNAMEMAX-1] = 0;
// classname
pgroup->classId = -1;
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
{
pgroup->classId = MXR_AddClassname( com_token );
}
}
else
DevMsg("Error: Parsing soundmixers.txt, mixgroup rules incomplete!\n");
// lookup chan
if(COM_TokenWaiting( pstart ))
{
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_BODY"))
pgroup->chantype = CHAN_BODY;
else if (!Q_stricmp(com_token, "CHAN_ITEM"))
pgroup->chantype = CHAN_ITEM;
}
else
pgroup->chantype = -1;
}
else
DevMsg("Error: Parsing soundmixers.txt, mixgroup rules incomplete!\n");
// get sndlvls
// soundlevel min
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
pgroup->soundlevel_min = atoi(com_token);
else
pgroup->soundlevel_min = -1;
}
else
DevMsg("Error: Parsing soundmixers.txt, mixgroup rules incomplete!\n");
// soundlevel max
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
pgroup->soundlevel_max = atoi(com_token);
else
pgroup->soundlevel_max = -1;
}
else
DevMsg("Error: Parsing soundmixers.txt, mixgroup rules incomplete!\n");
// get duck priority, IsDucked, Causes_ducking, duck_target_pct
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
pgroup->priority = atoi(com_token);
else
pgroup->priority = 50;
}
else
DevMsg("Error: Parsing soundmixers.txt, mixgroup rules incomplete!\n");
// mix group is ducked
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
pgroup->is_ducked = atoi(com_token);
else
pgroup->is_ducked = 0;
}
else
DevMsg("Error: Parsing soundmixers.txt, mixgroup rules incomplete!\n");
// mix group causes ducking
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
pgroup->causes_ducking = atoi(com_token);
else
pgroup->causes_ducking = 0;
}
else
DevMsg("Error: Parsing soundmixers.txt, mixgroup rules incomplete!\n");
// ducking target pct
if(COM_TokenWaiting( pstart ))
{
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;
}
else
DevMsg("Error: Parsing soundmixers.txt, mixgroup rules incomplete!\n");
// ducking target pct
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
pgroup->ducker_threshold = ((float)(atoi(com_token))) / 100.0f;
else
pgroup->ducker_threshold = 0.5f;
}
else
DevMsg("Error: Parsing soundmixers.txt, mixgroup rules incomplete!\n");
// set default values
pgroup->duck_ramp_val = 1.0;
pgroup->duck_target_vol = 1.0;
pgroup->total_vol = 0.0;
pgroup->trigger_vol = 0.0;
// set mixgroup id to -1
pgroup->mixgroupid = -1;
// update rule count
g_cgrouprules++;
return pstart;
}
const char *MXR_ParseSoundMixer(const char *pstart, soundmixer_t *pmixer)
{
int parse_debug = DebugMXRParse.GetInt();
// lookup mixgroupid for groupname
char szgroupname[CMXRNAMEMAX];
V_strcpy_safe(szgroupname, com_token);
// int mixgroupid = MXR_GetMixgroupFromName( com_token );
float volume = 1.0;
float level = 1.0;
float dsp = 1.0;
float solo = 0.0;
float mute = 0.0;
// get mix value
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if( com_token[0] )
volume = atof( com_token );
else
volume = 1.0;
}
else
DevMsg("Error: Parsing soundmixers.txt, soundmixer mix group values incomplete!\n");
// are we using new soundmixer features?
if(snd_soundmixer_version.GetInt() >= 2)
{
// checking for new mixer features
if(COM_TokenWaiting( pstart ))
{
// get "level" value
pstart = COM_Parse( pstart );
level = atof( com_token );
}
if(COM_TokenWaiting( pstart ))
{
// get "dsp" value
pstart = COM_Parse( pstart );
dsp = atof( com_token );
}
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
solo = atof( com_token );
}
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
mute = atof( com_token );
}
}
// scan group rules for mapping from name to id
for (int i = 0; i < g_cgrouprules; i++)
{
// if the mix groups name contains the string we set it
if ( !Q_strcmp( g_grouprules[i].szmixgroup, szgroupname ) )
{
// sanity check mix group
if(g_grouprules[i].mixgroupid < 0 || g_grouprules[i].mixgroupid >= CMXRGROUPMAX)
{
DevMsg("Error: MixGroup %s, in SoundMixer %s, cannot be resolved!\n", com_token, pmixer->szsoundmixer);
}
else
{
if(parse_debug)
DevMsg("MixGroup %s: %f : %f : %f : %f : %f \n", szgroupname, volume, level, dsp, solo, mute);
// store value for mixgroupid
pmixer->mapMixgroupidToVolume[g_grouprules[i].mixgroupid] = MAX(volume, 0.0);
pmixer->mapMixgroupidToLevel[g_grouprules[i].mixgroupid] = MAX(level, 0.0);
pmixer->mapMixgroupidToDsp[g_grouprules[i].mixgroupid] = MAX(dsp, 0.0);
pmixer->mapMixgroupidToSolo[g_grouprules[i].mixgroupid] = MAX(solo, 0.0);
pmixer->mapMixgroupidToMute[g_grouprules[i].mixgroupid] = MAX(mute, 0.0);
}
}
}
return pstart;
}
const char *MXR_ParseLayerTriggers(const char *pstart)
{
int parse_debug = DebugMXRParse.GetInt();
// copy mixgroup name, directory, classname
// if no value specified, set to 0 length string
if(parse_debug)
DevMsg("MixLayer triggered %s:\n", com_token);
int imixlayerid = MXR_GetMixLayerIndexFromName( com_token );
if ( imixlayerid == -1 )
{
Warning( "Failed to get mix layer %s!\n", com_token );
return pstart;
}
layertrigger_t *playertriggers = &g_layertriggers[imixlayerid];
// sanity check mix group
int imixgroupid = -1;
// path rule string
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
{
imixgroupid = MXR_GetMixgroupFromName( com_token );
}
}
else
{
DevMsg("Error: MixLayer Trigger entries require minimum 2 arguments\n");
return pstart;
}
if(imixgroupid < 0 || imixgroupid >= CMXRGROUPMAX)
{
DevMsg("Error: MixGroup %s, in LayerTriggers cannot be resolved!\n", com_token);
return pstart;
}
playertriggers->bistrigger[imixgroupid] = true;
playertriggers->bhastrigger = true;
// threshold
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
{
playertriggers->fthreshold[imixgroupid] = atof( com_token );
}
}
else
return pstart;
// mixamount
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
{
playertriggers->fmixamount[imixgroupid] = atof( com_token );
}
}
else
return pstart;
// attack
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
{
playertriggers->fattack[imixgroupid] = atof( com_token );
}
}
else
return pstart;
// release
if(COM_TokenWaiting( pstart ))
{
pstart = COM_Parse( pstart );
if (com_token[0])
{
playertriggers->frelease[imixgroupid] = atof( com_token );
}
}
return pstart;
}
bool MXR_LoadAllSoundMixers( void )
{
// init soundmixer globals
g_isoundmixer = -1;
g_szsoundmixer_cur[0] = 0;
g_csoundmixers = 0; // total number of soundmixers found
g_cmixlayers = 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_mixlayers, 0, sizeof(g_mixlayers));
Q_memset(g_grouprules, 0, sizeof(g_grouprules));
// init all mix group mixer values to -1.
for (int i = 0; i < CMXRSOUNDMIXERSMAX; i++)
{
// sound mixers
soundmixer_t *pmixer = &g_soundmixers[i];
V_strcpy_safe(pmixer->szsoundmixer, "");
// sound mixers default to full on
pmixer->mixAmount = 1.0;
for (int j = 0; j < CMXRGROUPMAX; j++)
{
pmixer->mapMixgroupidToVolume[j] = -1.0;
pmixer->mapMixgroupidToLevel[j] = 1.0;
pmixer->mapMixgroupidToDsp[j] = 1.0;
pmixer->mapMixgroupidToSolo[j] = 0.0;
pmixer->mapMixgroupidToMute[j] = 0.0;
}
}
for (int i = 0; i < CMXRMIXLAYERSMAX; i++)
{
// mix layers
soundmixer_t *pmixlayers = &g_mixlayers[i];
V_strcpy_safe(pmixlayers->szsoundmixer, "");
// mix layers default to all off
pmixlayers->mixAmount = 0.0;
for (int j = 0; j < CMXRGROUPMAX; j++)
{
pmixlayers->mapMixgroupidToVolume[j] = -1.0;
pmixlayers->mapMixgroupidToLevel[j] = 1.0;
pmixlayers->mapMixgroupidToDsp[j] = 1.0;
pmixlayers->mapMixgroupidToSolo[j] = 0.0;
pmixlayers->mapMixgroupidToMute[j] = 0.0;
}
}
// layer trigger defaults
for (int i = 0; i < CMXRMIXLAYERSMAX; i++)
{
layertrigger_t *playertriggers = &g_layertriggers[i];
playertriggers->bhastrigger = false;
for( int j = 0; j < CMXRGROUPMAX; j++)
{
playertriggers->bistrigger[j] = false;
playertriggers->fthreshold[j] = 0.0;
playertriggers->fmixamount[j] = 1.0;
playertriggers->fattack[j] = 0.0;
playertriggers->frelease[j] = 0.0;
}
}
// sound mixers file
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;
int parse_debug = DebugMXRParse.GetInt();
int currentMxrParse = MXRPARSE_NONE;
int currentParseLevel = 0;
// check for first CHAR_LEFT_PAREN
while (1)
{
pstart = COM_Parse( pstart );
if ( strlen(com_token) <= 0)
break; // eof
// handle in and out of brackets
if ( com_token[0] == CHAR_LEFT_PAREN )
{
currentParseLevel++;
//if(parse_debug)
//DevMsg("parse level %i:\n", currentParseLevel);
continue;
}
else if ( com_token[0] == CHAR_RIGHT_PAREN )
{
// do any clean up processing for the previous block
currentParseLevel--;
//if(parse_debug)
//DevMsg("parse level %i:\n", currentParseLevel);
if(currentMxrParse == MXRPARSE_MIXGROUPS)
{
// now process all groupids in groups, such that
// each mixgroup gets a unique id.
MXR_AssignGroupIds();
currentMxrParse = MXRPARSE_NONE;
if(parse_debug)
DevMsg("Total Mix Groups Rules: %i\n", g_cgrouprules);
}
else if(currentMxrParse == MXRPARSE_SOUNDMIXERGROUPS)
{
currentMxrParse = MXRPARSE_SOUNDMIXERS;
g_csoundmixers++;
}
else if(currentMxrParse == MXRPARSE_SOUNDMIXERS)
{
currentMxrParse = MXRPARSE_NONE;
}
else if(currentMxrParse == MXRPARSE_MIXLAYERGROUPS)
{
currentMxrParse = MXRPARSE_MIXLAYERS;
g_cmixlayers++;
}
else if(currentMxrParse == MXRPARSE_MIXLAYERS)
{
currentMxrParse = MXRPARSE_NONE;
if(parse_debug)
DevMsg("Total Mix Layers: %i\n", g_cmixlayers);
}
else if(currentMxrParse == MXRPARSE_LAYERTRIGGERS)
{
currentMxrParse = MXRPARSE_NONE;
}
continue;
}
// parsing the outside?
if( currentMxrParse == MXRPARSE_NONE)
{
if (!Q_strcmp( com_token, MIXGROUPS_STRING ) )
{
currentMxrParse = MXRPARSE_MIXGROUPS;
if(parse_debug)
DevMsg("Parsing MixGroups:\n");
continue;
}
else if (!Q_strcmp( com_token, SOUNDMIXERS_STRING ) )
{
currentMxrParse = MXRPARSE_SOUNDMIXERS;
if(parse_debug)
DevMsg("Parsing SoundMixers:\n");
continue;
}
else if (!Q_strcmp( com_token, MIXLAYERS_STRING ) )
{
currentMxrParse = MXRPARSE_MIXLAYERS;
if(parse_debug)
DevMsg("Parsing MixLayers:\n");
continue;
}
else if (!Q_strcmp( com_token, LAYERTRIGGERS_STRING ) )
{
currentMxrParse = MXRPARSE_LAYERTRIGGERS;
if(parse_debug)
DevMsg("Parsing LayerTriggers:\n");
continue;
}
}
else if ( currentMxrParse == MXRPARSE_MIXGROUPS )
{
if (g_cgrouprules > CMXRGROUPRULESMAX)
{
// UNDONE: error! too many rules
DevMsg("Error: Too many mix groups! MixGroup %s ignored\n", com_token);
continue;
}
pstart = MXR_ParseMixGroup(pstart);
}
else if ( currentMxrParse == MXRPARSE_LAYERTRIGGERS )
{
/*if (g_cgrouprules > CMXRGROUPRULESMAX)
{
// UNDONE: error! too many rules
DevMsg("Error: Too many mix groups! MixGroup %s ignored\n", com_token);
continue;
}*/
pstart = MXR_ParseLayerTriggers(pstart);
}
else if ( currentMxrParse == MXRPARSE_SOUNDMIXERS && currentParseLevel < 2 )
{
// save name in soundmixer
if (g_csoundmixers > CMXRSOUNDMIXERSMAX)
{
DevMsg("Error: Too many sound mixers! SoundMixer %s ignored\n", com_token);
continue;
}
soundmixer_t *pmixer = &g_soundmixers[g_csoundmixers];
if(parse_debug)
DevMsg("SoundMixer %s:\n", com_token);
currentMxrParse = MXRPARSE_SOUNDMIXERGROUPS;
Q_memcpy(pmixer->szsoundmixer, com_token, MIN(CMXRNAMEMAX-1, strlen(com_token)));
}
else if ( currentMxrParse == MXRPARSE_SOUNDMIXERGROUPS && currentParseLevel == 2 )
{
soundmixer_t *pmixer = &g_soundmixers[g_csoundmixers];
pstart = MXR_ParseSoundMixer(pstart, pmixer );
}
// mix layers
else if ( currentMxrParse == MXRPARSE_MIXLAYERS && currentParseLevel < 2 )
{
// save name in soundmixer
if (g_cmixlayers > CMXRMIXLAYERSMAX)
{
DevMsg("Error: Too many mix layers! MixLayer %s ignored\n", com_token);
continue;
}
soundmixer_t *pmixlayer = &g_mixlayers[g_cmixlayers];
if(parse_debug)
DevMsg("MixLayers %s:\n", com_token);
currentMxrParse = MXRPARSE_MIXLAYERGROUPS;
Q_memcpy(pmixlayer->szsoundmixer, com_token, MIN(CMXRNAMEMAX-1, strlen(com_token)));
}
else if ( currentMxrParse == MXRPARSE_MIXLAYERGROUPS && currentParseLevel == 2 )
{
soundmixer_t *pmixlayer = &g_mixlayers[g_cmixlayers];
pstart = MXR_ParseSoundMixer(pstart, pmixlayer);
}
}
bResult = true;
// loadmxr_exit:
free( pbuffer );
return bResult;
}
void MXR_ReleaseMemory( void )
{
// free all resources
}
//--------------------------------------------------------------------------------------------
//
// Debug and diagnostics
//
//---------------------------------------------------------------------------------------------
static void MXR_PrintMixGroups( soundmixer_t *pmixer )
{
int imixgroup = 0;
int nMixGroupId = 0;
for( int i = 0; i < g_cgrouprules; i++ )
{
imixgroup = g_grouprules[i].mixgroupid;
if( imixgroup < 0 )
{
nMixGroupId++;
continue;
}
else if ( imixgroup != nMixGroupId ) // only uniquely id'd
{
continue;
}
if( pmixer->mapMixgroupidToVolume[imixgroup] < 0.0 )
{
nMixGroupId++;
continue;
}
int nStrLen = V_strlen( g_grouprules[i].szmixgroup );
int nDiff = 32 - nStrLen;
char nTmpStr[32];
for( int j = 0; j < nDiff; j++ )
{
nTmpStr[j] = ' ';
}
nTmpStr[nDiff] = NULL;
DevMsg("%s: %s", g_grouprules[i].szmixgroup, nTmpStr );
DevMsg("vol: %3.2f ", pmixer->mapMixgroupidToVolume[imixgroup]);
DevMsg("lvl: %3.2f ", pmixer->mapMixgroupidToLevel[imixgroup]);
DevMsg("dsp: %3.2f ", pmixer->mapMixgroupidToDsp[imixgroup]);
DevMsg("solo: %3.2f ", pmixer->mapMixgroupidToSolo[imixgroup]);
DevMsg("mute: %3.2f\n", pmixer->mapMixgroupidToMute[imixgroup]);
nMixGroupId++;
}
}
static void MXR_ListMixers( const CCommand &args )
{
for (int i = 0; i < g_csoundmixers; i++)
{
soundmixer_t *pmixer = &g_soundmixers[i];
DevMsg("%s:\n", pmixer->szsoundmixer);
MXR_PrintMixGroups( pmixer );
}
}
static ConCommand snd_list_mixers("snd_soundmixer_list_mixers", MXR_ListMixers, "List all mixers to dev console." );
static void MXR_ListMixLayers( const CCommand &args )
{
for (int i = 0; i < g_cmixlayers; i++)
{
soundmixer_t *pmixer = &g_mixlayers[i];
DevMsg("%s: %f\n", pmixer->szsoundmixer, pmixer->mixAmount );
MXR_PrintMixGroups( pmixer );
}
DevMsg( "g_mastermixlayer:\n" );
MXR_PrintMixGroups( &g_mastermixlayer );
}
static ConCommand snd_list_mix_layers("snd_soundmixer_list_mix_layers", MXR_ListMixLayers, "List all mix layers to dev console.");
//---------------------------------------------------------------------------
//
// list all mix groups and their values
//
//---------------------------------------------------------------------------
static ConCommand snd_list_mix_groups("snd_soundmixer_list_mix_groups", MXR_ListMixGroups, "List all mix groups to dev console.");
static void MXR_ListMixGroups( const CCommand &args )
{
soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
MXR_PrintMixGroups( pmixer );
}
void S_GetMixGroupOfCurrentMixer( const char *szgroupname, soundmixer_t *pmixer)
{
// iterate over groups
int imixgroup = 0;
for (int i = 0; i < g_cgrouprules; i++)
{
if(Q_stristr(g_grouprules[i].szmixgroup, szgroupname))
{
imixgroup = g_grouprules[i].mixgroupid;
// float dynVolume = g_mastermixlayer.mapMixgroupidToVolume[imixgroup];
float volume = pmixer->mapMixgroupidToVolume[imixgroup];
// float dynLevel = g_mastermixlayer.mapMixgroupidToLevel[imixgroup];
float level = pmixer->mapMixgroupidToLevel[imixgroup];
// float dynDsp = g_mastermixlayer.mapMixgroupidToDsp[imixgroup];
float dsp = pmixer->mapMixgroupidToDsp[imixgroup];
// float dynMute = g_mastermixlayer.mapMixgroupidToMute[imixgroup];
float mute = pmixer->mapMixgroupidToMute[imixgroup];
// float dynSolo = g_mastermixlayer.mapMixgroupidToSolo[imixgroup];
float solo = pmixer->mapMixgroupidToSolo[imixgroup];
DevMsg("%s:\n", g_grouprules[i].szmixgroup);
DevMsg("\tVOL: %f\n\tLVL: %f\n\tDSP: %f\n\tMUTE: %f\n\tSOLO: %f\n\n",
volume, level, dsp, mute, solo);
}
}
}
static void MXR_GetSoundMixer( const CCommand &args )
{
soundmixer_t *pmixer = NULL;
if ( args.ArgC() == 2)
{
// get current mixer
if ( g_isoundmixer < 0 )
return;
pmixer = &g_soundmixers[g_isoundmixer];
}
else
{
//DevMsg("Parameters: mix group name, [vol, mute, solo], value");
return;
}
const char *szgroupname = args[1];
S_GetMixGroupOfCurrentMixer(szgroupname, pmixer);
}
static ConCommand snd_getmixer("snd_getmixer", MXR_GetSoundMixer, "Get data related to mix group matching string");
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
};
// show the current soundmixer output
// 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
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.0, 0.7);
vol2 = clamp(vol, 0.0, 0.95);
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;
}
}
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 mapMixgroupidToVolume which is not -1,
// set up groupvols
for (i = 0; i < CMXRGROUPMAX; i++)
{
// currently sound mixers are required and entry per mix group for anything to work!!
// TODO: change this and make sound mixers operate sparsely, like layers
if (pmixer->mapMixgroupidToVolume[i] >= 0)
{
groupvols[cgroups].mixgroupid = i;
groupvols[cgroups].psz = MXR_GetGroupnameFromId( i );
groupvols[cgroups].totalvol = 0.0;
groupvols[cgroups].vol = pmixer->mapMixgroupidToVolume[i] * g_mastermixlayer.mapMixgroupidToVolume[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
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->mapMixgroupidToVolume[imixgroup] = vol;
}
#endif //_DEBUG