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