diff options
Diffstat (limited to 'engine/audio/private/snd_dsp.cpp')
| -rw-r--r-- | engine/audio/private/snd_dsp.cpp | 9679 |
1 files changed, 9679 insertions, 0 deletions
diff --git a/engine/audio/private/snd_dsp.cpp b/engine/audio/private/snd_dsp.cpp new file mode 100644 index 0000000..4e73fdb --- /dev/null +++ b/engine/audio/private/snd_dsp.cpp @@ -0,0 +1,9679 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// snd_dsp.c -- audio processing routines + + +#include "audio_pch.h" +#include "snd_mix_buf.h" + +#include "iprediction.h" +#include "../../common.h" // for parsing routines +#include "vstdlib/random.h" +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define SIGN(d) ((d)<0?-1:1) + +#define ABS(a) abs(a) + +#define MSEC_TO_SAMPS(a) (((a)*SOUND_DMA_SPEED) / 1000) // convert milliseconds to # samples in equivalent time +#define SEC_TO_SAMPS(a) ((a)*SOUND_DMA_SPEED) // convert seconds to # samples in equivalent time + +// Suppress the noisy warnings caused by CLIP_DSP +#if defined(__clang__) + #pragma GCC diagnostic ignored "-Wself-assign" +#endif +#define CLIP_DSP(x) (x) + +extern ConVar das_debug; + +#define SOUND_MS_PER_FT 1 // sound travels approx 1 foot per millisecond +#define ROOM_MAX_SIZE 1000 // max size in feet of room simulation for dsp + +void DSP_ReleaseMemory( void ); +bool DSP_LoadPresetFile( void ); + +extern float Gain_To_dB ( float gain ); +extern float dB_To_Gain ( float dB ); +extern float Gain_To_Amplitude ( float gain ); +extern float Amplitude_To_Gain ( float amplitude ); + +extern bool g_bdas_room_init; +extern bool g_bdas_init_nodes; + +//=============================================================================== +// +// Digital Signal Processing algorithms for audio FX. +// +// KellyB 2/18/03 +//=============================================================================== + +// Performance notes: + +// DSP processing should take no more than 3ms total time per frame to remain on par with hl1 +// Assume a min frame rate of 24fps = 42ms per frame +// at 24fps, to maintain 44.1khz output rate, we must process about 1840 mono samples per frame. +// So we must process 1840 samples in 3ms. + +// on a 1Ghz CPU (mid-low end CPU) 3ms provides roughly 3,000,000 cycles. +// Thus we have 3e6 / 1840 = 1630 cycles per sample. + +#define PBITS 12 // parameter bits +#define PMAX ((1 << PBITS)) // parameter max + +// crossfade from y2 to y1 at point r (0 < r < PMAX) + +#define XFADE(y1,y2,r) ((y2) + ( ( ((y1) - (y2)) * (r) ) >> PBITS) ) + +// exponential crossfade from y2 to y1 at point r (0 < r < PMAX) + +#define XFADE_EXP(y1, y2, r) ((y2) + ((((((y1) - (y2)) * (r) ) >> PBITS) * (r)) >> PBITS) ) + +///////////////////// +// dsp helpers +///////////////////// + +// reverse delay pointer + +inline void DlyPtrReverse (int dlysize, int *psamps, int **ppsamp) +{ + // when *ppsamp = psamps - 1, it wraps around to *ppsamp = psamps + dlysize + + if ( *ppsamp < psamps ) + *ppsamp += dlysize + 1; +} + +// advance delay pointer + +inline void DlyPtrForward (int dlysize, int *psamps, int **ppsamp) +{ + // when *ppsamp = psamps + dlysize + 1, it wraps around to *ppsamp = psamps + + if ( *ppsamp > psamps + dlysize ) + *ppsamp -= dlysize + 1; +} + +// Infinite Impulse Response (feedback) filter, cannonical form + +// returns single sample 'out' for current input value 'in' +// in: input sample +// psamp: internal state array, dimension max(cdenom,cnumer) + 1 +// cnumer,cdenom: numerator and denominator filter orders +// denom,numer: cdenom+1 dimensional arrays of filter params +// +// for cdenom = 4: +// +// 1 psamp0(n) numer0 +// in(n)--->(+)--(*)---.------(*)---->(+)---> out(n) +// ^ | ^ +// | [Delay d] | +// | | | +// | -denom1 |psamp1 numer1 | +// ----(*)---.------(*)------- +// ^ | ^ +// | [Delay d] | +// | | | +// | -denom2 |psamp2 numer2 | +// ----(*)---.------(*)------- +// ^ | ^ +// | [Delay d] | +// | | | +// | -denom3 |psamp3 numer3 | +// ----(*)---.------(*)------- +// ^ | ^ +// | [Delay d] | +// | | | +// | -denom4 |psamp4 numer4 | +// ----(*)---.------(*)------- +// +// for each input sample in: +// psamp0 = in - denom1*psamp1 - denom2*psamp2 - ... +// out = numer0*psamp0 + numer1*psamp1 + ... +// psampi = psampi-1, i = cmax, cmax-1, ..., 1 + +inline int IIRFilter_Update_OrderN ( int cdenom, int *denom, int cnumer, int *numer, int *psamp, int in ) +{ + int cmax, i; + int out; + int in0; + + out = 0; + in0 = in; + + cmax = max ( cdenom, cnumer ); + + // add input values + + // for (i = 1; i <= cdenom; i++) + // psamp[0] -= ( denom[i] * psamp[i] ) >> PBITS; + + switch (cdenom) + { + case 12: in0 -= ( denom[12] * psamp[12] ) >> PBITS; + case 11: in0 -= ( denom[11] * psamp[11] ) >> PBITS; + case 10: in0 -= ( denom[10] * psamp[10] ) >> PBITS; + case 9: in0 -= ( denom[9] * psamp[9] ) >> PBITS; + case 8: in0 -= ( denom[8] * psamp[8] ) >> PBITS; + case 7: in0 -= ( denom[7] * psamp[7] ) >> PBITS; + case 6: in0 -= ( denom[6] * psamp[6] ) >> PBITS; + case 5: in0 -= ( denom[5] * psamp[5] ) >> PBITS; + case 4: in0 -= ( denom[4] * psamp[4] ) >> PBITS; + case 3: in0 -= ( denom[3] * psamp[3] ) >> PBITS; + case 2: in0 -= ( denom[2] * psamp[2] ) >> PBITS; + default: + case 1: in0 -= ( denom[1] * psamp[1] ) >> PBITS; + } + + psamp[0] = in0; + + // add output values + + //for (i = 0; i <= cnumer; i++) + // out += ( numer[i] * psamp[i] ) >> PBITS; + + switch (cnumer) + { + case 12: out += ( numer[12] * psamp[12] ) >> PBITS; + case 11: out += ( numer[11] * psamp[11] ) >> PBITS; + case 10: out += ( numer[10] * psamp[10] ) >> PBITS; + case 9: out += ( numer[9] * psamp[9] ) >> PBITS; + case 8: out += ( numer[8] * psamp[8] ) >> PBITS; + case 7: out += ( numer[7] * psamp[7] ) >> PBITS; + case 6: out += ( numer[6] * psamp[6] ) >> PBITS; + case 5: out += ( numer[5] * psamp[5] ) >> PBITS; + case 4: out += ( numer[4] * psamp[4] ) >> PBITS; + case 3: out += ( numer[3] * psamp[3] ) >> PBITS; + case 2: out += ( numer[2] * psamp[2] ) >> PBITS; + default: + case 1: out += ( numer[1] * psamp[1] ) >> PBITS; + case 0: out += ( numer[0] * psamp[0] ) >> PBITS; + } + + // update internal state (reverse order) + + for (i = cmax; i >= 1; i--) + psamp[i] = psamp[i-1]; + + // return current output sample + + return out; +} + +// 1st order filter - faster version + +inline int IIRFilter_Update_Order1 ( int *denom, int cnumer, int *numer, int *psamp, int in ) +{ + int out; + + if (!psamp[0] && !psamp[1] && !in) + return 0; + + psamp[0] = in - (( denom[1] * psamp[1] ) >> PBITS); + + out = ( ( numer[1] * psamp[1] ) + ( numer[0] * psamp[0] ) ) >> PBITS; + + psamp[1] = psamp[0]; + + return out; +} + +// return 'tdelay' delayed sample from delay buffer +// dlysize: delay samples +// psamps: head of delay buffer psamps[0...dlysize] +// psamp: current data pointer +// sdly: 0...dlysize + +inline int GetDly ( int dlysize, int *psamps, int *psamp, int tdelay ) +{ + int *pout; + + pout = psamp + tdelay; + + if ( pout <= (psamps + dlysize)) + return *pout; + else + return *(pout - dlysize - 1); +} + +// update the delay buffer pointer +// dlysize: delay samples +// psamps: head of delay buffer psamps[0...dlysize] +// ppsamp: data pointer + +inline void DlyUpdate ( int dlysize, int *psamps, int **ppsamp ) +{ + // decrement pointer and fix up on buffer boundary + + // when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize + + (*ppsamp)--; + DlyPtrReverse ( dlysize, psamps, ppsamp ); +} + +// simple delay with feedback, no filter in feedback line. +// delaysize: delay line size in samples +// tdelay: tap from this location - <= delaysize +// psamps: delay line buffer pointer of dimension delaysize+1 +// ppsamp: circular pointer, must be init to &psamps[0] before first call +// fbgain: feedback value, 0-PMAX (normalized to 0.0-1.0) +// outgain: gain +// in: input sample + +// psamps0(n) outgain +// in(n)--->(+)--------.-----(*)-> out(n) +// ^ | +// | [Delay d] +// | | +// | fbgain |Wd(n) +// ----(*)---. + +inline int ReverbSimple ( int delaysize, int tdelay, int *psamps, int **ppsamp, int fbgain, int outgain, int in ) +{ + int out, sD; + + // get current delay output + + sD = GetDly ( delaysize, psamps, *ppsamp, tdelay ); + + // calculate output + delay * gain + + out = in + (( fbgain * sD ) >> PBITS); + + // write to delay + + **ppsamp = out; + + // advance internal delay pointers + + DlyUpdate ( delaysize, psamps, ppsamp ); + + return ( (out * outgain) >> PBITS ); +} + +inline int ReverbSimple_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, int *psamps, int **ppsamp, int fbgain, int outgain, int in ) +{ + int out, sD; + int sDnew; + + // crossfade from tdelay to tdelaynew samples. xfade is 0..PMAX + + sD = GetDly ( delaysize, psamps, *ppsamp, tdelay ); + sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew ); + sD = sD + (((sDnew - sD) * xf) >> PBITS); + + out = in + (( fbgain * sD ) >> PBITS); + **ppsamp = out; + DlyUpdate ( delaysize, psamps, ppsamp ); + + return ( (out * outgain) >> PBITS ); +} + +// multitap simple reverb + +// NOTE: tdelay3 > tdelay2 > tdelay1 > t0 +// NOTE: fbgain * 4 < 1! + +inline int ReverbSimple_multitap ( int delaysize, int tdelay0, int tdelay1, int tdelay2, int tdelay3, int *psamps, int **ppsamp, int fbgain, int outgain, int in ) +{ + int s1, s2, s3, s4, sum; + + s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 ); + s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 ); + s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 ); + s4 = GetDly ( delaysize, psamps, *ppsamp, tdelay3 ); + + sum = s1 + s2 + s3 + s4; + + // write to delay + + **ppsamp = in + ((s4 * fbgain) >> PBITS); + + // update delay pointers + + DlyUpdate ( delaysize, psamps, ppsamp ); + + return ( ((sum + in) * outgain ) >> PBITS ); +} + +// modulate smallest tap delay only + +inline int ReverbSimple_multitap_xfade ( int delaysize, int tdelay0, int tdelaynew, int xf, int tdelay1, int tdelay2, int tdelay3, int *psamps, int **ppsamp, int fbgain, int outgain, int in ) +{ + int s1, s2, s3, s4, sum; + int sD, sDnew; + + // crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX + + sD = GetDly ( delaysize, psamps, *ppsamp, tdelay3 ); + sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew ); + + s4 = sD + (((sDnew - sD) * xf) >> PBITS); + + s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 ); + s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 ); + s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 ); + + sum = s1 + s2 + s3 + s4; + + // write to delay + + **ppsamp = in + ((s4 * fbgain) >> PBITS); + + // update delay pointers + + DlyUpdate ( delaysize, psamps, ppsamp ); + + return ( ((sum + in) * outgain ) >> PBITS ); +} + +// straight delay, no feedback +// +// delaysize: delay line size in samples +// tdelay: tap from this location - <= delaysize +// psamps: delay line buffer pointer of dimension delaysize+1 +// ppsamp: circular pointer, must be init to &psamps[0] before first call +// in: input sample +// +// in(n)--->[Delay d]---> out(n) +// + +inline int DelayLinear ( int delaysize, int tdelay, int *psamps, int **ppsamp, int in ) +{ + int out; + + out = GetDly ( delaysize, psamps, *ppsamp, tdelay ); + + **ppsamp = in; + + DlyUpdate ( delaysize, psamps, ppsamp ); + + return ( out ); +} + +// crossfade delay values from tdelay to tdelaynew, with xfade1 for tdelay and xfade2 for tdelaynew. xfade = 0...PMAX + +inline int DelayLinear_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, int *psamps, int **ppsamp, int in ) +{ + int out; + int outnew; + + out = GetDly ( delaysize, psamps, *ppsamp, tdelay ); + + outnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew ); + + out = out + (((outnew - out) * xf) >> PBITS); + + **ppsamp = in; + + DlyUpdate ( delaysize, psamps, ppsamp ); + + return ( out ); +} + +// lowpass reverberator, replace feedback multiplier 'fbgain' in +// reverberator with a low pass filter + +// delaysize: delay line size in samples +// tdelay: tap from this location - <= delaysize +// psamps: delay line buffer pointer of dimension delaysize+1 +// ppsamp: circular pointer, must be init to &w[0] before first call +// fbgain: feedback gain (built into filter gain) +// outgain: output gain +// cnumer: filter order +// numer: filter numerator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional +// denom: filter denominator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional +// pfsamps: filter state, cnumer+1 dimensional +// in: input sample + +// psamps0(n) outgain +// in(n)--->(+)--------------.----(*)--> out(n) +// ^ | +// | [Delay d] +// | | +// | fbgain |Wd(n) +// --(*)--[Filter])- + +inline int DelayLowpass ( int delaysize, int tdelay, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in ) +{ + int out, sD; + + // delay output is filter input + + sD = GetDly ( delaysize, psamps, *ppsamp, tdelay ); + + // filter output, with feedback 'fbgain' baked into filter params + + out = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD ); + + // write to delay + + **ppsamp = out; + + // update delay pointers + + DlyUpdate ( delaysize, psamps, ppsamp ); + + // output with gain + + return ( (out * outgain) >> PBITS ); +} + +inline int DelayLowpass_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in ) +{ + int out, sD; + int sDnew; + + // crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX + + sD = GetDly ( delaysize, psamps, *ppsamp, tdelay ); + sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew ); + sD = sD + (((sDnew - sD) * xf) >> PBITS); + + // filter output with feedback 'fbgain' baked into filter params + + out = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD ); + + // write to delay + + **ppsamp = out; + + // update delay ptrs + + DlyUpdate ( delaysize, psamps, ppsamp ); + + // output with gain + + return ( (out * outgain) >> PBITS ); +} + +// delay is multitap tdelay0,tdelay1,tdelay2,tdelay3 + +// NOTE: tdelay3 > tdelay2 > tdelay1 > tdelay0 +// NOTE: fbgain * 4 < 1! + +inline int DelayLowpass_multitap ( int delaysize, int tdelay0, int tdelay1, int tdelay2, int tdelay3, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in ) +{ + int s0, s1, s2, s3, s4, sum; + + s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 ); + s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 ); + s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 ); + s4 = GetDly ( delaysize, psamps, *ppsamp, tdelay3 ); + + sum = s1 + s2 + s3 + s4; + + s0 = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, s4 ); + + // write to delay + + **ppsamp = s0; + + // update delay ptrs + + DlyUpdate ( delaysize, psamps, ppsamp ); + + return ( ((sum + in) * outgain ) >> PBITS ); +} + +inline int DelayLowpass_multitap_xfade ( int delaysize, int tdelay0, int tdelaynew, int xf, int tdelay1, int tdelay2, int tdelay3, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in ) +{ + int s0, s1, s2, s3, s4, sum; + + int sD, sDnew; + + // crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX + + sD = GetDly ( delaysize, psamps, *ppsamp, tdelay3 ); + sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew ); + + s4 = sD + (((sDnew - sD) * xf) >> PBITS); + + s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 ); + s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 ); + s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 ); + + sum = s1 + s2 + s3 + s4; + + s0 = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, s4 ); + + **ppsamp = s0; + DlyUpdate ( delaysize, psamps, ppsamp ); + + return ( ((sum + in) * outgain ) >> PBITS ); +} + +// linear delay with lowpass filter on delay output and gain stage +// delaysize: delay line size in samples +// tdelay: delay tap from this location - <= delaysize +// psamps: delay line buffer pointer of dimension delaysize+1 +// ppsamp: circular pointer, must init &psamps[0] before first call +// fbgain: feedback gain (ignored) +// outgain: output gain +// cnumer: filter order +// numer: filter numerator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional +// denom: filter denominator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional +// pfsamps: filter state, cnumer+1 dimensional +// in: input sample + +// in(n)--->[Delay d]--->[Filter]-->(*outgain)---> out(n) + +inline int DelayLinear_lowpass ( int delaysize, int tdelay, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int cnumer, int *numer, int *pfsamps, int in ) +{ + int out, sD; + + // delay output is filter input + + sD = GetDly ( delaysize, psamps, *ppsamp, tdelay ); + + // calc filter output + + out = IIRFilter_Update_Order1 ( denom, cnumer, numer, pfsamps, sD ); + + // input sample to delay input + + **ppsamp = in; + + // update delay pointers + + DlyUpdate ( delaysize, psamps, ppsamp ); + + // output with gain + + return ( (out * outgain) >> PBITS ); +} + +inline int DelayLinear_lowpass_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int cnumer, int *numer, int *pfsamps, int in ) +{ + int out, sD; + int sDnew; + + // crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX + + sD = GetDly ( delaysize, psamps, *ppsamp, tdelay ); + sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew ); + sD = sD + (((sDnew - sD) * xf) >> PBITS); + + out = IIRFilter_Update_Order1 ( denom, cnumer, numer, pfsamps, sD ); + + **ppsamp = in; + + DlyUpdate ( delaysize, psamps, ppsamp ); + + return ( (out * outgain) >> PBITS ); +} + + +// classic allpass reverb +// delaysize: delay line size in samples +// tdelay: tap from this location - <= D +// psamps: delay line buffer pointer of dimension delaysize+1 +// ppsamp: circular pointer, must be init to &psamps[0] before first call +// fbgain: feedback value, 0-PMAX (normalized to 0.0-1.0) +// outgain: gain + +// psamps0(n) -fbgain outgain +// in(n)--->(+)--------.-----(*)-->(+)--(*)-> out(n) +// ^ | ^ +// | [Delay d] | +// | | | +// | fbgain |psampsd(n) | +// ----(*)---.------------- +// +// for each input sample 'in': +// psamps0 = in + fbgain * psampsd +// y = -fbgain * psamps0 + psampsd +// delay (d, psamps) - psamps is the delay buffer array +// +// or, using circular delay, for each input sample 'in': +// +// Sd = GetDly (delaysize,psamps,ppsamp,delaysize) +// S0 = in + fbgain*Sd +// y = -fbgain*S0 + Sd +// *ppsamp = S0 +// DlyUpdate(delaysize, psamps, &ppsamp) + +inline int DelayAllpass ( int delaysize, int tdelay, int *psamps, int **ppsamp, int fbgain, int outgain, int in ) +{ + int out, s0, sD; + + sD = GetDly ( delaysize, psamps, *ppsamp, tdelay ); + s0 = in + (( fbgain * sD ) >> PBITS); + + out = ( ( -fbgain * s0 ) >> PBITS ) + sD; + **ppsamp = s0; + DlyUpdate ( delaysize, psamps, ppsamp ); + + return ( (out * outgain) >> PBITS ); +} + + +inline int DelayAllpass_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, int *psamps, int **ppsamp, int fbgain, int outgain, int in ) +{ + int out, s0, sD; + int sDnew; + + // crossfade from t to tnew tap. xfade is 0..PMAX + + sD = GetDly ( delaysize, psamps, *ppsamp, tdelay ); + sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew ); + sD = sD + (((sDnew - sD) * xf) >> PBITS); + + s0 = in + (( fbgain * sD ) >> PBITS); + + out = ( ( -fbgain * s0 ) >> PBITS ) + sD; + **ppsamp = s0; + DlyUpdate ( delaysize, psamps, ppsamp ); + + return ( (out * outgain) >> PBITS ); +} + +/////////////////////////////////////////////////////////////////////////////////// +// fixed point math for real-time wave table traversing, pitch shifting, resampling +/////////////////////////////////////////////////////////////////////////////////// + +#define FIX20_BITS 20 // 20 bits of fractional part +#define FIX20_SCALE (1 << FIX20_BITS) + +#define FIX20_INTMAX ((1 << (32 - FIX20_BITS))-1) // maximum step integer + +#define FLOAT_TO_FIX20(a) ((int)((a) * (float)FIX20_SCALE)) // convert float to fixed point +#define INT_TO_FIX20(a) (((int)(a)) << FIX20_BITS) // convert int to fixed point +#define FIX20_TO_FLOAT(a) ((float)(a) / (float)FIX20_SCALE) // convert fix20 to float +#define FIX20_INTPART(a) (((int)(a)) >> FIX20_BITS) // get integer part of fixed point +#define FIX20_FRACPART(a) ((a) - (((a) >> FIX20_BITS) << FIX20_BITS)) // get fractional part of fixed point + +#define FIX20_FRACTION(a,b) (FIX(a)/(b)) // convert int a to fixed point, divide by b + +typedef int fix20int; + +///////////////////////////////// +// DSP processor parameter block +///////////////////////////////// + +// NOTE: these prototypes must match the XXX_Params ( prc_t *pprc ) and XXX_GetNext ( XXX_t *p, int x ) functions + +typedef void * (*prc_Param_t)( void *pprc ); // individual processor allocation functions +typedef int (*prc_GetNext_t) ( void *pdata, int x ); // get next function for processor +typedef int (*prc_GetNextN_t) ( void *pdata, portable_samplepair_t *pbuffer, int SampleCount, int op); // batch version of getnext +typedef void (*prc_Free_t) ( void *pdata ); // free function for processor +typedef void (*prc_Mod_t) (void *pdata, float v); // modulation function for processor + +#define OP_LEFT 0 // batch process left channel in place +#define OP_RIGHT 1 // batch process right channel in place +#define OP_LEFT_DUPLICATE 2 // batch process left channel in place, duplicate to right channel + +#define PRC_NULL 0 // pass through - must be 0 +#define PRC_DLY 1 // simple feedback reverb +#define PRC_RVA 2 // parallel reverbs +#define PRC_FLT 3 // lowpass or highpass filter +#define PRC_CRS 4 // chorus +#define PRC_PTC 5 // pitch shifter +#define PRC_ENV 6 // adsr envelope +#define PRC_LFO 7 // lfo +#define PRC_EFO 8 // envelope follower +#define PRC_MDY 9 // mod delay +#define PRC_DFR 10 // diffusor - n series allpass delays +#define PRC_AMP 11 // amplifier with distortion + +#define QUA_LO 0 // quality of filter or reverb. Must be 0,1,2,3. +#define QUA_MED 1 +#define QUA_HI 2 +#define QUA_VHI 3 +#define QUA_MAX QUA_VHI + +#define CPRCPARAMS 16 // up to 16 floating point params for each processor type + +// processor definition - one for each running instance of a dsp processor + +struct prc_t +{ + int type; // PRC type + + float prm[CPRCPARAMS]; // dsp processor parameters - array of floats + + prc_Param_t pfnParam; // allocation function - takes ptr to prc, returns ptr to specialized data struct for proc type + prc_GetNext_t pfnGetNext; // get next function + prc_GetNextN_t pfnGetNextN; // batch version of get next + prc_Free_t pfnFree; // free function + prc_Mod_t pfnMod; // modulation function + + void *pdata; // processor state data - ie: pdly, pflt etc. +}; + +// processor parameter ranges - for validating parameters during allocation of new processor + +typedef struct prm_rng_t +{ + int iprm; // parameter index + float lo; // min value of parameter + float hi; // max value of parameter +} prm_rng_s; + +void PRC_CheckParams ( prc_t *pprc, prm_rng_t *prng ); + +/////////// +// Filters +/////////// + +#define CFLTS 64 // max number of filters simultaneously active +#define FLT_M 12 // max order of any filter + +#define FLT_LP 0 // lowpass filter +#define FLT_HP 1 // highpass filter +#define FLT_BP 2 // bandpass filter +#define FTR_MAX FLT_BP + +// flt parameters + +struct flt_t +{ + bool fused; // true if slot in use + + int b[FLT_M+1]; // filter numerator parameters (convert 0.0-1.0 to 0-PMAX representation) + int a[FLT_M+1]; // filter denominator parameters (convert 0.0-1.0 to 0-PMAX representation) + int w[FLT_M+1]; // filter state - samples (dimension of max (M, L)) + int L; // filter order numerator (dimension of a[M+1]) + int M; // filter order denominator (dimension of b[L+1]) + int N; // # of series sections - 1 (0 = 1 section, 1 = 2 sections etc) + + flt_t *pf1; // series cascaded versions of filter + flt_t *pf2; + flt_t *pf3; +}; + +// flt flts + +flt_t flts[CFLTS]; + +void FLT_Init ( flt_t *pf ) { if ( pf ) Q_memset ( pf, 0, sizeof (flt_t) ); } +void FLT_InitAll ( void ) { for ( int i = 0 ; i < CFLTS; i++ ) FLT_Init ( &flts[i] ); } + +void FLT_Free ( flt_t *pf ) +{ + if ( pf ) + { + if (pf->pf1) + Q_memset ( pf->pf1, 0, sizeof (flt_t) ); + + if (pf->pf2) + Q_memset ( pf->pf2, 0, sizeof (flt_t) ); + + if (pf->pf3) + Q_memset ( pf->pf3, 0, sizeof (flt_t) ); + + Q_memset ( pf, 0, sizeof (flt_t) ); + } +} + +void FLT_FreeAll ( void ) { for (int i = 0 ; i < CFLTS; i++) FLT_Free ( &flts[i] ); } + + +// find a free filter from the filter pool +// initialize filter numerator, denominator b[0..M], a[0..L] +// gain scales filter numerator +// N is # of series sections - 1 + +flt_t * FLT_Alloc ( int N, int M, int L, int *a, int *b, float gain ) +{ + int i, j; + flt_t *pf = NULL; + + for (i = 0; i < CFLTS; i++) + { + if ( !flts[i].fused ) + { + pf = &flts[i]; + + // transfer filter params into filter struct + pf->M = M; + pf->L = L; + pf->N = N; + + for (j = 0; j <= M; j++) + pf->a[j] = a[j]; + + for (j = 0; j <= L; j++) + pf->b[j] = (int)((float)(b[j]) * gain); + + pf->pf1 = NULL; + pf->pf2 = NULL; + pf->pf3 = NULL; + + pf->fused = true; + break; + } + } + + Assert(pf); // make sure we're not trying to alloc more than CFLTS flts + + return pf; +} + +// convert filter params cutoff and type into +// iir transfer function params M, L, a[], b[] + +// iir filter, 1st order, transfer function is H(z) = b0 + b1 Z^-1 / a0 + a1 Z^-1 +// or H(z) = b0 - b1 Z^-1 / a0 + a1 Z^-1 for lowpass + +// design cutoff filter at 3db (.5 gain) p579 + +void FLT_Design_3db_IIR ( float cutoff, float ftype, int *pM, int *pL, int *a, int *b ) +{ + // ftype: FLT_LP, FLT_HP, FLT_BP + + double Wc = 2.0 * M_PI * cutoff / SOUND_DMA_SPEED; // radians per sample + double Oc; + double fa; + double fb; + + // calculations: + // Wc = 2pi * fc/44100 convert to radians + // Oc = tan (Wc/2) * Gc / sqt ( 1 - Gc^2) get analog version, low pass + // Oc = tan (Wc/2) * (sqt (1 - Gc^2)) / Gc analog version, high pass + // Gc = 10 ^ (-Ac/20) gain at cutoff. Ac = 3db, so Gc^2 = 0.5 + // a = ( 1 - Oc ) / ( 1 + Oc ) + // b = ( 1 - a ) / 2 + + Oc = tan ( Wc / 2.0 ); + + fa = ( 1.0 - Oc ) / ( 1.0 + Oc ); + + fb = ( 1.0 - fa ) / 2.0; + + if ( ftype == FLT_HP ) + fb = ( 1.0 + fa ) / 2.0; + + a[0] = 0; // a0 always ignored + a[1] = (int)( -fa * PMAX ); // quantize params down to 0-PMAX >> PBITS + b[0] = (int)( fb * PMAX ); + b[1] = b[0]; + + if ( ftype == FLT_HP ) + b[1] = -b[1]; + + *pM = *pL = 1; + + return; +} + +// filter parameter order + +typedef enum +{ + flt_iftype, + flt_icutoff, + flt_iqwidth, + flt_iquality, + flt_igain, + + flt_cparam // # of params +} flt_e; + +// filter parameter ranges + +prm_rng_t flt_rng[] = { + + {flt_cparam, 0, 0}, // first entry is # of parameters + + {flt_iftype, 0, FTR_MAX}, // filter type FLT_LP, FLT_HP, FLT_BP + {flt_icutoff, 10, 22050}, // cutoff frequency in hz at -3db gain + {flt_iqwidth, 0, 11025}, // width of BP (cut in starts at cutoff) + {flt_iquality, 0, QUA_MAX}, // QUA_LO, _MED, _HI, _VHI = # of series sections + {flt_igain, 0.0, 10.0}, // output gain 0-10.0 +}; + + +// convert prc float params to iir filter params, alloc filter and return ptr to it +// filter quality set by prc quality - 0,1,2 + +flt_t * FLT_Params ( prc_t *pprc ) +{ + float qual = pprc->prm[flt_iquality]; + float cutoff = pprc->prm[flt_icutoff]; + float ftype = pprc->prm[flt_iftype]; + float qwidth = pprc->prm[flt_iqwidth]; + float gain = pprc->prm[flt_igain]; + + int L = 0; // numerator order + int M = 0; // denominator order + int b[FLT_M+1]; // numerator params 0..PMAX + int b_scaled[FLT_M+1]; // gain scaled numerator + int a[FLT_M+1]; // denominator params 0..PMAX + + int L_bp = 0; // bandpass numerator order + int M_bp = 0; // bandpass denominator order + int b_bp[FLT_M+1]; // bandpass numerator params 0..PMAX + int b_bp_scaled[FLT_M+1]; // gain scaled numerator + int a_bp[FLT_M+1]; // bandpass denominator params 0..PMAX + + int N; // # of series sections + bool bpass = false; + + // if qwidth > 0 then alloc bandpass filter (pf is lowpass) + + if ( qwidth > 0.0 ) + bpass = true; + + if (bpass) + { + ftype = FLT_LP; + } + + // low pass and highpass filter design + + // 1st order IIR filter, 3db cutoff at fc + + if ( bpass ) + { + // highpass section + + FLT_Design_3db_IIR ( cutoff, FLT_HP, &M_bp, &L_bp, a_bp, b_bp ); + M_bp = clamp (M_bp, 1, FLT_M); + L_bp = clamp (L_bp, 1, FLT_M); + cutoff += qwidth; + } + + // lowpass section + + FLT_Design_3db_IIR ( cutoff, (int)ftype, &M, &L, a, b ); + + M = clamp (M, 1, FLT_M); + L = clamp (L, 1, FLT_M); + + // quality = # of series sections - 1 + + N = clamp ((int)qual, 0, 3); + + // make sure we alloc at least 2 filters + + if (bpass) + N = max(N, 1); + + flt_t *pf0 = NULL; + flt_t *pf1 = NULL; + flt_t *pf2 = NULL; + flt_t *pf3 = NULL; + + // scale b numerators with gain - only scale for first filter if series filters + + for (int i = 0; i < FLT_M; i++) + { + b_bp_scaled[i] = (int)((float)(b_bp[i]) * gain ); + b_scaled[i] = (int)((float)(b[i]) * gain ); + } + + if (bpass) + { + // 1st filter is lowpass + + pf0 = FLT_Alloc ( N, M_bp, L_bp, a_bp, b_bp_scaled, 1.0 ); + } + else + { + pf0 = FLT_Alloc ( N, M, L, a, b_scaled, 1.0 ); + } + + // allocate series filters + + if (pf0) + { + switch (N) + { + case 3: + // alloc last filter as lowpass also if FLT_BP + if (bpass) + pf3 = FLT_Alloc ( 0, M_bp, L_bp, a_bp, b_bp, 1.0 ); + else + pf3 = FLT_Alloc ( 0, M, L, a, b, 1.0 ); + case 2: + pf2 = FLT_Alloc ( 0, M, L, a, b, 1.0 ); + case 1: + pf1 = FLT_Alloc ( 0, M, L, a, b, 1.0 ); + case 0: + break; + } + + pf0->pf1 = pf1; + pf0->pf2 = pf2; + pf0->pf3 = pf3; + } + + return pf0; +} + +inline void * FLT_VParams ( void *p ) +{ + PRC_CheckParams( (prc_t *)p, flt_rng); + return (void *) FLT_Params ((prc_t *)p); +} + +inline void FLT_Mod ( void *p, float v ) { return; } + +// get next filter value for filter pf and input x + +inline int FLT_GetNext ( flt_t *pf, int x ) +{ + flt_t *pf1; + flt_t *pf2; + flt_t *pf3; + int y; + + switch( pf->N ) + { + default: + case 0: + return IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x); + case 1: + pf1 = pf->pf1; + + y = IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x); + return IIRFilter_Update_Order1(pf1->a, pf1->L, pf1->b, pf1->w, y); + case 2: + pf1 = pf->pf1; + pf2 = pf->pf2; + + y = IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x); + y = IIRFilter_Update_Order1(pf1->a, pf1->L, pf1->b, pf1->w, y); + return IIRFilter_Update_Order1(pf2->a, pf2->L, pf2->b, pf2->w, y); + case 3: + pf1 = pf->pf1; + pf2 = pf->pf2; + pf3 = pf->pf3; + + y = IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x); + y = IIRFilter_Update_Order1(pf1->a, pf1->L, pf1->b, pf1->w, y); + y = IIRFilter_Update_Order1(pf2->a, pf2->L, pf2->b, pf2->w, y); + return IIRFilter_Update_Order1(pf3->a, pf3->L, pf3->b, pf3->w, y); + } +} + +// batch version for performance + +inline void FLT_GetNextN( flt_t *pflt, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch (op) + { + default: + case OP_LEFT: + while (count--) + { + pb->left = FLT_GetNext( pflt, pb->left ); + pb++; + } + return; + case OP_RIGHT: + while (count--) + { + pb->right = FLT_GetNext( pflt, pb->right ); + pb++; + } + return; + case OP_LEFT_DUPLICATE: + while (count--) + { + pb->left = pb->right = FLT_GetNext( pflt, pb->left ); + pb++; + } + return; + } +} + +/////////////////////////////////////////////////////////////////////////// +// Positional updaters for pitch shift etc +/////////////////////////////////////////////////////////////////////////// + +// looping position within a wav, with integer and fractional parts +// used for pitch shifting, upsampling/downsampling +// 20 bits of fraction, 8+ bits of integer + +struct pos_t +{ + + fix20int step; // wave table whole and fractional step value + fix20int cstep; // current cummulative step value + int pos; // current position within wav table + + int D; // max dimension of array w[0...D] ie: # of samples = D+1 +}; + +// circular wrap of pointer p, relative to array w +// D max buffer index w[0...D] (count of samples in buffer is D+1) +// i circular index + +inline void POS_Wrap ( int D, int *i ) +{ + if ( *i > D ) + *i -= D + 1; // when *pi = D + 1, it wraps around to *pi = 0 + + if ( *i < 0 ) + *i += D + 1; // when *pi = - 1, it wraps around to *pi = D +} + +// set initial update value - fstep can have no more than 8 bits of integer and 20 bits of fract +// D is array max dimension w[0...D] (ie: size D+1) +// w is ptr to array +// p is ptr to pos_t to initialize + +inline void POS_Init( pos_t *p, int D, float fstep ) +{ + float step = fstep; + + // make sure int part of step is capped at fix20_intmax + + if ((int)step > FIX20_INTMAX) + step = (step - (int)step) + FIX20_INTMAX; + + p->step = FLOAT_TO_FIX20(step); // convert fstep to fixed point + p->cstep = 0; + p->pos = 0; // current update value + + p->D = D; // always init to end value, in case we're stepping backwards +} + +// change step value - this is an instantaneous change, not smoothed. + +inline void POS_ChangeVal( pos_t *p, float fstepnew ) +{ + p->step = FLOAT_TO_FIX20( fstepnew ); // convert fstep to fixed point +} + +// return current integer position, then update internal position value + +inline int POS_GetNext ( pos_t *p ) +{ + + //float f = FIX20_TO_FLOAT(p->cstep); + //int i1 = FIX20_INTPART(p->cstep); + //float f1 = FIX20_TO_FLOAT(FIX20_FRACPART(p->cstep)); + //float f2 = FIX20_TO_FLOAT(p->step); + + p->cstep += p->step; // update accumulated fraction step value (fixed point) + p->pos += FIX20_INTPART( p->cstep ); // update pos with integer part of accumulated step + p->cstep = FIX20_FRACPART( p->cstep ); // throw away the integer part of accumulated step + + // wrap pos around either end of buffer if needed + + POS_Wrap(p->D, &(p->pos)); + + // make sure returned position is within array bounds + + Assert (p->pos <= p->D); + + return p->pos; +} + +// oneshot position within wav +struct pos_one_t +{ + pos_t p; // pos_t + + bool fhitend; // flag indicating we hit end of oneshot wav +}; + +// set initial update value - fstep can have no more than 8 bits of integer and 20 bits of fract +// one shot position - play only once, don't wrap, when hit end of buffer, return last position + +inline void POS_ONE_Init( pos_one_t *p1, int D, float fstep ) +{ + POS_Init( &p1->p, D, fstep ) ; + + p1->fhitend = false; +} + +// return current integer position, then update internal position value + +inline int POS_ONE_GetNext ( pos_one_t *p1 ) +{ + int pos; + pos_t *p0; + + pos = p1->p.pos; // return current position + + if (p1->fhitend) + return pos; + + p0 = &(p1->p); + p0->cstep += p0->step; // update accumulated fraction step value (fixed point) + p0->pos += FIX20_INTPART( p0->cstep ); // update pos with integer part of accumulated step + //p0->cstep = SIGN(p0->cstep) * FIX20_FRACPART( p0->cstep ); + p0->cstep = FIX20_FRACPART( p0->cstep ); // throw away the integer part of accumulated step + + // if we wrapped, stop updating, always return last position + // if step value is 0, return hit end + + if (!p0->step || p0->pos < 0 || p0->pos >= p0->D ) + p1->fhitend = true; + else + pos = p0->pos; + + // make sure returned value is within array bounds + + Assert ( pos <= p0->D ); + + return pos; +} + + +///////////////////// +// Reverbs and delays +///////////////////// + +#define CDLYS 128 // max delay lines active. Also used for lfos. + +#define DLY_PLAIN 0 // single feedback loop +#define DLY_ALLPASS 1 // feedback and feedforward loop - flat frequency response (diffusor) +#define DLY_LOWPASS 2 // lowpass filter in feedback loop +#define DLY_LINEAR 3 // linear delay, no feedback, unity gain +#define DLY_FLINEAR 4 // linear delay with lowpass filter and output gain +#define DLY_LOWPASS_4TAP 5 // lowpass filter in feedback loop, 4 delay taps +#define DLY_PLAIN_4TAP 6 // single feedback loop, 4 delay taps + +#define DLY_MAX DLY_PLAIN_4TAP + +#define DLY_HAS_MULTITAP(a) ((a) == DLY_LOWPASS_4TAP || (a) == DLY_PLAIN_4TAP) +#define DLY_HAS_FILTER(a) ((a) == DLY_FLINEAR || (a) == DLY_LOWPASS || (a) == DLY_LOWPASS_4TAP) + +#define DLY_TAP_FEEDBACK_GAIN 0.25 // drop multitap feedback to compensate for sum of taps in dly_*multitap() + +#define DLY_NORMALIZING_REDUCTION_MAX 0.25 // don't reduce gain (due to feedback) below N% of original gain + +// delay line + +struct dly_t +{ + + bool fused; // true if dly is in use + int type; // delay type + + int D; // delay size, in samples + int t; // current tap, <= D + int tnew; // crossfading to tnew + int xf; // crossfade value of t (0..PMAX) + int t1,t2,t3; // additional taps for multi-tap delays + int a1,a2,a3; // feedback values for taps + int D0; // original delay size (only relevant if calling DLY_ChangeVal) + int *p; // circular buffer pointer + int *w; // array of samples + + int a; // feedback value 0..PMAX,normalized to 0-1.0 + int b; // gain value 0..PMAX, normalized to 0-1.0 + + flt_t *pflt; // pointer to filter, if type DLY_LOWPASS +}; + +dly_t dlys[CDLYS]; // delay lines + +void DLY_Init ( dly_t *pdly ) { if ( pdly ) Q_memset( pdly, 0, sizeof (dly_t)); } +void DLY_InitAll ( void ) { for (int i = 0 ; i < CDLYS; i++) DLY_Init ( &dlys[i] ); } +void DLY_Free ( dly_t *pdly ) +{ + // free memory buffer + + if ( pdly ) + { + FLT_Free ( pdly->pflt ); + + if ( pdly->w ) + { + delete[] pdly->w; + } + + // free dly slot + + Q_memset ( pdly, 0, sizeof (dly_t) ); + } +} + + +void DLY_FreeAll ( void ) { for (int i = 0; i < CDLYS; i++ ) DLY_Free ( &dlys[i] ); } + +// return adjusted feedback value for given dly +// such that decay time is same as that for dmin and fbmin + +// dmin - minimum delay +// fbmin - minimum feedback +// dly - delay to match decay to dmin, fbmin + +float DLY_NormalizeFeedback ( int dmin, float fbmin, int dly ) +{ + // minimum decay time T to -60db for a simple reverb is: + + // Tmin = (ln 10^-3 / Ln fbmin) * (Dmin / fs) + + // where fs = sample frequency + + // similarly, + + // Tdly = (ln 10^-3 / Ln fb) * (D / fs) + + // setting Tdly = Tmin and solving for fb gives: + + // D / Dmin = ln fb / ln fbmin + + // since y^x = z gives x = ln z / ln y + + // fb = fbmin ^ (D/Dmin) + + float fb = powf (fbmin, (float)dly / (float) dmin); + + return fb; +} + +// set up 'b' gain parameter of feedback delay to +// compensate for gain caused by feedback 'fb'. + +void DLY_SetNormalizingGain ( dly_t *pdly, int feedback ) +{ + // compute normalized gain, set as output gain + + // calculate gain of delay line with feedback, and use it to + // reduce output. ie: force delay line with feedback to unity gain + + // for constant input x with feedback fb: + + // out = x + x*fb + x * fb^2 + x * fb^3... + // gain = out/x + // so gain = 1 + fb + fb^2 + fb^3... + // which, by the miracle of geometric series, equates to 1/1-fb + // thus, gain = 1/(1-fb) + + float fgain = 0; + float gain; + int b; + float fb = (float)feedback; + + fb = fb / (float)PMAX; + fb = fpmin(fb, 0.999f); + + // if b is 0, set b to PMAX (1) + + b = pdly->b ? pdly->b : PMAX; + + fgain = 1.0 / (1.0 - fb); + + // compensating gain - multiply rva output by gain then >> PBITS + + gain = (int)((1.0 / fgain) * PMAX); + + gain = gain * 4; // compensate for fact that gain calculation is for +/- 32767 amplitude wavs + // ie: ok to allow a bit more gain because most wavs are not at theoretical peak amplitude at all times + + // limit gain reduction to N% PMAX + + gain = clamp (gain, (float)(PMAX * DLY_NORMALIZING_REDUCTION_MAX), (float)PMAX); + + gain = ((float)b/(float)PMAX) * gain; // scale final gain by pdly->b. + + pdly->b = (int)gain; +} + +void DLY_ChangeTaps ( dly_t *pdly, int t0, int t1, int t2, int t3 ); + +// allocate a new delay line +// D number of samples to delay +// a feedback value (0-PMAX normalized to 0.0-1.0) +// b gain value (0-PMAX normalized to 0.0-1.0) - this is folded into the filter fb params +// if DLY_LOWPASS or DLY_FLINEAR: +// L - numerator order of filter +// M - denominator order of filter +// fb - numerator params, M+1 +// fa - denominator params, L+1 + +dly_t * DLY_AllocLP ( int D, int a, int b, int type, int M, int L, int *fa, int *fb ) +{ + int *w; + int i; + dly_t *pdly = NULL; + int feedback; + + // find open slot + + for (i = 0; i < CDLYS; i++) + { + if (!dlys[i].fused) + { + pdly = &dlys[i]; + DLY_Init( pdly ); + break; + } + } + + if ( i == CDLYS ) + { + DevMsg ("DSP: Warning, failed to allocate delay line.\n" ); + return NULL; // all delay lines in use + } + + // save original feedback value + + feedback = a; + + // adjust feedback a, gain b if delay is multitap unit + + if ( DLY_HAS_MULTITAP(type) ) + { + // split output gain over 4 taps + + b = (int)((float)(b) * DLY_TAP_FEEDBACK_GAIN); + } + + if ( DLY_HAS_FILTER(type) ) + { + // alloc lowpass iir_filter + // delay feedback gain is built into filter gain + + float gain = (float)a / (float)(PMAX); + + pdly->pflt = FLT_Alloc( 0, M, L, fa, fb, gain ); + if ( !pdly->pflt ) + { + DevMsg ("DSP: Warning, failed to allocate filter for delay line.\n" ); + return NULL; + } + } + + // alloc delay memory + w = new int[D+1]; + if ( !w ) + { + Warning( "Sound DSP: Failed to lock.\n"); + FLT_Free ( pdly->pflt ); + return NULL; + } + + // clear delay array + + Q_memset (w, 0, sizeof(int) * (D+1)); + + // init values + + pdly->type = type; + pdly->D = D; + pdly->t = D; // set delay tap to full delay + pdly->tnew = D; + pdly->xf = 0; + pdly->D0 = D; + pdly->p = w; // init circular pointer to head of buffer + pdly->w = w; + pdly->a = min( a, PMAX - 1 ); // do not allow 100% feedback + pdly->b = b; + pdly->fused = true; + + if ( type == DLY_LINEAR || type == DLY_FLINEAR ) + { + // linear delay has no feedback and unity gain + + pdly->a = 0; + pdly->b = PMAX; + } + else + { + // adjust b to compensate for feedback gain of steady state max input + + DLY_SetNormalizingGain( pdly, feedback ); + } + + if ( DLY_HAS_MULTITAP(type) ) + { + // initially set up all taps to same value - caller uses DLY_ChangeTaps to change values + + DLY_ChangeTaps( pdly, D, D, D, D ); + } + + return (pdly); +} + +// allocate lowpass or allpass delay + +dly_t * DLY_Alloc( int D, int a, int b, int type ) +{ + return DLY_AllocLP( D, a, b, type, 0, 0, 0, 0 ); +} + + +// Allocate new delay, convert from float params in prc preset to internal parameters +// Uses filter params in prc if delay is type lowpass + +// delay parameter order + +typedef enum { + + dly_idtype, // NOTE: first 8 params must match those in mdy_e + dly_idelay, + dly_ifeedback, + dly_igain, + + dly_iftype, + dly_icutoff, + dly_iqwidth, + dly_iquality, + + dly_itap1, + dly_itap2, + dly_itap3, + + dly_cparam + +} dly_e; + + +// delay parameter ranges + +prm_rng_t dly_rng[] = { + + {dly_cparam, 0, 0}, // first entry is # of parameters + + // delay params + + {dly_idtype, 0, DLY_MAX}, // delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS etc + {dly_idelay, -1.0, 1000.0}, // delay in milliseconds (-1 forces auto dsp to set delay value from room size) + {dly_ifeedback, 0.0, 0.99}, // feedback 0-1.0 + {dly_igain, 0.0, 10.0}, // final gain of output stage, 0-10.0 + + // filter params if dly type DLY_LOWPASS or DLY_FLINEAR + + {dly_iftype, 0, FTR_MAX}, + {dly_icutoff, 10.0, 22050.0}, + {dly_iqwidth, 100.0, 11025.0}, + {dly_iquality, 0, QUA_MAX}, + // note: -1 flag tells auto dsp to get value directly from room size + {dly_itap1, -1.0, 1000.0}, // delay in milliseconds NOTE: delay > tap3 > tap2 > tap1 + {dly_itap2, -1.0, 1000.0}, // delay in milliseconds + {dly_itap3, -1.0, 1000.0}, // delay in milliseconds +}; + +dly_t * DLY_Params ( prc_t *pprc ) +{ + dly_t *pdly = NULL; + int D, a, b; + + float delay = fabs(pprc->prm[dly_idelay]); + float feedback = pprc->prm[dly_ifeedback]; + float gain = pprc->prm[dly_igain]; + int type = pprc->prm[dly_idtype]; + + float ftype = pprc->prm[dly_iftype]; + float cutoff = pprc->prm[dly_icutoff]; + float qwidth = pprc->prm[dly_iqwidth]; + float qual = pprc->prm[dly_iquality]; + + float t1 = fabs(pprc->prm[dly_itap1]); + float t2 = fabs(pprc->prm[dly_itap2]); + float t3 = fabs(pprc->prm[dly_itap3]); + + D = MSEC_TO_SAMPS(delay); // delay samples + a = feedback * PMAX; // feedback + b = gain * PMAX; // gain + + switch ( (int) type ) + { + case DLY_PLAIN: + case DLY_PLAIN_4TAP: + case DLY_ALLPASS: + case DLY_LINEAR: + pdly = DLY_Alloc( D, a, b, type ); + break; + + case DLY_FLINEAR: + case DLY_LOWPASS: + case DLY_LOWPASS_4TAP: + { + // set up dummy lowpass filter to convert params + + prc_t prcf; + + prcf.prm[flt_iquality] = qual; // 0,1,2 - (0 or 1 low quality implies faster execution time) + prcf.prm[flt_icutoff] = cutoff; + prcf.prm[flt_iftype] = ftype; + prcf.prm[flt_iqwidth] = qwidth; + prcf.prm[flt_igain] = 1.0; + + flt_t *pflt = (flt_t *)FLT_Params ( &prcf ); + + if ( !pflt ) + { + DevMsg ("DSP: Warning, failed to allocate filter.\n" ); + return NULL; + } + + pdly = DLY_AllocLP ( D, a, b, type, pflt->M, pflt->L, pflt->a, pflt->b ); + + FLT_Free ( pflt ); + break; + } + } + + // set up multi-tap delays + + if ( pdly && DLY_HAS_MULTITAP((int)type) ) + DLY_ChangeTaps( pdly, D, MSEC_TO_SAMPS(t1), MSEC_TO_SAMPS(t2), MSEC_TO_SAMPS(t3) ); + + return pdly; +} + +inline void * DLY_VParams ( void *p ) +{ + PRC_CheckParams( (prc_t *)p, dly_rng ); + return (void *) DLY_Params ((prc_t *)p); +} + +// get next value from delay line, move x into delay line + +inline int DLY_GetNext ( dly_t *pdly, int x ) +{ + switch (pdly->type) + { + default: + case DLY_PLAIN: + return ReverbSimple( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, x ); + case DLY_ALLPASS: + return DelayAllpass( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, x ); + case DLY_LOWPASS: + return DelayLowpass( pdly->D, pdly->t, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x ); + case DLY_LINEAR: + return DelayLinear( pdly->D, pdly->t, pdly->w, &pdly->p, x ); + case DLY_FLINEAR: + return DelayLinear_lowpass( pdly->D, pdly->t, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x ); + case DLY_PLAIN_4TAP: + return ReverbSimple_multitap( pdly->D, pdly->t, pdly->t1, pdly->t2, pdly->t3, pdly->w, &pdly->p, pdly->a, pdly->b, x ); + case DLY_LOWPASS_4TAP: + return DelayLowpass_multitap( pdly->D, pdly->t, pdly->t1, pdly->t2,pdly->t3, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x ); + } +} + +inline int DLY_GetNextXfade ( dly_t *pdly, int x ) +{ + + switch (pdly->type) + { + default: + case DLY_PLAIN: + return ReverbSimple_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &pdly->p, pdly->a, pdly->b, x ); + case DLY_ALLPASS: + return DelayAllpass_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &pdly->p, pdly->a, pdly->b, x ); + case DLY_LOWPASS: + return DelayLowpass_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x ); + case DLY_LINEAR: + return DelayLinear_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &pdly->p, x ); + case DLY_FLINEAR: + return DelayLinear_lowpass_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x ); + case DLY_PLAIN_4TAP: + return ReverbSimple_multitap_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->t1, pdly->t2, pdly->t3, pdly->w, &pdly->p, pdly->a, pdly->b, x ); + case DLY_LOWPASS_4TAP: + return DelayLowpass_multitap_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->t1, pdly->t2, pdly->t3, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x ); + } +} + +// batch version for performance +// UNDONE: a) unwind this more - pb increments by 2 to avoid pb->left or pb->right deref. +// UNDONE: b) all filter and delay params are dereferenced outside of DLY_GetNext and passed as register values +// UNDONE: c) pull case statement in dly_getnext out, so loop directly calls the inline dly_*() routine. + +inline void DLY_GetNextN( dly_t *pdly, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch (op) + { + default: + case OP_LEFT: + while (count--) + { + pb->left = DLY_GetNext( pdly, pb->left ); + pb++; + } + return; + case OP_RIGHT: + while (count--) + { + pb->right = DLY_GetNext( pdly, pb->right ); + pb++; + } + return; + case OP_LEFT_DUPLICATE: + while (count--) + { + pb->left = pb->right = DLY_GetNext( pdly, pb->left ); + pb++; + } + return; + } +} + +// get tap on t'th sample in delay - don't update buffer pointers, this is done via DLY_GetNext +// Only valid for DLY_LINEAR. + +inline int DLY_GetTap ( dly_t *pdly, int t ) +{ + return GetDly (pdly->D, pdly->w, pdly->p, t ); +} + +#define SWAP(a,b,t) {(t) = (a); (a) = (b); (b) = (t);} + +// make instantaneous change to tap values t0..t3 +// all values of t must be less than original delay D +// only processed for DLY_LOWPASS_4TAP & DLY_PLAIN_4TAP +// NOTE: pdly->a feedback must have been set before this call! +void DLY_ChangeTaps ( dly_t *pdly, int t0, int t1, int t2, int t3 ) +{ + if (!pdly) + return; + + int temp; + + // sort taps to make sure t3 > t2 > t1 > t0 ! + + for (int i = 0; i < 4; i++) + { + if (t0 > t1) SWAP(t0, t1, temp); + if (t1 > t2) SWAP(t1, t2, temp); + if (t2 > t3) SWAP(t2, t3, temp); + } + + pdly->t = min ( t0, pdly->D0 ); + pdly->t1 = min ( t1, pdly->D0 ); + pdly->t2 = min ( t2, pdly->D0 ); + pdly->t3 = min ( t3, pdly->D0 ); + +} + +// make instantaneous change for first delay tap 't' to new delay value. +// t tap value must be <= original D (ie: we don't do any reallocation here) + +void DLY_ChangeVal ( dly_t *pdly, int t ) +{ + // never set delay > original delay + + pdly->t = min ( t, pdly->D0 ); +} + +// ignored - use MDY_ for modulatable delay + +inline void DLY_Mod ( void *p, float v ) { return; } + + +///////////////////////////////////////////////////////////////////////////// +// Ramp - used for varying smoothly between int parameters ie: modulation delays +///////////////////////////////////////////////////////////////////////////// + + +struct rmp_t +{ + int initval; // initial ramp value + int target; // final ramp value + int sign; // increasing (1) or decreasing (-1) ramp + + int yprev; // previous output value + bool fhitend; // true if hit end of ramp + bool bEndAtTime; // if true, fhitend is true when ramp time is hit (even if target not hit) + // if false, then fhitend is true only when target is hit + pos_one_t ps; // current ramp output +}; + +// ramp smoothly between initial value and target value in approx 'ramptime' seconds. +// (initial value may be greater or less than target value) +// never changes output by more than +1 or -1 (which can cause the ramp to take longer to complete than ramptime - see bEndAtTime) +// called once per sample while ramping +// ramptime - duration of ramp in seconds +// initval - initial ramp value +// targetval - target ramp value +// if bEndAtTime is true, then RMP_HitEnd returns true when ramp time is reached, EVEN IF TARGETVAL IS NOT REACHED +// if bEndAtTime is false, then RMP_HitEnd returns true when targetval is reached, EVEN IF DELTA IN RAMP VALUES IS > +/- 1 + +void RMP_Init( rmp_t *prmp, float ramptime, int initval, int targetval, bool bEndAtTime ) +{ + int rise; + int run; + + if (prmp) + Q_memset( prmp, 0, sizeof (rmp_t) ); + else + return; + + run = (int) (ramptime * SOUND_DMA_SPEED); // 'samples' in ramp + rise = (targetval - initval); // height of ramp + + // init fixed point iterator to iterate along the height of the ramp 'rise' + // always iterates from 0..'rise', increasing in value + + POS_ONE_Init( &prmp->ps, ABS( rise ), ABS((float) rise) / ((float) run) ); + + prmp->yprev = initval; + prmp->initval = initval; + prmp->target = targetval; + prmp->sign = SIGN( rise ); + prmp->bEndAtTime = bEndAtTime; + +} + +// continues from current position to new target position + +void RMP_SetNext( rmp_t *prmp, float ramptime, int targetval ) +{ + RMP_Init ( prmp, ramptime, prmp->yprev, targetval, prmp->bEndAtTime ); +} + +inline bool RMP_HitEnd ( rmp_t *prmp ) +{ + return prmp->fhitend; +} + +inline void RMP_SetEnd ( rmp_t *prmp ) +{ + prmp->fhitend = true; +} + +// get next ramp value & update ramp, if bEndAtTime is true, never varies by more than +1 or -1 between calls +// when ramp hits target value, it thereafter always returns last value + +inline int RMP_GetNext( rmp_t *prmp ) +{ + int y; + int d; + + // if we hit ramp end, return last value + + if (prmp->fhitend) + return prmp->yprev; + + // get next integer position in ramp height. + + d = POS_ONE_GetNext( &prmp->ps ); + + if ( prmp->ps.fhitend ) + prmp->fhitend = true; + + // increase or decrease from initval, depending on ramp sign + + if ( prmp->sign > 0 ) + y = prmp->initval + d; + else + y = prmp->initval - d; + + // if bEndAtTime is true, only update current height by a max of +1 or -1 + // this also means that for short ramp times, we may not hit target + + if (prmp->bEndAtTime) + { + if ( ABS( y - prmp->yprev ) >= 1 ) + prmp->yprev += prmp->sign; + } + else + { + // always hits target - but varies by more than +/- 1 + + prmp->yprev = y; + } + + return prmp->yprev; +} + +// get current ramp value, don't update ramp + +inline int RMP_GetCurrent( rmp_t *prmp ) +{ + return prmp->yprev; +} + + +////////////// +// mod delay +////////////// + +// modulate delay time anywhere from 0..D using MDY_ChangeVal. no output glitches (uses RMP) + +#define CMDYS 64 // max # of mod delays active (steals from delays) + +struct mdy_t +{ + bool fused; + + bool fchanging; // true if modulating to new delay value + + dly_t *pdly; // delay + + float ramptime; // ramp 'glide' time - time in seconds to change between values + + int mtime; // time in samples between delay changes. 0 implies no self-modulating + int mtimecur; // current time in samples until next delay change + float depth; // modulate delay from D to D - (D*depth) depth 0-1.0 + + int mix; // PMAX as % processed fx signal mix + + rmp_t rmp_interp; // interpolation ramp 0...PMAX + + bool bPhaseInvert; // if true, invert phase of output + +}; + +mdy_t mdys[CMDYS]; + +void MDY_Init( mdy_t *pmdy ) { if (pmdy) Q_memset( pmdy, 0, sizeof (mdy_t) ); }; +void MDY_Free( mdy_t *pmdy ) { if (pmdy) { DLY_Free (pmdy->pdly); Q_memset( pmdy, 0, sizeof (mdy_t) ); } }; +void MDY_InitAll() { for (int i = 0; i < CMDYS; i++) MDY_Init( &mdys[i] ); }; +void MDY_FreeAll() { for (int i = 0; i < CMDYS; i++) MDY_Free( &mdys[i] ); }; + + +// allocate mod delay, given previously allocated dly (NOTE: mod delay only sweeps tap 0, not t1,t2 or t3) +// ramptime is time in seconds for delay to change from dcur to dnew +// modtime is time in seconds between modulations. 0 if no self-modulation +// depth is 0-1.0 multiplier, new delay values when modulating are Dnew = randomlong (D - D*depth, D) +// mix - 0-1.0, default 1.0 for 100% fx mix - pans between input signal and fx signal + +mdy_t *MDY_Alloc ( dly_t *pdly, float ramptime, float modtime, float depth, float mix ) +{ + int i; + mdy_t *pmdy; + + if ( !pdly ) + return NULL; + + for (i = 0; i < CMDYS; i++) + { + if ( !mdys[i].fused ) + { + pmdy = &mdys[i]; + + MDY_Init ( pmdy ); + + pmdy->pdly = pdly; + + if ( !pmdy->pdly ) + { + DevMsg ("DSP: Warning, failed to allocate delay for mod delay.\n" ); + return NULL; + } + + pmdy->fused = true; + pmdy->ramptime = ramptime; + pmdy->mtime = SEC_TO_SAMPS(modtime); + pmdy->mtimecur = pmdy->mtime; + pmdy->depth = depth; + pmdy->mix = int ( PMAX * mix ); + pmdy->bPhaseInvert = false; + + return pmdy; + } + } + + DevMsg ("DSP: Warning, failed to allocate mod delay.\n" ); + return NULL; +} + +// change to new delay tap value t samples, ramp linearly over ramptime seconds + +void MDY_ChangeVal ( mdy_t *pmdy, int t ) +{ + // if D > original delay value, cap at original value + + t = min (pmdy->pdly->D0, t); + + pmdy->fchanging = true; + + // init interpolation ramp - always hit target + + RMP_Init ( &pmdy->rmp_interp, pmdy->ramptime, 0, PMAX, false ); + + // init delay xfade values + + pmdy->pdly->tnew = t; + pmdy->pdly->xf = 0; +} + +// interpolate between current and target delay values + +inline int MDY_GetNext( mdy_t *pmdy, int x ) +{ + int xout; + + if ( !pmdy->fchanging ) + { + // not modulating... + + xout = DLY_GetNext( pmdy->pdly, x ); + + if ( !pmdy->mtime ) + { + // return right away if not modulating (not changing and not self modulating) + + goto mdy_return; + } + } + else + { + // modulating... + + xout = DLY_GetNextXfade( pmdy->pdly, x ); + + // get xfade ramp & set up delay xfade value for next call to DLY_GetNextXfade() + + pmdy->pdly->xf = RMP_GetNext( &pmdy->rmp_interp ); // 0...PMAX + + if ( RMP_HitEnd( &pmdy->rmp_interp ) ) + { + // done. set delay tap & value = target + + DLY_ChangeVal( pmdy->pdly, pmdy->pdly->tnew ); + + pmdy->pdly->t = pmdy->pdly->tnew; + + pmdy->fchanging = false; + } + } + + // if self-modulating and timer has expired, get next change + + if ( pmdy->mtime && !pmdy->mtimecur-- ) + { + pmdy->mtimecur = pmdy->mtime; + + int D0 = pmdy->pdly->D0; + int Dnew; + float D1; + + // modulate between 0 and 100% of d0 + + D1 = (float)D0 * (1.0 - pmdy->depth); + + Dnew = RandomInt( (int)D1, D0 ); + + // set up modulation to new value + + MDY_ChangeVal ( pmdy, Dnew ); + } + +mdy_return: + + // reverse phase of output + + if ( pmdy->bPhaseInvert ) + xout = -xout; + + // 100% fx mix + + if ( pmdy->mix == PMAX) + return xout; + + // special case 50/50 mix + + if ( pmdy->mix == PMAX / 2) + return ( (xout + x) >> 1 ); + + // return mix of input and processed signal + + return ( x + (((xout - x) * pmdy->mix) >> PBITS) ); +} + + +// batch version for performance +// UNDONE: unwind MDY_GetNext so that it directly calls DLY_GetNextN: +// UNDONE: a) if not currently modulating and never self-modulating, then just unwind like DLY_GetNext +// UNDONE: b) if not currently modulating, figure out how many samples N until self-modulation timer kicks in again +// and stream out N samples just like DLY_GetNext + +inline void MDY_GetNextN( mdy_t *pmdy, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch (op) + { + default: + case OP_LEFT: + while (count--) + { + pb->left = MDY_GetNext( pmdy, pb->left ); + pb++; + } + return; + case OP_RIGHT: + while (count--) + { + pb->right = MDY_GetNext( pmdy, pb->right ); + pb++; + } + return; + case OP_LEFT_DUPLICATE: + while (count--) + { + pb->left = pb->right = MDY_GetNext( pmdy, pb->left ); + pb++; + } + return; + } +} + +// parameter order + +typedef enum { + + mdy_idtype, // NOTE: first 8 params must match params in dly_e + mdy_idelay, + mdy_ifeedback, + mdy_igain, + + mdy_iftype, + mdy_icutoff, + mdy_iqwidth, + mdy_iquality, + + mdy_imodrate, + mdy_imoddepth, + mdy_imodglide, + + mdy_imix, + mdy_ibxfade, + + mdy_cparam + +} mdy_e; + + +// parameter ranges + +prm_rng_t mdy_rng[] = { + + {mdy_cparam, 0, 0}, // first entry is # of parameters + + // delay params + + {mdy_idtype, 0, DLY_MAX}, // delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS + {mdy_idelay, 0.0, 1000.0}, // delay in milliseconds + {mdy_ifeedback, 0.0, 0.99}, // feedback 0-1.0 + {mdy_igain, 0.0, 1.0}, // final gain of output stage, 0-1.0 + + // filter params if mdy type DLY_LOWPASS + + {mdy_iftype, 0, FTR_MAX}, + {mdy_icutoff, 10.0, 22050.0}, + {mdy_iqwidth, 100.0, 11025.0}, + {mdy_iquality, 0, QUA_MAX}, + + {mdy_imodrate, 0.01, 200.0}, // frequency at which delay values change to new random value. 0 is no self-modulation + {mdy_imoddepth, 0.0, 1.0}, // how much delay changes (decreases) from current value (0-1.0) + {mdy_imodglide, 0.01, 100.0}, // glide time between dcur and dnew in milliseconds + {mdy_imix, 0.0, 1.0} // 1.0 = full fx mix, 0.5 = 50% fx, 50% dry +}; + + +// convert user parameters to internal parameters, allocate and return + +mdy_t * MDY_Params ( prc_t *pprc ) +{ + mdy_t *pmdy; + dly_t *pdly; + + float ramptime = pprc->prm[mdy_imodglide] / 1000.0; // get ramp time in seconds + float modtime = 0.0f; + if ( pprc->prm[mdy_imodrate] != 0.0f ) + { + modtime = 1.0 / pprc->prm[mdy_imodrate]; // time between modulations in seconds + } + float depth = pprc->prm[mdy_imoddepth]; // depth of modulations 0-1.0 + float mix = pprc->prm[mdy_imix]; + + // alloc plain, allpass or lowpass delay + + pdly = DLY_Params( pprc ); + + if ( !pdly ) + return NULL; + + pmdy = MDY_Alloc ( pdly, ramptime, modtime, depth, mix ); + + return pmdy; +} + +inline void * MDY_VParams ( void *p ) +{ + PRC_CheckParams ( (prc_t *)p, mdy_rng ); + return (void *) MDY_Params ((prc_t *)p); +} + +// v is +/- 0-1.0 +// change current delay value 0..D + +void MDY_Mod ( mdy_t *pmdy, float v ) +{ + + int D0 = pmdy->pdly->D0; // base delay value + float v2; + + // if v is < -2.0 then delay is v + 10.0 + // invert phase of output. hack. + + if ( v < -2.0 ) + { + v = v + 10.0; + pmdy->bPhaseInvert = true; + } + else + { + pmdy->bPhaseInvert = false; + } + + v2 = -(v + 1.0)/2.0; // v2 varies -1.0-0.0 + + // D0 varies 0..D0 + + D0 = D0 + (int)((float)D0 * v2); + + // change delay + + MDY_ChangeVal( pmdy, D0 ); + + return; +} + + +/////////////////// +// Parallel reverbs +/////////////////// + +// Reverb A +// M parallel reverbs, mixed to mono output + +#define CRVAS 64 // max number of parallel series reverbs active + +#define CRVA_DLYS 12 // max number of delays making up reverb_a + +struct rva_t +{ + bool fused; + int m; // number of parallel plain or lowpass delays + int fparallel; // true if filters in parallel with delays, otherwise single output filter + flt_t *pflt; // series filters + + dly_t *pdlys[CRVA_DLYS]; // array of pointers to delays + mdy_t *pmdlys[CRVA_DLYS]; // array of pointers to mod delays + + bool fmoddly; // true if using mod delays +}; + +rva_t rvas[CRVAS]; + +void RVA_Init ( rva_t *prva ) { if ( prva ) Q_memset (prva, 0, sizeof (rva_t)); } +void RVA_InitAll( void ) { for (int i = 0; i < CRVAS; i++) RVA_Init ( &rvas[i] ); } + +// free parallel series reverb + +void RVA_Free( rva_t *prva ) +{ + int i; + + if ( prva ) + { + // free all delays + for (i = 0; i < CRVA_DLYS; i++) + DLY_Free ( prva->pdlys[i] ); + + // zero all ptrs to delays in mdy array + for (i = 0; i < CRVA_DLYS; i++) + { + if ( prva->pmdlys[i] ) + prva->pmdlys[i]->pdly = NULL; + } + + // free all mod delays + for (i = 0; i < CRVA_DLYS; i++) + MDY_Free ( prva->pmdlys[i] ); + + FLT_Free( prva->pflt ); + + Q_memset( prva, 0, sizeof (rva_t) ); + } +} + + +void RVA_FreeAll( void ) { for (int i = 0; i < CRVAS; i++) RVA_Free( &rvas[i] ); } + +// create parallel reverb - m parallel reverbs summed + +// D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples) +// a array of reverb feedback parms for parallel reverbs (CRVB_P_DLYS) +// if a[i] < 0 then this is a predelay - use DLY_FLINEAR instead of DLY_LOWPASS +// b array of CRVB_P_DLYS - mix params for parallel reverbs +// m - number of parallel delays +// pflt - filter template, to be used by all parallel delays +// fparallel - true if filter operates in parallel with delays, otherwise filter output only +// fmoddly - > 0 if delays are all mod delays (milliseconds of delay modulation) +// fmodrate - # of delay repetitions between changes to mod delay +// ftaps - if > 0, use 4 taps per reverb delay unit (increases density) tap = D - n*ftaps n = 0,1,2,3 + +rva_t * RVA_Alloc ( int *D, int *a, int *b, int m, flt_t *pflt, int fparallel, float fmoddly, float fmodrate, float ftaps ) +{ + + int i; + int dtype; + rva_t *prva; + flt_t *pflt2 = NULL; + + bool btaps = ftaps > 0.0; + + // find open slot + + for ( i = 0; i < CRVAS; i++ ) + { + if ( !rvas[i].fused ) + break; + } + + // return null if no free slots + + if (i == CRVAS) + { + DevMsg ("DSP: Warning, failed to allocate reverb.\n" ); + return NULL; + } + + prva = &rvas[i]; + + // if series filter specified, alloc two series filters + + if ( pflt && !fparallel) + { + // use filter data as template for a filter on output (2 cascaded filters) + + pflt2 = FLT_Alloc (0, pflt->M, pflt->L, pflt->a, pflt->b, 1.0); + + if (!pflt2) + { + DevMsg ("DSP: Warning, failed to allocate flt for reverb.\n" ); + return NULL; + } + + pflt2->pf1 = FLT_Alloc (0, pflt->M, pflt->L, pflt->a, pflt->b, 1.0); + pflt2->N = 1; + } + + // allocate parallel delays + + for (i = 0; i < m; i++) + { + // set delay type + + if ( pflt && fparallel ) + // if a[i] param is < 0, allocate delay as predelay instead of feedback delay + dtype = a[i] < 0 ? DLY_FLINEAR : DLY_LOWPASS; + else + // if no filter specified, alloc as plain or multitap plain delay + dtype = btaps ? DLY_PLAIN_4TAP : DLY_PLAIN; + + if ( dtype == DLY_LOWPASS && btaps ) + dtype = DLY_LOWPASS_4TAP; + + // if filter specified and parallel specified, alloc 1 filter per delay + + if ( DLY_HAS_FILTER(dtype) ) + prva->pdlys[i] = DLY_AllocLP( D[i], abs(a[i]), b[i], dtype, pflt->M, pflt->L, pflt->a, pflt->b ); + else + prva->pdlys[i] = DLY_Alloc( D[i], abs(a[i]), b[i], dtype ); + + if ( DLY_HAS_MULTITAP(dtype) ) + { + // set up delay taps to increase density around delay value. + + // value of ftaps is the seed for all tap values + + float t1 = max((double)MSEC_TO_SAMPS(5), D[i] * (1.0 - ftaps * 3.141592) ); + float t2 = max((double)MSEC_TO_SAMPS(7), D[i] * (1.0 - ftaps * 1.697043) ); + float t3 = max((double)MSEC_TO_SAMPS(10), D[i] * (1.0 - ftaps * 0.96325) ); + + DLY_ChangeTaps( prva->pdlys[i], (int)t1, (int)t2, (int)t3, D[i] ); + } + } + + + if ( fmoddly > 0.0 ) + { + // alloc mod delays, using previously alloc'd delays + + // ramptime is time in seconds for delay to change from dcur to dnew + // modtime is time in seconds between modulations. 0 if no self-modulation + // depth is 0-1.0 multiplier, new delay values when modulating are Dnew = randomlong (D - D*depth, D) + + float ramptime; + float modtime; + float depth; + + for (i = 0; i < m; i++) + { + int Do = prva->pdlys[i]->D; + + modtime = (float)Do / (float)(SOUND_DMA_SPEED); // seconds per delay + depth = (fmoddly * 0.001f) / modtime; // convert milliseconds to 'depth' % + depth = clamp (depth, 0.01f, 0.99f); + modtime = modtime * fmodrate; // modulate every N delay passes + + ramptime = fpmin(20.0f/1000.0f, modtime / 2); // ramp between delay values in N ms + + prva->pmdlys[i] = MDY_Alloc( prva->pdlys[i], ramptime, modtime, depth, 1.0 ); + } + + prva->fmoddly = true; + } + + // if we failed to alloc any reverb, free all, return NULL + + for (i = 0; i < m; i++) + { + if ( !prva->pdlys[i] ) + { + FLT_Free( pflt2 ); + RVA_Free( prva ); + DevMsg ("DSP: Warning, failed to allocate delay for reverb.\n" ); + return NULL; + } + } + + prva->fused = true; + prva->m = m; + prva->fparallel = fparallel; + prva->pflt = pflt2; + return prva; +} + + +// parallel reverberator +// +// for each input sample x do: +// x0 = plain(D0,w0,&p0,a0,x) +// x1 = plain(D1,w1,&p1,a1,x) +// x2 = plain(D2,w2,&p2,a2,x) +// x3 = plain(D3,w3,&p3,a3,x) +// y = b0*x0 + b1*x1 + b2*x2 + b3*x3 +// +// rgdly - array of M delays: +// D - Delay values (typical - 29, 37, 44, 50, 27, 31) +// w - array of delayed values +// p - array of pointers to circular delay line pointers +// a - array of M feedback values (typical - all equal, like 0.75 * PMAX) +// b - array of M gain values for plain reverb outputs (1, .9, .8, .7) +// xin - input value +// if fparallel, filters are built into delays, +// otherwise, filter is in feedback loop + + +int g_MapIntoPBITSDivInt[] = +{ + 0, PMAX/1, PMAX/2, PMAX/3, PMAX/4, PMAX/5, PMAX/6, PMAX/7, PMAX/8, + PMAX/9, PMAX/10, PMAX/11,PMAX/12,PMAX/13,PMAX/14,PMAX/15,PMAX/16, +}; + +inline int RVA_GetNext( rva_t *prva, int x ) +{ + int m = prva->m; + int y = 0; + + if ( prva->fmoddly ) + { + // get output of parallel mod delays + + for (int i = 0; i < m; i++ ) + y += MDY_GetNext( prva->pmdlys[i], x ); + } + else + { + // get output of parallel delays + + for (int i = 0; i < m; i++ ) + y += DLY_GetNext( prva->pdlys[i], x ); + } + + // PERFORMANCE: y/m is now baked into the 'b' gain params for each delay ( b = b/m ) + // y = (y * g_MapIntoPBITSDivInt[m]) >> PBITS; + + if ( prva->fparallel ) + return y; + + // run series filters if present + + if ( prva->pflt ) + { + y = FLT_GetNext( prva->pflt, y); + } + + return y; +} + + +// batch version for performance +// UNDONE: unwind RVA_GetNextN so that it directly calls DLY_GetNextN or MDY_GetNextN + +inline void RVA_GetNextN( rva_t *prva, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch (op) + { + default: + case OP_LEFT: + while (count--) + { + pb->left = RVA_GetNext( prva, pb->left ); + pb++; + } + return; + case OP_RIGHT: + while (count--) + { + pb->right = RVA_GetNext( prva, pb->right ); + pb++; + } + return; + case OP_LEFT_DUPLICATE: + while (count--) + { + pb->left = pb->right = RVA_GetNext( prva, pb->left ); + pb++; + } + return; + } +} + +// reverb parameter order + +typedef enum +{ + +// parameter order + + rva_size_max, + rva_size_min, + + rva_inumdelays, + rva_ifeedback, + rva_igain, + + rva_icutoff, + + rva_ifparallel, + rva_imoddly, + rva_imodrate, + + rva_width, + rva_depth, + rva_height, + + rva_fbwidth, + rva_fbdepth, + rva_fbheight, + + rva_iftaps, + + rva_cparam // # of params +} rva_e; + +// filter parameter ranges + +prm_rng_t rva_rng[] = { + + {rva_cparam, 0, 0}, // first entry is # of parameters + + // reverb params + {rva_size_max, 0.0, 1000.0}, // max room delay in milliseconds + {rva_size_min, 0.0, 1000.0}, // min room delay in milliseconds + {rva_inumdelays,1.0, 12.0}, // controls # of parallel or series delays + {rva_ifeedback, 0.0, 1.0}, // feedback of delays + {rva_igain, 0.0, 10.0}, // output gain + + // filter params for each parallel reverb (quality set to 0 for max execution speed) + + {rva_icutoff, 10, 22050}, + + {rva_ifparallel, 0, 1}, // if 1, then all filters operate in parallel with delays. otherwise filter output only + {rva_imoddly, 0.0, 50.0}, // if > 0 then all delays are modulating delays, mod param controls milliseconds of mod depth + {rva_imodrate, 0.0, 10.0}, // how many delay repetitions pass between mod changes to delayl + + // override params - for more detailed description of room + // note: width/depth/height < 0 only for some automatic dsp presets + {rva_width, -1000.0, 1000.0}, // 0-1000.0 millisec (room width in feet) - used instead of size if non-zero + {rva_depth, -1000.0, 1000.0}, // 0-1000.0 room depth in feet - used instead of size if non-zero + {rva_height, -1000.0, 1000.0}, // 0-1000.0 room height in feet - used instead of size if non-zero + + {rva_fbwidth, -1.0, 1.0}, // 0-1.0 material reflectivity - used as feedback param instead of decay if non-zero + {rva_fbdepth, -1.0, 1.0}, // 0-1.0 material reflectivity - used as feedback param instead of decay if non-zero + {rva_fbheight, -1.0, 1.0}, // 0-1.0 material reflectivity - used as feedback param instead of decay if non-zero + // if < 0, a predelay is allocated, then feedback is -1*param given + + {rva_iftaps, 0.0, 0.333} // if > 0, use 3 extra taps with delay values = d * (1 - faps*n) n = 0,1,2,3 +}; + +#define RVA_BASEM 1 // base number of parallel delays + +// nominal delay and feedback values. More delays = more density. + +#define RVADLYSMAX 49 +float rvadlys[] = {18, 23, 28, 33, 42, 21, 26, 36, 39, 45, 47, 30}; +float rvafbs[] = {0.9, 0.9, 0.9, 0.85, 0.8, 0.9, 0.9, 0.85, 0.8, 0.8, 0.8, 0.85}; + +#define SWAP(a,b,t) {(t) = (a); (a) = (b); (b) = (t);} + +#define RVA_MIN_SEPARATION 7 // minimum separation between reverbs, in ms. + +// Construct D,a,b delay arrays given array of length,width,height sizes and feedback values +// rgd[] array of delay values in milliseconds (feet) +// rgf[] array of feedback values 0..1 +// m # of parallel reverbs to construct +// D[] array of output delay values for parallel reverbs +// a[] array of output feedback values +// b[] array of output gain values = 1/m +// gain - output gain +// feedback - default feedback if rgf members are 0 + +void RVA_ConstructDelays( float *rgd, float *rgf, int m, int *D, int *a, int *b, float gain, float feedback ) +{ + + int i; + float r; + int d; + float t, d1, d2, dm; + bool bpredelay; + + // sort descending, so rgd[0] is largest delay & rgd[2] is smallest + + if (rgd[2] > rgd[1]) { SWAP(rgd[2], rgd[1], t); SWAP(rgf[2], rgf[1], t); } + if (rgd[1] > rgd[0]) { SWAP(rgd[0], rgd[1], t); SWAP(rgf[0], rgf[1], t); } + if (rgd[2] > rgd[1]) { SWAP(rgd[2], rgd[1], t); SWAP(rgf[2], rgf[1], t); } + + // if all feedback values 0, use default feedback + + if (rgf[0] == 0.0 && rgf[1] == 0.0 && rgf[2] == 0.0 ) + { + // use feedback param for all + + rgf[0] = rgf[1] = rgf[2] = feedback; + + // adjust feedback down for larger delays so that decay is constant for all delays + + rgf[0] = DLY_NormalizeFeedback( rgd[2], rgf[2], rgd[0] ); + rgf[1] = DLY_NormalizeFeedback( rgd[2], rgf[2], rgd[1] ); + + } + + // make sure all reverbs are different by at least RVA_MIN_SEPARATION * m/3 m is 3,6,9 or 12 + + int dmin = (m/3) * RVA_MIN_SEPARATION; + + d1 = rgd[1] - rgd[2]; + + if (d1 <= dmin) + rgd[1] += (dmin-d1); // make difference = dmin + + d2 = rgd[0] - rgd[1]; + + if (d2 <= dmin) + rgd[0] += (dmin-d1); // make difference = dmin + + for ( i = 0; i < m; i++ ) + { + // reverberations due to room width, depth, height + // assume sound moves at approx 1ft/ms + + int j = (int)(fmod ((float)i, 3.0f)); // j counts 0,1,2 0,1,2 0,1.. + + d = (int)rgd[j]; + r = fabs(rgf[j]); + + bpredelay = ((rgf[j] < 0) && i < 3); + + // re-use predelay values as reverb values: + + if (rgf[j] < 0 && !bpredelay) + d = max((int)(rgd[j] / 4.0), RVA_MIN_SEPARATION); + + if (i < 3) + dm = 0.0; + else + dm = max( (double)(RVA_MIN_SEPARATION * (i/3)), ((i/3) * ((float)d * 0.18)) ); + + d += (int)dm; + D[i] = MSEC_TO_SAMPS(d); + + // D[i] = MSEC_TO_SAMPS(d + ((i/3) * RVA_MIN_SEPARATION)); // (i/3) counts 0,0,0 1,1,1 2,2,2 ... separate all reverbs by 5ms + + // feedback - due to wall/floor/ceiling reflectivity + a[i] = (int) min (0.999 * PMAX, (double)PMAX * r); + + if (bpredelay) + a[i] = -a[i]; // flag delay as predelay + + b[i] = (int)((float)(gain * PMAX) / (float)m); + } +} + +void RVA_PerfTest() +{ + double time1, time2; + + int i; + int k; + int j; + int m; + int a[100]; + + time1 = Plat_FloatTime(); + + for (m = 0; m < 1000; m++) + { + for (i = 0, j = 10000; i < 10000; i++, j--) + { + // j = j % 6; + // k = (i * j) >> PBITS; + + k = i / ((j % 6) + 1); + } + } + + time2 = Plat_FloatTime(); + + DevMsg("divide = %2.5f \n", (time2-time1)); + + + for (i=1;i<10;i++) + a[i] = PMAX / i; + + time1 = Plat_FloatTime(); + + for (m = 0; m < 1000; m++) + { + for (i = 0, j = 10000; i < 10000; i++, j--) + { + k = (i * a[(j % 6) + 1] ) >> PBITS; + } + } + + time2 = Plat_FloatTime(); + + DevMsg("shift & multiply = %2.5f \n", (time2-time1)); +} + +rva_t * RVA_Params ( prc_t *pprc ) +{ + rva_t *prva; + + float size_max = pprc->prm[rva_size_max]; // max delay size + float size_min = pprc->prm[rva_size_min]; // min delay size + + float numdelays = pprc->prm[rva_inumdelays]; // controls # of parallel delays + float feedback = pprc->prm[rva_ifeedback]; // 0-1.0 controls feedback parameters + float gain = pprc->prm[rva_igain]; // 0-10.0 controls output gain + + float cutoff = pprc->prm[rva_icutoff]; // filter cutoff + + float fparallel = pprc->prm[rva_ifparallel]; // if true, all filters are in delay feedback paths - otherwise single flt on output + + float fmoddly = pprc->prm[rva_imoddly]; // if > 0, milliseconds of delay mod depth + float fmodrate = pprc->prm[rva_imodrate]; // if fmoddly > 0, # of delay repetitions between modulations + + float width = fabs(pprc->prm[rva_width]); // 0-1000 controls size of 1/3 of delays - used instead of size if non-zero + float depth = fabs(pprc->prm[rva_depth]); // 0-1000 controls size of 1/3 of delays - used instead of size if non-zero + float height = fabs(pprc->prm[rva_height]); // 0-1000 controls size of 1/3 of delays - used instead of size if non-zero + + float fbwidth = pprc->prm[rva_fbwidth]; // feedback parameter for walls 0..2 + float fbdepth = pprc->prm[rva_fbdepth]; // feedback parameter for floor + float fbheight = pprc->prm[rva_fbheight]; // feedback parameter for ceiling + + float ftaps = pprc->prm[rva_iftaps]; // if > 0 increase reverb density using 3 extra taps d = (1.0 - ftaps * n) n = 0,1,2,3 + + + +// RVA_PerfTest(); + + // D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples) + // a array of reverb feedback parms for parallel delays + // b array of CRVB_P_DLYS - mix params for parallel reverbs + // m - number of parallel delays + + int D[CRVA_DLYS]; + int a[CRVA_DLYS]; + int b[CRVA_DLYS]; + int m; + + // limit # delays 1-12 + + m = clamp (numdelays, (float)RVA_BASEM, (float)CRVA_DLYS); + + // set up D (delay) a (feedback) b (gain) arrays + + if ( int(width) || int(height) || int(depth) ) + { + // if width, height, depth given, use values as simple delays + + float rgd[3]; + float rgfb[3]; + + // force m to 3, 6, 9 or 12 + + if (m < 3) m = 3; + if (m > 3 && m < 6) m = 6; + if (m > 6 && m < 9) m = 9; + if (m > 9) m = 12; + + rgd[0] = width; rgfb[0] = fbwidth; + rgd[1] = depth; rgfb[1] = fbdepth; + rgd[2] = height; rgfb[2] = fbheight; + + RVA_ConstructDelays( rgd, rgfb, m, D, a, b, gain, feedback ); + } + else + { + // use size parameter instead of width/depth/height + + for ( int i = 0; i < m; i++ ) + { + // delays of parallel reverb. D[0] = size_min. + + D[i] = MSEC_TO_SAMPS( size_min + (int)( ((float)(size_max - size_min) / (float)m) * (float)i) ); + + // feedback and gain of parallel reverb + + if (i == 0) + { + // set feedback for smallest delay + + a[i] = (int) min (0.999 * PMAX, (double)PMAX * feedback ); + } + else + { + // adjust feedback down for larger delays so that decay time is constant + + a[i] = (int) min (0.999 * PMAX, (double)PMAX * DLY_NormalizeFeedback( D[0], feedback, D[i] ) ); + } + + b[i] = (int) ((float)(gain * PMAX) / (float)m); + } + } + + // add filter + + flt_t *pflt = NULL; + + if ( cutoff ) + { + + // set up dummy lowpass filter to convert params + + prc_t prcf; + + prcf.prm[flt_iquality] = QUA_LO; // force filter to low quality for faster execution time + prcf.prm[flt_icutoff] = cutoff; + prcf.prm[flt_iftype] = FLT_LP; + prcf.prm[flt_iqwidth] = 0; + prcf.prm[flt_igain] = 1.0; + + pflt = (flt_t *)FLT_Params ( &prcf ); + } + + prva = RVA_Alloc ( D, a, b, m, pflt, fparallel, fmoddly, fmodrate, ftaps ); + + FLT_Free( pflt ); + + return prva; +} + + +inline void * RVA_VParams ( void *p ) +{ + PRC_CheckParams ( (prc_t *)p, rva_rng ); + return (void *) RVA_Params ((prc_t *)p); +} + +inline void RVA_Mod ( void *p, float v ) { return; } + + + +//////////// +// Diffusor +/////////// + +// (N series allpass reverbs) + +#define CDFRS 64 // max number of series reverbs active + +#define CDFR_DLYS 16 // max number of delays making up diffusor + +struct dfr_t +{ + bool fused; + int n; // series allpass delays + int w[CDFR_DLYS]; // internal state array for series allpass filters + + dly_t *pdlys[CDFR_DLYS]; // array of pointers to delays +}; + +dfr_t dfrs[CDFRS]; + +void DFR_Init ( dfr_t *pdfr ) { if ( pdfr ) Q_memset (pdfr, 0, sizeof (dfr_t)); } +void DFR_InitAll( void ) { for (int i = 0; i < CDFRS; i++) DFR_Init ( &dfrs[i] ); } + +// free parallel series reverb + +void DFR_Free( dfr_t *pdfr ) +{ + if ( pdfr ) + { + // free all delays + + for (int i = 0; i < CDFR_DLYS; i++) + DLY_Free ( pdfr->pdlys[i] ); + + Q_memset( pdfr, 0, sizeof (dfr_t) ); + } +} + + +void DFR_FreeAll( void ) { for (int i = 0; i < CDFRS; i++) DFR_Free( &dfrs[i] ); } + +// create n series allpass reverbs + +// D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples) +// a array of reverb feedback parms for series delays +// b array of gain params for parallel reverbs +// n - number of series delays + +dfr_t * DFR_Alloc ( int *D, int *a, int *b, int n ) +{ + + int i; + dfr_t *pdfr; + + // find open slot + + for (i = 0; i < CDFRS; i++) + { + if (!dfrs[i].fused) + break; + } + + // return null if no free slots + + if (i == CDFRS) + { + DevMsg ("DSP: Warning, failed to allocate diffusor.\n" ); + return NULL; + } + + pdfr = &dfrs[i]; + + DFR_Init( pdfr ); + + // alloc reverbs + + for (i = 0; i < n; i++) + pdfr->pdlys[i] = DLY_Alloc( D[i], a[i], b[i], DLY_ALLPASS ); + + // if we failed to alloc any reverb, free all, return NULL + + for (i = 0; i < n; i++) + { + if ( !pdfr->pdlys[i]) + { + DFR_Free( pdfr ); + DevMsg ("DSP: Warning, failed to allocate delay for diffusor.\n" ); + return NULL; + } + } + + pdfr->fused = true; + pdfr->n = n; + + return pdfr; +} + + +// series reverberator + +inline int DFR_GetNext( dfr_t *pdfr, int x ) +{ + int i; + int y; + dly_t *pdly; + + y = x; + + for (i = 0; i < pdfr->n; i++) + { + pdly = pdfr->pdlys[i]; + y = DelayAllpass( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, y ); + } + + return y; +} + +// batch version for performance + +inline void DFR_GetNextN( dfr_t *pdfr, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch (op) + { + default: + case OP_LEFT: + while (count--) + { + pb->left = DFR_GetNext( pdfr, pb->left ); + pb++; + } + return; + case OP_RIGHT: + while (count--) + { + pb->right = DFR_GetNext( pdfr, pb->right ); + pb++; + } + return; + case OP_LEFT_DUPLICATE: + while (count--) + { + pb->left = pb->right = DFR_GetNext( pdfr, pb->left ); + pb++; + } + return; + } +} + +#define DFR_BASEN 1 // base number of series allpass delays + +// nominal diffusor delay and feedback values + +float dfrdlys[] = {13, 19, 26, 21, 32, 36, 38, 16, 24, 28, 41, 35, 10, 46, 50, 27}; +float dfrfbs[] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; + + +// diffusor parameter order + +typedef enum +{ + +// parameter order + + dfr_isize, + dfr_inumdelays, + dfr_ifeedback, + dfr_igain, + + dfr_cparam // # of params + +} dfr_e; + +// diffusor parameter ranges + +prm_rng_t dfr_rng[] = { + + {dfr_cparam, 0, 0}, // first entry is # of parameters + + {dfr_isize, 0.0, 1.0}, // 0-1.0 scales all delays + {dfr_inumdelays,0.0, 4.0}, // 0-4.0 controls # of series delays + {dfr_ifeedback, 0.0, 1.0}, // 0-1.0 scales all feedback parameters + {dfr_igain, 0.0, 10.0}, // 0-1.0 scales all feedback parameters +}; + + +dfr_t * DFR_Params ( prc_t *pprc ) +{ + dfr_t *pdfr; + int i; + int s; + float size = pprc->prm[dfr_isize]; // 0-1.0 scales all delays + float numdelays = pprc->prm[dfr_inumdelays]; // 0-4.0 controls # of series delays + float feedback = pprc->prm[dfr_ifeedback]; // 0-1.0 scales all feedback parameters + float gain = pprc->prm[dfr_igain]; // 0-10.0 controls output gain + + // D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples) + // a array of reverb feedback parms for series delays (CRVB_S_DLYS) + // b gain of each reverb section + // n - number of series delays + + int D[CDFR_DLYS]; + int a[CDFR_DLYS]; + int b[CDFR_DLYS]; + int n; + + if (gain == 0.0) + gain = 1.0; + + // get # series diffusors + + // limit m, n to half max number of delays + + n = clamp (Float2Int(numdelays), DFR_BASEN, CDFR_DLYS/2); + + // compute delays for diffusors + + for (i = 0; i < n; i++) + { + s = (int)( dfrdlys[i] * size ); + + // delay of diffusor + + D[i] = MSEC_TO_SAMPS(s); + + // feedback and gain of diffusor + + a[i] = min (0.999 * PMAX, (double)(dfrfbs[i] * PMAX * feedback)); + b[i] = (int) ( (float)(gain * (float)PMAX) ); + } + + + pdfr = DFR_Alloc ( D, a, b, n ); + + return pdfr; +} + +inline void * DFR_VParams ( void *p ) +{ + PRC_CheckParams ((prc_t *)p, dfr_rng); + return (void *) DFR_Params ((prc_t *)p); +} + +inline void DFR_Mod ( void *p, float v ) { return; } + + +////////////////////// +// LFO wav definitions +////////////////////// + +#define CLFOSAMPS 512 // samples per wav table - single cycle only +#define LFOBITS 14 // bits of peak amplitude of lfo wav +#define LFOAMP ((1<<LFOBITS)-1) // peak amplitude of lfo wav + +//types of lfo wavs + +#define LFO_SIN 0 // sine wav +#define LFO_TRI 1 // triangle wav +#define LFO_SQR 2 // square wave, 50% duty cycle +#define LFO_SAW 3 // forward saw wav +#define LFO_RND 4 // random wav +#define LFO_LOG_IN 5 // logarithmic fade in +#define LFO_LOG_OUT 6 // logarithmic fade out +#define LFO_LIN_IN 7 // linear fade in +#define LFO_LIN_OUT 8 // linear fade out +#define LFO_MAX LFO_LIN_OUT + +#define CLFOWAV 9 // number of LFO wav tables + +struct lfowav_t // lfo or envelope wave table +{ + int type; // lfo type + dly_t *pdly; // delay holds wav values and step pointers +}; + +lfowav_t lfowavs[CLFOWAV]; + +// deallocate lfo wave table. Called only when sound engine exits. + +void LFOWAV_Free( lfowav_t *plw ) +{ + // free delay + + if ( plw ) + DLY_Free( plw->pdly ); + + Q_memset( plw, 0, sizeof (lfowav_t) ); +} + +// deallocate all lfo wave tables. Called only when sound engine exits. + +void LFOWAV_FreeAll( void ) +{ + for ( int i = 0; i < CLFOWAV; i++ ) + LFOWAV_Free( &lfowavs[i] ); +} + +// fill lfo array w with count samples of lfo type 'type' +// all lfo wavs except fade out, rnd, and log_out should start with 0 output + +void LFOWAV_Fill( int *w, int count, int type ) +{ + int i,x; + switch (type) + { + default: + case LFO_SIN: // sine wav, all values 0 <= x <= LFOAMP, initial value = 0 + for (i = 0; i < count; i++ ) + { + x = ( int )( (float)(LFOAMP) * sinf( (2.0 * M_PI_F * (float)i / (float)count ) + (M_PI_F * 1.5) ) ); + w[i] = (x + LFOAMP)/2; + } + break; + case LFO_TRI: // triangle wav, all values 0 <= x <= LFOAMP, initial value = 0 + for (i = 0; i < count; i++) + { + w[i] = ( int ) ( (float)(2 * LFOAMP * i ) / (float)(count) ); + + if ( i > count / 2 ) + w[i] = ( int ) ( (float) (2 * LFOAMP) - (float)( 2 * LFOAMP * i ) / (float)(count) ); + } + break; + case LFO_SQR: // square wave, 50% duty cycle, all values 0 <= x <= LFOAMP, initial value = 0 + for (i = 0; i < count; i++) + w[i] = i > count / 2 ? 0 : LFOAMP; + break; + case LFO_SAW: // forward saw wav, aall values 0 <= x <= LFOAMP, initial value = 0 + for (i = 0; i < count; i++) + w[i] = ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) ); + break; + case LFO_RND: // random wav, all values 0 <= x <= LFOAMP + for (i = 0; i < count; i++) + w[i] = ( int ) ( RandomInt(0, LFOAMP) ); + break; + case LFO_LOG_IN: // logarithmic fade in, all values 0 <= x <= LFOAMP, initial value = 0 + for (i = 0; i < count; i++) + w[i] = ( int ) ( (float)(LFOAMP) * powf( (float)i / (float)count, 2)); + break; + case LFO_LOG_OUT: // logarithmic fade out, all values 0 <= x <= LFOAMP, initial value = LFOAMP + for (i = 0; i < count; i++) + w[i] = ( int ) ( (float)(LFOAMP) * powf( 1.0 - ((float)i / (float)count), 2 )); + break; + case LFO_LIN_IN: // linear fade in, all values 0 <= x <= LFOAMP, initial value = 0 + for (i = 0; i < count; i++) + w[i] = ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) ); + break; + case LFO_LIN_OUT: // linear fade out, all values 0 <= x <= LFOAMP, initial value = LFOAMP + for (i = 0; i < count; i++) + w[i] = LFOAMP - ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) ); + break; + } +} + +// allocate all lfo wave tables. Called only when sound engine loads. + +void LFOWAV_InitAll() +{ + int i; + dly_t *pdly; + + Q_memset( lfowavs, 0, sizeof( lfowavs ) ); + + // alloc space for each lfo wav type + + for (i = 0; i < CLFOWAV; i++) + { + pdly = DLY_Alloc( CLFOSAMPS, 0, 0 , DLY_PLAIN); + + lfowavs[i].pdly = pdly; + lfowavs[i].type = i; + + LFOWAV_Fill( pdly->w, CLFOSAMPS, i ); + } + + // if any dlys fail to alloc, free all + + for (i = 0; i < CLFOWAV; i++) + { + if ( !lfowavs[i].pdly ) + LFOWAV_FreeAll(); + } +} + + +//////////////////////////////////////// +// LFO iterators - one shot and looping +//////////////////////////////////////// + +#define CLFO 16 // max active lfos (this steals from active delays) + +struct lfo_t +{ + bool fused; // true if slot take + + dly_t *pdly; // delay points to lfo wav within lfowav_t (don't free this) + + int gain; + + float f; // playback frequency in hz + + pos_t pos; // current position within wav table, looping + pos_one_t pos1; // current position within wav table, one shot + + int foneshot; // true - one shot only, don't repeat +}; + +lfo_t lfos[CLFO]; + +void LFO_Init( lfo_t *plfo ) { if ( plfo ) Q_memset( plfo, 0, sizeof (lfo_t) ); } +void LFO_InitAll( void ) { for (int i = 0; i < CLFO; i++) LFO_Init(&lfos[i]); } +void LFO_Free( lfo_t *plfo ) { if ( plfo ) Q_memset( plfo, 0, sizeof (lfo_t) ); } +void LFO_FreeAll( void ) { for (int i = 0; i < CLFO; i++) LFO_Free(&lfos[i]); } + + +// get step value given desired playback frequency + +inline float LFO_HzToStep ( float freqHz ) +{ + float lfoHz; + + // calculate integer and fractional step values, + // assume an update rate of SOUND_DMA_SPEED samples/sec + + // 1 cycle/CLFOSAMPS * SOUND_DMA_SPEED samps/sec = cycles/sec = current lfo rate + // + // lforate * X = freqHz so X = freqHz/lforate = update rate + + lfoHz = (float)(SOUND_DMA_SPEED) / (float)(CLFOSAMPS); + + return freqHz / lfoHz; +} + +// return pointer to new lfo + +lfo_t * LFO_Alloc( int wtype, float freqHz, bool foneshot, float gain ) +{ + int i; + int type = min ( CLFOWAV - 1, wtype ); + float lfostep; + + for (i = 0; i < CLFO; i++) + if (!lfos[i].fused) + { + lfo_t *plfo = &lfos[i]; + + LFO_Init( plfo ); + + plfo->fused = true; + plfo->pdly = lfowavs[type].pdly; // pdly in lfo points to wav table data in lfowavs + plfo->f = freqHz; + plfo->foneshot = foneshot; + plfo->gain = gain * PMAX; + + lfostep = LFO_HzToStep( freqHz ); + + // init positional pointer (ie: fixed point updater for controlling pitch of lfo) + + if ( !foneshot ) + POS_Init(&(plfo->pos), plfo->pdly->D, lfostep ); + else + POS_ONE_Init(&(plfo->pos1), plfo->pdly->D,lfostep ); + + return plfo; + } + DevMsg ("DSP: Warning, failed to allocate LFO.\n" ); + return NULL; +} + +// get next lfo value +// Value returned is 0..LFOAMP. can be normalized by shifting right by LFOBITS +// To play back at correct passed in frequency, routien should be +// called once for every output sample (ie: at SOUND_DMA_SPEED) +// x is dummy param + +inline int LFO_GetNext( lfo_t *plfo, int x ) +{ + int i; + + // get current position + + if ( !plfo->foneshot ) + i = POS_GetNext( &plfo->pos ); + else + i = POS_ONE_GetNext( &plfo->pos1 ); + + // return current sample + + if (plfo->gain == PMAX) + return plfo->pdly->w[i]; + else + return (plfo->pdly->w[i] * plfo->gain ) >> PBITS; +} + +// batch version for performance + +inline void LFO_GetNextN( lfo_t *plfo, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch (op) + { + default: + case OP_LEFT: + while (count--) + { + pb->left = LFO_GetNext( plfo, pb->left ); + pb++; + } + return; + case OP_RIGHT: + while (count--) + { + pb->right = LFO_GetNext( plfo, pb->right ); + pb++; + } + return; + case OP_LEFT_DUPLICATE: + while (count--) + { + pb->left = pb->right = LFO_GetNext( plfo, pb->left ); + pb++; + } + return; + } +} + +// uses lfowav, rate, foneshot + +typedef enum +{ + +// parameter order + + lfo_iwav, + lfo_irate, + lfo_ifoneshot, + lfo_igain, + + lfo_cparam // # of params + +} lfo_e; + +// parameter ranges + +prm_rng_t lfo_rng[] = { + + {lfo_cparam, 0, 0}, // first entry is # of parameters + + {lfo_iwav, 0.0, LFO_MAX}, // lfo type to use (LFO_SIN, LFO_RND...) + {lfo_irate, 0.0, 16000.0}, // modulation rate in hz. for MDY, 1/rate = 'glide' time in seconds + {lfo_ifoneshot, 0.0, 1.0}, // 1.0 if lfo is oneshot + {lfo_igain, 0.0, 10.0}, // output gain +}; + + +lfo_t * LFO_Params ( prc_t *pprc ) +{ + lfo_t *plfo; + bool foneshot = pprc->prm[lfo_ifoneshot] > 0 ? true : false; + float gain = pprc->prm[lfo_igain]; + + plfo = LFO_Alloc ( pprc->prm[lfo_iwav], pprc->prm[lfo_irate], foneshot, gain ); + + return plfo; +} + +void LFO_ChangeVal ( lfo_t *plfo, float fhz ) +{ + float fstep = LFO_HzToStep( fhz ); + + // change lfo playback rate to new frequency fhz + + if ( plfo->foneshot ) + POS_ChangeVal( &plfo->pos, fstep ); + else + POS_ChangeVal( &plfo->pos1.p, fstep ); +} + +inline void * LFO_VParams ( void *p ) +{ + PRC_CheckParams ( (prc_t *)p, lfo_rng ); + return (void *) LFO_Params ((prc_t *)p); +} + +// v is +/- 0-1.0 +// v changes current lfo frequency up/down by +/- v% + +inline void LFO_Mod ( lfo_t *plfo, float v ) +{ + float fhz; + float fhznew; + + fhz = plfo->f; + fhznew = fhz * (1.0 + v); + + LFO_ChangeVal ( plfo, fhznew ); + + return; +} + + +//////////////////////////////////////// +// Time Compress/expand with pitch shift +//////////////////////////////////////// + +// realtime pitch shift - ie: pitch shift without change to playback rate + +#define CPTCS 64 + +struct ptc_t +{ + bool fused; + + dly_t *pdly_in; // input buffer space + dly_t *pdly_out; // output buffer space + + int *pin; // input buffer (pdly_in->w) + int *pout; // output buffer (pdly_out->w) + + int cin; // # samples in input buffer + int cout; // # samples in output buffer + + int cxfade; // # samples in crossfade segment + int ccut; // # samples to cut + int cduplicate; // # samples to duplicate (redundant - same as ccut) + + int iin; // current index into input buffer (reading) + + pos_one_t psn; // stepping index through output buffer + + bool fdup; // true if duplicating, false if cutting + + float fstep; // pitch shift & time compress/expand +}; + +ptc_t ptcs[CPTCS]; + +void PTC_Init( ptc_t *pptc ) { if (pptc) Q_memset( pptc, 0, sizeof (ptc_t) ); }; +void PTC_Free( ptc_t *pptc ) +{ + if (pptc) + { + DLY_Free (pptc->pdly_in); + DLY_Free (pptc->pdly_out); + + Q_memset( pptc, 0, sizeof (ptc_t) ); + } +}; +void PTC_InitAll() { for (int i = 0; i < CPTCS; i++) PTC_Init( &ptcs[i] ); }; +void PTC_FreeAll() { for (int i = 0; i < CPTCS; i++) PTC_Free( &ptcs[i] ); }; + + + +// Time compressor/expander with pitch shift (ie: pitch changes, playback rate does not) +// +// Algorithm: + +// 1) Duplicate or discard chunks of sound to provide tslice * fstep seconds of sound. +// (The user-selectable size of the buffer to process is tslice milliseconds in length) +// 2) Resample this compressed/expanded buffer at fstep to produce a pitch shifted +// output with the same duration as the input (ie: #samples out = # samples in, an +// obvious requirement for realtime inline processing). + +// timeslice is size in milliseconds of full buffer to process. +// timeslice * fstep is the size of the expanded/compressed buffer +// timexfade is length in milliseconds of crossfade region between duplicated or cut sections +// fstep is % expanded/compressed sound normalized to 0.01-2.0 (1% - 200%) + +// input buffer: + +// iin--> + +// [0... tslice ...D] input samples 0...D (D is NEWEST sample) +// [0... ...n][m... tseg ...D] region to be cut or duplicated m...D + +// [0... [p..txf1..n][m... tseg ...D] fade in region 1 txf1 p...n +// [0... ...n][m..[q..txf2..D] fade out region 2 txf2 q...D + + +// pitch up: duplicate into output buffer: tdup = tseg + +// [0... ...n][m... tdup ...D][m... tdup ...D] output buffer size with duplicate region +// [0... ...n][m..[p...xf1..n][m... tdup ...D] fade in p...n while fading out q...D +// [0... ...n][m..[q...xf2..D][m... tdup ...D] +// [0... ...n][m..[.XFADE...n][m... tdup ...D] final duplicated output buffer - resample at fstep + +// pitch down: cut into output buffer: tcut = tseg + +// [0... ...n][m... tcut ...D] input samples with cut region delineated m...D +// [0... ...n] output buffer size after cut +// [0... [q..txf2...D] fade in txf1 q...D while fade out txf2 p...n +// [0... [.XFADE ...D] final cut output buffer - resample at fstep + + +ptc_t * PTC_Alloc( float timeslice, float timexfade, float fstep ) +{ + + int i; + ptc_t *pptc; + float tout; + int cin, cout; + float tslice = timeslice; + float txfade = timexfade; + float tcutdup; + + // find time compressor slot + + for ( i = 0; i < CPTCS; i++ ) + { + if ( !ptcs[i].fused ) + break; + } + + if ( i == CPTCS ) + { + DevMsg ("DSP: Warning, failed to allocate pitch shifter.\n" ); + return NULL; + } + + pptc = &ptcs[i]; + + PTC_Init ( pptc ); + + // get size of region to cut or duplicate + + tcutdup = abs((fstep - 1.0) * timeslice); + + // to prevent buffer overruns: + + // make sure timeslice is greater than cut/dup time + + tslice = max ( (double)tslice, 1.1 * tcutdup); + + // make sure xfade time smaller than cut/dup time, and smaller than (timeslice-cutdup) time + + txfade = min ( (double)txfade, 0.9 * tcutdup ); + txfade = min ( (double)txfade, 0.9 * (tslice - tcutdup)); + + pptc->cxfade = MSEC_TO_SAMPS( txfade ); + pptc->ccut = MSEC_TO_SAMPS( tcutdup ); + pptc->cduplicate = MSEC_TO_SAMPS( tcutdup ); + + // alloc delay lines (buffers) + + tout = tslice * fstep; + + cin = MSEC_TO_SAMPS( tslice ); + cout = MSEC_TO_SAMPS( tout ); + + pptc->pdly_in = DLY_Alloc( cin, 0, 1, DLY_LINEAR ); // alloc input buffer + pptc->pdly_out = DLY_Alloc( cout, 0, 1, DLY_LINEAR); // alloc output buffer + + if ( !pptc->pdly_in || !pptc->pdly_out ) + { + PTC_Free( pptc ); + DevMsg ("DSP: Warning, failed to allocate delay for pitch shifter.\n" ); + return NULL; + } + + // buffer pointers + + pptc->pin = pptc->pdly_in->w; + pptc->pout = pptc->pdly_out->w; + + // input buffer index + + pptc->iin = 0; + + // output buffer index + + POS_ONE_Init ( &pptc->psn, cout, fstep ); + + // if fstep > 1.0 we're pitching shifting up, so fdup = true + + pptc->fdup = fstep > 1.0 ? true : false; + + pptc->cin = cin; + pptc->cout = cout; + + pptc->fstep = fstep; + pptc->fused = true; + + return pptc; +} + +// linear crossfader +// yfadein - instantaneous value fading in +// ydafeout -instantaneous value fading out +// nsamples - duration in #samples of fade +// isample - index in to fade 0...nsamples-1 + +inline int xfade ( int yfadein, int yfadeout, int nsamples, int isample ) +{ + int yout; + int m = (isample << PBITS ) / nsamples; + +// yout = ((yfadein * m) >> PBITS) + ((yfadeout * (PMAX - m)) >> PBITS); + yout = (yfadeout + (yfadein - yfadeout) * m ) >> PBITS; + + return yout; +} + +// w - pointer to start of input buffer samples +// v - pointer to start of output buffer samples +// cin - # of input buffer samples +// cout = # of output buffer samples +// cxfade = # of crossfade samples +// cduplicate = # of samples in duplicate/cut segment + +void TimeExpand( int *w, int *v, int cin, int cout, int cxfade, int cduplicate ) +{ + int i,j; + int m; + int p; + int q; + int D; + + // input buffer + // xfade source duplicate + // [0...........][p.......n][m...........D] + + // output buffer + // xfade region duplicate + // [0.....................n][m..[q.......D][m...........D] + + // D - index of last sample in input buffer + // m - index of 1st sample in duplication region + // p - index of 1st sample of crossfade source + // q - index of 1st sample in crossfade region + + D = cin - 1; + m = cin - cduplicate; + p = m - cxfade; + q = cin - cxfade; + + // copy up to crossfade region + + for (i = 0; i < q; i++) + v[i] = w[i]; + + // crossfade region + + j = p; + + for (i = q; i <= D; i++) + v[i] = xfade (w[j++], w[i], cxfade, i-q); // fade out p..n, fade in q..D + + // duplicate region + + j = D+1; + + for (i = m; i <= D; i++) + v[j++] = w[i]; + +} + +// cut ccut samples from end of input buffer, crossfade end of cut section +// with end of remaining section + +// w - pointer to start of input buffer samples +// v - pointer to start of output buffer samples +// cin - # of input buffer samples +// cout = # of output buffer samples +// cxfade = # of crossfade samples +// ccut = # of samples in cut segment + +void TimeCompress( int *w, int *v, int cin, int cout, int cxfade, int ccut ) +{ + int i,j; + int m; + int p; + int q; + int D; + + // input buffer + // xfade source + // [0.....................n][m..[p.......D] + + // xfade region cut + // [0...........][q.......n][m...........D] + + // output buffer + // xfade to source + // [0...........][p.......D] + + // D - index of last sample in input buffer + // m - index of 1st sample in cut region + // p - index of 1st sample of crossfade source + // q - index of 1st sample in crossfade region + + D = cin - 1; + m = cin - ccut; + p = cin - cxfade; + q = m - cxfade; + + // copy up to crossfade region + + for (i = 0; i < q; i++) + v[i] = w[i]; + + // crossfade region + + j = p; + + for (i = q; i < m; i++) + v[i] = xfade (w[j++], w[i], cxfade, i-q); // fade out p..n, fade in q..D + + // skip rest of input buffer +} + +// get next sample + +// put input sample into input (delay) buffer +// get output sample from output buffer, step by fstep % +// output buffer is time expanded or compressed version of previous input buffer + +inline int PTC_GetNext( ptc_t *pptc, int x ) +{ + int iout, xout; + bool fhitend = false; + + // write x into input buffer + Assert (pptc->iin < pptc->cin); + + pptc->pin[pptc->iin] = x; + + pptc->iin++; + + // check for end of input buffer + + if ( pptc->iin >= pptc->cin ) + fhitend = true; + + // read sample from output buffer, resampling at fstep + + iout = POS_ONE_GetNext( &pptc->psn ); + Assert (iout < pptc->cout); + xout = pptc->pout[iout]; + + if ( fhitend ) + { + // if hit end of input buffer (ie: input buffer is full) + // reset input buffer pointer + // reset output buffer pointer + // rebuild entire output buffer (TimeCompress/TimeExpand) + + pptc->iin = 0; + + POS_ONE_Init( &pptc->psn, pptc->cout, pptc->fstep ); + + if ( pptc->fdup ) + TimeExpand ( pptc->pin, pptc->pout, pptc->cin, pptc->cout, pptc->cxfade, pptc->cduplicate ); + else + TimeCompress ( pptc->pin, pptc->pout, pptc->cin, pptc->cout, pptc->cxfade, pptc->ccut ); + } + + return xout; +} + +// batch version for performance + +inline void PTC_GetNextN( ptc_t *pptc, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch (op) + { + default: + case OP_LEFT: + while (count--) + { + pb->left = PTC_GetNext( pptc, pb->left ); + pb++; + } + return; + case OP_RIGHT: + while (count--) + { + pb->right = PTC_GetNext( pptc, pb->right ); + pb++; + } + return; + case OP_LEFT_DUPLICATE: + while (count--) + { + pb->left = pb->right = PTC_GetNext( pptc, pb->left ); + pb++; + } + return; + } +} + +// change time compression to new value +// fstep is new value +// ramptime is how long change takes in seconds (ramps smoothly), 0 for no ramp + +void PTC_ChangeVal( ptc_t *pptc, float fstep, float ramptime ) +{ +// UNDONE: ignored +// UNDONE: just realloc time compressor with new fstep +} + +// uses pitch: +// 1.0 = playback normal rate +// 0.5 = cut 50% of sound (2x playback) +// 1.5 = add 50% sound (0.5x playback) + +typedef enum +{ + +// parameter order + + ptc_ipitch, + ptc_itimeslice, + ptc_ixfade, + + ptc_cparam // # of params + +} ptc_e; + +// diffusor parameter ranges + +prm_rng_t ptc_rng[] = { + + {ptc_cparam, 0, 0}, // first entry is # of parameters + + {ptc_ipitch, 0.1, 4.0}, // 0-n.0 where 1.0 = 1 octave up and 0.5 is one octave down + {ptc_itimeslice, 20.0, 300.0}, // in milliseconds - size of sound chunk to analyze and cut/duplicate - 100ms nominal + {ptc_ixfade, 1.0, 200.0}, // in milliseconds - size of crossfade region between spliced chunks - 20ms nominal +}; + + +ptc_t * PTC_Params ( prc_t *pprc ) +{ + ptc_t *pptc; + + float pitch = pprc->prm[ptc_ipitch]; + float timeslice = pprc->prm[ptc_itimeslice]; + float txfade = pprc->prm[ptc_ixfade]; + + pptc = PTC_Alloc( timeslice, txfade, pitch ); + + return pptc; +} + +inline void * PTC_VParams ( void *p ) +{ + PRC_CheckParams ( (prc_t *)p, ptc_rng ); + return (void *) PTC_Params ((prc_t *)p); +} + +// change to new pitch value +// v is +/- 0-1.0 +// v changes current pitch up/down by +/- v% + +void PTC_Mod ( ptc_t *pptc, float v ) +{ + float fstep; + float fstepnew; + + fstep = pptc->fstep; + fstepnew = fstep * (1.0 + v); + + PTC_ChangeVal( pptc, fstepnew, 0.01 ); +} + + +//////////////////// +// ADSR envelope +//////////////////// + +#define CENVS 64 // max # of envelopes active +#define CENVRMPS 4 // A, D, S, R + +#define ENV_LIN 0 // linear a,d,s,r +#define ENV_EXP 1 // exponential a,d,s,r +#define ENV_MAX ENV_EXP + +#define ENV_BITS 14 // bits of resolution of ramp + +struct env_t +{ + bool fused; + + bool fhitend; // true if done + bool fexp; // true if exponential ramps + + int ienv; // current ramp + rmp_t rmps[CENVRMPS]; // ramps +}; + +env_t envs[CENVS]; + +void ENV_Init( env_t *penv ) { if (penv) Q_memset( penv, 0, sizeof (env_t) ); }; +void ENV_Free( env_t *penv ) { if (penv) Q_memset( penv, 0, sizeof (env_t) ); }; +void ENV_InitAll() { for (int i = 0; i < CENVS; i++) ENV_Init( &envs[i] ); }; +void ENV_FreeAll() { for (int i = 0; i < CENVS; i++) ENV_Free( &envs[i] ); }; + + +// allocate ADSR envelope +// all times are in seconds +// amp1 - attack amplitude multiplier 0-1.0 +// amp2 - sustain amplitude multiplier 0-1.0 +// amp3 - end of sustain amplitude multiplier 0-1.0 + +env_t *ENV_Alloc ( int type, float famp1, float famp2, float famp3, float attack, float decay, float sustain, float release, bool fexp) +{ + int i; + env_t *penv; + + for (i = 0; i < CENVS; i++) + { + if ( !envs[i].fused ) + { + + int amp1 = famp1 * (1 << ENV_BITS); // ramp resolution + int amp2 = famp2 * (1 << ENV_BITS); + int amp3 = famp3 * (1 << ENV_BITS); + + penv = &envs[i]; + + ENV_Init (penv); + + // UNDONE: ignoring type = ENV_EXP - use oneshot LFOS instead with sawtooth/exponential + + // set up ramps + + RMP_Init( &penv->rmps[0], attack, 0, amp1, true ); + RMP_Init( &penv->rmps[1], decay, amp1, amp2, true ); + RMP_Init( &penv->rmps[2], sustain, amp2, amp3, true ); + RMP_Init( &penv->rmps[3], release, amp3, 0, true ); + + penv->ienv = 0; + penv->fused = true; + penv->fhitend = false; + penv->fexp = fexp; + return penv; + } + } + DevMsg ("DSP: Warning, failed to allocate envelope.\n" ); + return NULL; +} + + +inline int ENV_GetNext( env_t *penv, int x ) +{ + if ( !penv->fhitend ) + { + int i; + int y; + + i = penv->ienv; + y = RMP_GetNext ( &penv->rmps[i] ); + + // check for next ramp + + if ( penv->rmps[i].fhitend ) + i++; + + penv->ienv = i; + + // check for end of all ramps + + if ( i > 3) + penv->fhitend = true; + + // multiply input signal by ramp + + if (penv->fexp) + return (((x * y) >> ENV_BITS) * y) >> ENV_BITS; + else + return (x * y) >> ENV_BITS; + } + + return 0; +} + +// batch version for performance + +inline void ENV_GetNextN( env_t *penv, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch (op) + { + default: + case OP_LEFT: + while (count--) + { + pb->left = ENV_GetNext( penv, pb->left ); + pb++; + } + return; + case OP_RIGHT: + while (count--) + { + pb->right = ENV_GetNext( penv, pb->right ); + pb++; + } + return; + case OP_LEFT_DUPLICATE: + while (count--) + { + pb->left = pb->right = ENV_GetNext( penv, pb->left ); + pb++; + } + return; + } +} + +// uses lfowav, amp1, amp2, amp3, attack, decay, sustain, release +// lfowav is type, currently ignored - ie: LFO_LIN_IN, LFO_LOG_IN + +// parameter order + +typedef enum +{ + env_itype, + env_iamp1, + env_iamp2, + env_iamp3, + env_iattack, + env_idecay, + env_isustain, + env_irelease, + env_ifexp, + env_cparam // # of params + +} env_e; + +// parameter ranges + +prm_rng_t env_rng[] = { + + {env_cparam, 0, 0}, // first entry is # of parameters + + {env_itype, 0.0,ENV_MAX}, // ENV_LINEAR, ENV_LOG - currently ignored + {env_iamp1, 0.0, 1.0}, // attack peak amplitude 0-1.0 + {env_iamp2, 0.0, 1.0}, // decay target amplitued 0-1.0 + {env_iamp3, 0.0, 1.0}, // sustain target amplitude 0-1.0 + {env_iattack, 0.0, 20000.0}, // attack time in milliseconds + {env_idecay, 0.0, 20000.0}, // envelope decay time in milliseconds + {env_isustain, 0.0, 20000.0}, // sustain time in milliseconds + {env_irelease, 0.0, 20000.0}, // release time in milliseconds + {env_ifexp, 0.0, 1.0}, // 1.0 if exponential ramps +}; + +env_t * ENV_Params ( prc_t *pprc ) +{ + env_t *penv; + + float type = pprc->prm[env_itype]; + float amp1 = pprc->prm[env_iamp1]; + float amp2 = pprc->prm[env_iamp2]; + float amp3 = pprc->prm[env_iamp3]; + float attack = pprc->prm[env_iattack]/1000.0; + float decay = pprc->prm[env_idecay]/1000.0; + float sustain = pprc->prm[env_isustain]/1000.0; + float release = pprc->prm[env_irelease]/1000.0; + float fexp = pprc->prm[env_ifexp]; + bool bexp; + + bexp = fexp > 0.0 ? 1 : 0; + penv = ENV_Alloc ( type, amp1, amp2, amp3, attack, decay, sustain, release, bexp ); + return penv; +} + +inline void * ENV_VParams ( void *p ) +{ + PRC_CheckParams( (prc_t *)p, env_rng ); + return (void *) ENV_Params ((prc_t *)p); +} + +inline void ENV_Mod ( void *p, float v ) { return; } + +////////////////////////// +// Gate & envelope follower +////////////////////////// + +#define CEFOS 64 // max # of envelope followers active + +struct efo_t +{ + bool fused; + + int xout; // current output value + + // gate params + + bool bgate; // if true, gate function is on + + bool bgateon; // if true, gate is on + bool bexp; // if true, use exponential fade out + + int thresh; // amplitude threshold for gate on + int thresh_off; // amplitidue threshold for gate off + + float attack_time; // gate attack time in seconds + float decay_time; // gate decay time in seconds + + rmp_t rmp_attack; // gate on ramp - attack + rmp_t rmp_decay; // gate off ramp - decay +}; + +efo_t efos[CEFOS]; + +void EFO_Init( efo_t *pefo ) { if (pefo) Q_memset( pefo, 0, sizeof (efo_t) ); }; +void EFO_Free( efo_t *pefo ) { if (pefo) Q_memset( pefo, 0, sizeof (efo_t) ); }; +void EFO_InitAll() { for (int i = 0; i < CEFOS; i++) EFO_Init( &efos[i] ); }; +void EFO_FreeAll() { for (int i = 0; i < CEFOS; i++) EFO_Free( &efos[i] ); }; + +// return true when gate is off AND decay ramp has hit end + +inline bool EFO_GateOff( efo_t *pefo ) +{ + return ( !pefo->bgateon && RMP_HitEnd( &pefo->rmp_decay ) ); +} + + +// allocate enveloper follower + +#define EFO_HYST_AMP 1000 // hysteresis amplitude + +efo_t *EFO_Alloc ( float threshold, float attack_sec, float decay_sec, bool bexp ) +{ + int i; + efo_t *pefo; + + for (i = 0; i < CEFOS; i++) + { + if ( !efos[i].fused ) + { + pefo = &efos[i]; + + EFO_Init ( pefo ); + + pefo->xout = 0; + pefo->fused = true; + + // init gate params + + pefo->bgate = threshold > 0.0; + + if (pefo->bgate) + { + pefo->attack_time = attack_sec; + pefo->decay_time = decay_sec; + + RMP_Init( &pefo->rmp_attack, attack_sec, 0, PMAX, false); + RMP_Init( &pefo->rmp_decay, decay_sec, PMAX, 0, false); + RMP_SetEnd( &pefo->rmp_attack ); + RMP_SetEnd( &pefo->rmp_decay ); + + pefo->thresh = threshold; + pefo->thresh_off = max(1.f, threshold - EFO_HYST_AMP); + pefo->bgateon = false; + pefo->bexp = bexp; + } + + return pefo; + } + } + + DevMsg ("DSP: Warning, failed to allocate envelope follower.\n" ); + return NULL; +} + +// values of L for CEFO_BITS_DIVIDE: L = (1 - 1/(1 << CEFO_BITS_DIVIDE)) +// 1 L = 0.5 +// 2 L = 0.75 +// 3 L = 0.875 +// 4 L = 0.9375 +// 5 L = 0.96875 +// 6 L = 0.984375 +// 7 L = 0.9921875 +// 8 L = 0.99609375 +// 9 L = 0.998046875 +// 10 L = 0.9990234375 +// 11 L = 0.99951171875 +// 12 L = 0.999755859375 + + +// decay time constant for values of L, for E = 10^-3 = 60dB of attenuation +// +// Neff = Ln E / Ln L = -6.9077552 / Ln L +// +// 1 L = 0.5 Neff = 10 samples +// 2 L = 0.75 Neff = 24 +// 3 L = 0.875 Neff = 51 +// 4 L = 0.9375 Neff = 107 +// 5 L = 0.96875 Neff = 217 +// 6 L = 0.984375 Neff = 438 +// 7 L = 0.9921875 Neff = 880 +// 8 L = 0.99609375 Neff = 1764 +// 9 L = 0.998046875 Neff = 3533 +// 10 L = 0.9990234375 Neff = 7070 +// 11 L = 0.99951171875 Neff = 14143 +// 12 L = 0.999755859375 Neff = 28290 + +#define CEFO_BITS 11 // 14143 samples in gate window (3hz) + +inline int EFO_GetNext( efo_t *pefo, int x ) +{ + int r; + int xa = abs(x); + int xdif; + + + // get envelope: + // Cn = L * Cn-1 + ( 1 - L ) * |x| + + // which simplifies to: + // Cn = |x| + (Cn-1 - |x|) * L + + // for 0 < L < 1 + + // increasing L increases time to rise or fall to a new input level + + // so: increasing CEFO_BITS_DIVIDE increases rise/fall time + + // where: L = (1 - 1/(1 << CEFO_BITS)) + // xdif = Cn-1 - |x| + // so: xdif * L = xdif - xdif / (1 << CEFO_BITS) = ((xdif << CEFO_BITS) - xdif ) >> CEFO_BITS + + xdif = pefo->xout - xa; + + pefo->xout = xa + (((xdif << CEFO_BITS) - xdif) >> CEFO_BITS); + + if ( pefo->bgate ) + { + // gate + + bool bgateon_prev = pefo->bgateon; + + // gate hysteresis + + if (bgateon_prev) + // gate was on - it's off only if amp drops below thresh_off + pefo->bgateon = ( pefo->xout >= pefo->thresh_off ); + else + // gate was off - it's on only if amp > thresh + pefo->bgateon = ( pefo->xout >= pefo->thresh ); + + if ( pefo->bgateon ) + { + // gate is on + + if ( bgateon_prev && RMP_HitEnd( &pefo->rmp_attack )) + return x; // gate is fully on + + if ( !bgateon_prev ) + { + // gate just turned on, start ramp attack + + // start attack from previous decay ramp if active + + r = RMP_HitEnd( &pefo->rmp_decay ) ? 0 : RMP_GetNext( &pefo->rmp_decay ); + RMP_SetEnd( &pefo->rmp_decay); + + // DevMsg ("GATE ON \n"); + + RMP_Init( &pefo->rmp_attack, pefo->attack_time, r, PMAX, false); + + return (x * r) >> PBITS; + } + + if ( !RMP_HitEnd( &pefo->rmp_attack ) ) + { + r = RMP_GetNext( &pefo->rmp_attack ); + + // gate is on and ramping up + + return (x * r) >> PBITS; + } + + } + else + { + // gate is fully off + + if ( !bgateon_prev && RMP_HitEnd( &pefo->rmp_decay)) + return 0; + + if ( bgateon_prev ) + { + // gate just turned off, start ramp decay + + // start decay from previous attack ramp if active + + r = RMP_HitEnd( &pefo->rmp_attack ) ? PMAX : RMP_GetNext( &pefo->rmp_attack ); + RMP_SetEnd( &pefo->rmp_attack); + + RMP_Init( &pefo->rmp_decay, pefo->decay_time, r, 0, false); + + // DevMsg ("GATE OFF \n"); + + // if exponential set, gate has exponential ramp down. Otherwise linear ramp down. + + if ( pefo->bexp ) + return ( (((x * r) >> PBITS) * r ) >> PBITS ); + else + return (x * r) >> PBITS; + + } + else if ( !RMP_HitEnd( &pefo->rmp_decay ) ) + { + // gate is off and ramping down + + r = RMP_GetNext( &pefo->rmp_decay ); + + + // if exponential set, gate has exponential ramp down. Otherwise linear ramp down. + + if ( pefo->bexp ) + return ( (((x * r) >> PBITS) * r ) >> PBITS ); + else + return (x * r) >> PBITS; + } + } + + return x; + } + + return pefo->xout; +} + +// batch version for performance + +inline void EFO_GetNextN( efo_t *pefo, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch (op) + { + default: + case OP_LEFT: + while (count--) + { + pb->left = EFO_GetNext( pefo, pb->left ); + pb++; + } + return; + case OP_RIGHT: + while (count--) + { + pb->right = EFO_GetNext( pefo, pb->right ); + pb++; + } + return; + case OP_LEFT_DUPLICATE: + while (count--) + { + pb->left = pb->right = EFO_GetNext( pefo, pb->left ); + pb++; + } + return; + } +} +// parameter order + +typedef enum +{ + efo_ithreshold, + efo_iattack, + efo_idecay, + efo_iexp, + + efo_cparam // # of params + +} efo_e; + +// parameter ranges + +prm_rng_t efo_rng[] = { + + {efo_cparam, 0, 0}, // first entry is # of parameters + + {efo_ithreshold, -140.0, 0.0}, // gate threshold in db. if 0.0 then no gate. + {efo_iattack, 0.0, 20000.0}, // attack time in milliseconds + {efo_idecay, 0.0, 20000.0}, // envelope decay time in milliseconds + {efo_iexp, 0.0, 1.0}, // if 1, use exponential decay ramp (for more realistic reverb tail) + +}; + +efo_t * EFO_Params ( prc_t *pprc ) +{ + efo_t *penv; + + float threshold = Gain_To_Amplitude( dB_To_Gain(pprc->prm[efo_ithreshold]) ); + float attack = pprc->prm[efo_iattack]/1000.0; + float decay = pprc->prm[efo_idecay]/1000.0; + float fexp = pprc->prm[efo_iexp]; + bool bexp; + + // check for no gate + + if ( pprc->prm[efo_ithreshold] == 0.0 ) + threshold = 0.0; + + bexp = fexp > 0.0 ? 1 : 0; + + penv = EFO_Alloc ( threshold, attack, decay, bexp ); + return penv; +} + +inline void * EFO_VParams ( void *p ) +{ + PRC_CheckParams( (prc_t *)p, efo_rng ); + return (void *) EFO_Params ((prc_t *)p); +} + +inline void EFO_Mod ( void *p, float v ) { return; } + + +/////////////////////////////////////////// +// Chorus - lfo modulated delay +/////////////////////////////////////////// + + +#define CCRSS 64 // max number chorus' active + +struct crs_t +{ + bool fused; + + mdy_t *pmdy; // modulatable delay + lfo_t *plfo; // modulating lfo + + int lfoprev; // previous modulator value from lfo + +}; + +crs_t crss[CCRSS]; + +void CRS_Init( crs_t *pcrs ) { if (pcrs) Q_memset( pcrs, 0, sizeof (crs_t) ); }; +void CRS_Free( crs_t *pcrs ) +{ + if (pcrs) + { + MDY_Free ( pcrs->pmdy ); + LFO_Free ( pcrs->plfo ); + Q_memset( pcrs, 0, sizeof (crs_t) ); + } +} + + +void CRS_InitAll() { for (int i = 0; i < CCRSS; i++) CRS_Init( &crss[i] ); } +void CRS_FreeAll() { for (int i = 0; i < CCRSS; i++) CRS_Free( &crss[i] ); } + +// fstep is base pitch shift, ie: floating point step value, where 1.0 = +1 octave, 0.5 = -1 octave +// lfotype is LFO_SIN, LFO_RND, LFO_TRI etc (LFO_RND for chorus, LFO_SIN for flange) +// fHz is modulation frequency in Hz +// depth is modulation depth, 0-1.0 +// mix is mix of chorus and clean signal + +#define CRS_DELAYMAX 100 // max milliseconds of sweepable delay +#define CRS_RAMPTIME 5 // milliseconds to ramp between new delay values + +crs_t * CRS_Alloc( int lfotype, float fHz, float fdepth, float mix ) +{ + + int i; + crs_t *pcrs; + dly_t *pdly; + mdy_t *pmdy; + lfo_t *plfo; + float ramptime; + int D; + + // find free chorus slot + + for ( i = 0; i < CCRSS; i++ ) + { + if ( !crss[i].fused ) + break; + } + + if ( i == CCRSS ) + { + DevMsg ("DSP: Warning, failed to allocate chorus.\n" ); + return NULL; + } + + pcrs = &crss[i]; + + CRS_Init ( pcrs ); + + D = fdepth * MSEC_TO_SAMPS(CRS_DELAYMAX); // sweep from 0 - n milliseconds + + ramptime = (float) CRS_RAMPTIME / 1000.0; // # milliseconds to ramp between new values + + pdly = DLY_Alloc ( D, 0, 1, DLY_LINEAR ); + + pmdy = MDY_Alloc ( pdly, ramptime, 0.0, 0.0, mix ); + + plfo = LFO_Alloc ( lfotype, fHz, false, 1.0 ); + + if ( !plfo || !pmdy ) + { + LFO_Free ( plfo ); + MDY_Free ( pmdy ); + DevMsg ("DSP: Warning, failed to allocate lfo or mdy for chorus.\n" ); + return NULL; + } + + pcrs->pmdy = pmdy; + pcrs->plfo = plfo; + pcrs->fused = true; + + return pcrs; +} + +// return next chorused sample (modulated delay) mixed with input sample + +inline int CRS_GetNext( crs_t *pcrs, int x ) +{ + int l; + int y; + + // get current mod delay value + + y = MDY_GetNext ( pcrs->pmdy, x ); + + // get next lfo value for modulation + // note: lfo must return 0 as first value + + l = LFO_GetNext ( pcrs->plfo, x ); + + // if modulator has changed, change mdy + + if ( l != pcrs->lfoprev ) + { + // calculate new tap starts at D) + + int D = pcrs->pmdy->pdly->D0; + int tap; + + // lfo should always output values 0 <= l <= LFOMAX + + if (l < 0) + l = 0; + + tap = D - ((l * D) >> LFOBITS); + + MDY_ChangeVal ( pcrs->pmdy, tap ); + + pcrs->lfoprev = l; + } + + return y; +} + +// batch version for performance + +inline void CRS_GetNextN( crs_t *pcrs, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch (op) + { + default: + case OP_LEFT: + while (count--) + { + pb->left = CRS_GetNext( pcrs, pb->left ); + pb++; + } + return; + case OP_RIGHT: + while (count--) + { + pb->right = CRS_GetNext( pcrs, pb->right ); + pb++; + } + return; + case OP_LEFT_DUPLICATE: + while (count--) + { + pb->left = pb->right = CRS_GetNext( pcrs, pb->left ); + pb++; + } + return; + } +} + +// parameter order + +typedef enum { + + crs_ilfotype, + crs_irate, + crs_idepth, + crs_imix, + + crs_cparam + +} crs_e; + + +// parameter ranges + +prm_rng_t crs_rng[] = { + + {crs_cparam, 0, 0}, // first entry is # of parameters + + {crs_ilfotype, 0, LFO_MAX}, // lfotype is LFO_SIN, LFO_RND, LFO_TRI etc (LFO_RND for chorus, LFO_SIN for flange) + {crs_irate, 0.0, 1000.0}, // rate is modulation frequency in Hz + {crs_idepth, 0.0, 1.0}, // depth is modulation depth, 0-1.0 + {crs_imix, 0.0, 1.0}, // mix is mix of chorus and clean signal + +}; + +// uses pitch, lfowav, rate, depth + +crs_t * CRS_Params ( prc_t *pprc ) +{ + crs_t *pcrs; + + pcrs = CRS_Alloc ( pprc->prm[crs_ilfotype], pprc->prm[crs_irate], pprc->prm[crs_idepth], pprc->prm[crs_imix] ); + + return pcrs; +} + +inline void * CRS_VParams ( void *p ) +{ + PRC_CheckParams ( (prc_t *)p, crs_rng ); + return (void *) CRS_Params ((prc_t *)p); +} + +inline void CRS_Mod ( void *p, float v ) { return; } + + +//////////////////////////////////////////////////// +// amplifier - modulatable gain, distortion +//////////////////////////////////////////////////// + +#define CAMPS 64 // max number amps active + +#define AMPSLEW 10 // milliseconds of slew time between gain changes + +struct amp_t +{ + bool fused; + + int gain; // amplification 0-6.0 * PMAX + int gain_max; // original gain setting + int distmix; // 0-1.0 mix of distortion with clean * PMAX + int vfeed; // 0-1.0 feedback with distortion * PMAX + int vthresh; // amplitude of clipping threshold 0..32768 + + + bool fchanging; // true if modulating to new amp value + float ramptime; // ramp 'glide' time - time in seconds to change between values + int mtime; // time in samples between amp changes. 0 implies no self-modulating + int mtimecur; // current time in samples until next amp change + int depth; // modulate amp from A to A - (A*depth) depth 0-1.0 + bool brand; // if true, use random modulation otherwise alternate btwn max/min + rmp_t rmp_interp; // interpolation ramp 0...PMAX + +}; + +amp_t amps[CAMPS]; + +void AMP_Init( amp_t *pamp ) { if (pamp) Q_memset( pamp, 0, sizeof (amp_t) ); }; +void AMP_Free( amp_t *pamp ) +{ + if (pamp) + { + Q_memset( pamp, 0, sizeof (amp_t) ); + } +} + + +void AMP_InitAll() { for (int i = 0; i < CAMPS; i++) AMP_Init( &s[i] ); } +void AMP_FreeAll() { for (int i = 0; i < CAMPS; i++) AMP_Free( &s[i] ); } + + +amp_t * AMP_Alloc( float gain, float vthresh, float distmix, float vfeed, float ramptime, float modtime, float depth, bool brand ) +{ + int i; + amp_t *pamp; + + // find free amp slot + + for ( i = 0; i < CAMPS; i++ ) + { + if ( !amps[i].fused ) + break; + } + + if ( i == CAMPS ) + { + DevMsg ("DSP: Warning, failed to allocate amp.\n" ); + return NULL; + } + + pamp = &s[i]; + + AMP_Init ( pamp ); + + pamp->fused = true; + + pamp->gain = gain * PMAX; + pamp->gain_max = gain * PMAX; + pamp->distmix = distmix * PMAX; + pamp->vfeed = vfeed * PMAX; + pamp->vthresh = vthresh * 32767.0; + + // modrate, 0.01, 200.0}, // frequency at which amplitude values change to new random value. 0 is no self-modulation + // moddepth, 0.0, 1.0}, // how much amplitude changes (decreases) from current value (0-1.0) + // modglide, 0.01, 100.0}, // glide time between mapcur and ampnew in milliseconds + + pamp->ramptime = ramptime; + pamp->mtime = SEC_TO_SAMPS(modtime); + pamp->mtimecur = pamp->mtime; + pamp->depth = depth * PMAX; + pamp->brand = brand; + + return pamp; +} + +// return next amplified sample + +inline int AMP_GetNext( amp_t *pamp, int x ) +{ + int y = x; + int d; + + // if distortion is on, add distortion, feedback + + if ( pamp->vthresh < PMAX && pamp->distmix ) + { + int vthresh = pamp->vthresh; + +/* if ( pamp->vfeed > 0.0 ) + { + // UNDONE: feedback + } +*/ + // clip distort + + d = ( y > vthresh ? vthresh : ( y < -vthresh ? -vthresh : y)); + + // mix distorted with clean (1.0 = full distortion) + + if ( pamp->distmix < PMAX ) + y = y + (((d - y) * pamp->distmix ) >> PBITS); + else + y = d; + } + + // get output for current gain value + + int xout = (y * pamp->gain) >> PBITS; + + if ( !pamp->fchanging && !pamp->mtime ) + { + // if not modulating and not self modulating, return right away + + return xout; + } + + if (pamp->fchanging) + { + // modulating... + + // get next gain value + + pamp->gain = RMP_GetNext( &pamp->rmp_interp ); // 0...next gain + + if ( RMP_HitEnd( &pamp->rmp_interp ) ) + { + // done. + + pamp->fchanging = false; + } + } + + // if self-modulating and timer has expired, get next change + + if ( pamp->mtime && !pamp->mtimecur-- ) + { + pamp->mtimecur = pamp->mtime; + + int gain_new; + int G1; + int G2 = pamp->gain_max; + + // modulate between 0 and 100% of gain_max + + G1 = pamp->gain_max - ((pamp->gain_max * pamp->depth) >> PBITS); + + if (pamp->brand) + { + gain_new = RandomInt( min(G1,G2), max(G1,G2) ); + } + else + { + // alternate between min & max + + gain_new = (pamp->gain == G1 ? G2 : G1); + } + + // set up modulation to new value + + pamp->fchanging = true; + + // init gain ramp - always hit target + + RMP_Init ( &pamp->rmp_interp, pamp->ramptime, pamp->gain, gain_new, false ); + } + + return xout; + +} + +// batch version for performance + +inline void AMP_GetNextN( amp_t *pamp, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch (op) + { + default: + case OP_LEFT: + while (count--) + { + pb->left = AMP_GetNext( pamp, pb->left ); + pb++; + } + return; + case OP_RIGHT: + while (count--) + { + pb->right = AMP_GetNext( pamp, pb->right ); + pb++; + } + return; + case OP_LEFT_DUPLICATE: + while (count--) + { + pb->left = pb->right = AMP_GetNext( pamp, pb->left ); + pb++; + } + return; + } +} + +inline void AMP_Mod( amp_t *pamp, float v ) +{ +} + + +// parameter order + +typedef enum { + + amp_gain, + amp_vthresh, + amp_distmix, + amp_vfeed, + amp_imodrate, + amp_imoddepth, + amp_imodglide, + amp_irand, + amp_cparam + +} amp_e; + + +// parameter ranges + +prm_rng_t amp_rng[] = { + + {amp_cparam, 0, 0}, // first entry is # of parameters + + {amp_gain, 0.0, 1000.0}, // amplification + {amp_vthresh, 0.0, 1.0}, // threshold for distortion (1.0 = no distortion) + {amp_distmix, 0.0, 1.0}, // mix of clean and distortion (1.0 = full distortion, 0.0 = full clean) + {amp_vfeed, 0.0, 1.0}, // distortion feedback + + {amp_imodrate, 0.0, 200.0}, // frequency at which amplitude values change to new random value. 0 is no self-modulation + {amp_imoddepth, 0.0, 1.0}, // how much amplitude changes (decreases) from current value (0-1.0) + {amp_imodglide, 0.01, 100.0}, // glide time between mapcur and ampnew in milliseconds + {amp_irand, 0.0, 1.0}, // if 1, use random modulation otherwise alternate from max-min-max +}; + +amp_t * AMP_Params ( prc_t *pprc ) +{ + amp_t *pamp; + + float ramptime = 0.0; + float modtime = 0.0; + float depth = 0.0; + float rand = pprc->prm[amp_irand]; + bool brand; + + if (pprc->prm[amp_imodrate] > 0.0) + { + ramptime = pprc->prm[amp_imodglide] / 1000.0; // get ramp time in seconds + modtime = 1.0 / max((double)pprc->prm[amp_imodrate], 0.01); // time between modulations in seconds + depth = pprc->prm[amp_imoddepth]; // depth of modulations 0-1.0 + } + + brand = rand > 0.0 ? 1 : 0; + + pamp = AMP_Alloc ( pprc->prm[amp_gain], pprc->prm[amp_vthresh], pprc->prm[amp_distmix], pprc->prm[amp_vfeed], + ramptime, modtime, depth, brand ); + + return pamp; +} + +inline void * AMP_VParams ( void *p ) +{ + PRC_CheckParams ( (prc_t *)p, amp_rng ); + return (void *) AMP_Params ((prc_t *)p); +} + + +///////////////// +// NULL processor +///////////////// + +struct nul_t +{ + int type; +}; + +nul_t nuls[] = {{0}}; + +void NULL_Init ( nul_t *pnul ) { } +void NULL_InitAll( ) { } +void NULL_Free ( nul_t *pnul ) { } +void NULL_FreeAll ( ) { } +nul_t *NULL_Alloc ( ) { return &nuls[0]; } + +inline int NULL_GetNext ( void *p, int x) { return x; } + +inline void NULL_GetNextN( nul_t *pnul, portable_samplepair_t *pbuffer, int SampleCount, int op ) { return; } + +inline void NULL_Mod ( void *p, float v ) { return; } + +inline void * NULL_VParams ( void *p ) { return (void *) (&nuls[0]); } + +////////////////////////// +// DSP processors presets - see dsp_presets.txt +////////////////////////// + + + + +// init array of processors - first store pfnParam, pfnGetNext and pfnFree functions for type, +// then call the pfnParam function to initialize each processor + +// prcs - an array of prc structures, all with initialized params +// count - number of elements in the array + +// returns false if failed to init one or more processors + +bool PRC_InitAll( prc_t *prcs, int count ) +{ + int i; + prc_Param_t pfnParam; // allocation function - takes ptr to prc, returns ptr to specialized data struct for proc type + prc_GetNext_t pfnGetNext; // get next function + prc_GetNextN_t pfnGetNextN; // get next function, batch version + prc_Free_t pfnFree; + prc_Mod_t pfnMod; + + bool fok = true;; + + if ( count == 0 ) + count = 1; + + // set up pointers to XXX_Free, XXX_GetNext and XXX_Params functions + + for (i = 0; i < count; i++) + { + switch (prcs[i].type) + { + default: + case PRC_NULL: + pfnFree = (prc_Free_t)NULL_Free; + pfnGetNext = (prc_GetNext_t)NULL_GetNext; + pfnGetNextN = (prc_GetNextN_t)NULL_GetNextN; + pfnParam = NULL_VParams; + pfnMod = (prc_Mod_t)NULL_Mod; + break; + case PRC_DLY: + pfnFree = (prc_Free_t)DLY_Free; + pfnGetNext = (prc_GetNext_t)DLY_GetNext; + pfnGetNextN = (prc_GetNextN_t)DLY_GetNextN; + pfnParam = DLY_VParams; + pfnMod = (prc_Mod_t)DLY_Mod; + break; + case PRC_RVA: + pfnFree = (prc_Free_t)RVA_Free; + pfnGetNext = (prc_GetNext_t)RVA_GetNext; + pfnGetNextN = (prc_GetNextN_t)RVA_GetNextN; + pfnParam = RVA_VParams; + pfnMod = (prc_Mod_t)RVA_Mod; + break; + case PRC_FLT: + pfnFree = (prc_Free_t)FLT_Free; + pfnGetNext = (prc_GetNext_t)FLT_GetNext; + pfnGetNextN = (prc_GetNextN_t)FLT_GetNextN; + pfnParam = FLT_VParams; + pfnMod = (prc_Mod_t)FLT_Mod; + break; + case PRC_CRS: + pfnFree = (prc_Free_t)CRS_Free; + pfnGetNext = (prc_GetNext_t)CRS_GetNext; + pfnGetNextN = (prc_GetNextN_t)CRS_GetNextN; + pfnParam = CRS_VParams; + pfnMod = (prc_Mod_t)CRS_Mod; + break; + case PRC_PTC: + pfnFree = (prc_Free_t)PTC_Free; + pfnGetNext = (prc_GetNext_t)PTC_GetNext; + pfnGetNextN = (prc_GetNextN_t)PTC_GetNextN; + pfnParam = PTC_VParams; + pfnMod = (prc_Mod_t)PTC_Mod; + break; + case PRC_ENV: + pfnFree = (prc_Free_t)ENV_Free; + pfnGetNext = (prc_GetNext_t)ENV_GetNext; + pfnGetNextN = (prc_GetNextN_t)ENV_GetNextN; + pfnParam = ENV_VParams; + pfnMod = (prc_Mod_t)ENV_Mod; + break; + case PRC_LFO: + pfnFree = (prc_Free_t)LFO_Free; + pfnGetNext = (prc_GetNext_t)LFO_GetNext; + pfnGetNextN = (prc_GetNextN_t)LFO_GetNextN; + pfnParam = LFO_VParams; + pfnMod = (prc_Mod_t)LFO_Mod; + break; + case PRC_EFO: + pfnFree = (prc_Free_t)EFO_Free; + pfnGetNext = (prc_GetNext_t)EFO_GetNext; + pfnGetNextN = (prc_GetNextN_t)EFO_GetNextN; + pfnParam = EFO_VParams; + pfnMod = (prc_Mod_t)EFO_Mod; + break; + case PRC_MDY: + pfnFree = (prc_Free_t)MDY_Free; + pfnGetNext = (prc_GetNext_t)MDY_GetNext; + pfnGetNextN = (prc_GetNextN_t)MDY_GetNextN; + pfnParam = MDY_VParams; + pfnMod = (prc_Mod_t)MDY_Mod; + break; + case PRC_DFR: + pfnFree = (prc_Free_t)DFR_Free; + pfnGetNext = (prc_GetNext_t)DFR_GetNext; + pfnGetNextN = (prc_GetNextN_t)DFR_GetNextN; + pfnParam = DFR_VParams; + pfnMod = (prc_Mod_t)DFR_Mod; + break; + case PRC_AMP: + pfnFree = (prc_Free_t)AMP_Free; + pfnGetNext = (prc_GetNext_t)AMP_GetNext; + pfnGetNextN = (prc_GetNextN_t)AMP_GetNextN; + pfnParam = AMP_VParams; + pfnMod = (prc_Mod_t)AMP_Mod; + break; + } + + // set up function pointers + + prcs[i].pfnParam = pfnParam; + prcs[i].pfnGetNext = pfnGetNext; + prcs[i].pfnGetNextN = pfnGetNextN; + prcs[i].pfnFree = pfnFree; + prcs[i].pfnMod = pfnMod; + + // call param function, store pdata for the processor type + + prcs[i].pdata = pfnParam ( (void *) (&prcs[i]) ); + + if ( !prcs[i].pdata ) + fok = false; + } + + return fok; +} + +// free individual processor's data + +void PRC_Free ( prc_t *pprc ) +{ + if ( pprc->pfnFree && pprc->pdata ) + pprc->pfnFree ( pprc->pdata ); +} + +// free all processors for supplied array +// prcs - array of processors +// count - elements in array + +void PRC_FreeAll ( prc_t *prcs, int count ) +{ + for (int i = 0; i < count; i++) + PRC_Free( &prcs[i] ); +} + +// get next value for processor - (usually called directly by PSET_GetNext) + +inline int PRC_GetNext ( prc_t *pprc, int x ) +{ + return pprc->pfnGetNext ( pprc->pdata, x ); +} + +// automatic parameter range limiting +// force parameters between specified min/max in param_rng + +void PRC_CheckParams ( prc_t *pprc, prm_rng_t *prng ) +{ + // first entry in param_rng is # of parameters + + int cprm = prng[0].iprm; + + for (int i = 0; i < cprm; i++) + { + // if parameter is 0.0, always allow it (this is 'off' for most params) + + if ( pprc->prm[i] != 0.0 && (pprc->prm[i] > prng[i+1].hi || pprc->prm[i] < prng[i+1].lo) ) + { + DevMsg ("DSP: Warning, clamping out of range parameter.\n" ); + pprc->prm[i] = clamp (pprc->prm[i], prng[i+1].lo, prng[i+1].hi); + } + } +} + + +// DSP presets + +// A dsp preset comprises one or more dsp processors in linear, parallel or feedback configuration + +// preset configurations +// +#define PSET_SIMPLE 0 + +// x(n)--->P(0)--->y(n) + +#define PSET_LINEAR 1 + +// x(n)--->P(0)-->P(1)-->...P(m)--->y(n) + + +#define PSET_PARALLEL2 5 + +// x(n)--->P(0)-->(+)-->y(n) +// ^ +// | +// x(n)--->P(1)----- + +#define PSET_PARALLEL4 6 + +// x(n)--->P(0)-->P(1)-->(+)-->y(n) +// ^ +// | +// x(n)--->P(2)-->P(3)----- + +#define PSET_PARALLEL5 7 + +// x(n)--->P(0)-->P(1)-->(+)-->P(4)-->y(n) +// ^ +// | +// x(n)--->P(2)-->P(3)----- + +#define PSET_FEEDBACK 8 + +// x(n)-P(0)--(+)-->P(1)-->P(2)---->y(n) +// ^ | +// | v +// -----P(4)<--P(3)-- + +#define PSET_FEEDBACK3 9 + +// x(n)---(+)-->P(0)--------->y(n) +// ^ | +// | v +// -----P(2)<--P(1)-- + +#define PSET_FEEDBACK4 10 + +// x(n)---(+)-->P(0)-------->P(3)--->y(n) +// ^ | +// | v +// ---P(2)<--P(1)-- + +#define PSET_MOD 11 + +// +// x(n)------>P(1)--P(2)--P(3)--->y(n) +// ^ +// x(n)------>P(0)....: + +#define PSET_MOD2 12 + +// +// x(n)-------P(1)-->y(n) +// ^ +// x(n)-->P(0)..: + + +#define PSET_MOD3 13 + +// +// x(n)-------P(1)-->P(2)-->y(n) +// ^ +// x(n)-->P(0)..: + + +#define CPSETS 64 // max number of presets simultaneously active + +#define CPSET_PRCS 5 // max # of processors per dsp preset +#define CPSET_STATES (CPSET_PRCS+3) // # of internal states + +// NOTE: do not reorder members of pset_t - g_psettemplates relies on it!!! + +struct pset_t +{ + int type; // preset configuration type + int cprcs; // number of processors for this preset + + prc_t prcs[CPSET_PRCS]; // processor preset data + + float mix_min; // min dsp mix at close range + float mix_max; // max dsp mix at long range + float db_min; // if sndlvl of a new sound is < db_min, reduce mix_min/max by db_mixdrop + float db_mixdrop; // reduce mix_min/max by n% if sndlvl of new sound less than db_min + float duration; // if > 0, duration of preset in seconds (duration 0 = infinite) + float fade; // fade out time, exponential fade + + int csamp_duration; // duration counter # samples + + int w[CPSET_STATES]; // internal states + int fused; +}; + +pset_t psets[CPSETS]; + +pset_t *g_psettemplates = NULL; +int g_cpsettemplates = 0; + +// returns true if preset will expire after duration + +bool PSET_IsOneShot( pset_t *ppset ) +{ + return ppset->duration > 0.0; +} + +// return true if preset is no longer active - duration has expired + +bool PSET_HasExpired( pset_t *ppset ) +{ + if (!PSET_IsOneShot( ppset )) + return false; + + return ppset->csamp_duration <= 0; +} + +// if preset is oneshot, update duration counter by SampleCount samples + +void PSET_UpdateDuration( pset_t *ppset, int SampleCount ) +{ + if ( PSET_IsOneShot( ppset ) ) + { + // if oneshot preset and not expired, decrement sample count + + if (ppset->csamp_duration > 0) + ppset->csamp_duration -= SampleCount; + } +} + +// A dsp processor (prc) performs a single-sample function, such as pitch shift, delay, reverb, filter + + +// init a preset - just clear state array + +void PSET_Init( pset_t *ppset ) +{ + // clear state array + + if (ppset) + Q_memset( ppset->w, 0, sizeof (int) * (CPSET_STATES) ); +} + +// clear runtime slots + +void PSET_InitAll( void ) +{ + for (int i = 0; i < CPSETS; i++) + Q_memset( &psets[i], 0, sizeof(pset_t)); +} + +// free the preset - free all processors + +void PSET_Free( pset_t *ppset ) +{ + if (ppset) + { + // free processors + + PRC_FreeAll ( ppset->prcs, ppset->cprcs ); + + // clear + + Q_memset( ppset, 0, sizeof (pset_t)); + } +} + +void PSET_FreeAll() { for (int i = 0; i < CPSETS; i++) PSET_Free( &psets[i] ); }; + +// return preset struct, given index into preset template array +// NOTE: should not ever be more than 2 or 3 of these active simultaneously + +pset_t * PSET_Alloc ( int ipsettemplate ) +{ + pset_t *ppset; + bool fok; + + // don't excede array bounds + + if ( ipsettemplate >= g_cpsettemplates) + ipsettemplate = 0; + + // find free slot + int i = 0; + for (i = 0; i < CPSETS; i++) + { + if ( !psets[i].fused ) + break; + } + + if ( i == CPSETS ) + return NULL; + + if (das_debug.GetInt()) + { + int nSlots = 0; + for ( int j = 0; j < CPSETS; j++) + { + if ( psets[j].fused ) + nSlots++; + } + DevMsg("total preset slots used: %d \n", nSlots ); + } + + + ppset = &psets[i]; + + // clear preset + + Q_memset(ppset, 0, sizeof(pset_t)); + + // copy template into preset + + *ppset = g_psettemplates[ipsettemplate]; + + ppset->fused = true; + + // clear state array + + PSET_Init ( ppset ); + + // init all processors, set up processor function pointers + + fok = PRC_InitAll( ppset->prcs, ppset->cprcs ); + + if ( !fok ) + { + // failed to init one or more processors + Warning( "Sound DSP: preset failed to init.\n"); + PRC_FreeAll ( ppset->prcs, ppset->cprcs ); + return NULL; + } + + // if preset has duration, setup duration sample counter + + if ( PSET_IsOneShot( ppset ) ) + { + ppset->csamp_duration = SEC_TO_SAMPS( ppset->duration ); + } + + return ppset; +} + +// batch version of PSET_GetNext for linear array of processors. For performance. + +// ppset - preset array +// pbuffer - input sample data +// SampleCount - size of input buffer +// OP: OP_LEFT - process left channel in place +// OP_RIGHT - process right channel in place +// OP_LEFT_DUPLICATe - process left channel, duplicate into right + +inline void PSET_GetNextN( pset_t *ppset, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + portable_samplepair_t *pbf = pbuffer; + prc_t *pprc; + int count = ppset->cprcs; + + switch ( ppset->type ) + { + default: + case PSET_SIMPLE: + { + // x(n)--->P(0)--->y(n) + + ppset->prcs[0].pfnGetNextN (ppset->prcs[0].pdata, pbf, SampleCount, op); + return; + } + case PSET_LINEAR: + { + + // w0 w1 w2 + // x(n)--->P(0)-->P(1)-->...P(count-1)--->y(n) + + // w0 w1 w2 w3 w4 w5 + // x(n)--->P(0)-->P(1)-->P(2)-->P(3)-->P(4)-->y(n) + + // call batch processors in sequence - no internal state for batch processing + + // point to first processor + + pprc = &ppset->prcs[0]; + + for (int i = 0; i < count; i++) + { + pprc->pfnGetNextN (pprc->pdata, pbf, SampleCount, op); + pprc++; + } + + return; + } + } +} + + +// Get next sample from this preset. called once for every sample in buffer +// ppset is pointer to preset +// x is input sample + +inline int PSET_GetNext ( pset_t *ppset, int x ) +{ + + // pset_simple and pset_linear have no internal state: + // this is REQUIRED for all presets that have a batch getnextN equivalent! + + if ( ppset->type == PSET_SIMPLE ) + { + // x(n)--->P(0)--->y(n) + + return ppset->prcs[0].pfnGetNext (ppset->prcs[0].pdata, x); + } + + prc_t *pprc; + int count = ppset->cprcs; + + if ( ppset->type == PSET_LINEAR ) + { + int y = x; + + // w0 w1 w2 + // x(n)--->P(0)-->P(1)-->...P(count-1)--->y(n) + + // w0 w1 w2 w3 w4 w5 + // x(n)--->P(0)-->P(1)-->P(2)-->P(3)-->P(4)-->y(n) + + // call processors in reverse order, from count to 1 + + //for (int i = count; i > 0; i--, pprc--) + // w[i] = pprc->pfnGetNext (pprc->pdata, w[i-1]); + + // return w[count]; + + + // point to first processor, update sequentially, no state preserved + + pprc = &ppset->prcs[0]; + + switch (count) + { + default: + case 5: + y = pprc->pfnGetNext (pprc->pdata, y); + pprc++; + case 4: + y = pprc->pfnGetNext (pprc->pdata, y); + pprc++; + case 3: + y = pprc->pfnGetNext (pprc->pdata, y); + pprc++; + case 2: + y = pprc->pfnGetNext (pprc->pdata, y); + pprc++; + case 1: + case 0: + y = pprc->pfnGetNext (pprc->pdata, y); + } + + return y; + } + + // all other preset types have internal state: + + // initialize 0'th element of state array + + int *w = ppset->w; + w[0] = x; + + switch ( ppset->type ) + { + default: + + case PSET_PARALLEL2: + { // w0 w1 w3 + // x(n)--->P(0)-->(+)-->y(n) + // ^ + // w0 w2 | + // x(n)--->P(1)----- + + pprc = &ppset->prcs[0]; + + w[3] = w[1] + w[2]; + + w[1] = pprc->pfnGetNext( pprc->pdata, w[0] ); + pprc++; + w[2] = pprc->pfnGetNext( pprc->pdata, w[0] ); + + return w[3]; + } + + case PSET_PARALLEL4: + { // w0 w1 w2 w5 + // x(n)--->P(0)-->P(1)-->(+)-->y(n) + // ^ + // w0 w3 w4 | + // x(n)--->P(2)-->P(3)----- + + + pprc = &ppset->prcs[0]; + + w[5] = w[2] + w[4]; + + w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[1] ); + w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] ); + + w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] ); + w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[0] ); + + return w[5]; + } + + case PSET_PARALLEL5: + { // w0 w1 w2 w5 w6 + // x(n)--->P(0)-->P(1)-->(+)--P(4)-->y(n) + // ^ + // w0 w3 w4 | + // x(n)--->P(2)-->P(3)----- + + pprc = &ppset->prcs[0]; + + w[5] = w[2] + w[4]; + + w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[1] ); + w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] ); + + w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] ); + w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[0] ); + + return pprc[4].pfnGetNext( pprc[4].pdata, w[5] ); + } + + case PSET_FEEDBACK: + { + // w0 w1 w2 w3 w4 w7 + // x(n)-P(0)--(+)-->P(1)-->P(2)-->---->y(n) + // ^ | + // | w6 w5 v + // -----P(4)<--P(3)-- + + pprc = &ppset->prcs[0]; + + // start with adders + + w[2] = w[1] + w[6]; + + // evaluate in reverse order + + w[6] = pprc[4].pfnGetNext( pprc[4].pdata, w[5] ); + w[5] = pprc[3].pfnGetNext( pprc[3].pdata, w[4] ); + + w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] ); + w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] ); + w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] ); + + return w[4]; + } + case PSET_FEEDBACK3: + { + // w0 w1 w2 + // x(n)---(+)-->P(0)--------->y(n) + // ^ | + // | w4 w3 v + // -----P(2)<--P(1)-- + + pprc = &ppset->prcs[0]; + + // start with adders + + w[1] = w[0] + w[4]; + + // evaluate in reverse order + + w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] ); + w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] ); + w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[1] ); + + return w[2]; + } + case PSET_FEEDBACK4: + { + // w0 w1 w2 w5 + // x(n)---(+)-->P(0)-------->P(3)--->y(n) + // ^ | + // | w4 w3 v + // ---P(2)<--P(1)-- + + pprc = &ppset->prcs[0]; + + // start with adders + + w[1] = w[0] + w[4]; + + // evaluate in reverse order + + w[5] = pprc[3].pfnGetNext( pprc[3].pdata, w[2] ); + w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] ); + w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] ); + w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[1] ); + + return w[2]; + } + case PSET_MOD: + { + // w0 w1 w3 w4 + // x(n)------>P(1)--P(2)--P(3)--->y(n) + // w0 w2 ^ + // x(n)------>P(0)....: + + pprc = &ppset->prcs[0]; + + w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] ); + + w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[1] ); + + // modulate processor 2 + + pprc[2].pfnMod( pprc[2].pdata, ((float)w[2] / (float)PMAX)); + + // get modulator output + + w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] ); + + w[1] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] ); + + return w[4]; + } + case PSET_MOD2: + { + // w0 w2 + // x(n)---------P(1)-->y(n) + // w0 w1 ^ + // x(n)-->P(0)....: + + pprc = &ppset->prcs[0]; + + // modulate processor 1 + + pprc[1].pfnMod( pprc[1].pdata, ((float)w[1] / (float)PMAX)); + + // get modulator output + + w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] ); + + w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] ); + + return w[2]; + + } + case PSET_MOD3: + { + // w0 w2 w3 + // x(n)----------P(1)-->P(2)-->y(n) + // w0 w1 ^ + // x(n)-->P(0).....: + + pprc = &ppset->prcs[0]; + + w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[2] ); + + // modulate processor 1 + + pprc[1].pfnMod( pprc[1].pdata, ((float)w[1] / (float)PMAX)); + + // get modulator output + + w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] ); + + w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] ); + + return w[2]; + } + } +} + + +///////////// +// DSP system +///////////// + +// Main interface + +// Whenever the preset # changes on any of these processors, the old processor is faded out, new is faded in. +// dsp_chan is optionally set when a sound is played - a preset is sent with the start_static/dynamic sound. +// +// sound1---->dsp_chan--> -------------(+)---->dsp_water--->dsp_player--->out +// sound2---->dsp_chan--> | | +// sound3---------------> ----dsp_room--- +// | | +// --dsp_indirect- + +// dsp_room - set this cvar to a preset # to change the room dsp. room fx are more prevalent farther from player. +// use: when player moves into a new room, all sounds played in room take on its reverberant character +// dsp_water - set this cvar (once) to a preset # for serial underwater sound. +// use: when player goes under water, all sounds pass through this dsp (such as low pass filter) +// dsp_player - set this cvar to a preset # to cause all sounds to run through the effect (serial, in-line). +// use: player is deafened, player fires special weapon, player is hit by special weapon. +// dsp_facingaway- set this cvar to a preset # appropriate for sounds which are played facing away from player (weapon,voice) +// +// dsp_spatial - set by system to create modulated spatial delays for left/right/front/back ears - delay value +// modulates by distance to nearest l/r surface in world + +// Dsp presets + + +ConVar dsp_room ("dsp_room", "0", FCVAR_DEMO ); // room dsp preset - sounds more distant from player (1ch) +ConVar dsp_water ("dsp_water", "14", FCVAR_DEMO ); // "14" underwater dsp preset - sound when underwater (1-2ch) +ConVar dsp_player ("dsp_player", "0", FCVAR_DEMO | FCVAR_SERVER_CAN_EXECUTE ); // dsp on player - sound when player hit by special device (1-2ch) +ConVar dsp_facingaway ("dsp_facingaway", "0", FCVAR_DEMO ); // "30" sounds that face away from player (weapons, voice) (1-4ch) +ConVar dsp_speaker ("dsp_speaker", "50", FCVAR_DEMO ); // "50" small distorted speaker sound (1ch) +ConVar dsp_spatial ("dsp_spatial", "40", FCVAR_DEMO ); // spatial delays for l/r front/rear ears +ConVar dsp_automatic ("dsp_automatic", "0", FCVAR_DEMO ); // automatic room type detection. if non zero, replaces dsp_room + +int ipset_room_prev; +int ipset_water_prev; +int ipset_player_prev; +int ipset_facingaway_prev; +int ipset_speaker_prev; +int ipset_spatial_prev; +int ipset_automatic_prev; + +// legacy room_type support + +ConVar dsp_room_type ( "room_type", "0", FCVAR_DEMO ); +int ipset_room_typeprev; + + +// DSP processors + +int idsp_room; +int idsp_water; +int idsp_player; +int idsp_facingaway; +int idsp_speaker; +int idsp_spatial; +int idsp_automatic; + +ConVar dsp_off ("dsp_off", "0", FCVAR_CHEAT | FCVAR_ALLOWED_IN_COMPETITIVE ); // set to 1 to disable all dsp processing +ConVar dsp_slow_cpu ("dsp_slow_cpu", "0", FCVAR_ARCHIVE|FCVAR_DEMO ); // set to 1 if cpu bound - ie: does not process dsp_room fx +ConVar snd_profile ("snd_profile", "0", FCVAR_DEMO ); // 1 - profile dsp, 2 - mix, 3 - load sound, 4 - all sound +ConVar dsp_volume ("dsp_volume", "1.0", FCVAR_ARCHIVE|FCVAR_DEMO ); // 0.0 - 2.0; master dsp volume control +ConVar dsp_vol_5ch ("dsp_vol_5ch", "0.5", FCVAR_DEMO ); // 0.0 - 1.0; attenuate master dsp volume for 5ch surround +ConVar dsp_vol_4ch ("dsp_vol_4ch", "0.5", FCVAR_DEMO ); // 0.0 - 1.0; attenuate master dsp volume for 4ch surround +ConVar dsp_vol_2ch ("dsp_vol_2ch", "1.0", FCVAR_DEMO ); // 0.0 - 1.0; attenuate master dsp volume for 2ch surround + +ConVar dsp_enhance_stereo("dsp_enhance_stereo", "0", FCVAR_ARCHIVE ); // 1) use dsp_spatial delays on all reverb channels + +// DSP preset executor + +#define CDSPS 32 // max number dsp executors active +#define DSPCHANMAX 5 // max number of channels dsp can process (allocs a separte processor for each chan) + +struct dsp_t +{ + bool fused; + int cchan; // 1-5 channels, ie: mono, FrontLeft, FrontRight, RearLeft, RearRight, FrontCenter + + pset_t *ppset[DSPCHANMAX]; // current preset (1-5 channels) + int ipset; // current ipreset + + pset_t *ppsetprev[DSPCHANMAX]; // previous preset (1-5 channels) + int ipsetprev; // previous ipreset + + float xfade; // crossfade time between previous preset and new + float xfade_default; // default xfade value, set in DSP_Alloc + bool bexpfade; // true if exponential crossfade + + int ipsetsav_oneshot; // previous preset before one-shot preset was set + + rmp_t xramp; // crossfade ramp +}; + +dsp_t dsps[CDSPS]; + +void DSP_Init( int idsp ) +{ + dsp_t *pdsp; + + Assert( idsp < CDSPS ); + + if (idsp < 0 || idsp >= CDSPS) + return; + + pdsp = &dsps[idsp]; + + Q_memset( pdsp, 0, sizeof (dsp_t) ); +} + +void DSP_Free( int idsp ) +{ + dsp_t *pdsp; + + Assert( idsp < CDSPS ); + + if (idsp < 0 || idsp >= CDSPS) + return; + + pdsp = &dsps[idsp]; + + for (int i = 0; i < pdsp->cchan; i++) + { + if ( pdsp->ppset[i] ) + PSET_Free( pdsp->ppset[i] ); + + if ( pdsp->ppsetprev[i] ) + PSET_Free( pdsp->ppsetprev[i] ); + } + + Q_memset( pdsp, 0, sizeof (dsp_t) ); +} + +// Init all dsp processors - called once, during engine startup + +void DSP_InitAll ( bool bLoadPresetFile ) +{ + // only load template file on engine startup + + if ( bLoadPresetFile ) + DSP_LoadPresetFile(); + + // order is important, don't rearange. + + FLT_InitAll(); + DLY_InitAll(); + RVA_InitAll(); + LFOWAV_InitAll(); + LFO_InitAll(); + + CRS_InitAll(); + PTC_InitAll(); + ENV_InitAll(); + EFO_InitAll(); + MDY_InitAll(); + AMP_InitAll(); + + PSET_InitAll(); + + for (int idsp = 0; idsp < CDSPS; idsp++) + DSP_Init( idsp ); +} + +// free all resources associated with dsp - called once, during engine shutdown + +void DSP_FreeAll (void) +{ + // order is important, don't rearange. + + for (int idsp = 0; idsp < CDSPS; idsp++) + DSP_Free( idsp ); + + AMP_FreeAll(); + MDY_FreeAll(); + EFO_FreeAll(); + ENV_FreeAll(); + PTC_FreeAll(); + CRS_FreeAll(); + + LFO_FreeAll(); + LFOWAV_FreeAll(); + RVA_FreeAll(); + DLY_FreeAll(); + FLT_FreeAll(); +} + + +// allocate a new dsp processor chain, kill the old processor. Called during dsp init only. +// ipset is new preset +// xfade is crossfade time when switching between presets (milliseconds) +// cchan is how many simultaneous preset channels to allocate (1-4) +// return index to new dsp + +int DSP_Alloc( int ipset, float xfade, int cchan ) +{ + dsp_t *pdsp; + int i; + int idsp; + int cchans = clamp( cchan, 1, DSPCHANMAX); + + // find free slot + + for ( idsp = 0; idsp < CDSPS; idsp++ ) + { + if ( !dsps[idsp].fused ) + break; + } + + if ( idsp >= CDSPS ) + return -1; + + pdsp = &dsps[idsp]; + + DSP_Init ( idsp ); + + pdsp->fused = true; + + pdsp->cchan = cchans; + + // allocate a preset processor for each channel + + pdsp->ipset = ipset; + pdsp->ipsetprev = 0; + pdsp->ipsetsav_oneshot = 0; + + for (i = 0; i < pdsp->cchan; i++) + { + pdsp->ppset[i] = PSET_Alloc ( ipset ); + pdsp->ppsetprev[i] = NULL; + } + + // set up crossfade time in seconds + + pdsp->xfade = xfade / 1000.0; + pdsp->xfade_default = pdsp->xfade; + + RMP_SetEnd(&pdsp->xramp); + + return idsp; +} + +// call modulation function of specified processor within dsp preset + +// idsp - dsp preset +// channel - channel 1-5 (l,r,rl,rr,fc) +// iproc - which processor to change (normally 0) +// value - new parameter value for processor + +// NOTE: routine returns with no result or error if any parameter is invalid. + +void DSP_ChangePresetValue( int idsp, int channel, int iproc, float value ) +{ + + dsp_t *pdsp; + pset_t *ppset; // preset + prc_Mod_t pfnMod; // modulation function + + if (idsp < 0 || idsp >= CDSPS) + return; + + if (channel >= DSPCHANMAX) + return; + + if (iproc >= CPSET_PRCS) + return; + + // get ptr to processor preset + + pdsp = &dsps[idsp]; + + // assert that this dsp processor has enough separate channels + + Assert(channel <= pdsp->cchan); + + ppset = pdsp->ppset[channel]; + + if (!ppset) + return; + + // get ptr to modulation function + + pfnMod = ppset->prcs[iproc].pfnMod; + + if (!pfnMod) + return; + + // call modulation function with new value + + pfnMod (ppset->prcs[iproc].pdata, value); +} + + +#define DSP_AUTOMATIC 1 // corresponds to Generic preset + +// if dsp_room == DSP_AUTOMATIC, then use dsp_automatic value for dsp +// any subsequent reset of dsp_room will disable automatic room detection. + +// return true if automatic room detection is enabled + +bool DSP_CheckDspAutoEnabled( void ) +{ + return (dsp_room.GetInt() == DSP_AUTOMATIC); +} + +// set dsp_automatic preset, used in place of dsp_room when automatic room detection enabled + +void DSP_SetDspAuto( int dsp_preset ) +{ + // set dsp_preset into dsp_automatic + + dsp_automatic.SetValue( dsp_preset ); +} + +// wrapper on dsp_room GetInt so that dsp_automatic can override + +int dsp_room_GetInt ( void ) +{ + // if dsp_automatic is not enabled, get room + + if (! DSP_CheckDspAutoEnabled()) + return dsp_room.GetInt(); + + // automatic room detection is on, get dsp_automatic instead of dsp_room + + return dsp_automatic.GetInt(); +} + +// wrapper on idsp_room preset so that idsp_automatic can override + +int Get_idsp_room ( void ) +{ + + // if dsp_automatic is not enabled, get room + + if ( !DSP_CheckDspAutoEnabled()) + return idsp_room; + + // automatic room detection is on, return dsp_automatic preset instead of dsp_room preset + + return idsp_automatic; +} + + +// free previous preset if not 0 + +inline void DSP_FreePrevPreset( dsp_t *pdsp ) +{ + // free previous presets if non-null - ie: rapid change of preset just kills old without xfade + + if ( pdsp->ipsetprev ) + { + for (int i = 0; i < pdsp->cchan; i++) + { + if ( pdsp->ppsetprev[i] ) + { + PSET_Free( pdsp->ppsetprev[i] ); + pdsp->ppsetprev[i] = NULL; + } + } + + pdsp->ipsetprev = 0; + } + +} + +extern ConVar dsp_mix_min; +extern ConVar dsp_mix_max; +extern ConVar dsp_db_min; +extern ConVar dsp_db_mixdrop; + +// alloc new preset if different from current +// xfade from prev to new preset +// free previous preset, copy current into previous, set up xfade from previous to new + +void DSP_SetPreset( int idsp, int ipsetnew) +{ + dsp_t *pdsp; + pset_t *ppsetnew[DSPCHANMAX]; + + Assert (idsp >= 0 && idsp < CDSPS); + + pdsp = &dsps[idsp]; + + // validate new preset range + + if ( ipsetnew >= g_cpsettemplates || ipsetnew < 0 ) + return; + + // ignore if new preset is same as current preset + + if ( ipsetnew == pdsp->ipset ) + return; + + // alloc new presets (each channel is a duplicate preset) + + Assert (pdsp->cchan <= DSPCHANMAX); + + for (int i = 0; i < pdsp->cchan; i++) + { + ppsetnew[i] = PSET_Alloc ( ipsetnew ); + if ( !ppsetnew[i] ) + { + DevMsg("WARNING: DSP preset failed to allocate.\n"); + return; + } + } + + Assert (pdsp); + + // free PREVIOUS previous preset if not 0 + + DSP_FreePrevPreset( pdsp ); + + for (int i = 0; i < pdsp->cchan; i++) + { + // current becomes previous + + pdsp->ppsetprev[i] = pdsp->ppset[i]; + + // new becomes current + + pdsp->ppset[i] = ppsetnew[i]; + } + + pdsp->ipsetprev = pdsp->ipset; + pdsp->ipset = ipsetnew; + + if ( idsp == idsp_room || idsp == idsp_automatic ) + { + // set up new dsp mix min & max, db_min & db_drop params so that new channels get new mix values + + // NOTE: only new sounds will get the new mix min/max values set in their dspmix param + // NOTE: so - no crossfade is needed between dspmix and dspmix prev, but this also means + // NOTE: that currently playing ambients will not see changes to dspmix at all. + + float mix_min = pdsp->ppset[0]->mix_min; + float mix_max = pdsp->ppset[0]->mix_max; + float db_min = pdsp->ppset[0]->db_min; + float db_mixdrop = pdsp->ppset[0]->db_mixdrop; + + dsp_mix_min.SetValue( mix_min ); + dsp_mix_max.SetValue( mix_max ); + dsp_db_min.SetValue( db_min ); + dsp_db_mixdrop.SetValue( db_mixdrop ); + } + + RMP_SetEnd( &pdsp->xramp ); + + // make sure previous dsp preset has data + + Assert (pdsp->ppsetprev[0]); + + // shouldn't be crossfading if current dsp preset == previous dsp preset + + Assert (pdsp->ipset != pdsp->ipsetprev); + + // if new preset is one-shot, keep previous preset to restore when one-shot times out + // but: don't restore previous one-shots! + + pdsp->ipsetsav_oneshot = 0; + + if ( PSET_IsOneShot( pdsp->ppset[0] ) && !PSET_IsOneShot( pdsp->ppsetprev[0] ) ) + pdsp->ipsetsav_oneshot = pdsp->ipsetprev; + + // get new xfade time from previous preset (ie: fade out time). if 0 use default. if < 0, use exponential xfade + + if ( fabs(pdsp->ppsetprev[0]->fade) > 0.0 ) + { + pdsp->xfade = fabs(pdsp->ppsetprev[0]->fade); + pdsp->bexpfade = pdsp->ppsetprev[0]->fade < 0 ? 1 : 0; + } + else + { + // no previous preset - use defauts, set in DSP_Alloc + + pdsp->xfade = pdsp->xfade_default; + pdsp->bexpfade = false; + } + + RMP_Init( &(pdsp->xramp), pdsp->xfade, 0, PMAX, false ); +} + + +#define DSP_AUTO_BASE 60 // presets 60-100 in g_psettemplates are reserved as autocreated presets +#define DSP_CAUTO_PRESETS 40 // must be same as DAS_CNODES!!! + +// construct a dsp preset based on provided parameters, +// preset is constructed within g_psettemplates[] array. +// return preset # + +// parameter batch + +struct auto_params_t +{ + // passed in params + + bool bskyabove; // true if sky is mostly above player + int width; // max width of room in inches + int length; // max length of room in inches (length always > width) + int height; // max height of room in inches + float fdiffusion; // diffusion of room 0..1.0 + float freflectivity; // average reflectivity of all surfaces in room 0..1.0 + float surface_refl[6]; // reflectivity for left,right,front,back,ceiling,floor surfaces 0.0 for open surface (sky or no hit) + + // derived params + + int shape; // ADSP_ROOM, etc 0...4 + int size; // ADSP_SIZE_SMALL, etc 0...3 + int len; // ADSP_LENGTH_SHORT, etc 0...3 + int wid; // ADSP_WIDTH_NARROW, etc 0...3 + int ht; // ADSP_HEIGHT_LOW, etc 0...3 + int reflectivity; // ADSP_DULL, etc 0..3 + int diffusion; // ADSP_EMPTY, etc 0...3 +}; + + +// select type 1..5 based on params + // 1:simple reverb + // 2:diffusor + reverb + // 3:diffusor + delay + reverb + // 4:simple delay + // 5:diffusor + delay + +#define AROOM_SMALL (10.0 * 12.0) // small room +#define AROOM_MEDIUM (20.0 * 12.0) // medium room +#define AROOM_LARGE (40.0 * 12.0) // large room +#define AROOM_HUGE (100.0 * 12.0) // huge room +#define AROOM_GIGANTIC (200.0 * 12.0) // gigantic room + +#define AROOM_DUCT_WIDTH (4.0 * 12.0) // max width for duct +#define AROOM_DUCT_HEIGHT (6.0 * 12.0) + +#define AROOM_HALL_WIDTH (8.0 * 12.0) // max width for hall +#define AROOM_HALL_HEIGHT (16.0 * 12.0) // max height for hall + +#define AROOM_TUNNEL_WIDTH (20.0 * 12.0) // max width for tunnel +#define AROOM_TUNNEL_HEIGHT (30.0 * 12.0) // max height for tunnel + +#define AROOM_STREET_WIDTH (12.0 * 12.0) // min width for street + +#define AROOM_SHORT_LENGTH (12.0 * 12.0) // max length for short hall +#define AROOM_MEDIUM_LENGTH (24.0 * 12.0) // min length for medium hall +#define AROOM_LONG_LENGTH (48.0 * 12.0) // min length for long hall +#define AROOM_VLONG_LENGTH (96.0 * 12.0) // min length for very long hall +#define AROOM_XLONG_LENGTH (192.0 * 12.0) // min length for huge hall + +#define AROOM_LOW_HEIGHT (4.0 * 12.0) // short ceiling +#define AROOM_MEDIUM_HEIGHT (128) // medium ceiling +#define AROOM_TALL_HEIGHT (18.0 * 12.0) // tall ceiling +#define AROOM_VTALL_HEIGHT (32.0 * 12.0) // very tall ceiling +#define AROOM_XTALL_HEIGHT (64.0 * 12.0) // huge tall ceiling + +#define AROOM_NARROW_WIDTH (6.0 * 12.0) // narrow width +#define AROOM_MEDIUM_WIDTH (12.0 * 12.0) // medium width +#define AROOM_WIDE_WIDTH (24.0 * 12.0) // wide width +#define AROOM_VWIDE_WIDTH (48.0 * 12.0) // very wide +#define AROOM_XWIDE_WIDTH (96.0 * 12.0) // huge width + +#define BETWEEN(a,b,c) ( ((a) > (b)) && ((a) <= (c)) ) + +#define ADSP_IsShaft(pa) (pa->height > (3.0 * pa->length)) +#define ADSP_IsRoom(pa) (pa->length <= (2.5 * pa->width)) +#define ADSP_IsHall(pa) ((pa->length > (2.5 * pa->width)) && (BETWEEN(pa->width, AROOM_DUCT_WIDTH, AROOM_HALL_WIDTH))) +#define ADSP_IsTunnel(pa) ((pa->length > (4.0 * pa->width)) && (pa->width > AROOM_HALL_WIDTH)) +#define ADSP_IsDuct(pa) ((pa->length > (4.0 * pa->width)) && (pa->width <= AROOM_DUCT_WIDTH)) + +#define ADSP_IsCourtyard(pa) (pa->length <= (2.5 * pa->width)) +#define ADSP_IsAlley(pa) ((pa->length > (2.5 * pa->width)) && (pa->width <= AROOM_STREET_WIDTH)) +#define ADSP_IsStreet(pa) ((pa->length > (2.5 * pa->width)) && (pa->width > AROOM_STREET_WIDTH)) + +#define ADSP_IsSmallRoom(pa) (pa->length <= AROOM_SMALL) +#define ADSP_IsMediumRoom(pa) ((BETWEEN(pa->length, AROOM_SMALL, AROOM_MEDIUM)) ) // && (BETWEEN(pa->width, AROOM_SMALL, AROOM_MEDIUM))) +#define ADSP_IsLargeRoom(pa) (BETWEEN(pa->length, AROOM_MEDIUM, AROOM_LARGE) ) // && BETWEEN(pa->width, AROOM_MEDIUM, AROOM_LARGE)) +#define ADSP_IsHugeRoom(pa) (BETWEEN(pa->length, AROOM_LARGE, AROOM_HUGE) ) // && BETWEEN(pa->width, AROOM_LARGE, AROOM_HUGE)) +#define ADSP_IsGiganticRoom(pa) ((pa->length > AROOM_HUGE) ) // && (pa->width > AROOM_HUGE)) + +#define ADSP_IsShortLength(pa) (pa->length <= AROOM_SHORT_LENGTH) +#define ADSP_IsMediumLength(pa) (BETWEEN(pa->length, AROOM_SHORT_LENGTH, AROOM_MEDIUM_LENGTH)) +#define ADSP_IsLongLength(pa) (BETWEEN(pa->length, AROOM_MEDIUM_LENGTH, AROOM_LONG_LENGTH)) +#define ADSP_IsVLongLength(pa) (BETWEEN(pa->length, AROOM_LONG_LENGTH, AROOM_VLONG_LENGTH)) +#define ADSP_IsXLongLength(pa) (pa->length > AROOM_VLONG_LENGTH) + +#define ADSP_IsLowHeight(pa) (pa->height <= AROOM_LOW_HEIGHT) +#define ADSP_IsMediumHeight(pa) (BETWEEN(pa->height, AROOM_LOW_HEIGHT, AROOM_MEDIUM_HEIGHT)) +#define ADSP_IsTallHeight(pa) (BETWEEN(pa->height, AROOM_MEDIUM_HEIGHT, AROOM_TALL_HEIGHT)) +#define ADSP_IsVTallHeight(pa) (BETWEEN(pa->height, AROOM_TALL_HEIGHT, AROOM_VTALL_HEIGHT)) +#define ADSP_IsXTallHeight(pa) (pa->height > AROOM_VTALL_HEIGHT) + +#define ADSP_IsNarrowWidth(pa) (pa->width <= AROOM_NARROW_WIDTH) +#define ADSP_IsMediumWidth(pa) (BETWEEN(pa->width, AROOM_NARROW_WIDTH, AROOM_MEDIUM_WIDTH)) +#define ADSP_IsWideWidth(pa) (BETWEEN(pa->width, AROOM_MEDIUM_WIDTH, AROOM_WIDE_WIDTH)) +#define ADSP_IsVWideWidth(pa) (BETWEEN(pa->width, AROOM_WIDE_WIDTH, AROOM_VWIDE_WIDTH)) +#define ADSP_IsXWideWidth(pa) (pa->width > AROOM_VWIDE_WIDTH) + +#define ADSP_IsInside(pa) (!(pa->bskyabove)) + +// room diffusion + +#define ADSP_EMPTY 0 +#define ADSP_SPARSE 1 +#define ADSP_CLUTTERED 2 +#define ADSP_FULL 3 +#define ADSP_DIFFUSION_MAX 4 + +#define AROOM_DIF_EMPTY 0.01 // 1% of space by volume is other objects +#define AROOM_DIF_SPARSE 0.1 // 10% " +#define AROOM_DIF_CLUTTERED 0.3 // 30% " +#define AROOM_DIF_FULL 0.5 // 50% " + +#define ADSP_IsEmpty(pa) (pa->fdiffusion <= AROOM_DIF_EMPTY) +#define ADSP_IsSparse(pa) (BETWEEN(pa->fdiffusion, AROOM_DIF_EMPTY, AROOM_DIF_SPARSE)) +#define ADSP_IsCluttered(pa) (BETWEEN(pa->fdiffusion, AROOM_DIF_SPARSE, AROOM_DIF_CLUTTERED)) +#define ADSP_IsFull(pa) (pa->fdiffusion > AROOM_DIF_CLUTTERED) + +#define ADSP_IsDiffuse(pa) (pa->diffusion > ADSP_SPARSE) + +// room acoustic reflectivity + + // tile 0.3 * 3.3 = 0.99 + // metal 0.25 * 3.3 = 0.83 + // concrete,rock,brick,glass,gravel 0.2 * 3.3 = 0.66 + // metal panel/vent, wood, water 0.1 * 3.3 = 0.33 + // carpet,sand,snow,dirt 0.01 * 3.3 = 0.03 + +#define ADSP_DULL 0 +#define ADSP_FLAT 1 +#define ADSP_REFLECTIVE 2 +#define ADSP_BRIGHT 3 +#define ADSP_REFLECTIVITY_MAX 4 + +#define AROOM_REF_DULL 0.04 +#define AROOM_REF_FLAT 0.50 +#define AROOM_REF_REFLECTIVE 0.80 +#define AROOM_REF_BRIGHT 0.99 + +#define ADSP_IsDull(pa) (pa->freflectivity <= AROOM_REF_DULL) +#define ADSP_IsFlat(pa) (BETWEEN(pa->freflectivity, AROOM_REF_DULL, AROOM_REF_FLAT)) +#define ADSP_IsReflective(pa) (BETWEEN(pa->freflectivity, AROOM_REF_FLAT, AROOM_REF_REFLECTIVE)) +#define ADSP_IsBright(pa) (pa->freflectivity > AROOM_REF_REFLECTIVE) + +#define ADSP_IsRefl(pa) (pa->reflectivity > ADSP_FLAT) + +// room shapes + +#define ADSP_ROOM 0 +#define ADSP_DUCT 1 +#define ADSP_HALL 2 +#define ADSP_TUNNEL 3 +#define ADSP_STREET 4 +#define ADSP_ALLEY 5 +#define ADSP_COURTYARD 6 +#define ADSP_OPEN_SPACE 7 // NOTE: 7..10 must remain in order !!! +#define ADSP_OPEN_WALL 8 +#define ADSP_OPEN_STREET 9 +#define ADSP_OPEN_COURTYARD 10 + +// room sizes + +#define ADSP_SIZE_SMALL 0 // NOTE: must remain 0..4!!! +#define ADSP_SIZE_MEDIUM 1 +#define ADSP_SIZE_LARGE 2 +#define ADSP_SIZE_HUGE 3 +#define ADSP_SIZE_GIGANTIC 4 +#define ADSP_SIZE_MAX 5 + +#define ADSP_LENGTH_SHORT 0 +#define ADSP_LENGTH_MEDIUM 1 +#define ADSP_LENGTH_LONG 2 +#define ADSP_LENGTH_VLONG 3 +#define ADSP_LENGTH_XLONG 4 +#define ADSP_LENGTH_MAX 5 + +#define ADSP_WIDTH_NARROW 0 +#define ADSP_WIDTH_MEDIUM 1 +#define ADSP_WIDTH_WIDE 2 +#define ADSP_WIDTH_VWIDE 3 +#define ADSP_WIDTH_XWIDE 4 +#define ADSP_WIDTH_MAX 5 + +#define ADSP_HEIGHT_LOW 0 +#define ADSP_HEIGTH_MEDIUM 1 +#define ADSP_HEIGHT_TALL 2 +#define ADSP_HEIGHT_VTALL 3 +#define ADSP_HEIGHT_XTALL 4 +#define ADSP_HEIGHT_MAX 5 + + +// convert numeric size params to #defined size params + +void ADSP_GetSize( auto_params_t *pa ) +{ + pa->size = ((ADSP_IsSmallRoom(pa) ? 1 : 0) * ADSP_SIZE_SMALL) + + ((ADSP_IsMediumRoom(pa) ? 1 : 0) * ADSP_SIZE_MEDIUM) + + ((ADSP_IsLargeRoom(pa) ? 1 : 0) * ADSP_SIZE_LARGE) + + ((ADSP_IsHugeRoom(pa) ? 1 : 0) * ADSP_SIZE_HUGE) + + ((ADSP_IsGiganticRoom(pa) ? 1 : 0) * ADSP_SIZE_GIGANTIC); + + pa->len = ((ADSP_IsShortLength(pa) ? 1 : 0) * ADSP_LENGTH_SHORT) + + ((ADSP_IsMediumLength(pa) ? 1 : 0) * ADSP_LENGTH_MEDIUM) + + ((ADSP_IsLongLength(pa) ? 1 : 0) * ADSP_LENGTH_LONG) + + ((ADSP_IsVLongLength(pa) ? 1 : 0) * ADSP_LENGTH_VLONG) + + ((ADSP_IsXLongLength(pa) ? 1 : 0) * ADSP_LENGTH_XLONG); + + pa->wid = ((ADSP_IsNarrowWidth(pa) ? 1 : 0) * ADSP_WIDTH_NARROW) + + ((ADSP_IsMediumWidth(pa) ? 1 : 0) * ADSP_WIDTH_MEDIUM) + + ((ADSP_IsWideWidth(pa) ? 1 : 0) * ADSP_WIDTH_WIDE) + + ((ADSP_IsVWideWidth(pa) ? 1 : 0) * ADSP_WIDTH_VWIDE) + + ((ADSP_IsXWideWidth(pa) ? 1 : 0) * ADSP_WIDTH_XWIDE); + + pa->ht = ((ADSP_IsLowHeight(pa) ? 1 : 0) * ADSP_HEIGHT_LOW) + + ((ADSP_IsMediumHeight(pa) ? 1 : 0) * ADSP_HEIGTH_MEDIUM) + + ((ADSP_IsTallHeight(pa) ? 1 : 0) * ADSP_HEIGHT_TALL) + + ((ADSP_IsVTallHeight(pa) ? 1 : 0) * ADSP_HEIGHT_VTALL) + + ((ADSP_IsXTallHeight(pa) ? 1 : 0) * ADSP_HEIGHT_XTALL); + + pa->reflectivity = + ((ADSP_IsDull(pa) ? 1 : 0) * ADSP_DULL) + + ((ADSP_IsFlat(pa) ? 1 : 0) * ADSP_FLAT) + + ((ADSP_IsReflective(pa) ? 1 : 0) * ADSP_REFLECTIVE) + + ((ADSP_IsBright(pa) ? 1 : 0) * ADSP_BRIGHT); + + pa->diffusion = + ((ADSP_IsEmpty(pa) ? 1 : 0) * ADSP_EMPTY) + + ((ADSP_IsSparse(pa) ? 1 : 0) * ADSP_SPARSE) + + ((ADSP_IsCluttered(pa) ? 1 : 0) * ADSP_CLUTTERED) + + ((ADSP_IsFull(pa) ? 1 : 0) * ADSP_FULL); + + Assert(pa->size < ADSP_SIZE_MAX); + Assert(pa->len < ADSP_LENGTH_MAX); + Assert(pa->wid < ADSP_WIDTH_MAX); + Assert(pa->ht < ADSP_HEIGHT_MAX); + Assert(pa->reflectivity < ADSP_REFLECTIVITY_MAX); + Assert(pa->diffusion < ADSP_DIFFUSION_MAX); + + if ( pa->shape != ADSP_COURTYARD && pa->shape != ADSP_OPEN_COURTYARD ) + { + // fix up size for streets, alleys, halls, ducts, tunnelsy + + if (pa->shape == ADSP_STREET || pa->shape == ADSP_ALLEY ) + pa->size = pa->wid; + else + pa->size = (pa->len + pa->wid) / 2; + + } + +} + +void ADSP_GetOutsideSize( auto_params_t *pa ) +{ + ADSP_GetSize( pa ); +} + +// return # of sides that had max length or sky hits (out of 6 sides). + +int ADSP_COpenSides( auto_params_t *pa ) +{ + int count = 0; + + // only look at left,right,front,back walls - ignore floor, ceiling + + for (int i = 0; i < 4; i++) + { + if (pa->surface_refl[i] == 0.0) + count++; + } + + return count; +} + +// given auto params, return shape and size of room + +void ADSP_GetAutoShape( auto_params_t *pa ) +{ + + // INSIDE: + // shapes: duct, hall, tunnel, shaft (vertical duct, hall or tunnel) + // sizes: short->long, narrow->wide, low->tall + // shapes: room + // sizes: small->large, low->tall + + // OUTSIDE: + // shapes: street, alley + // sizes: short->long, narrow->wide + // shapes: courtyard + // sizes: small->large + + // shapes: open_space, wall, open_street, open_corner, open_courtyard + // sizes: open, narrow->wide + + bool bshaft = false; + int t; + + if (ADSP_IsInside(pa)) + { + if (ADSP_IsShaft(pa)) + { + // temp swap height and length + + bshaft = true; + t = pa->height; + pa->height = pa->length; + pa->length = t; + if (das_debug.GetInt() > 1) + DevMsg("VERTICAL SHAFT Detected \n"); + } + + // get shape + + if (ADSP_IsDuct(pa)) + { + pa->shape = ADSP_DUCT; + ADSP_GetSize( pa ); + if (das_debug.GetInt() > 1) + DevMsg("DUCT Detected \n"); + goto autoshape_exit; + } + + if (ADSP_IsHall(pa)) + { + // get size + pa->shape = ADSP_HALL; + ADSP_GetSize( pa ); + + if (das_debug.GetInt() > 1) + DevMsg("HALL Detected \n"); + + goto autoshape_exit; + } + + if (ADSP_IsTunnel(pa)) + { + // get size + pa->shape = ADSP_TUNNEL; + ADSP_GetSize( pa ); + + if (das_debug.GetInt() > 1) + DevMsg("TUNNEL Detected \n"); + + goto autoshape_exit; + } + + // default + // (ADSP_IsRoom(pa)) + { + // get size + pa->shape = ADSP_ROOM; + ADSP_GetSize( pa ); + + if (das_debug.GetInt() > 1) + DevMsg("ROOM Detected \n"); + + goto autoshape_exit; + } + } + + // outside: + + if (ADSP_COpenSides(pa) > 0) // side hit sky, or side has max length + { + // get shape - courtyard, street, wall or open space + // 10..7 + pa->shape = ADSP_OPEN_COURTYARD - (ADSP_COpenSides(pa) - 1); + ADSP_GetOutsideSize( pa ); + + if (das_debug.GetInt() > 1) + DevMsg("OPEN SIDED OUTDOOR AREA Detected \n"); + + goto autoshape_exit; + } + + // all sides closed: + + // get shape - closed street or alley or courtyard + + if (ADSP_IsCourtyard(pa)) + { + pa->shape = ADSP_COURTYARD; + ADSP_GetOutsideSize( pa ); + + if (das_debug.GetInt() > 1) + DevMsg("OUTSIDE COURTYARD Detected \n"); + + goto autoshape_exit; + } + + if (ADSP_IsAlley(pa)) + { + pa->shape = ADSP_ALLEY; + ADSP_GetOutsideSize( pa ); + + if (das_debug.GetInt() > 1) + DevMsg("OUTSIDE ALLEY Detected \n"); + goto autoshape_exit; + } + + // default to 'street' if sides are closed + + // if (ADSP_IsStreet(pa)) + { + pa->shape = ADSP_STREET; + ADSP_GetOutsideSize( pa ); + if (das_debug.GetInt() > 1) + DevMsg("OUTSIDE STREET Detected \n"); + goto autoshape_exit; + } + +autoshape_exit: + + // swap height & length if needed + + if (bshaft) + { + t = pa->height; + pa->height = pa->length; + pa->length = t; + } +} + +int MapReflectivityToDLYCutoff[] = +{ + 1000, // DULL + 2000, // FLAT + 4000, // REFLECTIVE + 6000 // BRIGHT +}; + +float MapSizeToDLYFeedback[] = +{ + 0.9, // 0.6, // SMALL + 0.8, // 0.5, // MEDIUM + 0.7, // 0.4, // LARGE + 0.6, // 0.3, // HUGE + 0.5, // 0.2, // GIGANTIC +}; + +void ADSP_SetupAutoDelay( prc_t *pprc_dly, auto_params_t *pa ) +{ + // shapes: + // inside: duct, long hall, long tunnel, large room + // outside: open courtyard, street wall, space + // outside: closed courtyard, alley, street + + // size 0..4 + // len 0..3 + // wid 0..3 + // reflectivity: 0..3 + // diffusion 0..3 + + // dtype: delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS + // delay: delay in milliseconds (room max size in feet) + // feedback: feedback 0-1.0 + // gain: final gain of output stage, 0-1.0 + + int size = pa->length * 2.0; + + if (pa->shape == ADSP_ALLEY || pa->shape == ADSP_STREET || pa->shape == ADSP_OPEN_STREET) + size = pa->width * 2.0; + + pprc_dly->type = PRC_DLY; + + pprc_dly->prm[dly_idtype] = DLY_LOWPASS; // delay with feedback + + pprc_dly->prm[dly_idelay] = clamp((size / 12.0), 5.0, 500.0); + + pprc_dly->prm[dly_ifeedback] = MapSizeToDLYFeedback[pa->len]; + + // reduce gain based on distance reflection travels +// float g = 1.0 - ( clamp(pprc_dly->prm[dly_idelay], 10.0, 1000.0) / (1000.0 - 10.0) ); +// pprc_dly->prm[dly_igain] = g; + + pprc_dly->prm[dly_iftype] = FLT_LP; + if (ADSP_IsInside(pa)) + pprc_dly->prm[dly_icutoff] = MapReflectivityToDLYCutoff[pa->reflectivity]; + else + pprc_dly->prm[dly_icutoff] = (int)((float)(MapReflectivityToDLYCutoff[pa->reflectivity]) * 0.75); + + pprc_dly->prm[dly_iqwidth] = 0; + + pprc_dly->prm[dly_iquality] = QUA_LO; + + float l = clamp((pa->length * 2.0 / 12.0), 14.0, 500.0); + float w = clamp((pa->width * 2.0 / 12.0), 14.0, 500.0); + + // convert to multitap delay + + pprc_dly->prm[dly_idtype] = DLY_LOWPASS_4TAP; + + pprc_dly->prm[dly_idelay] = l; + pprc_dly->prm[dly_itap1] = w; + pprc_dly->prm[dly_itap2] = l; // max(7, l * 0.7 ); + pprc_dly->prm[dly_itap3] = l; // max(7, w * 0.7 ); + + pprc_dly->prm[dly_igain] = 1.0; +} + +int MapReflectivityToRVACutoff[] = +{ + 1000, // DULL + 2000, // FLAT + 4000, // REFLECTIVE + 6000 // BRIGHT +}; + +float MapSizeToRVANumDelays[] = +{ + 3, // SMALL 3 reverbs + 6, // MEDIUM 6 reverbs + 6, // LARGE 6 reverbs + 9, // HUGE 9 reverbs + 12, // GIGANTIC 12 reverbs +}; + +float MapSizeToRVAFeedback[] = +{ + 0.75, // SMALL + 0.8, // MEDIUM + 0.9, // LARGE + 0.95, // HUGE + 0.98, // GIGANTIC +}; + +void ADSP_SetupAutoReverb( prc_t *pprc_rva, auto_params_t *pa ) +{ + // shape: hall, tunnel or room + // size 0..4 + // reflectivity: 0..3 + // diffusion 0..3 + + // size: 0-2.0 scales nominal delay parameters (18 to 47 ms * scale = delay) + // numdelays: 0-12 controls # of parallel or series delays + // decay: 0-2.0 scales feedback parameters (.7 to .9 * scale/2.0 = feedback) + // fparallel: if true, filters are built into delays, otherwise filter output only + // fmoddly: if true, all delays are modulating delays + float gain = 1.0; + + pprc_rva->type = PRC_RVA; + + pprc_rva->prm[rva_size_max] = 50.0; + pprc_rva->prm[rva_size_min] = 30.0; + + if (ADSP_IsRoom(pa)) + pprc_rva->prm[rva_inumdelays] = MapSizeToRVANumDelays[pa->size]; + else + pprc_rva->prm[rva_inumdelays] = MapSizeToRVANumDelays[pa->len]; + + pprc_rva->prm[rva_ifeedback] = 0.9; + + pprc_rva->prm[rva_icutoff] = MapReflectivityToRVACutoff[pa->reflectivity]; + + pprc_rva->prm[rva_ifparallel] = 1; + pprc_rva->prm[rva_imoddly] = ADSP_IsEmpty(pa) ? 0 : 4; + pprc_rva->prm[rva_imodrate] = 3.48; + + pprc_rva->prm[rva_iftaps] = 0; // 0.1 // use extra delay taps to increase density + + pprc_rva->prm[rva_width] = clamp( ((float)(pa->width) / 12.0), 6.0, 500.0); // in feet + pprc_rva->prm[rva_depth] = clamp( ((float)(pa->length) / 12.0), 6.0, 500.0); + pprc_rva->prm[rva_height] = clamp( ((float)(pa->height) / 12.0), 6.0, 500.0); + + // room + pprc_rva->prm[rva_fbwidth] = 0.9; // MapSizeToRVAFeedback[pa->size]; // larger size = more feedback + pprc_rva->prm[rva_fbdepth] = 0.9; // MapSizeToRVAFeedback[pa->size]; + pprc_rva->prm[rva_fbheight] = 0.5; // MapSizeToRVAFeedback[pa->size]; + + // feedback is based on size of room: + + if (ADSP_IsInside(pa)) + { + if (pa->shape == ADSP_HALL) + { + pprc_rva->prm[rva_fbwidth] = 0.7; //MapSizeToRVAFeedback[pa->wid]; + pprc_rva->prm[rva_fbdepth] = -0.5; //MapSizeToRVAFeedback[pa->len]; + pprc_rva->prm[rva_fbheight] = 0.3; //MapSizeToRVAFeedback[pa->ht]; + } + + if (pa->shape == ADSP_TUNNEL) + { + pprc_rva->prm[rva_fbwidth] = 0.9; + pprc_rva->prm[rva_fbdepth] = -0.8; // fixed pre-delay, no feedback + pprc_rva->prm[rva_fbheight] = 0.3; + } + } + else + { + if (pa->shape == ADSP_ALLEY) + { + pprc_rva->prm[rva_fbwidth] = 0.9; + pprc_rva->prm[rva_fbdepth] = -0.8; // fixed pre-delay, no feedback + pprc_rva->prm[rva_fbheight] = 0.0; + } + } + + if (!ADSP_IsInside(pa)) + pprc_rva->prm[rva_fbheight] = 0.0; + + pprc_rva->prm[rva_igain] = gain; +} + +// diffusor templates for auto create + + // size: 0-1.0 scales all delays (13ms to 41ms * scale = delay) + // numdelays: 0-4.0 controls # of series delays + // decay: 0-1.0 scales all feedback parameters + +// prctype size #dly feedback + +#if 0 +#define PRC_DFRA_S {PRC_DFR, {0.5, 2, 0.10}, NULL,NULL,NULL,NULL,NULL} // S room +#define PRC_DFRA_M {PRC_DFR, {0.75, 2, 0.12}, NULL,NULL,NULL,NULL,NULL} // M room +#define PRC_DFRA_L {PRC_DFR, {1.0, 3, 0.13}, NULL,NULL,NULL,NULL,NULL} // L room +#define PRC_DFRA_VL {PRC_DFR, {1.0, 3, 0.15}, NULL,NULL,NULL,NULL,NULL} // VL room + +prc_t g_prc_dfr_auto[] = {PRC_DFRA_S, PRC_DFRA_M, PRC_DFRA_L, PRC_DFRA_VL, PRC_DFRA_VL}; + +//$BUGBUGBUG: I think this should be sizeof(prc_t), not sizeof(pset_t)... +#define CDFRTEMPLATES (sizeof(g_prc_dfr_auto)/sizeof(pset_t)) // number of diffusor templates + +// copy diffusor template from preset list, based on room size + +void ADSP_SetupAutoDiffusor( prc_t *pprc_dfr, auto_params_t *pa ) +{ + int i = clamp(pa->size, 0, (int)CDFRTEMPLATES - 1); + + // copy diffusor preset based on size + + *pprc_dfr = g_prc_dfr_auto[i]; +} +#endif + +// return index to processor given processor type and preset +// skips N processors of similar type +// returns -1 if type not found + +int ADSP_FindProc( pset_t *ppset, int proc_type, int skip ) +{ + int skipcount = skip; + + for (int i = 0; i < ppset->cprcs; i++) + { + // look for match on processor type + + if ( ppset->prcs[i].type == proc_type ) + { + // skip first N procs of similar type, + + // return index to processor + + if (!skipcount) + return i; + + skipcount--; + } + + } + + return -1; +} + +// interpolate parameter: +// pnew - target preset +// pmin - preset with parameter with min value +// pmax - preset with parameter with max value +// proc_type - type of processor to look for ie: PRC_RVA or PRC_DLY +// skipprocs - skip n processors of type +// iparam - which parameter within processor to interpolate +// index - +// index_max: use index/index_max as interpolater between pmin param and pmax param +// if bexp is true, interpolate exponentially as (index/index_max)^2 + +// NOTE: returns with no result if processor type is not found in all presets. + +void ADSP_InterpParam( pset_t *pnew, pset_t *pmin, pset_t *pmax, int proc_type, int skipprocs, int iparam, int index, int index_max, bool bexp ) +{ + // find processor index in pnew + int iproc_new = ADSP_FindProc( pnew, proc_type, skipprocs); + int iproc_min = ADSP_FindProc( pmin, proc_type, skipprocs); + int iproc_max = ADSP_FindProc( pmax, proc_type, skipprocs); + + // make sure processor type found in all presets + + if ( iproc_new < 0 || iproc_min < 0 || iproc_max < 0 ) + return; + + float findex = (float)index/(float)index_max; + float vmin = pmin->prcs[iproc_min].prm[iparam]; + float vmax = pmax->prcs[iproc_max].prm[iparam]; + float vinterp; + + // interpolate + + if (!bexp) + vinterp = vmin + (vmax - vmin) * findex; + else + vinterp = vmin + (vmax - vmin) * findex * findex; + + pnew->prcs[iproc_new].prm[iparam] = vinterp; + + return; +} + +// directly set parameter + +void ADSP_SetParam( pset_t *pnew, int proc_type, int skipprocs, int iparam, float value ) +{ + int iproc_new = ADSP_FindProc( pnew, proc_type, skipprocs); + + if (iproc_new >= 0) + pnew->prcs[iproc_new].prm[iparam] = value; +} + +// directly set parameter if min or max is negative + +void ADSP_SetParamIfNegative( pset_t *pnew, pset_t *pmin, pset_t *pmax, int proc_type, int skipprocs, int iparam, int index, int index_max, bool bexp, float value ) +{ + // find processor index in pnew + int iproc_new = ADSP_FindProc( pnew, proc_type, skipprocs); + int iproc_min = ADSP_FindProc( pmin, proc_type, skipprocs); + int iproc_max = ADSP_FindProc( pmax, proc_type, skipprocs); + + // make sure processor type found in all presets + + if ( iproc_new < 0 || iproc_min < 0 || iproc_max < 0 ) + return; + + float vmin = pmin->prcs[iproc_min].prm[iparam]; + float vmax = pmax->prcs[iproc_max].prm[iparam]; + + if ( vmin < 0.0 || vmax < 0.0 ) + ADSP_SetParam( pnew, proc_type, skipprocs, iparam, value ); + else + ADSP_InterpParam( pnew, pmin, pmax, proc_type, skipprocs, iparam, index, index_max, bexp); + + return; +} + +// given min and max preset and auto parameters, create new preset +// NOTE: the # and type of processors making up pmin and pmax presets must be identical! + +void ADSP_InterpolatePreset( pset_t *pnew, pset_t *pmin, pset_t *pmax, auto_params_t *pa, int iskip ) +{ + int i; + + // if size > mid size, then copy basic processors from MAX preset, + // otherwise, copy from MIN preset + + if ( !iskip ) + { + // only copy on 1st call + + if ( pa->size > ADSP_SIZE_MEDIUM ) + { + *pnew = *pmax; + } + else + { + *pnew = *pmin; + } + } + + // DFR + + // interpolate all DFR params on size + + for (i = 0; i < dfr_cparam; i++) + ADSP_InterpParam( pnew, pmin, pmax, PRC_DFR, iskip, i, pa->size, ADSP_SIZE_MAX , 0); + + // RVA + + // interpolate size_max, size_min, feedback, #delays, moddly, imodrate, based on ap size + + ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_ifeedback, pa->size, ADSP_SIZE_MAX, 0); + ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_size_min, pa->size, ADSP_SIZE_MAX, 1); + ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_size_max, pa->size, ADSP_SIZE_MAX, 1); + ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_igain, pa->size, ADSP_SIZE_MAX, 0); + ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_inumdelays, pa->size, ADSP_SIZE_MAX , 0); + ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_imoddly, pa->size, ADSP_SIZE_MAX , 0); + ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_imodrate, pa->size, ADSP_SIZE_MAX , 0); + + // interpolate width,depth,height based on ap width length & height - exponential interpolation + // if pmin or pmax parameters are < 0, directly set value from w/l/h + + float w = clamp( ((float)(pa->width) / 12.0), 6.0, 500.0); // in feet + float l = clamp( ((float)(pa->length) / 12.0), 6.0, 500.0); + float h = clamp( ((float)(pa->height) / 12.0), 6.0, 500.0); + + ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_RVA, iskip, rva_width, pa->wid, ADSP_WIDTH_MAX, 1, w); + ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_RVA, iskip, rva_depth, pa->len, ADSP_LENGTH_MAX, 1, l); + ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_RVA, iskip, rva_height, pa->ht, ADSP_HEIGHT_MAX, 1, h); + + // interpolate w/d/h feedback based on ap w/d/f + + ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_fbwidth, pa->wid, ADSP_WIDTH_MAX , 0); + ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_fbdepth, pa->len, ADSP_LENGTH_MAX , 0); + ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_fbheight, pa->ht, ADSP_HEIGHT_MAX , 0); + + // interpolate cutoff based on ap reflectivity + // NOTE: cutoff goes from max to min! ie: small bright - large dull + + ADSP_InterpParam( pnew, pmax, pmin, PRC_RVA, iskip, rva_icutoff, pa->reflectivity, ADSP_REFLECTIVITY_MAX , 0); + + // don't interpolate: fparallel, ftaps + + // DLY + + // directly set delay value from pa->length if pmin or pmax value is < 0 + + l = clamp((pa->length * 2.0 / 12.0), 14.0, 500.0); + w = clamp((pa->width * 2.0 / 12.0), 14.0, 500.0); + + ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_idelay, pa->len, ADSP_LENGTH_MAX, 1, l); + + // interpolate feedback, gain, based on max size (length) + + ADSP_InterpParam( pnew, pmin, pmax, PRC_DLY, iskip, dly_ifeedback, pa->len, ADSP_LENGTH_MAX , 0); + ADSP_InterpParam( pnew, pmin, pmax, PRC_DLY, iskip, dly_igain, pa->len, ADSP_LENGTH_MAX , 0); + + // directly set tap value from pa->width if pmin or pmax value is < 0 + + ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_itap1, pa->len, ADSP_LENGTH_MAX, 1, w); + ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_itap2, pa->len, ADSP_LENGTH_MAX, 1, l); + ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_itap3, pa->len, ADSP_LENGTH_MAX, 1, l); + + // interpolate cutoff and qwidth based on reflectivity NOTE: this can affect gain! + // NOTE: cutoff goes from max to min! ie: small bright - large dull + + ADSP_InterpParam( pnew, pmax, pmin, PRC_DLY, iskip, dly_icutoff, pa->len, ADSP_LENGTH_MAX , 0); + ADSP_InterpParam( pnew, pmax, pmin, PRC_DLY, iskip, dly_iqwidth, pa->len, ADSP_LENGTH_MAX , 0); + + // interpolate all other parameters for all other processor types based on size + + // PRC_MDY, PRC_AMP, PRC_FLT, PTC, CRS, ENV, EFO, LFO + + for (i = 0; i < mdy_cparam; i++) + ADSP_InterpParam( pnew, pmin, pmax, PRC_MDY, iskip, i, pa->len, ADSP_LENGTH_MAX , 0); + + for (i = 0; i < amp_cparam; i++) + ADSP_InterpParam( pnew, pmin, pmax, PRC_AMP, iskip, i, pa->size, ADSP_SIZE_MAX , 0); + + for (i = 0; i < flt_cparam; i++) + ADSP_InterpParam( pnew, pmin, pmax, PRC_FLT, iskip, i, pa->size, ADSP_SIZE_MAX , 0); + + for (i = 0; i < ptc_cparam; i++) + ADSP_InterpParam( pnew, pmin, pmax, PRC_PTC, iskip, i, pa->size, ADSP_SIZE_MAX , 0); + + for (i = 0; i < crs_cparam; i++) + ADSP_InterpParam( pnew, pmin, pmax, PRC_CRS, iskip, i, pa->size, ADSP_SIZE_MAX , 0); + + for (i = 0; i < env_cparam; i++) + ADSP_InterpParam( pnew, pmin, pmax, PRC_ENV, iskip, i, pa->size, ADSP_SIZE_MAX , 0); + + for (i = 0; i < efo_cparam; i++) + ADSP_InterpParam( pnew, pmin, pmax, PRC_EFO, iskip, i, pa->size, ADSP_SIZE_MAX , 0); + + for (i = 0; i < lfo_cparam; i++) + ADSP_InterpParam( pnew, pmin, pmax, PRC_LFO, iskip, i, pa->size, ADSP_SIZE_MAX , 0); + +} + +// these convars store the index to the first preset for each shape type in dsp_presets.txt + +ConVar adsp_room_min ("adsp_room_min", "102"); +ConVar adsp_duct_min ("adsp_duct_min", "106"); +ConVar adsp_hall_min ("adsp_hall_min", "110"); +ConVar adsp_tunnel_min ("adsp_tunnel_min", "114"); +ConVar adsp_street_min ("adsp_street_min", "118"); +ConVar adsp_alley_min ("adsp_alley_min", "122"); +ConVar adsp_courtyard_min ("adsp_courtyard_min", "126"); +ConVar adsp_openspace_min ("adsp_openspace_min", "130"); +ConVar adsp_openwall_min ("adsp_openwall_min", "130"); +ConVar adsp_openstreet_min ("adsp_openstreet_min", "118"); +ConVar adsp_opencourtyard_min ("adsp_opencourtyard_min", "126"); + +// given room parameters, construct and return a dsp preset representing the room. +// bskyabove, width, length, height, fdiffusion, freflectivity are all passed-in room parameters +// psurf_refl is a passed-in array of reflectivity values for 6 surfaces +// inode is the location within g_psettemplates[] that the dsp preset will be constructed (inode = dsp preset#) +// cnode should always = DSP_CAUTO_PRESETS +// returns idsp preset. + +int DSP_ConstructPreset( bool bskyabove, int width, int length, int height, float fdiffusion, float freflectivity, float *psurf_refl, int inode, int cnodes ) +{ + auto_params_t ap; + auto_params_t *pa; + + pset_t new_pset; // preset + pset_t pset_min; + pset_t pset_max; + + int ipreset; + int ipset_min; + int ipset_max; + + if (inode >= DSP_CAUTO_PRESETS) + { + Assert(false); // check DAS_CNODES == DSP_CAUTO_PRESETS!!! + return 0; + } + + // fill parameter struct + + ap.bskyabove = bskyabove; + ap.width = width; + ap.length = length; + ap.height = height; + ap.fdiffusion = fdiffusion; + ap.freflectivity = freflectivity; + + for (int i = 0; i < 6; i++) + ap.surface_refl[i] = psurf_refl[i]; + + if (ap.bskyabove) + ap.surface_refl[4] = 0.0; + + // select shape, size based on params + + ADSP_GetAutoShape( &ap ); + + // set up min/max presets based on shape + + switch ( ap.shape ) + { + default: + case ADSP_ROOM: ipset_min = adsp_room_min.GetInt(); break; + case ADSP_DUCT: ipset_min = adsp_duct_min.GetInt(); break; + case ADSP_HALL: ipset_min = adsp_hall_min.GetInt(); break; + case ADSP_TUNNEL: ipset_min = adsp_tunnel_min.GetInt(); break; + case ADSP_STREET: ipset_min = adsp_street_min.GetInt(); break; + case ADSP_ALLEY: ipset_min = adsp_alley_min.GetInt(); break; + case ADSP_COURTYARD: ipset_min = adsp_courtyard_min.GetInt(); break; + case ADSP_OPEN_SPACE: ipset_min = adsp_openspace_min.GetInt(); break; + case ADSP_OPEN_WALL: ipset_min = adsp_openwall_min.GetInt(); break; + case ADSP_OPEN_STREET: ipset_min = adsp_openstreet_min.GetInt(); break; + case ADSP_OPEN_COURTYARD: ipset_min = adsp_opencourtyard_min.GetInt(); break; + } + + // presets in dsp_presets.txt are ordered as: + + // <shape><empty><min> + // <shape><empty><max> + // <shape><diffuse><min> + // <shape><diffuse><max> + pa = ≈ + if ( ADSP_IsDiffuse(pa) ) + ipset_min += 2; + + ipset_max = ipset_min + 1; + + pset_min = g_psettemplates[ipset_min]; + pset_max = g_psettemplates[ipset_max]; + + // given min and max preset and auto parameters, create new preset + + // interpolate between 1st instances of each processor type (ie: PRC_DLY) appearing in preset + + ADSP_InterpolatePreset( &new_pset, &pset_min, &pset_max, &ap, 0 ); + + // interpolate between 2nd instances of each processor type (ie: PRC_DLY) appearing in preset + + ADSP_InterpolatePreset( &new_pset, &pset_min, &pset_max, &ap, 1 ); + + // copy constructed preset back into node's template location + + ipreset = DSP_AUTO_BASE + inode; + + g_psettemplates[ipreset] = new_pset; + + return ipreset; +} + +/////////////////////////////////////// +// Helpers: called only from DSP_Process +/////////////////////////////////////// + + +// return true if batch processing version of preset exists + +inline bool FBatchPreset( pset_t *ppset ) +{ + + switch (ppset->type) + { + case PSET_LINEAR: + return true; + case PSET_SIMPLE: + return true; + default: + return false; + } +} + +// Helper: called only from DSP_Process +// mix front stereo buffer to mono buffer, apply dsp fx + +inline void DSP_ProcessStereoToMono(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading ) +{ + portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process + int count = sampleCount; + int av; + int x; + + if ( !bcrossfading ) + { + if ( !pdsp->ipset ) + return; + + if ( FBatchPreset(pdsp->ppset[0])) + { + // convert Stereo to Mono in place, then batch process fx: perf KDB + + // front->left + front->right / 2 into front->left, front->right duplicated. + + while ( count-- ) + { + pbf->left = (pbf->left + pbf->right) >> 1; + pbf++; + } + + // process left (mono), duplicate output into right + + PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT_DUPLICATE); + } + else + { + // avg left and right -> mono fx -> duplcate out left and right + while ( count-- ) + { + av = ( ( pbf->left + pbf->right ) >> 1 ); + x = PSET_GetNext( pdsp->ppset[0], av ); + x = CLIP_DSP( x ); + pbf->left = pbf->right = x; + pbf++; + } + } + return; + } + + // crossfading to current preset from previous preset + + if ( bcrossfading ) + { + int r; + int fl; + int fr; + int flp; + int frp; + int xf_fl; + int xf_fr; + bool bexp = pdsp->bexpfade; + bool bfadetostereo = (pdsp->ipset == 0); + bool bfadefromstereo = (pdsp->ipsetprev == 0); + + Assert ( !(bfadetostereo && bfadefromstereo) ); // don't call if ipset & ipsetprev both 0! + + if ( bfadetostereo || bfadefromstereo ) + { + // special case if fading to or from preset 0, stereo passthrough + + while ( count-- ) + { + av = ( ( pbf->left + pbf->right ) >> 1 ); + + // get current preset values + + if ( pdsp->ipset ) + { + fl = fr = PSET_GetNext( pdsp->ppset[0], av ); + } + else + { + fl = pbf->left; + fr = pbf->right; + } + + // get previous preset values + + if ( pdsp->ipsetprev ) + { + frp = flp = PSET_GetNext( pdsp->ppsetprev[0], av ); + } + else + { + flp = pbf->left; + frp = pbf->right; + } + + fl = CLIP_DSP(fl); + fr = CLIP_DSP(fr); + flp = CLIP_DSP(flp); + frp = CLIP_DSP(frp); + + // get current ramp value + + r = RMP_GetNext( &pdsp->xramp ); + + // crossfade from previous to current preset + + if (!bexp) + { + xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left + } + else + { + xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left + } + + pbf->left = xf_fl; // crossfaded front left, duplicate in right channel + pbf->right = xf_fr; + + pbf++; + } + + return; + } + + // crossfade mono to mono preset + + while ( count-- ) + { + av = ( ( pbf->left + pbf->right ) >> 1 ); + + // get current preset values + + fl = PSET_GetNext( pdsp->ppset[0], av ); + + // get previous preset values + + flp = PSET_GetNext( pdsp->ppsetprev[0], av ); + + fl = CLIP_DSP(fl); + flp = CLIP_DSP(flp); + + // get current ramp value + + r = RMP_GetNext( &pdsp->xramp ); + + // crossfade from previous to current preset + + if (!bexp) + xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left + else + xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left + + pbf->left = xf_fl; // crossfaded front left, duplicate in right channel + pbf->right = xf_fl; + + pbf++; + } + } +} + +// Helper: called only from DSP_Process +// DSP_Process stereo in to stereo out (if more than 2 procs, ignore them) + +inline void DSP_ProcessStereoToStereo(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading ) +{ + portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process + int count = sampleCount; + int fl, fr; + + if ( !bcrossfading ) + { + + if ( !pdsp->ipset ) + return; + + if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) ) + { + + // process left & right + + PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT ); + PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT ); + } + else + { + // left -> left fx, right -> right fx + while ( count-- ) + { + fl = PSET_GetNext( pdsp->ppset[0], pbf->left ); + fr = PSET_GetNext( pdsp->ppset[1], pbf->right ); + + fl = CLIP_DSP( fl ); + fr = CLIP_DSP( fr ); + + pbf->left = fl; + pbf->right = fr; + pbf++; + } + } + return; + } + + // crossfading to current preset from previous preset + + if ( bcrossfading ) + { + int r; + int flp, frp; + int xf_fl, xf_fr; + bool bexp = pdsp->bexpfade; + + while ( count-- ) + { + // get current preset values + + fl = PSET_GetNext( pdsp->ppset[0], pbf->left ); + fr = PSET_GetNext( pdsp->ppset[1], pbf->right ); + + // get previous preset values + + flp = PSET_GetNext( pdsp->ppsetprev[0], pbf->left ); + frp = PSET_GetNext( pdsp->ppsetprev[1], pbf->right ); + + // get current ramp value + + r = RMP_GetNext( &pdsp->xramp ); + + fl = CLIP_DSP( fl ); + fr = CLIP_DSP( fr ); + flp = CLIP_DSP( flp ); + frp = CLIP_DSP( frp ); + + // crossfade from previous to current preset + if (!bexp) + { + xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE(fr, frp, r); + } + else + { + xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE_EXP(fr, frp, r); + } + + pbf->left = xf_fl; // crossfaded front left + pbf->right = xf_fr; + + pbf++; + } + } +} + +// Helper: called only from DSP_Process +// DSP_Process quad in to mono out (front left = front right) + +inline void DSP_ProcessQuadToMono(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading ) +{ + portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process + portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process + int count = sampleCount; + int x; + int av; + + if ( !bcrossfading ) + { + if ( !pdsp->ipset ) + return; + + if ( FBatchPreset(pdsp->ppset[0]) ) + { + + // convert Quad to Mono in place, then batch process fx: perf KDB + + // left front + rear -> left, right front + rear -> right + while ( count-- ) + { + pbf->left = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2); + pbf++; + pbr++; + } + + // process left (mono), duplicate into right + + PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT_DUPLICATE); + + // copy processed front to rear + + count = sampleCount; + + pbf = pbfront; + pbr = pbrear; + + while ( count-- ) + { + pbr->left = pbf->left; + pbr->right = pbf->right; + pbf++; + pbr++; + } + + } + else + { + // avg fl,fr,rl,rr into mono fx, duplicate on all channels + while ( count-- ) + { + av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2); + x = PSET_GetNext( pdsp->ppset[0], av ); + x = CLIP_DSP( x ); + pbr->left = pbr->right = pbf->left = pbf->right = x; + pbf++; + pbr++; + } + } + return; + } + + if ( bcrossfading ) + { + int r; + int fl, fr, rl, rr; + int flp, frp, rlp, rrp; + int xf_fl, xf_fr, xf_rl, xf_rr; + bool bexp = pdsp->bexpfade; + bool bfadetoquad = (pdsp->ipset == 0); + bool bfadefromquad = (pdsp->ipsetprev == 0); + + if ( bfadetoquad || bfadefromquad ) + { + // special case if previous or current preset is 0 (quad passthrough) + + while ( count-- ) + { + av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2); + + // get current preset values + + // current preset is 0, which implies fading to passthrough quad output + // need to fade from mono to quad + + if ( pdsp->ipset ) + { + rl = rr = fl = fr = PSET_GetNext( pdsp->ppset[0], av ); + } + else + { + fl = pbf->left; + fr = pbf->right; + rl = pbr->left; + rr = pbr->right; + } + + // get previous preset values + + if ( pdsp->ipsetprev ) + { + rrp = rlp = frp = flp = PSET_GetNext( pdsp->ppsetprev[0], av ); + } + else + { + flp = pbf->left; + frp = pbf->right; + rlp = pbr->left; + rrp = pbr->right; + } + + fl = CLIP_DSP(fl); + fr = CLIP_DSP(fr); + flp = CLIP_DSP(flp); + frp = CLIP_DSP(frp); + rl = CLIP_DSP(rl); + rr = CLIP_DSP(rr); + rlp = CLIP_DSP(rlp); + rrp = CLIP_DSP(rrp); + + // get current ramp value + + r = RMP_GetNext( &pdsp->xramp ); + + // crossfade from previous to current preset + + if (!bexp) + { + xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left + xf_rl = XFADE(rl, rlp, r); // crossfade front left previous to front left + xf_rr = XFADE(rr, rrp, r); // crossfade front left previous to front left + } + else + { + xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left + xf_rl = XFADE_EXP(rl, rlp, r); // crossfade front left previous to front left + xf_rr = XFADE_EXP(rr, rrp, r); // crossfade front left previous to front left + } + + pbf->left = xf_fl; + pbf->right = xf_fr; + pbr->left = xf_rl; + pbr->right = xf_rr; + + pbf++; + pbr++; + } + + return; + } + + while ( count-- ) + { + + av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2); + + // get current preset values + + fl = PSET_GetNext( pdsp->ppset[0], av ); + + // get previous preset values + + flp = PSET_GetNext( pdsp->ppsetprev[0], av ); + + // get current ramp value + + r = RMP_GetNext( &pdsp->xramp ); + + fl = CLIP_DSP( fl ); + flp = CLIP_DSP( flp ); + + // crossfade from previous to current preset + if (!bexp) + xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left + else + xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left + + pbf->left = xf_fl; // crossfaded front left, duplicated to all channels + pbf->right = xf_fl; + pbr->left = xf_fl; + pbr->right = xf_fl; + + pbf++; + pbr++; + } + } +} + +// Helper: called only from DSP_Process +// DSP_Process quad in to stereo out (preserve stereo spatialization, throw away front/rear) + +inline void DSP_ProcessQuadToStereo(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading ) +{ + portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process + portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process + int count = sampleCount; + int fl, fr; + + if ( !bcrossfading ) + { + if ( !pdsp->ipset ) + return; + + if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) ) + { + + // convert Quad to Stereo in place, then batch process fx: perf KDB + + // left front + rear -> left, right front + rear -> right + + while ( count-- ) + { + pbf->left = (pbf->left + pbr->left) >> 1; + pbf->right = (pbf->right + pbr->right) >> 1; + pbf++; + pbr++; + } + + // process left & right + + PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT); + PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT ); + + // copy processed front to rear + + count = sampleCount; + + pbf = pbfront; + pbr = pbrear; + + while ( count-- ) + { + pbr->left = pbf->left; + pbr->right = pbf->right; + pbf++; + pbr++; + } + + } + else + { + // left front + rear -> left fx, right front + rear -> right fx + while ( count-- ) + { + fl = PSET_GetNext( pdsp->ppset[0], (pbf->left + pbr->left) >> 1); + fr = PSET_GetNext( pdsp->ppset[1], (pbf->right + pbr->right) >> 1); + fl = CLIP_DSP( fl ); + fr = CLIP_DSP( fr ); + + pbr->left = pbf->left = fl; + pbr->right = pbf->right = fr; + pbf++; + pbr++; + } + } + return; + } + + // crossfading to current preset from previous preset + + if ( bcrossfading ) + { + int r; + int rl, rr; + int flp, frp, rlp, rrp; + int xf_fl, xf_fr, xf_rl, xf_rr; + int avl, avr; + bool bexp = pdsp->bexpfade; + bool bfadetoquad = (pdsp->ipset == 0); + bool bfadefromquad = (pdsp->ipsetprev == 0); + + if ( bfadetoquad || bfadefromquad ) + { + // special case if previous or current preset is 0 (quad passthrough) + + while ( count-- ) + { + avl = (pbf->left + pbr->left) >> 1; + avr = (pbf->right + pbr->right) >> 1; + + // get current preset values + + // current preset is 0, which implies fading to passthrough quad output + // need to fade from stereo to quad + + if ( pdsp->ipset ) + { + rl = fl = PSET_GetNext( pdsp->ppset[0], avl ); + rr = fr = PSET_GetNext( pdsp->ppset[0], avr ); + } + else + { + fl = pbf->left; + fr = pbf->right; + rl = pbr->left; + rr = pbr->right; + } + + // get previous preset values + + if ( pdsp->ipsetprev ) + { + rlp = flp = PSET_GetNext( pdsp->ppsetprev[0], avl ); + rrp = frp = PSET_GetNext( pdsp->ppsetprev[0], avr ); + } + else + { + flp = pbf->left; + frp = pbf->right; + rlp = pbr->left; + rrp = pbr->right; + } + + fl = CLIP_DSP(fl); + fr = CLIP_DSP(fr); + flp = CLIP_DSP(flp); + frp = CLIP_DSP(frp); + rl = CLIP_DSP(rl); + rr = CLIP_DSP(rr); + rlp = CLIP_DSP(rlp); + rrp = CLIP_DSP(rrp); + + // get current ramp value + + r = RMP_GetNext( &pdsp->xramp ); + + // crossfade from previous to current preset + + if (!bexp) + { + xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left + xf_rl = XFADE(rl, rlp, r); // crossfade front left previous to front left + xf_rr = XFADE(rr, rrp, r); // crossfade front left previous to front left + } + else + { + xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left + xf_rl = XFADE_EXP(rl, rlp, r); // crossfade front left previous to front left + xf_rr = XFADE_EXP(rr, rrp, r); // crossfade front left previous to front left + } + + pbf->left = xf_fl; + pbf->right = xf_fr; + pbr->left = xf_rl; + pbr->right = xf_rr; + + pbf++; + pbr++; + } + + return; + } + + while ( count-- ) + { + avl = (pbf->left + pbr->left) >> 1; + avr = (pbf->right + pbr->right) >> 1; + + // get current preset values + + fl = PSET_GetNext( pdsp->ppset[0], avl ); + fr = PSET_GetNext( pdsp->ppset[1], avr ); + + // get previous preset values + + flp = PSET_GetNext( pdsp->ppsetprev[0], avl ); + frp = PSET_GetNext( pdsp->ppsetprev[1], avr ); + + + fl = CLIP_DSP( fl ); + fr = CLIP_DSP( fr ); + + // get previous preset values + + flp = CLIP_DSP( flp ); + frp = CLIP_DSP( frp ); + + // get current ramp value + + r = RMP_GetNext( &pdsp->xramp ); + + // crossfade from previous to current preset + if (!bexp) + { + xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE(fr, frp, r); + } + else + { + xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE_EXP(fr, frp, r); + } + + pbf->left = xf_fl; // crossfaded front left + pbf->right = xf_fr; + + pbr->left = xf_fl; // duplicate front channel to rear channel + pbr->right = xf_fr; + + pbf++; + pbr++; + } + } +} + +// Helper: called only from DSP_Process +// DSP_Process quad in to quad out + +inline void DSP_ProcessQuadToQuad(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading ) +{ + portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process + portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process + int count = sampleCount; + int fl, fr, rl, rr; + + if ( !bcrossfading ) + { + if ( !pdsp->ipset ) + return; + + // each channel gets its own processor + + if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) && FBatchPreset(pdsp->ppset[2]) && FBatchPreset(pdsp->ppset[3])) + { + // batch process fx front & rear, left & right: perf KDB + + PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT); + PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT ); + PSET_GetNextN( pdsp->ppset[2], pbrear, sampleCount, OP_LEFT ); + PSET_GetNextN( pdsp->ppset[3], pbrear, sampleCount, OP_RIGHT ); + } + else + { + while ( count-- ) + { + fl = PSET_GetNext( pdsp->ppset[0], pbf->left ); + fr = PSET_GetNext( pdsp->ppset[1], pbf->right ); + rl = PSET_GetNext( pdsp->ppset[2], pbr->left ); + rr = PSET_GetNext( pdsp->ppset[3], pbr->right ); + + pbf->left = CLIP_DSP( fl ); + pbf->right = CLIP_DSP( fr ); + pbr->left = CLIP_DSP( rl ); + pbr->right = CLIP_DSP( rr ); + + pbf++; + pbr++; + } + } + return; + } + + // crossfading to current preset from previous preset + + if ( bcrossfading ) + { + int r; + int flp, frp, rlp, rrp; + int xf_fl, xf_fr, xf_rl, xf_rr; + bool bexp = pdsp->bexpfade; + + while ( count-- ) + { + // get current preset values + + fl = PSET_GetNext( pdsp->ppset[0], pbf->left ); + fr = PSET_GetNext( pdsp->ppset[1], pbf->right ); + rl = PSET_GetNext( pdsp->ppset[2], pbr->left ); + rr = PSET_GetNext( pdsp->ppset[3], pbr->right ); + + // get previous preset values + + flp = PSET_GetNext( pdsp->ppsetprev[0], pbf->left ); + frp = PSET_GetNext( pdsp->ppsetprev[1], pbf->right ); + rlp = PSET_GetNext( pdsp->ppsetprev[2], pbr->left ); + rrp = PSET_GetNext( pdsp->ppsetprev[3], pbr->right ); + + // get current ramp value + + r = RMP_GetNext( &pdsp->xramp ); + + // crossfade from previous to current preset + if (!bexp) + { + xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE(fr, frp, r); + xf_rl = XFADE(rl, rlp, r); + xf_rr = XFADE(rr, rrp, r); + } + else + { + xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE_EXP(fr, frp, r); + xf_rl = XFADE_EXP(rl, rlp, r); + xf_rr = XFADE_EXP(rr, rrp, r); + } + + pbf->left = CLIP_DSP(xf_fl); // crossfaded front left + pbf->right = CLIP_DSP(xf_fr); + pbr->left = CLIP_DSP(xf_rl); + pbr->right = CLIP_DSP(xf_rr); + + pbf++; + pbr++; + } + } +} + + +// Helper: called only from DSP_Process +// DSP_Process quad + center in to mono out (front left = front right) + +inline void DSP_Process5To1(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, portable_samplepair_t *pbcenter, int sampleCount, bool bcrossfading ) +{ + portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process + portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process + portable_samplepair_t *pbc = pbcenter; // pointer to buffer of center mono samples to process + int count = sampleCount; + int x; + int av; + + if ( !bcrossfading ) + { + if ( !pdsp->ipset ) + return; + + if ( FBatchPreset(pdsp->ppset[0]) ) + { + + // convert Quad + Center to Mono in place, then batch process fx: perf KDB + + // left front + rear -> left, right front + rear -> right + while ( count-- ) + { + // pbf->left = ((pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) / 5); + + av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5 + av >>= 8; + pbf->left = av; + pbf++; + pbr++; + pbc++; + } + + // process left (mono), duplicate into right + + PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT_DUPLICATE); + + // copy processed front to rear & center + + count = sampleCount; + + pbf = pbfront; + pbr = pbrear; + pbc = pbcenter; + + while ( count-- ) + { + pbr->left = pbf->left; + pbr->right = pbf->right; + pbc->left = pbf->left; + pbf++; + pbr++; + pbc++; + } + + } + else + { + // avg fl,fr,rl,rr,fc into mono fx, duplicate on all channels + while ( count-- ) + { + // av = ((pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) / 5); + av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5 + av >>= 8; + x = PSET_GetNext( pdsp->ppset[0], av ); + x = CLIP_DSP( x ); + pbr->left = pbr->right = pbf->left = pbf->right = pbc->left = x; + pbf++; + pbr++; + pbc++; + } + } + return; + } + + if ( bcrossfading ) + { + int r; + int fl, fr, rl, rr, fc; + int flp, frp, rlp, rrp, fcp; + int xf_fl, xf_fr, xf_rl, xf_rr, xf_fc; + bool bexp = pdsp->bexpfade; + bool bfadetoquad = (pdsp->ipset == 0); + bool bfadefromquad = (pdsp->ipsetprev == 0); + + if ( bfadetoquad || bfadefromquad ) + { + // special case if previous or current preset is 0 (quad passthrough) + + while ( count-- ) + { + // av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2); + + av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5 + av >>= 8; + + // get current preset values + + // current preset is 0, which implies fading to passthrough quad output + // need to fade from mono to quad + + if ( pdsp->ipset ) + { + fc = rl = rr = fl = fr = PSET_GetNext( pdsp->ppset[0], av ); + } + else + { + fl = pbf->left; + fr = pbf->right; + rl = pbr->left; + rr = pbr->right; + fc = pbc->left; + } + + // get previous preset values + + if ( pdsp->ipsetprev ) + { + fcp = rrp = rlp = frp = flp = PSET_GetNext( pdsp->ppsetprev[0], av ); + } + else + { + flp = pbf->left; + frp = pbf->right; + rlp = pbr->left; + rrp = pbr->right; + fcp = pbc->left; + } + + fl = CLIP_DSP(fl); + fr = CLIP_DSP(fr); + flp = CLIP_DSP(flp); + frp = CLIP_DSP(frp); + rl = CLIP_DSP(rl); + rr = CLIP_DSP(rr); + rlp = CLIP_DSP(rlp); + rrp = CLIP_DSP(rrp); + fc = CLIP_DSP(fc); + fcp = CLIP_DSP(fcp); + + // get current ramp value + + r = RMP_GetNext( &pdsp->xramp ); + + // crossfade from previous to current preset + + if (!bexp) + { + xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left + xf_rl = XFADE(rl, rlp, r); // crossfade front left previous to front left + xf_rr = XFADE(rr, rrp, r); // crossfade front left previous to front left + xf_fc = XFADE(fc, fcp, r); // crossfade front left previous to front left + } + else + { + xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left + xf_rl = XFADE_EXP(rl, rlp, r); // crossfade front left previous to front left + xf_rr = XFADE_EXP(rr, rrp, r); // crossfade front left previous to front left + xf_fc = XFADE_EXP(fc, fcp, r); // crossfade front left previous to front left + } + + pbf->left = xf_fl; + pbf->right = xf_fr; + pbr->left = xf_rl; + pbr->right = xf_rr; + pbc->left = xf_fc; + + pbf++; + pbr++; + pbc++; + } + + return; + } + + while ( count-- ) + { + + // av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2); + av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5 + av >>= 8; + + // get current preset values + + fl = PSET_GetNext( pdsp->ppset[0], av ); + + // get previous preset values + + flp = PSET_GetNext( pdsp->ppsetprev[0], av ); + + // get current ramp value + + r = RMP_GetNext( &pdsp->xramp ); + + fl = CLIP_DSP( fl ); + flp = CLIP_DSP( flp ); + + // crossfade from previous to current preset + if (!bexp) + xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left + else + xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left + + pbf->left = xf_fl; // crossfaded front left, duplicated to all channels + pbf->right = xf_fl; + pbr->left = xf_fl; + pbr->right = xf_fl; + pbc->left = xf_fl; + + pbf++; + pbr++; + pbc++; + } + } +} + +// Helper: called only from DSP_Process +// DSP_Process quad + center in to quad + center out + +inline void DSP_Process5To5(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, portable_samplepair_t *pbcenter, int sampleCount, bool bcrossfading ) +{ + portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process + portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process + portable_samplepair_t *pbc = pbcenter; // pointer to buffer of center mono samples to process + + int count = sampleCount; + int fl, fr, rl, rr, fc; + + if ( !bcrossfading ) + { + if ( !pdsp->ipset ) + return; + + // each channel gets its own processor + + if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) && FBatchPreset(pdsp->ppset[2]) && FBatchPreset(pdsp->ppset[3])) + { + // batch process fx front & rear, left & right: perf KDB + + PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT); + PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT ); + PSET_GetNextN( pdsp->ppset[2], pbrear, sampleCount, OP_LEFT ); + PSET_GetNextN( pdsp->ppset[3], pbrear, sampleCount, OP_RIGHT ); + PSET_GetNextN( pdsp->ppset[4], pbcenter, sampleCount, OP_LEFT ); + } + else + { + while ( count-- ) + { + fl = PSET_GetNext( pdsp->ppset[0], pbf->left ); + fr = PSET_GetNext( pdsp->ppset[1], pbf->right ); + rl = PSET_GetNext( pdsp->ppset[2], pbr->left ); + rr = PSET_GetNext( pdsp->ppset[3], pbr->right ); + fc = PSET_GetNext( pdsp->ppset[4], pbc->left ); + + pbf->left = CLIP_DSP( fl ); + pbf->right = CLIP_DSP( fr ); + pbr->left = CLIP_DSP( rl ); + pbr->right = CLIP_DSP( rr ); + pbc->left = CLIP_DSP( fc ); + + pbf++; + pbr++; + pbc++; + } + } + return; + } + + // crossfading to current preset from previous preset + + if ( bcrossfading ) + { + int r; + int flp, frp, rlp, rrp, fcp; + int xf_fl, xf_fr, xf_rl, xf_rr, xf_fc; + bool bexp = pdsp->bexpfade; + + while ( count-- ) + { + // get current preset values + + fl = PSET_GetNext( pdsp->ppset[0], pbf->left ); + fr = PSET_GetNext( pdsp->ppset[1], pbf->right ); + rl = PSET_GetNext( pdsp->ppset[2], pbr->left ); + rr = PSET_GetNext( pdsp->ppset[3], pbr->right ); + fc = PSET_GetNext( pdsp->ppset[4], pbc->left ); + + // get previous preset values + + flp = PSET_GetNext( pdsp->ppsetprev[0], pbf->left ); + frp = PSET_GetNext( pdsp->ppsetprev[1], pbf->right ); + rlp = PSET_GetNext( pdsp->ppsetprev[2], pbr->left ); + rrp = PSET_GetNext( pdsp->ppsetprev[3], pbr->right ); + fcp = PSET_GetNext( pdsp->ppsetprev[4], pbc->left ); + + // get current ramp value + + r = RMP_GetNext( &pdsp->xramp ); + + // crossfade from previous to current preset + if (!bexp) + { + xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE(fr, frp, r); + xf_rl = XFADE(rl, rlp, r); + xf_rr = XFADE(rr, rrp, r); + xf_fc = XFADE(fc, fcp, r); + } + else + { + xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left + xf_fr = XFADE_EXP(fr, frp, r); + xf_rl = XFADE_EXP(rl, rlp, r); + xf_rr = XFADE_EXP(rr, rrp, r); + xf_fc = XFADE_EXP(fc, fcp, r); + } + + pbf->left = CLIP_DSP(xf_fl); // crossfaded front left + pbf->right = CLIP_DSP(xf_fr); + pbr->left = CLIP_DSP(xf_rl); + pbr->right = CLIP_DSP(xf_rr); + pbc->left = CLIP_DSP(xf_fc); + + pbf++; + pbr++; + pbc++; + } + } +} + +// This is an evil hack, but we need to restore the old presets after letting the sound system update for a few frames, so we just +// "defer" the restore until the top of the next call to CheckNewDspPresets. I put in a bit of warning in case we ever have code +// outside of this time period modifying any of the dsp convars. It doesn't seem to be an issue just save/loading between levels +static bool g_bNeedPresetRestore = false; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct PreserveDSP_t +{ + ConVar *cvar; + float oldvalue; +}; + +static PreserveDSP_t g_PreserveDSP[] = +{ + { &dsp_room }, + { &dsp_water }, + { &dsp_player }, + { &dsp_facingaway }, + { &dsp_speaker }, + { &dsp_spatial }, + { &dsp_automatic } +}; + +//----------------------------------------------------------------------------- +// Purpose: Called at the top of CheckNewDspPresets to restore ConVars to real values +//----------------------------------------------------------------------------- +void DSP_CheckRestorePresets() +{ + if ( !g_bNeedPresetRestore ) + return; + + g_bNeedPresetRestore = false; + + int i; + int c = ARRAYSIZE( g_PreserveDSP ); + + // Restore + for ( i = 0 ; i < c; ++i ) + { + PreserveDSP_t& slot = g_PreserveDSP[ i ]; + + ConVar *cv = slot.cvar; + Assert( cv ); + if ( cv->GetFloat() != 0.0f ) + { + // NOTE: dsp_speaker is being (correctly) save/restored by maps, which would trigger this warning + //Warning( "DSP_CheckRestorePresets: Value of %s was changed between DSP_ClearState and CheckNewDspPresets, not restoring to old value\n", cv->GetName() ); + continue; + } + cv->SetValue( slot.oldvalue ); + } + + // reinit all dsp processors (only load preset file on engine init, however) + + AllocDsps( false ); + + // flush dsp automatic nodes + + g_bdas_init_nodes = 0; + g_bdas_room_init = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DSP_ClearState() +{ + // if we already cleared dsp state, and a restore is pending, + // don't clear again + + if ( g_bNeedPresetRestore ) + return; + + // always save a cleared dsp automatic value to force reset of all adsp code + + dsp_automatic.SetValue(0); + + // Tracker 7155: YWB: This is a pretty ugly hack to zero out all of the dsp convars and bootstrap the dsp system into using them for a few frames + + int i; + int c = ARRAYSIZE( g_PreserveDSP ); + + for ( i = 0 ; i < c; ++i ) + { + PreserveDSP_t& slot = g_PreserveDSP[ i ]; + + ConVar *cv = slot.cvar; + Assert( cv ); + slot.oldvalue = cv->GetFloat(); + cv->SetValue( 0 ); + } + + // force all dsp presets to end crossfades, end one-shot presets, & release and reset all resources + // immediately. + + FreeDsps( false ); // free all dsp states, but don't discard preset templates + + // This forces the ConVars which we set to zero above to be reloaded to their old values at the time we issue the CheckNewDspPresets + // command. This seems to happen early enough in level changes were we don't appear to be trying to stomp real settings... + + g_bNeedPresetRestore = true; +} + +// return true if dsp's preset is one-shot and it has expired + +bool DSP_HasExpired( int idsp ) +{ + dsp_t *pdsp; + + Assert( idsp < CDSPS ); + + if (idsp < 0 || idsp >= CDSPS) + return false; + + pdsp = &dsps[idsp]; + + // if first preset has expired, dsp has expired + + if ( PSET_IsOneShot( pdsp->ppset[0] ) ) + return PSET_HasExpired( pdsp->ppset[0] ); + else + return false; +} + +// returns true if dsp is crossfading from previous dsp preset + +bool DSP_IsCrossfading( int idsp ) +{ + dsp_t *pdsp; + + Assert( idsp < CDSPS ); + + if (idsp < 0 || idsp >= CDSPS) + return false; + + pdsp = &dsps[idsp]; + + return !RMP_HitEnd( &pdsp->xramp ); + +} + +// returns previous preset # before oneshot preset was set + +int DSP_OneShotPrevious( int idsp ) +{ + dsp_t *pdsp; + int idsp_prev; + + Assert( idsp < CDSPS ); + + if (idsp < 0 || idsp >= CDSPS) + return 0; + + pdsp = &dsps[idsp]; + + idsp_prev = pdsp->ipsetsav_oneshot; + + return idsp_prev; +} + +// given idsp (processor index), return true if +// both current and previous presets are 0 for this processor + +bool DSP_PresetIsOff( int idsp ) +{ + dsp_t *pdsp; + + if (idsp < 0 || idsp >= CDSPS) + return true; + + Assert ( idsp < CDSPS ); // make sure idsp is valid + + pdsp = &dsps[idsp]; + + // if current and previous preset 0, return - preset 0 is 'off' + + return ( !pdsp->ipset && !pdsp->ipsetprev ); +} + +// returns true if dsp is off for room effects + +bool DSP_RoomDSPIsOff() +{ + return DSP_PresetIsOff( Get_idsp_room() ); +} + +// Main DSP processing routine: +// process samples in buffers using pdsp processor +// continue crossfade between 2 dsp processors if crossfading on switch +// pfront - front stereo buffer to process +// prear - rear stereo buffer to process (may be NULL) +// pcenter - front center mono buffer (may be NULL) +// sampleCount - number of samples in pbuf to process +// This routine also maps the # processing channels in the pdsp to the number of channels +// supplied. ie: if the pdsp has 4 channels and pbfront and pbrear are both non-null, the channels +// map 1:1 through the processors. + +void DSP_Process( int idsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, portable_samplepair_t *pbcenter, int sampleCount ) +{ + bool bcrossfading; + int cchan_in; // input channels (2,4 or 5) + int cprocs; // output cannels (1, 2 or 4) + dsp_t *pdsp; + + if (idsp < 0 || idsp >= CDSPS) + return; + + // Don't pull dsp data in if player is not connected (during load/level change) + if ( !g_pSoundServices->IsConnected() ) + return; + + Assert ( idsp < CDSPS ); // make sure idsp is valid + + pdsp = &dsps[idsp]; + + Assert (pbfront); + + // return right away if fx processing is turned off + + if ( dsp_off.GetInt() ) + return; + + // if current and previous preset 0, return - preset 0 is 'off' + + if ( !pdsp->ipset && !pdsp->ipsetprev ) + return; + + if ( sampleCount < 0 ) + return; + + bcrossfading = !RMP_HitEnd( &pdsp->xramp ); + + // if not crossfading, and previous channel is not null, free previous + + if ( !bcrossfading ) + DSP_FreePrevPreset( pdsp ); + + // if current and previous preset 0 (ie: just freed previous), return - preset 0 is 'off' + + if ( !pdsp->ipset && !pdsp->ipsetprev ) + return; + + cchan_in = (pbrear ? 4 : 2) + (pbcenter ? 1 : 0); + cprocs = pdsp->cchan; + + Assert(cchan_in == 2 || cchan_in == 4 || cchan_in == 5 ); + + // if oneshot preset, update the duration counter (only update front left counter) + + PSET_UpdateDuration( pdsp->ppset[0], sampleCount ); + + // NOTE: when mixing between different channel sizes, + // always AVERAGE down to fewer channels and DUPLICATE up more channels. + // The following routines always process cchan_in channels. + // ie: QuadToMono still updates 4 values in buffer + + // DSP_Process stereo in to mono out (ie: left and right are averaged) + + if ( cchan_in == 2 && cprocs == 1) + { + DSP_ProcessStereoToMono( pdsp, pbfront, pbrear, sampleCount, bcrossfading ); + return; + } + + // DSP_Process stereo in to stereo out (if more than 2 procs, ignore them) + + if ( cchan_in == 2 && cprocs >= 2) + { + DSP_ProcessStereoToStereo( pdsp, pbfront, pbrear, sampleCount, bcrossfading ); + return; + } + + + // DSP_Process quad in to mono out + + if ( cchan_in == 4 && cprocs == 1) + { + DSP_ProcessQuadToMono( pdsp, pbfront, pbrear, sampleCount, bcrossfading ); + return; + } + + + // DSP_Process quad in to stereo out (preserve stereo spatialization, loose front/rear) + + if ( cchan_in == 4 && cprocs == 2) + { + DSP_ProcessQuadToStereo( pdsp, pbfront, pbrear, sampleCount, bcrossfading ); + return; + } + + + // DSP_Process quad in to quad out + + if ( cchan_in == 4 && cprocs == 4) + { + DSP_ProcessQuadToQuad( pdsp, pbfront, pbrear, sampleCount, bcrossfading ); + return; + } + + // DSP_Process quad + center in to mono out + + if ( cchan_in == 5 && cprocs == 1) + { + DSP_Process5To1( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading ); + return; + } + + if ( cchan_in == 5 && cprocs == 2) + { + // undone: not used in AllocDsps + Assert(false); + //DSP_Process5to2( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading ); + return; + } + + if ( cchan_in == 5 && cprocs == 4) + { + // undone: not used in AllocDsps + Assert(false); + //DSP_Process5to4( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading ); + return; + } + + // DSP_Process quad + center in to quad + center out + + if ( cchan_in == 5 && cprocs == 5) + { + DSP_Process5To5( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading ); + return; + } + +} + +// DSP helpers + +// free all dsp processors + +void FreeDsps( bool bReleaseTemplateMemory ) +{ + + DSP_Free(idsp_room); + DSP_Free(idsp_water); + DSP_Free(idsp_player); + DSP_Free(idsp_facingaway); + DSP_Free(idsp_speaker); + DSP_Free(idsp_spatial); + DSP_Free(idsp_automatic); + + idsp_room = 0; + idsp_water = 0; + idsp_player = 0; + idsp_facingaway = 0; + idsp_speaker = 0; + idsp_spatial = 0; + idsp_automatic = 0; + + for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i ) + { + paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i ); + if ( pSpecialBuffer->nSpecialDSP != 0 ) + { + DSP_Free( pSpecialBuffer->idsp_specialdsp ); + pSpecialBuffer->idsp_specialdsp = 0; + pSpecialBuffer->nPrevSpecialDSP = 0; + pSpecialBuffer->nSpecialDSP = 0; + } + } + + DSP_FreeAll(); + + // only unlock and free psettemplate memory on engine shutdown + + if ( bReleaseTemplateMemory ) + DSP_ReleaseMemory(); +} + +// alloc dsp processors, load dsp preset array from file on engine init only + +bool AllocDsps( bool bLoadPresetFile ) +{ + int csurround = (g_AudioDevice->IsSurround() ? 2: 0); // surround channels to allocate + int ccenter = (g_AudioDevice->IsSurroundCenter() ? 1 : 0); // center channels to allocate + + DSP_InitAll( bLoadPresetFile ); + + idsp_room = -1; + idsp_water = -1; + idsp_player = -1; + idsp_facingaway = -1; + idsp_speaker = -1; + idsp_spatial = -1; + idsp_automatic = -1; + + // alloc dsp room channel (mono, stereo if dsp_stereo is 1) + + // dsp room is mono, 300ms default fade time + + idsp_room = DSP_Alloc( dsp_room.GetInt(), 200, 1 ); + + // dsp automatic overrides dsp_room, if dsp_room set to DSP_AUTOMATIC (1) + + idsp_automatic = DSP_Alloc( dsp_automatic.GetInt(), 200, 1 ) ; + + // alloc stereo or quad series processors for player or water + + // water and player presets are mono + + idsp_water = DSP_Alloc( dsp_water.GetInt(), 100, 1 ); + idsp_player = DSP_Alloc( dsp_player.GetInt(), 100, 1 ); + + // alloc facing away filters (stereo, quad or 5ch) + + idsp_facingaway = DSP_Alloc( dsp_facingaway.GetInt(), 100, 2 + csurround + ccenter ); + + // alloc speaker preset (mono) + + idsp_speaker = DSP_Alloc( dsp_speaker.GetInt(), 300, 1 ); + + // alloc spatial preset (2-5 chan) + + idsp_spatial = DSP_Alloc( dsp_spatial.GetInt(), 300, 2 + csurround + ccenter ); + + // init prev values + + ipset_room_prev = dsp_room.GetInt(); + ipset_water_prev = dsp_water.GetInt(); + ipset_player_prev = dsp_player.GetInt(); + ipset_facingaway_prev = dsp_facingaway.GetInt(); + ipset_room_typeprev = dsp_room_type.GetInt(); + ipset_speaker_prev = dsp_speaker.GetInt(); + ipset_spatial_prev = dsp_spatial.GetInt(); + ipset_automatic_prev = dsp_automatic.GetInt(); + + if (idsp_room < 0 || idsp_water < 0 || idsp_player < 0 || idsp_facingaway < 0 || idsp_speaker < 0 || idsp_spatial < 0 || idsp_automatic < 0) + { + DevMsg ("WARNING: DSP processor failed to initialize! \n" ); + + FreeDsps( true ); + return false; + } + + return true; +} + +// count number of dsp presets specified in preset file +// counts outer {} pairs, ignoring inner {} pairs. + +int DSP_CountFilePresets( const char *pstart ) +{ + int cpresets = 0; + bool binpreset = false; + bool blookleft = false; + + while ( 1 ) + { + pstart = COM_Parse( pstart ); + + if ( strlen(com_token) <= 0) + break; + + if ( com_token[0] == '{' ) // left paren + { + if (!binpreset) + { + cpresets++; // found preset: + blookleft = true; // look for another left + binpreset = true; + } + else + { + blookleft = false; // inside preset: next, look for matching right paren + } + + continue; + } + + if ( com_token[0] == '}' ) // right paren + { + if (binpreset) + { + if (!blookleft) // looking for right paren + { + blookleft = true; // found it, now look for another left + } + else + { + // expected inner left paren, found outer right - end of preset definition + binpreset = false; + blookleft = true; + } + } + else + { + // error - unexpected } paren + DevMsg("PARSE ERROR!!! dsp_presets.txt: unexpected '}' \n"); + continue; + } + } + + } + + return cpresets; +} + +struct dsp_stringmap_t +{ + char sz[33]; + int i; +}; + +// token map for dsp_preset.txt + +dsp_stringmap_t gdsp_stringmap[] = +{ + // PROCESSOR TYPE: + {"NULL", PRC_NULL}, + {"DLY", PRC_DLY}, + {"RVA", PRC_RVA}, + {"FLT", PRC_FLT}, + {"CRS", PRC_CRS}, + {"PTC", PRC_PTC}, + {"ENV", PRC_ENV}, + {"LFO", PRC_LFO}, + {"EFO", PRC_EFO}, + {"MDY", PRC_MDY}, + {"DFR", PRC_DFR}, + {"AMP", PRC_AMP}, + + // FILTER TYPE: + {"LP", FLT_LP}, + {"HP", FLT_HP}, + {"BP", FLT_BP}, + + // FILTER QUALITY: + {"LO", QUA_LO}, + {"MED", QUA_MED}, + {"HI", QUA_HI}, + {"VHI", QUA_VHI}, + + // DELAY TYPE: + {"PLAIN", DLY_PLAIN}, + {"ALLPASS", DLY_ALLPASS}, + {"LOWPASS", DLY_LOWPASS}, + {"DLINEAR", DLY_LINEAR}, + {"FLINEAR", DLY_FLINEAR}, + {"LOWPASS_4TAP",DLY_LOWPASS_4TAP}, + {"PLAIN_4TAP", DLY_PLAIN_4TAP}, + + // LFO TYPE: + {"SIN", LFO_SIN}, + {"TRI", LFO_TRI}, + {"SQR", LFO_SQR}, + {"SAW", LFO_SAW}, + {"RND", LFO_RND}, + {"LOG_IN", LFO_LOG_IN}, + {"LOG_OUT", LFO_LOG_OUT}, + {"LIN_IN", LFO_LIN_IN}, + {"LIN_OUT", LFO_LIN_OUT}, + + // ENVELOPE TYPE: + {"LIN", ENV_LIN}, + {"EXP", ENV_EXP}, + + // PRESET CONFIGURATION TYPE: + {"SIMPLE", PSET_SIMPLE}, + {"LINEAR", PSET_LINEAR}, + {"PARALLEL2", PSET_PARALLEL2}, + {"PARALLEL4", PSET_PARALLEL4}, + {"PARALLEL5", PSET_PARALLEL5}, + {"FEEDBACK", PSET_FEEDBACK}, + {"FEEDBACK3", PSET_FEEDBACK3}, + {"FEEDBACK4", PSET_FEEDBACK4}, + {"MOD1", PSET_MOD}, + {"MOD2", PSET_MOD2}, + {"MOD3", PSET_MOD3} +}; + +int gcdsp_stringmap = sizeof(gdsp_stringmap) / sizeof (dsp_stringmap_t); + +#define isnumber(c) (c == '+' || c == '-' || c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7'|| c == '8' || c == '9')\ + +// given ptr to null term. string, return integer or float value from g_dsp_stringmap + +float DSP_LookupStringToken( char *psz, int ipset ) +{ + int i; + float fipset = (float)ipset; + + if (isnumber(psz[0])) + return atof(psz); + + for (i = 0; i < gcdsp_stringmap; i++) + { + if (!strcmpi(gdsp_stringmap[i].sz, psz)) + return gdsp_stringmap[i].i; + } + + // not found + + DevMsg("DSP PARSE ERROR! token not found in dsp_presets.txt. Preset: %3.0f \n", fipset ); + return 0; +} + +// load dsp preset file, parse presets into g_psettemplate array +// format for each preset: +// { <preset #> <preset type> <#processors> <gain> { <processor type> <param0>...<param15> } {...} {...} } + +#define CHAR_LEFT_PAREN '{' +#define CHAR_RIGHT_PAREN '}' + +// free preset template memory + +void DSP_ReleaseMemory( void ) +{ + if (g_psettemplates) + { + delete[] g_psettemplates; + g_psettemplates = NULL; + } +} + +bool DSP_LoadPresetFile( void ) +{ + char szFile[ MAX_OSPATH ]; + char *pbuffer; + const char *pstart; + bool bResult = false; + int cpresets; + int ipreset; + int itype; + int cproc; + float mix_min; + float mix_max; + float db_min; + float db_mixdrop; + int j; + bool fdone; + float duration; + float fadeout; + + Q_snprintf( szFile, sizeof( szFile ), "scripts/dsp_presets.txt" ); + + MEM_ALLOC_CREDIT(); + + CUtlBuffer buf; + + if ( !g_pFullFileSystem->ReadFile( szFile, "GAME", buf ) ) + { + Error( "DSP_LoadPresetFile: unable to open '%s'\n", szFile ); + return bResult; + } + pbuffer = (char *)buf.PeekGet(); // Use malloc - free at end of this routine + + pstart = pbuffer; + + // figure out how many presets we're loading - count outer parens. + + cpresets = DSP_CountFilePresets( pstart ); + + g_cpsettemplates = cpresets; + + g_psettemplates = new pset_t[cpresets]; + if (!g_psettemplates) + { + Warning( "DSP Preset Loader: Out of memory.\n"); + goto load_exit; + } + memset (g_psettemplates, 0, cpresets * sizeof(pset_t)); + + + // parse presets into g_psettemplates array + + pstart = pbuffer; + + // for each preset... + + for ( j = 0; j < cpresets; j++) + { + // check for end of file or next CHAR_LEFT_PAREN + + while (1) + { + pstart = COM_Parse( pstart ); + + if ( strlen(com_token) <= 0) + break; + + if ( com_token[0] != CHAR_LEFT_PAREN ) + continue; + + break; + } + + // found start of a new preset definition + + // get preset #, type, cprocessors, gain + + pstart = COM_Parse( pstart ); + ipreset = atoi( com_token ); + + pstart = COM_Parse( pstart ); + itype = (int)DSP_LookupStringToken( com_token , ipreset); + + pstart = COM_Parse( pstart ); + mix_min = atof( com_token ); + + pstart = COM_Parse( pstart ); + mix_max = atof( com_token ); + + pstart = COM_Parse( pstart ); + duration = atof( com_token ); + + pstart = COM_Parse( pstart ); + fadeout = atof( com_token ); + + pstart = COM_Parse( pstart ); + db_min = atof( com_token ); + + pstart = COM_Parse( pstart ); + db_mixdrop = atof( com_token ); + + + g_psettemplates[ipreset].fused = true; + g_psettemplates[ipreset].mix_min = mix_min; + g_psettemplates[ipreset].mix_max = mix_max; + g_psettemplates[ipreset].duration = duration; + g_psettemplates[ipreset].fade = fadeout; + g_psettemplates[ipreset].db_min = db_min; + g_psettemplates[ipreset].db_mixdrop = db_mixdrop; + + // parse each processor for this preset + + fdone = false; + cproc = 0; + + while (1) + { + // find CHAR_LEFT_PAREN - start of new processor + + while (1) + { + pstart = COM_Parse( pstart ); + + if ( strlen(com_token) <= 0) + break; + + if (com_token[0] == CHAR_LEFT_PAREN) + break; + + if (com_token[0] == CHAR_RIGHT_PAREN) + { + // if found right paren, no more processors: done with this preset + fdone = true; + break; + } + } + + if ( fdone ) + break; + + // get processor type + + pstart = COM_Parse( pstart ); + g_psettemplates[ipreset].prcs[cproc].type = (int)DSP_LookupStringToken( com_token, ipreset ); + + // get param 0..n or stop when hit closing CHAR_RIGHT_PAREN + + int ip = 0; + + while (1) + { + pstart = COM_Parse( pstart ); + + if ( strlen(com_token) <= 0) + break; + + if ( com_token[0] == CHAR_RIGHT_PAREN ) + break; + + g_psettemplates[ipreset].prcs[cproc].prm[ip++] = DSP_LookupStringToken( com_token, ipreset ); + + // cap at max params + + ip = min(ip, CPRCPARAMS); + } + + cproc++; + if (cproc > CPSET_PRCS) + DevMsg("DSP PARSE ERROR!!! dsp_presets.txt: missing } or too many processors in preset #: %d \n", ipreset); + cproc = min(cproc, CPSET_PRCS); // don't overflow # procs + } + + // if cproc == 1, type is always SIMPLE + + if ( cproc == 1) + itype = PSET_SIMPLE; + + g_psettemplates[ipreset].type = itype; + g_psettemplates[ipreset].cprcs = cproc; + + } + + bResult = true; + +load_exit: + return bResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Called by client on level shutdown to clear ear ringing dsp effects +// could be extended to other stuff +//----------------------------------------------------------------------------- +void DSP_FastReset( int dspType ) +{ + int c = ARRAYSIZE( g_PreserveDSP ); + + // Restore + for ( int i = 0 ; i < c; ++i ) + { + PreserveDSP_t& slot = g_PreserveDSP[ i ]; + + if ( slot.cvar == &dsp_player ) + { + slot.oldvalue = dspType; + return; + } + } +} + +// Helper to check for change in preset of any of 4 processors +// if switching to a new preset, alloc new preset, simulate both presets in DSP_Process & xfade, +// called a few times per frame. + +void CheckNewDspPresets( void ) +{ + bool b_slow_cpu = dsp_slow_cpu.GetInt() == 0 ? false : true; + + DSP_CheckRestorePresets(); + + // room fx are on only if cpu is not slow + + int iroom = b_slow_cpu ? 0 : dsp_room.GetInt() ; + int ifacingaway = b_slow_cpu ? 0 : dsp_facingaway.GetInt(); + int iroomtype = b_slow_cpu ? 0 : dsp_room_type.GetInt(); + int ispatial = b_slow_cpu ? 0 : dsp_spatial.GetInt(); + int iautomatic = b_slow_cpu ? 0 : dsp_automatic.GetInt(); + + // always use dsp to process these + + int iwater = dsp_water.GetInt(); + int iplayer = dsp_player.GetInt(); + int ispeaker = dsp_speaker.GetInt(); + + // check for expired one-shot presets on player and room. + // Only check if a) no new preset has been set and b) not crossfading from previous preset (ie; previous is null) + + if ( iplayer == ipset_player_prev && !DSP_IsCrossfading( idsp_player ) ) + { + if ( DSP_HasExpired ( idsp_player ) ) + { + iplayer = DSP_OneShotPrevious( idsp_player); // preset has expired - revert to previous preset before one-shot + dsp_player.SetValue(iplayer); + } + } + + if ( iroom == ipset_room_prev && !DSP_IsCrossfading( idsp_room ) ) + { + if ( DSP_HasExpired ( idsp_room ) ) + { + iroom = DSP_OneShotPrevious( idsp_room ); // preset has expired - revert to previous preset before one-shot + dsp_room.SetValue(iroom); + } + } + + + // legacy code support for "room_type" Cvar + + if ( iroomtype != ipset_room_typeprev ) + { + // force dsp_room = room_type + + ipset_room_typeprev = iroomtype; + dsp_room.SetValue(iroomtype); + } + + // NOTE: don't change presets if currently crossfading from a previous preset + + if ( iroom != ipset_room_prev && !DSP_IsCrossfading( idsp_room) ) + { + DSP_SetPreset( idsp_room, iroom ); + ipset_room_prev = iroom; + + // force room_type = dsp_room + + dsp_room_type.SetValue(iroom); + ipset_room_typeprev = iroom; + } + + if ( iwater != ipset_water_prev && !DSP_IsCrossfading( idsp_water) ) + { + DSP_SetPreset( idsp_water, iwater ); + ipset_water_prev = iwater; + } + + if ( iplayer != ipset_player_prev && !DSP_IsCrossfading( idsp_player)) + { + DSP_SetPreset( idsp_player, iplayer ); + ipset_player_prev = iplayer; + } + + if ( ifacingaway != ipset_facingaway_prev && !DSP_IsCrossfading( idsp_facingaway) ) + { + DSP_SetPreset( idsp_facingaway, ifacingaway ); + ipset_facingaway_prev = ifacingaway; + } + + if ( ispeaker != ipset_speaker_prev && !DSP_IsCrossfading( idsp_speaker) ) + { + DSP_SetPreset( idsp_speaker, ispeaker ); + ipset_speaker_prev = ispeaker; + } + + if ( ispatial != ipset_spatial_prev && !DSP_IsCrossfading( idsp_spatial) ) + { + DSP_SetPreset( idsp_spatial, ispatial ); + ipset_spatial_prev = ispatial; + } + + if ( iautomatic != ipset_automatic_prev && !DSP_IsCrossfading( idsp_automatic) ) + { + DSP_SetPreset( idsp_automatic, iautomatic ); + ipset_automatic_prev = iautomatic; + } + + for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i ) + { + paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i ); + if ( pSpecialBuffer->nSpecialDSP != pSpecialBuffer->nPrevSpecialDSP && !DSP_IsCrossfading( pSpecialBuffer->idsp_specialdsp ) ) + { + DSP_SetPreset( pSpecialBuffer->idsp_specialdsp, pSpecialBuffer->nSpecialDSP ); + pSpecialBuffer->nPrevSpecialDSP = pSpecialBuffer->nSpecialDSP; + } + } +} + +// create idsp_room preset from set of values, reload the preset. +// modifies psettemplates in place. + +// ipreset is the preset # ie: 40 +// iproc is the processor to modify within the preset (typically 0) +// pvalues is an array of floating point parameters +// cparams is the # of elements in pvalues + +// USED FOR DEBUG ONLY. + +void DSP_DEBUGSetParams(int ipreset, int iproc, float *pvalues, int cparams) +{ + pset_t new_pset; // preset + int cparam = clamp (cparams, 0, CPRCPARAMS); + prc_t *pprct; + + // copy template preset from template array + + new_pset = g_psettemplates[ipreset]; + + // get iproc processor + + pprct = &(new_pset.prcs[iproc]); + + // copy parameters in to processor + + for (int i = 0; i < cparam; i++) + { + pprct->prm[i] = pvalues[i]; + } + + // copy constructed preset back into template location + + g_psettemplates[ipreset] = new_pset; + + // setup new preset + + dsp_room.SetValue( 0 ); + + CheckNewDspPresets(); + + dsp_room.SetValue( ipreset ); + + CheckNewDspPresets(); +} + +// reload entire preset file, reset all current dsp presets +// NOTE: this is debug code only. It doesn't do all mem free work correctly! + +void DSP_DEBUGReloadPresetFile( void ) +{ + int iroom = dsp_room.GetInt(); + int iwater = dsp_water.GetInt(); + int iplayer = dsp_player.GetInt(); +// int ifacingaway = dsp_facingaway.GetInt(); +// int iroomtype = dsp_room_type.GetInt(); + int ispeaker = dsp_speaker.GetInt(); + int ispatial = dsp_spatial.GetInt(); +// int iautomatic = dsp_automatic.GetInt(); + + // reload template array + + DSP_ReleaseMemory(); + + DSP_LoadPresetFile(); + + // force presets to reload + + dsp_room.SetValue( 0 ); + dsp_water.SetValue( 0 ); + dsp_player.SetValue( 0 ); + //dsp_facingaway.SetValue( 0 ); + //dsp_room_type.SetValue( 0 ); + dsp_speaker.SetValue( 0 ); + dsp_spatial.SetValue( 0 ); + //dsp_automatic.SetValue( 0 ); + + CUtlVector< int > specialDSPs; + for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i ) + { + paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i ); + + specialDSPs.AddToTail( pSpecialBuffer->nSpecialDSP ); + pSpecialBuffer->nSpecialDSP = 0; + } + + CheckNewDspPresets(); + + dsp_room.SetValue( iroom ); + dsp_water.SetValue( iwater ); + dsp_player.SetValue( iplayer ); + //dsp_facingaway.SetValue( ifacingaway ); + //dsp_room_type.SetValue( iroomtype ); + dsp_speaker.SetValue( ispeaker ); + dsp_spatial.SetValue( ispatial ); + //dsp_automatic.SetValue( iautomatic ); + + int nSpecialDSPNum = 0; + for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i ) + { + paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i ); + + pSpecialBuffer->nSpecialDSP = specialDSPs[ nSpecialDSPNum ]; + nSpecialDSPNum++; + } + + CheckNewDspPresets(); + + // flush dsp automatic nodes + + g_bdas_init_nodes = 0; + g_bdas_room_init = 0; +} + +// UNDONE: stock reverb presets: + +// carpet hallway +// tile hallway +// wood hallway +// metal hallway + +// train tunnel +// sewer main tunnel +// concrete access tunnel +// cave tunnel +// sand floor cave tunnel + +// metal duct shaft +// elevator shaft +// large elevator shaft + +// parking garage +// aircraft hangar +// cathedral +// train station + +// small cavern +// large cavern +// huge cavern +// watery cavern +// long, low cavern + +// wood warehouse +// metal warehouse +// concrete warehouse + +// small closet room +// medium drywall room +// medium wood room +// medium metal room + +// elevator +// small metal room +// medium metal room +// large metal room +// huge metal room + +// small metal room dense +// medium metal room dense +// large metal room dense +// huge metal room dense + +// small concrete room +// medium concrete room +// large concrete room +// huge concrete room + +// small concrete room dense +// medium concrete room dense +// large concrete room dense +// huge concrete room dense + +// soundproof room +// carpet lobby +// swimming pool +// open park +// open courtyard +// wide parkinglot +// narrow street +// wide street, short buildings +// wide street, tall buildings +// narrow canyon +// wide canyon +// huge canyon +// small valley +// wide valley +// wreckage & rubble +// small building cluster +// wide open plain +// high vista + +// alien interior small +// alien interior medium +// alien interior large +// alien interior huge + +// special fx presets: + +// alien citadel + +// teleport aftershock (these presets all ADSR timeout and reset the dsp_* to 0) +// on target teleport +// off target teleport +// death fade +// beam stasis +// scatterbrain +// pulse only +// slomo +// hypersensitive +// supershocker +// physwhacked +// forcefieldfry +// juiced +// zoomed in +// crabbed +// barnacle gut +// bad transmission + +//////////////////////// +// Dynamics processing +//////////////////////// + +// compressor defines +#define COMP_MAX_AMP 32767 // abs max amplitude +#define COMP_THRESH 20000 // start compressing at this threshold + +// compress input value - smoothly limit output y to -32767 <= y <= 32767 +// UNDONE: not tested or used + +inline int S_Compress( int xin ) +{ + + return CLIP( xin >> 2 ); // DEBUG - disabled + + + float Yn, Xn, Cn, Fn; + float C0 = 20000; // threshold + float p = .3; // compression ratio + float g = 1; // gain after compression + + Xn = (float)xin; + + // Compressor formula: + // Cn = l*Cn-1 + (1-l)*|Xn| // peak detector with memory + // f(Cn) = (Cn/C0)^(p-1) for Cn > C0 // gain function above threshold + // f(Cn) = 1 for C <= C0 // unity gain below threshold + // Yn = f(Cn) * Xn // compressor output + + // UNDONE: curves discontinuous at threshold, causes distortion, try catmul-rom + + //float l = .5; // compressor memory + //Cn = l * (*pCnPrev) + (1 - l) * fabs((float)xin); + //*pCnPrev = Cn; + + Cn = fabs((float)xin); + + if (Cn < C0) + Fn = 1; + else + Fn = powf((Cn / C0),(p - 1)); + + Yn = Fn * Xn * g; + + //if (Cn > 0) + // Msg("%d -> %d\n", xin, (int)Yn); // DEBUG + + //if (fabs(Yn) > 32767) + // Yn = Yn; // DEBUG + + return (CLIP((int)Yn)); +} + |