📄 snd_dsp.cpp
字号:
*i -= D + 1; // when *pi = D + 1, it wraps around to *pi = 0
if ( *i < 0 )
*i += D + 1; // when *pi = - 1, it wraps around to *pi = D
}
// set initial update value - fstep can have no more than 8 bits of integer and 20 bits of fract
// D is array max dimension w[0...D] (ie: size D+1)
// w is ptr to array
// p is ptr to pos_t to initialize
inline void POS_Init( pos_t *p, int D, float fstep )
{
float step = fstep;
// make sure int part of step is capped at fix20_intmax
if ((int)step > FIX20_INTMAX)
step = (step - (int)step) + FIX20_INTMAX;
p->step = FLOAT_TO_FIX20(step); // convert fstep to fixed point
p->cstep = 0;
p->pos = 0; // current update value
p->D = D; // always init to end value, in case we're stepping backwards
}
// change step value - this is an instantaneous change, not smoothed.
inline void POS_ChangeVal( pos_t *p, float fstepnew )
{
p->step = FLOAT_TO_FIX20( fstepnew ); // convert fstep to fixed point
}
// return current integer position, then update internal position value
inline int POS_GetNext ( pos_t *p )
{
//float f = FIX20_TO_FLOAT(p->cstep);
//int i1 = FIX20_INTPART(p->cstep);
//float f1 = FIX20_TO_FLOAT(FIX20_FRACPART(p->cstep));
//float f2 = FIX20_TO_FLOAT(p->step);
p->cstep += p->step; // update accumulated fraction step value (fixed point)
p->pos += FIX20_INTPART( p->cstep ); // update pos with integer part of accumulated step
p->cstep = FIX20_FRACPART( p->cstep ); // throw away the integer part of accumulated step
// wrap pos around either end of buffer if needed
POS_Wrap(p->D, &(p->pos));
// make sure returned position is within array bounds
Assert (p->pos <= p->D);
return p->pos;
}
// oneshot position within wav
struct pos_one_t
{
pos_t p; // pos_t
bool fhitend; // flag indicating we hit end of oneshot wav
};
// set initial update value - fstep can have no more than 8 bits of integer and 20 bits of fract
// one shot position - play only once, don't wrap, when hit end of buffer, return last position
inline void POS_ONE_Init( pos_one_t *p1, int D, float fstep )
{
POS_Init( &p1->p, D, fstep ) ;
p1->fhitend = false;
}
// return current integer position, then update internal position value
inline int POS_ONE_GetNext ( pos_one_t *p1 )
{
int pos;
pos_t *p0;
pos = p1->p.pos; // return current position
if (p1->fhitend)
return pos;
p0 = &(p1->p);
p0->cstep += p0->step; // update accumulated fraction step value (fixed point)
p0->pos += FIX20_INTPART( p0->cstep ); // update pos with integer part of accumulated step
//p0->cstep = SIGN(p0->cstep) * FIX20_FRACPART( p0->cstep );
p0->cstep = FIX20_FRACPART( p0->cstep ); // throw away the integer part of accumulated step
// if we wrapped, stop updating, always return last position
// if step value is 0, return hit end
if (!p0->step || p0->pos < 0 || p0->pos >= p0->D )
p1->fhitend = true;
else
pos = p0->pos;
// make sure returned value is within array bounds
Assert ( pos <= p0->D );
return pos;
}
/////////////////////
// Reverbs and delays
/////////////////////
#define CDLYS 128 // max delay lines active. Also used for lfos.
#define DLY_PLAIN 0 // single feedback loop
#define DLY_ALLPASS 1 // feedback and feedforward loop - flat frequency response (diffusor)
#define DLY_LOWPASS 2 // lowpass filter in feedback loop
#define DLY_LINEAR 3 // linear delay, no feedback, unity gain
#define DLY_MAX DLY_LINEAR
// delay line
struct dly_t
{
bool fused; // true if dly is in use
int type; // delay type
int D; // delay size, in samples
int t; // current tap, <= D
int 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 dlys[CDLYS]; // delay lines
void DLY_Init ( dly_t *pdly ) { if ( pdly ) Q_memset( pdly, 0, sizeof (dly_t)); }
void DLY_InitAll ( void ) { for (int i = 0 ; i < CDLYS; i++) DLY_Init ( &dlys[i] ); }
void DLY_Free ( dly_t *pdly )
{
// free memory buffer
if ( pdly )
{
FLT_Free ( pdly->pflt );
if ( pdly->w )
{
GlobalUnlock( pdly->h );
GlobalFree( pdly->h );
}
// free dly slot
Q_memset ( pdly, 0, sizeof (dly_t) );
}
}
void DLY_FreeAll ( void ) { for (int i = 0; i < CDLYS; i++ ) DLY_Free ( &dlys[i] ); }
// 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 )
{
DevMsg ("DSP: Warning, 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 )
{
DevMsg ("DSP: Warning, failed to allocate filter for delay line.\n" );
return NULL;
}
}
// alloc delay memory
h = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, cb);
if (!h)
{
g_pSoundServices->ConSafePrint ("Sound DSP: Out of memory.\n");
FLT_Free ( pdly->pflt );
return NULL;
}
// lock delay memory
w = (int *)GlobalLock(h);
if ( !w )
{
g_pSoundServices->ConSafePrint ("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 ( (int) 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;
prcf.prm[flt_iquality] = qual; // 0,1,2 - high, medium, low (low quality implies faster execution time)
prcf.prm[flt_icutoff] = cutoff;
prcf.prm[flt_iftype] = ftype;
prcf.prm[flt_iqwidth] = qwidth;
flt_t *pflt = (flt_t *)FLT_Params ( &prcf );
if ( !pflt )
{
DevMsg ("DSP: Warning, failed to allocate filter.\n" );
return NULL;
}
pdly = DLY_AllocLP ( D, a, b, type, pflt->M, pflt->L, pflt->a, pflt->b );
FLT_Free ( pflt );
break;
}
}
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++;
}
return;
case OP_RIGHT:
while (count--)
{
pb->right = DLY_GetNext( pdly, pb->right );
pb++;
}
return;
case OP_LEFT_DUPLICATE:
while (count--)
{
pb->left = pb->right = DLY_GetNext( pdly, pb->left );
pb++;
}
return;
}
}
// get tap on t'th sample in delay - don't update buffer pointers, this is done via DLY_GetNext
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 ) { return; }
///////////////////
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -