Implement yet another low-pass filter
This one using the Butterworth IIR filter design
This commit is contained in:
parent
e2ed8ff2bf
commit
c7e49c9f57
25
Alc/ALu.c
25
Alc/ALu.c
@ -596,8 +596,6 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
ALfloat Pitch;
|
||||
ALint Looping,State;
|
||||
ALint fraction,increment;
|
||||
ALint LowFrac;
|
||||
ALuint LowStep;
|
||||
ALuint Buffer;
|
||||
ALuint SamplesToDo;
|
||||
ALsource *ALSource;
|
||||
@ -609,6 +607,7 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
ALbufferlistitem *BufferListItem;
|
||||
ALuint loop;
|
||||
ALint64 DataSize64,DataPos64;
|
||||
FILTER *Filter;
|
||||
|
||||
SuspendContext(ALContext);
|
||||
|
||||
@ -667,6 +666,7 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
//Get source info
|
||||
DataPosInt = ALSource->position;
|
||||
DataPosFrac = ALSource->position_fraction;
|
||||
Filter = &ALSource->iirFilter;
|
||||
|
||||
//Compute 18.14 fixed point step
|
||||
increment = (ALint)(Pitch*(ALfloat)(1L<<FRACTIONBITS));
|
||||
@ -680,8 +680,6 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
DataPos64 <<= FRACTIONBITS;
|
||||
DataPos64 += DataPosFrac;
|
||||
BufferSize = (ALuint)((DataSize64-DataPos64+(increment-1)) / increment);
|
||||
LowStep = Frequency/LOWPASSFREQCUTOFF;
|
||||
if(LowStep < 1) LowStep = 1;
|
||||
|
||||
BufferListItem = ALSource->queue;
|
||||
for(loop = 0; loop < ALSource->BuffersPlayed; loop++)
|
||||
@ -718,16 +716,14 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
{
|
||||
k = DataPosFrac>>FRACTIONBITS;
|
||||
fraction = DataPosFrac&FRACTIONMASK;
|
||||
LowFrac = ((DataPosFrac+(DataPosInt<<FRACTIONBITS))/LowStep)&FRACTIONMASK;
|
||||
|
||||
if(Channels==1)
|
||||
{
|
||||
ALfloat sample, lowsamp, outsamp;
|
||||
//First order interpolator
|
||||
sample = (Data[k]*((1<<FRACTIONBITS)-fraction) +
|
||||
Data[k+1]*fraction) >> FRACTIONBITS;
|
||||
lowsamp = (Data[((k+DataPosInt)/LowStep )*LowStep - DataPosInt]*((1<<FRACTIONBITS)-LowFrac) +
|
||||
Data[((k+DataPosInt)/LowStep + 1)*LowStep - DataPosInt]*LowFrac) >>
|
||||
FRACTIONBITS;
|
||||
lowsamp = lpFilter(Filter, sample);
|
||||
|
||||
//Direct path final mix buffer and panning
|
||||
outsamp = aluComputeSample(DryGainHF, sample, lowsamp);
|
||||
@ -915,9 +911,9 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
ALfloat Gain = ALEffectSlot->effect.Reverb.Gain;
|
||||
ALfloat ReflectGain = ALEffectSlot->effect.Reverb.ReflectionsGain;
|
||||
ALfloat LateReverbGain = ALEffectSlot->effect.Reverb.LateReverbGain;
|
||||
ALfloat LastDecaySample = ALEffectSlot->LastDecaySample;
|
||||
ALfloat sample;
|
||||
ALfloat sample, lowsample;
|
||||
|
||||
Filter = &ALEffectSlot->iirFilter;
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
{
|
||||
DelayBuffer[Pos] = ReverbBuffer[i] * Gain;
|
||||
@ -927,12 +923,10 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
DelayBuffer[LatePos] *= LateReverbGain;
|
||||
|
||||
Pos = (Pos+1) % Length;
|
||||
DelayBuffer[Pos] *= DecayHFRatio;
|
||||
DelayBuffer[Pos] += LastDecaySample * (1.0f-DecayHFRatio);
|
||||
LastDecaySample = DelayBuffer[Pos];
|
||||
DelayBuffer[Pos] *= DecayGain;
|
||||
lowsample = lpFilter(Filter, DelayBuffer[Pos]);
|
||||
lowsample += (DelayBuffer[Pos]-lowsample) * DecayHFRatio;
|
||||
|
||||
DelayBuffer[LatePos] += DelayBuffer[Pos];
|
||||
DelayBuffer[LatePos] += lowsample * DecayGain;
|
||||
|
||||
sample += DelayBuffer[LatePos];
|
||||
|
||||
@ -950,7 +944,6 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
|
||||
ALEffectSlot->ReverbPos = Pos;
|
||||
ALEffectSlot->ReverbLatePos = LatePos;
|
||||
ALEffectSlot->ReverbReflectPos = ReflectPos;
|
||||
ALEffectSlot->LastDecaySample = LastDecaySample;
|
||||
}
|
||||
|
||||
ALEffectSlot = ALEffectSlot->next;
|
||||
|
509
Alc/lpfilter.c
Normal file
509
Alc/lpfilter.c
Normal file
@ -0,0 +1,509 @@
|
||||
/* ----------------- 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"
|
||||
|
||||
|
||||
#define FILTER_SECTIONS 2 /* 2 filter sections for 24 db/oct filter */
|
||||
|
||||
|
||||
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.
|
||||
* --------------------------------------------------------------------
|
||||
*/
|
||||
float lpFilter(FILTER *iir, float input)
|
||||
{
|
||||
unsigned int i;
|
||||
float *hist1_ptr,*hist2_ptr,*coef_ptr;
|
||||
float output,new_hist,history1,history2;
|
||||
|
||||
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++);
|
||||
|
||||
for(i = 0;i < FILTER_SECTIONS;i++)
|
||||
{
|
||||
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++;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
*
|
||||
* InitLowPassFilter()
|
||||
*
|
||||
* Initialize filter coefficients.
|
||||
* We create a 4th order filter (24 db/oct rolloff), consisting
|
||||
* of two second order sections.
|
||||
* --------------------------------------------------------------------
|
||||
*/
|
||||
int InitLowPassFilter(ALCcontext *Context, FILTER *iir)
|
||||
{
|
||||
float *coef;
|
||||
double fs, fc; /* Sampling frequency, cutoff frequency */
|
||||
double Q; /* Resonance > 1.0 < 1000 */
|
||||
unsigned nInd;
|
||||
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_SECTIONS]; /* Filter prototype coefficients,
|
||||
1 for each filter section */
|
||||
|
||||
|
||||
/*
|
||||
* Setup filter s-domain coefficients
|
||||
*/
|
||||
/* Section 1 */
|
||||
ProtoCoef[0].a0 = 1.0;
|
||||
ProtoCoef[0].a1 = 0;
|
||||
ProtoCoef[0].a2 = 0;
|
||||
ProtoCoef[0].b0 = 1.0;
|
||||
ProtoCoef[0].b1 = 0.765367;
|
||||
ProtoCoef[0].b2 = 1.0;
|
||||
|
||||
/* Section 2 */
|
||||
ProtoCoef[1].a0 = 1.0;
|
||||
ProtoCoef[1].a1 = 0;
|
||||
ProtoCoef[1].a2 = 0;
|
||||
ProtoCoef[1].b0 = 1.0;
|
||||
ProtoCoef[1].b1 = 1.847759;
|
||||
ProtoCoef[1].b2 = 1.0;
|
||||
|
||||
/*
|
||||
* Allocate array of z-domain coefficients for each filter section
|
||||
* plus filter gain variable
|
||||
*/
|
||||
iir->coef = (float*)calloc(4*FILTER_SECTIONS + 1, sizeof(float));
|
||||
if(!iir->coef)
|
||||
{
|
||||
AL_PRINT("Unable to allocate coef array\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* allocate history array */
|
||||
iir->history = (float*)calloc(2*FILTER_SECTIONS, sizeof(float));
|
||||
if(!iir->history) {
|
||||
AL_PRINT("\nUnable to allocate history array\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
*/
|
||||
for (nInd = 0; nInd < FILTER_SECTIONS; nInd++)
|
||||
{
|
||||
a0 = ProtoCoef[nInd].a0;
|
||||
a1 = ProtoCoef[nInd].a1;
|
||||
a2 = ProtoCoef[nInd].a2;
|
||||
|
||||
b0 = ProtoCoef[nInd].b0;
|
||||
b1 = ProtoCoef[nInd].b1 / Q; /* Divide by resonance or Q */
|
||||
b2 = ProtoCoef[nInd].b2;
|
||||
szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef);
|
||||
coef += 4; /* Point to next filter section */
|
||||
}
|
||||
|
||||
/* 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 24 db/oct filter, we take to 2nd order
|
||||
* sections and compute the coefficients separately for each section.
|
||||
*
|
||||
* 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=4, or two second order sections, we have following equasions for each
|
||||
* 2nd order stage:
|
||||
*
|
||||
* (1 / (s^2 + (1/Q) * 0.765367s + 1)) * (1 / (s^2 + (1/Q) * 1.847759s + 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 = 0.765367; b0 = 1;
|
||||
* numerator is
|
||||
* a2 = 0; a1 = 0; a0 = 1;
|
||||
*
|
||||
* The denominator coefficients for stage 1 of filter are:
|
||||
* b2 = 1; b1 = 1.847759; 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)
|
||||
{
|
||||
double wp, pi;
|
||||
|
||||
pi = 4.0 * atan(1.0);
|
||||
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 24 db/oct, which corresponds to 4th
|
||||
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.
|
||||
|
||||
A common practice is to chain several 2nd order sections,
|
||||
or biquads, as they commonly called, in order to achive a higher
|
||||
order filter. Each 2nd order section is a 2nd order filter, which
|
||||
has 12 db/oct roloff. So, we need 2 of those sections in series.
|
||||
|
||||
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=4 we have following equasion for the filter transfer function:
|
||||
*
|
||||
* 1 1
|
||||
* T(s) = --------------------------- * ----------------------------
|
||||
* s^2 + (1/Q) * 0.765367s + 1 s^2 + (1/Q) * 1.847759s + 1
|
||||
*
|
||||
|
||||
The filter consists of two 2nd order secions 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 0 + 0 + 1
|
||||
-------------------------- * --------------------------
|
||||
1 + ((1/Q) * 0.765367) + 1 1 + ((1/Q) * 1.847759) + 1
|
||||
|
||||
Section 1:
|
||||
a2 = 0; a1 = 0; a0 = 1;
|
||||
b2 = 1; b1 = 0.5176387; b0 = 1;
|
||||
|
||||
Section 2:
|
||||
a2 = 0; a1 = 0; a0 = 1;
|
||||
b2 = 1; b1 = 1.847759; 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 2 times becase we have 2 filter sections.
|
||||
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 second call 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 ----------------- */
|
@ -225,6 +225,7 @@ SET(ALC_OBJS Alc/ALc.c
|
||||
Alc/alcRing.c
|
||||
Alc/alcThread.c
|
||||
Alc/bs2b.c
|
||||
Alc/lpfilter.c
|
||||
Alc/wave.c
|
||||
)
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
#ifndef _AL_AUXEFFECTSLOT_H_
|
||||
#define _AL_AUXEFFECTSLOT_H_
|
||||
|
||||
#include "alEffect.h"
|
||||
#include "AL/al.h"
|
||||
#include "alEffect.h"
|
||||
#include "alFilter.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -28,7 +29,8 @@ typedef struct ALeffectslot
|
||||
ALuint ReverbReflectPos;
|
||||
ALuint ReverbLatePos;
|
||||
ALfloat ReverbDecayGain;
|
||||
ALfloat LastDecaySample;
|
||||
|
||||
FILTER iirFilter;
|
||||
|
||||
ALuint refcount;
|
||||
|
||||
|
@ -7,6 +7,11 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
float *history; /* pointer to history in filter */
|
||||
float *coef; /* pointer to coefficients of filter */
|
||||
} FILTER;
|
||||
|
||||
#define AL_FILTER_TYPE 0x8001
|
||||
|
||||
#define AL_FILTER_NULL 0x0000
|
||||
@ -48,6 +53,9 @@ AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pf
|
||||
|
||||
ALvoid ReleaseALFilters(ALvoid);
|
||||
|
||||
float lpFilter(FILTER *iir, float input);
|
||||
int InitLowPassFilter(ALCcontext *Context, FILTER *iir);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -75,6 +75,8 @@ typedef struct ALsource
|
||||
ALboolean WetGainHFAuto;
|
||||
ALfloat OuterGainHF;
|
||||
|
||||
FILTER iirFilter;
|
||||
|
||||
ALfloat AirAbsorptionFactor;
|
||||
|
||||
ALfloat RoomRolloffFactor;
|
||||
|
@ -71,6 +71,8 @@ AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslo
|
||||
break;
|
||||
}
|
||||
|
||||
InitLowPassFilter(Context, &(*list)->iirFilter);
|
||||
|
||||
(*list)->Gain = 1.0;
|
||||
(*list)->AuxSendAuto = AL_TRUE;
|
||||
(*list)->refcount = 0;
|
||||
@ -139,6 +141,9 @@ AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, ALuint *effect
|
||||
|
||||
ALAuxiliaryEffectSlot = ((ALeffectslot*)ALTHUNK_LOOKUPENTRY(effectslots[i]));
|
||||
|
||||
free(ALAuxiliaryEffectSlot->iirFilter.coef);
|
||||
free(ALAuxiliaryEffectSlot->iirFilter.history);
|
||||
|
||||
// Remove Source from list of Sources
|
||||
list = &Context->AuxiliaryEffectSlot;
|
||||
while(*list && *list != ALAuxiliaryEffectSlot)
|
||||
|
@ -75,6 +75,8 @@ ALAPI ALvoid ALAPIENTRY alGenSources(ALsizei n,ALuint *sources)
|
||||
break;
|
||||
}
|
||||
|
||||
InitLowPassFilter(Context, &(*list)->iirFilter);
|
||||
|
||||
sources[i] = (ALuint)ALTHUNK_ADDENTRY(*list);
|
||||
(*list)->source = sources[i];
|
||||
|
||||
@ -179,6 +181,9 @@ ALAPI ALvoid ALAPIENTRY alDeleteSources(ALsizei n, const ALuint *sources)
|
||||
ALSource->Send[j].Slot = NULL;
|
||||
}
|
||||
|
||||
free(ALSource->iirFilter.coef);
|
||||
free(ALSource->iirFilter.history);
|
||||
|
||||
// Decrement Source count
|
||||
Context->SourceCount--;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user