//========= 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));
}