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
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
|
|
|
|
|