AudioUnitGeneratorExample/Utility/Biquad.cpp
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
*/ |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad.cpp |
// |
// A generic biquad (two zero - two pole) IIR filter: |
// |
// lopass |
// hipass |
// parametric peaking |
// low shelving |
// high shelving |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
#include "Biquad.h" |
#include "ComplexNumber.h" |
#include <math.h> |
#define _PI 3.14159 |
const float kSquareRootOf2 = sqrt(2.); |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetK() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
static void GetK( float inFreq, // normalized to 0 -> 1 |
float &outK, |
float &outKSquared, |
float &outInvDenom ) |
{ |
float k = tan(_PI*0.5 * inFreq); |
outK = k; |
outKSquared = k*k; |
outInvDenom = 1.0 / (1 + kSquareRootOf2*k + outKSquared ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetLopassParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetLopassParams( float inFreq, |
float &a0, |
float &a1, |
float &a2, |
float &b1, |
float &b2 ) |
{ |
float k; |
float k_squared; |
float inv_denom; |
GetK(inFreq, k, k_squared, inv_denom ); |
a0 = k_squared * inv_denom; |
a1 = 2 * k_squared * inv_denom; |
a2 = k_squared * inv_denom; |
b1 = 2*(k_squared-1) * inv_denom; |
b2 = (1 - kSquareRootOf2*k + k_squared) * inv_denom; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetLopassParams() |
// |
// This version accepts a resonance parameter |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetLopassParams( float inFreq, |
float inResonance, |
float &a0, |
float &a1, |
float &a2, |
float &b1, |
float &b2 ) |
{ |
double d = pow(10.0, -inResonance/20.0); |
double temp = 0.5 * d * sin(_PI * inFreq); |
double beta = 0.5 * (1.0 - temp) / (1.0 + temp); |
double gamma = (0.5 + beta) * cos(_PI * inFreq); |
double alpha = (0.5 + beta - gamma) * 0.25; |
a0 = 2.0 * alpha; |
a1 = 2.0 * 2.0 * alpha; |
a2 = 2.0 * alpha; |
b1 = 2.0 * -gamma; |
b2 = 2.0 * beta; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetNotchParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetNotchParams( float inFreq, |
float inQ ) |
{ |
// restrict Q |
const double kQLimit = inFreq + 0.01; |
if(inQ < kQLimit) inQ = kQLimit; |
double theta0 = _PI * inFreq; |
double temp = tan(theta0/(2.0*inQ) ); |
double beta = 0.5 * (1.0 - temp) / (1.0 + temp); |
double gamma = (0.5 + beta) * cos(theta0); |
double alpha = 0.5 * (0.5 + beta); |
mA0 = 2.0 * alpha; |
mA1 = 2.0 * -gamma; |
mA2 = 2.0 * alpha; |
mB1 = 2.0 * -gamma; |
mB2 = 2.0 * beta; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetHipassParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetHipassParams( float inFreq, |
float &a0, |
float &a1, |
float &a2, |
float &b1, |
float &b2 ) |
{ |
float k; |
float k_squared; |
float inv_denom; |
GetK(inFreq, k, k_squared, inv_denom ); |
a0 = inv_denom; |
a1 = -2 * inv_denom; |
a2 = inv_denom; |
b1 = 2*(k_squared-1) * inv_denom; |
b2 = (1 - kSquareRootOf2*k + k_squared) * inv_denom; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetHipassParams() |
// |
// This version accepts a resonance parameter |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetHipassParams( float inFreq, |
float inResonance, |
float &a0, |
float &a1, |
float &a2, |
float &b1, |
float &b2 ) |
{ |
double d = pow(10.0, -inResonance/20.0); |
double temp = 0.5 * d * sin(_PI * inFreq); |
double beta = 0.5 * (1.0 - temp) / (1.0 + temp); |
double gamma = (0.5 + beta) * cos(_PI * inFreq); |
double alpha = (0.5 + beta + gamma) * 0.25; |
a0 = 2.0 * alpha; |
a1 = 2.0 * -2.0 * alpha; |
a2 = 2.0 * alpha; |
b1 = 2.0 * -gamma; |
b2 = 2.0 * beta; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetLowShelfParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetLowShelfParams( float inFreq, |
float inDbGain, // +/- 40dB |
float &outA0, |
float &outA1, |
float &outA2, |
float &outB1, |
float &outB2) |
{ |
float freq = inFreq; |
float omega = _PI * freq; |
float gain = inDbGain; |
float sn = sin(omega); |
float cs = cos(omega); |
float S = 1.0; |
float A = pow(10.0, (gain * 0.025 ) ); |
float Am = A - 1.0; |
float Ap = A + 1.0; |
float beta = sqrt( (A*A + 1.0)/S - Am*Am ); |
float b0 = A*( Ap - Am*cs + beta*sn ); |
float b1 = 2*A*( Am - Ap*cs ); |
float b2 = A*( Ap - Am*cs - beta*sn ); |
float a0 = Ap + Am*cs + beta*sn; |
float a1 = -2*( Am + Ap*cs ); |
float a2 = Ap + Am*cs - beta*sn; |
float a0_inv = 1.0 / a0; |
// this looks wrong, but it's OK |
outA0 = b0*a0_inv; |
outA1 = b1*a0_inv; |
outA2 = b2*a0_inv; |
outB1 = a1*a0_inv; |
outB2 = a2*a0_inv; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetHighShelfParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetHighShelfParams( float inFreq, |
float inDbGain, // +/- 40dB |
float &outA0, |
float &outA1, |
float &outA2, |
float &outB1, |
float &outB2) |
{ |
float freq = inFreq; |
float omega = _PI * freq; |
float gain = inDbGain; |
float sn = sin(omega); |
float cs = cos(omega); |
float S = 1.0; |
float A = pow(10.0, (gain * 0.025 ) ); |
float Am = A - 1.0; |
float Ap = A + 1.0; |
float beta = sqrt( (A*A + 1.0)/S - Am*Am ); |
float b0 = A*( Ap + Am*cs + beta*sn ); |
float b1 = -2*A*( Am + Ap*cs ); |
float b2 = A*( Ap + Am*cs - beta*sn ); |
float a0 = Ap - Am*cs + beta*sn; |
float a1 = 2*( Am - Ap*cs ); |
float a2 = Ap - Am*cs - beta*sn; |
float a0_inv = 1.0 / a0; |
// this looks wrong, but it's OK |
outA0 = b0*a0_inv; |
outA1 = b1*a0_inv; |
outA2 = b2*a0_inv; |
outB1 = a1*a0_inv; |
outB2 = a2*a0_inv; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::Biquad() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
Biquad::Biquad() |
{ |
mA0 = 1.0; |
mA1 = 0.0; |
mA2 = 0.0; |
mB1 = 0.0; |
mB2 = 0.0; |
Reset(); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::Reset() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::Reset() |
{ |
mX1 = 0.0; |
mX2 = 0.0; |
mY1 = 0.0; |
mY2 = 0.0; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetLopassParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetLopassParams( float inFreq ) |
{ |
GetLopassParams(inFreq, mA0, mA1, mA2, mB1, mB2 ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetLopassParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetLopassParams( float inFreq, float inResonance ) |
{ |
GetLopassParams(inFreq, inResonance, mA0, mA1, mA2, mB1, mB2 ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetHipassParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetHipassParams( float inFreq ) |
{ |
GetHipassParams(inFreq, mA0, mA1, mA2, mB1, mB2 ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetHipassParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetHipassParams( float inFreq, float inResonance ) |
{ |
GetHipassParams(inFreq, inResonance, mA0, mA1, mA2, mB1, mB2 ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetLowShelfParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetLowShelfParams( float inFreq, float inDbGain ) |
{ |
GetLowShelfParams(inFreq, inDbGain, mA0, mA1, mA2, mB1, mB2 ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetHighShelfParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetHighShelfParams( float inFreq, float inDbGain ) |
{ |
GetHighShelfParams(inFreq, inDbGain, mA0, mA1, mA2, mB1, mB2 ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::GetAllpassParams() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::GetAllpassParams( const Complex &inComplexPole ) |
{ |
SetPoleConjugateRoot(inComplexPole); |
mA0 = mB2; |
mA1 = mB1; |
mA2 = 1.0; /* mB0 */ |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::PolarToRect() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::PolarToRect( float inTheta, |
float inMag, |
float &outX, |
float &outY ) |
{ |
outX = cos(inTheta) * inMag; |
outY = sin(inTheta) * inMag; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::ConjugateRootToQuadCoeffs() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::ConjugateRootToQuadCoeffs( float inTheta, |
float inMag, |
float &out0, |
float &out1, |
float &out2 ) |
{ |
float x; |
float y; |
PolarToRect(inTheta, inMag, x, y ); |
out0 = 1.0; |
out1 = -2.0 * x; |
out2 = x*x + y*y; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::RealRootsToQuadCoeffs() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::RealRootsToQuadCoeffs( float inRoot1, |
float inRoot2, |
float &out0, |
float &out1, |
float &out2 ) |
{ |
// (x - inRoot1) * (x - inRoot2) |
// |
// x^2 + (-inRoot1 -inRoot2)*x + inRoot1*inRoot2 |
// |
out0 = 1.0; |
out1 = -inRoot1 -inRoot2; |
out2 = inRoot1*inRoot2; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::SetZeroConjugateRoot() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::SetZeroConjugateRoot( float inZeroTheta, |
float inZeroMag ) |
{ |
mA0 = 0.0; |
mA1 = 0.0; |
mA2 = 0.0; |
ConjugateRootToQuadCoeffs(inZeroTheta, inZeroMag, mA0, mA1, mA2 ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::SetPoleConjugateRoot() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::SetPoleConjugateRoot( float inPoleTheta, |
float inPoleMag ) |
{ |
mB1 = 0.0; |
mB2 = 0.0; |
float b0; |
ConjugateRootToQuadCoeffs(inPoleTheta, inPoleMag, b0, mB1, mB2 ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::SetZeroConjugateRoot() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::SetZeroConjugateRoot( const Complex &inComplexZero) |
{ |
mA0 = 1.0; |
mA1 = -2.0 * inComplexZero.GetReal(); |
double mag = inComplexZero.GetMagnitude(); |
mA2 = mag*mag; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::SetPoleConjugateRoot() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::SetPoleConjugateRoot( const Complex &inComplexPole) |
{ |
mB1 = -2.0 * inComplexPole.GetReal(); |
double mag = inComplexPole.GetMagnitude(); |
mB2 = mag*mag; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::SetZeroRealRoots() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::SetZeroRealRoots( float inRoot1, |
float inRoot2 ) |
{ |
RealRootsToQuadCoeffs( inRoot1, |
inRoot2, |
mA0, |
mA1, |
mA2 ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::SetPoleRealRoots() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::SetPoleRealRoots( float inRoot1, |
float inRoot2 ) |
{ |
float b0; |
RealRootsToQuadCoeffs( inRoot1, |
inRoot2, |
b0, |
mB1, |
mB2 ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Biquad::Process() |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
void Biquad::Process( const float *inSourceP, |
float *inDestP, |
int inFramesToProcess, |
int inInputNumberOfChannels, |
int inOutputNumberOfChannels |
) |
{ |
int nSampleFrames = inFramesToProcess; |
const float *sourceP = inSourceP; |
float *destP = inDestP; |
register float x1, y1, x2, y2; |
register float a0, a1, a2, b1, b2; |
//load class data into register |
x1 = mX1; |
y1 = mY1; |
x2 = mX2; |
y2 = mY2; |
a0 = mA0; |
a1 = mA1; |
a2 = mA2; |
b1 = mB1; |
b2 = mB2; |
if( nSampleFrames >= 4 ) |
{ |
register float xA, xB, xC, xD, xE; |
register float stage1, stage2, stage3, stage4, stage5A, stage5B, temp; |
// |
// In this section we process bulk data in a staggered loop. The calculation |
// that we do is fairly simply written as: |
// |
// while(nSampleFrames-- > 0) |
// { |
// float x = *sourceP; |
// sourceP += inInputNumberOfChannels; |
// |
// float y = a0*x + a1*x1 + a2*x2 - b1*y1 - b2*y2; |
// |
// x2 = x1; |
// x1 = x; |
// y2 = y1; |
// y1 = y; |
// |
// *destP = y; |
// destP += inOutputNumberOfChannels; |
// } |
// |
// In order to pad out all of the serial data stalls with work, the |
// loop is reorganized as a staggered loop. The main calculation: |
// |
// float y = a0*x + a1*x1 + a2*x2 - b1*y1 - b2*y2; |
// |
// ...is performed by the compiler as five instructions: |
// |
// fmulls stage1, a0, x #stage 1 |
// fmadds stage2, a1, x1, stage1 #stage 2 |
// fmadds stage3, a2, x2, stage2 #stage 3 |
// fnmsubs stage4, b1, y1, stage3 #stage 4 |
// fnmsubs stage5, b2, y2, stage4 #stage 5 |
// |
// The each calculation depends on the result from the last |
// so there are a lot of serial data dependencies causing |
// pipeline stalls here. In addition, as y1 and y2 depend on |
// the results of the previous calculation, there is a dependency |
// there too. |
// |
// To loosen up all of this dependency so that we can execute |
// with a minimum of pipeline stalls, we make use of staggered |
// loops. |
// |
// A staggered loop is organized such that at any given time, we are |
// calculating 5 results, and each of the five calculations is |
// currently working on a different stage. It is in some ways analogous |
// to a processor pipeline, with loop iterations in place of clock cycles: |
// |
// stage 1 stage 2 stage 3 stage 4 stage 5 store result |
// ------- ------- ------- ------- ------- ------------ |
// iter 1: calc 1 ---> |
// iter 2: calc 2 calc 1 ---> |
// iter 3: calc 3 calc 2 calc 1 ---> |
// iter 4: calc 4 calc 3 calc 2 calc 1 ---> |
// iter 5: calc 5 calc 4 calc 3 calc 2 calc 1 ---> |
// iter 6: calc 6 calc 5 calc 4 calc 3 calc 2 calc 1 stored |
// . |
// . |
// . |
// |
// In order to achieve this in software, we have to do the first 4 loop iterations |
// separately to avoid writing out garbage data while we wait for the pipeline to fill. |
// That is what we do first: |
//Build up a staggered loop |
//pseudo loop iteration 1 |
xA = sourceP[0]; sourceP += inInputNumberOfChannels; |
stage1 = a0 * xA; |
//pseudo loop iteration 2 |
xB = sourceP[0]; sourceP += inInputNumberOfChannels; |
stage2 = stage1 + a1 * x1; |
stage1 = a0 * xB; |
//pseudo loop iteration 3 |
xC = sourceP[0]; sourceP += inInputNumberOfChannels; |
stage3 = stage2 + a2 * x2; |
stage2 = stage1 + a1 * xA; |
stage1 = a0 * xC; |
//pseudo loop iteration 4 |
xD = sourceP[0]; sourceP += inInputNumberOfChannels; |
stage4 = stage3 - b1 * y1; |
stage3 = stage2 + a2 * x1; |
stage2 = stage1 + a1 * xB; |
stage1 = a0 * xD; |
// |
// Now the pipeline is full, so we can loop as long as we have data |
// to work on. Once a piece of data is loaded in from the input array, |
// it is used three times over a total of 5 loop iterations. For that reason |
// the loop is unrolled here five fold. If we dont do that then we waste lots |
// of time copying registers around. This lets us do each loop iteration |
// in only six cycles on PPC 7400 / 7410. It is probably 5 on PPC 7450. |
// |
//Set up a little bit of data for entry into the loop |
stage5A = y2; |
stage5B = y1; |
nSampleFrames -= 4; |
// Execute a staggered loop until we run out of input samples |
while( nSampleFrames >= 5 ) |
{ |
//pseudo loop 5 |
xE = sourceP[0]; sourceP += inInputNumberOfChannels; |
stage5A = stage4 - b2 * stage5A; |
stage4 = stage3 - b1 * stage5A; |
stage3 = stage2 + a2 * xA; |
stage2 = stage1 + a1 * xC; |
stage1 = a0 * xE; |
destP[0] = stage5A; destP += inOutputNumberOfChannels; |
//pseudo loop 6 |
xA = sourceP[0]; sourceP += inInputNumberOfChannels; |
stage5B = stage4 - b2 * stage5B; |
stage4 = stage3 - b1 * stage5B; |
stage3 = stage2 + a2 * xB; |
stage2 = stage1 + a1 * xD; |
stage1 = a0 * xA; |
destP[0] = stage5B; destP += inOutputNumberOfChannels; |
//pseudo loop 7 |
xB = sourceP[0]; sourceP += inInputNumberOfChannels; |
stage5A = stage4 - b2 * stage5A; |
stage4 = stage3 - b1 * stage5A; |
stage3 = stage2 + a2 * xC; |
stage2 = stage1 + a1 * xE; |
stage1 = a0 * xB; |
destP[0] = stage5A; destP += inOutputNumberOfChannels; |
//These last two are slightly different. That is because we have |
//five subloops in the loop, which means that our stage5A, stage5B |
//oscillation comes out odd instead of even. |
// |
//The simple solution is to swap stage5A and stage5B at the end of the large |
//loop: |
// |
// temp = stage5B; |
// stage5B = stage5A; |
// stage5A = temp; |
// |
//However it is more efficient to do the swap as we do the calculation |
//since this means we dont have to call fmr as much. Thus, in this |
//next pseudo loop, temp receives the result of the stage 5 calculation |
//instead of stage5B. |
//pseudo loop 8 |
xC = sourceP[0]; sourceP += inInputNumberOfChannels; |
temp = stage4 - b2 * stage5B; |
stage4 = stage3 - b1 * temp; |
stage3 = stage2 + a2 * xD; |
stage2 = stage1 + a1 * xA; |
stage1 = a0 * xC; |
destP[0] = temp /*stage5B*/; destP += inOutputNumberOfChannels; |
// |
// In this pseudo loop, our swap continues: stage5B receives the |
// stage5 result, rather than stage5A |
// |
//pseudo loop 9 |
xD = sourceP[0]; sourceP += inInputNumberOfChannels; |
stage5B = stage4 - b2 * stage5A; |
stage4 = stage3 - b1 * stage5B; |
stage3 = stage2 + a2 * xE; |
stage2 = stage1 + a1 * xB; |
stage1 = a0 * xD; |
destP[0] = stage5B /*stage5A*/; destP += inOutputNumberOfChannels; |
//The final step in the swap |
stage5A = temp; |
//decrement our loop counter |
nSampleFrames -= 5; |
} |
// |
// Now we have to deal with the problem that we have no input data coming in |
// so we need more special case code as the pipeline empties. We cant just reuse the |
// internal loop code because the loads would step off the end of the array on to |
// possible unmapped memory. |
// |
// Here we finish out the last four stages for our four remaining calculations: |
// |
//trailing pseudo loop 1 |
stage5A = stage4 - b2 * stage5A; |
stage4 = stage3 - b1 * stage5A; |
stage3 = stage2 + a2 * xA; |
stage2 = stage1 + a1 * xC; |
destP[0] = stage5A; destP += inOutputNumberOfChannels; |
//trailing pseudo loop 2 |
stage5B = stage4 - b2 * stage5B; |
stage4 = stage3 - b1 * stage5B; |
stage3 = stage2 + a2 * xB; |
destP[0] = stage5B; destP += inOutputNumberOfChannels; |
//trailing pseudo loop 3 |
stage5A = stage4 - b2 * stage5A; |
stage4 = stage3 - b1 * stage5A; |
destP[0] = stage5A; destP += inOutputNumberOfChannels; |
//trailing pseudo loop 4 |
stage5B = stage4 - b2 * stage5B; |
destP[0] = stage5B; destP += inOutputNumberOfChannels; |
//Put our data back into x1, y1, x2 and y2 |
x1 = xD; |
x2 = xC; |
y1 = stage5B; |
y2 = stage5A; |
} |
//Deal with the last few frames |
//This calculation is the same as the one above, but since |
//we dont have enough data to do anything fancy, |
//we just do the simple variant here: |
while(nSampleFrames-- > 0) |
{ |
float x = *sourceP; |
sourceP += inInputNumberOfChannels; |
float y = a0*x + a1*x1 + a2*x2 - b1*y1 - b2*y2; |
x2 = x1; |
x1 = x; |
y2 = y1; |
y1 = y; |
*destP = y; |
destP += inOutputNumberOfChannels; |
} |
//save our register values for x1, y1, x2, and y2 for posterity |
mX1 = x1; |
mY1 = y1; |
mX2 = x2; |
mY2 = y2; |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-02-19