Use a new low-pass filter, based on the I3DL2 spec
Many thanks to Christopher Fitzgerald, for helping with it
This commit is contained in:
parent
a2568409fc
commit
3863dcc9cb
85
Alc/ALu.c
85
Alc/ALu.c
@ -157,31 +157,18 @@ __inline ALuint aluChannelsFromFormat(ALenum format)
|
||||
|
||||
static __inline ALfloat lpFilter(FILTER *iir, ALfloat input)
|
||||
{
|
||||
float *hist1_ptr,*hist2_ptr,*coef_ptr;
|
||||
ALfloat output,new_hist,history1,history2;
|
||||
ALfloat *history = iir->history;
|
||||
ALfloat a = iir->coeff;
|
||||
ALfloat output = input;
|
||||
|
||||
coef_ptr = iir->coef; /* coefficient pointer */
|
||||
|
||||
hist1_ptr = iir->history; /* first history */
|
||||
hist2_ptr = hist1_ptr + 1; /* next history */
|
||||
|
||||
/* 1st number of coefficients array is overall input scale factor,
|
||||
* or filter gain */
|
||||
output = input * (*coef_ptr++);
|
||||
|
||||
history1 = *hist1_ptr; /* history values */
|
||||
history2 = *hist2_ptr;
|
||||
|
||||
output = output - history1 * (*coef_ptr++);
|
||||
new_hist = output - history2 * (*coef_ptr++); /* poles */
|
||||
|
||||
output = new_hist + history1 * (*coef_ptr++);
|
||||
output = output + history2 * (*coef_ptr++); /* zeros */
|
||||
|
||||
*hist2_ptr++ = *hist1_ptr;
|
||||
*hist1_ptr++ = new_hist;
|
||||
hist1_ptr++;
|
||||
hist2_ptr++;
|
||||
output = output + (history[0]-output)*a;
|
||||
history[0] = output;
|
||||
output = output + (history[1]-output)*a;
|
||||
history[1] = output;
|
||||
output = output + (history[2]-output)*a;
|
||||
history[2] = output;
|
||||
output = output + (history[3]-output)*a;
|
||||
history[3] = output;
|
||||
|
||||
return output;
|
||||
}
|
||||
@ -234,10 +221,6 @@ static __inline ALvoid aluMatrixVector(ALfloat *vector,ALfloat matrix[3][3])
|
||||
memcpy(vector, result, sizeof(result));
|
||||
}
|
||||
|
||||
static __inline ALfloat aluComputeSample(ALfloat GainHF, ALfloat sample, ALfloat LowSample)
|
||||
{
|
||||
return LowSample + ((sample - LowSample) * GainHF);
|
||||
}
|
||||
|
||||
static ALvoid CalcSourceParams(ALCcontext *ALContext, ALsource *ALSource,
|
||||
ALenum isMono, ALenum OutputFormat,
|
||||
@ -258,6 +241,7 @@ static ALvoid CalcSourceParams(ALCcontext *ALContext, ALsource *ALSource,
|
||||
ALfloat RoomRolloff;
|
||||
ALfloat DryGainHF = 1.0f;
|
||||
ALfloat WetGainHF = 1.0f;
|
||||
ALfloat cw, a, g;
|
||||
|
||||
//Get context properties
|
||||
DopplerFactor = ALContext->DopplerFactor * ALSource->DopplerFactor;
|
||||
@ -604,6 +588,25 @@ static ALvoid CalcSourceParams(ALCcontext *ALContext, ALsource *ALSource,
|
||||
break;
|
||||
}
|
||||
|
||||
// Update filter coefficients. Calculations based on the I3DL2 spec.
|
||||
cw = cos(2.0f*3.141592654f * LOWPASSFREQCUTOFF / ALContext->Frequency);
|
||||
// We use four chained one-pole filters, so we need to take the fourth
|
||||
// root of the squared gain, which is the same as the square root of
|
||||
// the base gain.
|
||||
// Be careful with gains < 0.0001, as that causes the coefficient to
|
||||
// head towards 1, which will flatten the signal
|
||||
g = aluSqrt(__max(DryGainHF, 0.0001f));
|
||||
a = 0.0f;
|
||||
if(g < 0.9999f) // 1-epsilon
|
||||
a = (1 - g*cw - aluSqrt(2*g*(1-cw) - g*g*(1 - cw*cw))) / (1 - g);
|
||||
ALSource->iirFilter.coeff = a;
|
||||
|
||||
g = aluSqrt(__max(WetGainHF, 0.0001f));
|
||||
a = 0.0f;
|
||||
if(g < 0.9999f) // 1-epsilon
|
||||
a = (1 - g*cw - aluSqrt(2*g*(1-cw) - g*g*(1 - cw*cw))) / (1 - g);
|
||||
ALSource->Send[0].iirFilter.coeff = a;
|
||||
|
||||
*drygainhf = DryGainHF;
|
||||
*wetgainhf = WetGainHF;
|
||||
}
|
||||
@ -648,8 +651,6 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
ALuint rampLength;
|
||||
ALfloat dryGainStep[OUTPUTCHANNELS];
|
||||
ALfloat wetGainStep[OUTPUTCHANNELS];
|
||||
ALfloat dryGainHFStep;
|
||||
ALfloat wetGainHFStep;
|
||||
ALuint BlockAlign,BufferSize;
|
||||
ALuint DataSize=0,DataPosInt=0,DataPosFrac=0;
|
||||
ALuint Channels,Frequency,ulExtraSamples;
|
||||
@ -667,7 +668,7 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
ALbufferlistitem *BufferListItem;
|
||||
ALuint loop;
|
||||
ALint64 DataSize64,DataPos64;
|
||||
FILTER *Filter;
|
||||
FILTER *DryFilter, *WetFilter;
|
||||
int fpuState;
|
||||
|
||||
SuspendContext(ALContext);
|
||||
@ -742,7 +743,8 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
//Get source info
|
||||
DataPosInt = ALSource->position;
|
||||
DataPosFrac = ALSource->position_fraction;
|
||||
Filter = &ALSource->iirFilter;
|
||||
DryFilter = &ALSource->iirFilter;
|
||||
WetFilter = &ALSource->Send[0].iirFilter;
|
||||
DrySend = ALSource->DryGains;
|
||||
WetSend = ALSource->WetGains;
|
||||
|
||||
@ -752,10 +754,6 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
dryGainStep[i] = (newDrySend[i]-DrySend[i]) / rampLength;
|
||||
wetGainStep[i] = (newWetSend[i]-WetSend[i]) / rampLength;
|
||||
}
|
||||
dryGainHFStep = (DryGainHF-ALSource->DryGainHF) / rampLength;
|
||||
wetGainHFStep = (WetGainHF-ALSource->WetGainHF) / rampLength;
|
||||
DryGainHF = ALSource->DryGainHF;
|
||||
WetGainHF = ALSource->WetGainHF;
|
||||
|
||||
//Compute 18.14 fixed point step
|
||||
increment = (ALint)(Pitch*(ALfloat)(1L<<FRACTIONBITS));
|
||||
@ -815,19 +813,16 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
DrySend[i] += dryGainStep[i];
|
||||
WetSend[i] += wetGainStep[i];
|
||||
}
|
||||
DryGainHF += dryGainHFStep;
|
||||
WetGainHF += wetGainHFStep;
|
||||
|
||||
if(Channels==1)
|
||||
{
|
||||
ALfloat sample, lowsamp, outsamp;
|
||||
ALfloat sample, outsamp;
|
||||
//First order interpolator
|
||||
sample = (Data[k]*((1<<FRACTIONBITS)-fraction) +
|
||||
Data[k+1]*fraction) >> FRACTIONBITS;
|
||||
lowsamp = lpFilter(Filter, sample);
|
||||
|
||||
//Direct path final mix buffer and panning
|
||||
outsamp = aluComputeSample(DryGainHF, sample, lowsamp);
|
||||
outsamp = lpFilter(DryFilter, sample);
|
||||
DryBuffer[j][FRONT_LEFT] += outsamp*DrySend[FRONT_LEFT];
|
||||
DryBuffer[j][FRONT_RIGHT] += outsamp*DrySend[FRONT_RIGHT];
|
||||
DryBuffer[j][SIDE_LEFT] += outsamp*DrySend[SIDE_LEFT];
|
||||
@ -835,7 +830,7 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
DryBuffer[j][BACK_LEFT] += outsamp*DrySend[BACK_LEFT];
|
||||
DryBuffer[j][BACK_RIGHT] += outsamp*DrySend[BACK_RIGHT];
|
||||
//Room path final mix buffer and panning
|
||||
outsamp = aluComputeSample(WetGainHF, sample, lowsamp);
|
||||
outsamp = lpFilter(WetFilter, sample);
|
||||
WetBuffer[j][FRONT_LEFT] += outsamp*WetSend[FRONT_LEFT];
|
||||
WetBuffer[j][FRONT_RIGHT] += outsamp*WetSend[FRONT_RIGHT];
|
||||
WetBuffer[j][SIDE_LEFT] += outsamp*WetSend[SIDE_LEFT];
|
||||
@ -923,8 +918,6 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
//Update source info
|
||||
ALSource->position = DataPosInt;
|
||||
ALSource->position_fraction = DataPosFrac;
|
||||
ALSource->DryGainHF = DryGainHF;
|
||||
ALSource->WetGainHF = WetGainHF;
|
||||
}
|
||||
|
||||
//Handle looping sources
|
||||
@ -1015,7 +1008,7 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
ALfloat LateReverbGain = ALEffectSlot->effect.Reverb.LateReverbGain;
|
||||
ALfloat sample, lowsample;
|
||||
|
||||
Filter = &ALEffectSlot->iirFilter;
|
||||
WetFilter = &ALEffectSlot->iirFilter;
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
{
|
||||
sample = WetBuffer[i][FRONT_LEFT] +WetBuffer[i][SIDE_LEFT] +WetBuffer[i][BACK_LEFT];
|
||||
@ -1027,7 +1020,7 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
DelayBuffer[LatePos] *= LateReverbGain;
|
||||
|
||||
Pos = (Pos+1) % Length;
|
||||
lowsample = lpFilter(Filter, DelayBuffer[Pos]);
|
||||
lowsample = lpFilter(WetFilter, DelayBuffer[Pos]);
|
||||
lowsample += (DelayBuffer[Pos]-lowsample) * DecayHFRatio;
|
||||
|
||||
DelayBuffer[LatePos] += lowsample * DecayGain;
|
||||
|
432
Alc/lpfilter.c
432
Alc/lpfilter.c
@ -1,432 +0,0 @@
|
||||
/* ----------------- file filterIIR00.c begin ----------------- */
|
||||
/*
|
||||
Resonant low pass filter source code.
|
||||
By baltrax@hotmail.com (Zxform)
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alFilter.h"
|
||||
|
||||
|
||||
static void szxform(
|
||||
double *a0, double *a1, double *a2, /* numerator coefficients */
|
||||
double *b0, double *b1, double *b2, /* denominator coefficients */
|
||||
double fc, /* Filter cutoff frequency */
|
||||
double fs, /* sampling rate */
|
||||
double *k, /* overall gain factor */
|
||||
float *coef); /* pointer to 4 iir coefficients */
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
*
|
||||
* lpFilter - Perform IIR filtering sample by sample on floats
|
||||
*
|
||||
* Implements cascaded direct form II second order sections.
|
||||
* Requires FILTER structure for history and coefficients.
|
||||
* The size of the history array is 2*FILTER_SECTIONS.
|
||||
* The size of the coefficient array is 4*FILTER_SECTIONS + 1 because
|
||||
* the first coefficient is the overall scale factor for the filter.
|
||||
* Returns one output sample for each input sample.
|
||||
*
|
||||
* float lpFilter(FILTER *iir,float input)
|
||||
*
|
||||
* FILTER *iir pointer to FILTER structure
|
||||
* float input new float input sample
|
||||
*
|
||||
* Returns float value giving the current output.
|
||||
* --------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*** moved to ALu.c ***/
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
*
|
||||
* InitLowPassFilter()
|
||||
*
|
||||
* Initialize filter coefficients.
|
||||
* We create a 2nd order filter (12 db/oct rolloff), consisting
|
||||
* of one second order section.
|
||||
* --------------------------------------------------------------------
|
||||
*/
|
||||
int InitLowPassFilter(ALCcontext *Context, FILTER *iir)
|
||||
{
|
||||
float *coef;
|
||||
double fs, fc; /* Sampling frequency, cutoff frequency */
|
||||
double Q; /* Resonance > 1.0 < 1000 */
|
||||
double a0, a1, a2, b0, b1, b2;
|
||||
double k; /* overall gain factor */
|
||||
struct {
|
||||
double a0, a1, a2; /* numerator coefficients */
|
||||
double b0, b1, b2; /* denominator coefficients */
|
||||
} ProtoCoef; /* Filter prototype coefficients,
|
||||
1 for each filter section */
|
||||
|
||||
|
||||
/*
|
||||
* Setup filter s-domain coefficients
|
||||
*/
|
||||
/* Section 1 */
|
||||
ProtoCoef.a0 = 1.0;
|
||||
ProtoCoef.a1 = 0;
|
||||
ProtoCoef.a2 = 0;
|
||||
ProtoCoef.b0 = 1.0;
|
||||
ProtoCoef.b1 = 1.4142;
|
||||
ProtoCoef.b2 = 1.0;
|
||||
|
||||
/* Clear the coefficient and history arrays */
|
||||
memset(iir->coef, 0, sizeof(iir->coef));
|
||||
memset(iir->history, 0, sizeof(iir->history));
|
||||
|
||||
k = 1.0; /* Set overall filter gain */
|
||||
coef = iir->coef + 1; /* Skip k, or gain */
|
||||
|
||||
Q = 1; /* Resonance */
|
||||
fc = LOWPASSFREQCUTOFF; /* Filter cutoff (Hz) */
|
||||
fs = Context->Frequency; /* Sampling frequency (Hz) */
|
||||
|
||||
/*
|
||||
* Compute z-domain coefficients for each biquad section
|
||||
* for new Cutoff Frequency and Resonance
|
||||
*/
|
||||
a0 = ProtoCoef.a0;
|
||||
a1 = ProtoCoef.a1;
|
||||
a2 = ProtoCoef.a2;
|
||||
|
||||
b0 = ProtoCoef.b0;
|
||||
b1 = ProtoCoef.b1 / Q; /* Divide by resonance or Q */
|
||||
b2 = ProtoCoef.b2;
|
||||
szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef);
|
||||
|
||||
/* Update overall filter gain in coef array */
|
||||
iir->coef[0] = k;
|
||||
|
||||
return 0;
|
||||
}
|
||||
/* ----------------- file filterIIR00.c end ----------------- */
|
||||
|
||||
|
||||
/* ----------------- file bilinear.c begin ----------------- */
|
||||
/*
|
||||
* ----------------------------------------------------------
|
||||
* bilinear.c
|
||||
*
|
||||
* Perform bilinear transformation on s-domain coefficients
|
||||
* of 2nd order biquad section.
|
||||
* First design an analog filter and use s-domain coefficients
|
||||
* as input to szxform() to convert them to z-domain.
|
||||
*
|
||||
* Here's the butterworth polinomials for 2nd, 4th and 6th order sections.
|
||||
* When we construct a 12 db/oct filter, we take a 2nd order
|
||||
* section and compute the coefficients.
|
||||
*
|
||||
* n Polinomials
|
||||
* --------------------------------------------------------------------
|
||||
* 2 s^2 + 1.4142s +1
|
||||
* 4 (s^2 + 0.765367s + 1) (s^2 + 1.847759s + 1)
|
||||
* 6 (s^2 + 0.5176387s + 1) (s^2 + 1.414214 + 1) (s^2 + 1.931852s + 1)
|
||||
*
|
||||
* Where n is a filter order.
|
||||
* For n=2, or one second order section, we have following equasion:
|
||||
*
|
||||
* (1 / (s^2 + (1/Q) * 1.4142s + 1))
|
||||
*
|
||||
* Where Q is filter quality factor in the range of
|
||||
* 1 to 1000. The overall filter Q is a product of all
|
||||
* 2nd order stages. For example, the 6th order filter
|
||||
* (3 stages, or biquads) with individual Q of 2 will
|
||||
* have filter Q = 2 * 2 * 2 = 8.
|
||||
*
|
||||
* The nominator part is just 1.
|
||||
* The denominator coefficients for stage 1 of filter are:
|
||||
* b2 = 1; b1 = 1.4142; b0 = 1;
|
||||
* numerator is
|
||||
* a2 = 0; a1 = 0; a0 = 1;
|
||||
*
|
||||
* These coefficients are used directly by the szxform()
|
||||
* and bilinear() functions. For all stages the numerator
|
||||
* is the same and the only thing that is different between
|
||||
* different stages is 1st order coefficient. The rest of
|
||||
* coefficients are the same for any stage and equal to 1.
|
||||
*
|
||||
* Any filter could be constructed using this approach.
|
||||
*
|
||||
* References:
|
||||
* Van Valkenburg, "Analog Filter Design"
|
||||
* Oxford University Press 1982
|
||||
* ISBN 0-19-510734-9
|
||||
*
|
||||
* C Language Algorithms for Digital Signal Processing
|
||||
* Paul Embree, Bruce Kimble
|
||||
* Prentice Hall, 1991
|
||||
* ISBN 0-13-133406-9
|
||||
*
|
||||
* Digital Filter Designer's Handbook
|
||||
* With C++ Algorithms
|
||||
* Britton Rorabaugh
|
||||
* McGraw Hill, 1997
|
||||
* ISBN 0-07-053806-9
|
||||
* ----------------------------------------------------------
|
||||
*/
|
||||
|
||||
static void prewarp(double *a0, double *a1, double *a2, double fc, double fs);
|
||||
static void bilinear(
|
||||
double a0, double a1, double a2, /* numerator coefficients */
|
||||
double b0, double b1, double b2, /* denominator coefficients */
|
||||
double *k, /* overall gain factor */
|
||||
double fs, /* sampling rate */
|
||||
float *coef); /* pointer to 4 iir coefficients */
|
||||
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------
|
||||
* Pre-warp the coefficients of a numerator or denominator.
|
||||
* Note that a0 is assumed to be 1, so there is no wrapping
|
||||
* of it.
|
||||
* ----------------------------------------------------------
|
||||
*/
|
||||
static void prewarp(
|
||||
double *a0, double *a1, double *a2,
|
||||
double fc, double fs)
|
||||
{
|
||||
static const double pi = 3.141592653589793238;
|
||||
|
||||
double wp = 2.0 * fs * tan(pi * fc / fs);
|
||||
|
||||
*a2 = (*a2) / (wp * wp);
|
||||
*a1 = (*a1) / wp;
|
||||
(void)a0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------
|
||||
* bilinear()
|
||||
*
|
||||
* Transform the numerator and denominator coefficients
|
||||
* of s-domain biquad section into corresponding
|
||||
* z-domain coefficients.
|
||||
*
|
||||
* Store the 4 IIR coefficients in array pointed by coef
|
||||
* in following order:
|
||||
* beta1, beta2 (denominator)
|
||||
* alpha1, alpha2 (numerator)
|
||||
*
|
||||
* Arguments:
|
||||
* a0-a2 - s-domain numerator coefficients
|
||||
* b0-b2 - s-domain denominator coefficients
|
||||
* k - filter gain factor. initially set to 1
|
||||
* and modified by each biquad section in such
|
||||
* a way, as to make it the coefficient by
|
||||
* which to multiply the overall filter gain
|
||||
* in order to achieve a desired overall filter gain,
|
||||
* specified in initial value of k.
|
||||
* fs - sampling rate (Hz)
|
||||
* coef - array of z-domain coefficients to be filled in.
|
||||
*
|
||||
* Return:
|
||||
* On return, set coef z-domain coefficients
|
||||
* ----------------------------------------------------------
|
||||
*/
|
||||
static void bilinear(
|
||||
double a0, double a1, double a2, /* numerator coefficients */
|
||||
double b0, double b1, double b2, /* denominator coefficients */
|
||||
double *k, /* overall gain factor */
|
||||
double fs, /* sampling rate */
|
||||
float *coef /* pointer to 4 iir coefficients */
|
||||
)
|
||||
{
|
||||
double ad, bd;
|
||||
|
||||
/* alpha (Numerator in s-domain) */
|
||||
ad = 4. * a2 * fs * fs + 2. * a1 * fs + a0;
|
||||
/* beta (Denominator in s-domain) */
|
||||
bd = 4. * b2 * fs * fs + 2. * b1* fs + b0;
|
||||
|
||||
/* update gain constant for this section */
|
||||
*k *= ad/bd;
|
||||
|
||||
/* Denominator */
|
||||
*coef++ = (2.*b0 - 8.*b2*fs*fs) / bd; /* beta1 */
|
||||
*coef++ = (4.*b2*fs*fs - 2.*b1*fs + b0) / bd; /* beta2 */
|
||||
|
||||
/* Nominator */
|
||||
*coef++ = (2.*a0 - 8.*a2*fs*fs) / ad; /* alpha1 */
|
||||
*coef = (4.*a2*fs*fs - 2.*a1*fs + a0) / ad; /* alpha2 */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------
|
||||
* Transform from s to z domain using bilinear transform
|
||||
* with prewarp.
|
||||
*
|
||||
* Arguments:
|
||||
* For argument description look at bilinear()
|
||||
*
|
||||
* coef - pointer to array of floating point coefficients,
|
||||
* corresponding to output of bilinear transofrm
|
||||
* (z domain).
|
||||
*
|
||||
* Note: frequencies are in Hz.
|
||||
* ----------------------------------------------------------
|
||||
*/
|
||||
static void szxform(
|
||||
double *a0, double *a1, double *a2, /* numerator coefficients */
|
||||
double *b0, double *b1, double *b2, /* denominator coefficients */
|
||||
double fc, /* Filter cutoff frequency */
|
||||
double fs, /* sampling rate */
|
||||
double *k, /* overall gain factor */
|
||||
float *coef) /* pointer to 4 iir coefficients */
|
||||
{
|
||||
/* Calculate a1 and a2 and overwrite the original values */
|
||||
prewarp(a0, a1, a2, fc, fs);
|
||||
prewarp(b0, b1, b2, fc, fs);
|
||||
bilinear(*a0, *a1, *a2, *b0, *b1, *b2, k, fs, coef);
|
||||
}
|
||||
|
||||
|
||||
/* ----------------- file bilinear.c end ----------------- */
|
||||
|
||||
/* ----------------- file filter.txt begin -----------------
|
||||
How to construct a kewl low pass resonant filter?
|
||||
|
||||
Lets assume we want to create a filter for analog synth.
|
||||
The filter rolloff is 12 db/oct, which corresponds to 2nd
|
||||
order filter. Filter of first order is equivalent to RC circuit
|
||||
and has max rolloff of 6 db/oct.
|
||||
|
||||
We will use classical Butterworth IIR filter design, as it
|
||||
exactly corresponds to our requirements.
|
||||
|
||||
Each 2nd order section is a 2nd order filter, which has 12 db/oct
|
||||
rolloff. So, we only need one section
|
||||
|
||||
To compute those sections, we use standard Butterworth polinomials,
|
||||
or so called s-domain representation and convert it into z-domain,
|
||||
or digital domain. The reason we need to do this is because
|
||||
the filter theory exists for analog filters for a long time
|
||||
and there exist no theory of working in digital domain directly.
|
||||
So the common practice is to take standard analog filter design
|
||||
and use so called bilinear transform to convert the butterworth
|
||||
equasion coefficients into z-domain.
|
||||
|
||||
Once we compute the z-domain coefficients, we can use them in
|
||||
a very simple transfer function, such as iir_filter() in our
|
||||
C source code, in order to perform the filtering function.
|
||||
The filter itself is the simpliest thing in the world.
|
||||
The most complicated thing is computing the coefficients
|
||||
for z-domain.
|
||||
|
||||
Ok, lets look at butterworth polynomials, arranged as a series
|
||||
of 2nd order sections:
|
||||
|
||||
* Note: n is filter order.
|
||||
*
|
||||
* n Polynomials
|
||||
* --------------------------------------------------------------------
|
||||
* 2 s^2 + 1.4142s +1
|
||||
* 4 (s^2 + 0.765367s + 1) * (s^2 + 1.847759s + 1)
|
||||
* 6 (s^2 + 0.5176387s + 1) * (s^2 + 1.414214 + 1) * (s^2 + 1.931852s + 1)
|
||||
*
|
||||
* For n=2 we have following equasion for the filter transfer function:
|
||||
*
|
||||
* 1
|
||||
* T(s) = -------------------------
|
||||
* s^2 + (1/Q) * 1.4142s + 1
|
||||
*
|
||||
|
||||
The filter consists of one 2nd order section since highest s power is 2.
|
||||
Now we can take the coefficients, or the numbers by which s is multiplied
|
||||
and plug them into a standard formula to be used by bilinear transform.
|
||||
|
||||
Our standard form for each 2nd order secion is:
|
||||
|
||||
a2 * s^2 + a1 * s + a0
|
||||
H(s) = ----------------------
|
||||
b2 * s^2 + b1 * s + b0
|
||||
|
||||
Note that butterworth nominator is 1 for all filter sections,
|
||||
which means s^2 = 0 and s^1 = 0
|
||||
|
||||
Lets convert standard butterworth polinomials into this form:
|
||||
|
||||
0 + 0 + 1
|
||||
------------------------
|
||||
1 + ((1/Q) * 1.4142) + 1
|
||||
|
||||
Section 1:
|
||||
a2 = 0; a1 = 0; a0 = 1;
|
||||
b2 = 1; b1 = 1.4142; b0 = 1;
|
||||
|
||||
That Q is filter quality factor or resonance, in the range of
|
||||
1 to 1000. The overall filter Q is a product of all 2nd order stages.
|
||||
For example, the 6th order filter (3 stages, or biquads)
|
||||
with individual Q of 2 will have filter Q = 2 * 2 * 2 = 8.
|
||||
|
||||
These a and b coefficients are used directly by the szxform()
|
||||
and bilinear() functions.
|
||||
|
||||
The transfer function for z-domain is:
|
||||
|
||||
1 + alpha1 * z^(-1) + alpha2 * z^(-2)
|
||||
H(z) = -------------------------------------
|
||||
1 + beta1 * z^(-1) + beta2 * z^(-2)
|
||||
|
||||
When you need to change the filter frequency cutoff or resonance,
|
||||
or Q, you call the szxform() function with proper a and b
|
||||
coefficients and the new filter cutoff frequency or resonance.
|
||||
You also need to supply the sampling rate and filter gain you want
|
||||
to achive. For our purposes the gain = 1.
|
||||
|
||||
We call szxform() function 1 time becase we have 1 filter section.
|
||||
Each call provides different coefficients.
|
||||
|
||||
The gain argument to szxform() is a pointer to desired filter
|
||||
gain variable.
|
||||
|
||||
double k = 1.0; // overall gain factor
|
||||
|
||||
Upon return from each call, the k argument will be set to a value,
|
||||
by which to multiply our actual signal in order for the gain
|
||||
to be one. On following calls to szxform() we provide k that was
|
||||
changed by the previous section. During actual audio filtering
|
||||
function iir_filter() will use this k
|
||||
|
||||
Summary:
|
||||
|
||||
Our filter is pretty close to ideal in terms of all relevant
|
||||
parameters and filter stability even with extremely large values
|
||||
of resonance. This filter design has been verified under all
|
||||
variations of parameters and it all appears to work as advertized.
|
||||
|
||||
Good luck with it.
|
||||
If you ever make a directX wrapper for it, post it to comp.dsp.
|
||||
|
||||
|
||||
*
|
||||
* ----------------------------------------------------------
|
||||
*References:
|
||||
*Van Valkenburg, "Analog Filter Design"
|
||||
*Oxford University Press 1982
|
||||
*ISBN 0-19-510734-9
|
||||
*
|
||||
*C Language Algorithms for Digital Signal Processing
|
||||
*Paul Embree, Bruce Kimble
|
||||
*Prentice Hall, 1991
|
||||
*ISBN 0-13-133406-9
|
||||
*
|
||||
*Digital Filter Designer's Handbook
|
||||
*With C++ Algorithms
|
||||
*Britton Rorabaugh
|
||||
*McGraw Hill, 1997
|
||||
*ISBN 0-07-053806-9
|
||||
* ----------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
// ----------------- file filter.txt end ----------------- */
|
@ -231,7 +231,6 @@ SET(ALC_OBJS Alc/ALc.c
|
||||
Alc/alcRing.c
|
||||
Alc/alcThread.c
|
||||
Alc/bs2b.c
|
||||
Alc/lpfilter.c
|
||||
Alc/wave.c
|
||||
)
|
||||
|
||||
|
@ -8,8 +8,8 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
float history[2]; /* history in filter */
|
||||
float coef[4 + 1]; /* coefficients of filter */
|
||||
ALfloat history[4];
|
||||
ALfloat coeff;
|
||||
} FILTER;
|
||||
|
||||
#define AL_FILTER_TYPE 0x8001
|
||||
@ -53,8 +53,6 @@ ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues
|
||||
|
||||
ALvoid ReleaseALFilters(ALvoid);
|
||||
|
||||
int InitLowPassFilter(ALCcontext *Context, FILTER *iir);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -69,6 +69,7 @@ typedef struct ALsource
|
||||
struct {
|
||||
struct ALeffectslot *Slot;
|
||||
ALfilter WetFilter;
|
||||
FILTER iirFilter;
|
||||
} Send[MAX_SENDS];
|
||||
|
||||
ALboolean DryGainHFAuto;
|
||||
@ -98,8 +99,6 @@ typedef struct ALsource
|
||||
// Current gains, which are ramped while mixed
|
||||
ALfloat DryGains[OUTPUTCHANNELS];
|
||||
ALfloat WetGains[OUTPUTCHANNELS];
|
||||
ALfloat DryGainHF;
|
||||
ALfloat WetGainHF;
|
||||
|
||||
struct ALsource *next;
|
||||
} ALsource;
|
||||
|
@ -71,8 +71,6 @@ ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots)
|
||||
break;
|
||||
}
|
||||
|
||||
InitLowPassFilter(Context, &(*list)->iirFilter);
|
||||
|
||||
(*list)->Gain = 1.0;
|
||||
(*list)->AuxSendAuto = AL_TRUE;
|
||||
(*list)->refcount = 0;
|
||||
|
@ -75,8 +75,6 @@ ALAPI ALvoid ALAPIENTRY alGenSources(ALsizei n,ALuint *sources)
|
||||
break;
|
||||
}
|
||||
|
||||
InitLowPassFilter(Context, &(*list)->iirFilter);
|
||||
|
||||
sources[i] = (ALuint)ALTHUNK_ADDENTRY(*list);
|
||||
(*list)->source = sources[i];
|
||||
|
||||
@ -1406,8 +1404,6 @@ ALAPI ALvoid ALAPIENTRY alSourcePlayv(ALsizei n, const ALuint *pSourceList)
|
||||
pSource->DryGains[j] = 0.0f;
|
||||
pSource->WetGains[j] = 0.0f;
|
||||
}
|
||||
pSource->DryGainHF = 0.0f;
|
||||
pSource->WetGainHF = 0.0f;
|
||||
|
||||
if (pSource->state != AL_PAUSED)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user