summaryrefslogtreecommitdiff
path: root/engine/audio/private/snd_dsp.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/audio/private/snd_dsp.cpp')
-rw-r--r--engine/audio/private/snd_dsp.cpp9679
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( &amps[i] ); }
+void AMP_FreeAll() { for (int i = 0; i < CAMPS; i++) AMP_Free( &amps[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 = &amps[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 = &ap;
+ 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));
+}
+