5458 lines
149 KiB
C
5458 lines
149 KiB
C
/*
|
|
s_dsp.c - digital signal processing algorithms for audio FX
|
|
Copyright (C) 2009 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "sound.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) // conver seconds to # samples in equivalent time
|
|
#define CLIP_DSP( x ) ( x )
|
|
#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
|
|
|
|
// 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)-1) // parameter max size
|
|
|
|
// crossfade from y2 to y1 at point r (0 < r < PMAX )
|
|
#define XFADE( y1, y2, r ) (((y1) * (r)) >> PBITS) + (((y2) * (PMAX - (r))) >> PBITS);
|
|
#define XFADEF( y1, y2, r ) (((y1) * (r)) / (float)(PMAX)) + (((y2) * (PMAX - (r))) / (float)(PMAX));
|
|
|
|
/////////////////////
|
|
// dsp helpers
|
|
/////////////////////
|
|
|
|
// dot two integer vectors of length M+1
|
|
// M is filter order, h is filter vector, w is filter state vector
|
|
_inline int dot ( int M, int *h, int *w )
|
|
{
|
|
int i, y;
|
|
|
|
for( y = 0, i = 0; i <= M; i++ )
|
|
y += ( h[i] * w[i] ) >> PBITS;
|
|
return y;
|
|
}
|
|
|
|
// delay array w[] by D samples
|
|
// w[0] = input, w[D] = output
|
|
// practical for filters, but not for large values of D
|
|
_inline void delay( int D, int *w )
|
|
{
|
|
int i;
|
|
|
|
for( i = D; i >= 1; i-- ) // reverse order updating
|
|
w[i] = w[i-1];
|
|
}
|
|
|
|
// circular wrap of pointer p, relative to array w
|
|
// D delay line size in samples w[0...D]
|
|
// w delay line buffer pointer, dimension D+1
|
|
// p circular pointer
|
|
_inline void wrap( int D, int *w, int **p )
|
|
{
|
|
if( *p > w + D ) *p -= D + 1; // when *p = w + D + 1, it wraps around to *p = w
|
|
if( *p < w ) *p += D + 1; // when *p = w - 1, it wraps around to *p = w + D
|
|
}
|
|
|
|
// simple averaging filter for performance - a[] is 0, b[] is 1, L is # of samples to average
|
|
_inline int avg_filter( int M, int *a, int L, int *b, int *w, int x )
|
|
{
|
|
int i, y = 0;
|
|
|
|
w[0] = x;
|
|
|
|
// output adder
|
|
switch( L )
|
|
{
|
|
default:
|
|
case 12: y += w[12];
|
|
case 11: y += w[11];
|
|
case 10: y += w[10];
|
|
case 9: y += w[9];
|
|
case 8: y += w[8];
|
|
case 7: y += w[7];
|
|
case 6: y += w[6];
|
|
case 5: y += w[5];
|
|
case 4: y += w[4];
|
|
case 3: y += w[3];
|
|
case 2: y += w[2];
|
|
case 1: y += w[1];
|
|
case 0: y += w[0];
|
|
}
|
|
|
|
for( i = L; i >= 1; i-- ) // reverse update internal state
|
|
w[i] = w[i-1];
|
|
|
|
switch( L )
|
|
{
|
|
default:
|
|
case 12: return y / 13;
|
|
case 11: return y / 12;
|
|
case 10: return y / 11;
|
|
case 9: return y / 10;
|
|
case 8: return y / 9;
|
|
case 7: return y >> 3;
|
|
case 6: return y / 7;
|
|
case 5: return y / 6;
|
|
case 4: return y / 5;
|
|
case 3: return y >> 2;
|
|
case 2: return y / 3;
|
|
case 1: return y >> 1;
|
|
case 0: return y;
|
|
}
|
|
}
|
|
|
|
// IIR filter, cannonical form
|
|
// returns single sample y for current input value x
|
|
// x is input sample
|
|
// w = internal state vector, dimension max(M,L) + 1
|
|
// L, M numerator and denominator filter orders
|
|
// a,b are M+1 dimensional arrays of filter params
|
|
//
|
|
// for M = 4:
|
|
//
|
|
// 1 w0(n) b0
|
|
// x(n)--->(+)--(*)-----.------(*)->(+)---> y(n)
|
|
// ^ | ^
|
|
// | [Delay d] |
|
|
// | | |
|
|
// | -a1 |W1 b1 |
|
|
// ----(*)---.------(*)----
|
|
// ^ | ^
|
|
// | [Delay d] |
|
|
// | | |
|
|
// | -a2 |W2 b2 |
|
|
// ----(*)---.------(*)----
|
|
// ^ | ^
|
|
// | [Delay d] |
|
|
// | | |
|
|
// | -a3 |W3 b3 |
|
|
// ----(*)---.------(*)----
|
|
// ^ | ^
|
|
// | [Delay d] |
|
|
// | | |
|
|
// | -a4 |W4 b4 |
|
|
// ----(*)---.------(*)----
|
|
//
|
|
// for each input sample x, do:
|
|
// w0 = x - a1*w1 - a2*w2 - ... aMwM
|
|
// y = b0*w0 + b1*w1 + ...bL*wL
|
|
// wi = wi-1, i = K, K-1, ..., 1
|
|
|
|
_inline int iir_filter( int M, int *a, int L, int *b, int *w, int x )
|
|
{
|
|
int K, i, y, x0;
|
|
|
|
if( M == 0 )
|
|
return avg_filter( M, a, L, b, w, x );
|
|
|
|
y = 0;
|
|
x0 = x;
|
|
|
|
K = max ( M, L );
|
|
|
|
// for (i = 1; i <= M; i++) // input adder
|
|
// w[0] -= ( a[i] * w[i] ) >> PBITS;
|
|
|
|
// M is clamped between 1 and FLT_M
|
|
// change this switch statement if FLT_M changes!
|
|
|
|
switch( M )
|
|
{
|
|
case 12: x0 -= ( a[12] * w[12] ) >> PBITS;
|
|
case 11: x0 -= ( a[11] * w[11] ) >> PBITS;
|
|
case 10: x0 -= ( a[10] * w[10] ) >> PBITS;
|
|
case 9: x0 -= ( a[9] * w[9] ) >> PBITS;
|
|
case 8: x0 -= ( a[8] * w[8] ) >> PBITS;
|
|
case 7: x0 -= ( a[7] * w[7] ) >> PBITS;
|
|
case 6: x0 -= ( a[6] * w[6] ) >> PBITS;
|
|
case 5: x0 -= ( a[5] * w[5] ) >> PBITS;
|
|
case 4: x0 -= ( a[4] * w[4] ) >> PBITS;
|
|
case 3: x0 -= ( a[3] * w[3] ) >> PBITS;
|
|
case 2: x0 -= ( a[2] * w[2] ) >> PBITS;
|
|
default:
|
|
case 1: x0 -= ( a[1] * w[1] ) >> PBITS;
|
|
}
|
|
|
|
w[0] = x0;
|
|
|
|
// for( i = 0; i <= L; i++ ) // output adder
|
|
// y += ( b[i] * w[i] ) >> PBITS;
|
|
|
|
switch( L )
|
|
{
|
|
case 12: y += ( b[12] * w[12] ) >> PBITS;
|
|
case 11: y += ( b[11] * w[11] ) >> PBITS;
|
|
case 10: y += ( b[10] * w[10] ) >> PBITS;
|
|
case 9: y += ( b[9] * w[9] ) >> PBITS;
|
|
case 8: y += ( b[8] * w[8] ) >> PBITS;
|
|
case 7: y += ( b[7] * w[7] ) >> PBITS;
|
|
case 6: y += ( b[6] * w[6] ) >> PBITS;
|
|
case 5: y += ( b[5] * w[5] ) >> PBITS;
|
|
case 4: y += ( b[4] * w[4] ) >> PBITS;
|
|
case 3: y += ( b[3] * w[3] ) >> PBITS;
|
|
case 2: y += ( b[2] * w[2] ) >> PBITS;
|
|
default:
|
|
case 1: y += ( b[1] * w[1] ) >> PBITS;
|
|
case 0: y += ( b[0] * w[0] ) >> PBITS;
|
|
}
|
|
|
|
for( i = K; i >= 1; i-- ) // reverse update internal state
|
|
w[i] = w[i-1];
|
|
|
|
return y; // current output sample
|
|
}
|
|
|
|
// IIR filter, cannonical form, using dot product and delay implementation
|
|
// (may be easier to optimize this routine.)
|
|
_inline int iir_filter2( int M, int *a, int L, int *b, int *w, int x )
|
|
{
|
|
int K, y;
|
|
|
|
K = max( M, L ); // K = max (M, L)
|
|
w[0] = 0; // needed for dot (M, a, w)
|
|
|
|
w[0] = x - dot( M, a, w ); // input adder
|
|
y = dot( L, b, w ); // output adder
|
|
|
|
delay( K, w ); // update delay line
|
|
|
|
return y; // current output sample
|
|
}
|
|
|
|
|
|
// fir filter - no feedback = high stability but also may be more expensive computationally
|
|
_inline int fir_filter( int M, int *h, int *w, int x )
|
|
{
|
|
int i, y;
|
|
|
|
w[0] = x;
|
|
|
|
for( y = 0, i = 0; i <= M; i++ )
|
|
y += h[i] * w[i];
|
|
|
|
for( i = M; i >= -1; i-- )
|
|
w[i] = w[i-1];
|
|
|
|
return y;
|
|
}
|
|
|
|
// fir filter, using dot product and delay implementation
|
|
_inline int fir_filter2( int M, int *h, int *w, int x )
|
|
{
|
|
int y;
|
|
|
|
w[0] = x;
|
|
y = dot( M, h, w );
|
|
delay( M, w );
|
|
|
|
return y;
|
|
}
|
|
|
|
|
|
// tap - i-th tap of circular delay line buffer
|
|
// D delay line size in samples
|
|
// w delay line buffer pointer, of dimension D+1
|
|
// p circular pointer
|
|
// t = 0...D
|
|
int tap( int D, int *w, int *p, int t )
|
|
{
|
|
return w[(p - w + t) % (D + 1)];
|
|
}
|
|
|
|
// tapi - interpolated tap output of a delay line
|
|
// interpolates sample between adjacent samples in delay line for 'frac' part of delay
|
|
// D delay line size in samples
|
|
// w delay line buffer pointer, of dimension D+1
|
|
// p circular pointer
|
|
// t - delay tap integer value 0...D. (complete delay is t.frac )
|
|
// frac - varying 16 bit fractional delay value 0...32767 (normalized to 0.0 - 1.0)
|
|
_inline int tapi( int D, int *w, int *p, int t, int frac )
|
|
{
|
|
int i, j;
|
|
int si, sj;
|
|
|
|
i = t; // tap value, interpolate between adjacent samples si and sj
|
|
j = (i + 1) % (D+1); // if i = D, then j = 0; otherwise, j = i + 1
|
|
|
|
si = tap( D, w, p, i ); // si(n) = x(n - i)
|
|
sj = tap( D, w, p, j ); // sj(n) = x(n - j)
|
|
|
|
return si + (((frac) * (sj - si) ) >> 16);
|
|
}
|
|
|
|
// circular delay line, D-fold delay
|
|
// D delay line size in samples w[0..D]
|
|
// w delay line buffer pointer, dimension D+1
|
|
// p circular pointer
|
|
_inline void cdelay( int D, int *w, int **p )
|
|
{
|
|
(*p)--; // decrement pointer and wrap modulo (D+1)
|
|
wrap ( D, w, p ); // when *p = w-1, it wraps around to *p = w+D
|
|
}
|
|
|
|
// plain reverberator with circular delay line
|
|
// D delay line size in samples
|
|
// t tap from this location - <= D
|
|
// w delay line buffer pointer of dimension D+1
|
|
// p circular pointer, must be init to &w[0] before first call
|
|
// a feedback value, 0-PMAX (normalized to 0.0-1.0)
|
|
// b gain
|
|
// x input sample
|
|
|
|
// w0(n) b
|
|
// x(n)--->(+)--------.-----(*)-> y(n)
|
|
// ^ |
|
|
// | [Delay d]
|
|
// | |
|
|
// | a |Wd(n)
|
|
// ----(*)---.
|
|
|
|
_inline int dly_plain( int D, int t, int *w, int **p, int a, int b, int x )
|
|
{
|
|
int y, sD;
|
|
|
|
sD = tap( D, w, *p, t ); // Tth tap delay output
|
|
y = x + (( a * sD ) >> PBITS); // filter output
|
|
**p = y; // delay input
|
|
cdelay( D, w, p ); // update delay line
|
|
|
|
return (( y * b ) >> PBITS );
|
|
}
|
|
|
|
// straight delay line
|
|
//
|
|
// D delay line size in samples
|
|
// t tap from this location - <= D
|
|
// w delay line buffer pointer of dimension D+1
|
|
// p circular pointer, must be init to &w[0] before first call
|
|
// x input sample
|
|
//
|
|
// x(n)--->[Delay d]---> y(n)
|
|
//
|
|
_inline int dly_linear ( int D, int t, int *w, int **p, int x )
|
|
{
|
|
int y;
|
|
|
|
y = tap( D, w, *p, t ); // Tth tap delay output
|
|
**p = x; // delay input
|
|
cdelay( D, w, p ); // update delay line
|
|
|
|
return y;
|
|
}
|
|
|
|
// lowpass reverberator, replace feedback multiplier 'a' in
|
|
// plain reverberator with a low pass filter
|
|
// D delay line size in samples
|
|
// t tap from this location - <= D
|
|
// w delay line buffer pointer of dimension D+1
|
|
// p circular pointer, must be init to &w[0] before first call
|
|
// a feedback gain
|
|
// b output gain
|
|
// M filter order
|
|
// bf filter numerator, 0-PMAX (normalized to 0.0-1.0), M+1 dimensional
|
|
// af filter denominator, 0-PMAX (normalized to 0.0-1.0), M+1 dimensional
|
|
// vf filter state, M+1 dimensional
|
|
// x input sample
|
|
// w0(n) b
|
|
// x(n)--->(+)--------------.----(*)--> y(n)
|
|
// ^ |
|
|
// | [Delay d]
|
|
// | |
|
|
// | a |Wd(n)
|
|
// --(*)--[Filter])-
|
|
|
|
int dly_lowpass( int D, int t, int *w, int **p, int a, int b, int M, int *af, int L, int *bf, int *vf, int x )
|
|
{
|
|
int y, sD;
|
|
|
|
sD = tap( D, w, *p, t ); // delay output is filter input
|
|
y = x + ((iir_filter ( M, af, L, bf, vf, sD ) * a) >> PBITS); // filter output with gain
|
|
**p = y; // delay input
|
|
cdelay( D, w, p ); // update delay line
|
|
|
|
return (( y * b ) >> PBITS ); // output with gain
|
|
}
|
|
|
|
// allpass reverberator with circular delay line
|
|
// D delay line size in samples
|
|
// t tap from this location - <= D
|
|
// w delay line buffer pointer of dimension D+1
|
|
// p circular pointer, must be init to &w[0] before first call
|
|
// a feedback value, 0-PMAX (normalized to 0.0-1.0)
|
|
// b gain
|
|
|
|
// w0(n) -a b
|
|
// x(n)--->(+)--------.-----(*)-->(+)--(*)-> y(n)
|
|
// ^ | ^
|
|
// | [Delay d] |
|
|
// | | |
|
|
// | a |Wd(n) |
|
|
// ----(*)---.-------------
|
|
//
|
|
// for each input sample x, do:
|
|
// w0 = x + a*Wd
|
|
// y = -a*w0 + Wd
|
|
// delay (d, W) - w is the delay buffer array
|
|
//
|
|
// or, using circular delay, for each input sample x do:
|
|
//
|
|
// Sd = tap (D,w,p,D)
|
|
// S0 = x + a*Sd
|
|
// y = -a*S0 + Sd
|
|
// *p = S0
|
|
// cdelay(D, w, &p)
|
|
|
|
_inline int dly_allpass( int D, int t, int *w, int **p, int a, int b, int x )
|
|
{
|
|
int y, s0, sD;
|
|
|
|
sD = tap( D, w, *p, t ); // Dth tap delay output
|
|
s0 = x + (( a * sD ) >> PBITS);
|
|
|
|
y = (( -a * s0 ) >> PBITS ) + sD; // filter output
|
|
**p = s0; // delay input
|
|
cdelay( D, w, p ); // update delay line
|
|
|
|
return (( y * b ) >> 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
|
|
typedef struct
|
|
{
|
|
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.
|
|
} prc_t;
|
|
|
|
// processor parameter ranges - for validating parameters during allocation of new processor
|
|
typedef struct prm_rng_s
|
|
{
|
|
int iprm; // parameter index
|
|
float lo; // min value of parameter
|
|
float hi; // max value of parameter
|
|
} prm_rng_t;
|
|
|
|
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 FTR_MAX FLT_HP
|
|
|
|
// flt parameters
|
|
|
|
typedef struct
|
|
{
|
|
qboolean 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])
|
|
} flt_t;
|
|
|
|
// 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 ) { int i; for( i = 0; i < CFLTS; i++ ) FLT_Init( &flts[i] ); }
|
|
void FLT_Free( flt_t *pf ) { if( pf ) Q_memset( pf, 0, sizeof( flt_t )); }
|
|
void FLT_FreeAll( void ) { int i; for( 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]
|
|
flt_t * FLT_Alloc( int M, int L, int *a, int *b )
|
|
{
|
|
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;
|
|
for( j = 0; j <= M; j++ )
|
|
pf->a[j] = a[j];
|
|
|
|
for( j = 0; j <= L; j++ )
|
|
pf->b[j] = b[j];
|
|
|
|
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 = M_PI2 * 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;
|
|
}
|
|
|
|
|
|
// convolution of x[n] with h[n], resulting in y[n]
|
|
// h, x, y filter, input and output arrays (double precision)
|
|
// M = filter order, L = input length
|
|
// h is M+1 dimensional
|
|
// x is L dimensional
|
|
// y is L+M dimensional
|
|
void conv( int M, double *h, int L, double *x, double *y )
|
|
{
|
|
int n, m;
|
|
|
|
for( n = 0; n < L+M; n++ )
|
|
{
|
|
for( y[n] = 0, m = max(0, n-L+1); m <= min(n, M); m++ )
|
|
{
|
|
y[n] += h[m] * x[n-m];
|
|
}
|
|
}
|
|
}
|
|
|
|
// cas2can - convert cascaded, second order section parameter arrays to
|
|
// canonical numerator/denominator arrays. Canonical implementations
|
|
// have half as many multiplies as cascaded implementations.
|
|
|
|
// K is number of cascaded sections
|
|
// A is Kx3 matrix of sos params A[K] = A[0]..A[K-1]
|
|
// a is (2K + 1) -dimensional output of canonical params
|
|
|
|
#define KMAX 32 // max # of sos sections - 8 is the most we should ever see at runtime
|
|
|
|
void cas2can( int K, double A[KMAX+1][3], int *aout )
|
|
{
|
|
int i, j;
|
|
double d[2*KMAX + 1];
|
|
double a[2*KMAX + 1];
|
|
|
|
ASSERT( K <= KMAX );
|
|
|
|
Q_memset( d, 0, sizeof( double ) * ( 2 * KMAX + 1 ));
|
|
Q_memset( a, 0, sizeof( double ) * ( 2 * KMAX + 1 ));
|
|
|
|
a[0] = 1;
|
|
|
|
for( i = 0; i < K; i++ )
|
|
{
|
|
conv( 2, A[i], 2 * i + 1, a, d );
|
|
|
|
for( j = 0; j < 2 * i + 3; j++ )
|
|
a[j] = d[j];
|
|
}
|
|
|
|
for( i = 0; i < (2*K + 1); i++ )
|
|
aout[i] = a[i] * PMAX;
|
|
}
|
|
|
|
|
|
// chebyshev IIR design, type 2, Lowpass or Highpass
|
|
|
|
#define lnf( e ) ( 2.303 * log10( e ))
|
|
#define acosh( e ) ( lnf( (e) + sqrt(( e ) * ( e ) - 1) ))
|
|
#define asinh( e ) ( lnf( (e) + sqrt(( e ) * ( e ) + 1) ))
|
|
|
|
|
|
// returns a[], b[] which are Kx3 matrices of cascaded second-order sections
|
|
// these matrices may be passed directly to the iir_cas() routine for evaluation
|
|
// Nmax - maximum order of filter
|
|
// cutoff, ftype, qwidth - filter cutoff in hz, filter type FLT_LOWPASS/HIGHPASS, qwidth in hz
|
|
// pM - denominator order
|
|
// pL - numerator order
|
|
// a - array of canonical filter params
|
|
// b - array of canonical filter params
|
|
void FLT_Design_Cheb( int Nmax, float cutoff, float ftype, float qwidth, int *pM, int *pL, int *a, int *b )
|
|
{
|
|
// p769 - converted from MATLAB
|
|
|
|
double s = (ftype == FLT_LP ? 1 : -1 ); // 1 for LP, -1 for HP
|
|
double fs = SOUND_DMA_SPEED; // sampling frequency
|
|
double fpass = cutoff; // cutoff frequency
|
|
double fstop = fpass + max (2000, qwidth); // stop frequency
|
|
double Apass = 0.5; // max attenuation of pass band UNDONE: use Quality to select this
|
|
double Astop = 10; // max amplitude of stop band UNDONE: use Quality to select this
|
|
|
|
double Wpass, Wstop, epass, estop, Nex, aa;
|
|
double W3, f3, W0, G, Wi2, W02, a1, a2, th, Wi, D, b1;
|
|
int i, K, r, N;
|
|
double A[KMAX+1][3]; // denominator output matrices, second order sections
|
|
double B[KMAX+1][3]; // numerator output matrices, second order sections
|
|
|
|
Wpass = tan( M_PI * fpass / fs );
|
|
Wpass = pow( Wpass, s );
|
|
Wstop = tan( M_PI * fstop / fs );
|
|
Wstop = pow( Wstop, s );
|
|
|
|
epass = sqrt( pow( (float)10.0f, (float)Apass/10.0f ) - 1 );
|
|
estop = sqrt( pow( (float)10.0f, (float)Astop/10.0f ) - 1 );
|
|
|
|
// calculate filter order N
|
|
|
|
Nex = acosh( estop/epass ) / acosh ( Wstop/Wpass );
|
|
N = min ( ceil(Nex), Nmax ); // don't exceed Nmax for filter order
|
|
r = ( (int)N & 1); // r == 1 if N is odd
|
|
K = (N - r ) / 2;
|
|
|
|
aa = asinh ( estop ) / N;
|
|
W3 = Wstop / cosh( acosh( estop ) / N );
|
|
f3 = (fs / M_PI) * atan( pow( W3, s ));
|
|
|
|
W0 = sinh( aa ) / Wstop;
|
|
W02 = W0 * W0;
|
|
|
|
// 1st order section for N odd
|
|
if( r == 1 )
|
|
{
|
|
G = 1 / (1 + W0);
|
|
A[0][0] = 1; A[0][1] = s * (2*G-1); A[0][2] = 0;
|
|
B[0][0] = G; B[0][1] = G * s; B[0][2] = 0;
|
|
}
|
|
else
|
|
{
|
|
A[0][0] = 1; A[0][1] = 0; A[0][2] = 0;
|
|
B[0][0] = 1; B[0][1] = 0; B[0][2] = 0;
|
|
}
|
|
|
|
for( i = 1; i <= K ; i++ )
|
|
{
|
|
th = M_PI * (N - 1 + 2 * i) / (2 * N);
|
|
Wi = sin( th ) / Wstop;
|
|
Wi2 = Wi * Wi;
|
|
|
|
D = 1 - 2 * W0 * cos( th ) + W02 + Wi2;
|
|
G = ( 1 + Wi2 ) / D;
|
|
|
|
b1 = 2 * ( 1 - Wi2 ) / ( 1 + Wi2 );
|
|
a1 = 2 * ( 1 - W02 - Wi2) / D;
|
|
a2 = ( 1 + 2 * W0 * cos( th ) + W02 + Wi2) / D;
|
|
|
|
A[i][0] = 1;
|
|
A[i][1] = s * a1;
|
|
A[i][2] = a2;
|
|
|
|
B[i][0] = G;
|
|
B[i][1] = G* s* b1;
|
|
B[i][2] = G;
|
|
}
|
|
|
|
// convert cascade parameters to canonical parameters
|
|
|
|
cas2can( K, A, a );
|
|
*pM = 2*K + 1;
|
|
|
|
cas2can( K, B, b );
|
|
*pL = 2*K + 1;
|
|
}
|
|
|
|
// filter parameter order
|
|
|
|
typedef enum
|
|
{
|
|
flt_iftype,
|
|
flt_icutoff,
|
|
flt_iqwidth,
|
|
flt_iquality,
|
|
|
|
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 (UNDONE: FLT_BP currently ignored)
|
|
{ flt_icutoff, 10, 22050 }, // cutoff frequency in hz at -3db gain
|
|
{ flt_iqwidth, 100, 11025 }, // width of BP, or steepness of LP/HP (ie: fcutoff + qwidth = -60db gain point)
|
|
{ flt_iquality, 0, QUA_MAX }, // QUA_LO, _MED, _HI 0,1,2,3
|
|
};
|
|
|
|
|
|
// 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];
|
|
|
|
int L = 0; // numerator order
|
|
int M = 0; // denominator order
|
|
int b[FLT_M+1]; // numerator params 0..PMAX
|
|
int a[FLT_M+1]; // denominator params 0..PMAX
|
|
|
|
// low pass and highpass filter design
|
|
|
|
if( (int)qual == QUA_LO )
|
|
qual = QUA_MED; // disable lowest quality filter - check perf on lowend KDB
|
|
|
|
switch ( (int)qual )
|
|
{
|
|
case QUA_LO:
|
|
// lowpass averaging filter: perf KDB
|
|
ASSERT( ftype == FLT_LP );
|
|
ASSERT( cutoff <= SOUND_DMA_SPEED );
|
|
M = 0;
|
|
|
|
// L is # of samples to average
|
|
|
|
L = 0;
|
|
if( cutoff <= SOUND_DMA_SPEED / 4 ) L = 1; // 11k
|
|
if( cutoff <= SOUND_DMA_SPEED / 8 ) L = 2; // 5.5k
|
|
if( cutoff <= SOUND_DMA_SPEED / 16 ) L = 4; // 2.75k
|
|
if( cutoff <= SOUND_DMA_SPEED / 32 ) L = 8; // 1.35k
|
|
if( cutoff <= SOUND_DMA_SPEED / 64 ) L = 12; // 750hz
|
|
|
|
break;
|
|
case QUA_MED:
|
|
// 1st order IIR filter, 3db cutoff at fc
|
|
FLT_Design_3db_IIR( cutoff, ftype, &M, &L, a, b );
|
|
|
|
M = bound( 1, M, FLT_M );
|
|
L = bound( 1, L, FLT_M );
|
|
break;
|
|
case QUA_HI:
|
|
// type 2 chebyshev N = 4 IIR
|
|
FLT_Design_Cheb( 4, cutoff, ftype, qwidth, &M, &L, a, b );
|
|
|
|
M = bound( 1, M, FLT_M );
|
|
L = bound( 1, L, FLT_M );
|
|
break;
|
|
case QUA_VHI:
|
|
// type 2 chebyshev N = 7 IIR
|
|
FLT_Design_Cheb( 8, cutoff, ftype, qwidth, &M, &L, a, b );
|
|
|
|
M = bound( 1, M, FLT_M );
|
|
L = bound( 1, L, FLT_M );
|
|
break;
|
|
}
|
|
|
|
return FLT_Alloc( M, L, a, b );
|
|
}
|
|
|
|
_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 )
|
|
{
|
|
}
|
|
|
|
// get next filter value for filter pf and input x
|
|
_inline int FLT_GetNext( flt_t *pf, int x )
|
|
{
|
|
return iir_filter( pf->M, pf->a, pf->L, pf->b, pf->w, x );
|
|
}
|
|
|
|
// 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++;
|
|
}
|
|
break;
|
|
case OP_RIGHT:
|
|
while( count-- )
|
|
{
|
|
pb->right = FLT_GetNext( pflt, pb->right );
|
|
pb++;
|
|
}
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
while( count-- )
|
|
{
|
|
pb->left = pb->right = FLT_GetNext( pflt, pb->left );
|
|
pb++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// 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
|
|
typedef struct
|
|
{
|
|
|
|
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
|
|
} pos_t;
|
|
|
|
// 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
|
|
typedef struct
|
|
{
|
|
pos_t p; // pos_t
|
|
qboolean fhitend; // flag indicating we hit end of oneshot wav
|
|
} pos_one_t;
|
|
|
|
// 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_MAX DLY_LINEAR
|
|
|
|
// delay line
|
|
typedef struct
|
|
{
|
|
qboolean fused; // true if dly is in use
|
|
int type; // delay type
|
|
int D; // delay size, in samples
|
|
int t; // current tap, <= D
|
|
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
|
|
HANDLE h; // memory handle for sample array
|
|
} dly_t;
|
|
|
|
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 ) { int i; for( 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 )
|
|
{
|
|
GlobalUnlock( pdly->h );
|
|
GlobalFree( pdly->h );
|
|
}
|
|
|
|
// free dly slot
|
|
Q_memset( pdly, 0, sizeof( dly_t ));
|
|
}
|
|
}
|
|
|
|
|
|
void DLY_FreeAll( void ) { int i; for( i = 0; i < CDLYS; i++ ) DLY_Free( &dlys[i] ); }
|
|
|
|
// set up 'b' gain parameter of feedback delay to
|
|
// compensate for gain caused by feedback.
|
|
void DLY_SetNormalizingGain( dly_t *pdly )
|
|
{
|
|
// 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;
|
|
|
|
// if b is 0, set b to PMAX (1)
|
|
b = pdly->b ? pdly->b : PMAX;
|
|
|
|
// fgain = b * (1.0 / (1.0 - (float)pdly->a / (float)PMAX)) / (float)PMAX;
|
|
fgain = (1.0 / (1.0 - (float)pdly->a / (float)PMAX ));
|
|
|
|
// 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
|
|
|
|
gain = min( gain, PMAX ); // cap at PMAX
|
|
gain = ((float)b/(float)PMAX) * gain; // scale final gain by pdly->b.
|
|
|
|
pdly->b = (int)gain;
|
|
}
|
|
|
|
// 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)
|
|
// if DLY_LOWPASS:
|
|
// 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 )
|
|
{
|
|
HANDLE h;
|
|
int cb;
|
|
int *w;
|
|
int i;
|
|
dly_t *pdly = NULL;
|
|
|
|
// find open slot
|
|
for( i = 0; i < CDLYS; i++ )
|
|
{
|
|
if( !dlys[i].fused )
|
|
{
|
|
pdly = &dlys[i];
|
|
DLY_Init( pdly );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( i == CDLYS )
|
|
{
|
|
MsgDev( D_WARN, "DSP: failed to allocate delay line.\n" );
|
|
return NULL; // all delay lines in use
|
|
}
|
|
|
|
cb = (D + 1) * sizeof( int ); // assume all samples are signed integers
|
|
|
|
if( type == DLY_LOWPASS )
|
|
{
|
|
// alloc lowpass fir_filter
|
|
pdly->pflt = FLT_Alloc( M, L, fa, fb );
|
|
if( !pdly->pflt )
|
|
{
|
|
MsgDev( D_WARN, "DSP: failed to allocate filter for delay line.\n" );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// alloc delay memory
|
|
h = GlobalAlloc( GMEM_MOVEABLE|GMEM_SHARE, cb );
|
|
if( !h )
|
|
{
|
|
MsgDev( D_ERROR, "Sound DSP: Out of memory.\n" );
|
|
FLT_Free( pdly->pflt );
|
|
return NULL;
|
|
}
|
|
|
|
// lock delay memory
|
|
w = (int *)GlobalLock( h );
|
|
|
|
if( !w )
|
|
{
|
|
MsgDev( D_ERROR, "Sound DSP: Failed to lock.\n" );
|
|
GlobalFree( h );
|
|
FLT_Free( pdly->pflt );
|
|
return NULL;
|
|
}
|
|
|
|
// clear delay array
|
|
Q_memset( w, 0, cb );
|
|
|
|
// init values
|
|
pdly->type = type;
|
|
pdly->D = D;
|
|
pdly->t = D; // set delay tap to full delay
|
|
pdly->D0 = D;
|
|
pdly->p = w; // init circular pointer to head of buffer
|
|
pdly->w = w;
|
|
pdly->h = h;
|
|
pdly->a = min( a, PMAX ); // do not allow 100% feedback
|
|
pdly->b = b;
|
|
pdly->fused = true;
|
|
|
|
if( type == DLY_LINEAR )
|
|
{
|
|
// linear delay has no feedback and unity gain
|
|
pdly->a = 0;
|
|
pdly->b = PMAX;
|
|
}
|
|
else
|
|
{
|
|
// adjust b to compensate for feedback gain
|
|
DLY_SetNormalizingGain( pdly );
|
|
}
|
|
|
|
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_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
|
|
{ dly_idelay, 0.0, 1000.0 }, // delay in milliseconds
|
|
{ dly_ifeedback, 0.0, 0.99 }, // feedback 0-1.0
|
|
{ dly_igain, 0.0, 1.0 }, // final gain of output stage, 0-1.0
|
|
|
|
// filter params if dly type DLY_LOWPASS
|
|
{ dly_iftype, 0, FTR_MAX },
|
|
{ dly_icutoff, 10.0, 22050.0 },
|
|
{ dly_iqwidth, 100.0, 11025.0 },
|
|
{ dly_iquality, 0, QUA_MAX },
|
|
};
|
|
|
|
dly_t * DLY_Params( prc_t *pprc )
|
|
{
|
|
dly_t *pdly = NULL;
|
|
int D, a, b;
|
|
|
|
float delay = 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];
|
|
|
|
D = MSEC_TO_SAMPS( delay ); // delay samples
|
|
a = feedback * PMAX; // feedback
|
|
b = gain * PMAX; // gain
|
|
|
|
switch( type )
|
|
{
|
|
case DLY_PLAIN:
|
|
case DLY_ALLPASS:
|
|
case DLY_LINEAR:
|
|
pdly = DLY_Alloc( D, a, b, type );
|
|
break;
|
|
case DLY_LOWPASS:
|
|
{
|
|
// set up dummy lowpass filter to convert params
|
|
prc_t prcf;
|
|
flt_t *pflt;
|
|
|
|
// 0,1,2 - high, medium, low (low quality implies faster execution time)
|
|
prcf.prm[flt_iquality] = qual;
|
|
prcf.prm[flt_icutoff] = cutoff;
|
|
prcf.prm[flt_iftype] = ftype;
|
|
prcf.prm[flt_iqwidth] = qwidth;
|
|
|
|
pflt = (flt_t *)FLT_Params( &prcf );
|
|
|
|
if( !pflt )
|
|
{
|
|
MsgDev( D_WARN, "DSP: 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;
|
|
}
|
|
}
|
|
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
|
|
int DLY_GetNext( dly_t *pdly, int x )
|
|
{
|
|
switch( pdly->type )
|
|
{
|
|
default:
|
|
case DLY_PLAIN:
|
|
return dly_plain( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, x );
|
|
case DLY_ALLPASS:
|
|
return dly_allpass( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, x );
|
|
case DLY_LOWPASS:
|
|
return dly_lowpass( pdly->D, pdly->t, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->M, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
|
|
case DLY_LINEAR:
|
|
return dly_linear( pdly->D, pdly->t, pdly->w, &pdly->p, x );
|
|
}
|
|
}
|
|
|
|
// batch version for performance
|
|
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++;
|
|
}
|
|
break;
|
|
case OP_RIGHT:
|
|
while( count-- )
|
|
{
|
|
pb->right = DLY_GetNext( pdly, pb->right );
|
|
pb++;
|
|
}
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
while( count-- )
|
|
{
|
|
pb->left = pb->right = DLY_GetNext( pdly, pb->left );
|
|
pb++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// get tap on t'th sample in delay - don't update buffer pointers, this is done via DLY_GetNext
|
|
_inline int DLY_GetTap( dly_t *pdly, int t )
|
|
{
|
|
return tap( pdly->D, pdly->w, pdly->p, t );
|
|
}
|
|
|
|
|
|
// make instantaneous change to new delay value D.
|
|
// 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 )
|
|
{
|
|
}
|
|
|
|
///////////////////
|
|
// 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
|
|
|
|
typedef struct
|
|
{
|
|
qboolean 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;
|
|
|
|
dly_t *pdlys[CRVA_DLYS]; // array of pointers to delays
|
|
} rva_t;
|
|
|
|
rva_t rvas[CRVAS];
|
|
|
|
void RVA_Init( rva_t *prva ) { if( prva ) Q_memset( prva, 0, sizeof( rva_t )); }
|
|
void RVA_InitAll( void ) { int i; for( i = 0; i < CRVAS; i++ ) RVA_Init( &rvas[i] ); }
|
|
|
|
// free parallel series reverb
|
|
void RVA_Free( rva_t *prva )
|
|
{
|
|
if( prva )
|
|
{
|
|
int i;
|
|
|
|
// free all delays
|
|
for( i = 0; i < CRVA_DLYS; i++)
|
|
DLY_Free ( prva->pdlys[i] );
|
|
|
|
FLT_Free( prva->pflt );
|
|
Q_memset( prva, 0, sizeof (rva_t) );
|
|
}
|
|
}
|
|
|
|
|
|
void RVA_FreeAll( void ) { int i; for( 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)
|
|
// 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
|
|
rva_t *RVA_Alloc( int *D, int *a, int *b, int m, flt_t *pflt, int fparallel )
|
|
{
|
|
int i;
|
|
rva_t *prva;
|
|
flt_t *pflt2 = NULL;
|
|
|
|
// find open slot
|
|
for( i = 0; i < CRVAS; i++ )
|
|
{
|
|
if( !rvas[i].fused )
|
|
break;
|
|
}
|
|
|
|
// return null if no free slots
|
|
if( i == CRVAS )
|
|
{
|
|
MsgDev( D_WARN, "DSP: failed to allocate reverb.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
prva = &rvas[i];
|
|
|
|
// if series filter specified, alloc
|
|
if( pflt && !fparallel )
|
|
{
|
|
// use filter data as template for a filter on output
|
|
pflt2 = FLT_Alloc( pflt->M, pflt->L, pflt->a, pflt->b );
|
|
|
|
if( !pflt2 )
|
|
{
|
|
MsgDev( D_WARN, "DSP: failed to allocate flt for reverb.\n" );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// alloc parallel reverbs
|
|
if( pflt && fparallel )
|
|
{
|
|
|
|
// use this filter data as a template to alloc a filter for each parallel delay
|
|
for( i = 0; i < m; i++ )
|
|
prva->pdlys[i] = DLY_AllocLP( D[i], a[i], b[i], DLY_LOWPASS, pflt->M, pflt->L, pflt->a, pflt->b );
|
|
}
|
|
else
|
|
{
|
|
// no filter specified, use plain delays in parallel sections
|
|
for( i = 0; i < m; i++ )
|
|
prva->pdlys[i] = DLY_Alloc( D[i], a[i], b[i], DLY_PLAIN );
|
|
}
|
|
|
|
|
|
// 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 );
|
|
MsgDev( D_WARN, "DSP: 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 6 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 6 feedback values (typical - all equal, like 0.75 * PMAX)
|
|
// b - array of 6 gain values for plain reverb outputs (1, .9, .8, .7)
|
|
// xin - input value
|
|
// if fparallel, filters are built into delays,
|
|
// otherwise, filter output
|
|
|
|
_inline int RVA_GetNext( rva_t *prva, int x )
|
|
{
|
|
int m = prva->m;
|
|
int i, y, sum;
|
|
|
|
sum = 0;
|
|
|
|
for( i = 0; i < m; i++ )
|
|
sum += DLY_GetNext( prva->pdlys[i], x );
|
|
|
|
// m is clamped between RVA_BASEM & CRVA_DLYS
|
|
|
|
if( m ) y = sum/m;
|
|
else y = x;
|
|
#if 0
|
|
// PERFORMANCE:
|
|
// UNDONE: build as array
|
|
int mm;
|
|
|
|
switch( m )
|
|
{
|
|
case 12: mm = (PMAX/12); break;
|
|
case 11: mm = (PMAX/11); break;
|
|
case 10: mm = (PMAX/10); break;
|
|
case 9: mm = (PMAX/9); break;
|
|
case 8: mm = (PMAX/8); break;
|
|
case 7: mm = (PMAX/7); break;
|
|
case 6: mm = (PMAX/6); break;
|
|
case 5: mm = (PMAX/5); break;
|
|
case 4: mm = (PMAX/4); break;
|
|
case 3: mm = (PMAX/3); break;
|
|
case 2: mm = (PMAX/2); break;
|
|
default:
|
|
case 1: mm = (PMAX/1); break;
|
|
}
|
|
|
|
y = (sum * mm) >> PBITS;
|
|
|
|
#endif // 0
|
|
|
|
// run series filter if present
|
|
if( prva->pflt && !prva->fparallel )
|
|
y = FLT_GetNext( prva->pflt, y );
|
|
|
|
return y;
|
|
}
|
|
|
|
// batch version for performance
|
|
_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++;
|
|
}
|
|
break;
|
|
case OP_RIGHT:
|
|
while( count-- )
|
|
{
|
|
pb->right = RVA_GetNext( prva, pb->right );
|
|
pb++;
|
|
}
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
while( count-- )
|
|
{
|
|
pb->left = pb->right = RVA_GetNext( prva, pb->left );
|
|
pb++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define RVA_BASEM 3 // base number of parallel delays
|
|
|
|
// nominal delay and feedback values
|
|
|
|
//float rvadlys[] = { 29, 37, 44, 50, 62, 75, 96, 118, 127, 143, 164, 175 };
|
|
float rvadlys[] = { 18, 23, 28, 36, 47, 21, 26, 33, 40, 49, 45, 38 };
|
|
float rvafbs[] = { 0.7, 0.7, 0.7, 0.8, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9 };
|
|
|
|
// reverb parameter order
|
|
typedef enum
|
|
{
|
|
// parameter order
|
|
rva_isize,
|
|
rva_idensity,
|
|
rva_idecay,
|
|
rva_iftype,
|
|
rva_icutoff,
|
|
rva_iqwidth,
|
|
rva_ifparallel,
|
|
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_isize, 0.0, 2.0 }, // 0-2.0 scales nominal delay parameters (starting at approx 20ms)
|
|
{ rva_idensity, 0.0, 2.0 }, // 0-2.0 density of reverbs (room shape) - controls # of parallel or series delays
|
|
{ rva_idecay, 0.0, 2.0 }, // 0-2.0 scales feedback parameters (starting at approx 0.15)
|
|
|
|
// filter params for each parallel reverb (quality set to 0 for max execution speed)
|
|
{ rva_iftype, 0, FTR_MAX },
|
|
{ rva_icutoff, 10, 22050 },
|
|
{ rva_iqwidth, 100, 11025 },
|
|
{ rva_ifparallel, 0, 1 } // if 1, then all filters operate in parallel with delays. otherwise filter output only
|
|
};
|
|
|
|
rva_t * RVA_Params( prc_t *pprc )
|
|
{
|
|
flt_t *pflt;
|
|
rva_t *prva;
|
|
float size = pprc->prm[rva_isize]; // 0-2.0 controls scale of delay parameters
|
|
float density = pprc->prm[rva_idensity]; // 0-2.0 density of reverbs (room shape) - controls # of parallel delays
|
|
float decay = pprc->prm[rva_idecay]; // 0-1.0 controls feedback parameters
|
|
|
|
float ftype = pprc->prm[rva_iftype];
|
|
float cutoff = pprc->prm[rva_icutoff];
|
|
float qwidth = pprc->prm[rva_iqwidth];
|
|
|
|
float fparallel = pprc->prm[rva_ifparallel];
|
|
|
|
// 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 = RVA_BASEM;
|
|
int i;
|
|
|
|
m = density * CRVA_DLYS / 2;
|
|
|
|
// limit # delays 3-12
|
|
m = bound( RVA_BASEM, m, CRVA_DLYS );
|
|
|
|
// average time sound takes to travel from most distant wall
|
|
// (cap at 1000 ft room)
|
|
for( i = 0; i < m; i++ )
|
|
{
|
|
// delays of parallel reverb
|
|
D[i] = MSEC_TO_SAMPS( rvadlys[i] * size );
|
|
|
|
// feedback and gain of parallel reverb
|
|
a[i] = (int)min( 0.9 * PMAX, rvafbs[i] * (float)PMAX * decay );
|
|
b[i] = PMAX;
|
|
}
|
|
|
|
// add filter
|
|
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] = ftype;
|
|
prcf.prm[flt_iqwidth] = qwidth;
|
|
|
|
pflt = (flt_t *)FLT_Params( &prcf );
|
|
}
|
|
|
|
prva = RVA_Alloc( D, a, b, m, pflt, fparallel );
|
|
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 )
|
|
{
|
|
}
|
|
|
|
|
|
////////////
|
|
// 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
|
|
|
|
typedef struct
|
|
{
|
|
qboolean 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;
|
|
|
|
dfr_t dfrs[CDFRS];
|
|
|
|
void DFR_Init( dfr_t *pdfr ) { if( pdfr ) Q_memset( pdfr, 0, sizeof( dfr_t )); }
|
|
void DFR_InitAll( void ) { int i; for( i = 0; i < CDFRS; i++ ) DFR_Init ( &dfrs[i] ); }
|
|
|
|
// free parallel series reverb
|
|
void DFR_Free( dfr_t *pdfr )
|
|
{
|
|
if( pdfr )
|
|
{
|
|
int i;
|
|
|
|
// free all delays
|
|
for( i = 0; i < CDFR_DLYS; i++ )
|
|
DLY_Free( pdfr->pdlys[i] );
|
|
|
|
Q_memset( pdfr, 0, sizeof( dfr_t ));
|
|
}
|
|
}
|
|
|
|
|
|
void DFR_FreeAll( void ) { int i; for( 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 )
|
|
{
|
|
MsgDev( D_WARN, "DSP: 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 );
|
|
MsgDev( D_WARN, "DSP: 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, y;
|
|
int n = pdfr->n;
|
|
|
|
y = x;
|
|
for( i = 0; i < n; i++ )
|
|
y = DLY_GetNext( pdfr->pdlys[i], y );
|
|
return y;
|
|
|
|
#if 0
|
|
// alternate method, using internal state - causes PREDELAY = sum of delay times
|
|
|
|
int *v = pdfr->w; // intermediate results
|
|
|
|
v[0] = x;
|
|
|
|
// reverse evaluate series delays
|
|
// w[0] w[1] w[2] w[n-1] w[n]
|
|
// x---->D[0]--->D[1]--->D[2]...-->D[n-1]--->out
|
|
//
|
|
|
|
for( i = n; i > 0; i-- )
|
|
v[i] = DLY_GetNext( pdfr->pdlys[i-1], v[i-1] );
|
|
|
|
return v[n];
|
|
#endif
|
|
}
|
|
|
|
// 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++;
|
|
}
|
|
break;
|
|
case OP_RIGHT:
|
|
while( count-- )
|
|
{
|
|
pb->right = DFR_GetNext( pdfr, pb->right );
|
|
pb++;
|
|
}
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
while( count-- )
|
|
{
|
|
pb->left = pb->right = DFR_GetNext( pdfr, pb->left );
|
|
pb++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define DFR_BASEN 2 // base number of series allpass delays
|
|
|
|
// nominal diffusor delay and feedback values
|
|
//float dfrdlys[] = { 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95 };
|
|
float dfrdlys[] = { 13, 19, 26, 21, 32, 36, 38, 16, 24, 28, 41, 35, 10, 46, 50, 27 };
|
|
float dfrfbs[] = { 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15 };
|
|
|
|
|
|
// diffusor parameter order
|
|
|
|
typedef enum
|
|
{
|
|
// parameter order
|
|
dfr_isize,
|
|
dfr_idensity,
|
|
dfr_idecay,
|
|
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_idensity, 0.0, 1.0 }, // 0-1.0 controls # of series delays
|
|
{ dfr_idecay, 0.0, 1.0 }, // 0-1.0 scales all feedback parameters
|
|
};
|
|
|
|
dfr_t *DFR_Params( prc_t *pprc )
|
|
{
|
|
dfr_t *pdfr;
|
|
int i, s;
|
|
float size = pprc->prm[dfr_isize]; // 0-1.0 scales all delays
|
|
float density = pprc->prm[dfr_idensity]; // 0-1.0 controls # of series delays
|
|
float diffusion = pprc->prm[dfr_idecay]; // 0-1.0 scales all feedback parameters
|
|
|
|
// 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 = DFR_BASEN;
|
|
|
|
// increase # of series diffusors with increased density
|
|
n += density * 2;
|
|
|
|
// limit m, n to half max number of delays
|
|
n = min( CDFR_DLYS / 2, n );
|
|
|
|
// 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.9 * PMAX, dfrfbs[i] * PMAX * diffusion );
|
|
b[i] = 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 )
|
|
{
|
|
}
|
|
|
|
//////////////////////
|
|
// 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
|
|
|
|
typedef struct // lfo or envelope wave table
|
|
{
|
|
int type; // lfo type
|
|
dly_t *pdly; // delay holds wav values and step pointers
|
|
} lfowav_t;
|
|
|
|
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 )
|
|
{
|
|
int i;
|
|
|
|
for( 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) * sin( (M_PI2 * (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 )( Com_RandomLong( 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) * pow( (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) * pow( 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( void )
|
|
{
|
|
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)
|
|
|
|
typedef struct
|
|
{
|
|
qboolean fused; // true if slot take
|
|
dly_t *pdly; // delay points to lfo wav within lfowav_t (don't free this)
|
|
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;
|
|
|
|
lfo_t lfos[CLFO];
|
|
|
|
void LFO_Init( lfo_t *plfo ) { if( plfo ) Q_memset( plfo, 0, sizeof( lfo_t )); }
|
|
void LFO_InitAll( void ) { int i; for( 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 ) { int i; for( 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, qboolean foneshot )
|
|
{
|
|
int i, 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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
MsgDev( D_WARN, "DSP: 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
|
|
return plfo->pdly->w[i];
|
|
}
|
|
|
|
// 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++;
|
|
}
|
|
break;
|
|
case OP_RIGHT:
|
|
while( count-- )
|
|
{
|
|
pb->right = LFO_GetNext( plfo, pb->right );
|
|
pb++;
|
|
}
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
while( count-- )
|
|
{
|
|
pb->left = pb->right = LFO_GetNext( plfo, pb->left );
|
|
pb++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// uses lfowav, rate, foneshot
|
|
typedef enum
|
|
{
|
|
// parameter order
|
|
lfo_iwav,
|
|
lfo_irate,
|
|
lfo_ifoneshot,
|
|
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_t * LFO_Params( prc_t *pprc )
|
|
{
|
|
lfo_t *plfo;
|
|
qboolean foneshot = pprc->prm[lfo_ifoneshot] > 0 ? true : false;
|
|
|
|
plfo = LFO_Alloc( pprc->prm[lfo_iwav], pprc->prm[lfo_irate], foneshot );
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Ramp - used for varying smoothly between int parameters ie: modulation delays
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
typedef struct
|
|
{
|
|
int initval; // initial ramp value
|
|
int target; // final ramp value
|
|
int sign; // increasing (1) or decreasing (-1) ramp
|
|
int yprev; // previous output value
|
|
qboolean fhitend; // true if hit end of ramp
|
|
pos_one_t ps; // current ramp output
|
|
} rmp_t;
|
|
|
|
// 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)
|
|
// called once per sample while ramping
|
|
// ramptime - duration of ramp in seconds
|
|
// initval - initial ramp value
|
|
// targetval - target ramp value
|
|
void RMP_Init( rmp_t *prmp, float ramptime, int initval, int targetval )
|
|
{
|
|
int rise;
|
|
int run;
|
|
|
|
if( prmp ) Q_memset( prmp, 0, sizeof( rmp_t ));
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
// 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 );
|
|
}
|
|
|
|
_inline qboolean RMP_HitEnd( rmp_t *prmp )
|
|
{
|
|
return prmp->fhitend;
|
|
}
|
|
|
|
_inline void RMP_SetEnd( rmp_t *prmp )
|
|
{
|
|
prmp->fhitend = true;
|
|
}
|
|
|
|
// get next ramp value & update ramp, 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, 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;
|
|
|
|
// only update current height by a max of +1 or -1
|
|
// this means that for short ramp times, we may not hit target
|
|
if( ABS( y - prmp->yprev ) >= 1 )
|
|
prmp->yprev += prmp->sign;
|
|
|
|
return prmp->yprev;
|
|
}
|
|
|
|
// get current ramp value, don't update ramp
|
|
_inline int RMP_GetCurrent( rmp_t *prmp )
|
|
{
|
|
return prmp->yprev;
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
// Time Compress/expand with pitch shift
|
|
////////////////////////////////////////
|
|
|
|
// realtime pitch shift - ie: pitch shift without change to playback rate
|
|
|
|
#define CPTCS 64
|
|
|
|
typedef struct
|
|
{
|
|
qboolean 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
|
|
qboolean fdup; // true if duplicating, false if cutting
|
|
float fstep; // pitch shift & time compress/expand
|
|
} ptc_t;
|
|
|
|
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() { int i; for( i = 0; i < CPTCS; i++ ) PTC_Init( &ptcs[i] ); };
|
|
void PTC_FreeAll() { int i; for( 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 )
|
|
{
|
|
MsgDev( D_WARN, "DSP: 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 ( tslice, 1.1 * tcutdup);
|
|
|
|
// make sure xfade time smaller than cut/dup time, and smaller than (timeslice-cutdup) time
|
|
txfade = min( txfade, 0.9 * tcutdup );
|
|
txfade = min( 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 );
|
|
MsgDev( D_WARN, "DSP: 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);
|
|
|
|
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;
|
|
qboolean 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++;
|
|
}
|
|
break;
|
|
case OP_RIGHT:
|
|
while( count-- )
|
|
{
|
|
pb->right = PTC_GetNext( pptc, pb->right );
|
|
pb++;
|
|
}
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
while( count-- )
|
|
{
|
|
pb->left = pb->right = PTC_GetNext( pptc, pb->left );
|
|
pb++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
|
|
typedef struct
|
|
{
|
|
qboolean fused;
|
|
qboolean fhitend; // true if done
|
|
int ienv; // current ramp
|
|
rmp_t rmps[CENVRMPS]; // ramps
|
|
} env_t;
|
|
|
|
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() { int i; for( i = 0; i < CENVS; i++ ) ENV_Init( &envs[i] ); };
|
|
void ENV_FreeAll() { int i; for( 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 )
|
|
{
|
|
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 );
|
|
RMP_Init( &penv->rmps[1], decay, amp1, amp2 );
|
|
RMP_Init( &penv->rmps[2], sustain, amp2, amp3 );
|
|
RMP_Init( &penv->rmps[3], release, amp3, 0 );
|
|
|
|
penv->ienv = 0;
|
|
penv->fused = true;
|
|
penv->fhitend = false;
|
|
|
|
return penv;
|
|
}
|
|
}
|
|
|
|
MsgDev( D_WARN, "DSP: failed to allocate envelope.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
_inline int ENV_GetNext( env_t *penv, int x )
|
|
{
|
|
if( !penv->fhitend )
|
|
{
|
|
int i, 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
|
|
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++;
|
|
}
|
|
break;
|
|
case OP_RIGHT:
|
|
while( count-- )
|
|
{
|
|
pb->right = ENV_GetNext( penv, pb->right );
|
|
pb++;
|
|
}
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
while( count-- )
|
|
{
|
|
pb->left = pb->right = ENV_GetNext( penv, pb->left );
|
|
pb++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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_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_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.0f;
|
|
float decay = pprc->prm[env_idecay] / 1000.0f;
|
|
float sustain = pprc->prm[env_isustain] / 1000.0f;
|
|
float release = pprc->prm[env_irelease] / 1000.0f;
|
|
|
|
penv = ENV_Alloc( type, amp1, amp2, amp3, attack, decay, sustain, release );
|
|
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 )
|
|
{
|
|
}
|
|
|
|
////////////////////
|
|
// envelope follower
|
|
////////////////////
|
|
#define CEFOS 64 // max # of envelope followers active
|
|
|
|
#define CEFOBITS 6 // size 2^6 = 64
|
|
#define CEFOWINDOW (1 << (CEFOBITS)) // size of sample window
|
|
|
|
typedef struct
|
|
{
|
|
qboolean fused;
|
|
int avg; // accumulating average over sample window
|
|
int cavg; // count down
|
|
int xout; // current output value
|
|
|
|
} efo_t;
|
|
|
|
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() { int i; for( i = 0; i < CEFOS; i++ ) EFO_Init( &efos[i] ); };
|
|
void EFO_FreeAll() { int i; for( i = 0; i < CEFOS; i++ ) EFO_Free( &efos[i] ); };
|
|
|
|
// allocate enveloper follower
|
|
efo_t *EFO_Alloc( void )
|
|
{
|
|
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->cavg = CEFOWINDOW;
|
|
pefo->fused = true;
|
|
|
|
return pefo;
|
|
}
|
|
}
|
|
|
|
MsgDev( D_WARN, "DSP: failed to allocate envelope follower.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
|
|
_inline int EFO_GetNext( efo_t *pefo, int x )
|
|
{
|
|
int xa = ABS( x ); // rectify input wav
|
|
|
|
// get running sum / 2
|
|
pefo->avg += xa >> 1; // divide by 2 to prevent overflow
|
|
|
|
pefo->cavg--;
|
|
|
|
if( !pefo->cavg )
|
|
{
|
|
// new output value - end of window
|
|
|
|
// get average over window
|
|
pefo->xout = pefo->avg >> (CEFOBITS - 1); // divide by window size / 2
|
|
pefo->cavg = CEFOWINDOW;
|
|
pefo->avg = 0;
|
|
}
|
|
|
|
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++;
|
|
}
|
|
break;
|
|
case OP_RIGHT:
|
|
while( count-- )
|
|
{
|
|
pb->right = EFO_GetNext( pefo, pb->right );
|
|
pb++;
|
|
}
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
while( count-- )
|
|
{
|
|
pb->left = pb->right = EFO_GetNext( pefo, pb->left );
|
|
pb++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
efo_t * EFO_Params( prc_t *pprc )
|
|
{
|
|
return EFO_Alloc();
|
|
}
|
|
|
|
_inline void *EFO_VParams( void *p )
|
|
{
|
|
// PRC_CheckParams(( prc_t *)p, efo_rng ); - efo has no params
|
|
return (void *)EFO_Params((prc_t *)p );
|
|
}
|
|
|
|
_inline void EFO_Mod( void *p, float v )
|
|
{
|
|
}
|
|
|
|
//////////////
|
|
// 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)
|
|
|
|
typedef struct
|
|
{
|
|
qboolean fused;
|
|
qboolean fchanging; // true if modulating to new delay value
|
|
dly_t *pdly; // delay
|
|
int Dcur; // current delay value
|
|
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 xprev; // previous delay output, used to smooth transitions between delays
|
|
rmp_t rmp; // ramp
|
|
} mdy_t;
|
|
|
|
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() { int i; for( i = 0; i < CMDYS; i++ ) MDY_Init( &mdys[i] ); };
|
|
void MDY_FreeAll() { int i; for( i = 0; i < CMDYS; i++ ) MDY_Free( &mdys[i] ); };
|
|
|
|
|
|
// allocate mod delay, given previously allocated dly
|
|
// 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)
|
|
mdy_t *MDY_Alloc( dly_t *pdly, float ramptime, float modtime, float depth )
|
|
{
|
|
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 )
|
|
{
|
|
MsgDev( D_WARN, "DSP: failed to allocate delay for mod delay.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
pmdy->Dcur = pdly->D0;
|
|
pmdy->fused = true;
|
|
pmdy->ramptime = ramptime;
|
|
pmdy->mtime = SEC_TO_SAMPS( modtime );
|
|
pmdy->mtimecur = pmdy->mtime;
|
|
pmdy->depth = depth;
|
|
|
|
return pmdy;
|
|
}
|
|
}
|
|
|
|
MsgDev( D_WARN, "DSP: 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;
|
|
|
|
RMP_Init( &pmdy->rmp, pmdy->ramptime, pmdy->Dcur, t );
|
|
}
|
|
|
|
// get next value from modulating delay
|
|
int MDY_GetNext( mdy_t *pmdy, int x )
|
|
{
|
|
int xout;
|
|
int xcur;
|
|
|
|
// get current delay output
|
|
xcur = DLY_GetNext( pmdy->pdly, x );
|
|
|
|
// return right away if not modulating (not changing and not self modulating)
|
|
if( !pmdy->fchanging && !pmdy->mtime )
|
|
{
|
|
pmdy->xprev = xcur;
|
|
return xcur;
|
|
}
|
|
|
|
xout = xcur;
|
|
|
|
// if currently changing to new delay target, get next delay value
|
|
if( pmdy->fchanging )
|
|
{
|
|
// get next ramp value, test for done
|
|
int r = RMP_GetNext( &pmdy->rmp );
|
|
|
|
if( RMP_HitEnd( &pmdy->rmp ))
|
|
pmdy->fchanging = false;
|
|
|
|
// if new delay different from current delay, change delay
|
|
if( r != pmdy->Dcur )
|
|
{
|
|
// ramp never changes by more than + or - 1
|
|
|
|
// change delay tap value to r
|
|
DLY_ChangeVal( pmdy->pdly, r );
|
|
|
|
pmdy->Dcur = r;
|
|
|
|
// filter delay output within transitions.
|
|
// note: xprev = xcur = 0 if changing delay on 1st sample
|
|
xout = ( xcur + pmdy->xprev ) >> 1;
|
|
}
|
|
}
|
|
|
|
// if self-modulating and timer has expired, get next change
|
|
if( pmdy->mtime && !pmdy->mtimecur-- )
|
|
{
|
|
int D0 = pmdy->pdly->D0;
|
|
int Dnew;
|
|
float D1;
|
|
|
|
pmdy->mtimecur = pmdy->mtime;
|
|
|
|
// modulate between 0 and 100% of d0
|
|
D1 = (float)D0 * (1.0 - pmdy->depth);
|
|
Dnew = Com_RandomLong( (int)D1, D0 );
|
|
|
|
MDY_ChangeVal( pmdy, Dnew );
|
|
}
|
|
|
|
pmdy->xprev = xcur;
|
|
|
|
return xout;
|
|
}
|
|
|
|
// batch version for performance
|
|
_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_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
|
|
};
|
|
|
|
// 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 = 1.0 / pprc->prm[mdy_imodrate]; // time between modulations in seconds
|
|
float depth = pprc->prm[mdy_imoddepth]; // depth of modulations 0-1.0
|
|
|
|
// alloc plain, allpass or lowpass delay
|
|
pdly = DLY_Params( pprc );
|
|
if( !pdly ) return NULL;
|
|
|
|
pmdy = MDY_Alloc( pdly, ramptime, modtime, depth );
|
|
|
|
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 D = pmdy->Dcur;
|
|
float v2 = -(v + 1.0)/2.0; // v2 varies -1.0-0.0
|
|
|
|
// D varies 0..D
|
|
D = D + (int)((float)D * v2);
|
|
|
|
// change delay
|
|
MDY_ChangeVal( pmdy, D );
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////
|
|
// Chorus - lfo modulated delay
|
|
///////////////////////////////////////////
|
|
|
|
|
|
#define CCRSS 64 // max number chorus' active
|
|
|
|
typedef struct
|
|
{
|
|
qboolean fused;
|
|
mdy_t *pmdy; // modulatable delay
|
|
lfo_t *plfo; // modulating lfo
|
|
int lfoprev; // previous modulator value from lfo
|
|
int mix; // mix of clean & chorus signal - 0..PMAX
|
|
} crs_t;
|
|
|
|
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() { int i; for( i = 0; i < CCRSS; i++ ) CRS_Init( &crss[i] ); }
|
|
void CRS_FreeAll() { int i; for( 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, D;
|
|
crs_t *pcrs;
|
|
dly_t *pdly;
|
|
mdy_t *pmdy;
|
|
lfo_t *plfo;
|
|
float ramptime;
|
|
|
|
// find free chorus slot
|
|
for( i = 0; i < CCRSS; i++ )
|
|
{
|
|
if( !crss[i].fused )
|
|
break;
|
|
}
|
|
|
|
if( i == CCRSS )
|
|
{
|
|
MsgDev( D_WARN, "DSP: 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.0f; // # milliseconds to ramp between new values
|
|
|
|
pdly = DLY_Alloc( D, 0, 1, DLY_LINEAR );
|
|
pmdy = MDY_Alloc( pdly, ramptime, 0.0, 0.0 );
|
|
plfo = LFO_Alloc( lfotype, fHz, false );
|
|
|
|
if( !plfo || !pmdy )
|
|
{
|
|
LFO_Free( plfo );
|
|
MDY_Free( pmdy );
|
|
MsgDev( D_WARN, "DSP: failed to allocate lfo or mdy for chorus.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
pcrs->pmdy = pmdy;
|
|
pcrs->plfo = plfo;
|
|
pcrs->mix = (int)( PMAX * mix );
|
|
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, 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 * pcrs->mix) >> PBITS) + x;
|
|
}
|
|
|
|
// 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++;
|
|
}
|
|
break;
|
|
case OP_RIGHT:
|
|
while( count-- )
|
|
{
|
|
pb->right = CRS_GetNext( pcrs, pb->right );
|
|
pb++;
|
|
}
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
while( count-- )
|
|
{
|
|
pb->left = pb->right = CRS_GetNext( pcrs, pb->left );
|
|
pb++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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 )
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////
|
|
// amplifier - modulatable gain, distortion
|
|
////////////////////////////////////////////////////
|
|
|
|
#define CAMPS 64 // max number amps active
|
|
#define AMPSLEW 10 // milliseconds of slew time between gain changes
|
|
|
|
typedef struct
|
|
{
|
|
qboolean fused;
|
|
float gain; // amplification 0-6.0
|
|
float vthresh; // clip distortion threshold 0-1.0
|
|
float distmix; // 0-1.0 mix of distortion with clean
|
|
float vfeed; // 0-1.0 feedback with distortion;
|
|
float gaintarget; // new gain
|
|
float gaindif; // incrementer
|
|
} amp_t;
|
|
|
|
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() { int i; for( i = 0; i < CAMPS; i++ ) AMP_Init( &s[i] ); }
|
|
void AMP_FreeAll() { int i; for( i = 0; i < CAMPS; i++ ) AMP_Free( &s[i] ); }
|
|
|
|
amp_t *AMP_Alloc( float gain, float vthresh, float distmix, float vfeed )
|
|
{
|
|
int i;
|
|
amp_t *pamp;
|
|
|
|
// find free amp slot
|
|
for( i = 0; i < CAMPS; i++ )
|
|
{
|
|
if ( !amps[i].fused )
|
|
break;
|
|
}
|
|
|
|
if( i == CAMPS )
|
|
{
|
|
MsgDev( D_WARN, "DSP: failed to allocate amp.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
pamp = &s[i];
|
|
|
|
AMP_Init ( pamp );
|
|
|
|
pamp->gain = gain;
|
|
pamp->vthresh = vthresh;
|
|
pamp->distmix = distmix;
|
|
pamp->vfeed = vfeed;
|
|
|
|
return pamp;
|
|
}
|
|
|
|
// return next amplified sample
|
|
_inline int AMP_GetNext( amp_t *pamp, int x )
|
|
{
|
|
float y = (float)x;
|
|
float yin;
|
|
float gain = pamp->gain;
|
|
|
|
yin = y;
|
|
|
|
// slew between gains
|
|
if( gain != pamp->gaintarget )
|
|
{
|
|
float gaintarget = pamp->gaintarget;
|
|
float gaindif = pamp->gaindif;
|
|
|
|
if( gain > gaintarget )
|
|
{
|
|
gain -= gaindif;
|
|
if( gain <= gaintarget )
|
|
pamp->gaintarget = gain;
|
|
}
|
|
else
|
|
{
|
|
gain += gaindif;
|
|
if( gain >= gaintarget )
|
|
pamp->gaintarget = gain;
|
|
}
|
|
|
|
pamp->gain = gain;
|
|
}
|
|
|
|
// if distortion is on, add distortion, feedback
|
|
if( pamp->vthresh < 1.0 )
|
|
{
|
|
float fclip = pamp->vthresh * 32767.0;
|
|
|
|
if( pamp->vfeed > 0.0 )
|
|
{
|
|
// UNDONE: feedback
|
|
}
|
|
|
|
// clip distort
|
|
y = ( y > fclip ? fclip : ( y < -fclip ? -fclip : y));
|
|
|
|
// mix distorted with clean (1.0 = full distortion)
|
|
if( pamp->distmix > 0.0 )
|
|
y = y * pamp->distmix + yin * (1.0 - pamp->distmix);
|
|
}
|
|
|
|
// amplify
|
|
y *= gain;
|
|
|
|
return (int)y;
|
|
}
|
|
|
|
// 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++;
|
|
}
|
|
break;
|
|
case OP_RIGHT:
|
|
while( count-- )
|
|
{
|
|
pb->right = AMP_GetNext( pamp, pb->right );
|
|
pb++;
|
|
}
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
while( count-- )
|
|
{
|
|
pb->left = pb->right = AMP_GetNext( pamp, pb->left );
|
|
pb++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
_inline void AMP_Mod( amp_t *pamp, float v )
|
|
{
|
|
float vmod = bound( v, 0.0, 1.0 );
|
|
float samps = MSEC_TO_SAMPS( AMPSLEW ); // # samples to slew between amp values
|
|
|
|
// ramp to new amplification value
|
|
pamp->gaintarget = pamp->gain * vmod;
|
|
|
|
pamp->gaindif = fabs( pamp->gain - pamp->gaintarget ) / samps;
|
|
|
|
if( pamp->gaindif == 0.0f )
|
|
pamp->gaindif = fabs( pamp->gain - pamp->gaintarget ) / 100;
|
|
}
|
|
|
|
|
|
// parameter order
|
|
typedef enum
|
|
{
|
|
amp_gain,
|
|
amp_vthresh,
|
|
amp_distmix,
|
|
amp_vfeed,
|
|
amp_cparam
|
|
} amp_e;
|
|
|
|
|
|
// parameter ranges
|
|
prm_rng_t amp_rng[] =
|
|
{
|
|
{ amp_cparam, 0, 0 }, // first entry is # of parameters
|
|
{ amp_gain, 0.0, 10.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_t * AMP_Params( prc_t *pprc )
|
|
{
|
|
amp_t *pamp;
|
|
|
|
pamp = AMP_Alloc( pprc->prm[amp_gain], pprc->prm[amp_vthresh], pprc->prm[amp_distmix], pprc->prm[amp_vfeed] );
|
|
|
|
return pamp;
|
|
}
|
|
|
|
_inline void *AMP_VParams( void *p )
|
|
{
|
|
PRC_CheckParams((prc_t *)p, amp_rng );
|
|
return (void *)AMP_Params((prc_t *)p );
|
|
}
|
|
|
|
|
|
/////////////////
|
|
// NULL processor
|
|
/////////////////
|
|
typedef struct
|
|
{
|
|
int type;
|
|
} nul_t;
|
|
|
|
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
|
|
//////////////////////////
|
|
|
|
// A dsp processor (prc) performs a single-sample function, such as pitch shift, delay, reverb, filter
|
|
|
|
// note, this array must have CPRCPARMS entries
|
|
#define PRMZERO 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
|
|
#define PFNZERO NULL,NULL,NULL,NULL,NULL // zero pointers for pfnparam...pdata within prc_t
|
|
|
|
//////////////////
|
|
// NULL processor
|
|
/////////////////
|
|
|
|
#define PRC_NULL1 { PRC_NULL, PRMZERO, PFNZERO }
|
|
|
|
#define PRC0 PRC_NULL1
|
|
|
|
//////////////
|
|
// Amplifiers
|
|
//////////////
|
|
|
|
// {amp_gain, 0.0, 10.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
|
|
|
|
// prctype gain vthresh distmix vfeed
|
|
#define PRC_AMP1 {PRC_AMP, { 1.0, 1.0, 0.0, 0.0, }, PFNZERO } // modulatable unity gain amp
|
|
#define PRC_AMP2 {PRC_AMP, { 1.5, 0.75, 1.0, 0.0, }, PFNZERO } // amp with light distortion
|
|
#define PRC_AMP3 {PRC_AMP, { 2.0, 0.5, 1.0, 0.0, }, PFNZERO } // amp with medium distortion
|
|
#define PRC_AMP4 {PRC_AMP, { 4.0, 0.25, 1.0, 0.0, }, PFNZERO } // amp with heavy distortion
|
|
#define PRC_AMP5 {PRC_AMP, { 10.0, 0.10, 1.0, 0.0, }, PFNZERO } // mega distortion
|
|
|
|
#define PRC_AMP6 {PRC_AMP, { 0.1, 1.0, 0.0, 0.0, }, PFNZERO } // fade out
|
|
#define PRC_AMP7 {PRC_AMP, { 0.2, 1.0, 0.0, 0.0, }, PFNZERO } // fade out
|
|
#define PRC_AMP8 {PRC_AMP, { 0.3, 1.0, 0.0, 0.0, }, PFNZERO } // fade out
|
|
|
|
#define PRC_AMP9 {PRC_AMP, { 0.75, 1.0, 0.0, 0.0, }, PFNZERO } // duck out
|
|
|
|
|
|
///////////
|
|
// Filters
|
|
///////////
|
|
|
|
// ftype: filter type FLT_LP, FLT_HP, FLT_BP (UNDONE: FLT_BP currently ignored)
|
|
// cutoff: cutoff frequency in hz at -3db gain
|
|
// qwidth: width of BP, or steepness of LP/HP (ie: fcutoff + qwidth = -60db gain point)
|
|
// quality: QUA_LO, _MED, _HI 0,1,2
|
|
|
|
// prctype ftype cutoff qwidth quality
|
|
#define PRC_FLT1 {PRC_FLT, { FLT_LP, 3000, 1000, QUA_MED, }, PFNZERO }
|
|
#define PRC_FLT2 {PRC_FLT, { FLT_LP, 2000, 2000, QUA_MED, }, PFNZERO } // lowpass for facing away
|
|
#define PRC_FLT3 {PRC_FLT, { FLT_LP, 1000, 1000, QUA_MED, }, PFNZERO }
|
|
#define PRC_FLT4 {PRC_FLT, { FLT_LP, 700, 700, QUA_LO, }, PFNZERO } // muffle filter
|
|
|
|
#define PRC_FLT5 {PRC_FLT, { FLT_HP, 700, 200, QUA_MED, }, PFNZERO } // highpass (bandpass pair)
|
|
#define PRC_FLT6 {PRC_FLT, { FLT_HP, 2000, 1000, QUA_MED, }, PFNZERO } // lowpass (bandpass pair)
|
|
|
|
//////////
|
|
// Delays
|
|
//////////
|
|
|
|
// dtype: delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS
|
|
// delay: delay in milliseconds
|
|
// feedback: feedback 0-1.0
|
|
// gain: final gain of output stage, 0-1.0
|
|
|
|
// prctype dtype delay feedbk gain ftype cutoff qwidth quality
|
|
#define PRC_DLY1 {PRC_DLY, { DLY_PLAIN, 500.0, 0.5, 0.6, 0.0, 0.0, 0.0, 0.0, }, PFNZERO }
|
|
#define PRC_DLY2 {PRC_DLY, { DLY_LOWPASS, 45.0, 0.8, 0.6, FLT_LP, 3000, 3000, QUA_LO, }, PFNZERO }
|
|
#define PRC_DLY3 {PRC_DLY, { DLY_LOWPASS, 300.0, 0.5, 0.6, FLT_LP, 2000, 2000, QUA_LO, }, PFNZERO } // outside S
|
|
#define PRC_DLY4 {PRC_DLY, { DLY_LOWPASS, 400.0, 0.5, 0.6, FLT_LP, 1500, 1500, QUA_LO, }, PFNZERO } // outside M
|
|
#define PRC_DLY5 {PRC_DLY, { DLY_LOWPASS, 750.0, 0.5, 0.6, FLT_LP, 1000, 1000, QUA_LO, }, PFNZERO } // outside L
|
|
#define PRC_DLY6 {PRC_DLY, { DLY_LOWPASS, 1000.0, 0.5, 0.6, FLT_LP, 800, 400, QUA_LO, }, PFNZERO } // outside VL
|
|
#define PRC_DLY7 {PRC_DLY, { DLY_LOWPASS, 45.0, 0.4, 0.5, FLT_LP, 3000, 3000, QUA_LO, }, PFNZERO } // tunnel S
|
|
#define PRC_DLY8 {PRC_DLY, { DLY_LOWPASS, 55.0, 0.4, 0.5, FLT_LP, 3000, 3000, QUA_LO, }, PFNZERO } // tunnel M
|
|
#define PRC_DLY9 {PRC_DLY, { DLY_LOWPASS, 65.0, 0.4, 0.5, FLT_LP, 3000, 3000, QUA_LO, }, PFNZERO } // tunnel L
|
|
#define PRC_DLY10 {PRC_DLY, { DLY_LOWPASS, 150.0, 0.5, 0.6, FLT_LP, 3000, 3000, QUA_LO, }, PFNZERO } // cavern S
|
|
#define PRC_DLY11 {PRC_DLY, { DLY_LOWPASS, 200.0, 0.7, 0.6, FLT_LP, 3000, 3000, QUA_LO, }, PFNZERO } // cavern M
|
|
#define PRC_DLY12 {PRC_DLY, { DLY_LOWPASS, 300.0, 0.7, 0.6, FLT_LP, 3000, 3000, QUA_LO, }, PFNZERO } // cavern L
|
|
#define PRC_DLY13 {PRC_DLY, { DLY_LINEAR, 300.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,}, PFNZERO } // straight delay 300ms
|
|
#define PRC_DLY14 {PRC_DLY, { DLY_LINEAR, 80.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,}, PFNZERO } // straight delay 80ms
|
|
|
|
///////////
|
|
// Reverbs
|
|
///////////
|
|
|
|
// size: 0-2.0 scales nominal delay parameters (starting at approx 20ms)
|
|
// density: 0-2.0 density of reverbs (room shape) - controls # of parallel or series delays
|
|
// decay: 0-2.0 scales feedback parameters (starting at approx 0.15)
|
|
|
|
// prctype size density decay ftype cutoff qwidth fparallel
|
|
#define PRC_RVA1 {PRC_RVA, {2.0, 0.5, 1.5, FLT_LP, 6000, 2000, 1}, PFNZERO }
|
|
#define PRC_RVA2 {PRC_RVA, {1.0, 0.2, 1.5, 0, 0, 0, 0}, PFNZERO }
|
|
|
|
#define PRC_RVA3 {PRC_RVA, {0.8, 0.5, 1.5, FLT_LP, 2500, 2000, 0}, PFNZERO } // metallic S
|
|
#define PRC_RVA4 {PRC_RVA, {1.0, 0.5, 1.5, FLT_LP, 2500, 2000, 0}, PFNZERO } // metallic M
|
|
#define PRC_RVA5 {PRC_RVA, {1.2, 0.5, 1.5, FLT_LP, 2500, 2000, 0}, PFNZERO } // metallic L
|
|
|
|
#define PRC_RVA6 {PRC_RVA, {0.8, 0.3, 1.5, FLT_LP, 4000, 2000, 0}, PFNZERO } // tunnel S
|
|
#define PRC_RVA7 {PRC_RVA, {0.9, 0.3, 1.5, FLT_LP, 4000, 2000, 0}, PFNZERO } // tunnel M
|
|
#define PRC_RVA8 {PRC_RVA, {1.0, 0.3, 1.5, FLT_LP, 4000, 2000, 0}, PFNZERO } // tunnel L
|
|
|
|
#define PRC_RVA9 {PRC_RVA, {2.0, 1.5, 2.0, FLT_LP, 1500, 1500, 1}, PFNZERO } // cavern S
|
|
#define PRC_RVA10 {PRC_RVA, {2.0, 1.5, 2.0, FLT_LP, 1500, 1500, 1}, PFNZERO } // cavern M
|
|
#define PRC_RVA11 {PRC_RVA, {2.0, 1.5, 2.0, FLT_LP, 1500, 1500, 1}, PFNZERO } // cavern L
|
|
|
|
#define PRC_RVA12 {PRC_RVA, {2.0, 0.5, 1.5, FLT_LP, 6000, 2000, 1}, PFNZERO } // chamber S
|
|
#define PRC_RVA13 {PRC_RVA, {2.0, 1.0, 1.5, FLT_LP, 6000, 2000, 1}, PFNZERO } // chamber M
|
|
#define PRC_RVA14 {PRC_RVA, {2.0, 2.0, 1.5, FLT_LP, 6000, 2000, 1}, PFNZERO } // chamber L
|
|
|
|
#define PRC_RVA15 {PRC_RVA, {1.7, 1.0, 1.2, FLT_LP, 5000, 4000, 1}, PFNZERO } // brite S
|
|
#define PRC_RVA16 {PRC_RVA, {1.75, 1.0, 1.5, FLT_LP, 5000, 4000, 1}, PFNZERO } // brite M
|
|
#define PRC_RVA17 {PRC_RVA, {1.85, 1.0, 2.0, FLT_LP, 6000, 4000, 1}, PFNZERO } // brite L
|
|
|
|
#define PRC_RVA18 {PRC_RVA, {1.0, 1.5, 1.0, FLT_LP, 1000, 1000, 0}, PFNZERO } // generic
|
|
|
|
#define PRC_RVA19 {PRC_RVA, {1.9, 1.8, 1.25, FLT_LP, 4000, 2000, 1}, PFNZERO } // concrete S
|
|
#define PRC_RVA20 {PRC_RVA, {2.0, 1.8, 1.5, FLT_LP, 3500, 2000, 1}, PFNZERO } // concrete M
|
|
#define PRC_RVA21 {PRC_RVA, {2.0, 1.8, 1.75, FLT_LP, 3000, 2000, 1}, PFNZERO } // concrete L
|
|
|
|
#define PRC_RVA22 {PRC_RVA, {1.8, 1.5, 1.5, FLT_LP, 1000, 1000, 0}, PFNZERO } // water S
|
|
#define PRC_RVA23 {PRC_RVA, {1.9, 1.75, 1.5, FLT_LP, 1000, 1000, 0}, PFNZERO } // water M
|
|
#define PRC_RVA24 {PRC_RVA, {2.0, 2.0, 1.5, FLT_LP, 1000, 1000, 0}, PFNZERO } // water L
|
|
|
|
|
|
/////////////
|
|
// Diffusors
|
|
/////////////
|
|
|
|
// size: 0-1.0 scales all delays
|
|
// density: 0-1.0 controls # of series delays
|
|
// decay: 0-1.0 scales all feedback parameters
|
|
|
|
// prctype size density decay
|
|
#define PRC_DFR1 {PRC_DFR, { 1.0, 0.5, 1.0 }, PFNZERO }
|
|
#define PRC_DFR2 {PRC_DFR, { 0.5, 0.3, 0.5 }, PFNZERO } // S
|
|
#define PRC_DFR3 {PRC_DFR, { 0.75, 0.5, 0.75 }, PFNZERO } // M
|
|
#define PRC_DFR4 {PRC_DFR, { 1.0, 0.5, 1.0 }, PFNZERO } // L
|
|
#define PRC_DFR5 {PRC_DFR, { 1.0, 1.0, 1.0 }, PFNZERO } // VL
|
|
|
|
////////
|
|
// LFOs
|
|
////////
|
|
|
|
// wavtype: lfo type to use (LFO_SIN, LFO_RND...)
|
|
// rate: modulation rate in hz. for MDY, 1/rate = 'glide' time in seconds
|
|
// foneshot: 1.0 if lfo is oneshot
|
|
|
|
// prctype wavtype rate foneshot
|
|
#define PRC_LFO1 {PRC_LFO, { LFO_SIN, 440.0, 0.0, }, PFNZERO}
|
|
#define PRC_LFO2 {PRC_LFO, { LFO_SIN, 3000.0, 0.0, }, PFNZERO} // ear noise ring
|
|
#define PRC_LFO3 {PRC_LFO, { LFO_SIN, 4500.0, 0.0, }, PFNZERO} // ear noise ring
|
|
#define PRC_LFO4 {PRC_LFO, { LFO_SIN, 6000.0, 0.0, }, PFNZERO} // ear noise ring
|
|
#define PRC_LFO5 {PRC_LFO, { LFO_SAW, 100.0, 0.0, }, PFNZERO} // sub bass
|
|
|
|
/////////
|
|
// Pitch
|
|
/////////
|
|
|
|
// pitch: 0-n.0 where 1.0 = 1 octave up and 0.5 is one octave down
|
|
// timeslice: in milliseconds - size of sound chunk to analyze and cut/duplicate - 100ms nominal
|
|
// xfade: in milliseconds - size of crossfade region between spliced chunks - 20ms nominal
|
|
|
|
// prctype pitch timeslice xfade
|
|
#define PRC_PTC1 {PRC_PTC, { 1.1, 100.0, 20.0 }, PFNZERO} // pitch up 10%
|
|
#define PRC_PTC2 {PRC_PTC, { 0.9, 100.0, 20.0 }, PFNZERO} // pitch down 10%
|
|
#define PRC_PTC3 {PRC_PTC, { 0.95, 100.0, 20.0 }, PFNZERO} // pitch down 5%
|
|
#define PRC_PTC4 {PRC_PTC, { 1.01, 100.0, 20.0 }, PFNZERO} // pitch up 1%
|
|
#define PRC_PTC5 {PRC_PTC, { 0.5, 100.0, 20.0 }, PFNZERO} // pitch down 50%
|
|
|
|
/////////////
|
|
// Envelopes
|
|
/////////////
|
|
|
|
// etype: ENV_LINEAR, ENV_LOG - currently ignored
|
|
// amp1: attack peak amplitude 0-1.0
|
|
// amp2: decay target amplitued 0-1.0
|
|
// amp3: sustain target amplitude 0-1.0
|
|
// attack time in milliseconds
|
|
// envelope decay time in milliseconds
|
|
// sustain time in milliseconds
|
|
// release time in milliseconds
|
|
|
|
// prctype etype amp1 amp2 amp3 attack decay sustain release
|
|
#define PRC_ENV1 {PRC_ENV, {ENV_LIN, 1.0, 0.5, 0.4, 500, 500, 3000, 6000 }, PFNZERO}
|
|
|
|
|
|
//////////////
|
|
// Mod delays
|
|
//////////////
|
|
|
|
// dtype: delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS
|
|
// delay: delay in milliseconds
|
|
// feedback: feedback 0-1.0
|
|
// gain: final gain of output stage, 0-1.0
|
|
|
|
// modrate: frequency at which delay values change to new random value. 0 is no self-modulation
|
|
// moddepth: how much delay changes (decreases) from current value (0-1.0)
|
|
// modglide: glide time between dcur and dnew in milliseconds
|
|
|
|
// prctype dtype delay feedback gain ftype cutoff qwidth qual modrate moddepth modglide
|
|
#define PRC_MDY1 {PRC_MDY, {DLY_PLAIN, 500.0, 0.5, 1.0, 0, 0, 0, 0, 10, 0.8, 5,}, PFNZERO}
|
|
#define PRC_MDY2 {PRC_MDY, {DLY_PLAIN, 50.0, 0.8, 1.0, 0, 0, 0, 0, 5, 0.8, 5,}, PFNZERO}
|
|
|
|
#define PRC_MDY3 {PRC_MDY, {DLY_PLAIN, 300.0, 0.2, 1.0, 0, 0, 0, 0, 30, 0.01, 15,}, PFNZERO } // weird 1
|
|
#define PRC_MDY4 {PRC_MDY, {DLY_PLAIN, 400.0, 0.3, 1.0, 0, 0, 0, 0, 0.25, 0.01, 15,}, PFNZERO } // weird 2
|
|
#define PRC_MDY5 {PRC_MDY, {DLY_PLAIN, 500.0, 0.4, 1.0, 0, 0, 0, 0, 0.25, 0.01, 15,}, PFNZERO } // weird 3
|
|
|
|
//////////
|
|
// Chorus
|
|
//////////
|
|
|
|
// lfowav: lfotype is LFO_SIN, LFO_RND, LFO_TRI etc (LFO_RND for chorus, LFO_SIN for flange)
|
|
// rate: rate is modulation frequency in Hz
|
|
// depth: depth is modulation depth, 0-1.0
|
|
// mix: mix is mix of chorus and clean signal
|
|
|
|
// prctype lfowav rate depth mix
|
|
#define PRC_CRS1 {PRC_CRS, { LFO_SIN, 10, 1.0, 0.5, }, PFNZERO }
|
|
|
|
/////////////////////
|
|
// Envelope follower
|
|
/////////////////////
|
|
|
|
// takes no parameters
|
|
#define PRC_EFO1 {PRC_EFO, { PRMZERO }, PFNZERO }
|
|
|
|
// 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
|
|
|
|
qboolean 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;
|
|
qboolean fok = true;
|
|
|
|
// set up pointers to XXX_Free, XXX_GetNext and XXX_Params functions
|
|
|
|
for( i = 0; i < count; i++ )
|
|
{
|
|
switch (prcs[i].type)
|
|
{
|
|
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 = &_VParams;
|
|
pfnMod = &(prc_Mod_t)AMP_Mod;
|
|
break;
|
|
case PRC_NULL:
|
|
default:
|
|
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;
|
|
}
|
|
|
|
// set up function pointers
|
|
prcs[i].pfnParam = pfnParam;
|
|
prcs[i].pfnGetNext = pfnGetNext;
|
|
prcs[i].pfnGetNextN = pfnGetNextN;
|
|
prcs[i].pfnFree = pfnFree;
|
|
|
|
// 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 )
|
|
{
|
|
int i;
|
|
|
|
for( 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;
|
|
int i;
|
|
|
|
for( i = 0; i < cprm; i++)
|
|
{
|
|
// if parameter is 0.0f, always allow it (this is 'off' for most params)
|
|
if( pprc->prm[i] != 0.0f && ( pprc->prm[i] > prng[i+1].hi || pprc->prm[i] < prng[i+1].lo ))
|
|
{
|
|
MsgDev( D_WARN, "DSP: clamping out of range parameter.\n" );
|
|
pprc->prm[i] = bound( prng[i+1].lo, pprc->prm[i], 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_PARALLEL6 4
|
|
|
|
// x(n)-P(0)-->P(1)-->P(2)-->(+)-P(5)->y(n)
|
|
// | ^
|
|
// | |
|
|
// -->P(3)-->P(4)---->
|
|
|
|
|
|
#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)-->P(5)->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 6 // max # of processors per dsp preset
|
|
#define CPSET_STATES (CPSET_PRCS+3) // # of internal states
|
|
|
|
// NOTE: do not reorder members of pset_t - psettemplates relies on it!!!
|
|
typedef struct
|
|
{
|
|
int type; // preset configuration type
|
|
int cprcs; // number of processors for this preset
|
|
prc_t prcs[CPSET_PRCS]; // processor preset data
|
|
float gain; // preset gain 0.1->2.0
|
|
int w[CPSET_STATES]; // internal states
|
|
int fused;
|
|
} pset_t;
|
|
|
|
pset_t psets[CPSETS];
|
|
|
|
// array of dsp presets, each with up to 6 processors per preset
|
|
|
|
#define WZERO {0,0,0,0,0,0,0,0,0}, 0
|
|
|
|
pset_t psettemplates[] =
|
|
{
|
|
// presets 0-29 map to legacy room_type 0-29
|
|
|
|
// type # proc P0 P1 P2 P3 P4 P5 GAIN
|
|
{PSET_SIMPLE, 1, { PRC_NULL1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // OFF 0
|
|
{PSET_SIMPLE, 1, { PRC_RVA18, PRC0, PRC0, PRC0, PRC0, PRC0 },1.4, WZERO }, // GENERIC 1 // general, low reflective, diffuse room
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA3, PRC0, PRC0, PRC0, PRC0 },1.4, WZERO }, // METALIC_S 2 // highly reflective, parallel surfaces
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA4, PRC0, PRC0, PRC0, PRC0 },1.4, WZERO }, // METALIC_M 3
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA5, PRC0, PRC0, PRC0, PRC0 },1.4, WZERO }, // METALIC_L 4
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA6, PRC0, PRC0, PRC0, PRC0 },2.0, WZERO }, // TUNNEL_S 5 // resonant reflective, long surfaces
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA7, PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // TUNNEL_M 6
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA8, PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // TUNNEL_L 7
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA12,PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // CHAMBER_S 8 // diffuse, moderately reflective surfaces
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA13,PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // CHAMBER_M 9
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA14,PRC0, PRC0, PRC0, PRC0 },1.9, WZERO }, // CHAMBER_L 10
|
|
{PSET_SIMPLE, 1, { PRC_RVA15, PRC0, PRC0, PRC0, PRC0, PRC0 },1.5, WZERO }, // BRITE_S 11 // diffuse, highly reflective
|
|
{PSET_SIMPLE, 1, { PRC_RVA16, PRC0, PRC0, PRC0, PRC0, PRC0 },1.6, WZERO }, // BRITE_M 12
|
|
{PSET_SIMPLE, 1, { PRC_RVA17, PRC0, PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // BRITE_L 13
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA22,PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // WATER1 14 // underwater fx
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA23,PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // WATER2 15
|
|
{PSET_LINEAR, 3, { PRC_DFR1, PRC_RVA24,PRC_MDY5, PRC0, PRC0, PRC0 },1.8, WZERO }, // WATER3 16
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA19,PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // CONCRTE_S 17 // bare, reflective, parallel surfaces
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA20,PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // CONCRTE_M 18
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA21,PRC0, PRC0, PRC0, PRC0 },1.9, WZERO }, // CONCRTE_L 19
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_DLY3, PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // OUTSIDE1 20 // echoing, moderately reflective
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_DLY4, PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // OUTSIDE2 21 // echoing, dull
|
|
{PSET_LINEAR, 3, { PRC_DFR1, PRC_DFR1, PRC_DLY5, PRC0, PRC0, PRC0 },1.6, WZERO }, // OUTSIDE3 22 // echoing, very dull
|
|
{PSET_LINEAR, 2, { PRC_DLY10, PRC_RVA10,PRC0, PRC0, PRC0, PRC0 },2.8, WZERO }, // CAVERN_S 23 // large, echoing area
|
|
{PSET_LINEAR, 2, { PRC_DLY11, PRC_RVA10,PRC0, PRC0, PRC0, PRC0 },2.6, WZERO }, // CAVERN_M 24
|
|
{PSET_LINEAR, 3, { PRC_DFR1, PRC_DLY12,PRC_RVA11,PRC0, PRC0, PRC0 },2.6, WZERO }, // CAVERN_L 25
|
|
{PSET_LINEAR, 2, { PRC_DLY7, PRC_DFR1, PRC0, PRC0, PRC0, PRC0 },2.0, WZERO }, // WEIRDO1 26
|
|
{PSET_LINEAR, 2, { PRC_DLY8, PRC_DFR1, PRC0, PRC0, PRC0, PRC0 },1.9, WZERO }, // WEIRDO2 27
|
|
{PSET_LINEAR, 2, { PRC_DLY9, PRC_DFR1, PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // WEIRDO3 28
|
|
{PSET_LINEAR, 2, { PRC_DLY9, PRC_DFR1, PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // WEIRDO4 29
|
|
|
|
// presets 30-40 are new presets
|
|
{PSET_SIMPLE, 1, { PRC_FLT2, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 30 lowpass - facing away
|
|
{PSET_LINEAR, 2, { PRC_FLT3, PRC_DLY14,PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 31 lowpass - facing away+80ms delay
|
|
//{PSET_PARALLEL2,2, { PRC_AMP6, PRC_LFO2, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 32 explosion ring 1
|
|
//{PSET_PARALLEL2,2, { PRC_AMP7, PRC_LFO3, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 33 explosion ring 2
|
|
//{PSET_PARALLEL2,2, { PRC_AMP8, PRC_LFO4, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 34 explosion ring 3
|
|
{PSET_LINEAR, 3, { PRC_DFR1, PRC_DFR1, PRC_FLT3, PRC0, PRC0, PRC0 },0.25, WZERO }, // 32 explosion ring
|
|
{PSET_LINEAR, 3, { PRC_DFR1, PRC_DFR1, PRC_FLT3, PRC0, PRC0, PRC0 },0.25, WZERO }, // 33 explosion ring 2
|
|
{PSET_LINEAR, 3, { PRC_DFR1, PRC_DFR1, PRC_FLT3, PRC0, PRC0, PRC0 },0.25, WZERO }, // 34 explosion ring 3
|
|
{PSET_PARALLEL2,2, { PRC_DFR1, PRC_LFO2, PRC0, PRC0, PRC0, PRC0 },0.25, WZERO }, // 35 shock muffle 1
|
|
{PSET_PARALLEL2,2, { PRC_DFR1, PRC_LFO2, PRC0, PRC0, PRC0, PRC0 },0.25, WZERO }, // 36 shock muffle 2
|
|
{PSET_PARALLEL2,2, { PRC_DFR1, PRC_LFO2, PRC0, PRC0, PRC0, PRC0 },0.25, WZERO }, // 37 shock muffle 3
|
|
//{PSET_LINEAR, 3, { PRC_DFR1, PRC_LFO4, PRC_FLT3, PRC0, PRC0, PRC0 },1.0, WZERO }, // 35 shock muffle 1
|
|
//{PSET_LINEAR, 3, { PRC_DFR1, PRC_LFO4, PRC_FLT3, PRC0, PRC0, PRC0 },1.0, WZERO }, // 36 shock muffle 2
|
|
//{PSET_LINEAR, 3, { PRC_DFR1, PRC_LFO4, PRC_FLT3, PRC0, PRC0, PRC0 },1.0, WZERO }, // 37 shock muffle 3
|
|
{PSET_FEEDBACK3,3, { PRC_DLY13, PRC_PTC4, PRC_FLT2, PRC0, PRC0, PRC0 },0.25, WZERO }, // 38 fade pitchdown 1
|
|
{PSET_LINEAR, 3, { PRC_AMP3, PRC_FLT5, PRC_FLT6, PRC0, PRC0, PRC0 },2.0, WZERO }, // 39 distorted speaker 1
|
|
|
|
// fade out fade in
|
|
|
|
// presets 40+ are test presets
|
|
{PSET_SIMPLE, 1, { PRC_NULL1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 39 null
|
|
{PSET_SIMPLE, 1, { PRC_DLY1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 40 delay
|
|
{PSET_SIMPLE, 1, { PRC_RVA1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 41 parallel reverb
|
|
{PSET_SIMPLE, 1, { PRC_DFR1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 42 series diffusor
|
|
{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA1, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 43 diff & reverb
|
|
{PSET_SIMPLE, 1, { PRC_DLY2, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 44 lowpass delay
|
|
{PSET_SIMPLE, 1, { PRC_MDY2, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 45 modulating delay
|
|
{PSET_SIMPLE, 1, { PRC_PTC1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 46 pitch shift
|
|
{PSET_SIMPLE, 1, { PRC_PTC2, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 47 pitch shift
|
|
{PSET_SIMPLE, 1, { PRC_FLT1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 48 filter
|
|
{PSET_SIMPLE, 1, { PRC_CRS1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 49 chorus
|
|
{PSET_SIMPLE, 1, { PRC_ENV1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 50
|
|
{PSET_SIMPLE, 1, { PRC_LFO1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 51 lfo
|
|
{PSET_SIMPLE, 1, { PRC_EFO1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 52
|
|
{PSET_SIMPLE, 1, { PRC_MDY1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 53 modulating delay
|
|
{PSET_SIMPLE, 1, { PRC_FLT2, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 54 lowpass - facing away
|
|
{PSET_PARALLEL2, 2, { PRC_PTC2, PRC_PTC1, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 55 ptc1/ptc2
|
|
{PSET_FEEDBACK, 6, { PRC_DLY1, PRC0, PRC0, PRC_PTC1, PRC_FLT1, PRC0 },1.0, WZERO }, // 56 dly/ptc1
|
|
{PSET_MOD, 4, { PRC_EFO1, PRC0, PRC_PTC1, PRC0, PRC0, PRC0 },1.0, WZERO }, // 57 efo mod ptc
|
|
{PSET_LINEAR, 3, { PRC_DLY1, PRC_RVA1, PRC_CRS1, PRC0, PRC0, PRC0 },1.0, WZERO } // 58 dly/rvb/crs
|
|
};
|
|
|
|
|
|
// number of presets currently defined above
|
|
|
|
#define CPSETTEMPLATES 60 //(sizeof( psets ) / sizeof( pset_t ))
|
|
|
|
// 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 )
|
|
{
|
|
int i;
|
|
|
|
for( 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() { int i; for( 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;
|
|
qboolean fok;
|
|
int i;
|
|
|
|
// don't excede array bounds
|
|
if( ipsettemplate >= CPSETTEMPLATES )
|
|
ipsettemplate = 0;
|
|
|
|
// find free slot
|
|
for( i = 0; i < CPSETS; i++)
|
|
{
|
|
if( !psets[i].fused )
|
|
break;
|
|
}
|
|
|
|
if( i == CPSETS )
|
|
return NULL;
|
|
|
|
ppset = &psets[i];
|
|
|
|
// copy template into preset
|
|
*ppset = 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
|
|
MsgDev( D_ERROR, "Sound DSP: preset failed to init.\n");
|
|
PRC_FreeAll( ppset->prcs, ppset->cprcs );
|
|
return NULL;
|
|
}
|
|
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 *pbf, int SampleCount, int op )
|
|
{
|
|
prc_t *pprc;
|
|
int i, 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 );
|
|
break;
|
|
}
|
|
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( i = 0; i < count; i++ )
|
|
{
|
|
pprc->pfnGetNextN( pprc->pdata, pbf, SampleCount, op );
|
|
pprc++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 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 )
|
|
{
|
|
int *w = ppset->w;
|
|
prc_t *pprc;
|
|
int count = ppset->cprcs;
|
|
|
|
// initialized 0'th element of state array
|
|
|
|
w[0] = x;
|
|
|
|
switch( ppset->type )
|
|
{
|
|
default:
|
|
case PSET_SIMPLE:
|
|
{
|
|
// x(n)--->P(0)--->y(n)
|
|
return ppset->prcs[0].pfnGetNext (ppset->prcs[0].pdata, x);
|
|
}
|
|
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 processors in reverse order, from count to 1
|
|
|
|
// point to last processor
|
|
|
|
pprc = &ppset->prcs[count-1];
|
|
|
|
switch( count )
|
|
{
|
|
default:
|
|
case 5:
|
|
w[5] = pprc->pfnGetNext (pprc->pdata, w[4]);
|
|
pprc--;
|
|
case 4:
|
|
w[4] = pprc->pfnGetNext (pprc->pdata, w[3]);
|
|
pprc--;
|
|
case 3:
|
|
w[3] = pprc->pfnGetNext (pprc->pdata, w[2]);
|
|
pprc--;
|
|
case 2:
|
|
w[2] = pprc->pfnGetNext (pprc->pdata, w[1]);
|
|
pprc--;
|
|
case 1:
|
|
w[1] = pprc->pfnGetNext (pprc->pdata, w[0]);
|
|
}
|
|
return w[count];
|
|
}
|
|
|
|
case PSET_PARALLEL6:
|
|
{
|
|
// w0 w1 w2 w3 w6 w7
|
|
// x(n)-P(0)-->P(1)-->P(2)-->(+)---P(5)--->y(n)
|
|
// | ^
|
|
// | w4 w5 |
|
|
// -->P(3)-->P(4)---->
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
// start with all adders
|
|
|
|
w[6] = w[3] + w[5];
|
|
|
|
// top branch - evaluate in reverse order
|
|
|
|
w[7] = pprc[5].pfnGetNext( pprc[5].pdata, w[6] );
|
|
w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[2] );
|
|
w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[1] );
|
|
|
|
// bottom branch - evaluate in reverse order
|
|
|
|
w[5] = pprc[4].pfnGetNext( pprc[4].pdata, w[4] );
|
|
w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[1] );
|
|
|
|
w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
|
|
|
|
return w[7];
|
|
}
|
|
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[6] = pprc[4].pfnGetNext( pprc[4].pdata, w[5] );
|
|
|
|
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[6];
|
|
}
|
|
|
|
case PSET_FEEDBACK:
|
|
{
|
|
// w0 w1 w2 w3 w4 w7
|
|
// x(n)-P(0)--(+)-->P(1)-->P(2)-->P(5)->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[7] = pprc[5].pfnGetNext( pprc[5].pdata, w[4] );
|
|
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[7];
|
|
}
|
|
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 presets
|
|
|
|
convar_t *dsp_room; // room dsp preset - sounds more distant from player (1ch)
|
|
|
|
int ipset_room_prev;
|
|
|
|
// legacy room_type support
|
|
convar_t *dsp_room_type;
|
|
int ipset_room_typeprev;
|
|
|
|
|
|
// DSP processors
|
|
|
|
int idsp_room;
|
|
convar_t *dsp_stereo; // set to 1 for true stereo processing. 2x perf hit.
|
|
|
|
// DSP preset executor
|
|
#define CDSPS 32 // max number dsp executors active
|
|
#define DSPCHANMAX 4 // max number of channels dsp can process (allocs a separte processor for each chan)
|
|
|
|
typedef struct
|
|
{
|
|
qboolean fused;
|
|
int cchan; // 1-4 channels, ie: mono, FrontLeft, FrontRight, RearLeft, RearRight
|
|
pset_t *ppset[DSPCHANMAX]; // current preset (1-4 channels)
|
|
int ipset; // current ipreset
|
|
pset_t *ppsetprev[DSPCHANMAX]; // previous preset (1-4 channels)
|
|
int ipsetprev; // previous ipreset
|
|
float xfade; // crossfade time between previous preset and new
|
|
rmp_t xramp; // crossfade ramp
|
|
} dsp_t;
|
|
|
|
dsp_t dsps[CDSPS];
|
|
|
|
void DSP_Init( int idsp )
|
|
{
|
|
dsp_t *pdsp;
|
|
|
|
if( idsp < 0 || idsp > CDSPS )
|
|
return;
|
|
|
|
pdsp = &dsps[idsp];
|
|
Q_memset( pdsp, 0, sizeof( dsp_t ));
|
|
}
|
|
|
|
void DSP_Free( int idsp )
|
|
{
|
|
dsp_t *pdsp;
|
|
int i;
|
|
|
|
if( idsp < 0 || idsp > CDSPS )
|
|
return;
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
for( 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( void )
|
|
{
|
|
int idsp;
|
|
|
|
// 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( idsp = 0; idsp < CDSPS; idsp++ )
|
|
DSP_Init( idsp );
|
|
}
|
|
|
|
// free all resources associated with dsp - called once, during engine shutdown
|
|
|
|
void DSP_FreeAll( void )
|
|
{
|
|
int idsp;
|
|
|
|
// order is important, don't rearange.
|
|
for( 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 by DSP_CheckNewPreset()
|
|
// 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, idsp;
|
|
int cchans = bound( 1, cchan, 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;
|
|
|
|
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.0f;
|
|
|
|
RMP_SetEnd( &pdsp->xramp );
|
|
|
|
return idsp;
|
|
}
|
|
|
|
// return gain for current preset associated with dsp
|
|
// get crossfade to new gain if switching from previous preset (from preset crossfader value)
|
|
// Returns 1.0 gain if no preset (preset 0)
|
|
float DSP_GetGain( int idsp )
|
|
{
|
|
float gain_target = 0.0;
|
|
float gain_prev = 0.0;
|
|
float gain;
|
|
dsp_t *pdsp;
|
|
int r;
|
|
|
|
if( idsp < 0 || idsp > CDSPS )
|
|
return 1.0f;
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
// get current preset's gain
|
|
if( pdsp->ppset[0] )
|
|
gain_target = pdsp->ppset[0]->gain;
|
|
else gain_target = 1.0f;
|
|
|
|
// if not crossfading, return current preset gain
|
|
if( RMP_HitEnd( &pdsp->xramp ))
|
|
{
|
|
// return current preset's gain
|
|
return gain_target;
|
|
}
|
|
|
|
// get previous preset gain
|
|
|
|
if( pdsp->ppsetprev[0] )
|
|
gain_prev = pdsp->ppsetprev[0]->gain;
|
|
else gain_prev = 1.0;
|
|
|
|
// if current gain = target preset gain, return
|
|
if( gain_target == gain_prev )
|
|
{
|
|
if( gain_target == 0.0f )
|
|
return 1.0f;
|
|
return gain_target;
|
|
}
|
|
|
|
// get crossfade ramp value (updated elsewhere, when actually crossfading preset data)
|
|
r = RMP_GetCurrent( &pdsp->xramp );
|
|
|
|
// crossfade from previous to current preset gain
|
|
if( gain_target > gain_prev )
|
|
{
|
|
// ramping gain up - ramp up gain to target in last 10% of ramp
|
|
float rf = (float)r;
|
|
float pmax = (float)PMAX;
|
|
|
|
rf = rf / pmax; // rf 0->1.0
|
|
|
|
if( rf < 0.9 ) rf = 0.0;
|
|
else rf = (rf - 0.9) / (1.0 - 0.9); // 0->1.0 after rf > 0.9
|
|
|
|
// crossfade gain from prev to target over rf
|
|
gain = gain_prev + (gain_target - gain_prev) * rf;
|
|
|
|
return gain;
|
|
}
|
|
else
|
|
{
|
|
// ramping gain down - drop gain to target in first 10% of ramp
|
|
float rf = (float) r;
|
|
float pmax = (float)PMAX;
|
|
|
|
rf = rf / pmax; // rf 0.0->1.0
|
|
|
|
if( rf < 0.1 ) rf = (rf - 0.1) / (0.0 - 0.1); // 1.0->0.0 if rf < 0.1
|
|
else rf = 0.0;
|
|
|
|
// crossfade gain from prev to target over rf
|
|
gain = gain_prev + (gain_target - gain_prev) * (1.0 - rf);
|
|
|
|
return gain;
|
|
}
|
|
}
|
|
|
|
// 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 )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < pdsp->cchan; i++ )
|
|
{
|
|
if( pdsp->ppsetprev[i] )
|
|
{
|
|
PSET_Free( pdsp->ppsetprev[i] );
|
|
pdsp->ppsetprev[i] = NULL;
|
|
}
|
|
}
|
|
pdsp->ipsetprev = 0;
|
|
}
|
|
|
|
}
|
|
|
|
// 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];
|
|
int i;
|
|
|
|
ASSERT( idsp >= 0 && idsp < CDSPS );
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
// validate new preset range
|
|
if( ipsetnew >= 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( i = 0; i < pdsp->cchan; i++ )
|
|
{
|
|
ppsetnew[i] = PSET_Alloc( ipsetnew );
|
|
|
|
if( !ppsetnew[i] )
|
|
{
|
|
MsgDev( D_NOTE, "DSP preset failed to allocate.\n" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
ASSERT( pdsp );
|
|
|
|
// free PREVIOUS previous preset if not 0
|
|
DSP_FreePrevPreset( pdsp );
|
|
|
|
for( 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;
|
|
|
|
// clear ramp
|
|
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 );
|
|
|
|
RMP_Init( &pdsp->xramp, pdsp->xfade, 0, PMAX );
|
|
}
|
|
|
|
///////////////////////////////////////
|
|
// Helpers: called only from DSP_Process
|
|
///////////////////////////////////////
|
|
|
|
// return true if batch processing version of preset exists
|
|
_inline qboolean 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, int sampleCount, qboolean bcrossfading )
|
|
{
|
|
portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
|
|
int count = sampleCount;
|
|
int av, x;
|
|
|
|
if( !bcrossfading )
|
|
{
|
|
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 = -1;
|
|
int fl, flp;
|
|
int xf_fl;
|
|
|
|
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
|
|
xf_fl = XFADE( 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, int sampleCount, qboolean bcrossfading )
|
|
{
|
|
portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
|
|
int count = sampleCount;
|
|
int fl, fr;
|
|
|
|
if( !bcrossfading )
|
|
{
|
|
|
|
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, flp, frp;
|
|
int xf_fl, xf_fr;
|
|
|
|
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
|
|
xf_fl = XFADE( fl, flp, r ); // crossfade front left previous to front left
|
|
xf_fr = XFADE( fr, frp, r );
|
|
|
|
pbf->left = xf_fl; // crossfaded front left
|
|
pbf->right = xf_fr;
|
|
|
|
pbf++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DSP_ClearState( void )
|
|
{
|
|
if( !dsp_room ) return; // not init
|
|
|
|
Cvar_SetFloat( "dsp_room", 0.0f );
|
|
Cvar_SetFloat( "room_type", 0.0f );
|
|
|
|
CheckNewDspPresets();
|
|
|
|
// don't crossfade
|
|
dsps[0].xramp.fhitend = true;
|
|
}
|
|
|
|
// 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)
|
|
// 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, int sampleCount )
|
|
{
|
|
qboolean bcrossfading;
|
|
int cprocs; // output cannels (1, 2 or 4)
|
|
dsp_t *pdsp;
|
|
|
|
if( idsp < 0 || idsp >= CDSPS )
|
|
return;
|
|
|
|
ASSERT ( idsp < CDSPS ); // make sure idsp is valid
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
// if current and previous preset 0, return - preset 0 is 'off'
|
|
if( !pdsp->ipset && !pdsp->ipsetprev )
|
|
return;
|
|
|
|
ASSERT( pbfront );
|
|
|
|
// return right away if fx processing is turned off
|
|
if( dsp_off->integer )
|
|
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 );
|
|
|
|
cprocs = pdsp->cchan;
|
|
|
|
// 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( cprocs == 1 )
|
|
{
|
|
DSP_ProcessStereoToMono( pdsp, pbfront, sampleCount, bcrossfading );
|
|
return;
|
|
}
|
|
|
|
// DSP_Process stereo in to stereo out (if more than 2 procs, ignore them)
|
|
if( cprocs >= 2 )
|
|
{
|
|
DSP_ProcessStereoToStereo( pdsp, pbfront, sampleCount, bcrossfading );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// DSP helpers
|
|
|
|
// free all dsp processors
|
|
void FreeDsps( void )
|
|
{
|
|
DSP_Free( idsp_room );
|
|
idsp_room = 0;
|
|
|
|
DSP_FreeAll();
|
|
}
|
|
|
|
// alloc dsp processors
|
|
qboolean AllocDsps( void )
|
|
{
|
|
DSP_InitAll();
|
|
|
|
idsp_room = -1.0;
|
|
|
|
// initialize DSP cvars
|
|
dsp_room = Cvar_Get( "dsp_room", "0", 0, "room dsp preset - sounds more distant from player (1ch)" );
|
|
dsp_room_type = Cvar_Get( "room_type", "0", 0, "duplicate for dsp_room cvar for backward compatibility" );
|
|
dsp_stereo = Cvar_Get( "dsp_stereo", "0", 0, "set to 1 for true stereo processing. 2x perf hits" );
|
|
|
|
// alloc dsp room channel (mono, stereo if dsp_stereo is 1)
|
|
|
|
// dsp room is mono, 300ms fade time
|
|
idsp_room = DSP_Alloc( dsp_room->integer, 300, dsp_stereo->integer * 2 );
|
|
|
|
// init prev values
|
|
ipset_room_prev = dsp_room->integer;
|
|
ipset_room_typeprev = dsp_room_type->integer;
|
|
|
|
if( idsp_room < 0 )
|
|
{
|
|
MsgDev( D_WARN, "DSP processor failed to initialize! \n" );
|
|
|
|
FreeDsps();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// 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,
|
|
void CheckNewDspPresets( void )
|
|
{
|
|
int iroomtype = dsp_room_type->integer;
|
|
int iroom;
|
|
|
|
if( dsp_off->integer )
|
|
return;
|
|
|
|
if( s_listener.waterlevel > 2 )
|
|
iroom = 15;
|
|
else if( s_listener.inmenu )
|
|
iroom = 0;
|
|
else iroom = dsp_room->integer;
|
|
|
|
// legacy code support for "room_type" Cvar
|
|
if( iroomtype != ipset_room_typeprev )
|
|
{
|
|
// force dsp_room = room_type
|
|
ipset_room_typeprev = iroomtype;
|
|
Cvar_SetFloat( "dsp_room", iroomtype );
|
|
}
|
|
|
|
if( iroom != ipset_room_prev )
|
|
{
|
|
DSP_SetPreset( idsp_room, iroom );
|
|
ipset_room_prev = iroom;
|
|
|
|
// force room_type = dsp_room
|
|
Cvar_SetFloat( "room_type", iroom );
|
|
ipset_room_typeprev = iroom;
|
|
}
|
|
}
|