/* By Daniel Klostermann Iowa Hills Software, LLC IowaHills.com If you find a problem, please leave a note at: http://www.iowahills.com/feedbackcomments.html May 1, 2016 ShowMessage is a C++ Builder function, and it usage has been commented out. If you are using C++ Builder, include vcl.h for ShowMessage. Otherwise replace ShowMessage with something appropriate for your compiler. See the FilterKitMain.cpp file for an example on how to use this code. */ #include "IIRFilterCode.h" #include "PFiftyOneRevE.h" #include "LowPassPrototypes.h" #include #include namespace kitiirfir { //--------------------------------------------------------------------------- /* This calculates the coefficients for IIR filters from a set of 2nd order s plane coefficients which are obtained by calling CalcLowPassProtoCoeff() in LowPassPrototypes.cpp. The s plane filters are frequency scaled so their 3 dB frequency is at s = omega = 1 rad/sec. The poles and zeros are also ordered in a manner appropriate for IIR filters. For a derivation of the formulas used here, see the IIREquationDerivations.pdf This shows how the various poly coefficients are defined. H(s) = ( Ds^2 + Es + F ) / ( As^2 + Bs + C ) H(z) = ( b2z^2 + b1z + b0 ) / ( a2z^2 + a1z + a0 ) */ TIIRCoeff CalcIIRFilterCoeff(TIIRFilterParams IIRFilt) { int j, k; double Scalar, SectionGain, Coeff[5]; double A, B, C, D, E, F, T, Q, Arg; double a2[ARRAY_DIM], a1[ARRAY_DIM], a0[ARRAY_DIM]; double b2[ARRAY_DIM], b1[ARRAY_DIM], b0[ARRAY_DIM]; std::complex Roots[5]; TIIRCoeff IIR; // Gets returned by this function. TLowPassParams LowPassFilt; // Passed to the CalcLowPassProtoCoeff() function. TSPlaneCoeff SPlaneCoeff; // Filled by the CalcLowPassProtoCoeff() function. // We can set the TLowPassParams variables directly from the TIIRFilterParams variables. LowPassFilt.ProtoType = IIRFilt.ProtoType; LowPassFilt.NumPoles = IIRFilt.NumPoles; LowPassFilt.Ripple = IIRFilt.Ripple; LowPassFilt.Gamma = IIRFilt.Gamma; LowPassFilt.StopBanddB = IIRFilt.StopBanddB; // Get the low pass prototype 2nd order s plane coefficients. SPlaneCoeff = CalcLowPassProtoCoeff(LowPassFilt); // Init the IIR structure. for (j = 0; j < ARRAY_DIM; j++) { IIR.a0[j] = 0.0; IIR.b0[j] = 0.0; IIR.a1[j] = 0.0; IIR.b1[j] = 0.0; IIR.a2[j] = 0.0; IIR.b2[j] = 0.0; IIR.a3[j] = 0.0; IIR.b3[j] = 0.0; IIR.a4[j] = 0.0; IIR.b4[j] = 0.0; } // Set the number of IIR filter sections we will be generating. IIR.NumSections = (IIRFilt.NumPoles + 1) / 2; if (IIRFilt.IIRPassType == iirBPF || IIRFilt.IIRPassType == iirNOTCH) IIR.NumSections = IIRFilt.NumPoles; // For All Pass filters, the numerator is set to the denominator values as shown here. // If the prototype was an Inv Cheby or Elliptic, the S plane numerator is discarded. // Use the Gauss as the prototype for the best all pass results (most linear phase). // The all pass H(s) = ( As^2 - Bs + C ) / ( As^2 + Bs + C ) if (IIRFilt.IIRPassType == iirALLPASS) { for (j = 0; j < SPlaneCoeff.NumSections; j++) { SPlaneCoeff.N2[j] = SPlaneCoeff.D2[j]; SPlaneCoeff.N1[j] = -SPlaneCoeff.D1[j]; SPlaneCoeff.N0[j] = SPlaneCoeff.D0[j]; } } // T sets the IIR filter's corner frequency, or center freqency. // The Bilinear transform is defined as: s = 2/T * tan(Omega/2) = 2/T * (1 - z)/(1 + z) T = 2.0 * tan(IIRFilt.OmegaC * M_PI_2); Q = 1.0 + IIRFilt.OmegaC; // Q is used for band pass and notch filters. if (Q > 1.95) Q = 1.95; Q = 0.8 * tan(Q * M_PI_4); // This is a correction factor for Q. Q = IIRFilt.OmegaC / IIRFilt.BW / Q; // This is the corrected Q. // Calc the IIR coefficients. // SPlaneCoeff.NumSections is the number of 1st and 2nd order s plane factors. k = 0; for (j = 0; j < SPlaneCoeff.NumSections; j++) { A = SPlaneCoeff.D2[j]; // We use A - F to make the code easier to read. B = SPlaneCoeff.D1[j]; C = SPlaneCoeff.D0[j]; D = SPlaneCoeff.N2[j]; E = SPlaneCoeff.N1[j]; // N1 is always zero, except for the all pass. Consequently, the equations below can be simplified a bit by removing E. F = SPlaneCoeff.N0[j]; // b's are the numerator a's are the denominator if (IIRFilt.IIRPassType == iirLPF || IIRFilt.IIRPassType == iirALLPASS) // Low Pass and All Pass { if (A == 0.0 && D == 0.0) // 1 pole case { Arg = (2.0 * B + C * T); IIR.a2[j] = 0.0; IIR.a1[j] = (-2.0 * B + C * T) / Arg; IIR.a0[j] = 1.0; IIR.b2[j] = 0.0; IIR.b1[j] = (-2.0 * E + F * T) / Arg * C / F; IIR.b0[j] = (2.0 * E + F * T) / Arg * C / F; } else // 2 poles { Arg = (4.0 * A + 2.0 * B * T + C * T * T); IIR.a2[j] = (4.0 * A - 2.0 * B * T + C * T * T) / Arg; IIR.a1[j] = (2.0 * C * T * T - 8.0 * A) / Arg; IIR.a0[j] = 1.0; // With all pole filters, our LPF numerator is (z+1)^2, so all our Z Plane zeros are at -1 IIR.b2[j] = (4.0 * D - 2.0 * E * T + F * T * T) / Arg * C / F; IIR.b1[j] = (2.0 * F * T * T - 8.0 * D) / Arg * C / F; IIR.b0[j] = (4 * D + F * T * T + 2.0 * E * T) / Arg * C / F; } } if (IIRFilt.IIRPassType == iirHPF) // High Pass { if (A == 0.0 && D == 0.0) // 1 pole { Arg = 2.0 * C + B * T; IIR.a2[j] = 0.0; IIR.a1[j] = (B * T - 2.0 * C) / Arg; IIR.a0[j] = 1.0; IIR.b2[j] = 0.0; IIR.b1[j] = (E * T - 2.0 * F) / Arg * C / F; IIR.b0[j] = (E * T + 2.0 * F) / Arg * C / F; } else // 2 poles { Arg = A * T * T + 4.0 * C + 2.0 * B * T; IIR.a2[j] = (A * T * T + 4.0 * C - 2.0 * B * T) / Arg; IIR.a1[j] = (2.0 * A * T * T - 8.0 * C) / Arg; IIR.a0[j] = 1.0; // With all pole filters, our HPF numerator is (z-1)^2, so all our Z Plane zeros are at 1 IIR.b2[j] = (D * T * T - 2.0 * E * T + 4.0 * F) / Arg * C / F; IIR.b1[j] = (2.0 * D * T * T - 8.0 * F) / Arg * C / F; IIR.b0[j] = (D * T * T + 4.0 * F + 2.0 * E * T) / Arg * C / F; } } if (IIRFilt.IIRPassType == iirBPF) // Band Pass { if (A == 0.0 && D == 0.0) // 1 pole { Arg = 4.0 * B * Q + 2.0 * C * T + B * Q * T * T; a2[k] = (B * Q * T * T + 4.0 * B * Q - 2.0 * C * T) / Arg; a1[k] = (2.0 * B * Q * T * T - 8.0 * B * Q) / Arg; a0[k] = 1.0; b2[k] = (E * Q * T * T + 4.0 * E * Q - 2.0 * F * T) / Arg * C / F; b1[k] = (2.0 * E * Q * T * T - 8.0 * E * Q) / Arg * C / F; b0[k] = (4.0 * E * Q + 2.0 * F * T + E * Q * T * T) / Arg * C / F; k++; } else //2 Poles { IIR.a4[j] = (16.0 * A * Q * Q + A * Q * Q * T * T * T * T + 8.0 * A * Q * Q * T * T - 2.0 * B * Q * T * T * T - 8.0 * B * Q * T + 4.0 * C * T * T) * F; IIR.a3[j] = (4.0 * T * T * T * T * A * Q * Q - 4.0 * Q * T * T * T * B + 16.0 * Q * B * T - 64.0 * A * Q * Q) * F; IIR.a2[j] = (96.0 * A * Q * Q - 16.0 * A * Q * Q * T * T + 6.0 * A * Q * Q * T * T * T * T - 8.0 * C * T * T) * F; IIR.a1[j] = (4.0 * T * T * T * T * A * Q * Q + 4.0 * Q * T * T * T * B - 16.0 * Q * B * T - 64.0 * A * Q * Q) * F; IIR.a0[j] = (16.0 * A * Q * Q + A * Q * Q * T * T * T * T + 8.0 * A * Q * Q * T * T + 2.0 * B * Q * T * T * T + 8.0 * B * Q * T + 4.0 * C * T * T) * F; // With all pole filters, our BPF numerator is (z-1)^2 * (z+1)^2 so the zeros come back as +/- 1 pairs IIR.b4[j] = (8.0 * D * Q * Q * T * T - 8.0 * E * Q * T + 16.0 * D * Q * Q - 2.0 * E * Q * T * T * T + D * Q * Q * T * T * T * T + 4.0 * F * T * T) * C; IIR.b3[j] = (16.0 * E * Q * T - 4.0 * E * Q * T * T * T - 64.0 * D * Q * Q + 4.0 * D * Q * Q * T * T * T * T) * C; IIR.b2[j] = (96.0 * D * Q * Q - 8.0 * F * T * T + 6.0 * D * Q * Q * T * T * T * T - 16.0 * D * Q * Q * T * T) * C; IIR.b1[j] = (4.0 * D * Q * Q * T * T * T * T - 64.0 * D * Q * Q + 4.0 * E * Q * T * T * T - 16.0 * E * Q * T) * C; IIR.b0[j] = (16.0 * D * Q * Q + 8.0 * E * Q * T + 8.0 * D * Q * Q * T * T + 2.0 * E * Q * T * T * T + 4.0 * F * T * T + D * Q * Q * T * T * T * T) * C; // T = 2 makes these values approach 0.0 (~ 1.0E-12) The root solver needs 0.0 for numerical reasons. if (fabs(T - 2.0) < 0.0005) { IIR.a3[j] = 0.0; IIR.a1[j] = 0.0; IIR.b3[j] = 0.0; IIR.b1[j] = 0.0; } // We now have a 4th order poly in the form a4*s^4 + a3*s^3 + a2*s^2 + a2*s + a0 // We find the roots of this so we can form two 2nd order polys. Coeff[0] = IIR.a4[j]; Coeff[1] = IIR.a3[j]; Coeff[2] = IIR.a2[j]; Coeff[3] = IIR.a1[j]; Coeff[4] = IIR.a0[j]; FindRoots(4, Coeff, Roots); // In effect, the root finder scales the poly by 1/a4 so we have to apply this factor back into // the two 2nd order equations we are forming. Scalar = sqrt(fabs(IIR.a4[j])); // Form the two 2nd order polys from the roots. a2[k] = Scalar; a1[k] = -(Roots[0] + Roots[1]).real() * Scalar; a0[k] = (Roots[0] * Roots[1]).real() * Scalar; k++; a2[k] = Scalar; a1[k] = -(Roots[2] + Roots[3]).real() * Scalar; a0[k] = (Roots[2] * Roots[3]).real() * Scalar; k--; // Now do the same with the numerator. Coeff[0] = IIR.b4[j]; Coeff[1] = IIR.b3[j]; Coeff[2] = IIR.b2[j]; Coeff[3] = IIR.b1[j]; Coeff[4] = IIR.b0[j]; if (IIRFilt.ProtoType == INVERSE_CHEBY || IIRFilt.ProtoType == ELLIPTIC) { FindRoots(4, Coeff, Roots); } else // With all pole filters (Butter, Cheb, etc), we know we have these 4 real roots. The root finder won't necessarily pair real roots the way we need, so rather than compute these, we simply set them. { Roots[0] = std::complex(-1.0, 0.0); Roots[1] = std::complex(1.0, 0.0); Roots[2] = std::complex(-1.0, 0.0); Roots[3] = std::complex(1.0, 0.0); } Scalar = sqrt(fabs(IIR.b4[j])); b2[k] = Scalar; if (IIRFilt.ProtoType == INVERSE_CHEBY || IIRFilt.ProtoType == ELLIPTIC) { b1[k] = -(Roots[0] + Roots[1]).real() * Scalar; // = 0.0 } else // else the prototype is an all pole filter { b1[k] = 0.0; // b1 = 0 for all pole filters, but the addition above won't always equal zero exactly. } b0[k] = (Roots[0] * Roots[1]).real() * Scalar; k++; b2[k] = Scalar; if (IIRFilt.ProtoType == INVERSE_CHEBY || IIRFilt.ProtoType == ELLIPTIC) { b1[k] = -(Roots[2] + Roots[3]).real() * Scalar; } else // All pole { b1[k] = 0.0; } b0[k] = (Roots[2] * Roots[3]).real() * Scalar; k++; // Go below to see where we store these 2nd order polys back into IIR } } if (IIRFilt.IIRPassType == iirNOTCH) // Notch { if (A == 0.0 && D == 0.0) // 1 pole { Arg = 2.0 * B * T + C * Q * T * T + 4.0 * C * Q; a2[k] = (4.0 * C * Q - 2.0 * B * T + C * Q * T * T) / Arg; a1[k] = (2.0 * C * Q * T * T - 8.0 * C * Q) / Arg; a0[k] = 1.0; b2[k] = (4.0 * F * Q - 2.0 * E * T + F * Q * T * T) / Arg * C / F; b1[k] = (2.0 * F * Q * T * T - 8.0 * F * Q) / Arg * C / F; b0[k] = (2.0 * E * T + F * Q * T * T + 4.0 * F * Q) / Arg * C / F; k++; } else { IIR.a4[j] = (4.0 * A * T * T - 2.0 * B * T * T * T * Q + 8.0 * C * Q * Q * T * T - 8.0 * B * T * Q + C * Q * Q * T * T * T * T + 16.0 * C * Q * Q) * -F; IIR.a3[j] = (16.0 * B * T * Q + 4.0 * C * Q * Q * T * T * T * T - 64.0 * C * Q * Q - 4.0 * B * T * T * T * Q) * -F; IIR.a2[j] = (96.0 * C * Q * Q - 8.0 * A * T * T - 16.0 * C * Q * Q * T * T + 6.0 * C * Q * Q * T * T * T * T) * -F; IIR.a1[j] = (4.0 * B * T * T * T * Q - 16.0 * B * T * Q - 64.0 * C * Q * Q + 4.0 * C * Q * Q * T * T * T * T) * -F; IIR.a0[j] = (4.0 * A * T * T + 2.0 * B * T * T * T * Q + 8.0 * C * Q * Q * T * T + 8.0 * B * T * Q + C * Q * Q * T * T * T * T + 16.0 * C * Q * Q) * -F; // Our Notch Numerator isn't simple. [ (4+T^2)*z^2 - 2*(4-T^2)*z + (4+T^2) ]^2 IIR.b4[j] = (2.0 * E * T * T * T * Q - 4.0 * D * T * T - 8.0 * F * Q * Q * T * T + 8.0 * E * T * Q - 16.0 * F * Q * Q - F * Q * Q * T * T * T * T) * C; IIR.b3[j] = (64.0 * F * Q * Q + 4.0 * E * T * T * T * Q - 16.0 * E * T * Q - 4.0 * F * Q * Q * T * T * T * T) * C; IIR.b2[j] = (8.0 * D * T * T - 96.0 * F * Q * Q + 16.0 * F * Q * Q * T * T - 6.0 * F * Q * Q * T * T * T * T) * C; IIR.b1[j] = (16.0 * E * T * Q - 4.0 * E * T * T * T * Q + 64.0 * F * Q * Q - 4.0 * F * Q * Q * T * T * T * T) * C; IIR.b0[j] = (-4.0 * D * T * T - 2.0 * E * T * T * T * Q - 8.0 * E * T * Q - 8.0 * F * Q * Q * T * T - F * Q * Q * T * T * T * T - 16.0 * F * Q * Q) * C; // T = 2 (OmegaC = 0.5) makes these values approach 0.0 (~ 1.0E-12). The root solver wants 0.0 for numerical reasons. if (fabs(T - 2.0) < 0.0005) { IIR.a3[j] = 0.0; IIR.a1[j] = 0.0; IIR.b3[j] = 0.0; IIR.b1[j] = 0.0; } // We now have a 4th order poly in the form a4*s^4 + a3*s^3 + a2*s^2 + a2*s + a0 // We find the roots of this so we can form two 2nd order polys. Coeff[0] = IIR.a4[j]; Coeff[1] = IIR.a3[j]; Coeff[2] = IIR.a2[j]; Coeff[3] = IIR.a1[j]; Coeff[4] = IIR.a0[j]; // In effect, the root finder scales the poly by 1/a4 so we have to apply this factor back into // the two 2nd order equations we are forming. FindRoots(4, Coeff, Roots); Scalar = sqrt(fabs(IIR.a4[j])); a2[k] = Scalar; a1[k] = -(Roots[0] + Roots[1]).real() * Scalar; a0[k] = (Roots[0] * Roots[1]).real() * Scalar; k++; a2[k] = Scalar; a1[k] = -(Roots[2] + Roots[3]).real() * Scalar; a0[k] = (Roots[2] * Roots[3]).real() * Scalar; k--; // Now do the same with the numerator. Coeff[0] = IIR.b4[j]; Coeff[1] = IIR.b3[j]; Coeff[2] = IIR.b2[j]; Coeff[3] = IIR.b1[j]; Coeff[4] = IIR.b0[j]; FindRoots(4, Coeff, Roots); Scalar = sqrt(fabs(IIR.b4[j])); b2[k] = Scalar; b1[k] = -(Roots[0] + Roots[1]).real() * Scalar; b0[k] = (Roots[0] * Roots[1]).real() * Scalar; k++; b2[k] = Scalar; b1[k] = -(Roots[2] + Roots[3]).real() * Scalar; b0[k] = (Roots[2] * Roots[3]).real() * Scalar; k++; } } } if (IIRFilt.IIRPassType == iirBPF || IIRFilt.IIRPassType == iirNOTCH) { // In the calcs above for the BPF and Notch, we didn't set a0=1, so we do it here. for (j = 0; j < IIR.NumSections; j++) { b2[j] /= a0[j]; b1[j] /= a0[j]; b0[j] /= a0[j]; a2[j] /= a0[j]; a1[j] /= a0[j]; a0[j] = 1.0; } for (j = 0; j < IIR.NumSections; j++) { IIR.a0[j] = a0[j]; IIR.a1[j] = a1[j]; IIR.a2[j] = a2[j]; IIR.b0[j] = b0[j]; IIR.b1[j] = b1[j]; IIR.b2[j] = b2[j]; } } // Adjust the b's or a0 for the desired Gain. SectionGain = pow(10.0, IIRFilt.dBGain / 20.0); SectionGain = pow(SectionGain, 1.0 / (double) IIR.NumSections); for (j = 0; j < IIR.NumSections; j++) { IIR.b0[j] *= SectionGain; IIR.b1[j] *= SectionGain; IIR.b2[j] *= SectionGain; // This is an alternative to adjusting the b's // IIR.a0[j] = SectionGain; } return (IIR); } //--------------------------------------------------------------------------- // This code implements an IIR filter as a Form 1 Biquad. // It uses 2 sets of shift registers, RegX on the input side and RegY on the output side. // There are many ways to implement an IIR filter, some very good, and some extremely bad. // For numerical reasons, a Form 1 Biquad implementation is among the best. void FilterWithIIR(TIIRCoeff IIRCoeff, double *Signal, double *FilteredSignal, int NumSigPts) { double y; int j, k; for (j = 0; j < NumSigPts; j++) { k = 0; y = SectCalc(j, k, Signal[j], IIRCoeff); for (k = 1; k < IIRCoeff.NumSections; k++) { y = SectCalc(j, k, y, IIRCoeff); } FilteredSignal[j] = y; } } //--------------------------------------------------------------------------- // This gets used with the function above, FilterWithIIR() // Note the use of MaxRegVal to avoid a math overflow condition. double SectCalc(int j, int k, double x, TIIRCoeff IIRCoeff) { double y, CenterTap; static double RegX1[ARRAY_DIM], RegX2[ARRAY_DIM], RegY1[ARRAY_DIM], RegY2[ARRAY_DIM], MaxRegVal; static bool MessageShown = false; // Zero the regiisters on the 1st call or on an overflow condition. The overflow limit used // here is small for double variables, but a filter that reaches this threshold is broken. if ((j == 0 && k == 0) || MaxRegVal > OVERFLOW_LIMIT) { if (MaxRegVal > OVERFLOW_LIMIT && !MessageShown) { // ShowMessage("ERROR: Math Over Flow in IIR Section Calc. \nThe register values exceeded 1.0E20 \n"); MessageShown = true; // So this message doesn't get shown thousands of times. } MaxRegVal = 1.0E-12; for (int i = 0; i < ARRAY_DIM; i++) { RegX1[i] = 0.0; RegX2[i] = 0.0; RegY1[i] = 0.0; RegY2[i] = 0.0; } } CenterTap = x * IIRCoeff.b0[k] + IIRCoeff.b1[k] * RegX1[k] + IIRCoeff.b2[k] * RegX2[k]; y = IIRCoeff.a0[k] * CenterTap - IIRCoeff.a1[k] * RegY1[k] - IIRCoeff.a2[k] * RegY2[k]; RegX2[k] = RegX1[k]; RegX1[k] = x; RegY2[k] = RegY1[k]; RegY1[k] = y; // MaxRegVal is used to prevent overflow. Overflow seldom occurs, but will // if the filter has faulty coefficients. MaxRegVal is usually less than 100.0 if (fabs(CenterTap) > MaxRegVal) MaxRegVal = fabs(CenterTap); if (fabs(y) > MaxRegVal) MaxRegVal = fabs(y); return (y); } //--------------------------------------------------------------------------- // This function calculates the frequency response of an IIR filter. // Probably the easiest way to determine the frequency response of an IIR filter is to send // an impulse through the filter and do an FFT on the output. This method does a DFT on // the coefficients of each biquad section. The results from the cascaded sections are // then multiplied together. // This approach works better than an FFT when the filter is very narrow. To analyze highly selective // filters with an FFT can require a very large number of points, which can be quite cumbersome. // This approach allows you to set the range of frequencies to be analyzed by modifying the statement // Arg = M_PI * (double)j / (double)NumPts; . void IIRFreqResponse(TIIRCoeff IIR, int NumSections, double *RealHofZ, double *ImagHofZ, int NumPts) { int j, n; double Arg; std::complex z1, z2, HofZ, Denom; for (j = 0; j < NumPts; j++) { Arg = M_PI * (double) j / (double) NumPts; z1 = std::complex(cos(Arg), -sin(Arg)); // z = e^(j*omega) z2 = z1 * z1; // z squared HofZ = std::complex(1.0, 0.0); for (n = 0; n < NumSections; n++) { HofZ *= IIR.a0[n]; // This can be in the denominator, but only if a0=1. a0 can be other than 1.0 to adjust the filter's gain. See the bottom of the CalcIIRFilterCoeff() function. HofZ *= IIR.b0[n] + IIR.b1[n] * z1 + IIR.b2[n] * z2; // Numerator Denom = 1.0 + IIR.a1[n] * z1 + IIR.a2[n] * z2; // Denominator if (std::abs(Denom) < 1.0E-12) Denom = 1.0E-12; // A pole on the unit circle would cause this to be zero, so this should never happen. It could happen however if the filter also has a zero at this frequency. Then H(z) needs to be determined by L'Hopitals rule at this freq. HofZ /= Denom; } RealHofZ[j] = HofZ.real(); ImagHofZ[j] = HofZ.imag(); } } //--------------------------------------------------------------------------- } // namespace