You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
12358 lines
336 KiB
12358 lines
336 KiB
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//
|
|
//=============================================================================//
|
|
// snd_dsp.c -- audio processing routines
|
|
|
|
|
|
#include "audio_pch.h"
|
|
|
|
#include "iprediction.h"
|
|
#include "../../common.h" // for parsing routines
|
|
#include "vstdlib/random.h"
|
|
#include "tier0/cache_hints.h"
|
|
#include "sound.h"
|
|
#include "client.h"
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
#define SIGN(d) ((d)<0?-1:1)
|
|
|
|
#define ABS(a) abs(a)
|
|
|
|
#define MSEC_TO_SAMPS(a) (((a)*SOUND_DMA_SPEED) / 1000) // convert milliseconds to # samples in equivalent time
|
|
#define SEC_TO_SAMPS(a) ((a)*SOUND_DMA_SPEED) // convert seconds to # samples in equivalent time
|
|
#define SAMPS_TO_SEC(a) ((a)/SOUND_DMA_SPEED) // convert seconds to # samples in equivalent time
|
|
|
|
#define CLIP_DSP(x) (x)
|
|
|
|
extern ConVar das_debug;
|
|
|
|
#define SOUND_MS_PER_FT 1 // sound travels approx 1 foot per millisecond
|
|
#define ROOM_MAX_SIZE 1000 // max size in feet of room simulation for dsp
|
|
|
|
void DSP_ReleaseMemory( void );
|
|
bool DSP_LoadPresetFile( void );
|
|
|
|
extern float Gain_To_dB ( float gain );
|
|
extern float dB_To_Gain ( float dB );
|
|
extern float Gain_To_Amplitude ( float gain );
|
|
extern float Amplitude_To_Gain ( float amplitude );
|
|
|
|
extern bool g_bdas_room_init;
|
|
extern bool g_bdas_init_nodes;
|
|
|
|
ConVar snd_dsp_optimization( "snd_dsp_optimization", "0", FCVAR_NONE, "Turns optimization on for DSP effects if set to 1 (default). 0 to turn the optimization off." );
|
|
ConVar snd_dsp_spew_changes( "snd_dsp_spew_changes", "0", FCVAR_NONE, "Spews major changes to the dsp or presets if set to 1. 0 to turn the spew off (default)." );
|
|
ConVar snd_dsp_cancel_old_preset_after_N_milliseconds( "snd_dsp_cancel_old_preset_after_N_milliseconds", "1000", FCVAR_NONE, "Number of milliseconds after an unused previous preset is not considered valid for the start of a cross-fade.");
|
|
ConVar snd_spew_dsp_process( "snd_spew_dsp_process", "0", FCVAR_NONE, "Spews text every time a DSP effect is applied if set to 1. 0 to turn the spew off (default)." );
|
|
|
|
ConVar snd_dsp_test1( "snd_dsp_test1", "1.0", FCVAR_NONE );
|
|
ConVar snd_dsp_test2( "snd_dsp_test2", "1.0", FCVAR_NONE );
|
|
|
|
// Use short to save some memory and L2 cache misses on X360 and PS3. Except in some cases, it does not seem that we need to use int for the samples.
|
|
// For the moment we enable it on all platforms to detect issues earlier, but later we may only use it for PS3.
|
|
typedef int LocalOutputSample_t;
|
|
typedef int CircularBufferSample_t;
|
|
|
|
//===============================================================================
|
|
//
|
|
// Digital Signal Processing algorithms for audio FX.
|
|
//
|
|
// KellyB 2/18/03
|
|
//===============================================================================
|
|
|
|
// Performance notes:
|
|
|
|
// DSP processing should take no more than 3ms total time per frame to remain on par with hl1
|
|
// Assume a min frame rate of 24fps = 42ms per frame
|
|
// at 24fps, to maintain 44.1khz output rate, we must process about 1840 mono samples per frame.
|
|
// So we must process 1840 samples in 3ms.
|
|
|
|
// on a 1Ghz CPU (mid-low end CPU) 3ms provides roughly 3,000,000 cycles.
|
|
// Thus we have 3e6 / 1840 = 1630 cycles per sample.
|
|
|
|
#define PBITS 12 // parameter bits
|
|
#define PMAX ((1 << PBITS)) // parameter max
|
|
|
|
// crossfade from y2 to y1 at point r (0 < r < PMAX)
|
|
|
|
#define XFADE(y1,y2,r) ((y2) + ( ( ((y1) - (y2)) * (r) ) >> PBITS) )
|
|
|
|
// exponential crossfade from y2 to y1 at point r (0 < r < PMAX)
|
|
|
|
#define XFADE_EXP(y1, y2, r) ((y2) + ((((((y1) - (y2)) * (r) ) >> PBITS) * (r)) >> PBITS) )
|
|
|
|
// In debug, we are going to compare the old code and the new code and make sure we get the exact same output
|
|
// As it has to clone all the filters, delays, modulated delays, rvas, samples, etc... The code is also initially checking
|
|
// that the clone works correctly with the original algorithm (so we separate issues related to incorrect cloning from incorrect optimization).
|
|
#if _DEBUG
|
|
#define CHECK_VALUES_AFTER_REFACTORING 1
|
|
#else
|
|
#define CHECK_VALUES_AFTER_REFACTORING 0
|
|
#endif
|
|
|
|
const int SAMPLES_BEFORE = 64;
|
|
const int SAMPLES_AFTER = 64;
|
|
const unsigned char FILL_PATTERN = 0xA5;
|
|
|
|
// To make sure we have the same results, duplicate everything
|
|
// Add some markers before and after to detect if we write outside of the range.
|
|
portable_samplepair_t * DuplicateSamplePairs(portable_samplepair_t * pInputBuffer, int nSampleCount)
|
|
{
|
|
if ( nSampleCount == 1 )
|
|
{
|
|
// There is a bug in some PC Asm code where it assumes that we have at least 2 samples
|
|
// Set the value to 2, so the FILL_PATTERN is handled correctly.
|
|
nSampleCount = 2;
|
|
}
|
|
|
|
portable_samplepair_t * pSamePairs = (portable_samplepair_t *)MemAlloc_AllocAligned( sizeof( portable_samplepair_t ) * ( SAMPLES_BEFORE + nSampleCount + SAMPLES_AFTER ), 16 );
|
|
// Because we are allocating a big size, we have a 16 bytes alignment on allocation
|
|
Assert( ( (intp)pSamePairs & 0xf ) == 0 );
|
|
memset( pSamePairs, FILL_PATTERN, SAMPLES_BEFORE * sizeof( portable_samplepair_t ) );
|
|
pSamePairs += SAMPLES_BEFORE;
|
|
memcpy( pSamePairs, pInputBuffer, nSampleCount * sizeof( portable_samplepair_t ) );
|
|
memset( pSamePairs + nSampleCount, FILL_PATTERN, SAMPLES_AFTER * sizeof( portable_samplepair_t ) );
|
|
return pSamePairs;
|
|
}
|
|
|
|
void FreeDuplicatedSamplePairs( portable_samplepair_t * pInputBuffer, int nSampleCount )
|
|
{
|
|
Assert( ( (intp)pInputBuffer & 0xf ) == 0 );
|
|
|
|
if ( nSampleCount == 1 )
|
|
{
|
|
// There is a bug in some PC Asm code where it assumes that we have at least 2 samples
|
|
// Set the value to 2, so the FILL_PATTERN is handled correctly.
|
|
nSampleCount = 2;
|
|
}
|
|
|
|
unsigned char * pAfterBuffer;
|
|
pAfterBuffer = (unsigned char *)( pInputBuffer + nSampleCount );
|
|
const int nAfterSize = SAMPLES_AFTER * sizeof( portable_samplepair_t );
|
|
for ( int i = 0 ; i < nAfterSize ; ++i )
|
|
{
|
|
Assert( pAfterBuffer[i] == FILL_PATTERN );
|
|
}
|
|
pInputBuffer -= SAMPLES_BEFORE;
|
|
const int nBeforeSize = SAMPLES_BEFORE * sizeof( portable_samplepair_t );
|
|
unsigned char * pBeforeBuffer;
|
|
pBeforeBuffer = (unsigned char *)( pInputBuffer );
|
|
for ( int i = 0 ; i < nBeforeSize ; ++i )
|
|
{
|
|
Assert( pBeforeBuffer[i] == FILL_PATTERN );
|
|
}
|
|
MemAlloc_FreeAligned( pInputBuffer );
|
|
}
|
|
|
|
const char * GetIndentationText( int nIndentation )
|
|
{
|
|
const int MAX_INDENTATION_SIZE = 32;
|
|
static char sIndentationBuffer[MAX_INDENTATION_SIZE + 1];
|
|
static bool sFirstTime = true;
|
|
if ( sFirstTime )
|
|
{
|
|
memset( sIndentationBuffer, '\t', MAX_INDENTATION_SIZE );
|
|
sIndentationBuffer[MAX_INDENTATION_SIZE] = '\0';
|
|
sFirstTime = false;
|
|
}
|
|
|
|
if ( nIndentation > MAX_INDENTATION_SIZE )
|
|
{
|
|
nIndentation = MAX_INDENTATION_SIZE;
|
|
}
|
|
// nIndentation corresponds to the number of tabs returned in the string
|
|
return sIndentationBuffer + MAX_INDENTATION_SIZE - nIndentation;
|
|
}
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
void LocalRandomSeed( )
|
|
{
|
|
// Do nothing...
|
|
}
|
|
|
|
// For the comparison test always return the same value... (some filters are using RandomInt() in some cases making the comparison impossible).
|
|
// Setting the seed before the test is not enough as another thread could call Random() and we would still have an indeterministic result.
|
|
// TODO: Improve this by creating a proper random stream...
|
|
int LocalRandomInt( int min, int max )
|
|
{
|
|
return ( min + max ) / 2;
|
|
}
|
|
|
|
#else
|
|
|
|
inline
|
|
int LocalRandomInt( int min, int max )
|
|
{
|
|
return RandomInt( min, max );
|
|
}
|
|
#endif
|
|
|
|
template <typename T>
|
|
bool CheckPointers( T * p1, T * p2 )
|
|
{
|
|
if ( p1 != NULL )
|
|
{
|
|
Assert( p2 != NULL );
|
|
// When pointers are not NULL, we want to make sure that they don't point to the same object (after all they are completely cloned).
|
|
Assert( p1 != p2 );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Assert( p2 == NULL );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/////////////////////
|
|
// dsp helpers
|
|
/////////////////////
|
|
|
|
// reverse delay pointer
|
|
|
|
inline void DlyPtrReverse (int dlysize, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp)
|
|
{
|
|
// when *ppsamp = psamps - 1, it wraps around to *ppsamp = psamps + dlysize
|
|
|
|
if ( *ppsamp < psamps )
|
|
*ppsamp += dlysize + 1;
|
|
}
|
|
|
|
// advance delay pointer
|
|
|
|
inline void DlyPtrForward (int dlysize, int *psamps, int **ppsamp)
|
|
{
|
|
// when *ppsamp = psamps + dlysize + 1, it wraps around to *ppsamp = psamps
|
|
|
|
if ( *ppsamp > psamps + dlysize )
|
|
*ppsamp -= dlysize + 1;
|
|
}
|
|
|
|
// Infinite Impulse Response (feedback) filter, cannonical form
|
|
|
|
// returns single sample 'out' for current input value 'in'
|
|
// in: input sample
|
|
// psamp: internal state array, dimension max(cdenom,cnumer) + 1
|
|
// cnumer,cdenom: numerator and denominator filter orders
|
|
// denom,numer: cdenom+1 dimensional arrays of filter params
|
|
//
|
|
// for cdenom = 4:
|
|
//
|
|
// 1 psamp0(n) numer0
|
|
// in(n)--->(+)--(*)---.------(*)---->(+)---> out(n)
|
|
// ^ | ^
|
|
// | [Delay d] |
|
|
// | | |
|
|
// | -denom1 |psamp1 numer1 |
|
|
// ----(*)---.------(*)-------
|
|
// ^ | ^
|
|
// | [Delay d] |
|
|
// | | |
|
|
// | -denom2 |psamp2 numer2 |
|
|
// ----(*)---.------(*)-------
|
|
// ^ | ^
|
|
// | [Delay d] |
|
|
// | | |
|
|
// | -denom3 |psamp3 numer3 |
|
|
// ----(*)---.------(*)-------
|
|
// ^ | ^
|
|
// | [Delay d] |
|
|
// | | |
|
|
// | -denom4 |psamp4 numer4 |
|
|
// ----(*)---.------(*)-------
|
|
//
|
|
// for each input sample in:
|
|
// psamp0 = in - denom1*psamp1 - denom2*psamp2 - ...
|
|
// out = numer0*psamp0 + numer1*psamp1 + ...
|
|
// psampi = psampi-1, i = cmax, cmax-1, ..., 1
|
|
|
|
inline int IIRFilter_Update_OrderN ( int cdenom, int *denom, int cnumer, int *numer, int *psamp, int in )
|
|
{
|
|
int cmax, i;
|
|
int out;
|
|
int in0;
|
|
|
|
out = 0;
|
|
in0 = in;
|
|
|
|
cmax = MAX ( cdenom, cnumer );
|
|
|
|
// add input values
|
|
|
|
// for (i = 1; i <= cdenom; i++)
|
|
// psamp[0] -= ( denom[i] * psamp[i] ) >> PBITS;
|
|
|
|
switch (cdenom)
|
|
{
|
|
case 12: in0 -= ( denom[12] * psamp[12] ) >> PBITS;
|
|
case 11: in0 -= ( denom[11] * psamp[11] ) >> PBITS;
|
|
case 10: in0 -= ( denom[10] * psamp[10] ) >> PBITS;
|
|
case 9: in0 -= ( denom[9] * psamp[9] ) >> PBITS;
|
|
case 8: in0 -= ( denom[8] * psamp[8] ) >> PBITS;
|
|
case 7: in0 -= ( denom[7] * psamp[7] ) >> PBITS;
|
|
case 6: in0 -= ( denom[6] * psamp[6] ) >> PBITS;
|
|
case 5: in0 -= ( denom[5] * psamp[5] ) >> PBITS;
|
|
case 4: in0 -= ( denom[4] * psamp[4] ) >> PBITS;
|
|
case 3: in0 -= ( denom[3] * psamp[3] ) >> PBITS;
|
|
case 2: in0 -= ( denom[2] * psamp[2] ) >> PBITS;
|
|
default:
|
|
case 1: in0 -= ( denom[1] * psamp[1] ) >> PBITS;
|
|
}
|
|
|
|
psamp[0] = in0;
|
|
|
|
// add output values
|
|
|
|
//for (i = 0; i <= cnumer; i++)
|
|
// out += ( numer[i] * psamp[i] ) >> PBITS;
|
|
|
|
switch (cnumer)
|
|
{
|
|
case 12: out += ( numer[12] * psamp[12] ) >> PBITS;
|
|
case 11: out += ( numer[11] * psamp[11] ) >> PBITS;
|
|
case 10: out += ( numer[10] * psamp[10] ) >> PBITS;
|
|
case 9: out += ( numer[9] * psamp[9] ) >> PBITS;
|
|
case 8: out += ( numer[8] * psamp[8] ) >> PBITS;
|
|
case 7: out += ( numer[7] * psamp[7] ) >> PBITS;
|
|
case 6: out += ( numer[6] * psamp[6] ) >> PBITS;
|
|
case 5: out += ( numer[5] * psamp[5] ) >> PBITS;
|
|
case 4: out += ( numer[4] * psamp[4] ) >> PBITS;
|
|
case 3: out += ( numer[3] * psamp[3] ) >> PBITS;
|
|
case 2: out += ( numer[2] * psamp[2] ) >> PBITS;
|
|
default:
|
|
case 1: out += ( numer[1] * psamp[1] ) >> PBITS;
|
|
case 0: out += ( numer[0] * psamp[0] ) >> PBITS;
|
|
}
|
|
|
|
// update internal state (reverse order)
|
|
|
|
for (i = cmax; i >= 1; i--)
|
|
psamp[i] = psamp[i-1];
|
|
|
|
// return current output sample
|
|
|
|
return out;
|
|
}
|
|
|
|
// 1st order filter - faster version
|
|
|
|
inline int IIRFilter_Update_Order1 ( int *denom, int cnumer, int *numer, int *psamp, int in )
|
|
{
|
|
int out;
|
|
|
|
if (!psamp[0] && !psamp[1] && !in)
|
|
return 0;
|
|
|
|
psamp[0] = in - (( denom[1] * psamp[1] ) >> PBITS);
|
|
|
|
out = ( ( numer[1] * psamp[1] ) + ( numer[0] * psamp[0] ) ) >> PBITS;
|
|
|
|
psamp[1] = psamp[0];
|
|
|
|
return out;
|
|
}
|
|
|
|
// return 'tdelay' delayed sample from delay buffer
|
|
// dlysize: delay samples
|
|
// psamps: head of delay buffer psamps[0...dlysize]
|
|
// psamp: current data pointer
|
|
// sdly: 0...dlysize
|
|
|
|
inline int GetDly ( int dlysize, CircularBufferSample_t *psamps, CircularBufferSample_t *psamp, int tdelay )
|
|
{
|
|
CircularBufferSample_t *pout;
|
|
|
|
pout = psamp + tdelay;
|
|
|
|
if ( pout <= (psamps + dlysize))
|
|
return *pout;
|
|
else
|
|
return *(pout - dlysize - 1);
|
|
}
|
|
|
|
// update the delay buffer pointer
|
|
// dlysize: delay samples
|
|
// psamps: head of delay buffer psamps[0...dlysize]
|
|
// ppsamp: data pointer
|
|
|
|
inline void DlyUpdate ( int dlysize, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp )
|
|
{
|
|
// decrement pointer and fix up on buffer boundary
|
|
|
|
// when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize
|
|
|
|
(*ppsamp)--;
|
|
DlyPtrReverse ( dlysize, psamps, ppsamp );
|
|
}
|
|
|
|
// simple delay with feedback, no filter in feedback line.
|
|
// delaysize: delay line size in samples
|
|
// tdelay: tap from this location - <= delaysize
|
|
// psamps: delay line buffer pointer of dimension delaysize+1
|
|
// ppsamp: circular pointer, must be init to &psamps[0] before first call
|
|
// fbgain: feedback value, 0-PMAX (normalized to 0.0-1.0)
|
|
// outgain: gain
|
|
// in: input sample
|
|
|
|
// psamps0(n) outgain
|
|
// in(n)--->(+)--------.-----(*)-> out(n)
|
|
// ^ |
|
|
// | [Delay d]
|
|
// | |
|
|
// | fbgain |Wd(n)
|
|
// ----(*)---.
|
|
|
|
inline int ReverbSimple ( int delaysize, int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int in )
|
|
{
|
|
int out, sD;
|
|
|
|
// get current delay output
|
|
|
|
sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
|
|
|
|
// calculate output + delay * gain
|
|
|
|
out = in + (( fbgain * sD ) >> PBITS);
|
|
|
|
// write to delay
|
|
|
|
**ppsamp = out;
|
|
|
|
// advance internal delay pointers
|
|
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
return ( (out * outgain) >> PBITS );
|
|
}
|
|
|
|
inline void ReverbSimple_Opt ( int delaysize, int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int * pIn, LocalOutputSample_t * pOut, int nCount )
|
|
{
|
|
while ( nCount-- > 0 )
|
|
{
|
|
*pOut++ += ReverbSimple( delaysize, tdelay, psamps, ppsamp, fbgain, outgain, *pIn );
|
|
pIn += 2; // Because pIn has both left AND right (but we read only one).
|
|
}
|
|
}
|
|
|
|
|
|
inline int ReverbSimple_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int in )
|
|
{
|
|
int out, sD;
|
|
int sDnew;
|
|
|
|
// crossfade from tdelay to tdelaynew samples. xfade is 0..PMAX
|
|
|
|
sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
|
|
sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
|
|
sD = sD + (((sDnew - sD) * xf) >> PBITS);
|
|
|
|
out = in + (( fbgain * sD ) >> PBITS);
|
|
**ppsamp = out;
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
return ( (out * outgain) >> PBITS );
|
|
}
|
|
|
|
// multitap simple reverb
|
|
|
|
// NOTE: tdelay3 > tdelay2 > tdelay1 > t0
|
|
// NOTE: fbgain * 4 < 1!
|
|
|
|
inline int ReverbSimple_multitap ( int delaysize, int tdelay0, int tdelay1, int tdelay2, int tdelay3, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int in )
|
|
{
|
|
int s1, s2, s3, s4, sum;
|
|
|
|
s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 );
|
|
s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 );
|
|
s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 );
|
|
s4 = GetDly ( delaysize, psamps, *ppsamp, tdelay3 );
|
|
|
|
sum = s1 + s2 + s3 + s4;
|
|
|
|
// write to delay
|
|
|
|
**ppsamp = in + ((s4 * fbgain) >> PBITS);
|
|
|
|
// update delay pointers
|
|
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
return ( ((sum + in) * outgain ) >> PBITS );
|
|
}
|
|
|
|
inline void ReverbSimple_multitap_Opt ( int delaysize, int tdelay0, int tdelay1, int tdelay2, int tdelay3, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int * pIn, LocalOutputSample_t * pOut, int nCount )
|
|
{
|
|
while ( nCount-- > 0 )
|
|
{
|
|
*pOut++ += ReverbSimple_multitap( delaysize, tdelay0, tdelay1, tdelay2, tdelay3, psamps, ppsamp, fbgain, outgain, *pIn );
|
|
pIn += 2; // Because pIn has both left AND right (but we read only one).
|
|
}
|
|
}
|
|
|
|
// modulate smallest tap delay only
|
|
|
|
inline int ReverbSimple_multitap_xfade ( int delaysize, int tdelay0, int tdelaynew, int xf, int tdelay1, int tdelay2, int tdelay3, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int in )
|
|
{
|
|
int s1, s2, s3, s4, sum;
|
|
int sD, sDnew;
|
|
|
|
// crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX
|
|
|
|
sD = GetDly ( delaysize, psamps, *ppsamp, tdelay3 );
|
|
sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
|
|
|
|
s4 = sD + (((sDnew - sD) * xf) >> PBITS);
|
|
|
|
s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 );
|
|
s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 );
|
|
s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 );
|
|
|
|
sum = s1 + s2 + s3 + s4;
|
|
|
|
// write to delay
|
|
|
|
**ppsamp = in + ((s4 * fbgain) >> PBITS);
|
|
|
|
// update delay pointers
|
|
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
return ( ((sum + in) * outgain ) >> PBITS );
|
|
}
|
|
|
|
// straight delay, no feedback
|
|
//
|
|
// delaysize: delay line size in samples
|
|
// tdelay: tap from this location - <= delaysize
|
|
// psamps: delay line buffer pointer of dimension delaysize+1
|
|
// ppsamp: circular pointer, must be init to &psamps[0] before first call
|
|
// in: input sample
|
|
//
|
|
// in(n)--->[Delay d]---> out(n)
|
|
//
|
|
|
|
inline int DelayLinear ( int delaysize, int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int in )
|
|
{
|
|
int out;
|
|
|
|
out = GetDly ( delaysize, psamps, *ppsamp, tdelay );
|
|
|
|
**ppsamp = in;
|
|
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
return ( out );
|
|
}
|
|
|
|
inline void DelayLinear_Opt ( int delaysize, int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int * pIn, LocalOutputSample_t * pOut, int nCount )
|
|
{
|
|
while ( nCount-- > 0 )
|
|
{
|
|
*pOut++ += DelayLinear( delaysize, tdelay, psamps, ppsamp, *pIn );
|
|
pIn += 2; // Because pIn has both left AND right (but we read only one).
|
|
}
|
|
}
|
|
|
|
// crossfade delay values from tdelay to tdelaynew, with xfade1 for tdelay and xfade2 for tdelaynew. xfade = 0...PMAX
|
|
|
|
inline int DelayLinear_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int in )
|
|
{
|
|
int out;
|
|
int outnew;
|
|
|
|
out = GetDly ( delaysize, psamps, *ppsamp, tdelay );
|
|
|
|
outnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
|
|
|
|
out = out + (((outnew - out) * xf) >> PBITS);
|
|
|
|
**ppsamp = in;
|
|
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
return ( out );
|
|
}
|
|
|
|
// lowpass reverberator, replace feedback multiplier 'fbgain' in
|
|
// reverberator with a low pass filter
|
|
|
|
// delaysize: delay line size in samples
|
|
// tdelay: tap from this location - <= delaysize
|
|
// psamps: delay line buffer pointer of dimension delaysize+1
|
|
// ppsamp: circular pointer, must be init to &w[0] before first call
|
|
// fbgain: feedback gain (built into filter gain)
|
|
// outgain: output gain
|
|
// cnumer: filter order
|
|
// numer: filter numerator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional
|
|
// denom: filter denominator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional
|
|
// pfsamps: filter state, cnumer+1 dimensional
|
|
// in: input sample
|
|
|
|
// psamps0(n) outgain
|
|
// in(n)--->(+)--------------.----(*)--> out(n)
|
|
// ^ |
|
|
// | [Delay d]
|
|
// | |
|
|
// | fbgain |Wd(n)
|
|
// --(*)--[Filter])-
|
|
|
|
inline int DelayLowPass ( int delaysize, int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in )
|
|
{
|
|
int out, sD;
|
|
|
|
// delay output is filter input
|
|
|
|
sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
|
|
|
|
// filter output, with feedback 'fbgain' baked into filter params
|
|
|
|
out = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD );
|
|
|
|
// write to delay
|
|
|
|
**ppsamp = out;
|
|
|
|
// update delay pointers
|
|
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
// output with gain
|
|
|
|
return ( (out * outgain) >> PBITS );
|
|
}
|
|
|
|
inline void DelayLowPass_Opt ( const int nDelaySize, const int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, const int fbgain, const int outgain, int *denom, const int Ll, int *numer, int *pfsamps, int * pIn, int * pOut, int nCount )
|
|
{
|
|
while ( nCount-- > 0 )
|
|
{
|
|
*pOut++ += DelayLowPass( nDelaySize, tdelay, psamps, ppsamp, fbgain, outgain, denom, Ll, numer, pfsamps, *pIn );
|
|
pIn += 2;
|
|
}
|
|
}
|
|
|
|
void DelayLowPass_Opt2 ( const int nDelaySize, const int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, const int fbgain, const int outgain, int *denom, const int Ll, int *numer, int *pfsamps, int * pIn, LocalOutputSample_t * pOut, int nCount )
|
|
{
|
|
// int pfsamps0 = pfsamps[0]; // pfsamps0 is not needed as it is overridden during the filtering
|
|
int pfsamps1 = pfsamps[1];
|
|
int numer0 = numer[0];
|
|
int numer1 = numer[1];
|
|
int denom1 = denom[1];
|
|
|
|
CircularBufferSample_t * pDelaySample = *ppsamp;
|
|
|
|
// Code path with no tests
|
|
// TODO: We could improve a bit further by calculating how many samples we can calculate without having to loop around
|
|
// This would reduce some of the overhead with the branch-less test (save a couple of cycles).
|
|
// TODO: Consider unrolling this, don't know how much we would really save though. And would make the code much less maintainable (esp. if we do the same for all filters).
|
|
CircularBufferSample_t * pSampsPDelaySize = psamps + nDelaySize;
|
|
const int nDelaySizeP1 = nDelaySize + 1;
|
|
while ( nCount-- > 0 )
|
|
{
|
|
// delay output is filter input
|
|
CircularBufferSample_t *pInputSampleDelay = pDelaySample + tdelay;
|
|
#if 1
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op).
|
|
// Re-use our own version of isel() as there is an optimization we can do
|
|
int nMask1 = (pSampsPDelaySize - pInputSampleDelay) >> 31; // 0xffffffff if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
nMask1 &= nDelaySizeP1; // nDelaySizeP1 if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
pInputSampleDelay -= nMask1;
|
|
#else
|
|
if ( pInputSampleDelay > pSampsPDelaySize)
|
|
{
|
|
pInputSampleDelay -= nDelaySizeP1;
|
|
}
|
|
#endif
|
|
|
|
int sD = *pInputSampleDelay;
|
|
|
|
// filter output, with feedback 'fbgain' baked into filter params
|
|
// IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD );
|
|
int pfsamps0 = sD - (( denom1 * pfsamps1 ) >> PBITS);
|
|
int nFilteredOutput = ( ( numer1 * pfsamps1 ) + ( numer0 * pfsamps0 ) ) >> PBITS;
|
|
pfsamps1 = pfsamps0;
|
|
|
|
int out = *pIn + nFilteredOutput;
|
|
pIn += 2; // Because pIn has both left AND right (but we read only one).
|
|
|
|
// write to delay
|
|
*pDelaySample = out;
|
|
// update delay pointers, decrement pointer and fix up on buffer boundary
|
|
// when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize
|
|
|
|
--pDelaySample;
|
|
#if 1
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op)
|
|
int nMask2 = (pDelaySample - psamps) >> 31; // 0xffffffff if pDelaySample < psamps, 0 otherwise
|
|
nMask2 &= nDelaySizeP1; // nDelaySizeP1 if pDelaySample < psamps, 0 otherwise
|
|
pDelaySample += nMask2;
|
|
#else
|
|
if ( pDelaySample < psamps )
|
|
{
|
|
pDelaySample += nDelaySizeP1;
|
|
}
|
|
#endif
|
|
// output with gain
|
|
*pOut++ += ( (out * outgain) >> PBITS );
|
|
|
|
if ( ( nCount % (CACHE_LINE_SIZE / sizeof(portable_samplepair_t) ) ) == 0)
|
|
{
|
|
// Prefetch the next input samples (output samples are already in cache anyway)
|
|
// Do conservative prefetching to reduce cache usage and in case the memory was virtual and would prefetch memory outside the buffer.
|
|
const int nBytesLeft = nCount * sizeof(portable_samplepair_t);
|
|
if ( nBytesLeft >= 3 * CACHE_LINE_SIZE )
|
|
{
|
|
PREFETCH_128( pIn, 4 * CACHE_LINE_SIZE );
|
|
}
|
|
const int OFFSET = CACHE_LINE_SIZE / sizeof( *pDelaySample );
|
|
if ( pDelaySample - OFFSET >= psamps )
|
|
{
|
|
PREFETCH_128( pDelaySample, -2 * CACHE_LINE_SIZE ); // We often read the delay sample just before the one that we are writing
|
|
// Thus one prefetch will handle gracefully both delay accesses
|
|
}
|
|
}
|
|
}
|
|
|
|
*ppsamp = pDelaySample;
|
|
pfsamps[0] = pfsamps1; // The [0] and [1] are the same after the filtering
|
|
pfsamps[1] = pfsamps1;
|
|
}
|
|
|
|
// By adding few constraints on the data and accepting some slight error in the calculation we could make the code much faster.
|
|
// Currently we can't SIMD this code the way it is but if we were calculating 4 samples at a time (completely independently), then we could make this loop 4-10 times faster.
|
|
// But the first thing would be to make the code work with floats.
|
|
void DelayLowPass_Opt3 ( const int nDelaySize, const int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, const int fbgain, const int outgain, int *denom, const int Ll, int *numer, int *pfsamps, int * pIn, LocalOutputSample_t * pOut, int nCount )
|
|
{
|
|
if ( nDelaySize != tdelay )
|
|
{
|
|
// Use less optimized code path if conditions are not right.
|
|
DelayLowPass_Opt2( nDelaySize, tdelay, psamps, ppsamp, fbgain, outgain, denom, Ll, numer, pfsamps, pIn, pOut, nCount );
|
|
return;
|
|
}
|
|
|
|
// Here it means that the read delay buffer is always just after the write delay buffer (have to account the wrap around obviously).
|
|
|
|
// int pfsamps0 = pfsamps[0]; // pfsamps0 is not needed as it is overridden during the filtering
|
|
int pfsamps1 = pfsamps[1];
|
|
int numer0 = numer[0];
|
|
int numer1 = numer[1];
|
|
int denom1 = denom[1];
|
|
|
|
CircularBufferSample_t * pDelaySample = *ppsamp;
|
|
|
|
// Code path with no tests
|
|
// TODO: We could improve a bit further by calculating how many samples we can calculate without having to loop around
|
|
// This would reduce some of the overhead with the branch-less test (save a couple of cycles).
|
|
// TODO: Consider unrolling this, don't know how much we would really save though. And would make the code much less maintainable (esp. if we do the same for all filters).
|
|
CircularBufferSample_t * pSampsPDelaySize = psamps + nDelaySize;
|
|
const int nDelaySizeP1 = nDelaySize + 1;
|
|
|
|
// First the final 3 samples (so nCount is aligned on 4 after this loop)
|
|
while ( (nCount & 3 ) != 0 )
|
|
{
|
|
--nCount;
|
|
// delay output is filter input
|
|
CircularBufferSample_t *pInputDelaySample = pDelaySample + nDelaySize;
|
|
|
|
int nMask1 = (pSampsPDelaySize - pInputDelaySample) >> 31; // 0xffffffff if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
nMask1 &= nDelaySizeP1; // nDelaySizeP1 if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
pInputDelaySample -= nMask1;
|
|
|
|
int sD = *pInputDelaySample;
|
|
|
|
// filter output, with feedback 'fbgain' baked into filter params
|
|
// IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD );
|
|
int pfsamps0 = sD - (( denom1 * pfsamps1 ) >> PBITS);
|
|
int nFilteredOutput = ( ( numer1 * pfsamps1 ) + ( numer0 * pfsamps0 ) ) >> PBITS;
|
|
pfsamps1 = pfsamps0;
|
|
|
|
int out = *pIn + nFilteredOutput;
|
|
pIn += 2; // Because pIn has both left AND right (but we read only one).
|
|
|
|
// write to delay
|
|
*pDelaySample = out;
|
|
// update delay pointers, decrement pointer and fix up on buffer boundary
|
|
// when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize
|
|
|
|
--pDelaySample;
|
|
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op)
|
|
int nMask2 = (pDelaySample - psamps) >> 31; // 0xffffffff if pDelaySample < psamps, 0 otherwise
|
|
nMask2 &= nDelaySizeP1; // nDelaySizeP1 if pDelaySample < psamps, 0 otherwise
|
|
pDelaySample += nMask2;
|
|
|
|
// output with gain
|
|
*pOut++ += ( (out * outgain) >> PBITS );
|
|
}
|
|
|
|
CircularBufferSample_t * RESTRICT pDelaySampleA = pDelaySample;
|
|
CircularBufferSample_t * RESTRICT pDelaySampleB = pDelaySample - 1;
|
|
CircularBufferSample_t * RESTRICT pDelaySampleC = pDelaySample - 2;
|
|
CircularBufferSample_t * RESTRICT pDelaySampleD = pDelaySample - 3;
|
|
// pDelaySampleA is already in the correct range
|
|
int nMask2B = (pDelaySampleB - psamps) >> 31;
|
|
nMask2B &= nDelaySizeP1;
|
|
pDelaySampleB += nMask2B;
|
|
int nMask2C = (pDelaySampleC - psamps) >> 31;
|
|
nMask2C &= nDelaySizeP1;
|
|
pDelaySampleC += nMask2C;
|
|
int nMask2D = (pDelaySampleD - psamps) >> 31;
|
|
nMask2D &= nDelaySizeP1;
|
|
pDelaySampleD += nMask2D;
|
|
|
|
// pDelaySampleD is the lowest address used, that's going to be the first one to cross the wrap-around
|
|
// We could have a more optimized path by getting rid of the wrap around when we are inside the safe zone.
|
|
// It would make the code more complicated though (safe zone, then unsafe for up to 2*4 samples, safe zone again...
|
|
while ( nCount >= 4 )
|
|
{
|
|
nCount -= 4;
|
|
// delay output is filter input
|
|
// Here is the trick, pDelaySampleA, B, C and D are all offseted by 4. Because read and write are only separated by one sample,
|
|
// What we read with A, we will write it with B, B to C, C to D. So we can avoid wrap-around for A, B and C as the values are already known.
|
|
// Only D will have to be calculated (the smaller value of the set of 4, not yet calculated).
|
|
CircularBufferSample_t *pInputDelaySampleD = pDelaySampleD + nDelaySize;
|
|
int nMask1D = (pSampsPDelaySize - pInputDelaySampleD) >> 31;
|
|
nMask1D &= nDelaySizeP1;
|
|
pInputDelaySampleD -= nMask1D;
|
|
|
|
int sDA = *pDelaySampleB; // Read A (from written position B)
|
|
int sDB = *pDelaySampleC; // Read B (from written position C)
|
|
int sDC = *pDelaySampleD; // Read C (from written position D)
|
|
int sDD = *pInputDelaySampleD;
|
|
|
|
// filter output, with feedback 'fbgain' baked into filter params
|
|
// IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD );
|
|
int pfsamps0A = sDA - (( denom1 * pfsamps1 ) >> PBITS);
|
|
int nFilteredOutputA = ( ( numer1 * pfsamps1 ) + ( numer0 * pfsamps0A ) ) >> PBITS;
|
|
int pfsamps0B = sDB - (( denom1 * pfsamps0A ) >> PBITS);
|
|
int nFilteredOutputB = ( ( numer1 * pfsamps0A ) + ( numer0 * pfsamps0B ) ) >> PBITS;
|
|
int pfsamps0C = sDC - (( denom1 * pfsamps0B ) >> PBITS);
|
|
int nFilteredOutputC = ( ( numer1 * pfsamps0B ) + ( numer0 * pfsamps0C ) ) >> PBITS;
|
|
int pfsamps0D = sDD - (( denom1 * pfsamps0C ) >> PBITS);
|
|
int nFilteredOutputD = ( ( numer1 * pfsamps0C ) + ( numer0 * pfsamps0D ) ) >> PBITS;
|
|
pfsamps1 = pfsamps0D;
|
|
|
|
// Assuming that the addresses were aligned, on PS3 we could load this on VMX
|
|
// Add the filter normally on an integer VMX base. And store the delay in an aligned manner.
|
|
int outA = *pIn + nFilteredOutputA;
|
|
int outB = *(pIn + 2) + nFilteredOutputB;
|
|
int outC = *(pIn + 4) + nFilteredOutputC;
|
|
int outD = *(pIn + 6) + nFilteredOutputD;
|
|
pIn += 8; // Because pIn has both left AND right (but we read only one).
|
|
|
|
// write to delay
|
|
*pDelaySampleA = outA;
|
|
*pDelaySampleB = outB;
|
|
*pDelaySampleC = outC;
|
|
*pDelaySampleD = outD;
|
|
// update delay pointers, decrement pointer and fix up on buffer boundary
|
|
// when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize
|
|
|
|
pDelaySampleA -= 4;
|
|
pDelaySampleB -= 4;
|
|
pDelaySampleC -= 4;
|
|
pDelaySampleD -= 4;
|
|
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op)
|
|
int nMask2A = (pDelaySampleA - psamps) >> 31; // 0xffffffff if pDelaySample < psamps, 0 otherwise
|
|
nMask2A &= nDelaySizeP1; // nDelaySizeP1 if pDelaySample < psamps, 0 otherwise
|
|
pDelaySampleA += nMask2A;
|
|
nMask2B = (pDelaySampleB - psamps) >> 31;
|
|
nMask2B &= nDelaySizeP1;
|
|
pDelaySampleB += nMask2B;
|
|
nMask2C = (pDelaySampleC - psamps) >> 31;
|
|
nMask2C &= nDelaySizeP1;
|
|
pDelaySampleC += nMask2C;
|
|
nMask2D = (pDelaySampleD - psamps) >> 31;
|
|
nMask2D &= nDelaySizeP1;
|
|
pDelaySampleD += nMask2D;
|
|
|
|
// output with gain
|
|
*pOut += ( (outA * outgain) >> PBITS );
|
|
*(pOut + 1) += ( (outB * outgain) >> PBITS );
|
|
*(pOut + 2) += ( (outC * outgain) >> PBITS );
|
|
*(pOut + 3) += ( (outD * outgain) >> PBITS );
|
|
pOut += 4;
|
|
|
|
if ( ( nCount % ( CACHE_LINE_SIZE / ( 4 * sizeof(portable_samplepair_t) ) ) ) == 0 )
|
|
{
|
|
// Prefetch the next input samples (output samples are already in cache anyway)
|
|
// Remove the conservative prefetching to make the loop a bit faster
|
|
PREFETCH_128( pIn, 4 * CACHE_LINE_SIZE );
|
|
if ( ( nCount % ( CACHE_LINE_SIZE / ( 4 * sizeof(*pDelaySampleD) ) ) ) == 0 )
|
|
{
|
|
// Delay sample is not prefetched as often.
|
|
PREFETCH_128( pDelaySampleD, -2 * CACHE_LINE_SIZE ); // We often read the delay sample just before the one that we are writing
|
|
}
|
|
}
|
|
}
|
|
|
|
*ppsamp = pDelaySampleA;
|
|
pfsamps[0] = pfsamps1; // The [0] and [1] are the same after the filtering
|
|
pfsamps[1] = pfsamps1;
|
|
}
|
|
|
|
inline int DelayLowpass_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in )
|
|
{
|
|
int out, sD;
|
|
int sDnew;
|
|
|
|
// crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX
|
|
|
|
sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
|
|
sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
|
|
sD = sD + (((sDnew - sD) * xf) >> PBITS);
|
|
|
|
// filter output with feedback 'fbgain' baked into filter params
|
|
|
|
out = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD );
|
|
|
|
// write to delay
|
|
|
|
**ppsamp = out;
|
|
|
|
// update delay ptrs
|
|
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
// output with gain
|
|
|
|
return ( (out * outgain) >> PBITS );
|
|
}
|
|
|
|
// delay is multitap tdelay0,tdelay1,tdelay2,tdelay3
|
|
|
|
// NOTE: tdelay3 > tdelay2 > tdelay1 > tdelay0
|
|
// NOTE: fbgain * 4 < 1!
|
|
|
|
inline int DelayLowpass_multitap ( int delaysize, int tdelay0, int tdelay1, int tdelay2, int tdelay3, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in )
|
|
{
|
|
int s0, s1, s2, s3, s4, sum;
|
|
|
|
s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 );
|
|
s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 );
|
|
s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 );
|
|
s4 = GetDly ( delaysize, psamps, *ppsamp, tdelay3 );
|
|
|
|
sum = s1 + s2 + s3 + s4;
|
|
|
|
s0 = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, s4 );
|
|
|
|
// write to delay
|
|
|
|
**ppsamp = s0;
|
|
|
|
// update delay ptrs
|
|
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
return ( ((sum + in) * outgain ) >> PBITS );
|
|
}
|
|
|
|
inline void DelayLowpass_multitap_Opt ( int delaysize, int tdelay0, int tdelay1, int tdelay2, int tdelay3, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int * pIn, LocalOutputSample_t *pOut, int nCount )
|
|
{
|
|
while ( nCount-- > 0 )
|
|
{
|
|
*pOut++ += DelayLowpass_multitap( delaysize, tdelay0, tdelay1, tdelay2, tdelay3, psamps, ppsamp, fbgain, outgain, denom, Ll, numer, pfsamps, *pIn );
|
|
pIn += 2; // Because pIn has both left AND right (but we read only one).
|
|
}
|
|
}
|
|
|
|
inline int DelayLowpass_multitap_xfade ( int delaysize, int tdelay0, int tdelaynew, int xf, int tdelay1, int tdelay2, int tdelay3, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in )
|
|
{
|
|
int s0, s1, s2, s3, s4, sum;
|
|
|
|
int sD, sDnew;
|
|
|
|
// crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX
|
|
|
|
sD = GetDly ( delaysize, psamps, *ppsamp, tdelay3 );
|
|
sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
|
|
|
|
s4 = sD + (((sDnew - sD) * xf) >> PBITS);
|
|
|
|
s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 );
|
|
s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 );
|
|
s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 );
|
|
|
|
sum = s1 + s2 + s3 + s4;
|
|
|
|
s0 = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, s4 );
|
|
|
|
**ppsamp = s0;
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
return ( ((sum + in) * outgain ) >> PBITS );
|
|
}
|
|
|
|
// linear delay with lowpass filter on delay output and gain stage
|
|
// delaysize: delay line size in samples
|
|
// tdelay: delay tap from this location - <= delaysize
|
|
// psamps: delay line buffer pointer of dimension delaysize+1
|
|
// ppsamp: circular pointer, must init &psamps[0] before first call
|
|
// fbgain: feedback gain (ignored)
|
|
// outgain: output gain
|
|
// cnumer: filter order
|
|
// numer: filter numerator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional
|
|
// denom: filter denominator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional
|
|
// pfsamps: filter state, cnumer+1 dimensional
|
|
// in: input sample
|
|
|
|
// in(n)--->[Delay d]--->[Filter]-->(*outgain)---> out(n)
|
|
|
|
inline int DelayLinearLowPass ( int delaysize, int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int *denom, int cnumer, int *numer, int *pfsamps, int in )
|
|
{
|
|
int out, sD;
|
|
|
|
// delay output is filter input
|
|
|
|
sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
|
|
|
|
// calc filter output
|
|
|
|
out = IIRFilter_Update_Order1 ( denom, cnumer, numer, pfsamps, sD );
|
|
|
|
// input sample to delay input
|
|
|
|
**ppsamp = in;
|
|
|
|
// update delay pointers
|
|
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
// output with gain
|
|
|
|
return ( (out * outgain) >> PBITS );
|
|
}
|
|
|
|
inline void DelayLinearLowPass_Opt ( const int nDelaySize, const int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, const int fbgain, const int outgain, int *denom, const int Ll, int *numer, int *pfsamps, int * pIn, int * pOut, int nCount )
|
|
{
|
|
while ( nCount-- > 0 )
|
|
{
|
|
*pOut++ += DelayLinearLowPass( nDelaySize, tdelay, psamps, ppsamp, fbgain, outgain, denom, Ll, numer, pfsamps, *pIn );
|
|
pIn += 2;
|
|
}
|
|
}
|
|
|
|
void DelayLinearLowPass_Opt2 ( const int nDelaySize, const int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, const int fbgain, const int outgain, int *denom, const int Ll, int *numer, int *pfsamps, int * pIn, LocalOutputSample_t * pOut, int nCount )
|
|
{
|
|
// int pfsamps0 = pfsamps[0]; // pfsamps0 is not needed as it is overridden during the filtering
|
|
int pfsamps1 = pfsamps[1];
|
|
int numer0 = numer[0];
|
|
int numer1 = numer[1];
|
|
int denom1 = denom[1];
|
|
|
|
CircularBufferSample_t * pDelaySample = *ppsamp;
|
|
|
|
// Code path with no tests
|
|
// TODO: We could improve a bit further by calculating how many samples we can calculate without having to loop around
|
|
// This would reduce some of the overhead with the branch-less test (save a couple of cycles).
|
|
// TODO: Consider unrolling this, don't know how much we would really save though.
|
|
CircularBufferSample_t * pSampsPDelaySize = psamps + nDelaySize;
|
|
const int nDelaySizeP1 = nDelaySize + 1;
|
|
while ( nCount-- > 0 )
|
|
{
|
|
// delay output is filter input
|
|
CircularBufferSample_t *pInputSampleDelay = pDelaySample + tdelay;
|
|
#if 1
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op).
|
|
// Re-use our own version of isel() as there is an optimization we can do
|
|
int nMask1 = (pSampsPDelaySize - pInputSampleDelay) >> 31; // 0xffffffff if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
nMask1 &= nDelaySizeP1; // nDelaySizeP1 if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
pInputSampleDelay -= nMask1;
|
|
#else
|
|
if ( pInputSampleDelay > pSampsPDelaySize)
|
|
{
|
|
pInputSampleDelay -= nDelaySizeP1;
|
|
}
|
|
#endif
|
|
|
|
int sD = *pInputSampleDelay;
|
|
|
|
// filter output, with feedback 'fbgain' baked into filter params
|
|
// IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD );
|
|
int pfsamps0 = sD - (( denom1 * pfsamps1 ) >> PBITS);
|
|
int nFilteredOutput = ( ( numer1 * pfsamps1 ) + ( numer0 * pfsamps0 ) ) >> PBITS;
|
|
pfsamps1 = pfsamps0;
|
|
|
|
// write to delay
|
|
*pDelaySample = *pIn;
|
|
pIn += 2; // Because pIn has both left AND right (but we read only one).
|
|
|
|
// update delay pointers, decrement pointer and fix up on buffer boundary
|
|
// when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize
|
|
|
|
--pDelaySample;
|
|
#if 1
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op)
|
|
int nMask2 = (pDelaySample - psamps) >> 31; // 0xffffffff if pDelaySample < psamps, 0 otherwise
|
|
nMask2 &= nDelaySizeP1; // nDelaySizeP1 if pDelaySample < psamps, 0 otherwise
|
|
pDelaySample += nMask2;
|
|
|
|
#else
|
|
if ( pDelaySample < psamps )
|
|
{
|
|
pDelaySample += nDelaySizeP1;
|
|
}
|
|
#endif
|
|
// output with gain
|
|
*pOut++ += ( (nFilteredOutput * outgain) >> PBITS );
|
|
|
|
if ( ( nCount % (CACHE_LINE_SIZE / sizeof(portable_samplepair_t) ) ) == 0)
|
|
{
|
|
// Prefetch the next input samples (output samples are already in cache anyway)
|
|
// Do conservative prefetching to reduce cache usage and in case the memory was virtual and would prefetch memory outside the buffer.
|
|
const int nBytesLeft = nCount * sizeof(portable_samplepair_t);
|
|
if ( nBytesLeft >= 3 * CACHE_LINE_SIZE )
|
|
{
|
|
PREFETCH_128( pIn, 4 * CACHE_LINE_SIZE );
|
|
}
|
|
const int OFFSET = CACHE_LINE_SIZE / sizeof( *pDelaySample );
|
|
if ( pDelaySample - OFFSET >= psamps )
|
|
{
|
|
PREFETCH_128( pDelaySample, -2 * CACHE_LINE_SIZE ); // We often read the delay sample just before the one that we are writing
|
|
// Thus one prefetch will handle gracefully both delay accesses
|
|
}
|
|
}
|
|
}
|
|
|
|
*ppsamp = pDelaySample;
|
|
pfsamps[0] = pfsamps1; // The [0] and [1] are the same after the filtering
|
|
pfsamps[1] = pfsamps1;
|
|
}
|
|
|
|
void DelayLinearLowPass_Opt3 ( const int nDelaySize, const int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, const int fbgain, const int outgain, int *denom, const int Ll, int *numer, int *pfsamps, int * pIn, LocalOutputSample_t * pOut, int nCount )
|
|
{
|
|
if ( nDelaySize != tdelay )
|
|
{
|
|
// Use less optimized code path if conditions are not right.
|
|
DelayLinearLowPass_Opt2( nDelaySize, tdelay, psamps, ppsamp, fbgain, outgain, denom, Ll, numer, pfsamps, pIn, pOut, nCount );
|
|
return;
|
|
}
|
|
|
|
// Here it means that the read delay buffer is always just after the write delay buffer (have to account the wrap around obviously).
|
|
|
|
// int pfsamps0 = pfsamps[0]; // pfsamps0 is not needed as it is overridden during the filtering
|
|
int pfsamps1 = pfsamps[1];
|
|
int numer0 = numer[0];
|
|
int numer1 = numer[1];
|
|
int denom1 = denom[1];
|
|
|
|
CircularBufferSample_t * pDelaySample = *ppsamp;
|
|
|
|
// Code path with no tests
|
|
// TODO: We could improve a bit further by calculating how many samples we can calculate without having to loop around
|
|
// This would reduce some of the overhead with the branch-less test (save a couple of cycles).
|
|
// TODO: Consider unrolling this, don't know how much we would really save though. And would make the code much less maintainable (esp. if we do the same for all filters).
|
|
CircularBufferSample_t * pSampsPDelaySize = psamps + nDelaySize;
|
|
const int nDelaySizeP1 = nDelaySize + 1;
|
|
|
|
// First the final 3 samples (so nCount is aligned on 4 after this loop)
|
|
while ( (nCount & 3 ) != 0 )
|
|
{
|
|
--nCount;
|
|
// delay output is filter input
|
|
CircularBufferSample_t *pInputDelaySample = pDelaySample + nDelaySize;
|
|
|
|
int nMask1 = (pSampsPDelaySize - pInputDelaySample) >> 31; // 0xffffffff if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
nMask1 &= nDelaySizeP1; // nDelaySizeP1 if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
pInputDelaySample -= nMask1;
|
|
|
|
int sD = *pInputDelaySample;
|
|
|
|
// filter output, with feedback 'fbgain' baked into filter params
|
|
// IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD );
|
|
int pfsamps0 = sD - (( denom1 * pfsamps1 ) >> PBITS);
|
|
int nFilteredOutput = ( ( numer1 * pfsamps1 ) + ( numer0 * pfsamps0 ) ) >> PBITS;
|
|
pfsamps1 = pfsamps0;
|
|
|
|
// write to delay
|
|
*pDelaySample = *pIn;
|
|
pIn += 2; // Because pIn has both left AND right (but we read only one).
|
|
|
|
// update delay pointers, decrement pointer and fix up on buffer boundary
|
|
// when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize
|
|
|
|
--pDelaySample;
|
|
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op)
|
|
int nMask2 = (pDelaySample - psamps) >> 31; // 0xffffffff if pDelaySample < psamps, 0 otherwise
|
|
nMask2 &= nDelaySizeP1; // nDelaySizeP1 if pDelaySample < psamps, 0 otherwise
|
|
pDelaySample += nMask2;
|
|
|
|
// output with gain
|
|
*pOut++ += ( (nFilteredOutput * outgain) >> PBITS );
|
|
}
|
|
|
|
CircularBufferSample_t * RESTRICT pDelaySampleA = pDelaySample;
|
|
CircularBufferSample_t * RESTRICT pDelaySampleB = pDelaySample - 1;
|
|
CircularBufferSample_t * RESTRICT pDelaySampleC = pDelaySample - 2;
|
|
CircularBufferSample_t * RESTRICT pDelaySampleD = pDelaySample - 3;
|
|
// pDelaySampleA is already in the correct range
|
|
int nMask2B = (pDelaySampleB - psamps) >> 31;
|
|
nMask2B &= nDelaySizeP1;
|
|
pDelaySampleB += nMask2B;
|
|
int nMask2C = (pDelaySampleC - psamps) >> 31;
|
|
nMask2C &= nDelaySizeP1;
|
|
pDelaySampleC += nMask2C;
|
|
int nMask2D = (pDelaySampleD - psamps) >> 31;
|
|
nMask2D &= nDelaySizeP1;
|
|
pDelaySampleD += nMask2D;
|
|
|
|
// pDelaySampleD is the lowest address used, that's going to be the first one to cross the wrap-around
|
|
// We could have a more optimized path by getting rid of the wrap around when we are inside the safe zone.
|
|
// It would make the code more complicated though (safe zone, then unsafe for up to 2*4 samples, safe zone again...
|
|
|
|
while ( nCount >= 4 )
|
|
{
|
|
nCount -= 4;
|
|
// delay output is filter input
|
|
// Here is the trick, pDelaySampleA, B, C and D are all offseted by 4. Because read and write are only separated by one sample,
|
|
// What we read with A, we will write it with B, B to C, C to D. So we can avoid wrap-around for A, B and C as the values are already known.
|
|
// Only D will have to be calculated (the smaller value of the set of 4, not yet calculated).
|
|
CircularBufferSample_t *pInputDelaySampleD = pDelaySampleD + nDelaySize;
|
|
int nMask1D = (pSampsPDelaySize - pInputDelaySampleD) >> 31;
|
|
nMask1D &= nDelaySizeP1;
|
|
pInputDelaySampleD -= nMask1D;
|
|
|
|
int sDA = *pDelaySampleB; // Read A (from written position B)
|
|
int sDB = *pDelaySampleC; // Read B (from written position C)
|
|
int sDC = *pDelaySampleD; // Read C (from written position D)
|
|
int sDD = *pInputDelaySampleD;
|
|
|
|
// filter output, with feedback 'fbgain' baked into filter params
|
|
// IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD );
|
|
int pfsamps0A = sDA - (( denom1 * pfsamps1 ) >> PBITS);
|
|
int nFilteredOutputA = ( ( numer1 * pfsamps1 ) + ( numer0 * pfsamps0A ) ) >> PBITS;
|
|
int pfsamps0B = sDB - (( denom1 * pfsamps0A ) >> PBITS);
|
|
int nFilteredOutputB = ( ( numer1 * pfsamps0A ) + ( numer0 * pfsamps0B ) ) >> PBITS;
|
|
int pfsamps0C = sDC - (( denom1 * pfsamps0B ) >> PBITS);
|
|
int nFilteredOutputC = ( ( numer1 * pfsamps0B ) + ( numer0 * pfsamps0C ) ) >> PBITS;
|
|
int pfsamps0D = sDD - (( denom1 * pfsamps0C ) >> PBITS);
|
|
int nFilteredOutputD = ( ( numer1 * pfsamps0C ) + ( numer0 * pfsamps0D ) ) >> PBITS;
|
|
pfsamps1 = pfsamps0D;
|
|
|
|
// write to delay
|
|
*pDelaySampleA = *pIn;
|
|
*pDelaySampleB = *(pIn + 2);
|
|
*pDelaySampleC = *(pIn + 4);
|
|
*pDelaySampleD = *(pIn + 6);
|
|
pIn += 8; // Because pIn has both left AND right (but we read only one).
|
|
// update delay pointers, decrement pointer and fix up on buffer boundary
|
|
// when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize
|
|
|
|
pDelaySampleA -= 4;
|
|
pDelaySampleB -= 4;
|
|
pDelaySampleC -= 4;
|
|
pDelaySampleD -= 4;
|
|
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op)
|
|
int nMask2A = (pDelaySampleA - psamps) >> 31; // 0xffffffff if pDelaySample < psamps, 0 otherwise
|
|
nMask2A &= nDelaySizeP1; // nDelaySizeP1 if pDelaySample < psamps, 0 otherwise
|
|
pDelaySampleA += nMask2A;
|
|
nMask2B = (pDelaySampleB - psamps) >> 31;
|
|
nMask2B &= nDelaySizeP1;
|
|
pDelaySampleB += nMask2B;
|
|
nMask2C = (pDelaySampleC - psamps) >> 31;
|
|
nMask2C &= nDelaySizeP1;
|
|
pDelaySampleC += nMask2C;
|
|
nMask2D = (pDelaySampleD - psamps) >> 31;
|
|
nMask2D &= nDelaySizeP1;
|
|
pDelaySampleD += nMask2D;
|
|
|
|
// output with gain
|
|
*pOut += ( (nFilteredOutputA * outgain) >> PBITS );
|
|
*(pOut + 1) += ( (nFilteredOutputB * outgain) >> PBITS );
|
|
*(pOut + 2) += ( (nFilteredOutputC * outgain) >> PBITS );
|
|
*(pOut + 3) += ( (nFilteredOutputD * outgain) >> PBITS );
|
|
pOut += 4;
|
|
|
|
if ( ( nCount % (CACHE_LINE_SIZE / (4 * sizeof(portable_samplepair_t) ) ) ) == 0)
|
|
{
|
|
// Prefetch the next input samples (output samples are already in cache anyway)
|
|
// Remove the conservative prefetching to make the loop a bit faster
|
|
PREFETCH_128( pIn, 4 * CACHE_LINE_SIZE );
|
|
if ( ( nCount % ( CACHE_LINE_SIZE / ( 4 * sizeof(*pDelaySampleD) ) ) ) == 0 )
|
|
{
|
|
PREFETCH_128( pDelaySampleD, -2 * CACHE_LINE_SIZE ); // We often read the delay sample just before the one that we are writing
|
|
}
|
|
}
|
|
}
|
|
|
|
*ppsamp = pDelaySampleA;
|
|
pfsamps[0] = pfsamps1; // The [0] and [1] are the same after the filtering
|
|
pfsamps[1] = pfsamps1;
|
|
}
|
|
|
|
inline int DelayLinear_lowpass_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int *denom, int cnumer, int *numer, int *pfsamps, int in )
|
|
{
|
|
int out, sD;
|
|
int sDnew;
|
|
|
|
// crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX
|
|
|
|
sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
|
|
sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
|
|
sD = sD + (((sDnew - sD) * xf) >> PBITS);
|
|
|
|
out = IIRFilter_Update_Order1 ( denom, cnumer, numer, pfsamps, sD );
|
|
|
|
**ppsamp = in;
|
|
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
return ( (out * outgain) >> PBITS );
|
|
}
|
|
|
|
|
|
// classic allpass reverb
|
|
// delaysize: delay line size in samples
|
|
// tdelay: tap from this location - <= D
|
|
// psamps: delay line buffer pointer of dimension delaysize+1
|
|
// ppsamp: circular pointer, must be init to &psamps[0] before first call
|
|
// fbgain: feedback value, 0-PMAX (normalized to 0.0-1.0)
|
|
// outgain: gain
|
|
|
|
// psamps0(n) -fbgain outgain
|
|
// in(n)--->(+)--------.-----(*)-->(+)--(*)-> out(n)
|
|
// ^ | ^
|
|
// | [Delay d] |
|
|
// | | |
|
|
// | fbgain |psampsd(n) |
|
|
// ----(*)---.-------------
|
|
//
|
|
// for each input sample 'in':
|
|
// psamps0 = in + fbgain * psampsd
|
|
// y = -fbgain * psamps0 + psampsd
|
|
// delay (d, psamps) - psamps is the delay buffer array
|
|
//
|
|
// or, using circular delay, for each input sample 'in':
|
|
//
|
|
// Sd = GetDly (delaysize,psamps,ppsamp,delaysize)
|
|
// S0 = in + fbgain*Sd
|
|
// y = -fbgain*S0 + Sd
|
|
// *ppsamp = S0
|
|
// DlyUpdate(delaysize, psamps, &ppsamp)
|
|
|
|
inline int DelayAllpass ( int delaysize, int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int in )
|
|
{
|
|
int out, s0, sD;
|
|
|
|
sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
|
|
s0 = in + (( fbgain * sD ) >> PBITS);
|
|
|
|
out = ( ( -fbgain * s0 ) >> PBITS ) + sD;
|
|
**ppsamp = s0;
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
return ( (out * outgain) >> PBITS );
|
|
}
|
|
|
|
|
|
enum MixMode
|
|
{
|
|
MM_ADD,
|
|
MM_REPLACE,
|
|
};
|
|
|
|
template <int SOURCE_INCREMENT, MixMode MODE>
|
|
inline void DelayAllpass_Opt ( int delaysize, int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int * pIn, LocalOutputSample_t * pOut, int nCount )
|
|
{
|
|
while ( nCount-- > 0 )
|
|
{
|
|
int nValue = DelayAllpass( delaysize, tdelay, psamps, ppsamp, fbgain, outgain, *pIn );
|
|
if ( MODE == MM_ADD )
|
|
{
|
|
*pOut++ += nValue;
|
|
}
|
|
else
|
|
{
|
|
Assert( MODE == MM_REPLACE );
|
|
*pOut++ = nValue;
|
|
}
|
|
pIn += SOURCE_INCREMENT; // Because pIn has both left AND right (but we read only one).
|
|
}
|
|
}
|
|
|
|
template <int SOURCE_INCREMENT, MixMode MODE>
|
|
inline void DelayAllpass_Opt2 ( int nDelaySize, int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int * pIn, LocalOutputSample_t * pOut, int nCount )
|
|
{
|
|
CircularBufferSample_t * pDelaySample = *ppsamp;
|
|
|
|
// Code path with no tests
|
|
// TODO: We could improve a bit further by calculating how many samples we can calculate without having to loop around
|
|
// This would reduce some of the overhead with the branch-less test (save a couple of cycles).
|
|
// TODO: Consider unrolling this, don't know how much we would really save though. And would make the code much less maintainable (esp. if we do the same for all filters).
|
|
CircularBufferSample_t * pSampsPDelaySize = psamps + nDelaySize;
|
|
const int nDelaySizeP1 = nDelaySize + 1;
|
|
while ( nCount-- > 0 )
|
|
{
|
|
// delay output is filter input
|
|
CircularBufferSample_t *pInputDelaySample = pDelaySample + tdelay;
|
|
#if 1
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op).
|
|
// Re-use our own version of isel() as there is an optimization we can do
|
|
int nMask1 = (pSampsPDelaySize - pInputDelaySample) >> 31; // 0xffffffff if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
nMask1 &= nDelaySizeP1; // nDelaySizeP1 if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
pInputDelaySample -= nMask1;
|
|
#else
|
|
if ( pInputDelaySample > pSampsPDelaySize)
|
|
{
|
|
pInputDelaySample -= nDelaySizeP1;
|
|
}
|
|
#endif
|
|
|
|
int sD = *pInputDelaySample;
|
|
int s0 = *pIn + (( fbgain * sD ) >> PBITS);
|
|
|
|
// write to delay
|
|
*pDelaySample = s0;
|
|
|
|
int out = ( ( -fbgain * s0 ) >> PBITS ) + sD;
|
|
pIn += SOURCE_INCREMENT; // Because pIn has both left AND right (but we read only one).
|
|
|
|
// update delay pointers, decrement pointer and fix up on buffer boundary
|
|
// when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize
|
|
|
|
--pDelaySample;
|
|
#if 1
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op)
|
|
int nMask2 = (pDelaySample - psamps) >> 31; // 0xffffffff if pDelaySample < psamps, 0 otherwise
|
|
nMask2 &= nDelaySizeP1; // nDelaySizeP1 if pDelaySample < psamps, 0 otherwise
|
|
pDelaySample += nMask2;
|
|
#else
|
|
if ( pDelaySample < psamps )
|
|
{
|
|
pDelaySample += nDelaySizeP1;
|
|
}
|
|
#endif
|
|
// output with gain
|
|
int nValue = ( (out * outgain) >> PBITS );
|
|
if ( MODE == MM_ADD )
|
|
{
|
|
*pOut++ += nValue;
|
|
}
|
|
else
|
|
{
|
|
Assert( MODE == MM_REPLACE );
|
|
*pOut++ = nValue;
|
|
}
|
|
|
|
if ( ( nCount % (CACHE_LINE_SIZE / sizeof(portable_samplepair_t) ) ) == 0)
|
|
{
|
|
// Prefetch the next input samples (output samples are already in cache anyway)
|
|
// Do conservative prefetching to reduce cache usage and in case the memory was virtual and would prefetch memory outside the buffer.
|
|
const int nBytesLeft = nCount * sizeof(portable_samplepair_t);
|
|
if ( nBytesLeft >= 3 * CACHE_LINE_SIZE )
|
|
{
|
|
PREFETCH_128( pIn, 4 * CACHE_LINE_SIZE );
|
|
}
|
|
const int OFFSET = CACHE_LINE_SIZE / sizeof( *pDelaySample );
|
|
if ( pDelaySample - OFFSET >= psamps )
|
|
{
|
|
PREFETCH_128( pDelaySample, -2 * CACHE_LINE_SIZE ); // We often read the delay sample just before the one that we are writing
|
|
// Thus one prefetch will handle gracefully both delay accesses
|
|
}
|
|
}
|
|
}
|
|
|
|
*ppsamp = pDelaySample;
|
|
}
|
|
|
|
template <int SOURCE_INCREMENT, MixMode MODE>
|
|
inline void DelayAllpass_Opt3 ( int nDelaySize, int tdelay, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int * pIn, LocalOutputSample_t * pOut, int nCount )
|
|
{
|
|
if ( nDelaySize != tdelay )
|
|
{
|
|
DelayAllpass_Opt2<SOURCE_INCREMENT, MODE>( nDelaySize, tdelay, psamps, ppsamp, fbgain, outgain, pIn, pOut, nCount );
|
|
return;
|
|
}
|
|
|
|
// Here it means that the read delay buffer is always just after the write delay buffer (have to account the wrap around obviously).
|
|
|
|
CircularBufferSample_t * pDelaySample = *ppsamp;
|
|
|
|
// Code path with no tests
|
|
// TODO: We could improve a bit further by calculating how many samples we can calculate without having to loop around
|
|
// This would reduce some of the overhead with the branch-less test (save a couple of cycles).
|
|
// TODO: Consider unrolling this, don't know how much we would really save though. And would make the code much less maintainable (esp. if we do the same for all filters).
|
|
CircularBufferSample_t * pSampsPDelaySize = psamps + nDelaySize;
|
|
const int nDelaySizeP1 = nDelaySize + 1;
|
|
|
|
// First the final 3 samples (so nCount is aligned on 4 after this loop)
|
|
while ( (nCount & 3 ) != 0 )
|
|
{
|
|
--nCount;
|
|
// delay output is filter input
|
|
CircularBufferSample_t *pInputDelaySample = pDelaySample + nDelaySize;
|
|
|
|
int nMask1 = (pSampsPDelaySize - pInputDelaySample) >> 31; // 0xffffffff if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
nMask1 &= nDelaySizeP1; // nDelaySizeP1 if pInputSampleDelay > pSampsPDelaySize, 0 otherwise
|
|
pInputDelaySample -= nMask1;
|
|
|
|
int sD = *pInputDelaySample;
|
|
int s0 = *pIn + (( fbgain * sD ) >> PBITS);
|
|
|
|
// write to delay
|
|
*pDelaySample = s0;
|
|
|
|
int out = ( ( -fbgain * s0 ) >> PBITS ) + sD;
|
|
pIn += SOURCE_INCREMENT; // Because pIn has both left AND right (but we read only one).
|
|
|
|
// update delay pointers, decrement pointer and fix up on buffer boundary
|
|
// when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize
|
|
--pDelaySample;
|
|
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op)
|
|
int nMask2 = (pDelaySample - psamps) >> 31; // 0xffffffff if pDelaySample < psamps, 0 otherwise
|
|
nMask2 &= nDelaySizeP1; // nDelaySizeP1 if pDelaySample < psamps, 0 otherwise
|
|
pDelaySample += nMask2;
|
|
|
|
// output with gain
|
|
int nValue = ( (out * outgain) >> PBITS );
|
|
if ( MODE == MM_ADD )
|
|
{
|
|
*pOut++ += nValue;
|
|
}
|
|
else
|
|
{
|
|
Assert( MODE == MM_REPLACE );
|
|
*pOut++ = nValue;
|
|
}
|
|
}
|
|
|
|
CircularBufferSample_t * RESTRICT pDelaySampleA = pDelaySample;
|
|
CircularBufferSample_t * RESTRICT pDelaySampleB = pDelaySample - 1;
|
|
CircularBufferSample_t * RESTRICT pDelaySampleC = pDelaySample - 2;
|
|
CircularBufferSample_t * RESTRICT pDelaySampleD = pDelaySample - 3;
|
|
// pDelaySampleA is already in the correct range
|
|
int nMask2B = (pDelaySampleB - psamps) >> 31;
|
|
nMask2B &= nDelaySizeP1;
|
|
pDelaySampleB += nMask2B;
|
|
int nMask2C = (pDelaySampleC - psamps) >> 31;
|
|
nMask2C &= nDelaySizeP1;
|
|
pDelaySampleC += nMask2C;
|
|
int nMask2D = (pDelaySampleD - psamps) >> 31;
|
|
nMask2D &= nDelaySizeP1;
|
|
pDelaySampleD += nMask2D;
|
|
|
|
// pDelaySampleD is the lowest address used, that's going to be the first one to cross the wrap-around
|
|
// We could have a more optimized path by getting rid of the wrap around when we are inside the safe zone.
|
|
// It would make the code more complicated though (safe zone, then unsafe for up to 2*4 samples, safe zone again...
|
|
while ( nCount >= 4 )
|
|
{
|
|
nCount -= 4;
|
|
// delay output is filter input
|
|
// Here is the trick, pDelaySampleA, B, C and D are all offseted by 4. Because read and write are only separated by one sample,
|
|
// What we read with A, we will write it with B, B to C, C to D. So we can avoid wrap-around for A, B and C as the values are already known.
|
|
// Only D will have to be calculated (the smaller value of the set of 4, not yet calculated).
|
|
CircularBufferSample_t *pInputDelaySampleD = pDelaySampleD + nDelaySize;
|
|
int nMask1D = (pSampsPDelaySize - pInputDelaySampleD) >> 31;
|
|
nMask1D &= nDelaySizeP1;
|
|
pInputDelaySampleD -= nMask1D;
|
|
|
|
int sDA = *pDelaySampleB; // Read A (from written position B)
|
|
int sDB = *pDelaySampleC; // Read B (from written position C)
|
|
int sDC = *pDelaySampleD; // Read C (from written position D)
|
|
int sDD = *pInputDelaySampleD;
|
|
|
|
int s0A = *pIn + (( fbgain * sDA ) >> PBITS);
|
|
int s0B = *(pIn + SOURCE_INCREMENT) + (( fbgain * sDB ) >> PBITS);
|
|
int s0C = *(pIn + 2 * SOURCE_INCREMENT) + (( fbgain * sDC ) >> PBITS);
|
|
int s0D = *(pIn + 3 * SOURCE_INCREMENT) + (( fbgain * sDD ) >> PBITS);
|
|
|
|
// write to delay
|
|
*pDelaySampleA = s0A;
|
|
*pDelaySampleB = s0B;
|
|
*pDelaySampleC = s0C;
|
|
*pDelaySampleD = s0D;
|
|
|
|
// Assuming that the addresses were aligned, on PS3 we could load this on VMX
|
|
// Add the filter normally on an integer VMX base. And store the delay in an aligned manner.
|
|
int outA = ( ( -fbgain * s0A ) >> PBITS ) + sDA;
|
|
int outB = ( ( -fbgain * s0B ) >> PBITS ) + sDB;
|
|
int outC = ( ( -fbgain * s0C ) >> PBITS ) + sDC;
|
|
int outD = ( ( -fbgain * s0D ) >> PBITS ) + sDD;
|
|
pIn += 4 * SOURCE_INCREMENT; // Because pIn has both left AND right (but we read only one).
|
|
|
|
// update delay pointers, decrement pointer and fix up on buffer boundary
|
|
// when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize
|
|
pDelaySampleA -= 4;
|
|
pDelaySampleB -= 4;
|
|
pDelaySampleC -= 4;
|
|
pDelaySampleD -= 4;
|
|
|
|
// 4 ops instead of 1 op + 1 branch (and potentially one more op)
|
|
int nMask2A = (pDelaySampleA - psamps) >> 31; // 0xffffffff if pDelaySample < psamps, 0 otherwise
|
|
nMask2A &= nDelaySizeP1; // nDelaySizeP1 if pDelaySample < psamps, 0 otherwise
|
|
pDelaySampleA += nMask2A;
|
|
nMask2B = (pDelaySampleB - psamps) >> 31;
|
|
nMask2B &= nDelaySizeP1;
|
|
pDelaySampleB += nMask2B;
|
|
nMask2C = (pDelaySampleC - psamps) >> 31;
|
|
nMask2C &= nDelaySizeP1;
|
|
pDelaySampleC += nMask2C;
|
|
nMask2D = (pDelaySampleD - psamps) >> 31;
|
|
nMask2D &= nDelaySizeP1;
|
|
pDelaySampleD += nMask2D;
|
|
|
|
// output with gain
|
|
int nValueA = ( (outA * outgain) >> PBITS );
|
|
int nValueB = ( (outB * outgain) >> PBITS );
|
|
int nValueC = ( (outC * outgain) >> PBITS );
|
|
int nValueD = ( (outD * outgain) >> PBITS );
|
|
if ( MODE == MM_ADD )
|
|
{
|
|
*pOut += nValueA;
|
|
*(pOut + 1) += nValueB;
|
|
*(pOut + 2) += nValueC;
|
|
*(pOut + 3) += nValueD;
|
|
}
|
|
else
|
|
{
|
|
Assert( MODE == MM_REPLACE );
|
|
*pOut = nValueA;
|
|
*(pOut + 1) = nValueB;
|
|
*(pOut + 2) = nValueC;
|
|
*(pOut + 3) = nValueD;
|
|
}
|
|
pOut += 4;
|
|
|
|
if ( ( nCount % ( CACHE_LINE_SIZE / ( 4 * sizeof(portable_samplepair_t) ) ) ) == 0 )
|
|
{
|
|
// Prefetch the next input samples (output samples are already in cache anyway)
|
|
// Remove the conservative prefetching to make the loop a bit faster
|
|
PREFETCH_128( pIn, 4 * CACHE_LINE_SIZE );
|
|
if ( ( nCount % ( CACHE_LINE_SIZE / ( 4 * sizeof(*pDelaySampleD) ) ) ) == 0 )
|
|
{
|
|
// Delay sample is not prefetched as often.
|
|
PREFETCH_128( pDelaySampleD, -2 * CACHE_LINE_SIZE ); // We often read the delay sample just before the one that we are writing
|
|
}
|
|
}
|
|
}
|
|
|
|
*ppsamp = pDelaySampleA;
|
|
}
|
|
|
|
inline int DelayAllpass_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, CircularBufferSample_t *psamps, CircularBufferSample_t **ppsamp, int fbgain, int outgain, int in )
|
|
{
|
|
int out, s0, sD;
|
|
int sDnew;
|
|
|
|
// crossfade from t to tnew tap. xfade is 0..PMAX
|
|
|
|
sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
|
|
sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
|
|
sD = sD + (((sDnew - sD) * xf) >> PBITS);
|
|
|
|
s0 = in + (( fbgain * sD ) >> PBITS);
|
|
|
|
out = ( ( -fbgain * s0 ) >> PBITS ) + sD;
|
|
**ppsamp = s0;
|
|
DlyUpdate ( delaysize, psamps, ppsamp );
|
|
|
|
return ( (out * outgain) >> PBITS );
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
// fixed point math for real-time wave table traversing, pitch shifting, resampling
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define FIX20_BITS 20 // 20 bits of fractional part
|
|
#define FIX20_SCALE (1 << FIX20_BITS)
|
|
|
|
#define FIX20_INTMAX ((1 << (32 - FIX20_BITS))-1) // maximum step integer
|
|
|
|
#define FLOAT_TO_FIX20(a) ((int)((a) * (float)FIX20_SCALE)) // convert float to fixed point
|
|
#define INT_TO_FIX20(a) (((int)(a)) << FIX20_BITS) // convert int to fixed point
|
|
#define FIX20_TO_FLOAT(a) ((float)(a) / (float)FIX20_SCALE) // convert fix20 to float
|
|
#define FIX20_INTPART(a) (((int)(a)) >> FIX20_BITS) // get integer part of fixed point
|
|
#define FIX20_FRACPART(a) ((a) - (((a) >> FIX20_BITS) << FIX20_BITS)) // get fractional part of fixed point
|
|
|
|
#define FIX20_FRACTION(a,b) (FIX(a)/(b)) // convert int a to fixed point, divide by b
|
|
|
|
typedef int fix20int;
|
|
|
|
/////////////////////////////////
|
|
// DSP processor parameter block
|
|
/////////////////////////////////
|
|
|
|
// NOTE: these prototypes must match the XXX_Params ( prc_t *pprc ) and XXX_GetNext ( XXX_t *p, int x ) functions
|
|
|
|
typedef void * (*prc_Param_t)( void *pprc ); // individual processor allocation functions
|
|
typedef int (*prc_GetNext_t) ( void *pdata, int x ); // get next function for processor
|
|
typedef int (*prc_GetNextN_t) ( void *pdata, portable_samplepair_t *pbuffer, int SampleCount, int op); // batch version of getnext
|
|
typedef void (*prc_Free_t) ( void *pdata ); // free function for processor
|
|
typedef void (*prc_Mod_t) (void *pdata, float v); // modulation function for processor
|
|
|
|
#define OP_LEFT 0 // batch process left channel in place
|
|
#define OP_RIGHT 1 // batch process right channel in place
|
|
#define OP_LEFT_DUPLICATE 2 // batch process left channel in place, duplicate to right channel
|
|
|
|
#define PRC_NULL 0 // pass through - must be 0
|
|
#define PRC_DLY 1 // simple feedback reverb
|
|
#define PRC_RVA 2 // parallel reverbs
|
|
#define PRC_FLT 3 // lowpass or highpass filter
|
|
#define PRC_CRS 4 // chorus
|
|
#define PRC_PTC 5 // pitch shifter
|
|
#define PRC_ENV 6 // adsr envelope
|
|
#define PRC_LFO 7 // lfo
|
|
#define PRC_EFO 8 // envelope follower
|
|
#define PRC_MDY 9 // mod delay
|
|
#define PRC_DFR 10 // diffusor - n series allpass delays
|
|
#define PRC_AMP 11 // amplifier with distortion
|
|
|
|
#define QUA_LO 0 // quality of filter or reverb. Must be 0,1,2,3.
|
|
#define QUA_MED 1
|
|
#define QUA_HI 2
|
|
#define QUA_VHI 3
|
|
#define QUA_MAX QUA_VHI
|
|
|
|
#define CPRCPARAMS 16 // up to 16 floating point params for each processor type
|
|
|
|
// processor definition - one for each running instance of a dsp processor
|
|
|
|
struct prc_t
|
|
{
|
|
int type; // PRC type
|
|
|
|
float prm[CPRCPARAMS]; // dsp processor parameters - array of floats
|
|
|
|
prc_Param_t pfnParam; // allocation function - takes ptr to prc, returns ptr to specialized data struct for proc type
|
|
prc_GetNext_t pfnGetNext; // get next function
|
|
prc_GetNextN_t pfnGetNextN; // batch version of get next
|
|
prc_Free_t pfnFree; // free function
|
|
prc_Mod_t pfnMod; // modulation function
|
|
|
|
void *pdata; // processor state data - ie: pdly, pflt etc.
|
|
};
|
|
|
|
// processor parameter ranges - for validating parameters during allocation of new processor
|
|
|
|
typedef struct prm_rng_t
|
|
{
|
|
int iprm; // parameter index
|
|
float lo; // min value of parameter
|
|
float hi; // max value of parameter
|
|
} prm_rng_s;
|
|
|
|
void PRC_CheckParams ( prc_t *pprc, prm_rng_t *prng );
|
|
|
|
///////////
|
|
// Filters
|
|
///////////
|
|
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CFLTS 128 // max number of filters simultaneously active
|
|
#else
|
|
#define CFLTS 64 // max number of filters simultaneously active
|
|
#endif
|
|
#define FLT_M 12 // max order of any filter
|
|
|
|
#define FLT_LP 0 // lowpass filter
|
|
#define FLT_HP 1 // highpass filter
|
|
#define FLT_BP 2 // bandpass filter
|
|
#define FTR_MAX FLT_BP
|
|
|
|
// flt parameters
|
|
|
|
struct flt_t
|
|
{
|
|
bool fused; // true if slot in use
|
|
|
|
int b[FLT_M+1]; // filter numerator parameters (convert 0.0-1.0 to 0-PMAX representation)
|
|
int a[FLT_M+1]; // filter denominator parameters (convert 0.0-1.0 to 0-PMAX representation)
|
|
int w[FLT_M+1]; // filter state - samples (dimension of max (M, L))
|
|
int L; // filter order numerator (dimension of a[M+1])
|
|
int M; // filter order denominator (dimension of b[L+1])
|
|
int N; // # of series sections - 1 (0 = 1 section, 1 = 2 sections etc)
|
|
|
|
flt_t *pf1; // series cascaded versions of filter
|
|
flt_t *pf2;
|
|
flt_t *pf3;
|
|
};
|
|
|
|
// flt flts
|
|
|
|
flt_t flts[CFLTS];
|
|
|
|
void FLT_Init ( flt_t *pf ) { if ( pf ) Q_memset ( pf, 0, sizeof (flt_t) ); }
|
|
void FLT_InitAll ( void ) { for ( int i = 0 ; i < CFLTS; i++ ) FLT_Init ( &flts[i] ); }
|
|
|
|
void FLT_Free ( flt_t *pf )
|
|
{
|
|
if ( pf )
|
|
{
|
|
if (pf->pf1)
|
|
Q_memset ( pf->pf1, 0, sizeof (flt_t) );
|
|
|
|
if (pf->pf2)
|
|
Q_memset ( pf->pf2, 0, sizeof (flt_t) );
|
|
|
|
if (pf->pf3)
|
|
Q_memset ( pf->pf3, 0, sizeof (flt_t) );
|
|
|
|
Q_memset ( pf, 0, sizeof (flt_t) );
|
|
}
|
|
}
|
|
|
|
void FLT_FreeAll ( void ) { for (int i = 0 ; i < CFLTS; i++) FLT_Free ( &flts[i] ); }
|
|
|
|
|
|
// find a free filter from the filter pool
|
|
// initialize filter numerator, denominator b[0..M], a[0..L]
|
|
// gain scales filter numerator
|
|
// N is # of series sections - 1
|
|
|
|
flt_t * FLT_Alloc ( int N, int M, int L, int *a, int *b, float gain )
|
|
{
|
|
int i, j;
|
|
flt_t *pf = NULL;
|
|
|
|
for (i = 0; i < CFLTS; i++)
|
|
{
|
|
if ( !flts[i].fused )
|
|
{
|
|
pf = &flts[i];
|
|
|
|
// transfer filter params into filter struct
|
|
pf->M = M;
|
|
pf->L = L;
|
|
pf->N = N;
|
|
|
|
for (j = 0; j <= M; j++)
|
|
pf->a[j] = a[j];
|
|
|
|
for (j = 0; j <= L; j++)
|
|
pf->b[j] = (int)((float)(b[j]) * gain);
|
|
|
|
pf->pf1 = NULL;
|
|
pf->pf2 = NULL;
|
|
pf->pf3 = NULL;
|
|
|
|
pf->fused = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert(pf); // make sure we're not trying to alloc more than CFLTS flts
|
|
|
|
return pf;
|
|
}
|
|
|
|
void FLT_Print( const flt_t & filter, int nIndentation )
|
|
{
|
|
const char * pIndent = GetIndentationText( nIndentation );
|
|
DevMsg( "%sFilter: %p [Addr]\n", pIndent, &filter );
|
|
DevMsg( "%sb[] = ", pIndent );
|
|
for ( int i = 0 ; i < FLT_M + 1 ; ++i )
|
|
{
|
|
DevMsg( "%d ", filter.b[i] );
|
|
}
|
|
DevMsg( "\n" );
|
|
DevMsg( "%sa[] = ", pIndent );
|
|
for ( int i = 0 ; i < FLT_M + 1 ; ++i )
|
|
{
|
|
DevMsg( "%d ", filter.a[i] );
|
|
}
|
|
DevMsg( "\n" );
|
|
DevMsg( "%sw[] = ", pIndent );
|
|
for ( int i = 0 ; i < FLT_M + 1 ; ++i )
|
|
{
|
|
DevMsg( "%d ", filter.w[i] );
|
|
}
|
|
DevMsg( "\n" );
|
|
|
|
DevMsg( "%sL: %d\n", pIndent, filter.L );
|
|
DevMsg( "%sM: %d\n", pIndent, filter.M );
|
|
DevMsg( "%sN: %d\n", pIndent, filter.N );
|
|
|
|
DevMsg( "%spf1:", pIndent );
|
|
if ( filter.pf1 != NULL )
|
|
{
|
|
FLT_Print( *filter.pf1, nIndentation + 1 );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "NULL\n" );
|
|
}
|
|
DevMsg( "%spf2:", pIndent );
|
|
if ( filter.pf2 != NULL )
|
|
{
|
|
FLT_Print( *filter.pf2, nIndentation + 1 );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "NULL\n" );
|
|
}
|
|
DevMsg( "%spf3:", pIndent );
|
|
if ( filter.pf3 != NULL )
|
|
{
|
|
FLT_Print( *filter.pf3, nIndentation + 1 );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "NULL\n" );
|
|
}
|
|
}
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
flt_t * FLT_Clone( flt_t * pOldFilter )
|
|
{
|
|
if ( pOldFilter == NULL )
|
|
{
|
|
return NULL;
|
|
}
|
|
// Use a gain of 1.0 to make sure we keep the same data.
|
|
flt_t * pNewFilter = FLT_Alloc( pOldFilter->N, pOldFilter->M, pOldFilter->L, pOldFilter->a, pOldFilter->b, 1.0f );
|
|
|
|
// Copy w
|
|
for ( int i = 0 ; i < FLT_M+1 ; ++i )
|
|
{
|
|
pNewFilter->w[i] = pOldFilter->w[i];
|
|
}
|
|
|
|
// Recursive deep-copy
|
|
pNewFilter->pf1 = FLT_Clone( pOldFilter->pf1 );
|
|
pNewFilter->pf2 = FLT_Clone( pOldFilter->pf2 );
|
|
pNewFilter->pf3 = FLT_Clone( pOldFilter->pf3 );
|
|
|
|
return pNewFilter;
|
|
}
|
|
|
|
void FLT_Compare( const flt_t & leftFilter, const flt_t & rightFilter )
|
|
{
|
|
Assert ( &leftFilter != &rightFilter );
|
|
for ( int i = 0 ; i < FLT_M + 1 ; ++i )
|
|
{
|
|
Assert( leftFilter.b[i] == rightFilter.b[i] );
|
|
}
|
|
for ( int i = 0 ; i < FLT_M + 1 ; ++i )
|
|
{
|
|
Assert( leftFilter.a[i] == rightFilter.a[i] );
|
|
}
|
|
for ( int i = 0 ; i < FLT_M + 1 ; ++i )
|
|
{
|
|
Assert( leftFilter.w[i] == rightFilter.w[i] );
|
|
}
|
|
|
|
Assert( leftFilter.L == rightFilter.L );
|
|
Assert( leftFilter.M == rightFilter.M );
|
|
Assert( leftFilter.N == rightFilter.N );
|
|
|
|
if ( CheckPointers( leftFilter.pf1, rightFilter.pf1 ) )
|
|
{
|
|
FLT_Compare( *leftFilter.pf1, *rightFilter.pf1 );
|
|
}
|
|
if ( CheckPointers( leftFilter.pf2, rightFilter.pf2 ) )
|
|
{
|
|
FLT_Compare( *leftFilter.pf2, *rightFilter.pf2 );
|
|
}
|
|
if ( CheckPointers( leftFilter.pf3, rightFilter.pf3 ) )
|
|
{
|
|
FLT_Compare( *leftFilter.pf3, *rightFilter.pf3 );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// convert filter params cutoff and type into
|
|
// iir transfer function params M, L, a[], b[]
|
|
|
|
// iir filter, 1st order, transfer function is H(z) = b0 + b1 Z^-1 / a0 + a1 Z^-1
|
|
// or H(z) = b0 - b1 Z^-1 / a0 + a1 Z^-1 for lowpass
|
|
|
|
// design cutoff filter at 3db (.5 gain) p579
|
|
|
|
void FLT_Design_3db_IIR ( float cutoff, float ftype, int *pM, int *pL, int *a, int *b )
|
|
{
|
|
// ftype: FLT_LP, FLT_HP, FLT_BP
|
|
|
|
double Wc = 2.0 * M_PI * cutoff / SOUND_DMA_SPEED; // radians per sample
|
|
double Oc;
|
|
double fa;
|
|
double fb;
|
|
|
|
// calculations:
|
|
// Wc = 2pi * fc/44100 convert to radians
|
|
// Oc = tan (Wc/2) * Gc / sqt ( 1 - Gc^2) get analog version, low pass
|
|
// Oc = tan (Wc/2) * (sqt (1 - Gc^2)) / Gc analog version, high pass
|
|
// Gc = 10 ^ (-Ac/20) gain at cutoff. Ac = 3db, so Gc^2 = 0.5
|
|
// a = ( 1 - Oc ) / ( 1 + Oc )
|
|
// b = ( 1 - a ) / 2
|
|
|
|
Oc = tan ( Wc / 2.0 );
|
|
|
|
fa = ( 1.0 - Oc ) / ( 1.0 + Oc );
|
|
|
|
fb = ( 1.0 - fa ) / 2.0;
|
|
|
|
if ( ftype == FLT_HP )
|
|
fb = ( 1.0 + fa ) / 2.0;
|
|
|
|
a[0] = 0; // a0 always ignored
|
|
a[1] = (int)( -fa * PMAX ); // quantize params down to 0-PMAX >> PBITS
|
|
b[0] = (int)( fb * PMAX );
|
|
b[1] = b[0];
|
|
|
|
if ( ftype == FLT_HP )
|
|
b[1] = -b[1];
|
|
|
|
*pM = *pL = 1;
|
|
|
|
return;
|
|
}
|
|
|
|
// filter parameter order
|
|
|
|
typedef enum
|
|
{
|
|
flt_iftype,
|
|
flt_icutoff,
|
|
flt_iqwidth,
|
|
flt_iquality,
|
|
flt_igain,
|
|
|
|
flt_cparam // # of params
|
|
} flt_e;
|
|
|
|
// filter parameter ranges
|
|
|
|
prm_rng_t flt_rng[] = {
|
|
|
|
{flt_cparam, 0, 0}, // first entry is # of parameters
|
|
|
|
{flt_iftype, 0, FTR_MAX}, // filter type FLT_LP, FLT_HP, FLT_BP
|
|
{flt_icutoff, 10, 22050}, // cutoff frequency in hz at -3db gain
|
|
{flt_iqwidth, 0, 11025}, // width of BP (cut in starts at cutoff)
|
|
{flt_iquality, 0, QUA_MAX}, // QUA_LO, _MED, _HI, _VHI = # of series sections
|
|
{flt_igain, 0.0, 10.0}, // output gain 0-10.0
|
|
};
|
|
|
|
|
|
// convert prc float params to iir filter params, alloc filter and return ptr to it
|
|
// filter quality set by prc quality - 0,1,2
|
|
|
|
flt_t * FLT_Params ( prc_t *pprc )
|
|
{
|
|
float qual = pprc->prm[flt_iquality];
|
|
float cutoff = pprc->prm[flt_icutoff];
|
|
float ftype = pprc->prm[flt_iftype];
|
|
float qwidth = pprc->prm[flt_iqwidth];
|
|
float gain = pprc->prm[flt_igain];
|
|
|
|
int L = 0; // numerator order
|
|
int M = 0; // denominator order
|
|
int b[FLT_M+1]; // numerator params 0..PMAX
|
|
int b_scaled[FLT_M+1]; // gain scaled numerator
|
|
int a[FLT_M+1]; // denominator params 0..PMAX
|
|
|
|
int L_bp = 0; // bandpass numerator order
|
|
int M_bp = 0; // bandpass denominator order
|
|
int b_bp[FLT_M+1]; // bandpass numerator params 0..PMAX
|
|
int b_bp_scaled[FLT_M+1]; // gain scaled numerator
|
|
int a_bp[FLT_M+1]; // bandpass denominator params 0..PMAX
|
|
|
|
int N; // # of series sections
|
|
bool bpass = false;
|
|
|
|
// if qwidth > 0 then alloc bandpass filter (pf is lowpass)
|
|
|
|
if ( qwidth > 0.0 )
|
|
bpass = true;
|
|
|
|
if (bpass)
|
|
{
|
|
ftype = FLT_LP;
|
|
}
|
|
|
|
// low pass and highpass filter design
|
|
|
|
// 1st order IIR filter, 3db cutoff at fc
|
|
|
|
if ( bpass )
|
|
{
|
|
// highpass section
|
|
|
|
FLT_Design_3db_IIR ( cutoff, FLT_HP, &M_bp, &L_bp, a_bp, b_bp );
|
|
M_bp = iclamp (M_bp, 1, FLT_M);
|
|
L_bp = iclamp (L_bp, 1, FLT_M);
|
|
cutoff += qwidth;
|
|
}
|
|
|
|
// lowpass section
|
|
|
|
FLT_Design_3db_IIR ( cutoff, (int)ftype, &M, &L, a, b );
|
|
|
|
M = iclamp (M, 1, FLT_M);
|
|
L = iclamp (L, 1, FLT_M);
|
|
|
|
// quality = # of series sections - 1
|
|
|
|
N = iclamp ((int)qual, 0, 3);
|
|
|
|
// make sure we alloc at least 2 filters
|
|
|
|
if (bpass)
|
|
N = MAX(N, 1);
|
|
|
|
flt_t *pf0 = NULL;
|
|
flt_t *pf1 = NULL;
|
|
flt_t *pf2 = NULL;
|
|
flt_t *pf3 = NULL;
|
|
|
|
// scale b numerators with gain - only scale for first filter if series filters
|
|
|
|
for (int i = 0; i < FLT_M; i++)
|
|
{
|
|
b_bp_scaled[i] = (int)((float)(b_bp[i]) * gain );
|
|
b_scaled[i] = (int)((float)(b[i]) * gain );
|
|
}
|
|
|
|
if (bpass)
|
|
{
|
|
// 1st filter is lowpass
|
|
|
|
pf0 = FLT_Alloc ( N, M_bp, L_bp, a_bp, b_bp_scaled, 1.0 );
|
|
}
|
|
else
|
|
{
|
|
pf0 = FLT_Alloc ( N, M, L, a, b_scaled, 1.0 );
|
|
}
|
|
|
|
// allocate series filters
|
|
|
|
if (pf0)
|
|
{
|
|
switch (N)
|
|
{
|
|
case 3:
|
|
// alloc last filter as lowpass also if FLT_BP
|
|
if (bpass)
|
|
pf3 = FLT_Alloc ( 0, M_bp, L_bp, a_bp, b_bp, 1.0 );
|
|
else
|
|
pf3 = FLT_Alloc ( 0, M, L, a, b, 1.0 );
|
|
case 2:
|
|
pf2 = FLT_Alloc ( 0, M, L, a, b, 1.0 );
|
|
case 1:
|
|
pf1 = FLT_Alloc ( 0, M, L, a, b, 1.0 );
|
|
case 0:
|
|
break;
|
|
}
|
|
|
|
pf0->pf1 = pf1;
|
|
pf0->pf2 = pf2;
|
|
pf0->pf3 = pf3;
|
|
}
|
|
|
|
return pf0;
|
|
}
|
|
|
|
inline void * FLT_VParams ( void *p )
|
|
{
|
|
PRC_CheckParams( (prc_t *)p, flt_rng);
|
|
return (void *) FLT_Params ((prc_t *)p);
|
|
}
|
|
|
|
inline void FLT_Mod ( void *p, float v ) { return; }
|
|
|
|
// get next filter value for filter pf and input x
|
|
|
|
inline int FLT_GetNext ( flt_t *pf, int x )
|
|
{
|
|
flt_t *pf1;
|
|
flt_t *pf2;
|
|
flt_t *pf3;
|
|
int y;
|
|
|
|
switch( pf->N )
|
|
{
|
|
default:
|
|
case 0:
|
|
return IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x);
|
|
case 1:
|
|
pf1 = pf->pf1;
|
|
|
|
y = IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x);
|
|
return IIRFilter_Update_Order1(pf1->a, pf1->L, pf1->b, pf1->w, y);
|
|
case 2:
|
|
pf1 = pf->pf1;
|
|
pf2 = pf->pf2;
|
|
|
|
y = IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x);
|
|
y = IIRFilter_Update_Order1(pf1->a, pf1->L, pf1->b, pf1->w, y);
|
|
return IIRFilter_Update_Order1(pf2->a, pf2->L, pf2->b, pf2->w, y);
|
|
case 3:
|
|
pf1 = pf->pf1;
|
|
pf2 = pf->pf2;
|
|
pf3 = pf->pf3;
|
|
|
|
y = IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x);
|
|
y = IIRFilter_Update_Order1(pf1->a, pf1->L, pf1->b, pf1->w, y);
|
|
y = IIRFilter_Update_Order1(pf2->a, pf2->L, pf2->b, pf2->w, y);
|
|
return IIRFilter_Update_Order1(pf3->a, pf3->L, pf3->b, pf3->w, y);
|
|
}
|
|
}
|
|
|
|
// batch version for performance
|
|
|
|
inline void FLT_GetNextN( flt_t *pflt, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb = pbuffer;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb->left = FLT_GetNext( pflt, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb->right = FLT_GetNext( pflt, pb->right );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb->left = pb->right = FLT_GetNext( pflt, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Positional updaters for pitch shift etc
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// looping position within a wav, with integer and fractional parts
|
|
// used for pitch shifting, upsampling/downsampling
|
|
// 20 bits of fraction, 8+ bits of integer
|
|
|
|
struct pos_t
|
|
{
|
|
|
|
fix20int step; // wave table whole and fractional step value
|
|
fix20int cstep; // current cumulative step value
|
|
int pos; // current position within wav table
|
|
|
|
int D; // max dimension of array w[0...D] ie: # of samples = D+1
|
|
};
|
|
|
|
// circular wrap of pointer p, relative to array w
|
|
// D max buffer index w[0...D] (count of samples in buffer is D+1)
|
|
// i circular index
|
|
|
|
inline void POS_Wrap ( int D, int *i )
|
|
{
|
|
if ( *i > D )
|
|
*i -= D + 1; // when *pi = D + 1, it wraps around to *pi = 0
|
|
|
|
if ( *i < 0 )
|
|
*i += D + 1; // when *pi = - 1, it wraps around to *pi = D
|
|
}
|
|
|
|
// set initial update value - fstep can have no more than 8 bits of integer and 20 bits of fract
|
|
// D is array max dimension w[0...D] (ie: size D+1)
|
|
// w is ptr to array
|
|
// p is ptr to pos_t to initialize
|
|
|
|
inline void POS_Init( pos_t *p, int D, float fstep )
|
|
{
|
|
float step = fstep;
|
|
|
|
// make sure int part of step is capped at fix20_intmax
|
|
|
|
if ((int)step > FIX20_INTMAX)
|
|
step = (step - (int)step) + FIX20_INTMAX;
|
|
|
|
p->step = FLOAT_TO_FIX20(step); // convert fstep to fixed point
|
|
p->cstep = 0;
|
|
p->pos = 0; // current update value
|
|
|
|
p->D = D; // always init to end value, in case we're stepping backwards
|
|
}
|
|
|
|
// change step value - this is an instantaneous change, not smoothed.
|
|
|
|
inline void POS_ChangeVal( pos_t *p, float fstepnew )
|
|
{
|
|
p->step = FLOAT_TO_FIX20( fstepnew ); // convert fstep to fixed point
|
|
}
|
|
|
|
// return current integer position, then update internal position value
|
|
|
|
inline int POS_GetNext ( pos_t *p )
|
|
{
|
|
|
|
//float f = FIX20_TO_FLOAT(p->cstep);
|
|
//int i1 = FIX20_INTPART(p->cstep);
|
|
//float f1 = FIX20_TO_FLOAT(FIX20_FRACPART(p->cstep));
|
|
//float f2 = FIX20_TO_FLOAT(p->step);
|
|
|
|
p->cstep += p->step; // update accumulated fraction step value (fixed point)
|
|
p->pos += FIX20_INTPART( p->cstep ); // update pos with integer part of accumulated step
|
|
p->cstep = FIX20_FRACPART( p->cstep ); // throw away the integer part of accumulated step
|
|
|
|
// wrap pos around either end of buffer if needed
|
|
|
|
POS_Wrap(p->D, &(p->pos));
|
|
|
|
// make sure returned position is within array bounds
|
|
|
|
Assert (p->pos <= p->D);
|
|
|
|
return p->pos;
|
|
}
|
|
|
|
void POS_Print( const pos_t & pos, int nIndentation )
|
|
{
|
|
const char * pIndent = GetIndentationText( nIndentation);
|
|
DevMsg( "%sPos: %p [Addr]\n", pIndent, &pos );
|
|
DevMsg( "%sstep: %d\n", pIndent, pos.step );
|
|
DevMsg( "%scstep: %d\n", pIndent, pos.cstep );
|
|
DevMsg( "%spos: %d\n", pIndent, pos.pos );
|
|
DevMsg( "%sD: %d\n", pIndent, pos.D );
|
|
}
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
void POS_Compare( const pos_t & leftPos, const pos_t & rightPos )
|
|
{
|
|
Assert ( &leftPos != &rightPos );
|
|
|
|
Assert( leftPos.step == rightPos.step );
|
|
Assert( leftPos.cstep == rightPos.cstep );
|
|
Assert( leftPos.pos == rightPos.pos );
|
|
Assert( leftPos.D == rightPos.D );
|
|
}
|
|
#endif
|
|
|
|
// 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;
|
|
}
|
|
|
|
void POS_ONE_Print( const pos_one_t & posOne, int nIndentation )
|
|
{
|
|
const char * pIndent = GetIndentationText( nIndentation );
|
|
DevMsg( "%sPosOne: %p [Addr]\n", pIndent, &posOne );
|
|
|
|
POS_Print( posOne.p, nIndentation + 1 );
|
|
DevMsg( "%sfhitend: %d\n", pIndent, posOne.fhitend );
|
|
}
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
void POS_ONE_Compare( const pos_one_t & leftPosOne, const pos_one_t & rightPosOne )
|
|
{
|
|
Assert ( &leftPosOne != &rightPosOne );
|
|
|
|
POS_Compare( leftPosOne.p, rightPosOne.p );
|
|
Assert( leftPosOne.fhitend == rightPosOne.fhitend );
|
|
}
|
|
#endif
|
|
|
|
/////////////////////
|
|
// Reverbs and delays
|
|
/////////////////////
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CDLYS 256 // max delay lines active. Also used for lfos. Need more of them when cloning all the buffers for consistency checking.
|
|
#else
|
|
#define CDLYS 128 // max delay lines active. Also used for lfos.
|
|
#endif
|
|
|
|
#define DLY_PLAIN 0 // single feedback loop
|
|
#define DLY_ALLPASS 1 // feedback and feedforward loop - flat frequency response (diffusor)
|
|
#define DLY_LOWPASS 2 // lowpass filter in feedback loop
|
|
#define DLY_LINEAR 3 // linear delay, no feedback, unity gain
|
|
#define DLY_FLINEAR 4 // linear delay with lowpass filter and output gain
|
|
#define DLY_LOWPASS_4TAP 5 // lowpass filter in feedback loop, 4 delay taps
|
|
#define DLY_PLAIN_4TAP 6 // single feedback loop, 4 delay taps
|
|
|
|
#define DLY_MAX DLY_PLAIN_4TAP
|
|
|
|
#define DLY_HAS_MULTITAP(a) ((a) == DLY_LOWPASS_4TAP || (a) == DLY_PLAIN_4TAP)
|
|
#define DLY_HAS_FILTER(a) ((a) == DLY_FLINEAR || (a) == DLY_LOWPASS || (a) == DLY_LOWPASS_4TAP)
|
|
|
|
#define DLY_TAP_FEEDBACK_GAIN 0.25 // drop multitap feedback to compensate for sum of taps in dly_*multitap()
|
|
|
|
#define DLY_NORMALIZING_REDUCTION_MAX 0.25 // don't reduce gain (due to feedback) below N% of original gain
|
|
|
|
// delay line
|
|
|
|
struct dly_t
|
|
{
|
|
bool fused; // true if dly is in use
|
|
int type; // delay type
|
|
|
|
int D; // delay size, in samples
|
|
int t; // current tap, <= D
|
|
int tnew; // crossfading to tnew
|
|
int xf; // crossfade value of t (0..PMAX)
|
|
int t1,t2,t3; // additional taps for multi-tap delays
|
|
int a1,a2,a3; // feedback values for taps
|
|
int D0; // original delay size (only relevant if calling DLY_ChangeVal)
|
|
CircularBufferSample_t *p; // circular buffer pointer
|
|
CircularBufferSample_t *w; // array of samples
|
|
|
|
int a; // feedback value 0..PMAX,normalized to 0-1.0
|
|
int b; // gain value 0..PMAX, normalized to 0-1.0
|
|
|
|
flt_t *pflt; // pointer to filter, if type DLY_LOWPASS
|
|
};
|
|
|
|
dly_t dlys[CDLYS]; // delay lines
|
|
|
|
void DLY_Init ( dly_t *pdly ) { if ( pdly ) Q_memset( pdly, 0, sizeof (dly_t)); }
|
|
void DLY_InitAll ( void ) { for (int i = 0 ; i < CDLYS; i++) DLY_Init ( &dlys[i] ); }
|
|
void DLY_Free ( dly_t *pdly )
|
|
{
|
|
// free memory buffer
|
|
|
|
if ( pdly )
|
|
{
|
|
FLT_Free ( pdly->pflt );
|
|
|
|
if ( pdly->w )
|
|
{
|
|
delete[] pdly->w;
|
|
}
|
|
|
|
// free dly slot
|
|
|
|
Q_memset ( pdly, 0, sizeof (dly_t) );
|
|
}
|
|
}
|
|
|
|
|
|
void DLY_FreeAll ( void ) { for (int i = 0; i < CDLYS; i++ ) DLY_Free ( &dlys[i] ); }
|
|
|
|
// return adjusted feedback value for given dly
|
|
// such that decay time is same as that for dmin and fbmin
|
|
|
|
// dmin - minimum delay
|
|
// fbmin - minimum feedback
|
|
// dly - delay to match decay to dmin, fbmin
|
|
|
|
float DLY_NormalizeFeedback ( int dmin, float fbmin, int dly )
|
|
{
|
|
// minimum decay time T to -60db for a simple reverb is:
|
|
|
|
// Tmin = (ln 10^-3 / Ln fbmin) * (Dmin / fs)
|
|
|
|
// where fs = sample frequency
|
|
|
|
// similarly,
|
|
|
|
// Tdly = (ln 10^-3 / Ln fb) * (D / fs)
|
|
|
|
// setting Tdly = Tmin and solving for fb gives:
|
|
|
|
// D / Dmin = ln fb / ln fbmin
|
|
|
|
// since y^x = z gives x = ln z / ln y
|
|
|
|
// fb = fbmin ^ (D/Dmin)
|
|
|
|
float fb = powf (fbmin, (float)dly / (float) dmin);
|
|
|
|
return fb;
|
|
}
|
|
|
|
// set up 'b' gain parameter of feedback delay to
|
|
// compensate for gain caused by feedback 'fb'.
|
|
|
|
void DLY_SetNormalizingGain ( dly_t *pdly, int feedback )
|
|
{
|
|
// compute normalized gain, set as output gain
|
|
|
|
// calculate gain of delay line with feedback, and use it to
|
|
// reduce output. ie: force delay line with feedback to unity gain
|
|
|
|
// for constant input x with feedback fb:
|
|
|
|
// out = x + x*fb + x * fb^2 + x * fb^3...
|
|
// gain = out/x
|
|
// so gain = 1 + fb + fb^2 + fb^3...
|
|
// which, by the miracle of geometric series, equates to 1/1-fb
|
|
// thus, gain = 1/(1-fb)
|
|
|
|
float fgain = 0;
|
|
float gain;
|
|
int b;
|
|
float fb = (float)feedback;
|
|
|
|
fb = fb / (float)PMAX;
|
|
fb = fpmin(fb, 0.999f);
|
|
|
|
// if b is 0, set b to PMAX (1)
|
|
|
|
b = pdly->b ? pdly->b : PMAX;
|
|
|
|
fgain = 1.0 / (1.0 - fb);
|
|
|
|
// compensating gain - multiply rva output by gain then >> PBITS
|
|
|
|
gain = (int)((1.0 / fgain) * PMAX);
|
|
|
|
gain = gain * 4; // compensate for fact that gain calculation is for +/- 32767 amplitude wavs
|
|
// ie: ok to allow a bit more gain because most wavs are not at theoretical peak amplitude at all times
|
|
|
|
// limit gain reduction to N% PMAX
|
|
|
|
gain = iclamp (gain, (PMAX * DLY_NORMALIZING_REDUCTION_MAX), PMAX);
|
|
|
|
gain = ((float)b/(float)PMAX) * gain; // scale final gain by pdly->b.
|
|
|
|
pdly->b = (int)gain;
|
|
}
|
|
|
|
void DLY_ChangeTaps ( dly_t *pdly, int t0, int t1, int t2, int t3 );
|
|
|
|
// allocate a new delay line
|
|
// D number of samples to delay
|
|
// a feedback value (0-PMAX normalized to 0.0-1.0)
|
|
// b gain value (0-PMAX normalized to 0.0-1.0) - this is folded into the filter fb params
|
|
// if DLY_LOWPASS or DLY_FLINEAR:
|
|
// L - numerator order of filter
|
|
// M - denominator order of filter
|
|
// fb - numerator params, M+1
|
|
// fa - denominator params, L+1
|
|
|
|
dly_t * DLY_AllocLP ( int D, int a, int b, int type, int M, int L, int *fa, int *fb )
|
|
{
|
|
CircularBufferSample_t *w;
|
|
int i;
|
|
dly_t *pdly = NULL;
|
|
int feedback;
|
|
|
|
// find open slot
|
|
|
|
for (i = 0; i < CDLYS; i++)
|
|
{
|
|
if (!dlys[i].fused)
|
|
{
|
|
pdly = &dlys[i];
|
|
DLY_Init( pdly );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i == CDLYS )
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate delay line.\n" );
|
|
return NULL; // all delay lines in use
|
|
}
|
|
|
|
// save original feedback value
|
|
|
|
feedback = a;
|
|
|
|
// adjust feedback a, gain b if delay is multitap unit
|
|
|
|
if ( DLY_HAS_MULTITAP(type) )
|
|
{
|
|
// split output gain over 4 taps
|
|
|
|
b = (int)((float)(b) * DLY_TAP_FEEDBACK_GAIN);
|
|
}
|
|
|
|
if ( DLY_HAS_FILTER(type) )
|
|
{
|
|
// alloc lowpass iir_filter
|
|
// delay feedback gain is built into filter gain
|
|
|
|
float gain = (float)a / (float)(PMAX);
|
|
|
|
pdly->pflt = FLT_Alloc( 0, M, L, fa, fb, gain );
|
|
if ( !pdly->pflt )
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate filter for delay line.\n" );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// alloc delay memory
|
|
w = new CircularBufferSample_t[D+1];
|
|
if ( !w )
|
|
{
|
|
Warning( "Sound DSP: Failed to lock.\n");
|
|
FLT_Free ( pdly->pflt );
|
|
return NULL;
|
|
}
|
|
|
|
// clear delay array
|
|
|
|
Q_memset (w, 0, sizeof(CircularBufferSample_t) * (D+1));
|
|
|
|
// init values
|
|
|
|
pdly->type = type;
|
|
pdly->D = D;
|
|
pdly->t = D; // set delay tap to full delay
|
|
pdly->tnew = D;
|
|
pdly->xf = 0;
|
|
pdly->D0 = D;
|
|
pdly->p = w; // init circular pointer to head of buffer
|
|
pdly->w = w;
|
|
pdly->a = MIN( a, PMAX - 1 ); // do not allow 100% feedback
|
|
pdly->b = b;
|
|
pdly->fused = true;
|
|
|
|
if ( type == DLY_LINEAR || type == DLY_FLINEAR )
|
|
{
|
|
// linear delay has no feedback and unity gain
|
|
|
|
pdly->a = 0;
|
|
pdly->b = PMAX;
|
|
}
|
|
else
|
|
{
|
|
// adjust b to compensate for feedback gain of steady state max input
|
|
|
|
DLY_SetNormalizingGain( pdly, feedback );
|
|
}
|
|
|
|
if ( DLY_HAS_MULTITAP(type) )
|
|
{
|
|
// initially set up all taps to same value - caller uses DLY_ChangeTaps to change values
|
|
|
|
DLY_ChangeTaps( pdly, D, D, D, D );
|
|
}
|
|
|
|
return (pdly);
|
|
}
|
|
|
|
// allocate lowpass or allpass delay
|
|
|
|
dly_t * DLY_Alloc( int D, int a, int b, int type )
|
|
{
|
|
return DLY_AllocLP( D, a, b, type, 0, 0, 0, 0 );
|
|
}
|
|
|
|
void DLY_Print( const dly_t & delay, int nIndentation )
|
|
{
|
|
const char * pIndent = GetIndentationText( nIndentation );
|
|
DevMsg( "%sDelay: %p [Addr]\n", pIndent, &delay );
|
|
|
|
DevMsg( "%sfused: %d\n", pIndent, delay.fused );
|
|
DevMsg( "%stype: %d\n", pIndent, delay.type );
|
|
DevMsg( "%sD: %d\n", pIndent, delay.D );
|
|
DevMsg( "%st: %d\n", pIndent, delay.t );
|
|
DevMsg( "%stnew: %d\n", pIndent, delay.tnew );
|
|
DevMsg( "%sxf: %d\n", pIndent, delay.xf );
|
|
DevMsg( "%st1: %d - t2: %d - t3: %d\n", pIndent, delay.t1, delay.t2, delay.t3 );
|
|
DevMsg( "%sa1: %d - a2: %d - a3: %d\n", pIndent, delay.a1, delay.a2, delay.a3 );
|
|
DevMsg( "%sD0: %d\n", pIndent, delay.D0 );
|
|
DevMsg( "%sw: %d\n", pIndent, delay.p - delay.w ); // Put the index and not the address
|
|
|
|
// See if we can reduce the output if possible
|
|
CircularBufferSample_t nFirstValue = delay.w[0];
|
|
bool bAllSame = true;
|
|
for ( int i = 1 ; i < delay.D + 1 ; ++i )
|
|
{
|
|
if ( delay.w[i] != nFirstValue )
|
|
{
|
|
bAllSame = false;
|
|
}
|
|
}
|
|
if ( bAllSame )
|
|
{
|
|
DevMsg( "%sAll %d values are equal to %d.\n", pIndent, delay.D + 1, nFirstValue );
|
|
}
|
|
else
|
|
{
|
|
// Values are different, list them
|
|
int nValues = delay.D + 1;
|
|
const int MAX_SAMPLES_DISPLAYED = 256;
|
|
if ( nValues > MAX_SAMPLES_DISPLAYED )
|
|
{
|
|
nValues = MAX_SAMPLES_DISPLAYED;
|
|
DevMsg( "%sDisplay only the first %d samples.\n", pIndent, nValues );
|
|
}
|
|
for ( int i = 0 ; i < nValues ; ++i )
|
|
{
|
|
if ( ( i % 64 ) == 0 )
|
|
{
|
|
DevMsg( "\n%s ", pIndent );
|
|
}
|
|
DevMsg( "%d ", delay.w[i] );
|
|
}
|
|
DevMsg( "\n" );
|
|
}
|
|
|
|
DevMsg( "%sa: %d\n", pIndent, delay.a );
|
|
DevMsg( "%sb: %d\n", pIndent, delay.b );
|
|
|
|
DevMsg( "%spflt: ", pIndent );
|
|
if (delay.pflt != NULL)
|
|
{
|
|
FLT_Print( *delay.pflt, nIndentation + 1 );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "NULL\n" );
|
|
}
|
|
}
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
dly_t * DLY_Clone(dly_t * pOldDelay)
|
|
{
|
|
flt_t * pFilter = pOldDelay->pflt;
|
|
dly_t * pNewDelay;
|
|
if ( pFilter != NULL )
|
|
{
|
|
pNewDelay = DLY_AllocLP(pOldDelay->D, pOldDelay->a, pOldDelay->b, pOldDelay->type, pFilter->M, pFilter->L, pFilter->a, pFilter->b);
|
|
}
|
|
else
|
|
{
|
|
pNewDelay = DLY_Alloc(pOldDelay->D, pOldDelay->a, pOldDelay->b, pOldDelay->type);
|
|
}
|
|
|
|
// Copy the samples
|
|
for (int i = 0 ; i < pOldDelay->D + 1 ; ++i)
|
|
{
|
|
pNewDelay->w[i] = pOldDelay->w[i];
|
|
}
|
|
// Update the offset
|
|
pNewDelay->p += ( pOldDelay->p - pOldDelay->w );
|
|
Assert( ( pNewDelay->p - pNewDelay->w ) == ( pOldDelay->p - pOldDelay->w ) );
|
|
|
|
// Let's make sure that the filters have the same values
|
|
if ( pFilter != NULL )
|
|
{
|
|
for (int i = 0 ; i < FLT_M + 1 ; ++i)
|
|
{
|
|
pNewDelay->pflt->b[i] = pFilter->b[i];
|
|
pNewDelay->pflt->a[i] = pFilter->a[i];
|
|
pNewDelay->pflt->w[i] = pFilter->w[i];
|
|
}
|
|
}
|
|
pNewDelay->b = pOldDelay->b;
|
|
pNewDelay->t = pOldDelay->t;
|
|
pNewDelay->xf = pOldDelay->xf;
|
|
pNewDelay->tnew = pOldDelay->tnew;
|
|
return pNewDelay;
|
|
}
|
|
|
|
void DLY_Compare( const dly_t & leftDelay, const dly_t & rightDelay )
|
|
{
|
|
Assert ( &leftDelay != &rightDelay );
|
|
|
|
Assert( leftDelay.fused == rightDelay.fused );
|
|
Assert( leftDelay.type == rightDelay.type );
|
|
Assert( leftDelay.D == rightDelay.D );
|
|
Assert( leftDelay.t == rightDelay.t );
|
|
Assert( leftDelay.tnew == rightDelay.tnew );
|
|
Assert( leftDelay.xf == rightDelay.xf );
|
|
Assert( leftDelay.t1 == rightDelay.t1 );
|
|
Assert( leftDelay.t2 == rightDelay.t2 );
|
|
Assert( leftDelay.t3 == rightDelay.t3 );
|
|
Assert( leftDelay.a1 == rightDelay.a1 );
|
|
Assert( leftDelay.a2 == rightDelay.a2 );
|
|
Assert( leftDelay.a3 == rightDelay.a3 );
|
|
Assert( leftDelay.D0 == rightDelay.D0 );
|
|
Assert( leftDelay.t1 == rightDelay.t1 );
|
|
Assert( (leftDelay.p - leftDelay.w) == (rightDelay.p - rightDelay.w) );
|
|
for ( int i = 0 ; i < leftDelay.D + 1 ; ++i )
|
|
{
|
|
Assert( leftDelay.w[i] == rightDelay.w[i] );
|
|
}
|
|
|
|
Assert( leftDelay.a == rightDelay.a );
|
|
Assert( leftDelay.b == rightDelay.b );
|
|
|
|
if ( CheckPointers( leftDelay.pflt, rightDelay.pflt ) )
|
|
{
|
|
FLT_Compare( *leftDelay.pflt, *rightDelay.pflt );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Allocate new delay, convert from float params in prc preset to internal parameters
|
|
// Uses filter params in prc if delay is type lowpass
|
|
|
|
// delay parameter order
|
|
|
|
typedef enum {
|
|
|
|
dly_idtype, // NOTE: first 8 params must match those in mdy_e
|
|
dly_idelay,
|
|
dly_ifeedback,
|
|
dly_igain,
|
|
|
|
dly_iftype,
|
|
dly_icutoff,
|
|
dly_iqwidth,
|
|
dly_iquality,
|
|
|
|
dly_itap1,
|
|
dly_itap2,
|
|
dly_itap3,
|
|
|
|
dly_cparam
|
|
|
|
} dly_e;
|
|
|
|
|
|
// delay parameter ranges
|
|
|
|
prm_rng_t dly_rng[] = {
|
|
|
|
{dly_cparam, 0, 0}, // first entry is # of parameters
|
|
|
|
// delay params
|
|
|
|
{dly_idtype, 0, DLY_MAX}, // delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS etc
|
|
{dly_idelay, -1.0, 1000.0}, // delay in milliseconds (-1 forces auto dsp to set delay value from room size)
|
|
{dly_ifeedback, 0.0, 0.99}, // feedback 0-1.0
|
|
{dly_igain, 0.0, 10.0}, // final gain of output stage, 0-10.0
|
|
|
|
// filter params if dly type DLY_LOWPASS or DLY_FLINEAR
|
|
|
|
{dly_iftype, 0, FTR_MAX},
|
|
{dly_icutoff, 10.0, 22050.0},
|
|
{dly_iqwidth, 100.0, 11025.0},
|
|
{dly_iquality, 0, QUA_MAX},
|
|
// note: -1 flag tells auto dsp to get value directly from room size
|
|
{dly_itap1, -1.0, 1000.0}, // delay in milliseconds NOTE: delay > tap3 > tap2 > tap1
|
|
{dly_itap2, -1.0, 1000.0}, // delay in milliseconds
|
|
{dly_itap3, -1.0, 1000.0}, // delay in milliseconds
|
|
};
|
|
|
|
dly_t * DLY_Params ( prc_t *pprc )
|
|
{
|
|
dly_t *pdly = NULL;
|
|
int D, a, b;
|
|
|
|
float delay = fabs(pprc->prm[dly_idelay]);
|
|
float feedback = pprc->prm[dly_ifeedback];
|
|
float gain = pprc->prm[dly_igain];
|
|
int type = pprc->prm[dly_idtype];
|
|
|
|
float ftype = pprc->prm[dly_iftype];
|
|
float cutoff = pprc->prm[dly_icutoff];
|
|
float qwidth = pprc->prm[dly_iqwidth];
|
|
float qual = pprc->prm[dly_iquality];
|
|
|
|
float t1 = fabs(pprc->prm[dly_itap1]);
|
|
float t2 = fabs(pprc->prm[dly_itap2]);
|
|
float t3 = fabs(pprc->prm[dly_itap3]);
|
|
|
|
D = MSEC_TO_SAMPS(delay); // delay samples
|
|
a = feedback * PMAX; // feedback
|
|
b = gain * PMAX; // gain
|
|
|
|
switch ( (int) type )
|
|
{
|
|
case DLY_PLAIN:
|
|
case DLY_PLAIN_4TAP:
|
|
case DLY_ALLPASS:
|
|
case DLY_LINEAR:
|
|
pdly = DLY_Alloc( D, a, b, type );
|
|
break;
|
|
|
|
case DLY_FLINEAR:
|
|
case DLY_LOWPASS:
|
|
case DLY_LOWPASS_4TAP:
|
|
{
|
|
// set up dummy lowpass filter to convert params
|
|
|
|
prc_t prcf;
|
|
|
|
prcf.prm[flt_iquality] = qual; // 0,1,2 - (0 or 1 low quality implies faster execution time)
|
|
prcf.prm[flt_icutoff] = cutoff;
|
|
prcf.prm[flt_iftype] = ftype;
|
|
prcf.prm[flt_iqwidth] = qwidth;
|
|
prcf.prm[flt_igain] = 1.0;
|
|
|
|
flt_t *pflt = (flt_t *)FLT_Params ( &prcf );
|
|
|
|
if ( !pflt )
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate filter.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
pdly = DLY_AllocLP ( D, a, b, type, pflt->M, pflt->L, pflt->a, pflt->b );
|
|
|
|
FLT_Free ( pflt );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// set up multi-tap delays
|
|
|
|
if ( pdly && DLY_HAS_MULTITAP((int)type) )
|
|
DLY_ChangeTaps( pdly, D, MSEC_TO_SAMPS(t1), MSEC_TO_SAMPS(t2), MSEC_TO_SAMPS(t3) );
|
|
|
|
return pdly;
|
|
}
|
|
|
|
inline void * DLY_VParams ( void *p )
|
|
{
|
|
PRC_CheckParams( (prc_t *)p, dly_rng );
|
|
return (void *) DLY_Params ((prc_t *)p);
|
|
}
|
|
|
|
// get next value from delay line, move x into delay line
|
|
|
|
inline int DLY_GetNext ( dly_t *pdly, int x )
|
|
{
|
|
switch (pdly->type)
|
|
{
|
|
default:
|
|
case DLY_PLAIN:
|
|
return ReverbSimple( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, x );
|
|
case DLY_ALLPASS:
|
|
return DelayAllpass( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, x );
|
|
case DLY_LOWPASS:
|
|
return DelayLowPass( pdly->D, pdly->t, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
|
|
case DLY_LINEAR:
|
|
return DelayLinear( pdly->D, pdly->t, pdly->w, &pdly->p, x );
|
|
case DLY_FLINEAR:
|
|
return DelayLinearLowPass( pdly->D, pdly->t, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
|
|
case DLY_PLAIN_4TAP:
|
|
return ReverbSimple_multitap( pdly->D, pdly->t, pdly->t1, pdly->t2, pdly->t3, pdly->w, &pdly->p, pdly->a, pdly->b, x );
|
|
case DLY_LOWPASS_4TAP:
|
|
return DelayLowpass_multitap( pdly->D, pdly->t, pdly->t1, pdly->t2,pdly->t3, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
|
|
}
|
|
}
|
|
|
|
inline void DLY_GetNext_Opt ( dly_t *pdly, int * pIn, LocalOutputSample_t * pOut, int nCount )
|
|
{
|
|
switch (pdly->type)
|
|
{
|
|
default:
|
|
case DLY_PLAIN:
|
|
ReverbSimple_Opt( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, pIn, pOut, nCount );
|
|
break;
|
|
case DLY_ALLPASS:
|
|
DelayAllpass_Opt3<2, MM_ADD>( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, pIn, pOut, nCount );
|
|
break;
|
|
case DLY_LOWPASS:
|
|
DelayLowPass_Opt3( pdly->D, pdly->t, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, pIn, pOut, nCount );
|
|
break;
|
|
case DLY_LINEAR:
|
|
DelayLinear_Opt( pdly->D, pdly->t, pdly->w, &pdly->p, pIn, pOut, nCount );
|
|
break;
|
|
case DLY_FLINEAR:
|
|
DelayLinearLowPass_Opt3( pdly->D, pdly->t, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, pIn, pOut, nCount );
|
|
break;
|
|
case DLY_PLAIN_4TAP:
|
|
ReverbSimple_multitap_Opt( pdly->D, pdly->t, pdly->t1, pdly->t2, pdly->t3, pdly->w, &pdly->p, pdly->a, pdly->b, pIn, pOut, nCount );
|
|
break;
|
|
case DLY_LOWPASS_4TAP:
|
|
DelayLowpass_multitap_Opt( pdly->D, pdly->t, pdly->t1, pdly->t2,pdly->t3, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, pIn, pOut, nCount );
|
|
break;
|
|
}
|
|
}
|
|
|
|
inline int DLY_GetNextXfade ( dly_t *pdly, int x )
|
|
{
|
|
|
|
switch (pdly->type)
|
|
{
|
|
default:
|
|
case DLY_PLAIN:
|
|
return ReverbSimple_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &pdly->p, pdly->a, pdly->b, x );
|
|
case DLY_ALLPASS:
|
|
return DelayAllpass_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &pdly->p, pdly->a, pdly->b, x );
|
|
case DLY_LOWPASS:
|
|
return DelayLowpass_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
|
|
case DLY_LINEAR:
|
|
return DelayLinear_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &pdly->p, x );
|
|
case DLY_FLINEAR:
|
|
return DelayLinear_lowpass_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
|
|
case DLY_PLAIN_4TAP:
|
|
return ReverbSimple_multitap_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->t1, pdly->t2, pdly->t3, pdly->w, &pdly->p, pdly->a, pdly->b, x );
|
|
case DLY_LOWPASS_4TAP:
|
|
return DelayLowpass_multitap_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->t1, pdly->t2, pdly->t3, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
|
|
}
|
|
}
|
|
|
|
// batch version for performance
|
|
// UNDONE: a) unwind this more - pb increments by 2 to avoid pb->left or pb->right deref.
|
|
// UNDONE: b) all filter and delay params are dereferenced outside of DLY_GetNext and passed as register values
|
|
// UNDONE: c) pull case statement in dly_getnext out, so loop directly calls the inline dly_*() routine.
|
|
|
|
inline void DLY_GetNextN( dly_t *pdly, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb = pbuffer;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb->left = DLY_GetNext( pdly, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb->right = DLY_GetNext( pdly, pb->right );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb->left = pb->right = DLY_GetNext( pdly, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// get tap on t'th sample in delay - don't update buffer pointers, this is done via DLY_GetNext
|
|
// Only valid for DLY_LINEAR.
|
|
|
|
inline int DLY_GetTap ( dly_t *pdly, int t )
|
|
{
|
|
return GetDly (pdly->D, pdly->w, pdly->p, t );
|
|
}
|
|
|
|
#define SWAP(a,b,t) {(t) = (a); (a) = (b); (b) = (t);}
|
|
|
|
// make instantaneous change to tap values t0..t3
|
|
// all values of t must be less than original delay D
|
|
// only processed for DLY_LOWPASS_4TAP & DLY_PLAIN_4TAP
|
|
// NOTE: pdly->a feedback must have been set before this call!
|
|
void DLY_ChangeTaps ( dly_t *pdly, int t0, int t1, int t2, int t3 )
|
|
{
|
|
if (!pdly)
|
|
return;
|
|
|
|
int temp;
|
|
|
|
// sort taps to make sure t3 > t2 > t1 > t0 !
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (t0 > t1) SWAP(t0, t1, temp);
|
|
if (t1 > t2) SWAP(t1, t2, temp);
|
|
if (t2 > t3) SWAP(t2, t3, temp);
|
|
}
|
|
|
|
pdly->t = MIN ( t0, pdly->D0 );
|
|
pdly->t1 = MIN ( t1, pdly->D0 );
|
|
pdly->t2 = MIN ( t2, pdly->D0 );
|
|
pdly->t3 = MIN ( t3, pdly->D0 );
|
|
|
|
}
|
|
|
|
// make instantaneous change for first delay tap 't' to new delay value.
|
|
// t tap value must be <= original D (ie: we don't do any reallocation here)
|
|
|
|
void DLY_ChangeVal ( dly_t *pdly, int t )
|
|
{
|
|
// never set delay > original delay
|
|
|
|
pdly->t = MIN ( t, pdly->D0 );
|
|
}
|
|
|
|
// ignored - use MDY_ for modulatable delay
|
|
|
|
inline void DLY_Mod ( void *p, float v ) { return; }
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Ramp - used for varying smoothly between int parameters ie: modulation delays
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
struct rmp_t
|
|
{
|
|
int initval; // initial ramp value
|
|
int target; // final ramp value
|
|
int sign; // increasing (1) or decreasing (-1) ramp
|
|
uint nEndRampTimeInMs;
|
|
|
|
int yprev; // previous output value
|
|
bool fhitend; // true if hit end of ramp
|
|
bool bEndAtTime; // if true, fhitend is true when ramp time is hit (even if target not hit)
|
|
// if false, then fhitend is true only when target is hit
|
|
pos_one_t ps; // current ramp output
|
|
};
|
|
|
|
// ramp smoothly between initial value and target value in approx 'ramptime' seconds.
|
|
// (initial value may be greater or less than target value)
|
|
// never changes output by more than +1 or -1 (which can cause the ramp to take longer to complete than ramptime - see bEndAtTime)
|
|
// called once per sample while ramping
|
|
// ramptime - duration of ramp in seconds
|
|
// initval - initial ramp value
|
|
// targetval - target ramp value
|
|
// if bEndAtTime is true, then RMP_HitEnd returns true when ramp time is reached, EVEN IF TARGETVAL IS NOT REACHED
|
|
// if bEndAtTime is false, then RMP_HitEnd returns true when targetval is reached, EVEN IF DELTA IN RAMP VALUES IS > +/- 1
|
|
|
|
void RMP_Init( rmp_t *prmp, float ramptime, int initval, int targetval, bool bEndAtTime )
|
|
{
|
|
int rise;
|
|
int run;
|
|
|
|
if (prmp)
|
|
Q_memset( prmp, 0, sizeof (rmp_t) );
|
|
else
|
|
return;
|
|
|
|
run = (int) (ramptime * SOUND_DMA_SPEED); // 'samples' in ramp
|
|
rise = (targetval - initval); // height of ramp
|
|
|
|
// init fixed point iterator to iterate along the height of the ramp 'rise'
|
|
// always iterates from 0..'rise', increasing in value
|
|
|
|
POS_ONE_Init( &prmp->ps, ABS( rise ), ABS((float) rise) / ((float) run) );
|
|
|
|
prmp->yprev = initval;
|
|
prmp->initval = initval;
|
|
prmp->target = targetval;
|
|
prmp->sign = SIGN( rise );
|
|
float fMinRampTime = MAX( ramptime, 0.016f ); // At minimum we are going to wait for 16 ms (a 60 Hz frame, to avoid issue with ramping at the sample level).
|
|
prmp->nEndRampTimeInMs = Plat_MSTime() + (int)( fMinRampTime * 1000.0f ); // Time when we know it is safe to not cross-fade anymore (time is expired).
|
|
prmp->bEndAtTime = bEndAtTime;
|
|
}
|
|
|
|
// continues from current position to new target position
|
|
|
|
void RMP_SetNext( rmp_t *prmp, float ramptime, int targetval )
|
|
{
|
|
RMP_Init ( prmp, ramptime, prmp->yprev, targetval, prmp->bEndAtTime );
|
|
}
|
|
|
|
inline bool RMP_HitEnd ( rmp_t *prmp )
|
|
{
|
|
return prmp->fhitend;
|
|
}
|
|
|
|
inline void RMP_SetEnd ( rmp_t *prmp )
|
|
{
|
|
prmp->fhitend = true;
|
|
}
|
|
|
|
// get next ramp value & update ramp, if bEndAtTime is true, never varies by more than +1 or -1 between calls
|
|
// when ramp hits target value, it thereafter always returns last value
|
|
|
|
inline int RMP_GetNext( rmp_t *prmp )
|
|
{
|
|
int y;
|
|
int d;
|
|
|
|
// if we hit ramp end, return last value
|
|
|
|
if (prmp->fhitend)
|
|
return prmp->yprev;
|
|
|
|
// get next integer position in ramp height.
|
|
|
|
d = POS_ONE_GetNext( &prmp->ps );
|
|
|
|
if ( prmp->ps.fhitend )
|
|
prmp->fhitend = true;
|
|
|
|
// increase or decrease from initval, depending on ramp sign
|
|
|
|
if ( prmp->sign > 0 )
|
|
y = prmp->initval + d;
|
|
else
|
|
y = prmp->initval - d;
|
|
|
|
// if bEndAtTime is true, only update current height by a max of +1 or -1
|
|
// this also means that for short ramp times, we may not hit target
|
|
|
|
if (prmp->bEndAtTime)
|
|
{
|
|
if ( ABS( y - prmp->yprev ) >= 1 )
|
|
prmp->yprev += prmp->sign;
|
|
}
|
|
else
|
|
{
|
|
// always hits target - but varies by more than +/- 1
|
|
|
|
prmp->yprev = y;
|
|
}
|
|
|
|
return prmp->yprev;
|
|
}
|
|
|
|
// get current ramp value, don't update ramp
|
|
|
|
inline int RMP_GetCurrent( rmp_t *prmp )
|
|
{
|
|
return prmp->yprev;
|
|
}
|
|
|
|
void RMP_Print( const rmp_t & rmp, int nIndentation )
|
|
{
|
|
const char * pIndent = GetIndentationText( nIndentation );
|
|
DevMsg( "%sRmp: %p [Addr]\n", pIndent, &rmp );
|
|
DevMsg( "%sinitval: %d\n", pIndent, rmp.initval );
|
|
DevMsg( "%starget: %d\n", pIndent, rmp.target );
|
|
DevMsg( "%ssign: %d\n", pIndent, rmp.sign );
|
|
DevMsg( "%sfhitend: %d\n", pIndent, rmp.fhitend );
|
|
DevMsg( "%sbEndAtTime: %d\n", pIndent, rmp.bEndAtTime );
|
|
|
|
POS_ONE_Print( rmp.ps, nIndentation + 1 );
|
|
}
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
void RMP_Compare( const rmp_t & leftRmp, const rmp_t & rightRmp )
|
|
{
|
|
Assert ( &leftRmp != &rightRmp );
|
|
|
|
Assert( leftRmp.initval == rightRmp.initval );
|
|
Assert( leftRmp.target == rightRmp.target );
|
|
Assert( leftRmp.sign == rightRmp.sign );
|
|
Assert( leftRmp.fhitend == rightRmp.fhitend );
|
|
Assert( leftRmp.bEndAtTime == rightRmp.bEndAtTime );
|
|
|
|
POS_ONE_Compare( leftRmp.ps, rightRmp.ps );
|
|
}
|
|
#endif
|
|
|
|
//////////////
|
|
// mod delay
|
|
//////////////
|
|
|
|
// modulate delay time anywhere from 0..D using MDY_ChangeVal. no output glitches (uses RMP)
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CMDYS 128
|
|
#else
|
|
#define CMDYS 64 // max # of mod delays active (steals from delays)
|
|
#endif
|
|
|
|
struct mdy_t
|
|
{
|
|
bool fused;
|
|
|
|
bool fchanging; // true if modulating to new delay value
|
|
|
|
dly_t *pdly; // delay
|
|
|
|
float ramptime; // ramp 'glide' time - time in seconds to change between values
|
|
|
|
int mtime; // time in samples between delay changes. 0 implies no self-modulating
|
|
int mtimecur; // current time in samples until next delay change
|
|
float depth; // modulate delay from D to D - (D*depth) depth 0-1.0
|
|
|
|
int mix; // PMAX as % processed fx signal mix
|
|
|
|
rmp_t rmp_interp; // interpolation ramp 0...PMAX
|
|
|
|
bool bPhaseInvert; // if true, invert phase of output
|
|
|
|
};
|
|
|
|
mdy_t mdys[CMDYS];
|
|
|
|
void MDY_Init( mdy_t *pmdy ) { if (pmdy) Q_memset( pmdy, 0, sizeof (mdy_t) ); };
|
|
void MDY_Free( mdy_t *pmdy ) { if (pmdy) { DLY_Free (pmdy->pdly); Q_memset( pmdy, 0, sizeof (mdy_t) ); } };
|
|
void MDY_InitAll() { for (int i = 0; i < CMDYS; i++) MDY_Init( &mdys[i] ); };
|
|
void MDY_FreeAll() { for (int i = 0; i < CMDYS; i++) MDY_Free( &mdys[i] ); };
|
|
|
|
|
|
// allocate mod delay, given previously allocated dly (NOTE: mod delay only sweeps tap 0, not t1,t2 or t3)
|
|
// ramptime is time in seconds for delay to change from dcur to dnew
|
|
// modtime is time in seconds between modulations. 0 if no self-modulation
|
|
// depth is 0-1.0 multiplier, new delay values when modulating are Dnew = randomlong (D - D*depth, D)
|
|
// mix - 0-1.0, default 1.0 for 100% fx mix - pans between input signal and fx signal
|
|
|
|
mdy_t *MDY_Alloc ( dly_t *pdly, float ramptime, float modtime, float depth, float mix )
|
|
{
|
|
int i;
|
|
mdy_t *pmdy;
|
|
|
|
if ( !pdly )
|
|
return NULL;
|
|
|
|
for (i = 0; i < CMDYS; i++)
|
|
{
|
|
if ( !mdys[i].fused )
|
|
{
|
|
pmdy = &mdys[i];
|
|
|
|
MDY_Init ( pmdy );
|
|
|
|
pmdy->pdly = pdly;
|
|
|
|
if ( !pmdy->pdly )
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate delay for mod delay.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
pmdy->fused = true;
|
|
pmdy->ramptime = ramptime;
|
|
pmdy->mtime = SEC_TO_SAMPS(modtime);
|
|
pmdy->mtimecur = pmdy->mtime;
|
|
pmdy->depth = depth;
|
|
pmdy->mix = int ( PMAX * mix );
|
|
pmdy->bPhaseInvert = false;
|
|
|
|
return pmdy;
|
|
}
|
|
}
|
|
|
|
DevMsg ("DSP: Warning, failed to allocate mod delay.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
void MDY_Print( const mdy_t & modDelay, int nIndentation )
|
|
{
|
|
const char * pIndent = GetIndentationText( nIndentation );
|
|
DevMsg( "%sModDelay: %p [Addr]\n", pIndent, &modDelay );
|
|
DevMsg( "%sfused: %d\n", pIndent, modDelay.fused );
|
|
DevMsg( "%sfchanging: %d\n", pIndent, modDelay.fchanging );
|
|
|
|
DevMsg( "%spdly: ", pIndent );
|
|
if ( modDelay.pdly != NULL )
|
|
{
|
|
DLY_Print( *modDelay.pdly, nIndentation + 1 );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "NULL\n" );
|
|
}
|
|
|
|
DevMsg( "%sramptime: %f\n", pIndent, modDelay.ramptime );
|
|
DevMsg( "%smtime: %d\n", pIndent, modDelay.mtime );
|
|
DevMsg( "%smtimecur: %d\n", pIndent, modDelay.mtimecur );
|
|
DevMsg( "%sdepth: %f\n", pIndent, modDelay.depth );
|
|
DevMsg( "%smix: %d\n", pIndent, modDelay.mix );
|
|
|
|
RMP_Print( modDelay.rmp_interp, nIndentation + 1 );
|
|
|
|
DevMsg( "%sbPhaseInvert: %d\n", pIndent, modDelay.bPhaseInvert );
|
|
}
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
mdy_t * MDY_Clone( mdy_t * pOldModDelay )
|
|
{
|
|
dly_t * pNewDelay = DLY_Clone( pOldModDelay->pdly );
|
|
// SAMPS_TO_SEC does not work as expected (number too small?) - we'll override the number anyway
|
|
// MDY_Alloc() should set the pdly accordingly.
|
|
mdy_t * pNewModDelay = MDY_Alloc( pNewDelay, pOldModDelay->ramptime, SAMPS_TO_SEC(pOldModDelay->mtime), pOldModDelay->depth, pOldModDelay->mix );
|
|
|
|
pNewModDelay->fchanging = pOldModDelay->fchanging;
|
|
pNewModDelay->ramptime = pOldModDelay->ramptime;
|
|
pNewModDelay->mtime = pOldModDelay->mtime;
|
|
pNewModDelay->mtimecur = pOldModDelay->mtimecur;
|
|
pNewModDelay->depth = pOldModDelay->depth;
|
|
pNewModDelay->mix = pOldModDelay->mix;
|
|
pNewModDelay->rmp_interp = pOldModDelay->rmp_interp;
|
|
pNewModDelay->bPhaseInvert = pOldModDelay->bPhaseInvert;
|
|
return pNewModDelay;
|
|
}
|
|
|
|
void MDY_Compare( const mdy_t & leftModDelay, const mdy_t & rightModDelay )
|
|
{
|
|
Assert ( &leftModDelay != &rightModDelay );
|
|
|
|
Assert( leftModDelay.fused == rightModDelay.fused );
|
|
Assert( leftModDelay.fchanging == rightModDelay.fchanging );
|
|
|
|
if ( CheckPointers( leftModDelay.pdly, rightModDelay.pdly ) )
|
|
{
|
|
DLY_Compare( *leftModDelay.pdly, *rightModDelay.pdly );
|
|
}
|
|
|
|
Assert( leftModDelay.ramptime == rightModDelay.ramptime );
|
|
Assert( leftModDelay.mtime == rightModDelay.mtime );
|
|
Assert( leftModDelay.mtimecur == rightModDelay.mtimecur );
|
|
Assert( leftModDelay.depth == rightModDelay.depth );
|
|
Assert( leftModDelay.mix == rightModDelay.mix );
|
|
|
|
RMP_Compare( leftModDelay.rmp_interp, rightModDelay.rmp_interp );
|
|
|
|
Assert( leftModDelay.bPhaseInvert == rightModDelay.bPhaseInvert );
|
|
}
|
|
#endif
|
|
|
|
// change to new delay tap value t samples, ramp linearly over ramptime seconds
|
|
|
|
void MDY_ChangeVal ( mdy_t *pmdy, int t )
|
|
{
|
|
// if D > original delay value, cap at original value
|
|
|
|
t = MIN (pmdy->pdly->D0, t);
|
|
|
|
pmdy->fchanging = true;
|
|
|
|
// init interpolation ramp - always hit target
|
|
|
|
RMP_Init ( &pmdy->rmp_interp, pmdy->ramptime, 0, PMAX, false );
|
|
|
|
// init delay xfade values
|
|
|
|
pmdy->pdly->tnew = t;
|
|
pmdy->pdly->xf = 0;
|
|
}
|
|
|
|
// interpolate between current and target delay values
|
|
|
|
inline int MDY_GetNext( mdy_t *pmdy, int x )
|
|
{
|
|
int xout;
|
|
|
|
if ( !pmdy->fchanging )
|
|
{
|
|
// not modulating...
|
|
|
|
xout = DLY_GetNext( pmdy->pdly, x );
|
|
|
|
if ( !pmdy->mtime )
|
|
{
|
|
// return right away if not modulating (not changing and not self modulating)
|
|
|
|
goto mdy_return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// modulating...
|
|
|
|
xout = DLY_GetNextXfade( pmdy->pdly, x );
|
|
|
|
// get xfade ramp & set up delay xfade value for next call to DLY_GetNextXfade()
|
|
|
|
pmdy->pdly->xf = RMP_GetNext( &pmdy->rmp_interp ); // 0...PMAX
|
|
|
|
if ( RMP_HitEnd( &pmdy->rmp_interp ) )
|
|
{
|
|
// done. set delay tap & value = target
|
|
|
|
DLY_ChangeVal( pmdy->pdly, pmdy->pdly->tnew );
|
|
|
|
pmdy->pdly->t = pmdy->pdly->tnew;
|
|
|
|
pmdy->fchanging = false;
|
|
}
|
|
}
|
|
|
|
// if self-modulating and timer has expired, get next change
|
|
|
|
if ( pmdy->mtime && !pmdy->mtimecur-- )
|
|
{
|
|
pmdy->mtimecur = pmdy->mtime;
|
|
|
|
int D0 = pmdy->pdly->D0;
|
|
int Dnew;
|
|
float D1;
|
|
|
|
// modulate between 0 and 100% of d0
|
|
|
|
D1 = (float)D0 * (1.0 - pmdy->depth);
|
|
|
|
Dnew = LocalRandomInt( (int)D1, D0 );
|
|
|
|
// set up modulation to new value
|
|
|
|
MDY_ChangeVal ( pmdy, Dnew );
|
|
}
|
|
|
|
mdy_return:
|
|
|
|
// reverse phase of output
|
|
|
|
if ( pmdy->bPhaseInvert )
|
|
xout = -xout;
|
|
|
|
// 100% fx mix
|
|
|
|
if ( pmdy->mix == PMAX)
|
|
return xout;
|
|
|
|
// special case 50/50 mix
|
|
|
|
if ( pmdy->mix == PMAX / 2)
|
|
return ( (xout + x) >> 1 );
|
|
|
|
// return mix of input and processed signal
|
|
|
|
return ( x + (((xout - x) * pmdy->mix) >> PBITS) );
|
|
}
|
|
|
|
|
|
// batch version for performance
|
|
// UNDONE: unwind MDY_GetNext so that it directly calls DLY_GetNextN:
|
|
// UNDONE: a) if not currently modulating and never self-modulating, then just unwind like DLY_GetNext
|
|
// UNDONE: b) if not currently modulating, figure out how many samples N until self-modulation timer kicks in again
|
|
// and stream out N samples just like DLY_GetNext
|
|
|
|
inline void MDY_GetNextN( mdy_t *pmdy, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb = pbuffer;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb->left = MDY_GetNext( pmdy, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb->right = MDY_GetNext( pmdy, pb->right );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb->left = pb->right = MDY_GetNext( pmdy, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// parameter order
|
|
|
|
typedef enum {
|
|
|
|
mdy_idtype, // NOTE: first 8 params must match params in dly_e
|
|
mdy_idelay,
|
|
mdy_ifeedback,
|
|
mdy_igain,
|
|
|
|
mdy_iftype,
|
|
mdy_icutoff,
|
|
mdy_iqwidth,
|
|
mdy_iquality,
|
|
|
|
mdy_imodrate,
|
|
mdy_imoddepth,
|
|
mdy_imodglide,
|
|
|
|
mdy_imix,
|
|
mdy_ibxfade,
|
|
|
|
mdy_cparam
|
|
|
|
} mdy_e;
|
|
|
|
|
|
// parameter ranges
|
|
|
|
prm_rng_t mdy_rng[] = {
|
|
|
|
{mdy_cparam, 0, 0}, // first entry is # of parameters
|
|
|
|
// delay params
|
|
|
|
{mdy_idtype, 0, DLY_MAX}, // delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS
|
|
{mdy_idelay, 0.0, 1000.0}, // delay in milliseconds
|
|
{mdy_ifeedback, 0.0, 0.99}, // feedback 0-1.0
|
|
{mdy_igain, 0.0, 1.0}, // final gain of output stage, 0-1.0
|
|
|
|
// filter params if mdy type DLY_LOWPASS
|
|
|
|
{mdy_iftype, 0, FTR_MAX},
|
|
{mdy_icutoff, 10.0, 22050.0},
|
|
{mdy_iqwidth, 100.0, 11025.0},
|
|
{mdy_iquality, 0, QUA_MAX},
|
|
|
|
{mdy_imodrate, 0.01, 200.0}, // frequency at which delay values change to new random value. 0 is no self-modulation
|
|
{mdy_imoddepth, 0.0, 1.0}, // how much delay changes (decreases) from current value (0-1.0)
|
|
{mdy_imodglide, 0.01, 100.0}, // glide time between dcur and dnew in milliseconds
|
|
{mdy_imix, 0.0, 1.0} // 1.0 = full fx mix, 0.5 = 50% fx, 50% dry
|
|
};
|
|
|
|
|
|
// convert user parameters to internal parameters, allocate and return
|
|
|
|
mdy_t * MDY_Params ( prc_t *pprc )
|
|
{
|
|
mdy_t *pmdy;
|
|
dly_t *pdly;
|
|
|
|
float ramptime = pprc->prm[mdy_imodglide] / 1000.0; // get ramp time in seconds
|
|
float modtime = 0.0f;
|
|
if ( pprc->prm[mdy_imodrate] != 0.0f )
|
|
{
|
|
modtime = 1.0 / pprc->prm[mdy_imodrate]; // time between modulations in seconds
|
|
}
|
|
float depth = pprc->prm[mdy_imoddepth]; // depth of modulations 0-1.0
|
|
float mix = pprc->prm[mdy_imix];
|
|
|
|
// alloc plain, allpass or lowpass delay
|
|
|
|
pdly = DLY_Params( pprc );
|
|
|
|
if ( !pdly )
|
|
return NULL;
|
|
|
|
pmdy = MDY_Alloc ( pdly, ramptime, modtime, depth, mix );
|
|
|
|
return pmdy;
|
|
}
|
|
|
|
inline void * MDY_VParams ( void *p )
|
|
{
|
|
PRC_CheckParams ( (prc_t *)p, mdy_rng );
|
|
return (void *) MDY_Params ((prc_t *)p);
|
|
}
|
|
|
|
// v is +/- 0-1.0
|
|
// change current delay value 0..D
|
|
|
|
void MDY_Mod ( mdy_t *pmdy, float v )
|
|
{
|
|
|
|
int D0 = pmdy->pdly->D0; // base delay value
|
|
float v2;
|
|
|
|
// if v is < -2.0 then delay is v + 10.0
|
|
// invert phase of output. hack.
|
|
|
|
if ( v < -2.0 )
|
|
{
|
|
v = v + 10.0;
|
|
pmdy->bPhaseInvert = true;
|
|
}
|
|
else
|
|
{
|
|
pmdy->bPhaseInvert = false;
|
|
}
|
|
|
|
v2 = -(v + 1.0)/2.0; // v2 varies -1.0-0.0
|
|
|
|
// D0 varies 0..D0
|
|
|
|
D0 = D0 + (int)((float)D0 * v2);
|
|
|
|
// change delay
|
|
|
|
MDY_ChangeVal( pmdy, D0 );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
///////////////////
|
|
// Parallel reverbs
|
|
///////////////////
|
|
|
|
// Reverb A
|
|
// M parallel reverbs, mixed to mono output
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CRVAS 128
|
|
#else
|
|
#define CRVAS 64 // max number of parallel series reverbs active
|
|
#endif
|
|
|
|
#define CRVA_DLYS 12 // max number of delays making up reverb_a
|
|
|
|
struct rva_t
|
|
{
|
|
bool fused;
|
|
int m; // number of parallel plain or lowpass delays
|
|
int fparallel; // true if filters in parallel with delays, otherwise single output filter
|
|
flt_t *pflt; // series filters
|
|
|
|
dly_t *pdlys[CRVA_DLYS]; // array of pointers to delays
|
|
mdy_t *pmdlys[CRVA_DLYS]; // array of pointers to mod delays
|
|
|
|
bool fmoddly; // true if using mod delays
|
|
};
|
|
|
|
rva_t rvas[CRVAS];
|
|
|
|
void RVA_Init ( rva_t *prva ) { if ( prva ) Q_memset (prva, 0, sizeof (rva_t)); }
|
|
void RVA_InitAll( void ) { for (int i = 0; i < CRVAS; i++) RVA_Init ( &rvas[i] ); }
|
|
|
|
// free parallel series reverb
|
|
|
|
void RVA_Free( rva_t *prva )
|
|
{
|
|
int i;
|
|
|
|
if ( prva )
|
|
{
|
|
// free all delays
|
|
for (i = 0; i < CRVA_DLYS; i++)
|
|
DLY_Free ( prva->pdlys[i] );
|
|
|
|
// zero all ptrs to delays in mdy array
|
|
for (i = 0; i < CRVA_DLYS; i++)
|
|
{
|
|
if ( prva->pmdlys[i] )
|
|
prva->pmdlys[i]->pdly = NULL;
|
|
}
|
|
|
|
// free all mod delays
|
|
for (i = 0; i < CRVA_DLYS; i++)
|
|
MDY_Free ( prva->pmdlys[i] );
|
|
|
|
FLT_Free( prva->pflt );
|
|
|
|
Q_memset( prva, 0, sizeof (rva_t) );
|
|
}
|
|
}
|
|
|
|
|
|
void RVA_FreeAll( void ) { for (int i = 0; i < CRVAS; i++) RVA_Free( &rvas[i] ); }
|
|
|
|
// create parallel reverb - m parallel reverbs summed
|
|
|
|
// D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
|
|
// a array of reverb feedback parms for parallel reverbs (CRVB_P_DLYS)
|
|
// if a[i] < 0 then this is a predelay - use DLY_FLINEAR instead of DLY_LOWPASS
|
|
// b array of CRVB_P_DLYS - mix params for parallel reverbs
|
|
// m - number of parallel delays
|
|
// pflt - filter template, to be used by all parallel delays
|
|
// fparallel - true if filter operates in parallel with delays, otherwise filter output only
|
|
// fmoddly - > 0 if delays are all mod delays (milliseconds of delay modulation)
|
|
// fmodrate - # of delay repetitions between changes to mod delay
|
|
// ftaps - if > 0, use 4 taps per reverb delay unit (increases density) tap = D - n*ftaps n = 0,1,2,3
|
|
|
|
rva_t * RVA_Alloc ( int *D, int *a, int *b, int m, flt_t *pflt, int fparallel, float fmoddly, float fmodrate, float ftaps )
|
|
{
|
|
|
|
int i;
|
|
int dtype;
|
|
rva_t *prva;
|
|
flt_t *pflt2 = NULL;
|
|
|
|
bool btaps = ftaps > 0.0;
|
|
|
|
// find open slot
|
|
|
|
for ( i = 0; i < CRVAS; i++ )
|
|
{
|
|
if ( !rvas[i].fused )
|
|
break;
|
|
}
|
|
|
|
// return null if no free slots
|
|
|
|
if (i == CRVAS)
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate reverb.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
prva = &rvas[i];
|
|
|
|
// if series filter specified, alloc two series filters
|
|
|
|
if ( pflt && !fparallel)
|
|
{
|
|
// use filter data as template for a filter on output (2 cascaded filters)
|
|
|
|
pflt2 = FLT_Alloc (0, pflt->M, pflt->L, pflt->a, pflt->b, 1.0);
|
|
|
|
if (!pflt2)
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate flt for reverb.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
pflt2->pf1 = FLT_Alloc (0, pflt->M, pflt->L, pflt->a, pflt->b, 1.0);
|
|
pflt2->N = 1;
|
|
}
|
|
|
|
// allocate parallel delays
|
|
|
|
for (i = 0; i < m; i++)
|
|
{
|
|
// set delay type
|
|
|
|
if ( pflt && fparallel )
|
|
// if a[i] param is < 0, allocate delay as predelay instead of feedback delay
|
|
dtype = a[i] < 0 ? DLY_FLINEAR : DLY_LOWPASS;
|
|
else
|
|
// if no filter specified, alloc as plain or multitap plain delay
|
|
dtype = btaps ? DLY_PLAIN_4TAP : DLY_PLAIN;
|
|
|
|
if ( dtype == DLY_LOWPASS && btaps )
|
|
dtype = DLY_LOWPASS_4TAP;
|
|
|
|
// if filter specified and parallel specified, alloc 1 filter per delay
|
|
|
|
if ( DLY_HAS_FILTER(dtype) )
|
|
prva->pdlys[i] = DLY_AllocLP( D[i], abs(a[i]), b[i], dtype, pflt->M, pflt->L, pflt->a, pflt->b );
|
|
else
|
|
prva->pdlys[i] = DLY_Alloc( D[i], abs(a[i]), b[i], dtype );
|
|
|
|
if ( DLY_HAS_MULTITAP(dtype) )
|
|
{
|
|
// set up delay taps to increase density around delay value.
|
|
|
|
// value of ftaps is the seed for all tap values
|
|
|
|
float t1 = MAX(MSEC_TO_SAMPS(5), D[i] * (1.0 - ftaps * 3.141592) );
|
|
float t2 = MAX(MSEC_TO_SAMPS(7), D[i] * (1.0 - ftaps * 1.697043) );
|
|
float t3 = MAX(MSEC_TO_SAMPS(10), D[i] * (1.0 - ftaps * 0.96325) );
|
|
|
|
DLY_ChangeTaps( prva->pdlys[i], (int)t1, (int)t2, (int)t3, D[i] );
|
|
}
|
|
}
|
|
|
|
|
|
if ( fmoddly > 0.0 )
|
|
{
|
|
// alloc mod delays, using previously alloc'd delays
|
|
|
|
// ramptime is time in seconds for delay to change from dcur to dnew
|
|
// modtime is time in seconds between modulations. 0 if no self-modulation
|
|
// depth is 0-1.0 multiplier, new delay values when modulating are Dnew = randomlong (D - D*depth, D)
|
|
|
|
float ramptime;
|
|
float modtime;
|
|
float depth;
|
|
|
|
for (i = 0; i < m; i++)
|
|
{
|
|
int D = prva->pdlys[i]->D;
|
|
|
|
modtime = (float)D / (float)(SOUND_DMA_SPEED); // seconds per delay
|
|
depth = (fmoddly / 1000.0) / modtime; // convert milliseconds to 'depth' %
|
|
depth = clamp (depth, 0.01, 0.99);
|
|
modtime = modtime * fmodrate; // modulate every N delay passes
|
|
|
|
ramptime = fpmin(20.0f/1000.0f, modtime / 2); // ramp between delay values in N ms
|
|
|
|
prva->pmdlys[i] = MDY_Alloc( prva->pdlys[i], ramptime, modtime, depth, 1.0 );
|
|
}
|
|
|
|
prva->fmoddly = true;
|
|
}
|
|
|
|
// if we failed to alloc any reverb, free all, return NULL
|
|
|
|
for (i = 0; i < m; i++)
|
|
{
|
|
if ( !prva->pdlys[i] )
|
|
{
|
|
FLT_Free( pflt2 );
|
|
RVA_Free( prva );
|
|
DevMsg ("DSP: Warning, failed to allocate delay for reverb.\n" );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
prva->fused = true;
|
|
prva->m = m;
|
|
prva->fparallel = fparallel;
|
|
prva->pflt = pflt2;
|
|
return prva;
|
|
}
|
|
|
|
void RVA_Print( const rva_t & rva, int nIndentation )
|
|
{
|
|
const char * pIndent = GetIndentationText( nIndentation );
|
|
DevMsg( "%sRVA: %p [Addr]\n", pIndent, &rva );
|
|
DevMsg( "%sfused: %d\n", pIndent, ( int ) rva.fused );
|
|
DevMsg( "%sm: %d\n", pIndent, rva.m );
|
|
DevMsg( "%sfparallel: %d\n", pIndent, rva.fparallel );
|
|
DevMsg( "%sFilter:", pIndent );
|
|
if ( rva.pflt != NULL )
|
|
{
|
|
FLT_Print( *rva.pflt, nIndentation + 1 );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "NULL\n" );
|
|
}
|
|
for ( int i = 0 ; i < CRVA_DLYS ; ++i )
|
|
{
|
|
DevMsg( "%sDelay[%d]: ", pIndent, i );
|
|
if ( rva.pdlys[i] != NULL )
|
|
{
|
|
DLY_Print( *rva.pdlys[i], nIndentation + 1 );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "NULL\n" );
|
|
}
|
|
}
|
|
for ( int i = 0 ; i < CRVA_DLYS ; ++i )
|
|
{
|
|
DevMsg( "%sModDelay[%d]: ", pIndent, i );
|
|
if ( rva.pmdlys[i] != NULL )
|
|
{
|
|
MDY_Print( *rva.pmdlys[i], nIndentation + 1 );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "NULL\n" );
|
|
}
|
|
}
|
|
DevMsg( "%sfmoddly: %d\n", pIndent, rva.fmoddly );
|
|
}
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
rva_t * RVA_Clone( rva_t * pOldRva )
|
|
{
|
|
int i;
|
|
for ( i = 0; i < CRVAS; i++ )
|
|
{
|
|
if ( !rvas[i].fused )
|
|
break;
|
|
}
|
|
|
|
// return null if no free slots
|
|
|
|
if (i == CRVAS)
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate reverb.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
rva_t * pNewRva = &rvas[i];
|
|
|
|
memcpy(pNewRva, pOldRva, sizeof(rva_t));
|
|
|
|
pNewRva->pflt = FLT_Clone(pOldRva->pflt);
|
|
|
|
for ( int j = 0 ; j < CRVA_DLYS ; ++j )
|
|
{
|
|
// First we do MDYs. In some cases, MDYs can point to DLY that can be stored in the pdlys array
|
|
// In that case instead of cloning the DLY, we will just update the pointer.
|
|
if ( pOldRva->pmdlys[j] != NULL )
|
|
{
|
|
pNewRva->pmdlys[j] = MDY_Clone( pOldRva->pmdlys[j] );
|
|
|
|
if ( pOldRva->pmdlys[j]->pdly == pOldRva->pdlys[j] )
|
|
{
|
|
// Update the pointer instead of cloning it
|
|
pNewRva->pdlys[j] = pNewRva->pmdlys[j]->pdly;
|
|
continue; // Don't clone afterward
|
|
}
|
|
}
|
|
if ( pOldRva->pdlys[j] != NULL )
|
|
{
|
|
pNewRva->pdlys[j] = DLY_Clone( pOldRva->pdlys[j] );
|
|
}
|
|
}
|
|
return pNewRva;
|
|
}
|
|
|
|
void RVA_Compare( const rva_t & leftRva, const rva_t & rightRva )
|
|
{
|
|
Assert ( &leftRva != &rightRva );
|
|
|
|
Assert( leftRva.fused == rightRva.fused );
|
|
Assert( leftRva.m == rightRva.m );
|
|
Assert( leftRva.fparallel == rightRva.fparallel );
|
|
if ( CheckPointers( leftRva.pflt, rightRva.pflt ) )
|
|
{
|
|
FLT_Compare( *leftRva.pflt, *rightRva.pflt );
|
|
}
|
|
for ( int i = 0 ; i < CRVA_DLYS ; ++i )
|
|
{
|
|
if ( CheckPointers( leftRva.pdlys[i], rightRva.pdlys[i] ) )
|
|
{
|
|
DLY_Compare( *leftRva.pdlys[i], *rightRva.pdlys[i] );
|
|
}
|
|
}
|
|
for ( int i = 0 ; i < CRVA_DLYS ; ++i )
|
|
{
|
|
if ( CheckPointers( leftRva.pmdlys[i], rightRva.pmdlys[i] ) )
|
|
{
|
|
MDY_Compare( *leftRva.pmdlys[i], *rightRva.pmdlys[i] );
|
|
}
|
|
}
|
|
Assert( leftRva.fmoddly == rightRva.fmoddly );
|
|
}
|
|
#endif
|
|
|
|
// parallel reverberator
|
|
//
|
|
// for each input sample x do:
|
|
// x0 = plain(D0,w0,&p0,a0,x)
|
|
// x1 = plain(D1,w1,&p1,a1,x)
|
|
// x2 = plain(D2,w2,&p2,a2,x)
|
|
// x3 = plain(D3,w3,&p3,a3,x)
|
|
// y = b0*x0 + b1*x1 + b2*x2 + b3*x3
|
|
//
|
|
// rgdly - array of M delays:
|
|
// D - Delay values (typical - 29, 37, 44, 50, 27, 31)
|
|
// w - array of delayed values
|
|
// p - array of pointers to circular delay line pointers
|
|
// a - array of M feedback values (typical - all equal, like 0.75 * PMAX)
|
|
// b - array of M gain values for plain reverb outputs (1, .9, .8, .7)
|
|
// xin - input value
|
|
// if fparallel, filters are built into delays,
|
|
// otherwise, filter is in feedback loop
|
|
|
|
|
|
int g_MapIntoPBITSDivInt[] =
|
|
{
|
|
0, PMAX/1, PMAX/2, PMAX/3, PMAX/4, PMAX/5, PMAX/6, PMAX/7, PMAX/8,
|
|
PMAX/9, PMAX/10, PMAX/11,PMAX/12,PMAX/13,PMAX/14,PMAX/15,PMAX/16,
|
|
};
|
|
|
|
inline int RVA_GetNext( rva_t *prva, int x )
|
|
{
|
|
int m = prva->m;
|
|
int y = 0;
|
|
|
|
if ( prva->fmoddly )
|
|
{
|
|
// get output of parallel mod delays
|
|
|
|
for (int i = 0; i < m; i++ )
|
|
y += MDY_GetNext( prva->pmdlys[i], x );
|
|
}
|
|
else
|
|
{
|
|
// get output of parallel delays
|
|
|
|
for (int i = 0; i < m; i++ )
|
|
y += DLY_GetNext( prva->pdlys[i], x );
|
|
}
|
|
|
|
// PERFORMANCE: y/m is now baked into the 'b' gain params for each delay ( b = b/m )
|
|
// y = (y * g_MapIntoPBITSDivInt[m]) >> PBITS;
|
|
|
|
if ( prva->fparallel )
|
|
return y;
|
|
|
|
// run series filters if present
|
|
|
|
if ( prva->pflt )
|
|
{
|
|
y = FLT_GetNext( prva->pflt, y);
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
template <int READER, int WRITER>
|
|
inline void RVA_GetNext_Opt( rva_t *pRva, portable_samplepair_t * pBuffer, int nCount )
|
|
{
|
|
int m = pRva->m;
|
|
|
|
// Because we do one filter at a time (and not one sample at a time)
|
|
// We either have to copy the input on an intermediate buffer, or write the output to an intermediate buffer.
|
|
// The faster is to use an intermediate output buffer as we can quickly zero it (it is slower to copy the input).
|
|
|
|
// Unlike the samples, the buffers here are actually short (so we use less memory - less L2 cache misses).
|
|
int nSizeToUse = sizeof(LocalOutputSample_t) * nCount;
|
|
int nSizeToAllocate = ALIGN_VALUE( nSizeToUse, CACHE_LINE_SIZE ); // Align on 128 as we are going to clear per cache-line
|
|
|
|
LocalOutputSample_t * pOutputSample = (LocalOutputSample_t *)alloca( nSizeToAllocate + CACHE_LINE_SIZE); // One more cache line as we are going to clear more than necessary...
|
|
pOutputSample = (LocalOutputSample_t *)ALIGN_VALUE( (intp) pOutputSample, CACHE_LINE_SIZE );
|
|
|
|
int nNumberOfCacheLinesToClear = ALIGN_VALUE( nSizeToAllocate, CACHE_LINE_SIZE ) / CACHE_LINE_SIZE;
|
|
LocalOutputSample_t * pCurrentCacheLine = pOutputSample;
|
|
// Given that we often have 500 to 1000 samples, it means that we are going to clear 2 to 4 Kb.
|
|
// (i.e. up to 32 cache lines). This will saturate the cache pipeline (but it easier to do it now instead of doing it within each filter).
|
|
while ( nNumberOfCacheLinesToClear > 0 )
|
|
{
|
|
PREZERO_128( pCurrentCacheLine, 0 );
|
|
pCurrentCacheLine += CACHE_LINE_SIZE / sizeof(LocalOutputSample_t);
|
|
--nNumberOfCacheLinesToClear;
|
|
}
|
|
|
|
// Then we are going to apply to the buffer each filter (one after the other for a set of samples)
|
|
// We are going to increase the number of loads and stores but at the end we can reduce the number of switches and unroll some calculation
|
|
|
|
int * pInputSample;
|
|
if ( READER == CHANNEL_LEFT )
|
|
{
|
|
pInputSample = &pBuffer->left;
|
|
}
|
|
else
|
|
{
|
|
Assert( READER == CHANNEL_RIGHT );
|
|
pInputSample = &pBuffer->right;
|
|
}
|
|
// At that point, each reader will have to skip one integer after reading one.
|
|
|
|
// Prefetch a bit (the next cache line) - again may continue saturate the buffer - but this will be read soon anyway.
|
|
PREFETCH_128( pInputSample, 128 );
|
|
|
|
if ( pRva->fmoddly )
|
|
{
|
|
// get output of parallel mod delays
|
|
for ( int i = 0; i < m; i++ )
|
|
{
|
|
mdy_t * pModDelay = pRva->pmdlys[i];
|
|
for ( int j = 0 ; j < nCount ; ++j )
|
|
{
|
|
int nSampleIn = pInputSample[j * 2]; // *2 because the input has both left AND right
|
|
int nSampleOut = MDY_GetNext( pModDelay, nSampleIn );
|
|
pOutputSample[j] += nSampleOut; // First operation could actually only write '=' instead of '+='
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// get output of parallel delays
|
|
for ( int i = 0; i < m; i++ )
|
|
{
|
|
dly_t * pDelay = pRva->pdlys[i];
|
|
#if 0
|
|
for ( int j = 0 ; j < nCount ; ++j )
|
|
{
|
|
int nSampleIn = pInputSample[j * 2]; // *2 because the input has both left AND right
|
|
int nSampleOut = DLY_GetNext( pDelay, nSampleIn );
|
|
pOutputSample[j] += nSampleOut; // First operation could actually only write '=' instead of '+='
|
|
}
|
|
#else
|
|
DLY_GetNext_Opt( pDelay, pInputSample, pOutputSample, nCount );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// PERFORMANCE: y/m is now baked into the 'b' gain params for each delay ( b = b/m )
|
|
// y = (y * g_MapIntoPBITSDivInt[m]) >> PBITS;
|
|
|
|
if ( pRva->fparallel == false )
|
|
{
|
|
// run series filters if present
|
|
|
|
flt_t * pFilter = pRva->pflt;
|
|
if ( pFilter != NULL)
|
|
{
|
|
for ( int j = 0 ; j < nCount ; ++j )
|
|
{
|
|
// For this, we are actually using the sample from the output buffer
|
|
int nSampleIn = pOutputSample[j];
|
|
int nSampleOut = FLT_GetNext( pFilter, nSampleIn );
|
|
pOutputSample[j] = nSampleOut;
|
|
}
|
|
}
|
|
}
|
|
|
|
// At the end, we have to write back the final result to the buffer (from pOutputSample to pBuffer).
|
|
// Because we have to skip integers it is not a simple memcpy.
|
|
if ( WRITER == ( 1 << CHANNEL_LEFT ) )
|
|
{
|
|
portable_samplepair_t * RESTRICT pWriteBuffer = pBuffer;
|
|
LocalOutputSample_t * RESTRICT pReadBuffer = pOutputSample;
|
|
while ( nCount >= 16 )
|
|
{
|
|
pWriteBuffer[0].left = pReadBuffer[0];
|
|
pWriteBuffer[1].left = pReadBuffer[1];
|
|
pWriteBuffer[2].left = pReadBuffer[2];
|
|
pWriteBuffer[3].left = pReadBuffer[3];
|
|
pWriteBuffer[4].left = pReadBuffer[4];
|
|
pWriteBuffer[5].left = pReadBuffer[5];
|
|
pWriteBuffer[6].left = pReadBuffer[6];
|
|
pWriteBuffer[7].left = pReadBuffer[7];
|
|
pWriteBuffer[8].left = pReadBuffer[8];
|
|
pWriteBuffer[9].left = pReadBuffer[9];
|
|
pWriteBuffer[10].left = pReadBuffer[10];
|
|
pWriteBuffer[11].left = pReadBuffer[11];
|
|
pWriteBuffer[12].left = pReadBuffer[12];
|
|
pWriteBuffer[13].left = pReadBuffer[13];
|
|
pWriteBuffer[14].left = pReadBuffer[14];
|
|
pWriteBuffer[15].left = pReadBuffer[15];
|
|
|
|
nCount -= 16;
|
|
pWriteBuffer += 16;
|
|
pReadBuffer += 16;
|
|
}
|
|
while ( nCount >= 1 )
|
|
{
|
|
pWriteBuffer->left = *pReadBuffer;
|
|
--nCount;
|
|
++pWriteBuffer;
|
|
++pReadBuffer;
|
|
}
|
|
}
|
|
else if ( WRITER == ( 1 << CHANNEL_RIGHT) )
|
|
{
|
|
portable_samplepair_t * RESTRICT pWriteBuffer = pBuffer;
|
|
LocalOutputSample_t * RESTRICT pReadBuffer = pOutputSample;
|
|
while ( nCount >= 16 )
|
|
{
|
|
pWriteBuffer[0].right = pReadBuffer[0];
|
|
pWriteBuffer[1].right = pReadBuffer[1];
|
|
pWriteBuffer[2].right = pReadBuffer[2];
|
|
pWriteBuffer[3].right = pReadBuffer[3];
|
|
pWriteBuffer[4].right = pReadBuffer[4];
|
|
pWriteBuffer[5].right = pReadBuffer[5];
|
|
pWriteBuffer[6].right = pReadBuffer[6];
|
|
pWriteBuffer[7].right = pReadBuffer[7];
|
|
pWriteBuffer[8].right = pReadBuffer[8];
|
|
pWriteBuffer[9].right = pReadBuffer[9];
|
|
pWriteBuffer[10].right = pReadBuffer[10];
|
|
pWriteBuffer[11].right = pReadBuffer[11];
|
|
pWriteBuffer[12].right = pReadBuffer[12];
|
|
pWriteBuffer[13].right = pReadBuffer[13];
|
|
pWriteBuffer[14].right = pReadBuffer[14];
|
|
pWriteBuffer[15].right = pReadBuffer[15];
|
|
|
|
nCount -= 16;
|
|
pWriteBuffer += 16;
|
|
pReadBuffer += 16;
|
|
}
|
|
while ( nCount >= 1 )
|
|
{
|
|
pWriteBuffer->right = *pReadBuffer;
|
|
--nCount;
|
|
++pWriteBuffer;
|
|
++pReadBuffer;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert( WRITER == ( ( 1 << CHANNEL_LEFT ) | ( 1 << CHANNEL_RIGHT ) ) );
|
|
|
|
portable_samplepair_t * RESTRICT pWriteBuffer = pBuffer;
|
|
LocalOutputSample_t * RESTRICT pReadBuffer = pOutputSample;
|
|
// Because we are writing left and write in this version, we could potentially use VMX operations
|
|
// Read 8 samples at a time (2 bytes * 8), sign extend them on 4 VMX registers and write them.
|
|
while ( nCount >= 16 )
|
|
{
|
|
pWriteBuffer[0].left = pWriteBuffer[0].right = pReadBuffer[0];
|
|
pWriteBuffer[1].left = pWriteBuffer[1].right = pReadBuffer[1];
|
|
pWriteBuffer[2].left = pWriteBuffer[2].right = pReadBuffer[2];
|
|
pWriteBuffer[3].left = pWriteBuffer[3].right = pReadBuffer[3];
|
|
pWriteBuffer[4].left = pWriteBuffer[4].right = pReadBuffer[4];
|
|
pWriteBuffer[5].left = pWriteBuffer[5].right = pReadBuffer[5];
|
|
pWriteBuffer[6].left = pWriteBuffer[6].right = pReadBuffer[6];
|
|
pWriteBuffer[7].left = pWriteBuffer[7].right = pReadBuffer[7];
|
|
pWriteBuffer[8].left = pWriteBuffer[8].right = pReadBuffer[8];
|
|
pWriteBuffer[9].left = pWriteBuffer[9].right = pReadBuffer[9];
|
|
pWriteBuffer[10].left = pWriteBuffer[10].right = pReadBuffer[10];
|
|
pWriteBuffer[11].left = pWriteBuffer[11].right = pReadBuffer[11];
|
|
pWriteBuffer[12].left = pWriteBuffer[12].right = pReadBuffer[12];
|
|
pWriteBuffer[13].left = pWriteBuffer[13].right = pReadBuffer[13];
|
|
pWriteBuffer[14].left = pWriteBuffer[14].right = pReadBuffer[14];
|
|
pWriteBuffer[15].left = pWriteBuffer[15].right = pReadBuffer[15];
|
|
|
|
nCount -= 16;
|
|
pWriteBuffer += 16;
|
|
pReadBuffer += 16;
|
|
}
|
|
while ( nCount >= 1 )
|
|
{
|
|
pWriteBuffer->left = pWriteBuffer->right = *pReadBuffer;
|
|
--nCount;
|
|
++pWriteBuffer;
|
|
++pReadBuffer;
|
|
}
|
|
}
|
|
}
|
|
|
|
// batch version for performance
|
|
// UNDONE: unwind RVA_GetNextN so that it directly calls DLY_GetNextN or MDY_GetNextN
|
|
|
|
inline void RVA_GetNextN( rva_t *prva, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb = pbuffer;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb->left = RVA_GetNext( prva, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb->right = RVA_GetNext( prva, pb->right );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb->left = pb->right = RVA_GetNext( prva, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
inline void RVA_GetNextN2( rva_t *pRva1, portable_samplepair_t *pbuffer1, rva_t *pRva2, portable_samplepair_t *pbuffer2, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb1 = pbuffer1;
|
|
portable_samplepair_t *pb2 = pbuffer2;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb1->left = RVA_GetNext( pRva1, pb1->left );
|
|
pb2->left = RVA_GetNext( pRva2, pb2->left );
|
|
RVA_Compare( *pRva1, *pRva2 );
|
|
pb1++;
|
|
pb2++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb1->right = RVA_GetNext( pRva1, pb1->right );
|
|
pb2->right = RVA_GetNext( pRva2, pb2->right );
|
|
RVA_Compare( *pRva1, *pRva2 );
|
|
pb1++;
|
|
pb2++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb1->left = pb1->right = RVA_GetNext( pRva1, pb1->left );
|
|
pb2->left = pb2->right = RVA_GetNext( pRva2, pb2->left );
|
|
RVA_Compare( *pRva1, *pRva2 );
|
|
pb1++;
|
|
pb2++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CheckCloneAccuracy( rva_t *prva, portable_samplepair_t *pbuffer, int nSampleCount, int op )
|
|
{
|
|
// Try not to modify the original values so the sound is kept pristine even with this test
|
|
portable_samplepair_t * pTempBuffer1 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
rva_t * pTempRva1 = RVA_Clone(prva);
|
|
RVA_Compare( *prva, *pTempRva1 );
|
|
|
|
portable_samplepair_t * pTempBuffer2 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
rva_t * pTempRva2 = RVA_Clone(prva);
|
|
RVA_Compare( *prva, *pTempRva2 );
|
|
|
|
portable_samplepair_t * pTempBuffer3 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
rva_t * pTempRva3 = RVA_Clone(prva);
|
|
RVA_Compare( *prva, *pTempRva3 );
|
|
|
|
// If we clone correctly, we should have the same output on the two buffers.
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
RVA_GetNextN( pTempRva1, pTempBuffer1, nSampleCount, op );
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
RVA_GetNextN( pTempRva2, pTempBuffer2, nSampleCount, op );
|
|
|
|
RVA_Compare( *pTempRva1, *pTempRva2 );
|
|
|
|
bool bFailed = ( memcmp( pTempBuffer1, pTempBuffer2, nSampleCount * sizeof( portable_samplepair_t ) ) != 0 );
|
|
if ( bFailed )
|
|
{
|
|
Warning("[Sound] Detected desynchronization during RVA cloning.\n");
|
|
|
|
// Normally the content should be the same, only the addresses (tagged [Addr]) should be different.
|
|
// No address should be the same (if that were the case, it would mean we missed something during the cloning).
|
|
|
|
DevMsg( "\n\nCloned RVA 1:\n\n" );
|
|
RVA_Print( *pTempRva1, 0 );
|
|
|
|
DevMsg( "\n\nCloned RVA 2:\n\n" );
|
|
RVA_Print( *pTempRva2, 0 );
|
|
|
|
// After that, let's try to re-clone again and display the values before any modification.
|
|
FreeDuplicatedSamplePairs( pTempBuffer1, nSampleCount );
|
|
RVA_Free(pTempRva1);
|
|
FreeDuplicatedSamplePairs( pTempBuffer2, nSampleCount );
|
|
RVA_Free(pTempRva2);
|
|
|
|
pTempBuffer1 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
pTempRva1 = RVA_Clone(prva);
|
|
RVA_Compare( *prva, *pTempRva1 );
|
|
|
|
pTempBuffer2 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
pTempRva2 = RVA_Clone(prva);
|
|
RVA_Compare( *prva, *pTempRva2 );
|
|
|
|
DevMsg( "\n\nInitial RVA:\n\n" );
|
|
RVA_Print( *prva, 0 );
|
|
|
|
DevMsg( "\n\nCloned RVA 1:\n\n" );
|
|
RVA_Print( *pTempRva1, 0 );
|
|
|
|
DevMsg( "\n\nCloned RVA 2:\n\n" );
|
|
RVA_Print( *pTempRva2, 0 );
|
|
|
|
// Re-run the transform so we can compare with the official result
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
RVA_GetNextN( pTempRva1, pTempBuffer1, nSampleCount, op );
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
RVA_GetNextN( pTempRva2, pTempBuffer2, nSampleCount, op );
|
|
RVA_Compare( *pTempRva1, *pTempRva2 );
|
|
}
|
|
|
|
// This will break the input buffer content, if this test is executed the sound will be off (esp. for reverberations and delays)
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
#if 0
|
|
// Use this method so it will compare sample by sample (help track more complex desyncs, but will slow the game down by a ton
|
|
RVA_GetNextN2( prva, pbuffer, pTempRva3, pTempBuffer3, nSampleCount, op );
|
|
#else
|
|
RVA_GetNextN( prva, pbuffer, nSampleCount, op );
|
|
#endif
|
|
RVA_Compare( *prva, *pTempRva1 );
|
|
|
|
bFailed = ( memcmp( pTempBuffer1, pbuffer, nSampleCount * sizeof( portable_samplepair_t ) ) != 0 );
|
|
if ( bFailed )
|
|
{
|
|
Warning("[Sound] Detected desynchronization during RVA cloning.\n");
|
|
|
|
// Normally the content should be the same, only the addresses (tagged [Addr]) should be different.
|
|
// No address should be the same (if that were the case, it would mean that we missed something during the cloning).
|
|
|
|
DevMsg( "\n\nInitial RVA:\n\n" );
|
|
RVA_Print( *prva, 0 );
|
|
|
|
DevMsg( "\n\nCloned RVA:\n\n" );
|
|
RVA_Print( *pTempRva1, 0 );
|
|
|
|
// Re-clone here to help detect the issue (before any modification)
|
|
portable_samplepair_t * pTempBuffer4 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
rva_t * pTempRva4 = RVA_Clone(prva);
|
|
|
|
DevMsg( "\n\nNew clone RVA:\n\n" );
|
|
RVA_Print( *pTempRva4, 0 );
|
|
|
|
FreeDuplicatedSamplePairs( pTempBuffer4, nSampleCount );
|
|
RVA_Free(pTempRva4);
|
|
}
|
|
|
|
FreeDuplicatedSamplePairs( pTempBuffer1, nSampleCount );
|
|
RVA_Free(pTempRva1);
|
|
FreeDuplicatedSamplePairs( pTempBuffer2, nSampleCount );
|
|
RVA_Free(pTempRva2);
|
|
FreeDuplicatedSamplePairs( pTempBuffer3, nSampleCount );
|
|
RVA_Free(pTempRva3);
|
|
}
|
|
#endif
|
|
|
|
inline void RVA_GetNextN_Opt( rva_t *prva, portable_samplepair_t *pbuffer, int nSampleCount, int op )
|
|
{
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
// Duplicate the values before the original buffer is going to be modified in CheckCloneAccuracy()
|
|
portable_samplepair_t * pTempBuffer = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
rva_t * pTempRva = RVA_Clone( prva );
|
|
RVA_Compare( *prva, *pTempRva );
|
|
|
|
CheckCloneAccuracy( prva, pbuffer, nSampleCount, op );
|
|
|
|
int count = nSampleCount;
|
|
portable_samplepair_t *pb = pTempBuffer;
|
|
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
RVA_GetNext_Opt<CHANNEL_LEFT, 1 << CHANNEL_LEFT>( pTempRva, pb, count );
|
|
break;
|
|
case OP_RIGHT:
|
|
RVA_GetNext_Opt<CHANNEL_RIGHT, 1 << CHANNEL_RIGHT>( pTempRva, pb, count );
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
RVA_GetNext_Opt<CHANNEL_LEFT, (1 << CHANNEL_LEFT) | (1 << CHANNEL_RIGHT)>( pTempRva, pb, count );
|
|
break;
|
|
}
|
|
|
|
RVA_Compare( *prva, *pTempRva );
|
|
|
|
bool bFailed = ( memcmp( pTempBuffer, pbuffer, nSampleCount * sizeof( portable_samplepair_t ) ) != 0 );
|
|
Assert( bFailed == false );
|
|
|
|
FreeDuplicatedSamplePairs( pTempBuffer, nSampleCount );
|
|
RVA_Free(pTempRva);
|
|
#else
|
|
if ( snd_dsp_optimization.GetBool() )
|
|
{
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
RVA_GetNext_Opt<CHANNEL_LEFT, 1 << CHANNEL_LEFT>( prva, pbuffer, nSampleCount );
|
|
break;
|
|
case OP_RIGHT:
|
|
RVA_GetNext_Opt<CHANNEL_RIGHT, 1 << CHANNEL_RIGHT>( prva, pbuffer, nSampleCount );
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
RVA_GetNext_Opt<CHANNEL_LEFT, (1 << CHANNEL_LEFT) | (1 << CHANNEL_RIGHT)>( prva, pbuffer, nSampleCount );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RVA_GetNextN( prva, pbuffer, nSampleCount, op );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// reverb parameter order
|
|
|
|
typedef enum
|
|
{
|
|
|
|
// parameter order
|
|
|
|
rva_size_max,
|
|
rva_size_min,
|
|
|
|
rva_inumdelays,
|
|
rva_ifeedback,
|
|
rva_igain,
|
|
|
|
rva_icutoff,
|
|
|
|
rva_ifparallel,
|
|
rva_imoddly,
|
|
rva_imodrate,
|
|
|
|
rva_width,
|
|
rva_depth,
|
|
rva_height,
|
|
|
|
rva_fbwidth,
|
|
rva_fbdepth,
|
|
rva_fbheight,
|
|
|
|
rva_iftaps,
|
|
|
|
rva_cparam // # of params
|
|
} rva_e;
|
|
|
|
// filter parameter ranges
|
|
|
|
prm_rng_t rva_rng[] = {
|
|
|
|
{rva_cparam, 0, 0}, // first entry is # of parameters
|
|
|
|
// reverb params
|
|
{rva_size_max, 0.0, 1000.0}, // max room delay in milliseconds
|
|
{rva_size_min, 0.0, 1000.0}, // min room delay in milliseconds
|
|
{rva_inumdelays,1.0, 12.0}, // controls # of parallel or series delays
|
|
{rva_ifeedback, 0.0, 1.0}, // feedback of delays
|
|
{rva_igain, 0.0, 10.0}, // output gain
|
|
|
|
// filter params for each parallel reverb (quality set to 0 for max execution speed)
|
|
|
|
{rva_icutoff, 10, 22050},
|
|
|
|
{rva_ifparallel, 0, 1}, // if 1, then all filters operate in parallel with delays. otherwise filter output only
|
|
{rva_imoddly, 0.0, 50.0}, // if > 0 then all delays are modulating delays, mod param controls milliseconds of mod depth
|
|
{rva_imodrate, 0.0, 10.0}, // how many delay repetitions pass between mod changes to delayl
|
|
|
|
// override params - for more detailed description of room
|
|
// note: width/depth/height < 0 only for some automatic dsp presets
|
|
{rva_width, -1000.0, 1000.0}, // 0-1000.0 millisec (room width in feet) - used instead of size if non-zero
|
|
{rva_depth, -1000.0, 1000.0}, // 0-1000.0 room depth in feet - used instead of size if non-zero
|
|
{rva_height, -1000.0, 1000.0}, // 0-1000.0 room height in feet - used instead of size if non-zero
|
|
|
|
{rva_fbwidth, -1.0, 1.0}, // 0-1.0 material reflectivity - used as feedback param instead of decay if non-zero
|
|
{rva_fbdepth, -1.0, 1.0}, // 0-1.0 material reflectivity - used as feedback param instead of decay if non-zero
|
|
{rva_fbheight, -1.0, 1.0}, // 0-1.0 material reflectivity - used as feedback param instead of decay if non-zero
|
|
// if < 0, a predelay is allocated, then feedback is -1*param given
|
|
|
|
{rva_iftaps, 0.0, 0.333} // if > 0, use 3 extra taps with delay values = d * (1 - faps*n) n = 0,1,2,3
|
|
};
|
|
|
|
#define RVA_BASEM 1 // base number of parallel delays
|
|
|
|
// nominal delay and feedback values. More delays = more density.
|
|
|
|
#define RVADLYSMAX 49
|
|
float rvadlys[] = {18, 23, 28, 33, 42, 21, 26, 36, 39, 45, 47, 30};
|
|
float rvafbs[] = {0.9, 0.9, 0.9, 0.85, 0.8, 0.9, 0.9, 0.85, 0.8, 0.8, 0.8, 0.85};
|
|
|
|
#define SWAP(a,b,t) {(t) = (a); (a) = (b); (b) = (t);}
|
|
|
|
#define RVA_MIN_SEPARATION 7 // minimum separation between reverbs, in ms.
|
|
|
|
// Construct D,a,b delay arrays given array of length,width,height sizes and feedback values
|
|
// rgd[] array of delay values in milliseconds (feet)
|
|
// rgf[] array of feedback values 0..1
|
|
// m # of parallel reverbs to construct
|
|
// D[] array of output delay values for parallel reverbs
|
|
// a[] array of output feedback values
|
|
// b[] array of output gain values = 1/m
|
|
// gain - output gain
|
|
// feedback - default feedback if rgf members are 0
|
|
|
|
void RVA_ConstructDelays( float *rgd, float *rgf, int m, int *D, int *a, int *b, float gain, float feedback )
|
|
{
|
|
|
|
int i;
|
|
float r;
|
|
int d;
|
|
float t, d1, d2, dm;
|
|
bool bpredelay;
|
|
|
|
// sort descending, so rgd[0] is largest delay & rgd[2] is smallest
|
|
|
|
if (rgd[2] > rgd[1]) { SWAP(rgd[2], rgd[1], t); SWAP(rgf[2], rgf[1], t); }
|
|
if (rgd[1] > rgd[0]) { SWAP(rgd[0], rgd[1], t); SWAP(rgf[0], rgf[1], t); }
|
|
if (rgd[2] > rgd[1]) { SWAP(rgd[2], rgd[1], t); SWAP(rgf[2], rgf[1], t); }
|
|
|
|
// if all feedback values 0, use default feedback
|
|
|
|
if (rgf[0] == 0.0 && rgf[1] == 0.0 && rgf[2] == 0.0 )
|
|
{
|
|
// use feedback param for all
|
|
|
|
rgf[0] = rgf[1] = rgf[2] = feedback;
|
|
|
|
// adjust feedback down for larger delays so that decay is constant for all delays
|
|
|
|
rgf[0] = DLY_NormalizeFeedback( rgd[2], rgf[2], rgd[0] );
|
|
rgf[1] = DLY_NormalizeFeedback( rgd[2], rgf[2], rgd[1] );
|
|
|
|
}
|
|
|
|
// make sure all reverbs are different by at least RVA_MIN_SEPARATION * m/3 m is 3,6,9 or 12
|
|
|
|
int dmin = (m/3) * RVA_MIN_SEPARATION;
|
|
|
|
d1 = rgd[1] - rgd[2];
|
|
|
|
if (d1 <= dmin)
|
|
rgd[1] += (dmin-d1); // make difference = dmin
|
|
|
|
d2 = rgd[0] - rgd[1];
|
|
|
|
if (d2 <= dmin)
|
|
rgd[0] += (dmin-d1); // make difference = dmin
|
|
|
|
for ( i = 0; i < m; i++ )
|
|
{
|
|
// reverberations due to room width, depth, height
|
|
// assume sound moves at approx 1ft/ms
|
|
|
|
int j = (int)(fmod ((float)i, 3.0f)); // j counts 0,1,2 0,1,2 0,1..
|
|
|
|
d = (int)rgd[j];
|
|
r = fabs(rgf[j]);
|
|
|
|
bpredelay = ((rgf[j] < 0) && i < 3);
|
|
|
|
// re-use predelay values as reverb values:
|
|
|
|
if (rgf[j] < 0 && !bpredelay)
|
|
d = MAX((int)(rgd[j] / 4.0), RVA_MIN_SEPARATION);
|
|
|
|
if (i < 3)
|
|
dm = 0.0;
|
|
else
|
|
dm = MAX( RVA_MIN_SEPARATION * (i/3), ((i/3) * ((float)d * 0.18)) );
|
|
|
|
d += (int)dm;
|
|
D[i] = MSEC_TO_SAMPS(d);
|
|
|
|
// D[i] = MSEC_TO_SAMPS(d + ((i/3) * RVA_MIN_SEPARATION)); // (i/3) counts 0,0,0 1,1,1 2,2,2 ... separate all reverbs by 5ms
|
|
|
|
// feedback - due to wall/floor/ceiling reflectivity
|
|
|
|
a[i] = (int) MIN (0.999 * PMAX, (float)PMAX * r);
|
|
|
|
if (bpredelay)
|
|
a[i] = -a[i]; // flag delay as predelay
|
|
|
|
b[i] = (int)((float)(gain * PMAX) / (float)m);
|
|
}
|
|
}
|
|
|
|
void RVA_PerfTest()
|
|
{
|
|
double time1, time2;
|
|
|
|
int i;
|
|
int k;
|
|
int j;
|
|
int m;
|
|
int a[100];
|
|
|
|
time1 = Plat_FloatTime();
|
|
|
|
for (m = 0; m < 1000; m++)
|
|
{
|
|
for (i = 0, j = 10000; i < 10000; i++, j--)
|
|
{
|
|
// j = j % 6;
|
|
// k = (i * j) >> PBITS;
|
|
|
|
k = i / ((j % 6) + 1);
|
|
}
|
|
}
|
|
|
|
time2 = Plat_FloatTime();
|
|
|
|
DevMsg("divide = %2.5f \n", (time2-time1));
|
|
|
|
|
|
for (i=1;i<10;i++)
|
|
a[i] = PMAX / i;
|
|
|
|
time1 = Plat_FloatTime();
|
|
|
|
for (m = 0; m < 1000; m++)
|
|
{
|
|
for (i = 0, j = 10000; i < 10000; i++, j--)
|
|
{
|
|
k = (i * a[(j % 6) + 1] ) >> PBITS;
|
|
}
|
|
}
|
|
|
|
time2 = Plat_FloatTime();
|
|
|
|
DevMsg("shift & multiply = %2.5f \n", (time2-time1));
|
|
}
|
|
|
|
rva_t * RVA_Params ( prc_t *pprc )
|
|
{
|
|
rva_t *prva;
|
|
|
|
float size_max = pprc->prm[rva_size_max]; // max delay size
|
|
float size_min = pprc->prm[rva_size_min]; // min delay size
|
|
|
|
float numdelays = pprc->prm[rva_inumdelays]; // controls # of parallel delays
|
|
float feedback = pprc->prm[rva_ifeedback]; // 0-1.0 controls feedback parameters
|
|
float gain = pprc->prm[rva_igain]; // 0-10.0 controls output gain
|
|
|
|
float cutoff = pprc->prm[rva_icutoff]; // filter cutoff
|
|
|
|
float fparallel = pprc->prm[rva_ifparallel]; // if true, all filters are in delay feedback paths - otherwise single flt on output
|
|
|
|
float fmoddly = pprc->prm[rva_imoddly]; // if > 0, milliseconds of delay mod depth
|
|
float fmodrate = pprc->prm[rva_imodrate]; // if fmoddly > 0, # of delay repetitions between modulations
|
|
|
|
float width = fabs(pprc->prm[rva_width]); // 0-1000 controls size of 1/3 of delays - used instead of size if non-zero
|
|
float depth = fabs(pprc->prm[rva_depth]); // 0-1000 controls size of 1/3 of delays - used instead of size if non-zero
|
|
float height = fabs(pprc->prm[rva_height]); // 0-1000 controls size of 1/3 of delays - used instead of size if non-zero
|
|
|
|
float fbwidth = pprc->prm[rva_fbwidth]; // feedback parameter for walls 0..2
|
|
float fbdepth = pprc->prm[rva_fbdepth]; // feedback parameter for floor
|
|
float fbheight = pprc->prm[rva_fbheight]; // feedback parameter for ceiling
|
|
|
|
float ftaps = pprc->prm[rva_iftaps]; // if > 0 increase reverb density using 3 extra taps d = (1.0 - ftaps * n) n = 0,1,2,3
|
|
|
|
|
|
|
|
// RVA_PerfTest();
|
|
|
|
// D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
|
|
// a array of reverb feedback parms for parallel delays
|
|
// b array of CRVB_P_DLYS - mix params for parallel reverbs
|
|
// m - number of parallel delays
|
|
|
|
int D[CRVA_DLYS];
|
|
int a[CRVA_DLYS];
|
|
int b[CRVA_DLYS];
|
|
int m;
|
|
|
|
// limit # delays 1-12
|
|
|
|
m = iclamp (numdelays, RVA_BASEM, CRVA_DLYS);
|
|
|
|
// set up D (delay) a (feedback) b (gain) arrays
|
|
|
|
if ( int(width) || int(height) || int(depth) )
|
|
{
|
|
// if width, height, depth given, use values as simple delays
|
|
|
|
float rgd[3];
|
|
float rgfb[3];
|
|
|
|
// force m to 3, 6, 9 or 12
|
|
|
|
if (m < 3) m = 3;
|
|
if (m > 3 && m < 6) m = 6;
|
|
if (m > 6 && m < 9) m = 9;
|
|
if (m > 9) m = 12;
|
|
|
|
rgd[0] = width; rgfb[0] = fbwidth;
|
|
rgd[1] = depth; rgfb[1] = fbdepth;
|
|
rgd[2] = height; rgfb[2] = fbheight;
|
|
|
|
RVA_ConstructDelays( rgd, rgfb, m, D, a, b, gain, feedback );
|
|
}
|
|
else
|
|
{
|
|
// use size parameter instead of width/depth/height
|
|
|
|
for ( int i = 0; i < m; i++ )
|
|
{
|
|
// delays of parallel reverb. D[0] = size_min.
|
|
|
|
D[i] = MSEC_TO_SAMPS( size_min + (int)( ((float)(size_max - size_min) / (float)m) * (float)i) );
|
|
|
|
// feedback and gain of parallel reverb
|
|
|
|
if (i == 0)
|
|
{
|
|
// set feedback for smallest delay
|
|
|
|
a[i] = (int) MIN (0.999 * PMAX, (float)PMAX * feedback );
|
|
}
|
|
else
|
|
{
|
|
// adjust feedback down for larger delays so that decay time is constant
|
|
|
|
a[i] = (int) MIN (0.999 * PMAX, (float)PMAX * DLY_NormalizeFeedback( D[0], feedback, D[i] ) );
|
|
}
|
|
|
|
b[i] = (int) ((float)(gain * PMAX) / (float)m);
|
|
}
|
|
}
|
|
|
|
// add filter
|
|
|
|
flt_t *pflt = NULL;
|
|
|
|
if ( cutoff )
|
|
{
|
|
|
|
// set up dummy lowpass filter to convert params
|
|
|
|
prc_t prcf;
|
|
|
|
prcf.prm[flt_iquality] = QUA_LO; // force filter to low quality for faster execution time
|
|
prcf.prm[flt_icutoff] = cutoff;
|
|
prcf.prm[flt_iftype] = FLT_LP;
|
|
prcf.prm[flt_iqwidth] = 0;
|
|
prcf.prm[flt_igain] = 1.0;
|
|
|
|
pflt = (flt_t *)FLT_Params ( &prcf );
|
|
}
|
|
|
|
prva = RVA_Alloc ( D, a, b, m, pflt, fparallel, fmoddly, fmodrate, ftaps );
|
|
|
|
FLT_Free( pflt );
|
|
|
|
return prva;
|
|
}
|
|
|
|
|
|
inline void * RVA_VParams ( void *p )
|
|
{
|
|
PRC_CheckParams ( (prc_t *)p, rva_rng );
|
|
return (void *) RVA_Params ((prc_t *)p);
|
|
}
|
|
|
|
inline void RVA_Mod ( void *p, float v ) { return; }
|
|
|
|
|
|
|
|
////////////
|
|
// Diffusor
|
|
///////////
|
|
|
|
// (N series allpass reverbs)
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CDFRS 128
|
|
#else
|
|
#define CDFRS 64 // max number of series reverbs active
|
|
#endif
|
|
|
|
#define CDFR_DLYS 16 // max number of delays making up diffusor
|
|
|
|
struct dfr_t
|
|
{
|
|
bool fused;
|
|
int n; // series allpass delays
|
|
int w[CDFR_DLYS]; // internal state array for series allpass filters
|
|
|
|
dly_t *pdlys[CDFR_DLYS]; // array of pointers to delays
|
|
};
|
|
|
|
dfr_t dfrs[CDFRS];
|
|
|
|
void DFR_Init ( dfr_t *pdfr ) { if ( pdfr ) Q_memset (pdfr, 0, sizeof (dfr_t)); }
|
|
void DFR_InitAll( void ) { for (int i = 0; i < CDFRS; i++) DFR_Init ( &dfrs[i] ); }
|
|
|
|
// free parallel series reverb
|
|
|
|
void DFR_Free( dfr_t *pdfr )
|
|
{
|
|
if ( pdfr )
|
|
{
|
|
// free all delays
|
|
|
|
for (int i = 0; i < CDFR_DLYS; i++)
|
|
DLY_Free ( pdfr->pdlys[i] );
|
|
|
|
Q_memset( pdfr, 0, sizeof (dfr_t) );
|
|
}
|
|
}
|
|
|
|
|
|
void DFR_FreeAll( void ) { for (int i = 0; i < CDFRS; i++) DFR_Free( &dfrs[i] ); }
|
|
|
|
// create n series allpass reverbs
|
|
|
|
// D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
|
|
// a array of reverb feedback parms for series delays
|
|
// b array of gain params for parallel reverbs
|
|
// n - number of series delays
|
|
|
|
dfr_t * DFR_Alloc ( int *D, int *a, int *b, int n )
|
|
{
|
|
|
|
int i;
|
|
dfr_t *pdfr;
|
|
|
|
// find open slot
|
|
|
|
for (i = 0; i < CDFRS; i++)
|
|
{
|
|
if (!dfrs[i].fused)
|
|
break;
|
|
}
|
|
|
|
// return null if no free slots
|
|
|
|
if (i == CDFRS)
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate diffusor.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
pdfr = &dfrs[i];
|
|
|
|
DFR_Init( pdfr );
|
|
|
|
// alloc reverbs
|
|
|
|
for (i = 0; i < n; i++)
|
|
pdfr->pdlys[i] = DLY_Alloc( D[i], a[i], b[i], DLY_ALLPASS );
|
|
|
|
// if we failed to alloc any reverb, free all, return NULL
|
|
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
if ( !pdfr->pdlys[i])
|
|
{
|
|
DFR_Free( pdfr );
|
|
DevMsg ("DSP: Warning, failed to allocate delay for diffusor.\n" );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
pdfr->fused = true;
|
|
pdfr->n = n;
|
|
|
|
return pdfr;
|
|
}
|
|
|
|
void DFR_Print( const dfr_t & dfr, int nIndentation )
|
|
{
|
|
const char * pIndent = GetIndentationText( nIndentation );
|
|
DevMsg( "%sDFR: %p [Addr]\n", pIndent, &dfr );
|
|
DevMsg( "%sfused: %d\n", pIndent, ( int ) dfr.fused );
|
|
for ( int i = 0 ; i < CDFR_DLYS ; ++i )
|
|
{
|
|
DevMsg( "%sDelay[%d]: ", pIndent, i );
|
|
if ( dfr.pdlys[i] != NULL )
|
|
{
|
|
DLY_Print( *dfr.pdlys[i], nIndentation + 1 );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "NULL\n" );
|
|
}
|
|
}
|
|
}
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
dfr_t * DFR_Clone( dfr_t * pOldDfr )
|
|
{
|
|
int i;
|
|
for ( i = 0; i < CDFRS; i++ )
|
|
{
|
|
if ( !dfrs[i].fused )
|
|
break;
|
|
}
|
|
|
|
// return null if no free slots
|
|
|
|
if (i == CDFRS)
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate diffusor.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
dfr_t * pNewDfr = &dfrs[i];
|
|
|
|
memcpy(pNewDfr, pOldDfr, sizeof(dfr_t));
|
|
|
|
for ( int j = 0 ; j < CRVA_DLYS ; ++j )
|
|
{
|
|
// First we do MDYs. In some cases, MDYs can point to DLY that can be stored in the pdlys array
|
|
// In that case instead of cloning the DLY, we will just update the pointer.
|
|
if ( pOldDfr->pdlys[j] != NULL )
|
|
{
|
|
pNewDfr->pdlys[j] = DLY_Clone( pOldDfr->pdlys[j] );
|
|
}
|
|
}
|
|
return pNewDfr;
|
|
}
|
|
|
|
void DFR_Compare( const dfr_t & leftDfr, const dfr_t & rightDfr )
|
|
{
|
|
Assert ( &leftDfr != &rightDfr );
|
|
|
|
Assert( leftDfr.fused == rightDfr.fused );
|
|
for ( int i = 0 ; i < CDFR_DLYS ; ++i )
|
|
{
|
|
if ( CheckPointers( leftDfr.pdlys[i], rightDfr.pdlys[i] ) )
|
|
{
|
|
DLY_Compare( *leftDfr.pdlys[i], *rightDfr.pdlys[i] );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// series reverberator
|
|
|
|
inline int DFR_GetNext( dfr_t *pdfr, int x )
|
|
{
|
|
int i;
|
|
int y;
|
|
dly_t *pdly;
|
|
|
|
y = x;
|
|
|
|
for (i = 0; i < pdfr->n; i++)
|
|
{
|
|
pdly = pdfr->pdlys[i];
|
|
y = DelayAllpass( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, y );
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
template <int READER, int WRITER>
|
|
inline void DFR_GetNext_Opt ( dfr_t *pdfr, portable_samplepair_t * pBuffer, int nCount )
|
|
{
|
|
// Because we do one filter at a time (and not one sample at a time)
|
|
// We either have to copy the input on an intermediate buffer, or write the output to an intermediate buffer.
|
|
// The faster is to use an intermediate output buffer as we can quickly zero it (it is slower to copy the input).
|
|
|
|
// Unlike the samples, the buffers here are actually short (so we use less memory - less L2 cache misses).
|
|
int nSizeToUse = sizeof(LocalOutputSample_t) * nCount;
|
|
int nSizeToAllocate = ALIGN_VALUE( nSizeToUse, CACHE_LINE_SIZE ); // Align on 128 as we are going to clear per cache-line
|
|
|
|
LocalOutputSample_t * pOutputSample = (LocalOutputSample_t *)alloca( nSizeToAllocate + CACHE_LINE_SIZE); // One more cache line as we are going to clear more than necessary...
|
|
pOutputSample = (LocalOutputSample_t *)ALIGN_VALUE( (intp)pOutputSample, CACHE_LINE_SIZE );
|
|
|
|
int nNumberOfCacheLinesToClear = ALIGN_VALUE( nSizeToAllocate, CACHE_LINE_SIZE ) / CACHE_LINE_SIZE;
|
|
LocalOutputSample_t * pCurrentCacheLine = pOutputSample;
|
|
// Given that we often have 500 to 1000 samples, it means that we are going to clear 2 to 4 Kb.
|
|
// (i.e. up to 32 cache lines). This will saturate the cache pipeline (but it easier to do it now instead of doing it within each filter).
|
|
while ( nNumberOfCacheLinesToClear > 0 )
|
|
{
|
|
PREZERO_128( pCurrentCacheLine, 0 );
|
|
pCurrentCacheLine += CACHE_LINE_SIZE / sizeof(LocalOutputSample_t);
|
|
--nNumberOfCacheLinesToClear;
|
|
}
|
|
|
|
// Then we are going to apply to the buffer each filter (one after the other for a set of samples)
|
|
// We are going to increase the number of loads and stores but at the end we can reduce the number of switches and unroll some calculation
|
|
|
|
int * pInputSample;
|
|
if ( READER == CHANNEL_LEFT )
|
|
{
|
|
pInputSample = &pBuffer->left;
|
|
}
|
|
else
|
|
{
|
|
Assert( READER == CHANNEL_RIGHT );
|
|
pInputSample = &pBuffer->right;
|
|
}
|
|
// At that point, each reader will have to skip one integer after reading one.
|
|
|
|
// Prefetch a bit (the next cache line) - again may continue saturate the buffer - but this will be read soon anyway.
|
|
PREFETCH_128( pInputSample, 128 );
|
|
|
|
// We are using the delay pass differently here compared to other cases, instead of adding the delay one after the other,
|
|
// they are used in feedback loop. Output of one is the input of the other...
|
|
if ( pdfr->n != 0)
|
|
{
|
|
// The first one has the normal input (with increment of 2), and normal output (with increment of 1).
|
|
// We don't care about the previous value so replace instead of adding to 0
|
|
dly_t *pdly = pdfr->pdlys[0];
|
|
DelayAllpass_Opt3<2, MM_REPLACE>( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, pInputSample, pOutputSample, nCount );
|
|
}
|
|
else
|
|
{
|
|
Assert( false ); // This code does not handle this gracefully - normally we would copy the input to the output directly...
|
|
// TODO: Add support for this
|
|
}
|
|
// Then we do the delays after (so starting at 1)
|
|
for (int i = 1; i < pdfr->n; ++i)
|
|
{
|
|
dly_t *pdly = pdfr->pdlys[i];
|
|
// This time, the input is the previous output, thus the increment is 1
|
|
// And we replace the value (for the feedback loop) instead of adding
|
|
DelayAllpass_Opt3<1, MM_REPLACE>( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, pOutputSample, pOutputSample, nCount );
|
|
}
|
|
|
|
// At the end, we have to write back the final result to the buffer (from pOutputSample to pBuffer).
|
|
// Because we have to skip integers it is not a simple memcpy.
|
|
if ( WRITER == ( 1 << CHANNEL_LEFT ) )
|
|
{
|
|
portable_samplepair_t * RESTRICT pWriteBuffer = pBuffer;
|
|
LocalOutputSample_t * RESTRICT pReadBuffer = pOutputSample;
|
|
while ( nCount >= 16 )
|
|
{
|
|
pWriteBuffer[0].left = pReadBuffer[0];
|
|
pWriteBuffer[1].left = pReadBuffer[1];
|
|
pWriteBuffer[2].left = pReadBuffer[2];
|
|
pWriteBuffer[3].left = pReadBuffer[3];
|
|
pWriteBuffer[4].left = pReadBuffer[4];
|
|
pWriteBuffer[5].left = pReadBuffer[5];
|
|
pWriteBuffer[6].left = pReadBuffer[6];
|
|
pWriteBuffer[7].left = pReadBuffer[7];
|
|
pWriteBuffer[8].left = pReadBuffer[8];
|
|
pWriteBuffer[9].left = pReadBuffer[9];
|
|
pWriteBuffer[10].left = pReadBuffer[10];
|
|
pWriteBuffer[11].left = pReadBuffer[11];
|
|
pWriteBuffer[12].left = pReadBuffer[12];
|
|
pWriteBuffer[13].left = pReadBuffer[13];
|
|
pWriteBuffer[14].left = pReadBuffer[14];
|
|
pWriteBuffer[15].left = pReadBuffer[15];
|
|
|
|
nCount -= 16;
|
|
pWriteBuffer += 16;
|
|
pReadBuffer += 16;
|
|
}
|
|
while ( nCount >= 1 )
|
|
{
|
|
pWriteBuffer->left = *pReadBuffer;
|
|
--nCount;
|
|
++pWriteBuffer;
|
|
++pReadBuffer;
|
|
}
|
|
}
|
|
else if ( WRITER == ( 1 << CHANNEL_RIGHT) )
|
|
{
|
|
portable_samplepair_t * RESTRICT pWriteBuffer = pBuffer;
|
|
LocalOutputSample_t * RESTRICT pReadBuffer = pOutputSample;
|
|
while ( nCount >= 16 )
|
|
{
|
|
pWriteBuffer[0].right = pReadBuffer[0];
|
|
pWriteBuffer[1].right = pReadBuffer[1];
|
|
pWriteBuffer[2].right = pReadBuffer[2];
|
|
pWriteBuffer[3].right = pReadBuffer[3];
|
|
pWriteBuffer[4].right = pReadBuffer[4];
|
|
pWriteBuffer[5].right = pReadBuffer[5];
|
|
pWriteBuffer[6].right = pReadBuffer[6];
|
|
pWriteBuffer[7].right = pReadBuffer[7];
|
|
pWriteBuffer[8].right = pReadBuffer[8];
|
|
pWriteBuffer[9].right = pReadBuffer[9];
|
|
pWriteBuffer[10].right = pReadBuffer[10];
|
|
pWriteBuffer[11].right = pReadBuffer[11];
|
|
pWriteBuffer[12].right = pReadBuffer[12];
|
|
pWriteBuffer[13].right = pReadBuffer[13];
|
|
pWriteBuffer[14].right = pReadBuffer[14];
|
|
pWriteBuffer[15].right = pReadBuffer[15];
|
|
|
|
nCount -= 16;
|
|
pWriteBuffer += 16;
|
|
pReadBuffer += 16;
|
|
}
|
|
while ( nCount >= 1 )
|
|
{
|
|
pWriteBuffer->right = *pReadBuffer;
|
|
--nCount;
|
|
++pWriteBuffer;
|
|
++pReadBuffer;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert( WRITER == ( ( 1 << CHANNEL_LEFT ) | ( 1 << CHANNEL_RIGHT ) ) );
|
|
|
|
portable_samplepair_t * RESTRICT pWriteBuffer = pBuffer;
|
|
LocalOutputSample_t * RESTRICT pReadBuffer = pOutputSample;
|
|
// Because we are writing left and write in this version, we could potentially use VMX operations
|
|
// Read 8 samples at a time (2 bytes * 8), sign extend them on 4 VMX registers and write them.
|
|
while ( nCount >= 16 )
|
|
{
|
|
pWriteBuffer[0].left = pWriteBuffer[0].right = pReadBuffer[0];
|
|
pWriteBuffer[1].left = pWriteBuffer[1].right = pReadBuffer[1];
|
|
pWriteBuffer[2].left = pWriteBuffer[2].right = pReadBuffer[2];
|
|
pWriteBuffer[3].left = pWriteBuffer[3].right = pReadBuffer[3];
|
|
pWriteBuffer[4].left = pWriteBuffer[4].right = pReadBuffer[4];
|
|
pWriteBuffer[5].left = pWriteBuffer[5].right = pReadBuffer[5];
|
|
pWriteBuffer[6].left = pWriteBuffer[6].right = pReadBuffer[6];
|
|
pWriteBuffer[7].left = pWriteBuffer[7].right = pReadBuffer[7];
|
|
pWriteBuffer[8].left = pWriteBuffer[8].right = pReadBuffer[8];
|
|
pWriteBuffer[9].left = pWriteBuffer[9].right = pReadBuffer[9];
|
|
pWriteBuffer[10].left = pWriteBuffer[10].right = pReadBuffer[10];
|
|
pWriteBuffer[11].left = pWriteBuffer[11].right = pReadBuffer[11];
|
|
pWriteBuffer[12].left = pWriteBuffer[12].right = pReadBuffer[12];
|
|
pWriteBuffer[13].left = pWriteBuffer[13].right = pReadBuffer[13];
|
|
pWriteBuffer[14].left = pWriteBuffer[14].right = pReadBuffer[14];
|
|
pWriteBuffer[15].left = pWriteBuffer[15].right = pReadBuffer[15];
|
|
|
|
nCount -= 16;
|
|
pWriteBuffer += 16;
|
|
pReadBuffer += 16;
|
|
}
|
|
while ( nCount >= 1 )
|
|
{
|
|
pWriteBuffer->left = pWriteBuffer->right = *pReadBuffer;
|
|
--nCount;
|
|
++pWriteBuffer;
|
|
++pReadBuffer;
|
|
}
|
|
}
|
|
}
|
|
|
|
// batch version for performance
|
|
|
|
inline void DFR_GetNextN( dfr_t *pdfr, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb = pbuffer;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb->left = DFR_GetNext( pdfr, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb->right = DFR_GetNext( pdfr, pb->right );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb->left = pb->right = DFR_GetNext( pdfr, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
void CheckCloneAccuracy( dfr_t *pDfr, portable_samplepair_t *pbuffer, int nSampleCount, int op )
|
|
{
|
|
// Try not to modify the original values so the sound is kept pristine even with this test
|
|
portable_samplepair_t * pTempBuffer1 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
dfr_t * pTempDfr1 = DFR_Clone(pDfr);
|
|
DFR_Compare( *pDfr, *pTempDfr1 );
|
|
|
|
portable_samplepair_t * pTempBuffer2 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
dfr_t * pTempDfr2 = DFR_Clone(pDfr);
|
|
DFR_Compare( *pDfr, *pTempDfr2 );
|
|
|
|
portable_samplepair_t * pTempBuffer3 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
dfr_t * pTempDfr3 = DFR_Clone(pDfr);
|
|
DFR_Compare( *pDfr, *pTempDfr3 );
|
|
|
|
// If we clone correctly, we should have the same output on the two buffers.
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
DFR_GetNextN( pTempDfr1, pTempBuffer1, nSampleCount, op );
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
DFR_GetNextN( pTempDfr2, pTempBuffer2, nSampleCount, op );
|
|
|
|
DFR_Compare( *pTempDfr1, *pTempDfr2 );
|
|
|
|
bool bFailed = ( memcmp( pTempBuffer1, pTempBuffer2, nSampleCount * sizeof( portable_samplepair_t ) ) != 0 );
|
|
if ( bFailed )
|
|
{
|
|
Warning("[Sound] Detected desynchronization during DFR cloning.\n");
|
|
|
|
// Normally the content should be the same, only the addresses (tagged [Addr]) should be different.
|
|
// No address should be the same (if that were the case, it would mean we missed something during the cloning).
|
|
|
|
DevMsg( "\n\nCloned RVA 1:\n\n" );
|
|
DFR_Print( *pTempDfr1, 0 );
|
|
|
|
DevMsg( "\n\nCloned RVA 2:\n\n" );
|
|
DFR_Print( *pTempDfr2, 0 );
|
|
|
|
// After that, let's try to re-clone again and display the values before any modification.
|
|
FreeDuplicatedSamplePairs( pTempBuffer1, nSampleCount );
|
|
DFR_Free(pTempDfr1);
|
|
FreeDuplicatedSamplePairs( pTempBuffer2, nSampleCount );
|
|
DFR_Free(pTempDfr2);
|
|
|
|
pTempBuffer1 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
pTempDfr1 = DFR_Clone(pDfr);
|
|
DFR_Compare( *pDfr, *pTempDfr1 );
|
|
|
|
pTempBuffer2 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
pTempDfr2 = DFR_Clone(pDfr);
|
|
DFR_Compare( *pDfr, *pTempDfr2 );
|
|
|
|
DevMsg( "\n\nInitial DFR:\n\n" );
|
|
DFR_Print( *pDfr, 0 );
|
|
|
|
DevMsg( "\n\nCloned DFR 1:\n\n" );
|
|
DFR_Print( *pTempDfr1, 0 );
|
|
|
|
DevMsg( "\n\nCloned DFR 2:\n\n" );
|
|
DFR_Print( *pTempDfr2, 0 );
|
|
|
|
// Re-run the transform so we can compare with the official result
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
DFR_GetNextN( pTempDfr1, pTempBuffer1, nSampleCount, op );
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
DFR_GetNextN( pTempDfr2, pTempBuffer2, nSampleCount, op );
|
|
DFR_Compare( *pTempDfr1, *pTempDfr2 );
|
|
}
|
|
|
|
// This will break the input buffer content, if this test is executed the sound will be off (esp. for reverberations and delays)
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
#if 0
|
|
// Use this method so it will compare sample by sample (help track more complex desyncs, but will slow the game down by a ton
|
|
DFR_GetNextN2( prva, pbuffer, pTempRva3, pTempBuffer3, nSampleCount, op );
|
|
#else
|
|
DFR_GetNextN( pDfr, pbuffer, nSampleCount, op );
|
|
#endif
|
|
DFR_Compare( *pDfr, *pTempDfr1 );
|
|
|
|
bFailed = ( memcmp( pTempBuffer1, pbuffer, nSampleCount * sizeof( portable_samplepair_t ) ) != 0 );
|
|
if ( bFailed )
|
|
{
|
|
Warning("[Sound] Detected desynchronization during DFR cloning.\n");
|
|
|
|
// Normally the content should be the same, only the addresses (tagged [Addr]) should be different.
|
|
// No address should be the same (if that were the case, it would mean that we missed something during the cloning).
|
|
|
|
DevMsg( "\n\nInitial DFR:\n\n" );
|
|
DFR_Print( *pDfr, 0 );
|
|
|
|
DevMsg( "\n\nCloned DFR:\n\n" );
|
|
DFR_Print( *pTempDfr1, 0 );
|
|
|
|
// Re-clone here to help detect the issue (before any modification)
|
|
portable_samplepair_t * pTempBuffer4 = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
dfr_t * pTempDfr4 = DFR_Clone(pDfr);
|
|
|
|
DevMsg( "\n\nNew clone DFR:\n\n" );
|
|
DFR_Print( *pTempDfr4, 0 );
|
|
|
|
FreeDuplicatedSamplePairs( pTempBuffer4, nSampleCount );
|
|
DFR_Free(pTempDfr4);
|
|
}
|
|
|
|
FreeDuplicatedSamplePairs( pTempBuffer1, nSampleCount );
|
|
DFR_Free(pTempDfr1);
|
|
FreeDuplicatedSamplePairs( pTempBuffer2, nSampleCount );
|
|
DFR_Free(pTempDfr2);
|
|
FreeDuplicatedSamplePairs( pTempBuffer3, nSampleCount );
|
|
DFR_Free(pTempDfr3);
|
|
}
|
|
#endif
|
|
|
|
inline void DFR_GetNextN_Opt( dfr_t *pdfr, portable_samplepair_t *pbuffer, int nSampleCount, int op )
|
|
{
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
// Duplicate the values before the original buffer is going to be modified in CheckCloneAccuracy()
|
|
portable_samplepair_t * pTempBuffer = DuplicateSamplePairs( pbuffer, nSampleCount );
|
|
dfr_t * pTempDfr = DFR_Clone( pdfr );
|
|
DFR_Compare( *pdfr, *pTempDfr );
|
|
|
|
CheckCloneAccuracy( pdfr, pbuffer, nSampleCount, op );
|
|
|
|
int count = nSampleCount;
|
|
portable_samplepair_t *pb = pTempBuffer;
|
|
|
|
LocalRandomSeed(); // Some of the filters are using Random, so we can see some divergence in some cases. Force the same seed.
|
|
|
|
switch ( op )
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
DFR_GetNext_Opt<CHANNEL_LEFT, 1 << CHANNEL_LEFT>( pTempDfr, pb, count );
|
|
break;
|
|
case OP_RIGHT:
|
|
DFR_GetNext_Opt<CHANNEL_RIGHT, 1 << CHANNEL_RIGHT>( pTempDfr, pb, count );
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
DFR_GetNext_Opt<CHANNEL_LEFT, (1 << CHANNEL_LEFT) | (1 << CHANNEL_RIGHT)>( pTempDfr, pb, count );
|
|
break;
|
|
}
|
|
|
|
DFR_Compare( *pdfr, *pTempDfr );
|
|
|
|
bool bFailed = ( memcmp( pTempBuffer, pbuffer, nSampleCount * sizeof( portable_samplepair_t ) ) != 0 );
|
|
Assert( bFailed == false );
|
|
|
|
FreeDuplicatedSamplePairs( pTempBuffer, nSampleCount );
|
|
DFR_Free( pTempDfr );
|
|
#else
|
|
if ( snd_dsp_optimization.GetBool() )
|
|
{
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
DFR_GetNext_Opt<CHANNEL_LEFT, 1 << CHANNEL_LEFT>( pdfr, pbuffer, nSampleCount );
|
|
break;
|
|
case OP_RIGHT:
|
|
DFR_GetNext_Opt<CHANNEL_RIGHT, 1 << CHANNEL_RIGHT>( pdfr, pbuffer, nSampleCount );
|
|
break;
|
|
case OP_LEFT_DUPLICATE:
|
|
DFR_GetNext_Opt<CHANNEL_LEFT, (1 << CHANNEL_LEFT) | (1 << CHANNEL_RIGHT)>( pdfr, pbuffer, nSampleCount );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DFR_GetNextN( pdfr, pbuffer, nSampleCount, op );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
#define DFR_BASEN 1 // base number of series allpass delays
|
|
|
|
// nominal diffusor delay and feedback values
|
|
|
|
float dfrdlys[] = {13, 19, 26, 21, 32, 36, 38, 16, 24, 28, 41, 35, 10, 46, 50, 27};
|
|
float dfrfbs[] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
|
|
|
|
|
|
// diffusor parameter order
|
|
|
|
typedef enum
|
|
{
|
|
|
|
// parameter order
|
|
|
|
dfr_isize,
|
|
dfr_inumdelays,
|
|
dfr_ifeedback,
|
|
dfr_igain,
|
|
|
|
dfr_cparam // # of params
|
|
|
|
} dfr_e;
|
|
|
|
// diffusor parameter ranges
|
|
|
|
prm_rng_t dfr_rng[] = {
|
|
|
|
{dfr_cparam, 0, 0}, // first entry is # of parameters
|
|
|
|
{dfr_isize, 0.0, 1.0}, // 0-1.0 scales all delays
|
|
{dfr_inumdelays,0.0, 4.0}, // 0-4.0 controls # of series delays
|
|
{dfr_ifeedback, 0.0, 1.0}, // 0-1.0 scales all feedback parameters
|
|
{dfr_igain, 0.0, 10.0}, // 0-1.0 scales all feedback parameters
|
|
};
|
|
|
|
|
|
dfr_t * DFR_Params ( prc_t *pprc )
|
|
{
|
|
dfr_t *pdfr;
|
|
int i;
|
|
int s;
|
|
float size = pprc->prm[dfr_isize]; // 0-1.0 scales all delays
|
|
float numdelays = pprc->prm[dfr_inumdelays]; // 0-4.0 controls # of series delays
|
|
float feedback = pprc->prm[dfr_ifeedback]; // 0-1.0 scales all feedback parameters
|
|
float gain = pprc->prm[dfr_igain]; // 0-10.0 controls output gain
|
|
|
|
// D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
|
|
// a array of reverb feedback parms for series delays (CRVB_S_DLYS)
|
|
// b gain of each reverb section
|
|
// n - number of series delays
|
|
|
|
int D[CDFR_DLYS];
|
|
int a[CDFR_DLYS];
|
|
int b[CDFR_DLYS];
|
|
int n;
|
|
|
|
if (gain == 0.0)
|
|
gain = 1.0;
|
|
|
|
// get # series diffusors
|
|
|
|
// limit m, n to half max number of delays
|
|
|
|
n = iclamp (numdelays, DFR_BASEN, CDFR_DLYS/2);
|
|
|
|
// compute delays for diffusors
|
|
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
s = (int)( dfrdlys[i] * size );
|
|
|
|
// delay of diffusor
|
|
|
|
D[i] = MSEC_TO_SAMPS(s);
|
|
|
|
// feedback and gain of diffusor
|
|
|
|
a[i] = MIN (0.999 * PMAX, dfrfbs[i] * PMAX * feedback);
|
|
b[i] = (int) ( (float)(gain * (float)PMAX) );
|
|
}
|
|
|
|
|
|
pdfr = DFR_Alloc ( D, a, b, n );
|
|
|
|
return pdfr;
|
|
}
|
|
|
|
inline void * DFR_VParams ( void *p )
|
|
{
|
|
PRC_CheckParams ((prc_t *)p, dfr_rng);
|
|
return (void *) DFR_Params ((prc_t *)p);
|
|
}
|
|
|
|
inline void DFR_Mod ( void *p, float v ) { return; }
|
|
|
|
|
|
//////////////////////
|
|
// LFO wav definitions
|
|
//////////////////////
|
|
|
|
#define CLFOSAMPS 512 // samples per wav table - single cycle only
|
|
#define LFOBITS 14 // bits of peak amplitude of lfo wav
|
|
#define LFOAMP ((1<<LFOBITS)-1) // peak amplitude of lfo wav
|
|
|
|
//types of lfo wavs
|
|
|
|
#define LFO_SIN 0 // sine wav
|
|
#define LFO_TRI 1 // triangle wav
|
|
#define LFO_SQR 2 // square wave, 50% duty cycle
|
|
#define LFO_SAW 3 // forward saw wav
|
|
#define LFO_RND 4 // random wav
|
|
#define LFO_LOG_IN 5 // logarithmic fade in
|
|
#define LFO_LOG_OUT 6 // logarithmic fade out
|
|
#define LFO_LIN_IN 7 // linear fade in
|
|
#define LFO_LIN_OUT 8 // linear fade out
|
|
#define LFO_MAX LFO_LIN_OUT
|
|
|
|
#define CLFOWAV 9 // number of LFO wav tables
|
|
|
|
struct lfowav_t // lfo or envelope wave table
|
|
{
|
|
int type; // lfo type
|
|
dly_t *pdly; // delay holds wav values and step pointers
|
|
};
|
|
|
|
lfowav_t lfowavs[CLFOWAV];
|
|
|
|
// deallocate lfo wave table. Called only when sound engine exits.
|
|
|
|
void LFOWAV_Free( lfowav_t *plw )
|
|
{
|
|
// free delay
|
|
|
|
if ( plw )
|
|
DLY_Free( plw->pdly );
|
|
|
|
Q_memset( plw, 0, sizeof (lfowav_t) );
|
|
}
|
|
|
|
// deallocate all lfo wave tables. Called only when sound engine exits.
|
|
|
|
void LFOWAV_FreeAll( void )
|
|
{
|
|
for ( int i = 0; i < CLFOWAV; i++ )
|
|
LFOWAV_Free( &lfowavs[i] );
|
|
}
|
|
|
|
// fill lfo array w with count samples of lfo type 'type'
|
|
// all lfo wavs except fade out, rnd, and log_out should start with 0 output
|
|
|
|
void LFOWAV_Fill( CircularBufferSample_t *w, int count, int type )
|
|
{
|
|
int i,x;
|
|
switch (type)
|
|
{
|
|
default:
|
|
case LFO_SIN: // sine wav, all values 0 <= x <= LFOAMP, initial value = 0
|
|
for (i = 0; i < count; i++ )
|
|
{
|
|
x = ( int )( (float)(LFOAMP) * sinf( (2.0 * M_PI_F * (float)i / (float)count ) + (M_PI_F * 1.5) ) );
|
|
w[i] = (x + LFOAMP)/2;
|
|
}
|
|
break;
|
|
case LFO_TRI: // triangle wav, all values 0 <= x <= LFOAMP, initial value = 0
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
w[i] = ( int ) ( (float)(2 * LFOAMP * i ) / (float)(count) );
|
|
|
|
if ( i > count / 2 )
|
|
w[i] = ( int ) ( (float) (2 * LFOAMP) - (float)( 2 * LFOAMP * i ) / (float)(count) );
|
|
}
|
|
break;
|
|
case LFO_SQR: // square wave, 50% duty cycle, all values 0 <= x <= LFOAMP, initial value = 0
|
|
for (i = 0; i < count; i++)
|
|
w[i] = i > count / 2 ? 0 : LFOAMP;
|
|
break;
|
|
case LFO_SAW: // forward saw wav, aall values 0 <= x <= LFOAMP, initial value = 0
|
|
for (i = 0; i < count; i++)
|
|
w[i] = ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) );
|
|
break;
|
|
case LFO_RND: // random wav, all values 0 <= x <= LFOAMP
|
|
for (i = 0; i < count; i++)
|
|
w[i] = ( int ) ( LocalRandomInt(0, LFOAMP) );
|
|
break;
|
|
case LFO_LOG_IN: // logarithmic fade in, all values 0 <= x <= LFOAMP, initial value = 0
|
|
for (i = 0; i < count; i++)
|
|
w[i] = ( int ) ( (float)(LFOAMP) * powf( (float)i / (float)count, 2));
|
|
break;
|
|
case LFO_LOG_OUT: // logarithmic fade out, all values 0 <= x <= LFOAMP, initial value = LFOAMP
|
|
for (i = 0; i < count; i++)
|
|
w[i] = ( int ) ( (float)(LFOAMP) * powf( 1.0 - ((float)i / (float)count), 2 ));
|
|
break;
|
|
case LFO_LIN_IN: // linear fade in, all values 0 <= x <= LFOAMP, initial value = 0
|
|
for (i = 0; i < count; i++)
|
|
w[i] = ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) );
|
|
break;
|
|
case LFO_LIN_OUT: // linear fade out, all values 0 <= x <= LFOAMP, initial value = LFOAMP
|
|
for (i = 0; i < count; i++)
|
|
w[i] = LFOAMP - ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// allocate all lfo wave tables. Called only when sound engine loads.
|
|
|
|
void LFOWAV_InitAll()
|
|
{
|
|
int i;
|
|
dly_t *pdly;
|
|
|
|
Q_memset( lfowavs, 0, sizeof( lfowavs ) );
|
|
|
|
// alloc space for each lfo wav type
|
|
|
|
for (i = 0; i < CLFOWAV; i++)
|
|
{
|
|
pdly = DLY_Alloc( CLFOSAMPS, 0, 0 , DLY_PLAIN);
|
|
|
|
lfowavs[i].pdly = pdly;
|
|
lfowavs[i].type = i;
|
|
|
|
LFOWAV_Fill( pdly->w, CLFOSAMPS, i );
|
|
}
|
|
|
|
// if any dlys fail to alloc, free all
|
|
|
|
for (i = 0; i < CLFOWAV; i++)
|
|
{
|
|
if ( !lfowavs[i].pdly )
|
|
LFOWAV_FreeAll();
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////
|
|
// LFO iterators - one shot and looping
|
|
////////////////////////////////////////
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CLFO 32
|
|
#else
|
|
#define CLFO 16 // max active lfos (this steals from active delays)
|
|
#endif
|
|
|
|
struct lfo_t
|
|
{
|
|
bool fused; // true if slot take
|
|
|
|
dly_t *pdly; // delay points to lfo wav within lfowav_t (don't free this)
|
|
|
|
int gain;
|
|
|
|
float f; // playback frequency in hz
|
|
|
|
pos_t pos; // current position within wav table, looping
|
|
pos_one_t pos1; // current position within wav table, one shot
|
|
|
|
int foneshot; // true - one shot only, don't repeat
|
|
};
|
|
|
|
lfo_t lfos[CLFO];
|
|
|
|
void LFO_Init( lfo_t *plfo ) { if ( plfo ) Q_memset( plfo, 0, sizeof (lfo_t) ); }
|
|
void LFO_InitAll( void ) { for (int i = 0; i < CLFO; i++) LFO_Init(&lfos[i]); }
|
|
void LFO_Free( lfo_t *plfo ) { if ( plfo ) Q_memset( plfo, 0, sizeof (lfo_t) ); }
|
|
void LFO_FreeAll( void ) { for (int i = 0; i < CLFO; i++) LFO_Free(&lfos[i]); }
|
|
|
|
|
|
// get step value given desired playback frequency
|
|
|
|
inline float LFO_HzToStep ( float freqHz )
|
|
{
|
|
float lfoHz;
|
|
|
|
// calculate integer and fractional step values,
|
|
// assume an update rate of SOUND_DMA_SPEED samples/sec
|
|
|
|
// 1 cycle/CLFOSAMPS * SOUND_DMA_SPEED samps/sec = cycles/sec = current lfo rate
|
|
//
|
|
// lforate * X = freqHz so X = freqHz/lforate = update rate
|
|
|
|
lfoHz = (float)(SOUND_DMA_SPEED) / (float)(CLFOSAMPS);
|
|
|
|
return freqHz / lfoHz;
|
|
}
|
|
|
|
// return pointer to new lfo
|
|
|
|
lfo_t * LFO_Alloc( int wtype, float freqHz, bool foneshot, float gain )
|
|
{
|
|
int i;
|
|
int type = MIN ( CLFOWAV - 1, wtype );
|
|
float lfostep;
|
|
|
|
for (i = 0; i < CLFO; i++)
|
|
if (!lfos[i].fused)
|
|
{
|
|
lfo_t *plfo = &lfos[i];
|
|
|
|
LFO_Init( plfo );
|
|
|
|
plfo->fused = true;
|
|
plfo->pdly = lfowavs[type].pdly; // pdly in lfo points to wav table data in lfowavs
|
|
plfo->f = freqHz;
|
|
plfo->foneshot = foneshot;
|
|
plfo->gain = gain * PMAX;
|
|
|
|
lfostep = LFO_HzToStep( freqHz );
|
|
|
|
// init positional pointer (ie: fixed point updater for controlling pitch of lfo)
|
|
|
|
if ( !foneshot )
|
|
POS_Init(&(plfo->pos), plfo->pdly->D, lfostep );
|
|
else
|
|
POS_ONE_Init(&(plfo->pos1), plfo->pdly->D,lfostep );
|
|
|
|
return plfo;
|
|
}
|
|
DevMsg ("DSP: Warning, failed to allocate LFO.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
void LFO_Print( const lfo_t & crs, int nIndentation )
|
|
{
|
|
DevMsg( "LFO_Print is not implemented\n" );
|
|
}
|
|
|
|
// get next lfo value
|
|
// Value returned is 0..LFOAMP. can be normalized by shifting right by LFOBITS
|
|
// To play back at correct passed in frequency, routien should be
|
|
// called once for every output sample (ie: at SOUND_DMA_SPEED)
|
|
// x is dummy param
|
|
|
|
inline int LFO_GetNext( lfo_t *plfo, int x )
|
|
{
|
|
int i;
|
|
|
|
// get current position
|
|
|
|
if ( !plfo->foneshot )
|
|
i = POS_GetNext( &plfo->pos );
|
|
else
|
|
i = POS_ONE_GetNext( &plfo->pos1 );
|
|
|
|
// return current sample
|
|
|
|
if (plfo->gain == PMAX)
|
|
return plfo->pdly->w[i];
|
|
else
|
|
return (plfo->pdly->w[i] * plfo->gain ) >> PBITS;
|
|
}
|
|
|
|
// batch version for performance
|
|
|
|
inline void LFO_GetNextN( lfo_t *plfo, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb = pbuffer;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb->left = LFO_GetNext( plfo, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb->right = LFO_GetNext( plfo, pb->right );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb->left = pb->right = LFO_GetNext( plfo, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// uses lfowav, rate, foneshot
|
|
|
|
typedef enum
|
|
{
|
|
|
|
// parameter order
|
|
|
|
lfo_iwav,
|
|
lfo_irate,
|
|
lfo_ifoneshot,
|
|
lfo_igain,
|
|
|
|
lfo_cparam // # of params
|
|
|
|
} lfo_e;
|
|
|
|
// parameter ranges
|
|
|
|
prm_rng_t lfo_rng[] = {
|
|
|
|
{lfo_cparam, 0, 0}, // first entry is # of parameters
|
|
|
|
{lfo_iwav, 0.0, LFO_MAX}, // lfo type to use (LFO_SIN, LFO_RND...)
|
|
{lfo_irate, 0.0, 16000.0}, // modulation rate in hz. for MDY, 1/rate = 'glide' time in seconds
|
|
{lfo_ifoneshot, 0.0, 1.0}, // 1.0 if lfo is oneshot
|
|
{lfo_igain, 0.0, 10.0}, // output gain
|
|
};
|
|
|
|
|
|
lfo_t * LFO_Params ( prc_t *pprc )
|
|
{
|
|
lfo_t *plfo;
|
|
bool foneshot = pprc->prm[lfo_ifoneshot] > 0 ? true : false;
|
|
float gain = pprc->prm[lfo_igain];
|
|
|
|
plfo = LFO_Alloc ( pprc->prm[lfo_iwav], pprc->prm[lfo_irate], foneshot, gain );
|
|
|
|
return plfo;
|
|
}
|
|
|
|
void LFO_ChangeVal ( lfo_t *plfo, float fhz )
|
|
{
|
|
float fstep = LFO_HzToStep( fhz );
|
|
|
|
// change lfo playback rate to new frequency fhz
|
|
|
|
if ( plfo->foneshot )
|
|
POS_ChangeVal( &plfo->pos, fstep );
|
|
else
|
|
POS_ChangeVal( &plfo->pos1.p, fstep );
|
|
}
|
|
|
|
inline void * LFO_VParams ( void *p )
|
|
{
|
|
PRC_CheckParams ( (prc_t *)p, lfo_rng );
|
|
return (void *) LFO_Params ((prc_t *)p);
|
|
}
|
|
|
|
// v is +/- 0-1.0
|
|
// v changes current lfo frequency up/down by +/- v%
|
|
|
|
inline void LFO_Mod ( lfo_t *plfo, float v )
|
|
{
|
|
float fhz;
|
|
float fhznew;
|
|
|
|
fhz = plfo->f;
|
|
fhznew = fhz * (1.0 + v);
|
|
|
|
LFO_ChangeVal ( plfo, fhznew );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////
|
|
// Time Compress/expand with pitch shift
|
|
////////////////////////////////////////
|
|
|
|
// realtime pitch shift - ie: pitch shift without change to playback rate
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CPTCS 128
|
|
#else
|
|
#define CPTCS 64
|
|
#endif
|
|
|
|
struct ptc_t
|
|
{
|
|
bool fused;
|
|
|
|
dly_t *pdly_in; // input buffer space
|
|
dly_t *pdly_out; // output buffer space
|
|
|
|
CircularBufferSample_t *pin; // input buffer (pdly_in->w)
|
|
CircularBufferSample_t *pout; // output buffer (pdly_out->w)
|
|
|
|
int cin; // # samples in input buffer
|
|
int cout; // # samples in output buffer
|
|
|
|
int cxfade; // # samples in crossfade segment
|
|
int ccut; // # samples to cut
|
|
int cduplicate; // # samples to duplicate (redundant - same as ccut)
|
|
|
|
int iin; // current index into input buffer (reading)
|
|
|
|
pos_one_t psn; // stepping index through output buffer
|
|
|
|
bool fdup; // true if duplicating, false if cutting
|
|
|
|
float fstep; // pitch shift & time compress/expand
|
|
};
|
|
|
|
ptc_t ptcs[CPTCS];
|
|
|
|
void PTC_Init( ptc_t *pptc ) { if (pptc) Q_memset( pptc, 0, sizeof (ptc_t) ); };
|
|
void PTC_Free( ptc_t *pptc )
|
|
{
|
|
if (pptc)
|
|
{
|
|
DLY_Free (pptc->pdly_in);
|
|
DLY_Free (pptc->pdly_out);
|
|
|
|
Q_memset( pptc, 0, sizeof (ptc_t) );
|
|
}
|
|
};
|
|
void PTC_InitAll() { for (int i = 0; i < CPTCS; i++) PTC_Init( &ptcs[i] ); };
|
|
void PTC_FreeAll() { for (int i = 0; i < CPTCS; i++) PTC_Free( &ptcs[i] ); };
|
|
|
|
|
|
|
|
// Time compressor/expander with pitch shift (ie: pitch changes, playback rate does not)
|
|
//
|
|
// Algorithm:
|
|
|
|
// 1) Duplicate or discard chunks of sound to provide tslice * fstep seconds of sound.
|
|
// (The user-selectable size of the buffer to process is tslice milliseconds in length)
|
|
// 2) Resample this compressed/expanded buffer at fstep to produce a pitch shifted
|
|
// output with the same duration as the input (ie: #samples out = # samples in, an
|
|
// obvious requirement for realtime inline processing).
|
|
|
|
// timeslice is size in milliseconds of full buffer to process.
|
|
// timeslice * fstep is the size of the expanded/compressed buffer
|
|
// timexfade is length in milliseconds of crossfade region between duplicated or cut sections
|
|
// fstep is % expanded/compressed sound normalized to 0.01-2.0 (1% - 200%)
|
|
|
|
// input buffer:
|
|
|
|
// iin-->
|
|
|
|
// [0... tslice ...D] input samples 0...D (D is NEWEST sample)
|
|
// [0... ...n][m... tseg ...D] region to be cut or duplicated m...D
|
|
|
|
// [0... [p..txf1..n][m... tseg ...D] fade in region 1 txf1 p...n
|
|
// [0... ...n][m..[q..txf2..D] fade out region 2 txf2 q...D
|
|
|
|
|
|
// pitch up: duplicate into output buffer: tdup = tseg
|
|
|
|
// [0... ...n][m... tdup ...D][m... tdup ...D] output buffer size with duplicate region
|
|
// [0... ...n][m..[p...xf1..n][m... tdup ...D] fade in p...n while fading out q...D
|
|
// [0... ...n][m..[q...xf2..D][m... tdup ...D]
|
|
// [0... ...n][m..[.XFADE...n][m... tdup ...D] final duplicated output buffer - resample at fstep
|
|
|
|
// pitch down: cut into output buffer: tcut = tseg
|
|
|
|
// [0... ...n][m... tcut ...D] input samples with cut region delineated m...D
|
|
// [0... ...n] output buffer size after cut
|
|
// [0... [q..txf2...D] fade in txf1 q...D while fade out txf2 p...n
|
|
// [0... [.XFADE ...D] final cut output buffer - resample at fstep
|
|
|
|
|
|
ptc_t * PTC_Alloc( float timeslice, float timexfade, float fstep )
|
|
{
|
|
|
|
int i;
|
|
ptc_t *pptc;
|
|
float tout;
|
|
int cin, cout;
|
|
float tslice = timeslice;
|
|
float txfade = timexfade;
|
|
float tcutdup;
|
|
|
|
// find time compressor slot
|
|
|
|
for ( i = 0; i < CPTCS; i++ )
|
|
{
|
|
if ( !ptcs[i].fused )
|
|
break;
|
|
}
|
|
|
|
if ( i == CPTCS )
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate pitch shifter.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
pptc = &ptcs[i];
|
|
|
|
PTC_Init ( pptc );
|
|
|
|
// get size of region to cut or duplicate
|
|
|
|
tcutdup = abs((fstep - 1.0) * timeslice);
|
|
|
|
// to prevent buffer overruns:
|
|
|
|
// make sure timeslice is greater than cut/dup time
|
|
|
|
tslice = MAX ( 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 );
|
|
DevMsg ("DSP: Warning, failed to allocate delay for pitch shifter.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
// buffer pointers
|
|
|
|
pptc->pin = pptc->pdly_in->w;
|
|
pptc->pout = pptc->pdly_out->w;
|
|
|
|
// input buffer index
|
|
|
|
pptc->iin = 0;
|
|
|
|
// output buffer index
|
|
|
|
POS_ONE_Init ( &pptc->psn, cout, fstep );
|
|
|
|
// if fstep > 1.0 we're pitching shifting up, so fdup = true
|
|
|
|
pptc->fdup = fstep > 1.0 ? true : false;
|
|
|
|
pptc->cin = cin;
|
|
pptc->cout = cout;
|
|
|
|
pptc->fstep = fstep;
|
|
pptc->fused = true;
|
|
|
|
return pptc;
|
|
}
|
|
|
|
void PTC_Print( const ptc_t & crs, int nIndentation )
|
|
{
|
|
DevMsg( "PTC_Print is not implemented\n" );
|
|
}
|
|
|
|
// linear crossfader
|
|
// yfadein - instantaneous value fading in
|
|
// ydafeout -instantaneous value fading out
|
|
// nsamples - duration in #samples of fade
|
|
// isample - index in to fade 0...nsamples-1
|
|
|
|
inline int xfade ( int yfadein, int yfadeout, int nsamples, int isample )
|
|
{
|
|
int yout;
|
|
int m = (isample << PBITS ) / nsamples;
|
|
|
|
// yout = ((yfadein * m) >> PBITS) + ((yfadeout * (PMAX - m)) >> PBITS);
|
|
yout = (yfadeout + (yfadein - yfadeout) * m ) >> PBITS;
|
|
|
|
return yout;
|
|
}
|
|
|
|
// w - pointer to start of input buffer samples
|
|
// v - pointer to start of output buffer samples
|
|
// cin - # of input buffer samples
|
|
// cout = # of output buffer samples
|
|
// cxfade = # of crossfade samples
|
|
// cduplicate = # of samples in duplicate/cut segment
|
|
|
|
void TimeExpand( CircularBufferSample_t *w, CircularBufferSample_t *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( CircularBufferSample_t *w, CircularBufferSample_t *v, int cin, int cout, int cxfade, int ccut )
|
|
{
|
|
int i,j;
|
|
int m;
|
|
int p;
|
|
int q;
|
|
int D;
|
|
|
|
// input buffer
|
|
// xfade source
|
|
// [0.....................n][m..[p.......D]
|
|
|
|
// xfade region cut
|
|
// [0...........][q.......n][m...........D]
|
|
|
|
// output buffer
|
|
// xfade to source
|
|
// [0...........][p.......D]
|
|
|
|
// D - index of last sample in input buffer
|
|
// m - index of 1st sample in cut region
|
|
// p - index of 1st sample of crossfade source
|
|
// q - index of 1st sample in crossfade region
|
|
|
|
D = cin - 1;
|
|
m = cin - ccut;
|
|
p = cin - cxfade;
|
|
q = m - cxfade;
|
|
|
|
// copy up to crossfade region
|
|
|
|
for (i = 0; i < q; i++)
|
|
v[i] = w[i];
|
|
|
|
// crossfade region
|
|
|
|
j = p;
|
|
|
|
for (i = q; i < m; i++)
|
|
v[i] = xfade (w[j++], w[i], cxfade, i-q); // fade out p..n, fade in q..D
|
|
|
|
// skip rest of input buffer
|
|
}
|
|
|
|
// get next sample
|
|
|
|
// put input sample into input (delay) buffer
|
|
// get output sample from output buffer, step by fstep %
|
|
// output buffer is time expanded or compressed version of previous input buffer
|
|
|
|
inline int PTC_GetNext( ptc_t *pptc, int x )
|
|
{
|
|
int iout, xout;
|
|
bool fhitend = false;
|
|
|
|
// write x into input buffer
|
|
Assert (pptc->iin < pptc->cin);
|
|
|
|
pptc->pin[pptc->iin] = x;
|
|
|
|
pptc->iin++;
|
|
|
|
// check for end of input buffer
|
|
|
|
if ( pptc->iin >= pptc->cin )
|
|
fhitend = true;
|
|
|
|
// read sample from output buffer, resampling at fstep
|
|
|
|
iout = POS_ONE_GetNext( &pptc->psn );
|
|
Assert (iout < pptc->cout);
|
|
xout = pptc->pout[iout];
|
|
|
|
if ( fhitend )
|
|
{
|
|
// if hit end of input buffer (ie: input buffer is full)
|
|
// reset input buffer pointer
|
|
// reset output buffer pointer
|
|
// rebuild entire output buffer (TimeCompress/TimeExpand)
|
|
|
|
pptc->iin = 0;
|
|
|
|
POS_ONE_Init( &pptc->psn, pptc->cout, pptc->fstep );
|
|
|
|
if ( pptc->fdup )
|
|
TimeExpand ( pptc->pin, pptc->pout, pptc->cin, pptc->cout, pptc->cxfade, pptc->cduplicate );
|
|
else
|
|
TimeCompress ( pptc->pin, pptc->pout, pptc->cin, pptc->cout, pptc->cxfade, pptc->ccut );
|
|
}
|
|
|
|
return xout;
|
|
}
|
|
|
|
// batch version for performance
|
|
|
|
inline void PTC_GetNextN( ptc_t *pptc, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb = pbuffer;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb->left = PTC_GetNext( pptc, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb->right = PTC_GetNext( pptc, pb->right );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb->left = pb->right = PTC_GetNext( pptc, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// change time compression to new value
|
|
// fstep is new value
|
|
// ramptime is how long change takes in seconds (ramps smoothly), 0 for no ramp
|
|
|
|
void PTC_ChangeVal( ptc_t *pptc, float fstep, float ramptime )
|
|
{
|
|
// UNDONE: ignored
|
|
// UNDONE: just realloc time compressor with new fstep
|
|
}
|
|
|
|
// uses pitch:
|
|
// 1.0 = playback normal rate
|
|
// 0.5 = cut 50% of sound (2x playback)
|
|
// 1.5 = add 50% sound (0.5x playback)
|
|
|
|
typedef enum
|
|
{
|
|
|
|
// parameter order
|
|
|
|
ptc_ipitch,
|
|
ptc_itimeslice,
|
|
ptc_ixfade,
|
|
|
|
ptc_cparam // # of params
|
|
|
|
} ptc_e;
|
|
|
|
// diffusor parameter ranges
|
|
|
|
prm_rng_t ptc_rng[] = {
|
|
|
|
{ptc_cparam, 0, 0}, // first entry is # of parameters
|
|
|
|
{ptc_ipitch, 0.1, 4.0}, // 0-n.0 where 1.0 = 1 octave up and 0.5 is one octave down
|
|
{ptc_itimeslice, 20.0, 300.0}, // in milliseconds - size of sound chunk to analyze and cut/duplicate - 100ms nominal
|
|
{ptc_ixfade, 1.0, 200.0}, // in milliseconds - size of crossfade region between spliced chunks - 20ms nominal
|
|
};
|
|
|
|
|
|
ptc_t * PTC_Params ( prc_t *pprc )
|
|
{
|
|
ptc_t *pptc;
|
|
|
|
float pitch = pprc->prm[ptc_ipitch];
|
|
float timeslice = pprc->prm[ptc_itimeslice];
|
|
float txfade = pprc->prm[ptc_ixfade];
|
|
|
|
pptc = PTC_Alloc( timeslice, txfade, pitch );
|
|
|
|
return pptc;
|
|
}
|
|
|
|
inline void * PTC_VParams ( void *p )
|
|
{
|
|
PRC_CheckParams ( (prc_t *)p, ptc_rng );
|
|
return (void *) PTC_Params ((prc_t *)p);
|
|
}
|
|
|
|
// change to new pitch value
|
|
// v is +/- 0-1.0
|
|
// v changes current pitch up/down by +/- v%
|
|
|
|
void PTC_Mod ( ptc_t *pptc, float v )
|
|
{
|
|
float fstep;
|
|
float fstepnew;
|
|
|
|
fstep = pptc->fstep;
|
|
fstepnew = fstep * (1.0 + v);
|
|
|
|
PTC_ChangeVal( pptc, fstepnew, 0.01 );
|
|
}
|
|
|
|
|
|
////////////////////
|
|
// ADSR envelope
|
|
////////////////////
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CENVS 128
|
|
#else
|
|
#define CENVS 64 // max # of envelopes active
|
|
#endif
|
|
|
|
#define CENVRMPS 4 // A, D, S, R
|
|
|
|
#define ENV_LIN 0 // linear a,d,s,r
|
|
#define ENV_EXP 1 // exponential a,d,s,r
|
|
#define ENV_MAX ENV_EXP
|
|
|
|
#define ENV_BITS 14 // bits of resolution of ramp
|
|
|
|
struct env_t
|
|
{
|
|
bool fused;
|
|
|
|
bool fhitend; // true if done
|
|
bool fexp; // true if exponential ramps
|
|
|
|
int ienv; // current ramp
|
|
rmp_t rmps[CENVRMPS]; // ramps
|
|
};
|
|
|
|
env_t envs[CENVS];
|
|
|
|
void ENV_Init( env_t *penv ) { if (penv) Q_memset( penv, 0, sizeof (env_t) ); };
|
|
void ENV_Free( env_t *penv ) { if (penv) Q_memset( penv, 0, sizeof (env_t) ); };
|
|
void ENV_InitAll() { for (int i = 0; i < CENVS; i++) ENV_Init( &envs[i] ); };
|
|
void ENV_FreeAll() { for (int i = 0; i < CENVS; i++) ENV_Free( &envs[i] ); };
|
|
|
|
|
|
// allocate ADSR envelope
|
|
// all times are in seconds
|
|
// amp1 - attack amplitude multiplier 0-1.0
|
|
// amp2 - sustain amplitude multiplier 0-1.0
|
|
// amp3 - end of sustain amplitude multiplier 0-1.0
|
|
|
|
env_t *ENV_Alloc ( int type, float famp1, float famp2, float famp3, float attack, float decay, float sustain, float release, bool fexp)
|
|
{
|
|
int i;
|
|
env_t *penv;
|
|
|
|
for (i = 0; i < CENVS; i++)
|
|
{
|
|
if ( !envs[i].fused )
|
|
{
|
|
|
|
int amp1 = famp1 * (1 << ENV_BITS); // ramp resolution
|
|
int amp2 = famp2 * (1 << ENV_BITS);
|
|
int amp3 = famp3 * (1 << ENV_BITS);
|
|
|
|
penv = &envs[i];
|
|
|
|
ENV_Init (penv);
|
|
|
|
// UNDONE: ignoring type = ENV_EXP - use oneshot LFOS instead with sawtooth/exponential
|
|
|
|
// set up ramps
|
|
|
|
RMP_Init( &penv->rmps[0], attack, 0, amp1, true );
|
|
RMP_Init( &penv->rmps[1], decay, amp1, amp2, true );
|
|
RMP_Init( &penv->rmps[2], sustain, amp2, amp3, true );
|
|
RMP_Init( &penv->rmps[3], release, amp3, 0, true );
|
|
|
|
penv->ienv = 0;
|
|
penv->fused = true;
|
|
penv->fhitend = false;
|
|
penv->fexp = fexp;
|
|
return penv;
|
|
}
|
|
}
|
|
DevMsg ("DSP: Warning, failed to allocate envelope.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
void ENV_Print( const env_t & env, int nIndentation )
|
|
{
|
|
DevMsg( "ENV_Print is not implemented\n" );
|
|
}
|
|
|
|
inline int ENV_GetNext( env_t *penv, int x )
|
|
{
|
|
if ( !penv->fhitend )
|
|
{
|
|
int i;
|
|
int y;
|
|
|
|
i = penv->ienv;
|
|
y = RMP_GetNext ( &penv->rmps[i] );
|
|
|
|
// check for next ramp
|
|
|
|
if ( penv->rmps[i].fhitend )
|
|
i++;
|
|
|
|
penv->ienv = i;
|
|
|
|
// check for end of all ramps
|
|
|
|
if ( i > 3)
|
|
penv->fhitend = true;
|
|
|
|
// multiply input signal by ramp
|
|
|
|
if (penv->fexp)
|
|
return (((x * y) >> ENV_BITS) * y) >> ENV_BITS;
|
|
else
|
|
return (x * y) >> ENV_BITS;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// batch version for performance
|
|
|
|
inline void ENV_GetNextN( env_t *penv, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb = pbuffer;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb->left = ENV_GetNext( penv, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb->right = ENV_GetNext( penv, pb->right );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb->left = pb->right = ENV_GetNext( penv, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// uses lfowav, amp1, amp2, amp3, attack, decay, sustain, release
|
|
// lfowav is type, currently ignored - ie: LFO_LIN_IN, LFO_LOG_IN
|
|
|
|
// parameter order
|
|
|
|
typedef enum
|
|
{
|
|
env_itype,
|
|
env_iamp1,
|
|
env_iamp2,
|
|
env_iamp3,
|
|
env_iattack,
|
|
env_idecay,
|
|
env_isustain,
|
|
env_irelease,
|
|
env_ifexp,
|
|
env_cparam // # of params
|
|
|
|
} env_e;
|
|
|
|
// parameter ranges
|
|
|
|
prm_rng_t env_rng[] = {
|
|
|
|
{env_cparam, 0, 0}, // first entry is # of parameters
|
|
|
|
{env_itype, 0.0,ENV_MAX}, // ENV_LINEAR, ENV_LOG - currently ignored
|
|
{env_iamp1, 0.0, 1.0}, // attack peak amplitude 0-1.0
|
|
{env_iamp2, 0.0, 1.0}, // decay target amplitued 0-1.0
|
|
{env_iamp3, 0.0, 1.0}, // sustain target amplitude 0-1.0
|
|
{env_iattack, 0.0, 20000.0}, // attack time in milliseconds
|
|
{env_idecay, 0.0, 20000.0}, // envelope decay time in milliseconds
|
|
{env_isustain, 0.0, 20000.0}, // sustain time in milliseconds
|
|
{env_irelease, 0.0, 20000.0}, // release time in milliseconds
|
|
{env_ifexp, 0.0, 1.0}, // 1.0 if exponential ramps
|
|
};
|
|
|
|
env_t * ENV_Params ( prc_t *pprc )
|
|
{
|
|
env_t *penv;
|
|
|
|
float type = pprc->prm[env_itype];
|
|
float amp1 = pprc->prm[env_iamp1];
|
|
float amp2 = pprc->prm[env_iamp2];
|
|
float amp3 = pprc->prm[env_iamp3];
|
|
float attack = pprc->prm[env_iattack]/1000.0;
|
|
float decay = pprc->prm[env_idecay]/1000.0;
|
|
float sustain = pprc->prm[env_isustain]/1000.0;
|
|
float release = pprc->prm[env_irelease]/1000.0;
|
|
float fexp = pprc->prm[env_ifexp];
|
|
bool bexp;
|
|
|
|
bexp = fexp > 0.0 ? 1 : 0;
|
|
penv = ENV_Alloc ( type, amp1, amp2, amp3, attack, decay, sustain, release, bexp );
|
|
return penv;
|
|
}
|
|
|
|
inline void * ENV_VParams ( void *p )
|
|
{
|
|
PRC_CheckParams( (prc_t *)p, env_rng );
|
|
return (void *) ENV_Params ((prc_t *)p);
|
|
}
|
|
|
|
inline void ENV_Mod ( void *p, float v ) { return; }
|
|
|
|
//////////////////////////
|
|
// Gate & envelope follower
|
|
//////////////////////////
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CEFOS 128
|
|
#else
|
|
#define CEFOS 64 // max # of envelope followers active
|
|
#endif
|
|
|
|
struct efo_t
|
|
{
|
|
bool fused;
|
|
|
|
int xout; // current output value
|
|
|
|
// gate params
|
|
|
|
bool bgate; // if true, gate function is on
|
|
|
|
bool bgateon; // if true, gate is on
|
|
bool bexp; // if true, use exponential fade out
|
|
|
|
int thresh; // amplitude threshold for gate on
|
|
int thresh_off; // amplitidue threshold for gate off
|
|
|
|
float attack_time; // gate attack time in seconds
|
|
float decay_time; // gate decay time in seconds
|
|
|
|
rmp_t rmp_attack; // gate on ramp - attack
|
|
rmp_t rmp_decay; // gate off ramp - decay
|
|
};
|
|
|
|
efo_t efos[CEFOS];
|
|
|
|
void EFO_Init( efo_t *pefo ) { if (pefo) Q_memset( pefo, 0, sizeof (efo_t) ); };
|
|
void EFO_Free( efo_t *pefo ) { if (pefo) Q_memset( pefo, 0, sizeof (efo_t) ); };
|
|
void EFO_InitAll() { for (int i = 0; i < CEFOS; i++) EFO_Init( &efos[i] ); };
|
|
void EFO_FreeAll() { for (int i = 0; i < CEFOS; i++) EFO_Free( &efos[i] ); };
|
|
|
|
// return true when gate is off AND decay ramp has hit end
|
|
|
|
inline bool EFO_GateOff( efo_t *pefo )
|
|
{
|
|
return ( !pefo->bgateon && RMP_HitEnd( &pefo->rmp_decay ) );
|
|
}
|
|
|
|
|
|
// allocate enveloper follower
|
|
|
|
#define EFO_HYST_AMP 1000 // hysteresis amplitude
|
|
|
|
efo_t *EFO_Alloc ( float threshold, float attack_sec, float decay_sec, bool bexp )
|
|
{
|
|
int i;
|
|
efo_t *pefo;
|
|
|
|
for (i = 0; i < CEFOS; i++)
|
|
{
|
|
if ( !efos[i].fused )
|
|
{
|
|
pefo = &efos[i];
|
|
|
|
EFO_Init ( pefo );
|
|
|
|
pefo->xout = 0;
|
|
pefo->fused = true;
|
|
|
|
// init gate params
|
|
|
|
pefo->bgate = threshold > 0.0;
|
|
|
|
if (pefo->bgate)
|
|
{
|
|
pefo->attack_time = attack_sec;
|
|
pefo->decay_time = decay_sec;
|
|
|
|
RMP_Init( &pefo->rmp_attack, attack_sec, 0, PMAX, false);
|
|
RMP_Init( &pefo->rmp_decay, decay_sec, PMAX, 0, false);
|
|
RMP_SetEnd( &pefo->rmp_attack );
|
|
RMP_SetEnd( &pefo->rmp_decay );
|
|
|
|
pefo->thresh = threshold;
|
|
pefo->thresh_off = MAX(1, threshold - EFO_HYST_AMP);
|
|
pefo->bgateon = false;
|
|
pefo->bexp = bexp;
|
|
}
|
|
|
|
return pefo;
|
|
}
|
|
}
|
|
|
|
DevMsg ("DSP: Warning, failed to allocate envelope follower.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
void EFO_Print( const efo_t & crs, int nIndentation )
|
|
{
|
|
DevMsg( "EFO_Print is not implemented\n" );
|
|
}
|
|
|
|
// values of L for CEFO_BITS_DIVIDE: L = (1 - 1/(1 << CEFO_BITS_DIVIDE))
|
|
// 1 L = 0.5
|
|
// 2 L = 0.75
|
|
// 3 L = 0.875
|
|
// 4 L = 0.9375
|
|
// 5 L = 0.96875
|
|
// 6 L = 0.984375
|
|
// 7 L = 0.9921875
|
|
// 8 L = 0.99609375
|
|
// 9 L = 0.998046875
|
|
// 10 L = 0.9990234375
|
|
// 11 L = 0.99951171875
|
|
// 12 L = 0.999755859375
|
|
|
|
|
|
// decay time constant for values of L, for E = 10^-3 = 60dB of attenuation
|
|
//
|
|
// Neff = Ln E / Ln L = -6.9077552 / Ln L
|
|
//
|
|
// 1 L = 0.5 Neff = 10 samples
|
|
// 2 L = 0.75 Neff = 24
|
|
// 3 L = 0.875 Neff = 51
|
|
// 4 L = 0.9375 Neff = 107
|
|
// 5 L = 0.96875 Neff = 217
|
|
// 6 L = 0.984375 Neff = 438
|
|
// 7 L = 0.9921875 Neff = 880
|
|
// 8 L = 0.99609375 Neff = 1764
|
|
// 9 L = 0.998046875 Neff = 3533
|
|
// 10 L = 0.9990234375 Neff = 7070
|
|
// 11 L = 0.99951171875 Neff = 14143
|
|
// 12 L = 0.999755859375 Neff = 28290
|
|
|
|
#define CEFO_BITS 11 // 14143 samples in gate window (3hz)
|
|
|
|
inline int EFO_GetNext( efo_t *pefo, int x )
|
|
{
|
|
int r;
|
|
int xa = abs(x);
|
|
int xdif;
|
|
|
|
|
|
// get envelope:
|
|
// Cn = L * Cn-1 + ( 1 - L ) * |x|
|
|
|
|
// which simplifies to:
|
|
// Cn = |x| + (Cn-1 - |x|) * L
|
|
|
|
// for 0 < L < 1
|
|
|
|
// increasing L increases time to rise or fall to a new input level
|
|
|
|
// so: increasing CEFO_BITS_DIVIDE increases rise/fall time
|
|
|
|
// where: L = (1 - 1/(1 << CEFO_BITS))
|
|
// xdif = Cn-1 - |x|
|
|
// so: xdif * L = xdif - xdif / (1 << CEFO_BITS) = ((xdif << CEFO_BITS) - xdif ) >> CEFO_BITS
|
|
|
|
xdif = pefo->xout - xa;
|
|
|
|
pefo->xout = xa + (((xdif << CEFO_BITS) - xdif) >> CEFO_BITS);
|
|
|
|
if ( pefo->bgate )
|
|
{
|
|
// gate
|
|
|
|
bool bgateon_prev = pefo->bgateon;
|
|
|
|
// gate hysteresis
|
|
|
|
if (bgateon_prev)
|
|
// gate was on - it's off only if amp drops below thresh_off
|
|
pefo->bgateon = ( pefo->xout >= pefo->thresh_off );
|
|
else
|
|
// gate was off - it's on only if amp > thresh
|
|
pefo->bgateon = ( pefo->xout >= pefo->thresh );
|
|
|
|
if ( pefo->bgateon )
|
|
{
|
|
// gate is on
|
|
|
|
if ( bgateon_prev && RMP_HitEnd( &pefo->rmp_attack ))
|
|
return x; // gate is fully on
|
|
|
|
if ( !bgateon_prev )
|
|
{
|
|
// gate just turned on, start ramp attack
|
|
|
|
// start attack from previous decay ramp if active
|
|
|
|
r = RMP_HitEnd( &pefo->rmp_decay ) ? 0 : RMP_GetNext( &pefo->rmp_decay );
|
|
RMP_SetEnd( &pefo->rmp_decay);
|
|
|
|
// DevMsg ("GATE ON \n");
|
|
|
|
RMP_Init( &pefo->rmp_attack, pefo->attack_time, r, PMAX, false);
|
|
|
|
return (x * r) >> PBITS;
|
|
}
|
|
|
|
if ( !RMP_HitEnd( &pefo->rmp_attack ) )
|
|
{
|
|
r = RMP_GetNext( &pefo->rmp_attack );
|
|
|
|
// gate is on and ramping up
|
|
|
|
return (x * r) >> PBITS;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// gate is fully off
|
|
|
|
if ( !bgateon_prev && RMP_HitEnd( &pefo->rmp_decay))
|
|
return 0;
|
|
|
|
if ( bgateon_prev )
|
|
{
|
|
// gate just turned off, start ramp decay
|
|
|
|
// start decay from previous attack ramp if active
|
|
|
|
r = RMP_HitEnd( &pefo->rmp_attack ) ? PMAX : RMP_GetNext( &pefo->rmp_attack );
|
|
RMP_SetEnd( &pefo->rmp_attack);
|
|
|
|
RMP_Init( &pefo->rmp_decay, pefo->decay_time, r, 0, false);
|
|
|
|
// DevMsg ("GATE OFF \n");
|
|
|
|
// if exponential set, gate has exponential ramp down. Otherwise linear ramp down.
|
|
|
|
if ( pefo->bexp )
|
|
return ( (((x * r) >> PBITS) * r ) >> PBITS );
|
|
else
|
|
return (x * r) >> PBITS;
|
|
|
|
}
|
|
else if ( !RMP_HitEnd( &pefo->rmp_decay ) )
|
|
{
|
|
// gate is off and ramping down
|
|
|
|
r = RMP_GetNext( &pefo->rmp_decay );
|
|
|
|
|
|
// if exponential set, gate has exponential ramp down. Otherwise linear ramp down.
|
|
|
|
if ( pefo->bexp )
|
|
return ( (((x * r) >> PBITS) * r ) >> PBITS );
|
|
else
|
|
return (x * r) >> PBITS;
|
|
}
|
|
}
|
|
|
|
return x;
|
|
}
|
|
|
|
return pefo->xout;
|
|
}
|
|
|
|
// batch version for performance
|
|
|
|
inline void EFO_GetNextN( efo_t *pefo, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb = pbuffer;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb->left = EFO_GetNext( pefo, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb->right = EFO_GetNext( pefo, pb->right );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb->left = pb->right = EFO_GetNext( pefo, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// parameter order
|
|
|
|
typedef enum
|
|
{
|
|
efo_ithreshold,
|
|
efo_iattack,
|
|
efo_idecay,
|
|
efo_iexp,
|
|
|
|
efo_cparam // # of params
|
|
|
|
} efo_e;
|
|
|
|
// parameter ranges
|
|
|
|
prm_rng_t efo_rng[] = {
|
|
|
|
{efo_cparam, 0, 0}, // first entry is # of parameters
|
|
|
|
{efo_ithreshold, -140.0, 0.0}, // gate threshold in db. if 0.0 then no gate.
|
|
{efo_iattack, 0.0, 20000.0}, // attack time in milliseconds
|
|
{efo_idecay, 0.0, 20000.0}, // envelope decay time in milliseconds
|
|
{efo_iexp, 0.0, 1.0}, // if 1, use exponential decay ramp (for more realistic reverb tail)
|
|
|
|
};
|
|
|
|
efo_t * EFO_Params ( prc_t *pprc )
|
|
{
|
|
efo_t *penv;
|
|
|
|
float threshold = Gain_To_Amplitude( dB_To_Gain(pprc->prm[efo_ithreshold]) );
|
|
float attack = pprc->prm[efo_iattack]/1000.0;
|
|
float decay = pprc->prm[efo_idecay]/1000.0;
|
|
float fexp = pprc->prm[efo_iexp];
|
|
bool bexp;
|
|
|
|
// check for no gate
|
|
|
|
if ( pprc->prm[efo_ithreshold] == 0.0 )
|
|
threshold = 0.0;
|
|
|
|
bexp = fexp > 0.0 ? 1 : 0;
|
|
|
|
penv = EFO_Alloc ( threshold, attack, decay, bexp );
|
|
return penv;
|
|
}
|
|
|
|
inline void * EFO_VParams ( void *p )
|
|
{
|
|
PRC_CheckParams( (prc_t *)p, efo_rng );
|
|
return (void *) EFO_Params ((prc_t *)p);
|
|
}
|
|
|
|
inline void EFO_Mod ( void *p, float v ) { return; }
|
|
|
|
|
|
///////////////////////////////////////////
|
|
// Chorus - lfo modulated delay
|
|
///////////////////////////////////////////
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CCRSS 128
|
|
#else
|
|
#define CCRSS 64 // max number chorus' active
|
|
#endif
|
|
|
|
struct crs_t
|
|
{
|
|
bool fused;
|
|
|
|
mdy_t *pmdy; // modulatable delay
|
|
lfo_t *plfo; // modulating lfo
|
|
|
|
int lfoprev; // previous modulator value from lfo
|
|
|
|
};
|
|
|
|
crs_t crss[CCRSS];
|
|
|
|
void CRS_Init( crs_t *pcrs ) { if (pcrs) Q_memset( pcrs, 0, sizeof (crs_t) ); };
|
|
void CRS_Free( crs_t *pcrs )
|
|
{
|
|
if (pcrs)
|
|
{
|
|
MDY_Free ( pcrs->pmdy );
|
|
LFO_Free ( pcrs->plfo );
|
|
Q_memset( pcrs, 0, sizeof (crs_t) );
|
|
}
|
|
}
|
|
|
|
|
|
void CRS_InitAll() { for (int i = 0; i < CCRSS; i++) CRS_Init( &crss[i] ); }
|
|
void CRS_FreeAll() { for (int i = 0; i < CCRSS; i++) CRS_Free( &crss[i] ); }
|
|
|
|
// fstep is base pitch shift, ie: floating point step value, where 1.0 = +1 octave, 0.5 = -1 octave
|
|
// lfotype is LFO_SIN, LFO_RND, LFO_TRI etc (LFO_RND for chorus, LFO_SIN for flange)
|
|
// fHz is modulation frequency in Hz
|
|
// depth is modulation depth, 0-1.0
|
|
// mix is mix of chorus and clean signal
|
|
|
|
#define CRS_DELAYMAX 100 // max milliseconds of sweepable delay
|
|
#define CRS_RAMPTIME 5 // milliseconds to ramp between new delay values
|
|
|
|
crs_t * CRS_Alloc( int lfotype, float fHz, float fdepth, float mix )
|
|
{
|
|
|
|
int i;
|
|
crs_t *pcrs;
|
|
dly_t *pdly;
|
|
mdy_t *pmdy;
|
|
lfo_t *plfo;
|
|
float ramptime;
|
|
int D;
|
|
|
|
// find free chorus slot
|
|
|
|
for ( i = 0; i < CCRSS; i++ )
|
|
{
|
|
if ( !crss[i].fused )
|
|
break;
|
|
}
|
|
|
|
if ( i == CCRSS )
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate chorus.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
pcrs = &crss[i];
|
|
|
|
CRS_Init ( pcrs );
|
|
|
|
D = fdepth * MSEC_TO_SAMPS(CRS_DELAYMAX); // sweep from 0 - n milliseconds
|
|
|
|
ramptime = (float) CRS_RAMPTIME / 1000.0; // # milliseconds to ramp between new values
|
|
|
|
pdly = DLY_Alloc ( D, 0, 1, DLY_LINEAR );
|
|
|
|
pmdy = MDY_Alloc ( pdly, ramptime, 0.0, 0.0, mix );
|
|
|
|
plfo = LFO_Alloc ( lfotype, fHz, false, 1.0 );
|
|
|
|
if ( !plfo || !pmdy )
|
|
{
|
|
LFO_Free ( plfo );
|
|
MDY_Free ( pmdy );
|
|
DevMsg ("DSP: Warning, failed to allocate lfo or mdy for chorus.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
pcrs->pmdy = pmdy;
|
|
pcrs->plfo = plfo;
|
|
pcrs->fused = true;
|
|
|
|
return pcrs;
|
|
}
|
|
|
|
void CRS_Print( const crs_t & crs, int nIndentation )
|
|
{
|
|
DevMsg( "CRS_Print is not implemented\n" );
|
|
}
|
|
|
|
// return next chorused sample (modulated delay) mixed with input sample
|
|
|
|
inline int CRS_GetNext( crs_t *pcrs, int x )
|
|
{
|
|
int l;
|
|
int y;
|
|
|
|
// get current mod delay value
|
|
|
|
y = MDY_GetNext ( pcrs->pmdy, x );
|
|
|
|
// get next lfo value for modulation
|
|
// note: lfo must return 0 as first value
|
|
|
|
l = LFO_GetNext ( pcrs->plfo, x );
|
|
|
|
// if modulator has changed, change mdy
|
|
|
|
if ( l != pcrs->lfoprev )
|
|
{
|
|
// calculate new tap starts at D)
|
|
|
|
int D = pcrs->pmdy->pdly->D0;
|
|
int tap;
|
|
|
|
// lfo should always output values 0 <= l <= LFOMAX
|
|
|
|
if (l < 0)
|
|
l = 0;
|
|
|
|
tap = D - ((l * D) >> LFOBITS);
|
|
|
|
MDY_ChangeVal ( pcrs->pmdy, tap );
|
|
|
|
pcrs->lfoprev = l;
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
// batch version for performance
|
|
|
|
inline void CRS_GetNextN( crs_t *pcrs, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb = pbuffer;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb->left = CRS_GetNext( pcrs, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb->right = CRS_GetNext( pcrs, pb->right );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb->left = pb->right = CRS_GetNext( pcrs, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// parameter order
|
|
|
|
typedef enum {
|
|
|
|
crs_ilfotype,
|
|
crs_irate,
|
|
crs_idepth,
|
|
crs_imix,
|
|
|
|
crs_cparam
|
|
|
|
} crs_e;
|
|
|
|
|
|
// parameter ranges
|
|
|
|
prm_rng_t crs_rng[] = {
|
|
|
|
{crs_cparam, 0, 0}, // first entry is # of parameters
|
|
|
|
{crs_ilfotype, 0, LFO_MAX}, // lfotype is LFO_SIN, LFO_RND, LFO_TRI etc (LFO_RND for chorus, LFO_SIN for flange)
|
|
{crs_irate, 0.0, 1000.0}, // rate is modulation frequency in Hz
|
|
{crs_idepth, 0.0, 1.0}, // depth is modulation depth, 0-1.0
|
|
{crs_imix, 0.0, 1.0}, // mix is mix of chorus and clean signal
|
|
|
|
};
|
|
|
|
// uses pitch, lfowav, rate, depth
|
|
|
|
crs_t * CRS_Params ( prc_t *pprc )
|
|
{
|
|
crs_t *pcrs;
|
|
|
|
pcrs = CRS_Alloc ( pprc->prm[crs_ilfotype], pprc->prm[crs_irate], pprc->prm[crs_idepth], pprc->prm[crs_imix] );
|
|
|
|
return pcrs;
|
|
}
|
|
|
|
inline void * CRS_VParams ( void *p )
|
|
{
|
|
PRC_CheckParams ( (prc_t *)p, crs_rng );
|
|
return (void *) CRS_Params ((prc_t *)p);
|
|
}
|
|
|
|
inline void CRS_Mod ( void *p, float v ) { return; }
|
|
|
|
|
|
////////////////////////////////////////////////////
|
|
// amplifier - modulatable gain, distortion
|
|
////////////////////////////////////////////////////
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CAMPS 128
|
|
#else
|
|
#define CAMPS 64 // max number amps active
|
|
#endif
|
|
|
|
#define AMPSLEW 10 // milliseconds of slew time between gain changes
|
|
|
|
struct amp_t
|
|
{
|
|
bool fused;
|
|
|
|
int gain; // amplification 0-6.0 * PMAX
|
|
int gain_max; // original gain setting
|
|
int distmix; // 0-1.0 mix of distortion with clean * PMAX
|
|
int vfeed; // 0-1.0 feedback with distortion * PMAX
|
|
int vthresh; // amplitude of clipping threshold 0..32768
|
|
|
|
|
|
bool fchanging; // true if modulating to new amp value
|
|
float ramptime; // ramp 'glide' time - time in seconds to change between values
|
|
int mtime; // time in samples between amp changes. 0 implies no self-modulating
|
|
int mtimecur; // current time in samples until next amp change
|
|
int depth; // modulate amp from A to A - (A*depth) depth 0-1.0
|
|
bool brand; // if true, use random modulation otherwise alternate btwn max/min
|
|
rmp_t rmp_interp; // interpolation ramp 0...PMAX
|
|
|
|
};
|
|
|
|
amp_t amps[CAMPS];
|
|
|
|
void AMP_Init( amp_t *pamp ) { if (pamp) Q_memset( pamp, 0, sizeof (amp_t) ); };
|
|
void AMP_Free( amp_t *pamp )
|
|
{
|
|
if (pamp)
|
|
{
|
|
Q_memset( pamp, 0, sizeof (amp_t) );
|
|
}
|
|
}
|
|
|
|
|
|
void AMP_InitAll() { for (int i = 0; i < CAMPS; i++) AMP_Init( &s[i] ); }
|
|
void AMP_FreeAll() { for (int i = 0; i < CAMPS; i++) AMP_Free( &s[i] ); }
|
|
|
|
|
|
amp_t * AMP_Alloc( float gain, float vthresh, float distmix, float vfeed, float ramptime, float modtime, float depth, bool brand )
|
|
{
|
|
int i;
|
|
amp_t *pamp;
|
|
|
|
// find free amp slot
|
|
|
|
for ( i = 0; i < CAMPS; i++ )
|
|
{
|
|
if ( !amps[i].fused )
|
|
break;
|
|
}
|
|
|
|
if ( i == CAMPS )
|
|
{
|
|
DevMsg ("DSP: Warning, failed to allocate amp.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
pamp = &s[i];
|
|
|
|
AMP_Init ( pamp );
|
|
|
|
pamp->fused = true;
|
|
|
|
pamp->gain = gain * PMAX;
|
|
pamp->gain_max = gain * PMAX;
|
|
pamp->distmix = distmix * PMAX;
|
|
pamp->vfeed = vfeed * PMAX;
|
|
pamp->vthresh = vthresh * 32767.0;
|
|
|
|
// modrate, 0.01, 200.0}, // frequency at which amplitude values change to new random value. 0 is no self-modulation
|
|
// moddepth, 0.0, 1.0}, // how much amplitude changes (decreases) from current value (0-1.0)
|
|
// modglide, 0.01, 100.0}, // glide time between mapcur and ampnew in milliseconds
|
|
|
|
pamp->ramptime = ramptime;
|
|
pamp->mtime = SEC_TO_SAMPS(modtime);
|
|
pamp->mtimecur = pamp->mtime;
|
|
pamp->depth = depth * PMAX;
|
|
pamp->brand = brand;
|
|
|
|
return pamp;
|
|
}
|
|
|
|
void AMP_Print( const amp_t & crs, int nIndentation )
|
|
{
|
|
DevMsg( "AMP_Print is not implemented\n" );
|
|
}
|
|
|
|
// return next amplified sample
|
|
|
|
inline int AMP_GetNext( amp_t *pamp, int x )
|
|
{
|
|
int y = x;
|
|
int d;
|
|
|
|
// if distortion is on, add distortion, feedback
|
|
|
|
if ( pamp->vthresh < PMAX && pamp->distmix )
|
|
{
|
|
int vthresh = pamp->vthresh;
|
|
|
|
/* if ( pamp->vfeed > 0.0 )
|
|
{
|
|
// UNDONE: feedback
|
|
}
|
|
*/
|
|
// clip distort
|
|
|
|
d = ( y > vthresh ? vthresh : ( y < -vthresh ? -vthresh : y));
|
|
|
|
// mix distorted with clean (1.0 = full distortion)
|
|
|
|
if ( pamp->distmix < PMAX )
|
|
y = y + (((d - y) * pamp->distmix ) >> PBITS);
|
|
else
|
|
y = d;
|
|
}
|
|
|
|
// get output for current gain value
|
|
|
|
int xout = (y * pamp->gain) >> PBITS;
|
|
|
|
if ( !pamp->fchanging && !pamp->mtime )
|
|
{
|
|
// if not modulating and not self modulating, return right away
|
|
|
|
return xout;
|
|
}
|
|
|
|
if (pamp->fchanging)
|
|
{
|
|
// modulating...
|
|
|
|
// get next gain value
|
|
|
|
pamp->gain = RMP_GetNext( &pamp->rmp_interp ); // 0...next gain
|
|
|
|
if ( RMP_HitEnd( &pamp->rmp_interp ) )
|
|
{
|
|
// done.
|
|
|
|
pamp->fchanging = false;
|
|
}
|
|
}
|
|
|
|
// if self-modulating and timer has expired, get next change
|
|
|
|
if ( pamp->mtime && !pamp->mtimecur-- )
|
|
{
|
|
pamp->mtimecur = pamp->mtime;
|
|
|
|
int gain_new;
|
|
int G1;
|
|
int G2 = pamp->gain_max;
|
|
|
|
// modulate between 0 and 100% of gain_max
|
|
|
|
G1 = pamp->gain_max - ((pamp->gain_max * pamp->depth) >> PBITS);
|
|
|
|
if (pamp->brand)
|
|
{
|
|
gain_new = LocalRandomInt( MIN(G1,G2), MAX(G1,G2) );
|
|
}
|
|
else
|
|
{
|
|
// alternate between min & max
|
|
|
|
gain_new = (pamp->gain == G1 ? G2 : G1);
|
|
}
|
|
|
|
// set up modulation to new value
|
|
|
|
pamp->fchanging = true;
|
|
|
|
// init gain ramp - always hit target
|
|
|
|
RMP_Init ( &pamp->rmp_interp, pamp->ramptime, pamp->gain, gain_new, false );
|
|
}
|
|
|
|
return xout;
|
|
|
|
}
|
|
|
|
// batch version for performance
|
|
|
|
inline void AMP_GetNextN( amp_t *pamp, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
int count = SampleCount;
|
|
portable_samplepair_t *pb = pbuffer;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
case OP_LEFT:
|
|
while (count--)
|
|
{
|
|
pb->left = AMP_GetNext( pamp, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_RIGHT:
|
|
while (count--)
|
|
{
|
|
pb->right = AMP_GetNext( pamp, pb->right );
|
|
pb++;
|
|
}
|
|
return;
|
|
case OP_LEFT_DUPLICATE:
|
|
while (count--)
|
|
{
|
|
pb->left = pb->right = AMP_GetNext( pamp, pb->left );
|
|
pb++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
inline void AMP_Mod( amp_t *pamp, float v )
|
|
{
|
|
}
|
|
|
|
|
|
// parameter order
|
|
|
|
typedef enum {
|
|
|
|
amp_gain,
|
|
amp_vthresh,
|
|
amp_distmix,
|
|
amp_vfeed,
|
|
amp_imodrate,
|
|
amp_imoddepth,
|
|
amp_imodglide,
|
|
amp_irand,
|
|
amp_cparam
|
|
|
|
} amp_e;
|
|
|
|
|
|
// parameter ranges
|
|
|
|
prm_rng_t amp_rng[] = {
|
|
|
|
{amp_cparam, 0, 0}, // first entry is # of parameters
|
|
|
|
{amp_gain, 0.0, 1000.0}, // amplification
|
|
{amp_vthresh, 0.0, 1.0}, // threshold for distortion (1.0 = no distortion)
|
|
{amp_distmix, 0.0, 1.0}, // mix of clean and distortion (1.0 = full distortion, 0.0 = full clean)
|
|
{amp_vfeed, 0.0, 1.0}, // distortion feedback
|
|
|
|
{amp_imodrate, 0.0, 200.0}, // frequency at which amplitude values change to new random value. 0 is no self-modulation
|
|
{amp_imoddepth, 0.0, 1.0}, // how much amplitude changes (decreases) from current value (0-1.0)
|
|
{amp_imodglide, 0.01, 100.0}, // glide time between mapcur and ampnew in milliseconds
|
|
{amp_irand, 0.0, 1.0}, // if 1, use random modulation otherwise alternate from max-min-max
|
|
};
|
|
|
|
amp_t * AMP_Params ( prc_t *pprc )
|
|
{
|
|
amp_t *pamp;
|
|
|
|
float ramptime = 0.0;
|
|
float modtime = 0.0;
|
|
float depth = 0.0;
|
|
float rand = pprc->prm[amp_irand];
|
|
bool brand;
|
|
|
|
if (pprc->prm[amp_imodrate] > 0.0)
|
|
{
|
|
ramptime = pprc->prm[amp_imodglide] / 1000.0; // get ramp time in seconds
|
|
modtime = 1.0 / MAX(pprc->prm[amp_imodrate], 0.01); // time between modulations in seconds
|
|
depth = pprc->prm[amp_imoddepth]; // depth of modulations 0-1.0
|
|
}
|
|
|
|
brand = rand > 0.0 ? 1 : 0;
|
|
|
|
pamp = AMP_Alloc ( pprc->prm[amp_gain], pprc->prm[amp_vthresh], pprc->prm[amp_distmix], pprc->prm[amp_vfeed],
|
|
ramptime, modtime, depth, brand );
|
|
|
|
return pamp;
|
|
}
|
|
|
|
inline void * AMP_VParams ( void *p )
|
|
{
|
|
PRC_CheckParams ( (prc_t *)p, amp_rng );
|
|
return (void *) AMP_Params ((prc_t *)p);
|
|
}
|
|
|
|
|
|
/////////////////
|
|
// NULL processor
|
|
/////////////////
|
|
|
|
struct nul_t
|
|
{
|
|
int type;
|
|
};
|
|
|
|
nul_t nuls[] = {0};
|
|
|
|
void NULL_Init ( nul_t *pnul ) { }
|
|
void NULL_InitAll( ) { }
|
|
void NULL_Free ( nul_t *pnul ) { }
|
|
void NULL_FreeAll ( ) { }
|
|
nul_t *NULL_Alloc ( ) { return &nuls[0]; }
|
|
|
|
inline int NULL_GetNext ( void *p, int x) { return x; }
|
|
|
|
inline void NULL_GetNextN( nul_t *pnul, portable_samplepair_t *pbuffer, int SampleCount, int op ) { return; }
|
|
|
|
inline void NULL_Mod ( void *p, float v ) { return; }
|
|
|
|
inline void * NULL_VParams ( void *p ) { return (void *) (&nuls[0]); }
|
|
|
|
//////////////////////////
|
|
// DSP processors presets - see dsp_presets.txt
|
|
//////////////////////////
|
|
|
|
|
|
|
|
|
|
// init array of processors - first store pfnParam, pfnGetNext and pfnFree functions for type,
|
|
// then call the pfnParam function to initialize each processor
|
|
|
|
// prcs - an array of prc structures, all with initialized params
|
|
// count - number of elements in the array
|
|
|
|
// returns false if failed to init one or more processors
|
|
|
|
bool PRC_InitAll( prc_t *prcs, int count )
|
|
{
|
|
int i;
|
|
prc_Param_t pfnParam; // allocation function - takes ptr to prc, returns ptr to specialized data struct for proc type
|
|
prc_GetNext_t pfnGetNext; // get next function
|
|
prc_GetNextN_t pfnGetNextN; // get next function, batch version
|
|
prc_Free_t pfnFree;
|
|
prc_Mod_t pfnMod;
|
|
|
|
bool fok = true;;
|
|
|
|
if ( count == 0 )
|
|
count = 1;
|
|
|
|
// set up pointers to XXX_Free, XXX_GetNext and XXX_Params functions
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
switch (prcs[i].type)
|
|
{
|
|
default:
|
|
case PRC_NULL:
|
|
pfnFree = (prc_Free_t)NULL_Free;
|
|
pfnGetNext = (prc_GetNext_t)NULL_GetNext;
|
|
pfnGetNextN = (prc_GetNextN_t)NULL_GetNextN;
|
|
pfnParam = NULL_VParams;
|
|
pfnMod = (prc_Mod_t)NULL_Mod;
|
|
break;
|
|
case PRC_DLY:
|
|
pfnFree = (prc_Free_t)DLY_Free;
|
|
pfnGetNext = (prc_GetNext_t)DLY_GetNext;
|
|
pfnGetNextN = (prc_GetNextN_t)DLY_GetNextN;
|
|
pfnParam = DLY_VParams;
|
|
pfnMod = (prc_Mod_t)DLY_Mod;
|
|
break;
|
|
case PRC_RVA:
|
|
pfnFree = (prc_Free_t)RVA_Free;
|
|
pfnGetNext = (prc_GetNext_t)RVA_GetNext;
|
|
pfnGetNextN = (prc_GetNextN_t)RVA_GetNextN_Opt;
|
|
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_Opt;
|
|
pfnParam = DFR_VParams;
|
|
pfnMod = (prc_Mod_t)DFR_Mod;
|
|
break;
|
|
case PRC_AMP:
|
|
pfnFree = (prc_Free_t)AMP_Free;
|
|
pfnGetNext = (prc_GetNext_t)AMP_GetNext;
|
|
pfnGetNextN = (prc_GetNextN_t)AMP_GetNextN;
|
|
pfnParam = AMP_VParams;
|
|
pfnMod = (prc_Mod_t)AMP_Mod;
|
|
break;
|
|
}
|
|
|
|
// set up function pointers
|
|
|
|
prcs[i].pfnParam = pfnParam;
|
|
prcs[i].pfnGetNext = pfnGetNext;
|
|
prcs[i].pfnGetNextN = pfnGetNextN;
|
|
prcs[i].pfnFree = pfnFree;
|
|
prcs[i].pfnMod = pfnMod;
|
|
|
|
// call param function, store pdata for the processor type
|
|
|
|
prcs[i].pdata = pfnParam ( (void *) (&prcs[i]) );
|
|
|
|
if ( !prcs[i].pdata )
|
|
fok = false;
|
|
}
|
|
|
|
return fok;
|
|
}
|
|
|
|
// free individual processor's data
|
|
|
|
void PRC_Free ( prc_t *pprc )
|
|
{
|
|
if ( pprc->pfnFree && pprc->pdata )
|
|
pprc->pfnFree ( pprc->pdata );
|
|
}
|
|
|
|
// free all processors for supplied array
|
|
// prcs - array of processors
|
|
// count - elements in array
|
|
|
|
void PRC_FreeAll ( prc_t *prcs, int count )
|
|
{
|
|
for (int i = 0; i < count; i++)
|
|
PRC_Free( &prcs[i] );
|
|
}
|
|
|
|
// get next value for processor - (usually called directly by PSET_GetNext)
|
|
|
|
inline int PRC_GetNext ( prc_t *pprc, int x )
|
|
{
|
|
return pprc->pfnGetNext ( pprc->pdata, x );
|
|
}
|
|
|
|
// automatic parameter range limiting
|
|
// force parameters between specified min/max in param_rng
|
|
|
|
void PRC_CheckParams ( prc_t *pprc, prm_rng_t *prng )
|
|
{
|
|
// first entry in param_rng is # of parameters
|
|
|
|
int cprm = prng[0].iprm;
|
|
|
|
for (int i = 0; i < cprm; i++)
|
|
{
|
|
// if parameter is 0.0, always allow it (this is 'off' for most params)
|
|
|
|
if ( pprc->prm[i] != 0.0 && (pprc->prm[i] > prng[i+1].hi || pprc->prm[i] < prng[i+1].lo) )
|
|
{
|
|
DevMsg ("DSP: Warning, clamping out of range parameter.\n" );
|
|
pprc->prm[i] = iclamp (pprc->prm[i], prng[i+1].lo, prng[i+1].hi);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PRC_Print( const prc_t &prc, int nIndentation )
|
|
{
|
|
const char * pIndent = GetIndentationText( nIndentation );
|
|
DevMsg( "%sPRC: %p [Addr]\n", pIndent, &prc );
|
|
|
|
const char * pType = "Unknown";
|
|
// Use a switch case instead of a table as it is more resistant to change (and this code is not performance critical).
|
|
switch ( prc.type )
|
|
{
|
|
case PRC_NULL: pType = "NULL"; break;
|
|
case PRC_DLY: pType = "DLY - Simple feedback reverb"; break;
|
|
case PRC_RVA: pType = "RVA - Parallel reverbs"; break;
|
|
case PRC_FLT: pType = "FLT - Lowpass or highpass filter"; break;
|
|
case PRC_CRS: pType = "CRS - Chorus"; break;
|
|
case PRC_PTC: pType = "PTC - Pitch shifter"; break;
|
|
case PRC_ENV: pType = "ENV - Adsr envelope"; break;
|
|
case PRC_LFO: pType = "LFO"; break;
|
|
case PRC_EFO: pType = "EFO - Envelope follower"; break;
|
|
case PRC_MDY: pType = "MDY - Mod delay"; break;
|
|
case PRC_DFR: pType = "DFR - Diffusor - n series allpass delays"; break;
|
|
case PRC_AMP: pType = "AMP - Amplifier with distortion"; break;
|
|
}
|
|
|
|
DevMsg( "%sprm: ", pIndent );
|
|
for (int i = 0 ; i < CPRCPARAMS ; ++i )
|
|
{
|
|
DevMsg( "%f ", prc.prm[i] );
|
|
}
|
|
DevMsg( "\n" );
|
|
|
|
DevMsg( "%sType: %s -", pIndent, pType );
|
|
switch ( prc.type )
|
|
{
|
|
case PRC_DLY: DLY_Print( *(dly_t *)prc.pdata, nIndentation + 1 ); break;
|
|
case PRC_RVA: RVA_Print( *(rva_t *)prc.pdata, nIndentation + 1 ); break;
|
|
case PRC_FLT: FLT_Print( *(flt_t *)prc.pdata, nIndentation + 1 ); break;
|
|
case PRC_CRS: CRS_Print( *(crs_t *)prc.pdata, nIndentation + 1 ); break;
|
|
case PRC_PTC: PTC_Print( *(ptc_t *)prc.pdata, nIndentation + 1 ); break;
|
|
case PRC_ENV: ENV_Print( *(env_t *)prc.pdata, nIndentation + 1 ); break;
|
|
case PRC_LFO: LFO_Print( *(lfo_t *)prc.pdata, nIndentation + 1 ); break;
|
|
case PRC_EFO: EFO_Print( *(efo_t *)prc.pdata, nIndentation + 1 ); break;
|
|
case PRC_MDY: MDY_Print( *(mdy_t *)prc.pdata, nIndentation + 1 ); break;
|
|
case PRC_DFR: DFR_Print( *(dfr_t *)prc.pdata, nIndentation + 1 ); break;
|
|
case PRC_AMP: AMP_Print( *(amp_t *)prc.pdata, nIndentation + 1 ); break;
|
|
}
|
|
}
|
|
|
|
// DSP presets
|
|
|
|
// A dsp preset comprises one or more dsp processors in linear, parallel or feedback configuration
|
|
|
|
// preset configurations
|
|
//
|
|
#define PSET_SIMPLE 0
|
|
|
|
// x(n)--->P(0)--->y(n)
|
|
|
|
#define PSET_LINEAR 1
|
|
|
|
// x(n)--->P(0)-->P(1)-->...P(m)--->y(n)
|
|
|
|
|
|
#define PSET_PARALLEL2 5
|
|
|
|
// x(n)--->P(0)-->(+)-->y(n)
|
|
// ^
|
|
// |
|
|
// x(n)--->P(1)-----
|
|
|
|
#define PSET_PARALLEL4 6
|
|
|
|
// x(n)--->P(0)-->P(1)-->(+)-->y(n)
|
|
// ^
|
|
// |
|
|
// x(n)--->P(2)-->P(3)-----
|
|
|
|
#define PSET_PARALLEL5 7
|
|
|
|
// x(n)--->P(0)-->P(1)-->(+)-->P(4)-->y(n)
|
|
// ^
|
|
// |
|
|
// x(n)--->P(2)-->P(3)-----
|
|
|
|
#define PSET_FEEDBACK 8
|
|
|
|
// x(n)-P(0)--(+)-->P(1)-->P(2)---->y(n)
|
|
// ^ |
|
|
// | v
|
|
// -----P(4)<--P(3)--
|
|
|
|
#define PSET_FEEDBACK3 9
|
|
|
|
// x(n)---(+)-->P(0)--------->y(n)
|
|
// ^ |
|
|
// | v
|
|
// -----P(2)<--P(1)--
|
|
|
|
#define PSET_FEEDBACK4 10
|
|
|
|
// x(n)---(+)-->P(0)-------->P(3)--->y(n)
|
|
// ^ |
|
|
// | v
|
|
// ---P(2)<--P(1)--
|
|
|
|
#define PSET_MOD 11
|
|
|
|
//
|
|
// x(n)------>P(1)--P(2)--P(3)--->y(n)
|
|
// ^
|
|
// x(n)------>P(0)....:
|
|
|
|
#define PSET_MOD2 12
|
|
|
|
//
|
|
// x(n)-------P(1)-->y(n)
|
|
// ^
|
|
// x(n)-->P(0)..:
|
|
|
|
|
|
#define PSET_MOD3 13
|
|
|
|
//
|
|
// x(n)-------P(1)-->P(2)-->y(n)
|
|
// ^
|
|
// x(n)-->P(0)..:
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CPSETS 128
|
|
#else
|
|
#define CPSETS 64 // max number of presets simultaneously active
|
|
#endif
|
|
|
|
#define CPSET_PRCS 5 // max # of processors per dsp preset
|
|
#define CPSET_STATES (CPSET_PRCS+3) // # of internal states
|
|
|
|
// NOTE: do not reorder members of pset_t - g_psettemplates relies on it!!!
|
|
|
|
struct pset_t
|
|
{
|
|
int type; // preset configuration type
|
|
int cprcs; // number of processors for this preset
|
|
|
|
prc_t prcs[CPSET_PRCS]; // processor preset data
|
|
|
|
float mix_min; // min dsp mix at close range
|
|
float mix_max; // max dsp mix at long range
|
|
float db_min; // if sndlvl of a new sound is < db_min, reduce mix_min/max by db_mixdrop
|
|
float db_mixdrop; // reduce mix_min/max by n% if sndlvl of new sound less than db_min
|
|
float duration; // if > 0, duration of preset in seconds (duration 0 = infinite)
|
|
float fade; // fade out time, exponential fade
|
|
|
|
int csamp_duration; // duration counter # samples
|
|
|
|
int w[CPSET_STATES]; // internal states
|
|
int fused;
|
|
|
|
uint nLastUpdatedTimeInMilliseconds;
|
|
};
|
|
|
|
pset_t psets[CPSETS];
|
|
|
|
pset_t *g_psettemplates = NULL;
|
|
int g_cpsettemplates = 0;
|
|
|
|
// returns true if preset will expire after duration
|
|
|
|
bool PSET_IsOneShot( pset_t *ppset )
|
|
{
|
|
if ( ppset == NULL )
|
|
{
|
|
return false;
|
|
}
|
|
return ppset->duration > 0.0;
|
|
}
|
|
|
|
// return true if preset is no longer active - duration has expired
|
|
|
|
bool PSET_HasExpired( pset_t *ppset )
|
|
{
|
|
if (!PSET_IsOneShot( ppset ))
|
|
return false;
|
|
|
|
return ppset->csamp_duration <= 0;
|
|
}
|
|
|
|
// if preset is oneshot, update duration counter by SampleCount samples
|
|
|
|
void PSET_UpdateDuration( pset_t *ppset, int SampleCount )
|
|
{
|
|
if ( PSET_IsOneShot( ppset ) )
|
|
{
|
|
// if oneshot preset and not expired, decrement sample count
|
|
|
|
if (ppset->csamp_duration > 0)
|
|
ppset->csamp_duration -= SampleCount;
|
|
}
|
|
}
|
|
|
|
// A dsp processor (prc) performs a single-sample function, such as pitch shift, delay, reverb, filter
|
|
|
|
|
|
// init a preset - just clear state array
|
|
|
|
void PSET_Init( pset_t *ppset )
|
|
{
|
|
// clear state array
|
|
|
|
if (ppset)
|
|
Q_memset( ppset->w, 0, sizeof (int) * (CPSET_STATES) );
|
|
}
|
|
|
|
// clear runtime slots
|
|
|
|
void PSET_InitAll( void )
|
|
{
|
|
for (int i = 0; i < CPSETS; i++)
|
|
Q_memset( &psets[i], 0, sizeof(pset_t));
|
|
}
|
|
|
|
// free the preset - free all processors
|
|
|
|
void PSET_Free( pset_t *ppset )
|
|
{
|
|
if (ppset)
|
|
{
|
|
// free processors
|
|
|
|
PRC_FreeAll ( ppset->prcs, ppset->cprcs );
|
|
|
|
// clear
|
|
|
|
Q_memset( ppset, 0, sizeof (pset_t));
|
|
}
|
|
}
|
|
|
|
void PSET_FreeAll() { for (int i = 0; i < CPSETS; i++) PSET_Free( &psets[i] ); };
|
|
|
|
// return preset struct, given index into preset template array
|
|
// NOTE: should not ever be more than 2 or 3 of these active simultaneously
|
|
|
|
pset_t * PSET_Alloc ( int ipsettemplate )
|
|
{
|
|
pset_t *ppset;
|
|
bool fok;
|
|
|
|
// don't excede array bounds
|
|
|
|
if ( ipsettemplate >= g_cpsettemplates)
|
|
ipsettemplate = 0;
|
|
|
|
// find free slot
|
|
int i = 0;
|
|
for (; i < CPSETS; i++)
|
|
{
|
|
if ( !psets[i].fused )
|
|
break;
|
|
}
|
|
|
|
if ( i == CPSETS )
|
|
return NULL;
|
|
|
|
if (das_debug.GetInt())
|
|
{
|
|
int j = 0;
|
|
for (int i = 0; i < CPSETS; i++)
|
|
{
|
|
if ( psets[i].fused )
|
|
j++;
|
|
}
|
|
DevMsg("total preset slots used: %d \n", j);
|
|
}
|
|
|
|
ppset = &psets[i];
|
|
|
|
// clear preset
|
|
|
|
Q_memset(ppset, 0, sizeof(pset_t));
|
|
|
|
// copy template into preset
|
|
|
|
*ppset = g_psettemplates[ipsettemplate];
|
|
|
|
ppset->fused = true;
|
|
|
|
// clear state array
|
|
|
|
PSET_Init ( ppset );
|
|
|
|
// init all processors, set up processor function pointers
|
|
|
|
fok = PRC_InitAll( ppset->prcs, ppset->cprcs );
|
|
|
|
if ( !fok )
|
|
{
|
|
// failed to init one or more processors
|
|
Warning( "Sound DSP: preset failed to init.\n");
|
|
PRC_FreeAll ( ppset->prcs, ppset->cprcs );
|
|
return NULL;
|
|
}
|
|
|
|
// if preset has duration, setup duration sample counter
|
|
|
|
if ( PSET_IsOneShot( ppset ) )
|
|
{
|
|
ppset->csamp_duration = SEC_TO_SAMPS( ppset->duration );
|
|
}
|
|
|
|
return ppset;
|
|
}
|
|
|
|
void PSET_Print( const pset_t & pset, int nIndentation )
|
|
{
|
|
const char * pIndent = GetIndentationText( nIndentation );
|
|
DevMsg( "%sPSET: %p [Addr]\n", pIndent, &pset );
|
|
DevMsg( "%sType: %d\n", pIndent, pset.type );
|
|
DevMsg( "%scprcs: %d\n", pIndent, pset.cprcs );
|
|
for ( int i = 0 ; i < pset.cprcs ; ++i )
|
|
{
|
|
PRC_Print( pset.prcs[i], nIndentation + 1 );
|
|
}
|
|
DevMsg( "%smix_min: %f\n", pIndent, pset.mix_min );
|
|
DevMsg( "%smix_max: %f\n", pIndent, pset.mix_max );
|
|
DevMsg( "%sdb_min: %f\n", pIndent, pset.db_min );
|
|
DevMsg( "%sdb_mixdrop: %f\n", pIndent, pset.db_mixdrop );
|
|
DevMsg( "%sduration: %f\n", pIndent, pset.duration );
|
|
DevMsg( "%sfade: %f\n", pIndent, pset.fade );
|
|
DevMsg( "%scsamp_duration: %d\n", pIndent, pset.csamp_duration );
|
|
DevMsg( "%sw: ", pIndent );
|
|
for (int i = 0 ; i < CPSET_STATES ; ++i )
|
|
{
|
|
DevMsg( "%d ", pset.w[i] );
|
|
}
|
|
DevMsg( "\n" );
|
|
DevMsg("%sfused: %d\n", pIndent, pset.fused );
|
|
}
|
|
|
|
// batch version of PSET_GetNext for linear array of processors. For performance.
|
|
|
|
// ppset - preset array
|
|
// pbuffer - input sample data
|
|
// SampleCount - size of input buffer
|
|
// OP: OP_LEFT - process left channel in place
|
|
// OP_RIGHT - process right channel in place
|
|
// OP_LEFT_DUPLICATe - process left channel, duplicate into right
|
|
|
|
inline void PSET_GetNextN( pset_t *ppset, portable_samplepair_t *pbuffer, int SampleCount, int op )
|
|
{
|
|
portable_samplepair_t *pbf = pbuffer;
|
|
prc_t *pprc;
|
|
int count = ppset->cprcs;
|
|
|
|
switch ( ppset->type )
|
|
{
|
|
default:
|
|
case PSET_SIMPLE:
|
|
{
|
|
// x(n)--->P(0)--->y(n)
|
|
|
|
ppset->prcs[0].pfnGetNextN (ppset->prcs[0].pdata, pbf, SampleCount, op);
|
|
return;
|
|
}
|
|
case PSET_LINEAR:
|
|
{
|
|
|
|
// w0 w1 w2
|
|
// x(n)--->P(0)-->P(1)-->...P(count-1)--->y(n)
|
|
|
|
// w0 w1 w2 w3 w4 w5
|
|
// x(n)--->P(0)-->P(1)-->P(2)-->P(3)-->P(4)-->y(n)
|
|
|
|
// call batch processors in sequence - no internal state for batch processing
|
|
|
|
// point to first processor
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
pprc->pfnGetNextN (pprc->pdata, pbf, SampleCount, op);
|
|
pprc++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Get next sample from this preset. called once for every sample in buffer
|
|
// ppset is pointer to preset
|
|
// x is input sample
|
|
|
|
inline int PSET_GetNext ( pset_t *ppset, int x )
|
|
{
|
|
|
|
// pset_simple and pset_linear have no internal state:
|
|
// this is REQUIRED for all presets that have a batch getnextN equivalent!
|
|
|
|
if ( ppset->type == PSET_SIMPLE )
|
|
{
|
|
// x(n)--->P(0)--->y(n)
|
|
|
|
return ppset->prcs[0].pfnGetNext (ppset->prcs[0].pdata, x);
|
|
}
|
|
|
|
prc_t *pprc;
|
|
int count = ppset->cprcs;
|
|
|
|
if ( ppset->type == PSET_LINEAR )
|
|
{
|
|
int y = x;
|
|
|
|
// w0 w1 w2
|
|
// x(n)--->P(0)-->P(1)-->...P(count-1)--->y(n)
|
|
|
|
// w0 w1 w2 w3 w4 w5
|
|
// x(n)--->P(0)-->P(1)-->P(2)-->P(3)-->P(4)-->y(n)
|
|
|
|
// call processors in reverse order, from count to 1
|
|
|
|
//for (int i = count; i > 0; i--, pprc--)
|
|
// w[i] = pprc->pfnGetNext (pprc->pdata, w[i-1]);
|
|
|
|
// return w[count];
|
|
|
|
|
|
// point to first processor, update sequentially, no state preserved
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
switch (count)
|
|
{
|
|
default:
|
|
case 5:
|
|
y = pprc->pfnGetNext (pprc->pdata, y);
|
|
pprc++;
|
|
case 4:
|
|
y = pprc->pfnGetNext (pprc->pdata, y);
|
|
pprc++;
|
|
case 3:
|
|
y = pprc->pfnGetNext (pprc->pdata, y);
|
|
pprc++;
|
|
case 2:
|
|
y = pprc->pfnGetNext (pprc->pdata, y);
|
|
pprc++;
|
|
case 1:
|
|
case 0:
|
|
y = pprc->pfnGetNext (pprc->pdata, y);
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
// all other preset types have internal state:
|
|
|
|
// initialize 0'th element of state array
|
|
|
|
int *w = ppset->w;
|
|
w[0] = x;
|
|
|
|
switch ( ppset->type )
|
|
{
|
|
default:
|
|
|
|
case PSET_PARALLEL2:
|
|
{ // w0 w1 w3
|
|
// x(n)--->P(0)-->(+)-->y(n)
|
|
// ^
|
|
// w0 w2 |
|
|
// x(n)--->P(1)-----
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
w[3] = w[1] + w[2];
|
|
|
|
w[1] = pprc->pfnGetNext( pprc->pdata, w[0] );
|
|
pprc++;
|
|
w[2] = pprc->pfnGetNext( pprc->pdata, w[0] );
|
|
|
|
return w[3];
|
|
}
|
|
|
|
case PSET_PARALLEL4:
|
|
{ // w0 w1 w2 w5
|
|
// x(n)--->P(0)-->P(1)-->(+)-->y(n)
|
|
// ^
|
|
// w0 w3 w4 |
|
|
// x(n)--->P(2)-->P(3)-----
|
|
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
w[5] = w[2] + w[4];
|
|
|
|
w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[1] );
|
|
w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] );
|
|
|
|
w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
|
|
w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[0] );
|
|
|
|
return w[5];
|
|
}
|
|
|
|
case PSET_PARALLEL5:
|
|
{ // w0 w1 w2 w5 w6
|
|
// x(n)--->P(0)-->P(1)-->(+)--P(4)-->y(n)
|
|
// ^
|
|
// w0 w3 w4 |
|
|
// x(n)--->P(2)-->P(3)-----
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
w[5] = w[2] + w[4];
|
|
|
|
w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[1] );
|
|
w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] );
|
|
|
|
w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
|
|
w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[0] );
|
|
|
|
return pprc[4].pfnGetNext( pprc[4].pdata, w[5] );
|
|
}
|
|
|
|
case PSET_FEEDBACK:
|
|
{
|
|
// w0 w1 w2 w3 w4 w7
|
|
// x(n)-P(0)--(+)-->P(1)-->P(2)-->---->y(n)
|
|
// ^ |
|
|
// | w6 w5 v
|
|
// -----P(4)<--P(3)--
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
// start with adders
|
|
|
|
w[2] = w[1] + w[6];
|
|
|
|
// evaluate in reverse order
|
|
|
|
w[6] = pprc[4].pfnGetNext( pprc[4].pdata, w[5] );
|
|
w[5] = pprc[3].pfnGetNext( pprc[3].pdata, w[4] );
|
|
|
|
w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] );
|
|
w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] );
|
|
w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
|
|
|
|
return w[4];
|
|
}
|
|
case PSET_FEEDBACK3:
|
|
{
|
|
// w0 w1 w2
|
|
// x(n)---(+)-->P(0)--------->y(n)
|
|
// ^ |
|
|
// | w4 w3 v
|
|
// -----P(2)<--P(1)--
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
// start with adders
|
|
|
|
w[1] = w[0] + w[4];
|
|
|
|
// evaluate in reverse order
|
|
|
|
w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] );
|
|
w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] );
|
|
w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[1] );
|
|
|
|
return w[2];
|
|
}
|
|
case PSET_FEEDBACK4:
|
|
{
|
|
// w0 w1 w2 w5
|
|
// x(n)---(+)-->P(0)-------->P(3)--->y(n)
|
|
// ^ |
|
|
// | w4 w3 v
|
|
// ---P(2)<--P(1)--
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
// start with adders
|
|
|
|
w[1] = w[0] + w[4];
|
|
|
|
// evaluate in reverse order
|
|
|
|
w[5] = pprc[3].pfnGetNext( pprc[3].pdata, w[2] );
|
|
w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] );
|
|
w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] );
|
|
w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[1] );
|
|
|
|
return w[2];
|
|
}
|
|
case PSET_MOD:
|
|
{
|
|
// w0 w1 w3 w4
|
|
// x(n)------>P(1)--P(2)--P(3)--->y(n)
|
|
// w0 w2 ^
|
|
// x(n)------>P(0)....:
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] );
|
|
|
|
w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[1] );
|
|
|
|
// modulate processor 2
|
|
|
|
pprc[2].pfnMod( pprc[2].pdata, ((float)w[2] / (float)PMAX));
|
|
|
|
// get modulator output
|
|
|
|
w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
|
|
|
|
w[1] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] );
|
|
|
|
return w[4];
|
|
}
|
|
case PSET_MOD2:
|
|
{
|
|
// w0 w2
|
|
// x(n)---------P(1)-->y(n)
|
|
// w0 w1 ^
|
|
// x(n)-->P(0)....:
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
// modulate processor 1
|
|
|
|
pprc[1].pfnMod( pprc[1].pdata, ((float)w[1] / (float)PMAX));
|
|
|
|
// get modulator output
|
|
|
|
w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
|
|
|
|
w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] );
|
|
|
|
return w[2];
|
|
|
|
}
|
|
case PSET_MOD3:
|
|
{
|
|
// w0 w2 w3
|
|
// x(n)----------P(1)-->P(2)-->y(n)
|
|
// w0 w1 ^
|
|
// x(n)-->P(0).....:
|
|
|
|
pprc = &ppset->prcs[0];
|
|
|
|
w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[2] );
|
|
|
|
// modulate processor 1
|
|
|
|
pprc[1].pfnMod( pprc[1].pdata, ((float)w[1] / (float)PMAX));
|
|
|
|
// get modulator output
|
|
|
|
w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
|
|
|
|
w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] );
|
|
|
|
return w[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/////////////
|
|
// DSP system
|
|
/////////////
|
|
|
|
// Main interface
|
|
|
|
// Whenever the preset # changes on any of these processors, the old processor is faded out, new is faded in.
|
|
// dsp_chan is optionally set when a sound is played - a preset is sent with the start_static/dynamic sound.
|
|
//
|
|
// sound1---->dsp_chan--> -------------(+)---->dsp_water--->dsp_player--->out
|
|
// sound2---->dsp_chan--> | |
|
|
// sound3---------------> ----dsp_room---
|
|
// | |
|
|
// --dsp_indirect-
|
|
|
|
// dsp_room - set this cvar to a preset # to change the room dsp. room fx are more prevalent farther from player.
|
|
// use: when player moves into a new room, all sounds played in room take on its reverberant character
|
|
// dsp_water - set this cvar (once) to a preset # for serial underwater sound.
|
|
// use: when player goes under water, all sounds pass through this dsp (such as low pass filter)
|
|
// dsp_player - set this cvar to a preset # to cause all sounds to run through the effect (serial, in-line).
|
|
// use: player is deafened, player fires special weapon, player is hit by special weapon.
|
|
// dsp_facingaway- set this cvar to a preset # appropriate for sounds which are played facing away from player (weapon,voice)
|
|
//
|
|
// dsp_spatial - set by system to create modulated spatial delays for left/right/front/back ears - delay value
|
|
// modulates by distance to nearest l/r surface in world
|
|
|
|
// Dsp presets
|
|
|
|
#ifdef PORTAL2
|
|
ConVar dsp_room ("dsp_room", "1", FCVAR_DEMO ); // room dsp preset - sounds more distant from player (1ch)
|
|
#else
|
|
ConVar dsp_room ("dsp_room", "0", FCVAR_DEMO ); // room dsp preset - sounds more distant from player (1ch)
|
|
#endif
|
|
ConVar dsp_water ("dsp_water", "14", FCVAR_DEMO ); // "14" underwater dsp preset - sound when underwater (1-2ch)
|
|
static int dsp_player_value = 0;
|
|
static void dsp_player_changed( IConVar *pConVar, const char *pOldValue, float flOldValue )
|
|
{
|
|
#ifndef DEDICATED
|
|
CClientState &cl = GetBaseLocalClient();
|
|
if ( cl.ishltv || !cl.IsConnected() )
|
|
#endif
|
|
{
|
|
ConVarRef var( pConVar );
|
|
dsp_player_value = var.GetInt();
|
|
}
|
|
// When connected to the server sync up convar value only via server message
|
|
}
|
|
int dsp_player_get()
|
|
{
|
|
return dsp_player_value;
|
|
}
|
|
void dsp_player_set( int val )
|
|
{
|
|
extern ConVar dsp_player;
|
|
dsp_player.SetValue( val );
|
|
dsp_player_value = val;
|
|
}
|
|
ConVar dsp_player ("dsp_player", "0", FCVAR_DEMO | FCVAR_SERVER_CAN_EXECUTE | FCVAR_RELEASE, "", dsp_player_changed ); // dsp on player - sound when player hit by special device (1-2ch)
|
|
ConVar dsp_facingaway ("dsp_facingaway", "0", FCVAR_DEMO ); // "30" sounds that face away from player (weapons, voice) (1-4ch)
|
|
ConVar dsp_speaker ("dsp_speaker", "50", FCVAR_DEMO ); // "50" small distorted speaker sound (1ch)
|
|
ConVar dsp_spatial ("dsp_spatial", "40", FCVAR_DEMO ); // spatial delays for l/r front/rear ears
|
|
ConVar dsp_automatic ("dsp_automatic", "0", FCVAR_DEMO ); // automatic room type detection. if non zero, replaces dsp_room
|
|
|
|
int ipset_room_prev;
|
|
int ipset_water_prev;
|
|
int ipset_player_prev;
|
|
int ipset_facingaway_prev;
|
|
int ipset_speaker_prev;
|
|
int ipset_spatial_prev;
|
|
int ipset_automatic_prev;
|
|
|
|
// legacy room_type support
|
|
|
|
ConVar dsp_room_type ( "room_type", "0", FCVAR_DEMO );
|
|
int ipset_room_typeprev;
|
|
|
|
|
|
// DSP processors
|
|
|
|
int idsp_room;
|
|
int idsp_water;
|
|
int idsp_player;
|
|
int idsp_facingaway;
|
|
int idsp_speaker;
|
|
int idsp_spatial;
|
|
int idsp_automatic;
|
|
|
|
ConVar dsp_off ("dsp_off", "0", FCVAR_CHEAT ); // set to 1 to disable all dsp processing
|
|
ConVar dsp_slow_cpu ("dsp_slow_cpu", "0", FCVAR_CHEAT ); // set to 1 if cpu bound - ie: does not process dsp_room fx
|
|
ConVar snd_profile ("snd_profile", "0", FCVAR_DEMO ); // 1 - profile dsp, 2 - mix, 3 - load sound, 4 - all sound
|
|
ConVar dsp_volume ("dsp_volume", "0.8", FCVAR_CHEAT ); // 0.0 - 2.0; master dsp volume control
|
|
ConVar dsp_vol_5ch ("dsp_vol_5ch", "0.5", FCVAR_DEMO ); // 0.0 - 1.0; attenuate master dsp volume for 5ch surround
|
|
ConVar dsp_vol_4ch ("dsp_vol_4ch", "0.5", FCVAR_DEMO ); // 0.0 - 1.0; attenuate master dsp volume for 4ch surround
|
|
ConVar dsp_vol_2ch ("dsp_vol_2ch", "1.0", FCVAR_DEMO ); // 0.0 - 1.0; attenuate master dsp volume for 2ch surround
|
|
|
|
ConVar dsp_enhance_stereo("dsp_enhance_stereo", "0", FCVAR_ARCHIVE ); // 1) use dsp_spatial delays on all reverb channels
|
|
|
|
// DSP preset executor
|
|
|
|
#if CHECK_VALUES_AFTER_REFACTORING
|
|
#define CDSPS 64
|
|
#else
|
|
#define CDSPS 32 // max number dsp executors active
|
|
#endif
|
|
|
|
#define DSPCHANMAX 5 // max number of channels dsp can process (allocs a separate processor for each channel)
|
|
|
|
struct dsp_t
|
|
{
|
|
bool fused;
|
|
bool bEnabled;
|
|
int cchan; // 1-5 channels, ie: mono, FrontLeft, FrontRight, RearLeft, RearRight, FrontCenter
|
|
|
|
pset_t *ppset[DSPCHANMAX]; // current preset (1-5 channels)
|
|
int ipset; // current ipreset
|
|
|
|
pset_t *ppsetprev[DSPCHANMAX]; // previous preset (1-5 channels)
|
|
int ipsetprev; // previous ipreset
|
|
|
|
float xfade; // crossfade time between previous preset and new
|
|
float xfade_default; // default xfade value, set in DSP_Alloc
|
|
bool bexpfade; // true if exponential crossfade
|
|
|
|
int ipsetsav_oneshot; // previous preset before one-shot preset was set
|
|
|
|
rmp_t xramp; // crossfade ramp
|
|
};
|
|
|
|
dsp_t dsps[CDSPS];
|
|
|
|
void DSP_Init( int idsp )
|
|
{
|
|
dsp_t *pdsp;
|
|
|
|
Assert( idsp < CDSPS );
|
|
|
|
if (idsp < 0 || idsp >= CDSPS)
|
|
return;
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
Q_memset( pdsp, 0, sizeof (dsp_t) );
|
|
}
|
|
|
|
void DSP_Free( int idsp )
|
|
{
|
|
dsp_t *pdsp;
|
|
|
|
Assert( idsp < CDSPS );
|
|
|
|
if (idsp < 0 || idsp >= CDSPS)
|
|
return;
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
for (int i = 0; i < pdsp->cchan; i++)
|
|
{
|
|
if ( pdsp->ppset[i] )
|
|
PSET_Free( pdsp->ppset[i] );
|
|
|
|
if ( pdsp->ppsetprev[i] )
|
|
PSET_Free( pdsp->ppsetprev[i] );
|
|
}
|
|
|
|
Q_memset( pdsp, 0, sizeof (dsp_t) );
|
|
}
|
|
|
|
// Init all dsp processors - called once, during engine startup
|
|
|
|
void DSP_InitAll ( bool bLoadPresetFile )
|
|
{
|
|
// only load template file on engine startup
|
|
|
|
if ( bLoadPresetFile )
|
|
DSP_LoadPresetFile();
|
|
|
|
// order is important, don't rearange.
|
|
|
|
FLT_InitAll();
|
|
DLY_InitAll();
|
|
RVA_InitAll();
|
|
LFOWAV_InitAll();
|
|
LFO_InitAll();
|
|
|
|
CRS_InitAll();
|
|
PTC_InitAll();
|
|
ENV_InitAll();
|
|
EFO_InitAll();
|
|
MDY_InitAll();
|
|
AMP_InitAll();
|
|
|
|
PSET_InitAll();
|
|
|
|
for (int idsp = 0; idsp < CDSPS; idsp++)
|
|
DSP_Init( idsp );
|
|
}
|
|
|
|
// free all resources associated with dsp - called once, during engine shutdown
|
|
|
|
void DSP_FreeAll (void)
|
|
{
|
|
// order is important, don't rearange.
|
|
|
|
for (int idsp = 0; idsp < CDSPS; idsp++)
|
|
DSP_Free( idsp );
|
|
|
|
AMP_FreeAll();
|
|
MDY_FreeAll();
|
|
EFO_FreeAll();
|
|
ENV_FreeAll();
|
|
PTC_FreeAll();
|
|
CRS_FreeAll();
|
|
|
|
LFO_FreeAll();
|
|
LFOWAV_FreeAll();
|
|
RVA_FreeAll();
|
|
DLY_FreeAll();
|
|
FLT_FreeAll();
|
|
}
|
|
|
|
|
|
// allocate a new dsp processor chain, kill the old processor. Called during dsp init only.
|
|
// ipset is new preset
|
|
// xfade is crossfade time when switching between presets (milliseconds)
|
|
// cchan is how many simultaneous preset channels to allocate (1-4)
|
|
// return index to new dsp
|
|
|
|
int DSP_Alloc( int ipset, float xfade, int cchan )
|
|
{
|
|
dsp_t *pdsp;
|
|
int i;
|
|
int idsp;
|
|
int cchans = iclamp( cchan, 1, DSPCHANMAX);
|
|
|
|
// find free slot
|
|
|
|
for ( idsp = 0; idsp < CDSPS; idsp++ )
|
|
{
|
|
if ( !dsps[idsp].fused )
|
|
break;
|
|
}
|
|
|
|
if ( idsp >= CDSPS )
|
|
return -1;
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
DSP_Init ( idsp );
|
|
|
|
pdsp->fused = true;
|
|
pdsp->bEnabled = true;
|
|
|
|
pdsp->cchan = cchans;
|
|
|
|
// allocate a preset processor for each channel
|
|
|
|
pdsp->ipset = ipset;
|
|
pdsp->ipsetprev = 0;
|
|
pdsp->ipsetsav_oneshot = 0;
|
|
|
|
for (i = 0; i < pdsp->cchan; i++)
|
|
{
|
|
pdsp->ppset[i] = ( pdsp->ipset != 0 ) ? PSET_Alloc ( ipset ) : NULL; // Allocate a preset only if it is meaningful
|
|
// This will also remove ambiguities where ipset is zero, but the pointer is not NULL
|
|
pdsp->ppsetprev[i] = NULL;
|
|
}
|
|
|
|
// set up crossfade time in seconds
|
|
|
|
pdsp->xfade = xfade / 1000.0;
|
|
pdsp->xfade_default = pdsp->xfade;
|
|
|
|
RMP_SetEnd(&pdsp->xramp);
|
|
|
|
return idsp;
|
|
}
|
|
|
|
// call modulation function of specified processor within dsp preset
|
|
|
|
// idsp - dsp preset
|
|
// channel - channel 1-5 (l,r,rl,rr,fc)
|
|
// iproc - which processor to change (normally 0)
|
|
// value - new parameter value for processor
|
|
|
|
// NOTE: routine returns with no result or error if any parameter is invalid.
|
|
|
|
void DSP_ChangePresetValue( int idsp, int channel, int iproc, float value )
|
|
{
|
|
|
|
dsp_t *pdsp;
|
|
pset_t *ppset; // preset
|
|
prc_Mod_t pfnMod; // modulation function
|
|
|
|
if (idsp < 0 || idsp >= CDSPS)
|
|
return;
|
|
|
|
if (channel >= DSPCHANMAX)
|
|
return;
|
|
|
|
if (iproc >= CPSET_PRCS)
|
|
return;
|
|
|
|
// get ptr to processor preset
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
// assert that this dsp processor has enough separate channels
|
|
|
|
Assert(channel <= pdsp->cchan);
|
|
|
|
ppset = pdsp->ppset[channel];
|
|
|
|
if (!ppset)
|
|
return;
|
|
|
|
// get ptr to modulation function
|
|
|
|
pfnMod = ppset->prcs[iproc].pfnMod;
|
|
|
|
if (!pfnMod)
|
|
return;
|
|
|
|
// call modulation function with new value
|
|
|
|
pfnMod (ppset->prcs[iproc].pdata, value);
|
|
}
|
|
|
|
void DSP_Print( const dsp_t & dsp, int nIndentation )
|
|
{
|
|
const char * pIndent = GetIndentationText( nIndentation );
|
|
DevMsg( "%sDSP: %p [Addr]\n", pIndent, &dsp );
|
|
DevMsg( "%sfused: %s\n", pIndent, dsp.fused ? "True" : "False" );
|
|
DevMsg( "%sbEnabled: %s\n", pIndent, dsp.bEnabled ? "True" : "False" );
|
|
DevMsg( "%scchan: %d\n", pIndent, dsp.cchan );
|
|
DevMsg( "%sCurrent preset: %d\n", pIndent, dsp.ipset );
|
|
for (int i = 0 ; i < DSPCHANMAX ; ++i )
|
|
{
|
|
pset_t * pPreset = dsp.ppset[i];
|
|
DevMsg( "%sPSET[%d]: ", pIndent, i );
|
|
if ( pPreset == NULL )
|
|
{
|
|
DevMsg( "None\n" );
|
|
continue;
|
|
}
|
|
PSET_Print( *pPreset, nIndentation + 1 );
|
|
}
|
|
DevMsg( "%sPrevious preset: %d\n", pIndent, dsp.ipsetprev );
|
|
for (int i = 0 ; i < DSPCHANMAX ; ++i )
|
|
{
|
|
pset_t * pPreset = dsp.ppsetprev[i];
|
|
DevMsg( "%sPSET[%d]: ", pIndent, i );
|
|
if ( pPreset == NULL )
|
|
{
|
|
DevMsg( "None\n" );
|
|
continue;
|
|
}
|
|
PSET_Print( *pPreset, nIndentation + 1 );
|
|
}
|
|
DevMsg( "%sxfade: %f\n", pIndent, dsp.xfade );
|
|
DevMsg( "%sxfade default: %f\n", pIndent, dsp.xfade_default );
|
|
DevMsg( "%sbexpfade: %s\n", pIndent, dsp.bexpfade ? "True" : "False" );
|
|
|
|
RMP_Print( dsp.xramp, nIndentation + 1 );
|
|
}
|
|
|
|
#define DSP_AUTOMATIC 1 // corresponds to Generic preset
|
|
|
|
// if dsp_room == DSP_AUTOMATIC, then use dsp_automatic value for dsp
|
|
// any subsequent reset of dsp_room will disable automatic room detection.
|
|
|
|
// return true if automatic room detection is enabled
|
|
|
|
bool DSP_CheckDspAutoEnabled( void )
|
|
{
|
|
return (dsp_room.GetInt() == DSP_AUTOMATIC);
|
|
}
|
|
|
|
// set dsp_automatic preset, used in place of dsp_room when automatic room detection enabled
|
|
|
|
void DSP_SetDspAuto( int dsp_preset )
|
|
{
|
|
// set dsp_preset into dsp_automatic
|
|
|
|
dsp_automatic.SetValue( dsp_preset );
|
|
}
|
|
|
|
// wrapper on dsp_room GetInt so that dsp_automatic can override
|
|
|
|
int dsp_room_GetInt ( void )
|
|
{
|
|
// if dsp_automatic is not enabled, get room
|
|
|
|
if (! DSP_CheckDspAutoEnabled())
|
|
return dsp_room.GetInt();
|
|
|
|
// automatic room detection is on, get dsp_automatic instead of dsp_room
|
|
|
|
return dsp_automatic.GetInt();
|
|
}
|
|
|
|
// wrapper on idsp_room preset so that idsp_automatic can override
|
|
|
|
int Get_idsp_room ( void )
|
|
{
|
|
|
|
// if dsp_automatic is not enabled, get room
|
|
|
|
if ( !DSP_CheckDspAutoEnabled())
|
|
return idsp_room;
|
|
|
|
// automatic room detection is on, return dsp_automatic preset instead of dsp_room preset
|
|
|
|
return idsp_automatic;
|
|
}
|
|
|
|
|
|
// free previous
|
|
|
|
inline void DSP_FreePrevPreset( dsp_t *pdsp )
|
|
{
|
|
|
|
Assert( pdsp );
|
|
|
|
bool didFree = false;
|
|
|
|
for (int i = 0; i < pdsp->cchan; i++)
|
|
{
|
|
if ( pdsp->ppsetprev[i] )
|
|
{
|
|
PSET_Free( pdsp->ppsetprev[i] );
|
|
pdsp->ppsetprev[i] = NULL;
|
|
didFree = true;
|
|
}
|
|
}
|
|
|
|
if ( didFree && snd_dsp_spew_changes.GetBool() )
|
|
{
|
|
DevMsg( "[Sound DSP] Free previous preset %d.\n", pdsp->ipsetprev );
|
|
}
|
|
|
|
pdsp->ipsetprev = 0;
|
|
}
|
|
|
|
extern ConVar dsp_mix_min;
|
|
extern ConVar dsp_mix_max;
|
|
extern ConVar dsp_db_min;
|
|
extern ConVar dsp_db_mixdrop;
|
|
|
|
// alloc new preset if different from current
|
|
// xfade from prev to new preset
|
|
// free previous preset, copy current into previous, set up xfade from previous to new
|
|
|
|
void DSP_SetPreset( int idsp, int ipsetnew, const char * pDspName)
|
|
{
|
|
dsp_t *pdsp;
|
|
pset_t *ppsetnew[DSPCHANMAX];
|
|
|
|
Assert (idsp >= 0 && idsp < CDSPS);
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
// validate new preset range
|
|
|
|
if ( ipsetnew >= g_cpsettemplates || ipsetnew < 0 )
|
|
return;
|
|
|
|
// ignore if new preset is same as current preset
|
|
|
|
if ( ipsetnew == pdsp->ipset )
|
|
return;
|
|
|
|
if ( snd_dsp_spew_changes.GetBool() )
|
|
{
|
|
DevMsg( "[Sound DSP] For Dsp %d, %s switch presets from %d to %d.\n", idsp, pDspName, pdsp->ipset, ipsetnew );
|
|
}
|
|
|
|
// alloc new presets (each channel is a duplicate preset)
|
|
|
|
Assert (pdsp->cchan <= DSPCHANMAX);
|
|
|
|
for (int i = 0; i < pdsp->cchan; i++)
|
|
{
|
|
ppsetnew[i] = PSET_Alloc ( ipsetnew );
|
|
if ( !ppsetnew[i] )
|
|
{
|
|
DevMsg("WARNING: DSP preset failed to allocate.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
Assert (pdsp);
|
|
|
|
// free PREVIOUS previous preset if not 0, it will be replaced with a new prev
|
|
DSP_FreePrevPreset( pdsp );
|
|
|
|
for (int i = 0; i < pdsp->cchan; i++)
|
|
{
|
|
pdsp->ppsetprev[i] = pdsp->ppset[i]; // current becomes previous
|
|
pdsp->ppset[i] = ppsetnew[i]; // new becomes current
|
|
}
|
|
|
|
pdsp->ipsetprev = pdsp->ipset;
|
|
pdsp->ipset = ipsetnew;
|
|
|
|
#if 0
|
|
if ( pdsp->ppsetprev )
|
|
{
|
|
uint nCurrentTime = Plat_MSTime();
|
|
if ( nCurrentTime > pdsp->ppsetprev[0]->nLastUpdatedTimeInMilliseconds + snd_dsp_cancel_old_preset_after_N_milliseconds.GetInt() )
|
|
{
|
|
if ( snd_dsp_spew_changes.GetBool() )
|
|
{
|
|
DevMsg( "[Sound DSP] For Dsp %d, %s previous preset %d has not been updated for a while. Do not cross-fade form it.\n", idsp, pDspName, pdsp->ipsetprev );
|
|
}
|
|
// The preset that we are going to cross from is actually quite old.
|
|
// Let's cancel it too, so we can only hear the new preset. This case does not happen often but avoid some old sounds to be played.
|
|
DSP_FreePrevPreset( pdsp );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( idsp == idsp_room || idsp == idsp_automatic )
|
|
{
|
|
// set up new dsp mix min & max, db_min & db_drop params so that new channels get new mix values
|
|
|
|
// NOTE: only new sounds will get the new mix min/max values set in their dspmix param
|
|
// NOTE: so - no crossfade is needed betweeen dspmix and dspmix prev, but this also means
|
|
// NOTE: that currently playing ambients will not see changes to dspmix at all.
|
|
|
|
float mix_min = pdsp->ppset[0]->mix_min;
|
|
float mix_max = pdsp->ppset[0]->mix_max;
|
|
float db_min = pdsp->ppset[0]->db_min;
|
|
float db_mixdrop = pdsp->ppset[0]->db_mixdrop;
|
|
|
|
dsp_mix_min.SetValue( mix_min );
|
|
dsp_mix_max.SetValue( mix_max );
|
|
dsp_db_min.SetValue( db_min );
|
|
dsp_db_mixdrop.SetValue( db_mixdrop );
|
|
}
|
|
|
|
RMP_SetEnd( &pdsp->xramp ); // oliviern: I'm not sure this is necessary as we call RMP_Init afterward
|
|
// Potentially something to remove if not used?
|
|
|
|
// shouldn't be crossfading if current dsp preset == previous dsp preset
|
|
|
|
Assert (pdsp->ipset != pdsp->ipsetprev);
|
|
|
|
// if new preset is one-shot, keep previous preset to restore when one-shot times out
|
|
// but: don't restore previous one-shots!
|
|
|
|
pdsp->ipsetsav_oneshot = 0;
|
|
|
|
if ( PSET_IsOneShot( pdsp->ppset[0] ) && !PSET_IsOneShot( pdsp->ppsetprev[0] ) )
|
|
pdsp->ipsetsav_oneshot = pdsp->ipsetprev;
|
|
|
|
// get new xfade time from previous preset (ie: fade out time). if 0 use default. if < 0, use exponential xfade
|
|
if ( ( pdsp->ppsetprev[0] != NULL) && ( fabs(pdsp->ppsetprev[0]->fade) > 0.0 ) )
|
|
{
|
|
pdsp->xfade = fabs(pdsp->ppsetprev[0]->fade);
|
|
pdsp->bexpfade = pdsp->ppsetprev[0]->fade < 0 ? 1 : 0;
|
|
}
|
|
else
|
|
{
|
|
// no previous preset - use defauts, set in DSP_Alloc
|
|
pdsp->xfade = pdsp->xfade_default;
|
|
pdsp->bexpfade = false;
|
|
}
|
|
|
|
RMP_Init( &(pdsp->xramp), pdsp->xfade, 0, PMAX, false );
|
|
}
|
|
|
|
#define DSP_AUTO_BASE 60 // presets 60-100 in g_psettemplates are reserved as autocreated presets
|
|
#define DSP_CAUTO_PRESETS 40 // must be same as DAS_CNODES!!!
|
|
|
|
// construct a dsp preset based on provided parameters,
|
|
// preset is constructed within g_psettemplates[] array.
|
|
// return preset #
|
|
|
|
// parameter batch
|
|
|
|
struct adsp_auto_params_t
|
|
{
|
|
// passed in params
|
|
|
|
bool bskyabove; // true if sky is mostly above player
|
|
int width; // max width of room in inches
|
|
int length; // max length of room in inches (length always > width)
|
|
int height; // max height of room in inches
|
|
float fdiffusion; // diffusion of room 0..1.0
|
|
float freflectivity; // average reflectivity of all surfaces in room 0..1.0
|
|
float surface_refl[6]; // reflectivity for left,right,front,back,ceiling,floor surfaces 0.0 for open surface (sky or no hit)
|
|
|
|
// derived params
|
|
|
|
int shape; // ADSP_ROOM, etc 0...4
|
|
int size; // ADSP_SIZE_SMALL, etc 0...3
|
|
int len; // ADSP_LENGTH_SHORT, etc 0...3
|
|
int wid; // ADSP_WIDTH_NARROW, etc 0...3
|
|
int ht; // ADSP_HEIGHT_LOW, etc 0...3
|
|
int reflectivity; // ADSP_DULL, etc 0..3
|
|
int diffusion; // ADSP_EMPTY, etc 0...3
|
|
};
|
|
|
|
// room shapes
|
|
|
|
#define ADSP_ROOM 0
|
|
#define ADSP_DUCT 1
|
|
#define ADSP_HALL 2
|
|
#define ADSP_TUNNEL 3
|
|
#define ADSP_STREET 4
|
|
#define ADSP_ALLEY 5
|
|
#define ADSP_COURTYARD 6
|
|
#define ADSP_OPEN_SPACE 7 // NOTE: 7..10 must remain in order !!!
|
|
#define ADSP_OPEN_WALL 8
|
|
#define ADSP_OPEN_STREET 9
|
|
#define ADSP_OPEN_COURTYARD 10
|
|
|
|
// room sizes
|
|
|
|
#define ADSP_SIZE_SMALL 0 // NOTE: must remain 0..4!!!
|
|
#define ADSP_SIZE_MEDIUM 1
|
|
#define ADSP_SIZE_LARGE 2
|
|
#define ADSP_SIZE_HUGE 3
|
|
#define ADSP_SIZE_GIGANTIC 4
|
|
#define ADSP_SIZE_MAX 5
|
|
|
|
#define ADSP_LENGTH_SHORT 0
|
|
#define ADSP_LENGTH_MEDIUM 1
|
|
#define ADSP_LENGTH_LONG 2
|
|
#define ADSP_LENGTH_VLONG 3
|
|
#define ADSP_LENGTH_XLONG 4
|
|
#define ADSP_LENGTH_MAX 5
|
|
|
|
#define ADSP_WIDTH_NARROW 0
|
|
#define ADSP_WIDTH_MEDIUM 1
|
|
#define ADSP_WIDTH_WIDE 2
|
|
#define ADSP_WIDTH_VWIDE 3
|
|
#define ADSP_WIDTH_XWIDE 4
|
|
#define ADSP_WIDTH_MAX 5
|
|
|
|
#define ADSP_HEIGHT_LOW 0
|
|
#define ADSP_HEIGTH_MEDIUM 1
|
|
#define ADSP_HEIGHT_TALL 2
|
|
#define ADSP_HEIGHT_VTALL 3
|
|
#define ADSP_HEIGHT_XTALL 4
|
|
#define ADSP_HEIGHT_MAX 5
|
|
|
|
|
|
// select type 1..5 based on params
|
|
// 1:simple reverb
|
|
// 2:diffusor + reverb
|
|
// 3:diffusor + delay + reverb
|
|
// 4:simple delay
|
|
// 5:diffusor + delay
|
|
|
|
#define AROOM_SMALL (10.0 * 12.0) // small room
|
|
#define AROOM_MEDIUM (20.0 * 12.0) // medium room
|
|
#define AROOM_LARGE (40.0 * 12.0) // large room
|
|
#define AROOM_HUGE (100.0 * 12.0) // huge room
|
|
#define AROOM_GIGANTIC (200.0 * 12.0) // gigantic room
|
|
|
|
#define AROOM_DUCT_WIDTH (4.0 * 12.0) // max width for duct
|
|
#define AROOM_DUCT_HEIGHT (6.0 * 12.0)
|
|
|
|
#define AROOM_HALL_WIDTH (8.0 * 12.0) // max width for hall
|
|
#define AROOM_HALL_HEIGHT (16.0 * 12.0) // max height for hall
|
|
|
|
#define AROOM_TUNNEL_WIDTH (20.0 * 12.0) // max width for tunnel
|
|
#define AROOM_TUNNEL_HEIGHT (30.0 * 12.0) // max height for tunnel
|
|
|
|
#define AROOM_STREET_WIDTH (12.0 * 12.0) // min width for street
|
|
|
|
#define AROOM_SHORT_LENGTH (12.0 * 12.0) // max length for short hall
|
|
#define AROOM_MEDIUM_LENGTH (24.0 * 12.0) // min length for medium hall
|
|
#define AROOM_LONG_LENGTH (48.0 * 12.0) // min length for long hall
|
|
#define AROOM_VLONG_LENGTH (96.0 * 12.0) // min length for very long hall
|
|
#define AROOM_XLONG_LENGTH (192.0 * 12.0) // min length for huge hall
|
|
|
|
#define AROOM_LOW_HEIGHT (4.0 * 12.0) // short ceiling
|
|
#define AROOM_MEDIUM_HEIGHT (128) // medium ceiling
|
|
#define AROOM_TALL_HEIGHT (18.0 * 12.0) // tall ceiling
|
|
#define AROOM_VTALL_HEIGHT (32.0 * 12.0) // very tall ceiling
|
|
#define AROOM_XTALL_HEIGHT (64.0 * 12.0) // huge tall ceiling
|
|
|
|
#define AROOM_NARROW_WIDTH (6.0 * 12.0) // narrow width
|
|
#define AROOM_MEDIUM_WIDTH (12.0 * 12.0) // medium width
|
|
#define AROOM_WIDE_WIDTH (24.0 * 12.0) // wide width
|
|
#define AROOM_VWIDE_WIDTH (48.0 * 12.0) // very wide
|
|
#define AROOM_XWIDE_WIDTH (96.0 * 12.0) // huge width
|
|
|
|
#define BETWEEN(a,b,c) ( ((a) > (b)) && ((a) <= (c)) )
|
|
|
|
#define ADSP_IsShaft(pa) (pa->height > (3.0 * pa->length))
|
|
#define ADSP_IsRoom(pa) (pa->length <= (2.5 * pa->width))
|
|
#define ADSP_IsHall(pa) ((pa->length > (2.5 * pa->width)) && (BETWEEN(pa->width, AROOM_DUCT_WIDTH, AROOM_HALL_WIDTH)))
|
|
#define ADSP_IsTunnel(pa) ((pa->length > (4.0 * pa->width)) && (pa->width > AROOM_HALL_WIDTH))
|
|
#define ADSP_IsDuct(pa) ((pa->length > (4.0 * pa->width)) && (pa->width <= AROOM_DUCT_WIDTH))
|
|
|
|
#define ADSP_IsCourtyard(pa) (pa->length <= (2.5 * pa->width))
|
|
#define ADSP_IsAlley(pa) ((pa->length > (2.5 * pa->width)) && (pa->width <= AROOM_STREET_WIDTH))
|
|
#define ADSP_IsStreet(pa) ((pa->length > (2.5 * pa->width)) && (pa->width > AROOM_STREET_WIDTH))
|
|
|
|
#define ADSP_IsSmallRoom(pa) (pa->length <= AROOM_SMALL)
|
|
#define ADSP_IsMediumRoom(pa) ((BETWEEN(pa->length, AROOM_SMALL, AROOM_MEDIUM)) ) // && (BETWEEN(pa->width, AROOM_SMALL, AROOM_MEDIUM)))
|
|
#define ADSP_IsLargeRoom(pa) (BETWEEN(pa->length, AROOM_MEDIUM, AROOM_LARGE) ) // && BETWEEN(pa->width, AROOM_MEDIUM, AROOM_LARGE))
|
|
#define ADSP_IsHugeRoom(pa) (BETWEEN(pa->length, AROOM_LARGE, AROOM_HUGE) ) // && BETWEEN(pa->width, AROOM_LARGE, AROOM_HUGE))
|
|
#define ADSP_IsGiganticRoom(pa) ((pa->length > AROOM_HUGE) ) // && (pa->width > AROOM_HUGE))
|
|
|
|
#define ADSP_IsShortLength(pa) (pa->length <= AROOM_SHORT_LENGTH)
|
|
#define ADSP_IsMediumLength(pa) (BETWEEN(pa->length, AROOM_SHORT_LENGTH, AROOM_MEDIUM_LENGTH))
|
|
#define ADSP_IsLongLength(pa) (BETWEEN(pa->length, AROOM_MEDIUM_LENGTH, AROOM_LONG_LENGTH))
|
|
#define ADSP_IsVLongLength(pa) (BETWEEN(pa->length, AROOM_LONG_LENGTH, AROOM_VLONG_LENGTH))
|
|
#define ADSP_IsXLongLength(pa) (pa->length > AROOM_VLONG_LENGTH)
|
|
|
|
#define ADSP_IsLowHeight(pa) (pa->height <= AROOM_LOW_HEIGHT)
|
|
#define ADSP_IsMediumHeight(pa) (BETWEEN(pa->height, AROOM_LOW_HEIGHT, AROOM_MEDIUM_HEIGHT))
|
|
#define ADSP_IsTallHeight(pa) (BETWEEN(pa->height, AROOM_MEDIUM_HEIGHT, AROOM_TALL_HEIGHT))
|
|
#define ADSP_IsVTallHeight(pa) (BETWEEN(pa->height, AROOM_TALL_HEIGHT, AROOM_VTALL_HEIGHT))
|
|
#define ADSP_IsXTallHeight(pa) (pa->height > AROOM_VTALL_HEIGHT)
|
|
|
|
#define ADSP_IsNarrowWidth(pa) (pa->width <= AROOM_NARROW_WIDTH)
|
|
#define ADSP_IsMediumWidth(pa) (BETWEEN(pa->width, AROOM_NARROW_WIDTH, AROOM_MEDIUM_WIDTH))
|
|
#define ADSP_IsWideWidth(pa) (BETWEEN(pa->width, AROOM_MEDIUM_WIDTH, AROOM_WIDE_WIDTH))
|
|
#define ADSP_IsVWideWidth(pa) (BETWEEN(pa->width, AROOM_WIDE_WIDTH, AROOM_VWIDE_WIDTH))
|
|
#define ADSP_IsXWideWidth(pa) (pa->width > AROOM_VWIDE_WIDTH)
|
|
|
|
#define ADSP_IsInside(pa) (!(pa->bskyabove))
|
|
|
|
// room diffusion
|
|
|
|
#define ADSP_EMPTY 0
|
|
#define ADSP_SPARSE 1
|
|
#define ADSP_CLUTTERED 2
|
|
#define ADSP_FULL 3
|
|
#define ADSP_DIFFUSION_MAX 4
|
|
|
|
#define AROOM_DIF_EMPTY 0.01 // 1% of space by volume is other objects
|
|
#define AROOM_DIF_SPARSE 0.1 // 10% "
|
|
#define AROOM_DIF_CLUTTERED 0.3 // 30% "
|
|
#define AROOM_DIF_FULL 0.5 // 50% "
|
|
|
|
#define ADSP_IsEmpty(pa) (pa->fdiffusion <= AROOM_DIF_EMPTY)
|
|
#define ADSP_IsSparse(pa) (BETWEEN(pa->fdiffusion, AROOM_DIF_EMPTY, AROOM_DIF_SPARSE))
|
|
#define ADSP_IsCluttered(pa) (BETWEEN(pa->fdiffusion, AROOM_DIF_SPARSE, AROOM_DIF_CLUTTERED))
|
|
#define ADSP_IsFull(pa) (pa->fdiffusion > AROOM_DIF_CLUTTERED)
|
|
|
|
#define ADSP_IsDiffuse(pa) (pa->diffusion > ADSP_SPARSE)
|
|
|
|
// room acoustic reflectivity
|
|
|
|
// tile 0.3 * 3.3 = 0.99
|
|
// metal 0.25 * 3.3 = 0.83
|
|
// concrete,rock,brick,glass,gravel 0.2 * 3.3 = 0.66
|
|
// metal panel/vent, wood, water 0.1 * 3.3 = 0.33
|
|
// carpet,sand,snow,dirt 0.01 * 3.3 = 0.03
|
|
|
|
#define ADSP_DULL 0
|
|
#define ADSP_FLAT 1
|
|
#define ADSP_REFLECTIVE 2
|
|
#define ADSP_BRIGHT 3
|
|
#define ADSP_REFLECTIVITY_MAX 4
|
|
|
|
#define AROOM_REF_DULL 0.04
|
|
#define AROOM_REF_FLAT 0.50
|
|
#define AROOM_REF_REFLECTIVE 0.80
|
|
#define AROOM_REF_BRIGHT 0.99
|
|
|
|
#define ADSP_IsDull(pa) (pa->freflectivity <= AROOM_REF_DULL)
|
|
#define ADSP_IsFlat(pa) (BETWEEN(pa->freflectivity, AROOM_REF_DULL, AROOM_REF_FLAT))
|
|
#define ADSP_IsReflective(pa) (BETWEEN(pa->freflectivity, AROOM_REF_FLAT, AROOM_REF_REFLECTIVE))
|
|
#define ADSP_IsBright(pa) (pa->freflectivity > AROOM_REF_REFLECTIVE)
|
|
|
|
#define ADSP_IsRefl(pa) (pa->reflectivity > ADSP_FLAT)
|
|
|
|
|
|
// convert numeric size params to #defined size params
|
|
|
|
void ADSP_GetSize( adsp_auto_params_t *pa )
|
|
{
|
|
pa->size = ((ADSP_IsSmallRoom(pa) ? 1 : 0) * ADSP_SIZE_SMALL) +
|
|
((ADSP_IsMediumRoom(pa) ? 1 : 0) * ADSP_SIZE_MEDIUM) +
|
|
((ADSP_IsLargeRoom(pa) ? 1 : 0) * ADSP_SIZE_LARGE) +
|
|
((ADSP_IsHugeRoom(pa) ? 1 : 0) * ADSP_SIZE_HUGE) +
|
|
((ADSP_IsGiganticRoom(pa) ? 1 : 0) * ADSP_SIZE_GIGANTIC);
|
|
|
|
pa->len = ((ADSP_IsShortLength(pa) ? 1 : 0) * ADSP_LENGTH_SHORT) +
|
|
((ADSP_IsMediumLength(pa) ? 1 : 0) * ADSP_LENGTH_MEDIUM) +
|
|
((ADSP_IsLongLength(pa) ? 1 : 0) * ADSP_LENGTH_LONG) +
|
|
((ADSP_IsVLongLength(pa) ? 1 : 0) * ADSP_LENGTH_VLONG) +
|
|
((ADSP_IsXLongLength(pa) ? 1 : 0) * ADSP_LENGTH_XLONG);
|
|
|
|
pa->wid = ((ADSP_IsNarrowWidth(pa) ? 1 : 0) * ADSP_WIDTH_NARROW) +
|
|
((ADSP_IsMediumWidth(pa) ? 1 : 0) * ADSP_WIDTH_MEDIUM) +
|
|
((ADSP_IsWideWidth(pa) ? 1 : 0) * ADSP_WIDTH_WIDE) +
|
|
((ADSP_IsVWideWidth(pa) ? 1 : 0) * ADSP_WIDTH_VWIDE) +
|
|
((ADSP_IsXWideWidth(pa) ? 1 : 0) * ADSP_WIDTH_XWIDE);
|
|
|
|
pa->ht = ((ADSP_IsLowHeight(pa) ? 1 : 0) * ADSP_HEIGHT_LOW) +
|
|
((ADSP_IsMediumHeight(pa) ? 1 : 0) * ADSP_HEIGTH_MEDIUM) +
|
|
((ADSP_IsTallHeight(pa) ? 1 : 0) * ADSP_HEIGHT_TALL) +
|
|
((ADSP_IsVTallHeight(pa) ? 1 : 0) * ADSP_HEIGHT_VTALL) +
|
|
((ADSP_IsXTallHeight(pa) ? 1 : 0) * ADSP_HEIGHT_XTALL);
|
|
|
|
pa->reflectivity =
|
|
((ADSP_IsDull(pa) ? 1 : 0) * ADSP_DULL) +
|
|
((ADSP_IsFlat(pa) ? 1 : 0) * ADSP_FLAT) +
|
|
((ADSP_IsReflective(pa) ? 1 : 0) * ADSP_REFLECTIVE) +
|
|
((ADSP_IsBright(pa) ? 1 : 0) * ADSP_BRIGHT);
|
|
|
|
pa->diffusion =
|
|
((ADSP_IsEmpty(pa) ? 1 : 0) * ADSP_EMPTY) +
|
|
((ADSP_IsSparse(pa) ? 1 : 0) * ADSP_SPARSE) +
|
|
((ADSP_IsCluttered(pa) ? 1 : 0) * ADSP_CLUTTERED) +
|
|
((ADSP_IsFull(pa) ? 1 : 0) * ADSP_FULL);
|
|
|
|
Assert(pa->size < ADSP_SIZE_MAX);
|
|
Assert(pa->len < ADSP_LENGTH_MAX);
|
|
Assert(pa->wid < ADSP_WIDTH_MAX);
|
|
Assert(pa->ht < ADSP_HEIGHT_MAX);
|
|
Assert(pa->reflectivity < ADSP_REFLECTIVITY_MAX);
|
|
Assert(pa->diffusion < ADSP_DIFFUSION_MAX);
|
|
|
|
if ( pa->shape != ADSP_COURTYARD && pa->shape != ADSP_OPEN_COURTYARD )
|
|
{
|
|
// fix up size for streets, alleys, halls, ducts, tunnelsy
|
|
|
|
if (pa->shape == ADSP_STREET || pa->shape == ADSP_ALLEY )
|
|
pa->size = pa->wid;
|
|
else
|
|
pa->size = (pa->len + pa->wid) / 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void ADSP_GetOutsideSize( adsp_auto_params_t *pa )
|
|
{
|
|
ADSP_GetSize( pa );
|
|
}
|
|
|
|
// return # of sides that had max length or sky hits (out of 6 sides).
|
|
|
|
int ADSP_COpenSides( adsp_auto_params_t *pa )
|
|
{
|
|
int count = 0;
|
|
|
|
// only look at left,right,front,back walls - ignore floor, ceiling
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (pa->surface_refl[i] == 0.0)
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
// given auto params, return shape and size of room
|
|
|
|
void ADSP_GetAutoShape( adsp_auto_params_t *pa )
|
|
{
|
|
|
|
// INSIDE:
|
|
// shapes: duct, hall, tunnel, shaft (vertical duct, hall or tunnel)
|
|
// sizes: short->long, narrow->wide, low->tall
|
|
// shapes: room
|
|
// sizes: small->large, low->tall
|
|
|
|
// OUTSIDE:
|
|
// shapes: street, alley
|
|
// sizes: short->long, narrow->wide
|
|
// shapes: courtyard
|
|
// sizes: small->large
|
|
|
|
// shapes: open_space, wall, open_street, open_corner, open_courtyard
|
|
// sizes: open, narrow->wide
|
|
|
|
bool bshaft = false;
|
|
int t;
|
|
|
|
if (ADSP_IsInside(pa))
|
|
{
|
|
if (ADSP_IsShaft(pa))
|
|
{
|
|
// temp swap height and length
|
|
|
|
bshaft = true;
|
|
t = pa->height;
|
|
pa->height = pa->length;
|
|
pa->length = t;
|
|
if (das_debug.GetInt() > 1)
|
|
DevMsg("VERTICAL SHAFT Detected \n");
|
|
}
|
|
|
|
// get shape
|
|
|
|
if (ADSP_IsDuct(pa))
|
|
{
|
|
pa->shape = ADSP_DUCT;
|
|
ADSP_GetSize( pa );
|
|
if (das_debug.GetInt() > 1)
|
|
DevMsg("DUCT Detected \n");
|
|
goto autoshape_exit;
|
|
}
|
|
|
|
if (ADSP_IsHall(pa))
|
|
{
|
|
// get size
|
|
pa->shape = ADSP_HALL;
|
|
ADSP_GetSize( pa );
|
|
|
|
if (das_debug.GetInt() > 1)
|
|
DevMsg("HALL Detected \n");
|
|
|
|
goto autoshape_exit;
|
|
}
|
|
|
|
if (ADSP_IsTunnel(pa))
|
|
{
|
|
// get size
|
|
pa->shape = ADSP_TUNNEL;
|
|
ADSP_GetSize( pa );
|
|
|
|
if (das_debug.GetInt() > 1)
|
|
DevMsg("TUNNEL Detected \n");
|
|
|
|
goto autoshape_exit;
|
|
}
|
|
|
|
// default
|
|
// (ADSP_IsRoom(pa))
|
|
{
|
|
// get size
|
|
pa->shape = ADSP_ROOM;
|
|
ADSP_GetSize( pa );
|
|
|
|
if (das_debug.GetInt() > 1)
|
|
DevMsg("ROOM Detected \n");
|
|
|
|
goto autoshape_exit;
|
|
}
|
|
}
|
|
|
|
// outside:
|
|
|
|
if (ADSP_COpenSides(pa) > 0) // side hit sky, or side has max length
|
|
{
|
|
// get shape - courtyard, street, wall or open space
|
|
// 10..7
|
|
pa->shape = ADSP_OPEN_COURTYARD - (ADSP_COpenSides(pa) - 1);
|
|
ADSP_GetOutsideSize( pa );
|
|
|
|
if (das_debug.GetInt() > 1)
|
|
DevMsg("OPEN SIDED OUTDOOR AREA Detected \n");
|
|
|
|
goto autoshape_exit;
|
|
}
|
|
|
|
// all sides closed:
|
|
|
|
// get shape - closed street or alley or courtyard
|
|
|
|
if (ADSP_IsCourtyard(pa))
|
|
{
|
|
pa->shape = ADSP_COURTYARD;
|
|
ADSP_GetOutsideSize( pa );
|
|
|
|
if (das_debug.GetInt() > 1)
|
|
DevMsg("OUTSIDE COURTYARD Detected \n");
|
|
|
|
goto autoshape_exit;
|
|
}
|
|
|
|
if (ADSP_IsAlley(pa))
|
|
{
|
|
pa->shape = ADSP_ALLEY;
|
|
ADSP_GetOutsideSize( pa );
|
|
|
|
if (das_debug.GetInt() > 1)
|
|
DevMsg("OUTSIDE ALLEY Detected \n");
|
|
goto autoshape_exit;
|
|
}
|
|
|
|
// default to 'street' if sides are closed
|
|
|
|
// if (ADSP_IsStreet(pa))
|
|
{
|
|
pa->shape = ADSP_STREET;
|
|
ADSP_GetOutsideSize( pa );
|
|
if (das_debug.GetInt() > 1)
|
|
DevMsg("OUTSIDE STREET Detected \n");
|
|
goto autoshape_exit;
|
|
}
|
|
|
|
autoshape_exit:
|
|
|
|
// swap height & length if needed
|
|
|
|
if (bshaft)
|
|
{
|
|
t = pa->height;
|
|
pa->height = pa->length;
|
|
pa->length = t;
|
|
}
|
|
}
|
|
|
|
int MapReflectivityToDLYCutoff[] =
|
|
{
|
|
1000, // DULL
|
|
2000, // FLAT
|
|
4000, // REFLECTIVE
|
|
6000 // BRIGHT
|
|
};
|
|
|
|
float MapSizeToDLYFeedback[] =
|
|
{
|
|
0.9, // 0.6, // SMALL
|
|
0.8, // 0.5, // MEDIUM
|
|
0.7, // 0.4, // LARGE
|
|
0.6, // 0.3, // HUGE
|
|
0.5, // 0.2, // GIGANTIC
|
|
};
|
|
|
|
void ADSP_SetupAutoDelay( prc_t *pprc_dly, adsp_auto_params_t *pa )
|
|
{
|
|
// shapes:
|
|
// inside: duct, long hall, long tunnel, large room
|
|
// outside: open courtyard, street wall, space
|
|
// outside: closed courtyard, alley, street
|
|
|
|
// size 0..4
|
|
// len 0..3
|
|
// wid 0..3
|
|
// reflectivity: 0..3
|
|
// diffusion 0..3
|
|
|
|
// dtype: delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS
|
|
// delay: delay in milliseconds (room max size in feet)
|
|
// feedback: feedback 0-1.0
|
|
// gain: final gain of output stage, 0-1.0
|
|
|
|
int size = pa->length * 2.0;
|
|
|
|
if (pa->shape == ADSP_ALLEY || pa->shape == ADSP_STREET || pa->shape == ADSP_OPEN_STREET)
|
|
size = pa->width * 2.0;
|
|
|
|
pprc_dly->type = PRC_DLY;
|
|
|
|
pprc_dly->prm[dly_idtype] = DLY_LOWPASS; // delay with feedback
|
|
|
|
pprc_dly->prm[dly_idelay] = clamp((size / 12.0), 5.0, 500.0);
|
|
|
|
pprc_dly->prm[dly_ifeedback] = MapSizeToDLYFeedback[pa->len];
|
|
|
|
// reduce gain based on distance reflection travels
|
|
// float g = 1.0 - ( clamp(pprc_dly->prm[dly_idelay], 10.0, 1000.0) / (1000.0 - 10.0) );
|
|
// pprc_dly->prm[dly_igain] = g;
|
|
|
|
pprc_dly->prm[dly_iftype] = FLT_LP;
|
|
if (ADSP_IsInside(pa))
|
|
pprc_dly->prm[dly_icutoff] = MapReflectivityToDLYCutoff[pa->reflectivity];
|
|
else
|
|
pprc_dly->prm[dly_icutoff] = (int)((float)(MapReflectivityToDLYCutoff[pa->reflectivity]) * 0.75);
|
|
|
|
pprc_dly->prm[dly_iqwidth] = 0;
|
|
|
|
pprc_dly->prm[dly_iquality] = QUA_LO;
|
|
|
|
float l = clamp((pa->length * 2.0 / 12.0), 14.0, 500.0);
|
|
float w = clamp((pa->width * 2.0 / 12.0), 14.0, 500.0);
|
|
|
|
// convert to multitap delay
|
|
|
|
pprc_dly->prm[dly_idtype] = DLY_LOWPASS_4TAP;
|
|
|
|
pprc_dly->prm[dly_idelay] = l;
|
|
pprc_dly->prm[dly_itap1] = w;
|
|
pprc_dly->prm[dly_itap2] = l; // max(7, l * 0.7 );
|
|
pprc_dly->prm[dly_itap3] = l; // max(7, w * 0.7 );
|
|
|
|
pprc_dly->prm[dly_igain] = 1.0;
|
|
}
|
|
|
|
int MapReflectivityToRVACutoff[] =
|
|
{
|
|
1000, // DULL
|
|
2000, // FLAT
|
|
4000, // REFLECTIVE
|
|
6000 // BRIGHT
|
|
};
|
|
|
|
float MapSizeToRVANumDelays[] =
|
|
{
|
|
3, // SMALL 3 reverbs
|
|
6, // MEDIUM 6 reverbs
|
|
6, // LARGE 6 reverbs
|
|
9, // HUGE 9 reverbs
|
|
12, // GIGANTIC 12 reverbs
|
|
};
|
|
|
|
float MapSizeToRVAFeedback[] =
|
|
{
|
|
0.75, // SMALL
|
|
0.8, // MEDIUM
|
|
0.9, // LARGE
|
|
0.95, // HUGE
|
|
0.98, // GIGANTIC
|
|
};
|
|
|
|
void ADSP_SetupAutoReverb( prc_t *pprc_rva, adsp_auto_params_t *pa )
|
|
{
|
|
// shape: hall, tunnel or room
|
|
// size 0..4
|
|
// reflectivity: 0..3
|
|
// diffusion 0..3
|
|
|
|
// size: 0-2.0 scales nominal delay parameters (18 to 47 ms * scale = delay)
|
|
// numdelays: 0-12 controls # of parallel or series delays
|
|
// decay: 0-2.0 scales feedback parameters (.7 to .9 * scale/2.0 = feedback)
|
|
// fparallel: if true, filters are built into delays, otherwise filter output only
|
|
// fmoddly: if true, all delays are modulating delays
|
|
float gain = 1.0;
|
|
|
|
pprc_rva->type = PRC_RVA;
|
|
|
|
pprc_rva->prm[rva_size_max] = 50.0;
|
|
pprc_rva->prm[rva_size_min] = 30.0;
|
|
|
|
if (ADSP_IsRoom(pa))
|
|
pprc_rva->prm[rva_inumdelays] = MapSizeToRVANumDelays[pa->size];
|
|
else
|
|
pprc_rva->prm[rva_inumdelays] = MapSizeToRVANumDelays[pa->len];
|
|
|
|
pprc_rva->prm[rva_ifeedback] = 0.9;
|
|
|
|
pprc_rva->prm[rva_icutoff] = MapReflectivityToRVACutoff[pa->reflectivity];
|
|
|
|
pprc_rva->prm[rva_ifparallel] = 1;
|
|
pprc_rva->prm[rva_imoddly] = ADSP_IsEmpty(pa) ? 0 : 4;
|
|
pprc_rva->prm[rva_imodrate] = 3.48;
|
|
|
|
pprc_rva->prm[rva_iftaps] = 0; // 0.1 // use extra delay taps to increase density
|
|
|
|
pprc_rva->prm[rva_width] = clamp( ((float)(pa->width) / 12.0), 6.0, 500.0); // in feet
|
|
pprc_rva->prm[rva_depth] = clamp( ((float)(pa->length) / 12.0), 6.0, 500.0);
|
|
pprc_rva->prm[rva_height] = clamp( ((float)(pa->height) / 12.0), 6.0, 500.0);
|
|
|
|
// room
|
|
pprc_rva->prm[rva_fbwidth] = 0.9; // MapSizeToRVAFeedback[pa->size]; // larger size = more feedback
|
|
pprc_rva->prm[rva_fbdepth] = 0.9; // MapSizeToRVAFeedback[pa->size];
|
|
pprc_rva->prm[rva_fbheight] = 0.5; // MapSizeToRVAFeedback[pa->size];
|
|
|
|
// feedback is based on size of room:
|
|
|
|
if (ADSP_IsInside(pa))
|
|
{
|
|
if (pa->shape == ADSP_HALL)
|
|
{
|
|
pprc_rva->prm[rva_fbwidth] = 0.7; //MapSizeToRVAFeedback[pa->wid];
|
|
pprc_rva->prm[rva_fbdepth] = -0.5; //MapSizeToRVAFeedback[pa->len];
|
|
pprc_rva->prm[rva_fbheight] = 0.3; //MapSizeToRVAFeedback[pa->ht];
|
|
}
|
|
|
|
if (pa->shape == ADSP_TUNNEL)
|
|
{
|
|
pprc_rva->prm[rva_fbwidth] = 0.9;
|
|
pprc_rva->prm[rva_fbdepth] = -0.8; // fixed pre-delay, no feedback
|
|
pprc_rva->prm[rva_fbheight] = 0.3;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pa->shape == ADSP_ALLEY)
|
|
{
|
|
pprc_rva->prm[rva_fbwidth] = 0.9;
|
|
pprc_rva->prm[rva_fbdepth] = -0.8; // fixed pre-delay, no feedback
|
|
pprc_rva->prm[rva_fbheight] = 0.0;
|
|
}
|
|
}
|
|
|
|
if (!ADSP_IsInside(pa))
|
|
pprc_rva->prm[rva_fbheight] = 0.0;
|
|
|
|
pprc_rva->prm[rva_igain] = gain;
|
|
}
|
|
|
|
// diffusor templates for auto create
|
|
|
|
// size: 0-1.0 scales all delays (13ms to 41ms * scale = delay)
|
|
// numdelays: 0-4.0 controls # of series delays
|
|
// decay: 0-1.0 scales all feedback parameters
|
|
|
|
// prctype size #dly feedback
|
|
|
|
#define PRC_DFRA_S {PRC_DFR, {0.5, 2, 0.10}, NULL,NULL,NULL,NULL,NULL} // S room
|
|
#define PRC_DFRA_M {PRC_DFR, {0.75, 2, 0.12}, NULL,NULL,NULL,NULL,NULL} // M room
|
|
#define PRC_DFRA_L {PRC_DFR, {1.0, 3, 0.13}, NULL,NULL,NULL,NULL,NULL} // L room
|
|
#define PRC_DFRA_VL {PRC_DFR, {1.0, 3, 0.15}, NULL,NULL,NULL,NULL,NULL} // VL room
|
|
|
|
prc_t g_prc_dfr_auto[] = {PRC_DFRA_S, PRC_DFRA_M, PRC_DFRA_L, PRC_DFRA_VL, PRC_DFRA_VL};
|
|
|
|
#define CDFRTEMPLATES (sizeof(g_prc_dfr_auto)/sizeof(pset_t)) // number of diffusor templates
|
|
|
|
// copy diffusor template from preset list, based on room size
|
|
|
|
void ADSP_SetupAutoDiffusor( prc_t *pprc_dfr, adsp_auto_params_t *pa )
|
|
{
|
|
int i = iclamp(pa->size, 0, (int)CDFRTEMPLATES - 1);
|
|
|
|
// copy diffusor preset based on size
|
|
|
|
*pprc_dfr = g_prc_dfr_auto[i];
|
|
}
|
|
|
|
// return index to processor given processor type and preset
|
|
// skips N processors of similar type
|
|
// returns -1 if type not found
|
|
|
|
int ADSP_FindProc( pset_t *ppset, int proc_type, int skip )
|
|
{
|
|
int skipcount = skip;
|
|
|
|
for (int i = 0; i < ppset->cprcs; i++)
|
|
{
|
|
// look for match on processor type
|
|
|
|
if ( ppset->prcs[i].type == proc_type )
|
|
{
|
|
// skip first N procs of similar type,
|
|
|
|
// return index to processor
|
|
|
|
if (!skipcount)
|
|
return i;
|
|
|
|
skipcount--;
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// interpolate parameter:
|
|
// pnew - target preset
|
|
// pmin - preset with parameter with min value
|
|
// pmax - preset with parameter with max value
|
|
// proc_type - type of processor to look for ie: PRC_RVA or PRC_DLY
|
|
// skipprocs - skip n processors of type
|
|
// iparam - which parameter within processor to interpolate
|
|
// index -
|
|
// index_max: use index/index_max as interpolater between pmin param and pmax param
|
|
// if bexp is true, interpolate exponentially as (index/index_max)^2
|
|
|
|
// NOTE: returns with no result if processor type is not found in all presets.
|
|
|
|
void ADSP_InterpParam( pset_t *pnew, pset_t *pmin, pset_t *pmax, int proc_type, int skipprocs, int iparam, int index, int index_max, bool bexp, float flScale = 1.0 )
|
|
{
|
|
// find processor index in pnew
|
|
int iproc_new = ADSP_FindProc( pnew, proc_type, skipprocs);
|
|
int iproc_min = ADSP_FindProc( pmin, proc_type, skipprocs);
|
|
int iproc_max = ADSP_FindProc( pmax, proc_type, skipprocs);
|
|
|
|
// make sure processor type found in all presets
|
|
|
|
if ( iproc_new < 0 || iproc_min < 0 || iproc_max < 0 )
|
|
return;
|
|
|
|
float findex = (float)index/(float)index_max;
|
|
float vmin = pmin->prcs[iproc_min].prm[iparam];
|
|
float vmax = pmax->prcs[iproc_max].prm[iparam];
|
|
float vinterp;
|
|
|
|
// interpolate
|
|
|
|
if (!bexp)
|
|
vinterp = vmin + (vmax - vmin) * findex;
|
|
else
|
|
vinterp = vmin + (vmax - vmin) * findex * findex;
|
|
|
|
pnew->prcs[iproc_new].prm[iparam] = vinterp * flScale;
|
|
|
|
return;
|
|
}
|
|
|
|
// directly set parameter
|
|
|
|
void ADSP_SetParam( pset_t *pnew, int proc_type, int skipprocs, int iparam, float value )
|
|
{
|
|
int iproc_new = ADSP_FindProc( pnew, proc_type, skipprocs);
|
|
|
|
if (iproc_new >= 0)
|
|
pnew->prcs[iproc_new].prm[iparam] = value;
|
|
}
|
|
|
|
// directly set parameter if min or max is negative
|
|
|
|
void ADSP_SetParamIfNegative( pset_t *pnew, pset_t *pmin, pset_t *pmax, int proc_type, int skipprocs, int iparam, int index, int index_max, bool bexp, float value )
|
|
{
|
|
// find processor index in pnew
|
|
int iproc_new = ADSP_FindProc( pnew, proc_type, skipprocs);
|
|
int iproc_min = ADSP_FindProc( pmin, proc_type, skipprocs);
|
|
int iproc_max = ADSP_FindProc( pmax, proc_type, skipprocs);
|
|
|
|
// make sure processor type found in all presets
|
|
|
|
if ( iproc_new < 0 || iproc_min < 0 || iproc_max < 0 )
|
|
return;
|
|
|
|
float vmin = pmin->prcs[iproc_min].prm[iparam];
|
|
float vmax = pmax->prcs[iproc_max].prm[iparam];
|
|
|
|
if ( vmin < 0.0 || vmax < 0.0 )
|
|
ADSP_SetParam( pnew, proc_type, skipprocs, iparam, value );
|
|
else
|
|
ADSP_InterpParam( pnew, pmin, pmax, proc_type, skipprocs, iparam, index, index_max, bexp);
|
|
|
|
return;
|
|
}
|
|
|
|
// given min and max preset and auto parameters, create new preset
|
|
// NOTE: the # and type of processors making up pmin and pmax presets must be identical!
|
|
|
|
ConVar adsp_scale_delay_gain("adsp_scale_delay_gain", "0.2", FCVAR_NONE );
|
|
ConVar adsp_scale_delay_feedback("adsp_scale_delay_feedback", "0.2", FCVAR_NONE );
|
|
|
|
void ADSP_InterpolatePreset( pset_t *pnew, pset_t *pmin, pset_t *pmax, adsp_auto_params_t *pa, int iskip )
|
|
{
|
|
int i;
|
|
|
|
// if size > mid size, then copy basic processors from MAX preset,
|
|
// otherwise, copy from MIN preset
|
|
|
|
if ( !iskip )
|
|
{
|
|
// only copy on 1st call
|
|
|
|
if ( pa->size > ADSP_SIZE_MEDIUM )
|
|
{
|
|
*pnew = *pmax;
|
|
}
|
|
else
|
|
{
|
|
*pnew = *pmin;
|
|
}
|
|
}
|
|
|
|
// DFR
|
|
|
|
// interpolate all DFR params on size
|
|
|
|
for (i = 0; i < dfr_cparam; i++)
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_DFR, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
|
|
|
|
// RVA
|
|
|
|
// interpolate size_max, size_min, feedback, #delays, moddly, imodrate, based on ap size
|
|
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_ifeedback, pa->size, ADSP_SIZE_MAX, 0);
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_size_min, pa->size, ADSP_SIZE_MAX, 1);
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_size_max, pa->size, ADSP_SIZE_MAX, 1);
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_igain, pa->size, ADSP_SIZE_MAX, 0);
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_inumdelays, pa->size, ADSP_SIZE_MAX , 0);
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_imoddly, pa->size, ADSP_SIZE_MAX , 0);
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_imodrate, pa->size, ADSP_SIZE_MAX , 0);
|
|
|
|
// interpolate width,depth,height based on ap width length & height - exponential interpolation
|
|
// if pmin or pmax parameters are < 0, directly set value from w/l/h
|
|
|
|
float w = clamp( ((float)(pa->width) / 12.0), 6.0, 500.0); // in feet
|
|
float l = clamp( ((float)(pa->length) / 12.0), 6.0, 500.0);
|
|
float h = clamp( ((float)(pa->height) / 12.0), 6.0, 500.0);
|
|
|
|
ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_RVA, iskip, rva_width, pa->wid, ADSP_WIDTH_MAX, 1, w);
|
|
ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_RVA, iskip, rva_depth, pa->len, ADSP_LENGTH_MAX, 1, l);
|
|
ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_RVA, iskip, rva_height, pa->ht, ADSP_HEIGHT_MAX, 1, h);
|
|
|
|
// interpolate w/d/h feedback based on ap w/d/f
|
|
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_fbwidth, pa->wid, ADSP_WIDTH_MAX , 0);
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_fbdepth, pa->len, ADSP_LENGTH_MAX , 0);
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_fbheight, pa->ht, ADSP_HEIGHT_MAX , 0);
|
|
|
|
// interpolate cutoff based on ap reflectivity
|
|
// NOTE: cutoff goes from max to min! ie: small bright - large dull
|
|
|
|
ADSP_InterpParam( pnew, pmax, pmin, PRC_RVA, iskip, rva_icutoff, pa->reflectivity, ADSP_REFLECTIVITY_MAX , 0);
|
|
|
|
// don't interpolate: fparallel, ftaps
|
|
|
|
// DLY
|
|
|
|
// directly set delay value from pa->length if pmin or pmax value is < 0
|
|
|
|
l = clamp((pa->length * 2.0 / 12.0), 14.0, 500.0);
|
|
w = clamp((pa->width * 2.0 / 12.0), 14.0, 500.0);
|
|
|
|
ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_idelay, pa->len, ADSP_LENGTH_MAX, 1, l);
|
|
|
|
// interpolate feedback, gain, based on max size (length)
|
|
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_DLY, iskip, dly_ifeedback, pa->len, ADSP_LENGTH_MAX , 0, adsp_scale_delay_gain.GetFloat() );
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_DLY, iskip, dly_igain, pa->len, ADSP_LENGTH_MAX , 0, adsp_scale_delay_feedback.GetFloat());
|
|
|
|
// directly set tap value from pa->width if pmin or pmax value is < 0
|
|
|
|
ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_itap1, pa->len, ADSP_LENGTH_MAX, 1, w);
|
|
ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_itap2, pa->len, ADSP_LENGTH_MAX, 1, l);
|
|
ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_itap3, pa->len, ADSP_LENGTH_MAX, 1, l);
|
|
|
|
// interpolate cutoff and qwidth based on reflectivity NOTE: this can affect gain!
|
|
// NOTE: cutoff goes from max to min! ie: small bright - large dull
|
|
|
|
ADSP_InterpParam( pnew, pmax, pmin, PRC_DLY, iskip, dly_icutoff, pa->len, ADSP_LENGTH_MAX , 0);
|
|
ADSP_InterpParam( pnew, pmax, pmin, PRC_DLY, iskip, dly_iqwidth, pa->len, ADSP_LENGTH_MAX , 0);
|
|
|
|
// interpolate all other parameters for all other processor types based on size
|
|
|
|
// PRC_MDY, PRC_AMP, PRC_FLT, PTC, CRS, ENV, EFO, LFO
|
|
|
|
for (i = 0; i < mdy_cparam; i++)
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_MDY, iskip, i, pa->len, ADSP_LENGTH_MAX , 0);
|
|
|
|
for (i = 0; i < amp_cparam; i++)
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_AMP, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
|
|
|
|
for (i = 0; i < flt_cparam; i++)
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_FLT, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
|
|
|
|
for (i = 0; i < ptc_cparam; i++)
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_PTC, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
|
|
|
|
for (i = 0; i < crs_cparam; i++)
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_CRS, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
|
|
|
|
for (i = 0; i < env_cparam; i++)
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_ENV, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
|
|
|
|
for (i = 0; i < efo_cparam; i++)
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_EFO, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
|
|
|
|
for (i = 0; i < lfo_cparam; i++)
|
|
ADSP_InterpParam( pnew, pmin, pmax, PRC_LFO, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
|
|
|
|
}
|
|
|
|
// these convars store the index to the first preset for each shape type in dsp_presets.txt
|
|
|
|
ConVar adsp_room_min ("adsp_room_min", "102");
|
|
ConVar adsp_duct_min ("adsp_duct_min", "106");
|
|
ConVar adsp_hall_min ("adsp_hall_min", "110");
|
|
ConVar adsp_tunnel_min ("adsp_tunnel_min", "114");
|
|
ConVar adsp_street_min ("adsp_street_min", "118");
|
|
ConVar adsp_alley_min ("adsp_alley_min", "122");
|
|
ConVar adsp_courtyard_min ("adsp_courtyard_min", "126");
|
|
ConVar adsp_openspace_min ("adsp_openspace_min", "130");
|
|
ConVar adsp_openwall_min ("adsp_openwall_min", "130");
|
|
ConVar adsp_openstreet_min ("adsp_openstreet_min", "118");
|
|
ConVar adsp_opencourtyard_min ("adsp_opencourtyard_min", "126");
|
|
|
|
// given room parameters, construct and return a dsp preset representing the room.
|
|
// bskyabove, width, length, height, fdiffusion, freflectivity are all passed-in room parameters
|
|
// psurf_refl is a passed-in array of reflectivity values for 6 surfaces
|
|
// inode is the location within g_psettemplates[] that the dsp preset will be constructed (inode = dsp preset#)
|
|
// cnode should always = DSP_CAUTO_PRESETS
|
|
// returns idsp preset.
|
|
|
|
int DSP_ConstructPreset( bool bskyabove, int width, int length, int height, float fdiffusion, float freflectivity, float *psurf_refl, int inode, int cnodes)
|
|
{
|
|
adsp_auto_params_t ap;
|
|
adsp_auto_params_t *pa;
|
|
|
|
pset_t new_pset; // preset
|
|
pset_t pset_min;
|
|
pset_t pset_max;
|
|
|
|
int ipreset;
|
|
int ipset_min;
|
|
int ipset_max;
|
|
|
|
if (inode >= DSP_CAUTO_PRESETS)
|
|
{
|
|
Assert(false); // check DAS_CNODES == DSP_CAUTO_PRESETS!!!
|
|
return 0;
|
|
}
|
|
|
|
// fill parameter struct
|
|
|
|
ap.bskyabove = bskyabove;
|
|
ap.width = width;
|
|
ap.length = length;
|
|
ap.height = height;
|
|
ap.fdiffusion = fdiffusion;
|
|
ap.freflectivity = freflectivity;
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
ap.surface_refl[i] = psurf_refl[i];
|
|
|
|
if (ap.bskyabove)
|
|
ap.surface_refl[4] = 0.0;
|
|
|
|
// select shape, size based on params
|
|
|
|
ADSP_GetAutoShape( &ap );
|
|
|
|
// set up min/max presets based on shape
|
|
|
|
switch ( ap.shape )
|
|
{
|
|
default:
|
|
case ADSP_ROOM: ipset_min = adsp_room_min.GetInt(); break;
|
|
case ADSP_DUCT: ipset_min = adsp_duct_min.GetInt(); break;
|
|
case ADSP_HALL: ipset_min = adsp_hall_min.GetInt(); break;
|
|
case ADSP_TUNNEL: ipset_min = adsp_tunnel_min.GetInt(); break;
|
|
case ADSP_STREET: ipset_min = adsp_street_min.GetInt(); break;
|
|
case ADSP_ALLEY: ipset_min = adsp_alley_min.GetInt(); break;
|
|
case ADSP_COURTYARD: ipset_min = adsp_courtyard_min.GetInt(); break;
|
|
case ADSP_OPEN_SPACE: ipset_min = adsp_openspace_min.GetInt(); break;
|
|
case ADSP_OPEN_WALL: ipset_min = adsp_openwall_min.GetInt(); break;
|
|
case ADSP_OPEN_STREET: ipset_min = adsp_openstreet_min.GetInt(); break;
|
|
case ADSP_OPEN_COURTYARD: ipset_min = adsp_opencourtyard_min.GetInt(); break;
|
|
}
|
|
|
|
// presets in dsp_presets.txt are ordered as:
|
|
|
|
// <shape><empty><min>
|
|
// <shape><empty><max>
|
|
// <shape><diffuse><min>
|
|
// <shape><diffuse><max>
|
|
pa = ≈
|
|
if ( ADSP_IsDiffuse(pa) )
|
|
ipset_min += 2;
|
|
|
|
ipset_max = ipset_min + 1;
|
|
|
|
pset_min = g_psettemplates[ipset_min];
|
|
pset_max = g_psettemplates[ipset_max];
|
|
|
|
if( das_debug.GetInt() )
|
|
{
|
|
DevMsg( "DAS: Min Preset Index: %i\nDAS: Max Preset Index: %i\n", ipset_min, ipset_max );
|
|
}
|
|
|
|
// given min and max preset and auto parameters, create new preset
|
|
|
|
// interpolate between 1st instances of each processor type (ie: PRC_DLY) appearing in preset
|
|
|
|
ADSP_InterpolatePreset( &new_pset, &pset_min, &pset_max, &ap, 0 );
|
|
|
|
// interpolate between 2nd instances of each processor type (ie: PRC_DLY) appearing in preset
|
|
|
|
ADSP_InterpolatePreset( &new_pset, &pset_min, &pset_max, &ap, 1 );
|
|
|
|
// copy constructed preset back into node's template location
|
|
|
|
ipreset = DSP_AUTO_BASE + inode;
|
|
|
|
g_psettemplates[ipreset] = new_pset;
|
|
|
|
return ipreset;
|
|
}
|
|
|
|
///////////////////////////////////////
|
|
// Helpers: called only from DSP_Process
|
|
///////////////////////////////////////
|
|
|
|
|
|
// return true if batch processing version of preset exists
|
|
|
|
inline bool FBatchPreset( pset_t *ppset )
|
|
{
|
|
switch (ppset->type)
|
|
{
|
|
case PSET_LINEAR:
|
|
return true;
|
|
case PSET_SIMPLE:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Helper: called only from DSP_Process
|
|
// mix front stereo buffer to mono buffer, apply dsp fx
|
|
|
|
inline void DSP_ProcessStereoToMono(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading )
|
|
{
|
|
VPROF( "DSP_ProcessStereoToMono" );
|
|
|
|
portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
|
|
int count = sampleCount;
|
|
int av;
|
|
int x;
|
|
|
|
if ( !bcrossfading )
|
|
{
|
|
if ( !pdsp->ipset )
|
|
return;
|
|
|
|
if ( FBatchPreset(pdsp->ppset[0]))
|
|
{
|
|
// convert Stereo to Mono in place, then batch process fx: perf KDB
|
|
|
|
// front->left + front->right / 2 into front->left, front->right duplicated.
|
|
|
|
while ( count-- )
|
|
{
|
|
pbf->left = (pbf->left + pbf->right) >> 1;
|
|
pbf++;
|
|
}
|
|
|
|
// process left (mono), duplicate output into right
|
|
|
|
PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT_DUPLICATE);
|
|
}
|
|
else
|
|
{
|
|
// avg left and right -> mono fx -> duplicate out left and right
|
|
while ( count-- )
|
|
{
|
|
av = ( ( pbf->left + pbf->right ) >> 1 );
|
|
x = PSET_GetNext( pdsp->ppset[0], av );
|
|
x = CLIP_DSP( x );
|
|
pbf->left = pbf->right = x;
|
|
pbf++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// crossfading to current preset from previous preset
|
|
|
|
if ( bcrossfading )
|
|
{
|
|
int r;
|
|
int fl;
|
|
int fr;
|
|
int flp;
|
|
int frp;
|
|
int xf_fl;
|
|
int xf_fr;
|
|
bool bexp = pdsp->bexpfade;
|
|
bool bfadetostereo = (pdsp->ipset == 0);
|
|
bool bfadefromstereo = (pdsp->ipsetprev == 0);
|
|
|
|
Assert ( !(bfadetostereo && bfadefromstereo) ); // don't call if ipset & ipsetprev both 0!
|
|
|
|
if ( bfadetostereo || bfadefromstereo )
|
|
{
|
|
// special case if fading to or from preset 0, stereo passthrough
|
|
|
|
while ( count-- )
|
|
{
|
|
av = ( ( pbf->left + pbf->right ) >> 1 );
|
|
|
|
// get current preset values
|
|
|
|
if ( pdsp->ipset )
|
|
{
|
|
fl = fr = PSET_GetNext( pdsp->ppset[0], av );
|
|
}
|
|
else
|
|
{
|
|
fl = pbf->left;
|
|
fr = pbf->right;
|
|
}
|
|
|
|
// get previous preset values
|
|
|
|
if ( pdsp->ipsetprev )
|
|
{
|
|
frp = flp = PSET_GetNext( pdsp->ppsetprev[0], av );
|
|
}
|
|
else
|
|
{
|
|
flp = pbf->left;
|
|
frp = pbf->right;
|
|
}
|
|
|
|
fl = CLIP_DSP(fl);
|
|
fr = CLIP_DSP(fr);
|
|
flp = CLIP_DSP(flp);
|
|
frp = CLIP_DSP(frp);
|
|
|
|
// get current ramp value
|
|
|
|
r = RMP_GetNext( &pdsp->xramp );
|
|
|
|
// crossfade from previous to current preset
|
|
|
|
if (!bexp)
|
|
{
|
|
xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left
|
|
}
|
|
else
|
|
{
|
|
xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left
|
|
}
|
|
|
|
pbf->left = xf_fl; // crossfaded front left, duplicate in right channel
|
|
pbf->right = xf_fr;
|
|
|
|
pbf++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// crossfade mono to mono preset
|
|
|
|
while ( count-- )
|
|
{
|
|
av = ( ( pbf->left + pbf->right ) >> 1 );
|
|
|
|
// get current preset values
|
|
|
|
fl = PSET_GetNext( pdsp->ppset[0], av );
|
|
|
|
// get previous preset values
|
|
|
|
flp = PSET_GetNext( pdsp->ppsetprev[0], av );
|
|
|
|
fl = CLIP_DSP(fl);
|
|
flp = CLIP_DSP(flp);
|
|
|
|
// get current ramp value
|
|
|
|
r = RMP_GetNext( &pdsp->xramp );
|
|
|
|
// crossfade from previous to current preset
|
|
|
|
if (!bexp)
|
|
xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
|
|
else
|
|
xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
|
|
|
|
pbf->left = xf_fl; // crossfaded front left, duplicate in right channel
|
|
pbf->right = xf_fl;
|
|
|
|
pbf++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper: called only from DSP_Process
|
|
// DSP_Process stereo in to stereo out (if more than 2 procs, ignore them)
|
|
|
|
inline void DSP_ProcessStereoToStereo(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading )
|
|
{
|
|
VPROF( "DSP_ProcessStereoToStereo" );
|
|
portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
|
|
int count = sampleCount;
|
|
int fl, fr;
|
|
|
|
if ( !bcrossfading )
|
|
{
|
|
|
|
if ( !pdsp->ipset )
|
|
return;
|
|
|
|
if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) )
|
|
{
|
|
|
|
// process left & right
|
|
|
|
PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT );
|
|
PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT );
|
|
}
|
|
else
|
|
{
|
|
// left -> left fx, right -> right fx
|
|
while ( count-- )
|
|
{
|
|
fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
|
|
fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
|
|
|
|
fl = CLIP_DSP( fl );
|
|
fr = CLIP_DSP( fr );
|
|
|
|
pbf->left = fl;
|
|
pbf->right = fr;
|
|
pbf++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// crossfading to current preset from previous preset
|
|
|
|
if ( bcrossfading )
|
|
{
|
|
int r;
|
|
int flp, frp;
|
|
int xf_fl, xf_fr;
|
|
bool bexp = pdsp->bexpfade;
|
|
|
|
while ( count-- )
|
|
{
|
|
// get current preset values
|
|
|
|
fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
|
|
fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
|
|
|
|
// get previous preset values
|
|
|
|
flp = PSET_GetNext( pdsp->ppsetprev[0], pbf->left );
|
|
frp = PSET_GetNext( pdsp->ppsetprev[1], pbf->right );
|
|
|
|
// get current ramp value
|
|
|
|
r = RMP_GetNext( &pdsp->xramp );
|
|
|
|
fl = CLIP_DSP( fl );
|
|
fr = CLIP_DSP( fr );
|
|
flp = CLIP_DSP( flp );
|
|
frp = CLIP_DSP( frp );
|
|
|
|
// crossfade from previous to current preset
|
|
if (!bexp)
|
|
{
|
|
xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE(fr, frp, r);
|
|
}
|
|
else
|
|
{
|
|
xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE_EXP(fr, frp, r);
|
|
}
|
|
|
|
pbf->left = xf_fl; // crossfaded front left
|
|
pbf->right = xf_fr;
|
|
|
|
pbf++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper: called only from DSP_Process
|
|
// DSP_Process quad in to mono out (front left = front right)
|
|
|
|
inline void DSP_ProcessQuadToMono(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading )
|
|
{
|
|
VPROF( "DSP_ProcessQuadToMono" );
|
|
portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
|
|
portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process
|
|
int count = sampleCount;
|
|
int x;
|
|
int av;
|
|
|
|
if ( !bcrossfading )
|
|
{
|
|
if ( !pdsp->ipset )
|
|
return;
|
|
|
|
if ( FBatchPreset(pdsp->ppset[0]) )
|
|
{
|
|
|
|
// convert Quad to Mono in place, then batch process fx: perf KDB
|
|
|
|
// left front + rear -> left, right front + rear -> right
|
|
while ( count-- )
|
|
{
|
|
pbf->left = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
|
|
// process left (mono), duplicate into right
|
|
|
|
PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT_DUPLICATE);
|
|
|
|
// copy processed front to rear
|
|
|
|
count = sampleCount;
|
|
|
|
pbf = pbfront;
|
|
pbr = pbrear;
|
|
|
|
while ( count-- )
|
|
{
|
|
pbr->left = pbf->left;
|
|
pbr->right = pbf->right;
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// avg fl,fr,rl,rr into mono fx, duplicate on all channels
|
|
while ( count-- )
|
|
{
|
|
av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
|
|
x = PSET_GetNext( pdsp->ppset[0], av );
|
|
x = CLIP_DSP( x );
|
|
pbr->left = pbr->right = pbf->left = pbf->right = x;
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( bcrossfading )
|
|
{
|
|
int r;
|
|
int fl, fr, rl, rr;
|
|
int flp, frp, rlp, rrp;
|
|
int xf_fl, xf_fr, xf_rl, xf_rr;
|
|
bool bexp = pdsp->bexpfade;
|
|
bool bfadetoquad = (pdsp->ipset == 0);
|
|
bool bfadefromquad = (pdsp->ipsetprev == 0);
|
|
|
|
if ( bfadetoquad || bfadefromquad )
|
|
{
|
|
// special case if previous or current preset is 0 (quad passthrough)
|
|
|
|
while ( count-- )
|
|
{
|
|
av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
|
|
|
|
// get current preset values
|
|
|
|
// current preset is 0, which implies fading to passthrough quad output
|
|
// need to fade from mono to quad
|
|
|
|
if ( pdsp->ipset )
|
|
{
|
|
rl = rr = fl = fr = PSET_GetNext( pdsp->ppset[0], av );
|
|
}
|
|
else
|
|
{
|
|
fl = pbf->left;
|
|
fr = pbf->right;
|
|
rl = pbr->left;
|
|
rr = pbr->right;
|
|
}
|
|
|
|
// get previous preset values
|
|
|
|
if ( pdsp->ipsetprev )
|
|
{
|
|
rrp = rlp = frp = flp = PSET_GetNext( pdsp->ppsetprev[0], av );
|
|
}
|
|
else
|
|
{
|
|
flp = pbf->left;
|
|
frp = pbf->right;
|
|
rlp = pbr->left;
|
|
rrp = pbr->right;
|
|
}
|
|
|
|
fl = CLIP_DSP(fl);
|
|
fr = CLIP_DSP(fr);
|
|
flp = CLIP_DSP(flp);
|
|
frp = CLIP_DSP(frp);
|
|
rl = CLIP_DSP(rl);
|
|
rr = CLIP_DSP(rr);
|
|
rlp = CLIP_DSP(rlp);
|
|
rrp = CLIP_DSP(rrp);
|
|
|
|
// get current ramp value
|
|
|
|
r = RMP_GetNext( &pdsp->xramp );
|
|
|
|
// crossfade from previous to current preset
|
|
|
|
if (!bexp)
|
|
{
|
|
xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left
|
|
xf_rl = XFADE(rl, rlp, r); // crossfade front left previous to front left
|
|
xf_rr = XFADE(rr, rrp, r); // crossfade front left previous to front left
|
|
}
|
|
else
|
|
{
|
|
xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left
|
|
xf_rl = XFADE_EXP(rl, rlp, r); // crossfade front left previous to front left
|
|
xf_rr = XFADE_EXP(rr, rrp, r); // crossfade front left previous to front left
|
|
}
|
|
|
|
pbf->left = xf_fl;
|
|
pbf->right = xf_fr;
|
|
pbr->left = xf_rl;
|
|
pbr->right = xf_rr;
|
|
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
while ( count-- )
|
|
{
|
|
|
|
av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
|
|
|
|
// get current preset values
|
|
|
|
fl = PSET_GetNext( pdsp->ppset[0], av );
|
|
|
|
// get previous preset values
|
|
|
|
flp = PSET_GetNext( pdsp->ppsetprev[0], av );
|
|
|
|
// get current ramp value
|
|
|
|
r = RMP_GetNext( &pdsp->xramp );
|
|
|
|
fl = CLIP_DSP( fl );
|
|
flp = CLIP_DSP( flp );
|
|
|
|
// crossfade from previous to current preset
|
|
if (!bexp)
|
|
xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
|
|
else
|
|
xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
|
|
|
|
pbf->left = xf_fl; // crossfaded front left, duplicated to all channels
|
|
pbf->right = xf_fl;
|
|
pbr->left = xf_fl;
|
|
pbr->right = xf_fl;
|
|
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper: called only from DSP_Process
|
|
// DSP_Process quad in to stereo out (preserve stereo spatialization, throw away front/rear)
|
|
|
|
inline void DSP_ProcessQuadToStereo(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading )
|
|
{
|
|
VPROF( "DSP_ProcessQuadToStereo" );
|
|
|
|
portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
|
|
portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process
|
|
int count = sampleCount;
|
|
int fl, fr;
|
|
|
|
if ( !bcrossfading )
|
|
{
|
|
if ( !pdsp->ipset )
|
|
return;
|
|
|
|
if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) )
|
|
{
|
|
|
|
// convert Quad to Stereo in place, then batch process fx: perf KDB
|
|
|
|
// left front + rear -> left, right front + rear -> right
|
|
|
|
while ( count-- )
|
|
{
|
|
pbf->left = (pbf->left + pbr->left) >> 1;
|
|
pbf->right = (pbf->right + pbr->right) >> 1;
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
|
|
// process left & right
|
|
|
|
PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT);
|
|
PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT );
|
|
|
|
// copy processed front to rear
|
|
|
|
count = sampleCount;
|
|
|
|
pbf = pbfront;
|
|
pbr = pbrear;
|
|
|
|
while ( count-- )
|
|
{
|
|
pbr->left = pbf->left;
|
|
pbr->right = pbf->right;
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// left front + rear -> left fx, right front + rear -> right fx
|
|
while ( count-- )
|
|
{
|
|
fl = PSET_GetNext( pdsp->ppset[0], (pbf->left + pbr->left) >> 1);
|
|
fr = PSET_GetNext( pdsp->ppset[1], (pbf->right + pbr->right) >> 1);
|
|
fl = CLIP_DSP( fl );
|
|
fr = CLIP_DSP( fr );
|
|
|
|
pbr->left = pbf->left = fl;
|
|
pbr->right = pbf->right = fr;
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// crossfading to current preset from previous preset
|
|
|
|
if ( bcrossfading )
|
|
{
|
|
int r;
|
|
int fl, fr, rl, rr;
|
|
int flp, frp, rlp, rrp;
|
|
int xf_fl, xf_fr, xf_rl, xf_rr;
|
|
int avl, avr;
|
|
bool bexp = pdsp->bexpfade;
|
|
bool bfadetoquad = (pdsp->ipset == 0);
|
|
bool bfadefromquad = (pdsp->ipsetprev == 0);
|
|
|
|
if ( bfadetoquad || bfadefromquad )
|
|
{
|
|
// special case if previous or current preset is 0 (quad passthrough)
|
|
|
|
while ( count-- )
|
|
{
|
|
avl = (pbf->left + pbr->left) >> 1;
|
|
avr = (pbf->right + pbr->right) >> 1;
|
|
|
|
// get current preset values
|
|
|
|
// current preset is 0, which implies fading to passthrough quad output
|
|
// need to fade from stereo to quad
|
|
|
|
if ( pdsp->ipset )
|
|
{
|
|
rl = fl = PSET_GetNext( pdsp->ppset[0], avl );
|
|
rr = fr = PSET_GetNext( pdsp->ppset[0], avr );
|
|
}
|
|
else
|
|
{
|
|
fl = pbf->left;
|
|
fr = pbf->right;
|
|
rl = pbr->left;
|
|
rr = pbr->right;
|
|
}
|
|
|
|
// get previous preset values
|
|
|
|
if ( pdsp->ipsetprev )
|
|
{
|
|
rlp = flp = PSET_GetNext( pdsp->ppsetprev[0], avl );
|
|
rrp = frp = PSET_GetNext( pdsp->ppsetprev[0], avr );
|
|
}
|
|
else
|
|
{
|
|
flp = pbf->left;
|
|
frp = pbf->right;
|
|
rlp = pbr->left;
|
|
rrp = pbr->right;
|
|
}
|
|
|
|
fl = CLIP_DSP(fl);
|
|
fr = CLIP_DSP(fr);
|
|
flp = CLIP_DSP(flp);
|
|
frp = CLIP_DSP(frp);
|
|
rl = CLIP_DSP(rl);
|
|
rr = CLIP_DSP(rr);
|
|
rlp = CLIP_DSP(rlp);
|
|
rrp = CLIP_DSP(rrp);
|
|
|
|
// get current ramp value
|
|
|
|
r = RMP_GetNext( &pdsp->xramp );
|
|
|
|
// crossfade from previous to current preset
|
|
|
|
if (!bexp)
|
|
{
|
|
xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left
|
|
xf_rl = XFADE(rl, rlp, r); // crossfade front left previous to front left
|
|
xf_rr = XFADE(rr, rrp, r); // crossfade front left previous to front left
|
|
}
|
|
else
|
|
{
|
|
xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left
|
|
xf_rl = XFADE_EXP(rl, rlp, r); // crossfade front left previous to front left
|
|
xf_rr = XFADE_EXP(rr, rrp, r); // crossfade front left previous to front left
|
|
}
|
|
|
|
pbf->left = xf_fl;
|
|
pbf->right = xf_fr;
|
|
pbr->left = xf_rl;
|
|
pbr->right = xf_rr;
|
|
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
while ( count-- )
|
|
{
|
|
avl = (pbf->left + pbr->left) >> 1;
|
|
avr = (pbf->right + pbr->right) >> 1;
|
|
|
|
// get current preset values
|
|
|
|
fl = PSET_GetNext( pdsp->ppset[0], avl );
|
|
fr = PSET_GetNext( pdsp->ppset[1], avr );
|
|
|
|
// get previous preset values
|
|
|
|
flp = PSET_GetNext( pdsp->ppsetprev[0], avl );
|
|
frp = PSET_GetNext( pdsp->ppsetprev[1], avr );
|
|
|
|
|
|
fl = CLIP_DSP( fl );
|
|
fr = CLIP_DSP( fr );
|
|
|
|
// get previous preset values
|
|
|
|
flp = CLIP_DSP( flp );
|
|
frp = CLIP_DSP( frp );
|
|
|
|
// get current ramp value
|
|
|
|
r = RMP_GetNext( &pdsp->xramp );
|
|
|
|
// crossfade from previous to current preset
|
|
if (!bexp)
|
|
{
|
|
xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE(fr, frp, r);
|
|
}
|
|
else
|
|
{
|
|
xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE_EXP(fr, frp, r);
|
|
}
|
|
|
|
pbf->left = xf_fl; // crossfaded front left
|
|
pbf->right = xf_fr;
|
|
|
|
pbr->left = xf_fl; // duplicate front channel to rear channel
|
|
pbr->right = xf_fr;
|
|
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper: called only from DSP_Process
|
|
// DSP_Process quad in to quad out
|
|
|
|
inline void DSP_ProcessQuadToQuad(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading )
|
|
{
|
|
portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
|
|
portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process
|
|
int count = sampleCount;
|
|
int fl, fr, rl, rr;
|
|
|
|
if ( !bcrossfading )
|
|
{
|
|
if ( !pdsp->ipset )
|
|
return;
|
|
|
|
// each channel gets its own processor
|
|
|
|
if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) && FBatchPreset(pdsp->ppset[2]) && FBatchPreset(pdsp->ppset[3]))
|
|
{
|
|
// batch process fx front & rear, left & right: perf KDB
|
|
|
|
PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT);
|
|
PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT );
|
|
PSET_GetNextN( pdsp->ppset[2], pbrear, sampleCount, OP_LEFT );
|
|
PSET_GetNextN( pdsp->ppset[3], pbrear, sampleCount, OP_RIGHT );
|
|
}
|
|
else
|
|
{
|
|
while ( count-- )
|
|
{
|
|
fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
|
|
fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
|
|
rl = PSET_GetNext( pdsp->ppset[2], pbr->left );
|
|
rr = PSET_GetNext( pdsp->ppset[3], pbr->right );
|
|
|
|
pbf->left = CLIP_DSP( fl );
|
|
pbf->right = CLIP_DSP( fr );
|
|
pbr->left = CLIP_DSP( rl );
|
|
pbr->right = CLIP_DSP( rr );
|
|
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// crossfading to current preset from previous preset
|
|
|
|
if ( bcrossfading )
|
|
{
|
|
int r;
|
|
int fl, fr, rl, rr;
|
|
int flp, frp, rlp, rrp;
|
|
int xf_fl, xf_fr, xf_rl, xf_rr;
|
|
bool bexp = pdsp->bexpfade;
|
|
|
|
while ( count-- )
|
|
{
|
|
// get current preset values
|
|
|
|
fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
|
|
fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
|
|
rl = PSET_GetNext( pdsp->ppset[2], pbr->left );
|
|
rr = PSET_GetNext( pdsp->ppset[3], pbr->right );
|
|
|
|
// get previous preset values
|
|
|
|
flp = PSET_GetNext( pdsp->ppsetprev[0], pbf->left );
|
|
frp = PSET_GetNext( pdsp->ppsetprev[1], pbf->right );
|
|
rlp = PSET_GetNext( pdsp->ppsetprev[2], pbr->left );
|
|
rrp = PSET_GetNext( pdsp->ppsetprev[3], pbr->right );
|
|
|
|
// get current ramp value
|
|
|
|
r = RMP_GetNext( &pdsp->xramp );
|
|
|
|
// crossfade from previous to current preset
|
|
if (!bexp)
|
|
{
|
|
xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE(fr, frp, r);
|
|
xf_rl = XFADE(rl, rlp, r);
|
|
xf_rr = XFADE(rr, rrp, r);
|
|
}
|
|
else
|
|
{
|
|
xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE_EXP(fr, frp, r);
|
|
xf_rl = XFADE_EXP(rl, rlp, r);
|
|
xf_rr = XFADE_EXP(rr, rrp, r);
|
|
}
|
|
|
|
pbf->left = CLIP_DSP(xf_fl); // crossfaded front left
|
|
pbf->right = CLIP_DSP(xf_fr);
|
|
pbr->left = CLIP_DSP(xf_rl);
|
|
pbr->right = CLIP_DSP(xf_rr);
|
|
|
|
pbf++;
|
|
pbr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Helper: called only from DSP_Process
|
|
// DSP_Process quad + center in to mono out (front left = front right)
|
|
|
|
inline void DSP_Process5To1(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, portable_samplepair_t *pbcenter, int sampleCount, bool bcrossfading )
|
|
{
|
|
VPROF( "DSP_Process5To1" );
|
|
|
|
portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
|
|
portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process
|
|
portable_samplepair_t *pbc = pbcenter; // pointer to buffer of center mono samples to process
|
|
int count = sampleCount;
|
|
int x;
|
|
int av;
|
|
|
|
if ( !bcrossfading )
|
|
{
|
|
if ( !pdsp->ipset )
|
|
return;
|
|
|
|
if ( FBatchPreset(pdsp->ppset[0]) )
|
|
{
|
|
|
|
// convert Quad + Center to Mono in place, then batch process fx: perf KDB
|
|
|
|
// left front + rear -> left, right front + rear -> right
|
|
while ( count-- )
|
|
{
|
|
// pbf->left = ((pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) / 5);
|
|
|
|
av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5
|
|
av >>= 8;
|
|
pbf->left = av;
|
|
pbf++;
|
|
pbr++;
|
|
pbc++;
|
|
}
|
|
|
|
// process left (mono), duplicate into right
|
|
|
|
PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT_DUPLICATE);
|
|
|
|
// copy processed front to rear & center
|
|
|
|
count = sampleCount;
|
|
|
|
pbf = pbfront;
|
|
pbr = pbrear;
|
|
pbc = pbcenter;
|
|
|
|
while ( count-- )
|
|
{
|
|
pbr->left = pbf->left;
|
|
pbr->right = pbf->right;
|
|
pbc->left = pbf->left;
|
|
pbf++;
|
|
pbr++;
|
|
pbc++;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// avg fl,fr,rl,rr,fc into mono fx, duplicate on all channels
|
|
while ( count-- )
|
|
{
|
|
// av = ((pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) / 5);
|
|
av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5
|
|
av >>= 8;
|
|
x = PSET_GetNext( pdsp->ppset[0], av );
|
|
x = CLIP_DSP( x );
|
|
pbr->left = pbr->right = pbf->left = pbf->right = pbc->left = x;
|
|
pbf++;
|
|
pbr++;
|
|
pbc++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( bcrossfading )
|
|
{
|
|
int r;
|
|
int fl, fr, rl, rr, fc;
|
|
int flp, frp, rlp, rrp, fcp;
|
|
int xf_fl, xf_fr, xf_rl, xf_rr, xf_fc;
|
|
bool bexp = pdsp->bexpfade;
|
|
bool bfadetoquad = (pdsp->ipset == 0);
|
|
bool bfadefromquad = (pdsp->ipsetprev == 0);
|
|
|
|
if ( bfadetoquad || bfadefromquad )
|
|
{
|
|
// special case if previous or current preset is 0 (quad passthrough)
|
|
|
|
while ( count-- )
|
|
{
|
|
// av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
|
|
|
|
av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5
|
|
av >>= 8;
|
|
|
|
// get current preset values
|
|
|
|
// current preset is 0, which implies fading to passthrough quad output
|
|
// need to fade from mono to quad
|
|
|
|
if ( pdsp->ipset )
|
|
{
|
|
fc = rl = rr = fl = fr = PSET_GetNext( pdsp->ppset[0], av );
|
|
}
|
|
else
|
|
{
|
|
fl = pbf->left;
|
|
fr = pbf->right;
|
|
rl = pbr->left;
|
|
rr = pbr->right;
|
|
fc = pbc->left;
|
|
}
|
|
|
|
// get previous preset values
|
|
|
|
if ( pdsp->ipsetprev )
|
|
{
|
|
fcp = rrp = rlp = frp = flp = PSET_GetNext( pdsp->ppsetprev[0], av );
|
|
}
|
|
else
|
|
{
|
|
flp = pbf->left;
|
|
frp = pbf->right;
|
|
rlp = pbr->left;
|
|
rrp = pbr->right;
|
|
fcp = pbc->left;
|
|
}
|
|
|
|
fl = CLIP_DSP(fl);
|
|
fr = CLIP_DSP(fr);
|
|
flp = CLIP_DSP(flp);
|
|
frp = CLIP_DSP(frp);
|
|
rl = CLIP_DSP(rl);
|
|
rr = CLIP_DSP(rr);
|
|
rlp = CLIP_DSP(rlp);
|
|
rrp = CLIP_DSP(rrp);
|
|
fc = CLIP_DSP(fc);
|
|
fcp = CLIP_DSP(fcp);
|
|
|
|
// get current ramp value
|
|
|
|
r = RMP_GetNext( &pdsp->xramp );
|
|
|
|
// crossfade from previous to current preset
|
|
|
|
if (!bexp)
|
|
{
|
|
xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left
|
|
xf_rl = XFADE(rl, rlp, r); // crossfade front left previous to front left
|
|
xf_rr = XFADE(rr, rrp, r); // crossfade front left previous to front left
|
|
xf_fc = XFADE(fc, fcp, r); // crossfade front left previous to front left
|
|
}
|
|
else
|
|
{
|
|
xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left
|
|
xf_rl = XFADE_EXP(rl, rlp, r); // crossfade front left previous to front left
|
|
xf_rr = XFADE_EXP(rr, rrp, r); // crossfade front left previous to front left
|
|
xf_fc = XFADE_EXP(fc, fcp, r); // crossfade front left previous to front left
|
|
}
|
|
|
|
pbf->left = xf_fl;
|
|
pbf->right = xf_fr;
|
|
pbr->left = xf_rl;
|
|
pbr->right = xf_rr;
|
|
pbc->left = xf_fc;
|
|
|
|
pbf++;
|
|
pbr++;
|
|
pbc++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
while ( count-- )
|
|
{
|
|
|
|
// av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
|
|
av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5
|
|
av >>= 8;
|
|
|
|
// get current preset values
|
|
|
|
fl = PSET_GetNext( pdsp->ppset[0], av );
|
|
|
|
// get previous preset values
|
|
|
|
flp = PSET_GetNext( pdsp->ppsetprev[0], av );
|
|
|
|
// get current ramp value
|
|
|
|
r = RMP_GetNext( &pdsp->xramp );
|
|
|
|
fl = CLIP_DSP( fl );
|
|
flp = CLIP_DSP( flp );
|
|
|
|
// crossfade from previous to current preset
|
|
if (!bexp)
|
|
xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
|
|
else
|
|
xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
|
|
|
|
pbf->left = xf_fl; // crossfaded front left, duplicated to all channels
|
|
pbf->right = xf_fl;
|
|
pbr->left = xf_fl;
|
|
pbr->right = xf_fl;
|
|
pbc->left = xf_fl;
|
|
|
|
pbf++;
|
|
pbr++;
|
|
pbc++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper: called only from DSP_Process
|
|
// DSP_Process quad + center in to quad + center out
|
|
|
|
inline void DSP_Process5To5(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, portable_samplepair_t *pbcenter, int sampleCount, bool bcrossfading )
|
|
{
|
|
VPROF( "DSP_Process5To5" );
|
|
|
|
portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
|
|
portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process
|
|
portable_samplepair_t *pbc = pbcenter; // pointer to buffer of center mono samples to process
|
|
|
|
int count = sampleCount;
|
|
int fl, fr, rl, rr, fc;
|
|
|
|
if ( !bcrossfading )
|
|
{
|
|
if ( !pdsp->ipset )
|
|
return;
|
|
|
|
// each channel gets its own processor
|
|
|
|
if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) && FBatchPreset(pdsp->ppset[2]) && FBatchPreset(pdsp->ppset[3]))
|
|
{
|
|
// batch process fx front & rear, left & right: perf KDB
|
|
|
|
PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT);
|
|
PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT );
|
|
PSET_GetNextN( pdsp->ppset[2], pbrear, sampleCount, OP_LEFT );
|
|
PSET_GetNextN( pdsp->ppset[3], pbrear, sampleCount, OP_RIGHT );
|
|
PSET_GetNextN( pdsp->ppset[4], pbcenter, sampleCount, OP_LEFT );
|
|
}
|
|
else
|
|
{
|
|
while ( count-- )
|
|
{
|
|
fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
|
|
fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
|
|
rl = PSET_GetNext( pdsp->ppset[2], pbr->left );
|
|
rr = PSET_GetNext( pdsp->ppset[3], pbr->right );
|
|
fc = PSET_GetNext( pdsp->ppset[4], pbc->left );
|
|
|
|
pbf->left = CLIP_DSP( fl );
|
|
pbf->right = CLIP_DSP( fr );
|
|
pbr->left = CLIP_DSP( rl );
|
|
pbr->right = CLIP_DSP( rr );
|
|
pbc->left = CLIP_DSP( fc );
|
|
|
|
pbf++;
|
|
pbr++;
|
|
pbc++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// crossfading to current preset from previous preset
|
|
|
|
if ( bcrossfading )
|
|
{
|
|
int r;
|
|
int fl, fr, rl, rr, fc;
|
|
int flp, frp, rlp, rrp, fcp;
|
|
int xf_fl, xf_fr, xf_rl, xf_rr, xf_fc;
|
|
bool bexp = pdsp->bexpfade;
|
|
|
|
while ( count-- )
|
|
{
|
|
// get current preset values
|
|
|
|
fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
|
|
fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
|
|
rl = PSET_GetNext( pdsp->ppset[2], pbr->left );
|
|
rr = PSET_GetNext( pdsp->ppset[3], pbr->right );
|
|
fc = PSET_GetNext( pdsp->ppset[4], pbc->left );
|
|
|
|
// get previous preset values
|
|
|
|
flp = PSET_GetNext( pdsp->ppsetprev[0], pbf->left );
|
|
frp = PSET_GetNext( pdsp->ppsetprev[1], pbf->right );
|
|
rlp = PSET_GetNext( pdsp->ppsetprev[2], pbr->left );
|
|
rrp = PSET_GetNext( pdsp->ppsetprev[3], pbr->right );
|
|
fcp = PSET_GetNext( pdsp->ppsetprev[4], pbc->left );
|
|
|
|
// get current ramp value
|
|
|
|
r = RMP_GetNext( &pdsp->xramp );
|
|
|
|
// crossfade from previous to current preset
|
|
if (!bexp)
|
|
{
|
|
xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE(fr, frp, r);
|
|
xf_rl = XFADE(rl, rlp, r);
|
|
xf_rr = XFADE(rr, rrp, r);
|
|
xf_fc = XFADE(fc, fcp, r);
|
|
}
|
|
else
|
|
{
|
|
xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
|
|
xf_fr = XFADE_EXP(fr, frp, r);
|
|
xf_rl = XFADE_EXP(rl, rlp, r);
|
|
xf_rr = XFADE_EXP(rr, rrp, r);
|
|
xf_fc = XFADE_EXP(fc, fcp, r);
|
|
}
|
|
|
|
pbf->left = CLIP_DSP(xf_fl); // crossfaded front left
|
|
pbf->right = CLIP_DSP(xf_fr);
|
|
pbr->left = CLIP_DSP(xf_rl);
|
|
pbr->right = CLIP_DSP(xf_rr);
|
|
pbc->left = CLIP_DSP(xf_fc);
|
|
|
|
pbf++;
|
|
pbr++;
|
|
pbc++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is an evil hack, but we need to restore the old presets after letting the sound system update for a few frames, so we just
|
|
// "defer" the restore until the top of the next call to CheckNewDspPresets. I put in a bit of warning in case we ever have code
|
|
// outside of this time period modifying any of the dsp convars. It doesn't seem to be an issue just save/loading between levels
|
|
static bool g_bNeedPresetRestore = false;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
struct PreserveDSP_t
|
|
{
|
|
ConVar *cvar;
|
|
float oldvalue;
|
|
};
|
|
|
|
static PreserveDSP_t g_PreserveDSP[] =
|
|
{
|
|
{ &dsp_room },
|
|
{ &dsp_water },
|
|
{ &dsp_player },
|
|
{ &dsp_facingaway },
|
|
{ &dsp_speaker },
|
|
{ &dsp_spatial },
|
|
{ &dsp_automatic }
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called at the top of CheckNewDspPresets to restore ConVars to real values
|
|
//-----------------------------------------------------------------------------
|
|
void DSP_CheckRestorePresets()
|
|
{
|
|
if ( !g_bNeedPresetRestore )
|
|
return;
|
|
|
|
g_bNeedPresetRestore = false;
|
|
|
|
int i;
|
|
int c = ARRAYSIZE( g_PreserveDSP );
|
|
|
|
// Restore
|
|
for ( i = 0 ; i < c; ++i )
|
|
{
|
|
PreserveDSP_t& slot = g_PreserveDSP[ i ];
|
|
|
|
ConVar *cv = slot.cvar;
|
|
Assert( cv );
|
|
float flVal = cv->GetFloat();
|
|
if ( cv == &dsp_player )
|
|
flVal = dsp_player_get();
|
|
if ( flVal != 0.0f )
|
|
{
|
|
// NOTE: dsp_speaker is being (correctly) save/restored by maps, which would trigger this warning
|
|
//Warning( "DSP_CheckRestorePresets: Value of %s was changed between DSP_ClearState and CheckNewDspPresets, not restoring to old value\n", cv->GetName() );
|
|
continue;
|
|
}
|
|
if ( cv == &dsp_player )
|
|
dsp_player_set( slot.oldvalue );
|
|
else
|
|
cv->SetValue( slot.oldvalue );
|
|
}
|
|
|
|
// reinit all dsp processors (only load preset file on engine init, however)
|
|
|
|
AllocDsps( false );
|
|
|
|
// flush dsp automatic nodes
|
|
|
|
g_bdas_init_nodes = 0;
|
|
g_bdas_room_init = 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void DSP_ClearState()
|
|
{
|
|
// if we already cleared dsp state, and a restore is pending,
|
|
// don't clear again
|
|
|
|
if ( g_bNeedPresetRestore )
|
|
return;
|
|
|
|
// always save a cleared dsp automatic value to force reset of all adsp code
|
|
|
|
dsp_automatic.SetValue(0);
|
|
|
|
// Tracker 7155: YWB: This is a pretty ugly hack to zero out all of the dsp convars and bootstrap the dsp system into using them for a few frames
|
|
|
|
int i;
|
|
int c = ARRAYSIZE( g_PreserveDSP );
|
|
|
|
for ( i = 0 ; i < c; ++i )
|
|
{
|
|
PreserveDSP_t& slot = g_PreserveDSP[ i ];
|
|
|
|
ConVar *cv = slot.cvar;
|
|
Assert( cv );
|
|
if ( cv == &dsp_player )
|
|
{
|
|
slot.oldvalue = dsp_player_get();
|
|
dsp_player_set( 0 );
|
|
}
|
|
else
|
|
{
|
|
slot.oldvalue = cv->GetFloat();
|
|
cv->SetValue( 0 );
|
|
}
|
|
}
|
|
|
|
// force all dsp presets to end crossfades, end one-shot presets, & release and reset all resources
|
|
// immediately.
|
|
|
|
FreeDsps( false ); // free all dsp states, but don't discard preset templates
|
|
|
|
// This forces the ConVars which we set to zero above to be reloaded to their old values at the time we issue the CheckNewDspPresets
|
|
// command. This seems to happen early enough in level changes were we don't appear to be trying to stomp real settings...
|
|
|
|
g_bNeedPresetRestore = true;
|
|
}
|
|
|
|
// return true if dsp's preset is one-shot and it has expired
|
|
|
|
bool DSP_HasExpired( int idsp )
|
|
{
|
|
dsp_t *pdsp;
|
|
|
|
Assert( idsp < CDSPS );
|
|
|
|
if (idsp < 0 || idsp >= CDSPS)
|
|
return false;
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
// if first preset has expired, dsp has expired
|
|
|
|
if ( PSET_IsOneShot( pdsp->ppset[0] ) )
|
|
return PSET_HasExpired( pdsp->ppset[0] );
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// returns true if dsp is crossfading from previous dsp preset
|
|
|
|
bool DSP_IsCrossfading( int idsp, bool bUseMsTimeExpiration )
|
|
{
|
|
dsp_t *pdsp;
|
|
|
|
Assert( idsp < CDSPS );
|
|
|
|
if (idsp < 0 || idsp >= CDSPS)
|
|
return false;
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
if ( bUseMsTimeExpiration && ( pdsp->ipsetprev != 0 ) )
|
|
{
|
|
if ( pdsp->xramp.nEndRampTimeInMs < Plat_MSTime() )
|
|
{
|
|
// This case happens often when we previously switched from one DSP (that was fading) to another.
|
|
// When we re-switch to the first DSP, the cross fading is still considered valid, except that the sounds in the delay are actually not valid anymore.
|
|
// Let's not indicate that we are cross-fading, that way we can switch to the new preset sooner rather than later.
|
|
if ( snd_dsp_spew_changes.GetBool() )
|
|
{
|
|
DevMsg( "[Sound DSP] For Dsp %d, don't consider cross fading from presets %d to %d as previous preset is expired.\n", idsp, pdsp->ipsetprev, pdsp->ipset );
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return !RMP_HitEnd( &pdsp->xramp );
|
|
}
|
|
|
|
// returns previous preset # before oneshot preset was set
|
|
|
|
int DSP_OneShotPrevious( int idsp )
|
|
{
|
|
dsp_t *pdsp;
|
|
int idsp_prev;
|
|
|
|
Assert( idsp < CDSPS );
|
|
|
|
if (idsp < 0 || idsp >= CDSPS)
|
|
return 0;
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
idsp_prev = pdsp->ipsetsav_oneshot;
|
|
|
|
return idsp_prev;
|
|
}
|
|
|
|
// given idsp (processor index), return true if
|
|
// both current and previous presets are 0 for this processor
|
|
|
|
bool DSP_PresetIsOff( int idsp )
|
|
{
|
|
dsp_t *pdsp;
|
|
|
|
if (idsp < 0 || idsp >= CDSPS)
|
|
return true;
|
|
|
|
Assert ( idsp < CDSPS ); // make sure idsp is valid
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
// if current and previous preset 0, return - preset 0 is 'off'
|
|
|
|
return ( !pdsp->ipset && !pdsp->ipsetprev );
|
|
}
|
|
|
|
// returns true if dsp is off for room effects
|
|
|
|
bool DSP_RoomDSPIsOff()
|
|
{
|
|
return DSP_PresetIsOff( Get_idsp_room() );
|
|
}
|
|
|
|
// Main DSP processing routine:
|
|
// process samples in buffers using pdsp processor
|
|
// continue crossfade between 2 dsp processors if crossfading on switch
|
|
// pfront - front stereo buffer to process
|
|
// prear - rear stereo buffer to process (may be NULL)
|
|
// pcenter - front center mono buffer (may be NULL)
|
|
// sampleCount - number of samples in pbuf to process
|
|
// This routine also maps the # processing channels in the pdsp to the number of channels
|
|
// supplied. ie: if the pdsp has 4 channels and pbfront and pbrear are both non-null, the channels
|
|
// map 1:1 through the processors.
|
|
|
|
void DSP_Process( int idsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, portable_samplepair_t *pbcenter, int sampleCount )
|
|
{
|
|
bool bcrossfading;
|
|
int cchan_in; // input channels (2,4 or 5)
|
|
int cprocs; // output channels (1, 2 or 4)
|
|
dsp_t *pdsp;
|
|
|
|
if (idsp < 0 || idsp >= CDSPS)
|
|
return;
|
|
|
|
// Don't pull dsp data in if player is not connected (during load/level change)
|
|
if ( !g_pSoundServices->IsConnected() )
|
|
return;
|
|
|
|
Assert ( idsp < CDSPS ); // make sure idsp is valid
|
|
|
|
pdsp = &dsps[idsp];
|
|
|
|
Assert (pbfront);
|
|
|
|
// return right away if fx processing is turned off
|
|
|
|
if ( dsp_off.GetInt() )
|
|
return;
|
|
|
|
if ( pdsp->bEnabled == false )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if current and previous preset 0, return - preset 0 is 'off'
|
|
|
|
if ( !pdsp->ipset && !pdsp->ipsetprev )
|
|
return;
|
|
|
|
if ( sampleCount < 0 )
|
|
return;
|
|
|
|
bcrossfading = !RMP_HitEnd( &pdsp->xramp );
|
|
|
|
if ( pdsp->ipsetprev != 0 )
|
|
{
|
|
if ( pdsp->xramp.nEndRampTimeInMs < Plat_MSTime() )
|
|
{
|
|
// This case happens often when we previously switched from one DSP (that was fading) to another.
|
|
// When we re-switch to the first DSP, the cross-fading is still considered valid, except that the sounds in the delay are actually not valid anymore.
|
|
// By canceling the cross-fading, we only hear the new effect and not the old effect.
|
|
if ( snd_dsp_spew_changes.GetBool() )
|
|
{
|
|
DevMsg( "[Sound DSP] For Dsp %d, suppress cross fading from presets %d to %d as previous preset is expired.\n", idsp, pdsp->ipsetprev, pdsp->ipset );
|
|
}
|
|
RMP_SetEnd( &pdsp->xramp );
|
|
bcrossfading = false;
|
|
}
|
|
}
|
|
|
|
// if not crossfading, and previous channel is not null, free previous
|
|
if ( !bcrossfading )
|
|
{
|
|
DSP_FreePrevPreset( pdsp );
|
|
}
|
|
|
|
// if current and previous preset 0 (ie: just freed previous), return - preset 0 is 'off'
|
|
if ( !pdsp->ipset && !pdsp->ipsetprev )
|
|
return;
|
|
|
|
uint nCurrentTime = Plat_MSTime();
|
|
if ( snd_dsp_spew_changes.GetBool() )
|
|
{
|
|
if ( bcrossfading )
|
|
{
|
|
DevMsg("[Sound DSP] Dsp %d processed. Cross-fading presets from %d to %d.\n", idsp, pdsp->ipsetprev, pdsp->ipset );
|
|
}
|
|
else
|
|
{
|
|
static uint sLastDisplay = 0;
|
|
if ( nCurrentTime > sLastDisplay + 1000 )
|
|
{
|
|
DevMsg( "[Sound DSP] Dsp %d processed.\n", idsp ); // Displayed every second
|
|
sLastDisplay = nCurrentTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pdsp->ipset != 0 )
|
|
{
|
|
pdsp->ppset[0]->nLastUpdatedTimeInMilliseconds = nCurrentTime;
|
|
}
|
|
if ( pdsp->ipsetprev != 0 )
|
|
{
|
|
pdsp->ppsetprev[0]->nLastUpdatedTimeInMilliseconds = nCurrentTime;
|
|
}
|
|
|
|
cchan_in = (pbrear ? 4 : 2) + (pbcenter ? 1 : 0);
|
|
cprocs = pdsp->cchan;
|
|
|
|
Assert(cchan_in == 2 || cchan_in == 4 || cchan_in == 5 );
|
|
|
|
// if oneshot preset, update the duration counter (only update front left counter)
|
|
|
|
PSET_UpdateDuration( pdsp->ppset[0], sampleCount );
|
|
|
|
// NOTE: when mixing between different channel sizes,
|
|
// always AVERAGE down to fewer channels and DUPLICATE up more channels.
|
|
// The following routines always process cchan_in channels.
|
|
// ie: QuadToMono still updates 4 values in buffer
|
|
|
|
// DSP_Process stereo in to mono out (ie: left and right are averaged)
|
|
|
|
if ( snd_spew_dsp_process.GetBool() )
|
|
{
|
|
Msg( "[Sound] DSP_Process() called. DSP index: %d - Sample cout: %d\n", idsp, sampleCount );
|
|
}
|
|
|
|
if ( cchan_in == 2 && cprocs == 1)
|
|
{
|
|
DSP_ProcessStereoToMono( pdsp, pbfront, pbrear, sampleCount, bcrossfading );
|
|
return;
|
|
}
|
|
|
|
// DSP_Process stereo in to stereo out (if more than 2 procs, ignore them)
|
|
|
|
if ( cchan_in == 2 && cprocs >= 2)
|
|
{
|
|
DSP_ProcessStereoToStereo( pdsp, pbfront, pbrear, sampleCount, bcrossfading );
|
|
return;
|
|
}
|
|
|
|
|
|
// DSP_Process quad in to mono out
|
|
|
|
if ( cchan_in == 4 && cprocs == 1)
|
|
{
|
|
DSP_ProcessQuadToMono( pdsp, pbfront, pbrear, sampleCount, bcrossfading );
|
|
return;
|
|
}
|
|
|
|
|
|
// DSP_Process quad in to stereo out (preserve stereo spatialization, loose front/rear)
|
|
|
|
if ( cchan_in == 4 && cprocs == 2)
|
|
{
|
|
DSP_ProcessQuadToStereo( pdsp, pbfront, pbrear, sampleCount, bcrossfading );
|
|
return;
|
|
}
|
|
|
|
|
|
// DSP_Process quad in to quad out
|
|
|
|
if ( cchan_in == 4 && cprocs == 4)
|
|
{
|
|
DSP_ProcessQuadToQuad( pdsp, pbfront, pbrear, sampleCount, bcrossfading );
|
|
return;
|
|
}
|
|
|
|
// DSP_Process quad + center in to mono out
|
|
|
|
if ( cchan_in == 5 && cprocs == 1)
|
|
{
|
|
DSP_Process5To1( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading );
|
|
return;
|
|
}
|
|
|
|
if ( cchan_in == 5 && cprocs == 2)
|
|
{
|
|
// undone: not used in AllocDsps
|
|
Assert(false);
|
|
//DSP_Process5to2( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading );
|
|
return;
|
|
}
|
|
|
|
if ( cchan_in == 5 && cprocs == 4)
|
|
{
|
|
// undone: not used in AllocDsps
|
|
Assert(false);
|
|
//DSP_Process5to4( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading );
|
|
return;
|
|
}
|
|
|
|
// DSP_Process quad + center in to quad + center out
|
|
|
|
if ( cchan_in == 5 && cprocs == 5)
|
|
{
|
|
DSP_Process5To5( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading );
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
// DSP helpers
|
|
|
|
// free all dsp processors
|
|
|
|
void FreeDsps( bool bReleaseTemplateMemory )
|
|
{
|
|
|
|
DSP_Free(idsp_room);
|
|
DSP_Free(idsp_water);
|
|
DSP_Free(idsp_player);
|
|
DSP_Free(idsp_facingaway);
|
|
DSP_Free(idsp_speaker);
|
|
DSP_Free(idsp_spatial);
|
|
DSP_Free(idsp_automatic);
|
|
|
|
idsp_room = 0;
|
|
idsp_water = 0;
|
|
idsp_player = 0;
|
|
idsp_facingaway = 0;
|
|
idsp_speaker = 0;
|
|
idsp_spatial = 0;
|
|
idsp_automatic = 0;
|
|
|
|
DSP_FreeAll();
|
|
|
|
// only unlock and free psettemplate memory on engine shutdown
|
|
|
|
if ( bReleaseTemplateMemory )
|
|
DSP_ReleaseMemory();
|
|
}
|
|
|
|
// alloc dsp processors, load dsp preset array from file on engine init only
|
|
|
|
bool AllocDsps( bool bLoadPresetFile )
|
|
{
|
|
int csurround = (g_AudioDevice->IsSurround() ? 2: 0); // surround channels to allocate
|
|
int ccenter = (g_AudioDevice->IsSurroundCenter() ? 1 : 0); // center channels to allocate
|
|
|
|
DSP_InitAll( bLoadPresetFile );
|
|
|
|
idsp_room = -1;
|
|
idsp_water = -1;
|
|
idsp_player = -1;
|
|
idsp_facingaway = -1;
|
|
idsp_speaker = -1;
|
|
idsp_spatial = -1;
|
|
idsp_automatic = -1;
|
|
|
|
// alloc dsp room channel (mono, stereo if dsp_stereo is 1)
|
|
|
|
// dsp room is mono, 300ms default fade time
|
|
|
|
idsp_room = DSP_Alloc( dsp_room.GetInt(), 200, 1 );
|
|
|
|
// dsp automatic overrides dsp_room, if dsp_room set to DSP_AUTOMATIC (1)
|
|
|
|
idsp_automatic = DSP_Alloc( dsp_automatic.GetInt(), 200, 1 ) ;
|
|
|
|
// alloc stereo or quad series processors for player or water
|
|
|
|
// water and player presets are mono
|
|
|
|
idsp_water = DSP_Alloc( dsp_water.GetInt(), 100, 1 );
|
|
idsp_player = DSP_Alloc( dsp_player_get(), 100, 1 );
|
|
|
|
// alloc facing away filters (stereo, quad or 5ch)
|
|
|
|
idsp_facingaway = DSP_Alloc( dsp_facingaway.GetInt(), 100, 2 + csurround + ccenter );
|
|
|
|
// alloc speaker preset (mono)
|
|
|
|
idsp_speaker = DSP_Alloc( dsp_speaker.GetInt(), 300, 1 );
|
|
|
|
// alloc spatial preset (2-5 chan)
|
|
|
|
idsp_spatial = DSP_Alloc( dsp_spatial.GetInt(), 300, 2 + csurround + ccenter );
|
|
|
|
// init prev values
|
|
|
|
ipset_room_prev = dsp_room.GetInt();
|
|
ipset_water_prev = dsp_water.GetInt();
|
|
ipset_player_prev = dsp_player_get();
|
|
ipset_facingaway_prev = dsp_facingaway.GetInt();
|
|
ipset_room_typeprev = dsp_room_type.GetInt();
|
|
ipset_speaker_prev = dsp_speaker.GetInt();
|
|
ipset_spatial_prev = dsp_spatial.GetInt();
|
|
ipset_automatic_prev = dsp_automatic.GetInt();
|
|
|
|
if (idsp_room < 0 || idsp_water < 0 || idsp_player < 0 || idsp_facingaway < 0 || idsp_speaker < 0 || idsp_spatial < 0 || idsp_automatic < 0)
|
|
{
|
|
DevMsg ("WARNING: DSP processor failed to initialize! \n" );
|
|
|
|
FreeDsps( true );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// count number of dsp presets specified in preset file
|
|
// counts outer {} pairs, ignoring inner {} pairs.
|
|
|
|
int DSP_CountFilePresets( const char *pstart )
|
|
{
|
|
int cpresets = 0;
|
|
bool binpreset = false;
|
|
bool blookleft = false;
|
|
|
|
while ( 1 )
|
|
{
|
|
pstart = COM_Parse( pstart );
|
|
|
|
if ( strlen(com_token) <= 0)
|
|
break;
|
|
|
|
if ( com_token[0] == '{' ) // left paren
|
|
{
|
|
if (!binpreset)
|
|
{
|
|
cpresets++; // found preset:
|
|
blookleft = true; // look for another left
|
|
binpreset = true;
|
|
}
|
|
else
|
|
{
|
|
blookleft = false; // inside preset: next, look for matching right paren
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if ( com_token[0] == '}' ) // right paren
|
|
{
|
|
if (binpreset)
|
|
{
|
|
if (!blookleft) // looking for right paren
|
|
{
|
|
blookleft = true; // found it, now look for another left
|
|
}
|
|
else
|
|
{
|
|
// expected inner left paren, found outer right - end of preset definition
|
|
binpreset = false;
|
|
blookleft = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// error - unexpected } paren
|
|
DevMsg("PARSE ERROR!!! dsp_presets.txt: unexpected '}' \n");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return cpresets;
|
|
}
|
|
|
|
struct dsp_stringmap_t
|
|
{
|
|
char sz[33];
|
|
int i;
|
|
};
|
|
|
|
// token map for dsp_preset.txt
|
|
|
|
dsp_stringmap_t gdsp_stringmap[] =
|
|
{
|
|
// PROCESSOR TYPE:
|
|
{"NULL", PRC_NULL},
|
|
{"DLY", PRC_DLY},
|
|
{"RVA", PRC_RVA},
|
|
{"FLT", PRC_FLT},
|
|
{"CRS", PRC_CRS},
|
|
{"PTC", PRC_PTC},
|
|
{"ENV", PRC_ENV},
|
|
{"LFO", PRC_LFO},
|
|
{"EFO", PRC_EFO},
|
|
{"MDY", PRC_MDY},
|
|
{"DFR", PRC_DFR},
|
|
{"AMP", PRC_AMP},
|
|
|
|
// FILTER TYPE:
|
|
{"LP", FLT_LP},
|
|
{"HP", FLT_HP},
|
|
{"BP", FLT_BP},
|
|
|
|
// FILTER QUALITY:
|
|
{"LO", QUA_LO},
|
|
{"MED", QUA_MED},
|
|
{"HI", QUA_HI},
|
|
{"VHI", QUA_VHI},
|
|
|
|
// DELAY TYPE:
|
|
{"PLAIN", DLY_PLAIN},
|
|
{"ALLPASS", DLY_ALLPASS},
|
|
{"LOWPASS", DLY_LOWPASS},
|
|
{"DLINEAR", DLY_LINEAR},
|
|
{"FLINEAR", DLY_FLINEAR},
|
|
{"LOWPASS_4TAP",DLY_LOWPASS_4TAP},
|
|
{"PLAIN_4TAP", DLY_PLAIN_4TAP},
|
|
|
|
// LFO TYPE:
|
|
{"SIN", LFO_SIN},
|
|
{"TRI", LFO_TRI},
|
|
{"SQR", LFO_SQR},
|
|
{"SAW", LFO_SAW},
|
|
{"RND", LFO_RND},
|
|
{"LOG_IN", LFO_LOG_IN},
|
|
{"LOG_OUT", LFO_LOG_OUT},
|
|
{"LIN_IN", LFO_LIN_IN},
|
|
{"LIN_OUT", LFO_LIN_OUT},
|
|
|
|
// ENVELOPE TYPE:
|
|
{"LIN", ENV_LIN},
|
|
{"EXP", ENV_EXP},
|
|
|
|
// PRESET CONFIGURATION TYPE:
|
|
{"SIMPLE", PSET_SIMPLE},
|
|
{"LINEAR", PSET_LINEAR},
|
|
{"PARALLEL2", PSET_PARALLEL2},
|
|
{"PARALLEL4", PSET_PARALLEL4},
|
|
{"PARALLEL5", PSET_PARALLEL5},
|
|
{"FEEDBACK", PSET_FEEDBACK},
|
|
{"FEEDBACK3", PSET_FEEDBACK3},
|
|
{"FEEDBACK4", PSET_FEEDBACK4},
|
|
{"MOD1", PSET_MOD},
|
|
{"MOD2", PSET_MOD2},
|
|
{"MOD3", PSET_MOD3}
|
|
};
|
|
|
|
int gcdsp_stringmap = sizeof(gdsp_stringmap) / sizeof (dsp_stringmap_t);
|
|
|
|
#define isnumber(c) (c == '+' || c == '-' || c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7'|| c == '8' || c == '9')\
|
|
|
|
// given ptr to null term. string, return integer or float value from g_dsp_stringmap
|
|
|
|
float DSP_LookupStringToken( char *psz, int ipset )
|
|
{
|
|
int i;
|
|
float fipset = (float)ipset;
|
|
|
|
if (isnumber(psz[0]))
|
|
return atof(psz);
|
|
|
|
for (i = 0; i < gcdsp_stringmap; i++)
|
|
{
|
|
if (!strcmpi(gdsp_stringmap[i].sz, psz))
|
|
return gdsp_stringmap[i].i;
|
|
}
|
|
|
|
// not found
|
|
|
|
DevMsg("DSP PARSE ERROR! token not found in dsp_presets.txt. Preset: %3.0f \n", fipset );
|
|
return 0;
|
|
}
|
|
|
|
// load dsp preset file, parse presets into g_psettemplate array
|
|
// format for each preset:
|
|
// { <preset #> <preset type> <#processors> <gain> { <processor type> <param0>...<param15> } {...} {...} }
|
|
|
|
#define CHAR_LEFT_PAREN '{'
|
|
#define CHAR_RIGHT_PAREN '}'
|
|
|
|
// free preset template memory
|
|
|
|
void DSP_ReleaseMemory( void )
|
|
{
|
|
if (g_psettemplates)
|
|
{
|
|
delete[] g_psettemplates;
|
|
g_psettemplates = NULL;
|
|
}
|
|
}
|
|
|
|
bool DSP_LoadPresetFile( void )
|
|
{
|
|
char szFile[ MAX_OSPATH ];
|
|
char *pbuffer;
|
|
const char *pstart;
|
|
bool bResult = false;
|
|
int cpresets;
|
|
int ipreset;
|
|
int itype;
|
|
int cproc;
|
|
float mix_min;
|
|
float mix_max;
|
|
float db_min;
|
|
float db_mixdrop;
|
|
int j;
|
|
bool fdone;
|
|
float duration;
|
|
float fadeout;
|
|
|
|
Q_snprintf( szFile, sizeof( szFile ), "scripts/dsp_presets.txt" );
|
|
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
CUtlBuffer buf;
|
|
|
|
if ( !g_pFullFileSystem->ReadFile( szFile, "GAME", buf ) )
|
|
{
|
|
Error( "DSP_LoadPresetFile: unable to open '%s'\n", szFile );
|
|
return bResult;
|
|
}
|
|
pbuffer = (char *)buf.PeekGet(); // Use malloc - free at end of this routine
|
|
|
|
pstart = pbuffer;
|
|
|
|
// figure out how many presets we're loading - count outer parens.
|
|
|
|
cpresets = DSP_CountFilePresets( pstart );
|
|
|
|
g_cpsettemplates = cpresets;
|
|
|
|
g_psettemplates = new pset_t[cpresets];
|
|
if (!g_psettemplates)
|
|
{
|
|
Warning( "DSP Preset Loader: Out of memory.\n");
|
|
goto load_exit;
|
|
}
|
|
memset (g_psettemplates, 0, cpresets * sizeof(pset_t));
|
|
|
|
|
|
// parse presets into g_psettemplates array
|
|
|
|
pstart = pbuffer;
|
|
|
|
// for each preset...
|
|
|
|
for ( j = 0; j < cpresets; j++)
|
|
{
|
|
// check for end of file or next CHAR_LEFT_PAREN
|
|
|
|
while (1)
|
|
{
|
|
pstart = COM_Parse( pstart );
|
|
|
|
if ( strlen(com_token) <= 0)
|
|
break;
|
|
|
|
if ( com_token[0] != CHAR_LEFT_PAREN )
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
// found start of a new preset definition
|
|
|
|
// get preset #, type, cprocessors, gain
|
|
|
|
pstart = COM_Parse( pstart );
|
|
ipreset = atoi( com_token );
|
|
|
|
pstart = COM_Parse( pstart );
|
|
itype = (int)DSP_LookupStringToken( com_token , ipreset);
|
|
|
|
pstart = COM_Parse( pstart );
|
|
mix_min = atof( com_token );
|
|
|
|
pstart = COM_Parse( pstart );
|
|
mix_max = atof( com_token );
|
|
|
|
pstart = COM_Parse( pstart );
|
|
duration = atof( com_token );
|
|
|
|
pstart = COM_Parse( pstart );
|
|
fadeout = atof( com_token );
|
|
|
|
pstart = COM_Parse( pstart );
|
|
db_min = atof( com_token );
|
|
|
|
pstart = COM_Parse( pstart );
|
|
db_mixdrop = atof( com_token );
|
|
|
|
|
|
g_psettemplates[ipreset].fused = true;
|
|
g_psettemplates[ipreset].mix_min = mix_min;
|
|
g_psettemplates[ipreset].mix_max = mix_max;
|
|
g_psettemplates[ipreset].duration = duration;
|
|
g_psettemplates[ipreset].fade = fadeout;
|
|
g_psettemplates[ipreset].db_min = db_min;
|
|
g_psettemplates[ipreset].db_mixdrop = db_mixdrop;
|
|
|
|
// parse each processor for this preset
|
|
|
|
fdone = false;
|
|
cproc = 0;
|
|
|
|
while (1)
|
|
{
|
|
// find CHAR_LEFT_PAREN - start of new processor
|
|
|
|
while (1)
|
|
{
|
|
pstart = COM_Parse( pstart );
|
|
|
|
if ( strlen(com_token) <= 0)
|
|
break;
|
|
|
|
if (com_token[0] == CHAR_LEFT_PAREN)
|
|
break;
|
|
|
|
if (com_token[0] == CHAR_RIGHT_PAREN)
|
|
{
|
|
// if found right paren, no more processors: done with this preset
|
|
fdone = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( fdone )
|
|
break;
|
|
|
|
// get processor type
|
|
|
|
pstart = COM_Parse( pstart );
|
|
g_psettemplates[ipreset].prcs[cproc].type = (int)DSP_LookupStringToken( com_token, ipreset );
|
|
|
|
// get param 0..n or stop when hit closing CHAR_RIGHT_PAREN
|
|
|
|
int ip = 0;
|
|
|
|
while (1)
|
|
{
|
|
pstart = COM_Parse( pstart );
|
|
|
|
if ( strlen(com_token) <= 0)
|
|
break;
|
|
|
|
if ( com_token[0] == CHAR_RIGHT_PAREN )
|
|
break;
|
|
|
|
g_psettemplates[ipreset].prcs[cproc].prm[ip++] = DSP_LookupStringToken( com_token, ipreset );
|
|
|
|
// cap at max params
|
|
|
|
ip = MIN(ip, CPRCPARAMS);
|
|
}
|
|
|
|
cproc++;
|
|
if (cproc > CPSET_PRCS)
|
|
DevMsg("DSP PARSE ERROR!!! dsp_presets.txt: missing } or too many processors in preset #: %3.0f \n", (float)ipreset);
|
|
cproc = MIN(cproc, CPSET_PRCS); // don't overflow # procs
|
|
}
|
|
|
|
// if cproc == 1, type is always SIMPLE
|
|
|
|
if ( cproc == 1)
|
|
itype = PSET_SIMPLE;
|
|
|
|
g_psettemplates[ipreset].type = itype;
|
|
g_psettemplates[ipreset].cprcs = cproc;
|
|
|
|
}
|
|
|
|
bResult = true;
|
|
|
|
load_exit:
|
|
return bResult;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called by client on level shutdown to clear ear ringing dsp effects
|
|
// could be extended to other stuff
|
|
//-----------------------------------------------------------------------------
|
|
void DSP_FastReset( int dspType )
|
|
{
|
|
int c = ARRAYSIZE( g_PreserveDSP );
|
|
|
|
// Restore
|
|
for ( int i = 0 ; i < c; ++i )
|
|
{
|
|
PreserveDSP_t& slot = g_PreserveDSP[ i ];
|
|
|
|
if ( slot.cvar == &dsp_player )
|
|
{
|
|
slot.oldvalue = dspType;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HandlePresetChange( int nDspIndex, int & nPrevPreset, int nNewPreset, const char * pDspName )
|
|
{
|
|
if ( nPrevPreset == nNewPreset )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( !DSP_IsCrossfading( nDspIndex, true ) )
|
|
{
|
|
DSP_SetPreset( nDspIndex, nNewPreset, pDspName );
|
|
nPrevPreset = nNewPreset;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if ( snd_dsp_spew_changes.GetBool() )
|
|
{
|
|
DevMsg( "[Sound DSP] For Dsp %d, %s changed presets from %d to %d. Have to wait end of cross-fading.\n", nDspIndex, pDspName, nPrevPreset, nNewPreset );
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Helper to check for change in preset of any of 4 processors
|
|
// if switching to a new preset, alloc new preset, simulate both presets in DSP_Process & xfade,
|
|
// called a few times per frame.
|
|
|
|
void CheckNewDspPresets( void )
|
|
{
|
|
bool b_slow_cpu = dsp_slow_cpu.GetBool();
|
|
|
|
DSP_CheckRestorePresets();
|
|
|
|
// room fx are on only if cpu is not slow
|
|
|
|
int iroom = b_slow_cpu ? 0 : dsp_room.GetInt() ;
|
|
int ifacingaway = b_slow_cpu ? 0 : dsp_facingaway.GetInt();
|
|
int iroomtype = b_slow_cpu ? 0 : dsp_room_type.GetInt();
|
|
int ispatial = b_slow_cpu ? 0 : dsp_spatial.GetInt();
|
|
int iautomatic = b_slow_cpu ? 0 : dsp_automatic.GetInt();
|
|
|
|
// always use dsp to process these
|
|
|
|
int iwater = dsp_water.GetInt();
|
|
int iplayer = dsp_player_get();
|
|
int ispeaker = dsp_speaker.GetInt();
|
|
|
|
// check for expired one-shot presets on player and room.
|
|
// Only check if a) no new preset has been set and b) not crossfading from previous preset (ie; previous is null)
|
|
|
|
// Note that in this code we are testing several time against last updated time.
|
|
// The code could be optimized further but fortunately, most times, it not executed
|
|
|
|
if ( iplayer == ipset_player_prev && !DSP_IsCrossfading( idsp_player, false ) )
|
|
{
|
|
if ( DSP_HasExpired ( idsp_player ) )
|
|
{
|
|
iplayer = DSP_OneShotPrevious( idsp_player); // preset has expired - revert to previous preset before one-shot
|
|
dsp_player_set(iplayer);
|
|
}
|
|
}
|
|
|
|
if ( iroom == ipset_room_prev && !DSP_IsCrossfading( idsp_room, false ) )
|
|
{
|
|
if ( DSP_HasExpired ( idsp_room ) )
|
|
{
|
|
iroom = DSP_OneShotPrevious( idsp_room ); // preset has expired - revert to previous preset before one-shot
|
|
dsp_room.SetValue(iroom);
|
|
}
|
|
}
|
|
|
|
|
|
// legacy code support for "room_type" Cvar
|
|
|
|
if ( iroomtype != ipset_room_typeprev )
|
|
{
|
|
// force dsp_room = room_type
|
|
|
|
ipset_room_typeprev = iroomtype;
|
|
dsp_room.SetValue(iroomtype);
|
|
}
|
|
|
|
// NOTE: don't change presets if currently crossfading from a previous preset
|
|
|
|
if ( HandlePresetChange( idsp_room, ipset_room_prev, iroom, "room" ) )
|
|
{
|
|
// Force room_type = dsp_room
|
|
dsp_room_type.SetValue(iroom);
|
|
ipset_room_typeprev = iroom;
|
|
}
|
|
HandlePresetChange( idsp_water, ipset_water_prev, iwater, "water" );
|
|
HandlePresetChange( idsp_player, ipset_player_prev, iplayer, "player" );
|
|
HandlePresetChange( idsp_facingaway, ipset_facingaway_prev, ifacingaway, "facingaway" );
|
|
HandlePresetChange( idsp_speaker, ipset_speaker_prev, ispeaker, "speaker" );
|
|
HandlePresetChange( idsp_spatial, ipset_spatial_prev, ispatial, "spatial" );
|
|
HandlePresetChange( idsp_automatic, ipset_automatic_prev, iautomatic, "automatic" );
|
|
}
|
|
|
|
// create idsp_room preset from set of values, reload the preset.
|
|
// modifies psettemplates in place.
|
|
|
|
// ipreset is the preset # ie: 40
|
|
// iproc is the processor to modify within the preset (typically 0)
|
|
// pvalues is an array of floating point parameters
|
|
// cparams is the # of elements in pvalues
|
|
|
|
// USED FOR DEBUG ONLY.
|
|
|
|
void DSP_DEBUGSetParams(int ipreset, int iproc, float *pvalues, int cparams)
|
|
{
|
|
pset_t new_pset; // preset
|
|
int cparam = iclamp (cparams, 0, CPRCPARAMS);
|
|
prc_t *pprct;
|
|
|
|
// copy template preset from template array
|
|
|
|
new_pset = g_psettemplates[ipreset];
|
|
|
|
// get iproc processor
|
|
|
|
pprct = &(new_pset.prcs[iproc]);
|
|
|
|
// copy parameters in to processor
|
|
|
|
for (int i = 0; i < cparam; i++)
|
|
{
|
|
pprct->prm[i] = pvalues[i];
|
|
}
|
|
|
|
// copy constructed preset back into template location
|
|
|
|
g_psettemplates[ipreset] = new_pset;
|
|
|
|
// setup new preset
|
|
|
|
dsp_room.SetValue( 0 );
|
|
|
|
CheckNewDspPresets();
|
|
|
|
dsp_room.SetValue( ipreset );
|
|
|
|
CheckNewDspPresets();
|
|
}
|
|
|
|
// reload entire preset file, reset all current dsp presets
|
|
// NOTE: this is debug code only. It doesn't do all mem free work correctly!
|
|
|
|
void DSP_DEBUGReloadPresetFile( void )
|
|
{
|
|
int iroom = dsp_room.GetInt();
|
|
int iwater = dsp_water.GetInt();
|
|
int iplayer = dsp_player_get();
|
|
// int ifacingaway = dsp_facingaway.GetInt();
|
|
// int iroomtype = dsp_room_type.GetInt();
|
|
int ispeaker = dsp_speaker.GetInt();
|
|
int ispatial = dsp_spatial.GetInt();
|
|
// int iautomatic = dsp_automatic.GetInt();
|
|
|
|
// reload template array
|
|
|
|
DSP_ReleaseMemory();
|
|
|
|
DSP_LoadPresetFile();
|
|
|
|
// force presets to reload
|
|
|
|
dsp_room.SetValue( 0 );
|
|
dsp_water.SetValue( 0 );
|
|
dsp_player_set( 0 );
|
|
//dsp_facingaway.SetValue( 0 );
|
|
//dsp_room_type.SetValue( 0 );
|
|
dsp_speaker.SetValue( 0 );
|
|
dsp_spatial.SetValue( 0 );
|
|
//dsp_automatic.SetValue( 0 );
|
|
|
|
CheckNewDspPresets();
|
|
|
|
dsp_room.SetValue( iroom );
|
|
dsp_water.SetValue( iwater );
|
|
dsp_player_set( iplayer );
|
|
//dsp_facingaway.SetValue( ifacingaway );
|
|
//dsp_room_type.SetValue( iroomtype );
|
|
dsp_speaker.SetValue( ispeaker );
|
|
dsp_spatial.SetValue( ispatial );
|
|
//dsp_automatic.SetValue( iautomatic );
|
|
|
|
CheckNewDspPresets();
|
|
|
|
// flush dsp automatic nodes
|
|
|
|
g_bdas_init_nodes = 0;
|
|
g_bdas_room_init = 0;
|
|
}
|
|
|
|
// UNDONE: stock reverb presets:
|
|
|
|
// carpet hallway
|
|
// tile hallway
|
|
// wood hallway
|
|
// metal hallway
|
|
|
|
// train tunnel
|
|
// sewer main tunnel
|
|
// concrete access tunnel
|
|
// cave tunnel
|
|
// sand floor cave tunnel
|
|
|
|
// metal duct shaft
|
|
// elevator shaft
|
|
// large elevator shaft
|
|
|
|
// parking garage
|
|
// aircraft hangar
|
|
// cathedral
|
|
// train station
|
|
|
|
// small cavern
|
|
// large cavern
|
|
// huge cavern
|
|
// watery cavern
|
|
// long, low cavern
|
|
|
|
// wood warehouse
|
|
// metal warehouse
|
|
// concrete warehouse
|
|
|
|
// small closet room
|
|
// medium drywall room
|
|
// medium wood room
|
|
// medium metal room
|
|
|
|
// elevator
|
|
// small metal room
|
|
// medium metal room
|
|
// large metal room
|
|
// huge metal room
|
|
|
|
// small metal room dense
|
|
// medium metal room dense
|
|
// large metal room dense
|
|
// huge metal room dense
|
|
|
|
// small concrete room
|
|
// medium concrete room
|
|
// large concrete room
|
|
// huge concrete room
|
|
|
|
// small concrete room dense
|
|
// medium concrete room dense
|
|
// large concrete room dense
|
|
// huge concrete room dense
|
|
|
|
// soundproof room
|
|
// carpet lobby
|
|
// swimming pool
|
|
// open park
|
|
// open courtyard
|
|
// wide parkinglot
|
|
// narrow street
|
|
// wide street, short buildings
|
|
// wide street, tall buildings
|
|
// narrow canyon
|
|
// wide canyon
|
|
// huge canyon
|
|
// small valley
|
|
// wide valley
|
|
// wreckage & rubble
|
|
// small building cluster
|
|
// wide open plain
|
|
// high vista
|
|
|
|
// alien interior small
|
|
// alien interior medium
|
|
// alien interior large
|
|
// alien interior huge
|
|
|
|
// special fx presets:
|
|
|
|
// alien citadel
|
|
|
|
// teleport aftershock (these presets all ADSR timeout and reset the dsp_* to 0)
|
|
// on target teleport
|
|
// off target teleport
|
|
// death fade
|
|
// beam stasis
|
|
// scatterbrain
|
|
// pulse only
|
|
// slomo
|
|
// hypersensitive
|
|
// supershocker
|
|
// physwhacked
|
|
// forcefieldfry
|
|
// juiced
|
|
// zoomed in
|
|
// crabbed
|
|
// barnacle gut
|
|
// bad transmission
|
|
|
|
////////////////////////
|
|
// Dynamics processing
|
|
////////////////////////
|
|
|
|
// compressor defines
|
|
#define COMP_MAX_AMP 32767 // abs max amplitude
|
|
#define COMP_THRESH 20000 // start compressing at this threshold
|
|
|
|
// compress input value - smoothly limit output y to -32767 <= y <= 32767
|
|
// UNDONE: not tested or used
|
|
|
|
inline int S_Compress( int xin )
|
|
{
|
|
|
|
return iclip( xin >> 2 ); // DEBUG - disabled
|
|
|
|
|
|
float Yn, Xn, Cn, Fn;
|
|
float C0 = 20000; // threshold
|
|
float p = .3; // compression ratio
|
|
float g = 1; // gain after compression
|
|
|
|
Xn = (float)xin;
|
|
|
|
// Compressor formula:
|
|
// Cn = l*Cn-1 + (1-l)*|Xn| // peak detector with memory
|
|
// f(Cn) = (Cn/C0)^(p-1) for Cn > C0 // gain function above threshold
|
|
// f(Cn) = 1 for C <= C0 // unity gain below threshold
|
|
// Yn = f(Cn) * Xn // compressor output
|
|
|
|
// UNDONE: curves discontinuous at threshold, causes distortion, try catmul-rom
|
|
|
|
//float l = .5; // compressor memory
|
|
//Cn = l * (*pCnPrev) + (1 - l) * fabs((float)xin);
|
|
//*pCnPrev = Cn;
|
|
|
|
Cn = fabs((float)xin);
|
|
|
|
if (Cn < C0)
|
|
Fn = 1;
|
|
else
|
|
Fn = powf((Cn / C0),(p - 1));
|
|
|
|
Yn = Fn * Xn * g;
|
|
|
|
//if (Cn > 0)
|
|
// Msg("%d -> %d\n", xin, (int)Yn); // DEBUG
|
|
|
|
//if (fabs(Yn) > 32767)
|
|
// Yn = Yn; // DEBUG
|
|
|
|
return (iclip((int)Yn));
|
|
}
|
|
|
|
CON_COMMAND( snd_print_dsp_effect, "Prints the content of a dsp effect." )
|
|
{
|
|
if ( args.ArgC() != 2 )
|
|
{
|
|
Warning( "Incorrect usage of snd_print_dsp_effect. snd_print_dsp_effect <dspindex>.\n" );
|
|
return;
|
|
}
|
|
int nDspIndex = atoi( args.Arg( 1 ) );
|
|
|
|
if ( ( nDspIndex < 0) || ( nDspIndex >= CDSPS ) )
|
|
{
|
|
Warning( "DSP index is out of range. It should be between 0 and %d.\n", CDSPS );
|
|
return;
|
|
}
|
|
|
|
dsp_t *pDsp = &dsps[ nDspIndex ];
|
|
DSP_Print( *pDsp, 0 );
|
|
}
|
|
|