mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-05 16:31:15 -05:00
684 lines
23 KiB
C++
684 lines
23 KiB
C++
/*
|
|
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
|
|
|
|
|
|
This code calculates the roots for the following filter polynomials.
|
|
Butterworth, Chebyshev, Bessel, Gauss, Adjustable Gauss, Inverse Chebyshev, Papoulis, and Elliptic.
|
|
|
|
These filters are described in most filter design texts, except the Papoulis and Adjustable Gauss.
|
|
|
|
The Adjustable Gauss is a transitional filter invented by Iowa Hills that can generate a response
|
|
anywhere between a Gauss and a Butterworth. Use Gamma to control the response.
|
|
Gamma = -1.0 nearly a Gauss
|
|
Gamma = -0.7 nearly a Bessel
|
|
Gamma = 1.0 nearly a Butterworth
|
|
|
|
The Papoulis (Classic L) provides a response between the Butterworth the Chebyshev.
|
|
It has a faster roll off than the Butterworth but without the Chebyshev ripple.
|
|
It does however have some roll off in the pass band, which can be controlled by the RollOff parameter.
|
|
|
|
The limits on the arguments for these functions (such as pole count or ripple) are not
|
|
checked here. See the LowPassPrototypes.cpp for argument limits.
|
|
*/
|
|
|
|
#include <math.h>
|
|
#include <complex>
|
|
#include "LowPassRoots.h"
|
|
#include "PFiftyOneRevE.h"
|
|
|
|
namespace kitiirfir
|
|
{
|
|
|
|
//---------------------------------------------------------------------------
|
|
// This used by several of the functions below. It returns a double so
|
|
// we don't overflow and because we need a double for subsequent calculations.
|
|
double Factorial(int N) {
|
|
int j;
|
|
double Fact = 1.0;
|
|
for (j = 1; j <= N; j++)
|
|
Fact *= (double) j;
|
|
return (Fact);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Some of the code below generates the coefficients in reverse order
|
|
// needed for the root finder. This reverses the poly.
|
|
void ReverseCoeff(double *P, int N) {
|
|
int j;
|
|
double Temp;
|
|
for (j = 0; j <= N / 2; j++) {
|
|
Temp = P[j];
|
|
P[j] = P[N - j];
|
|
P[N - j] = Temp;
|
|
}
|
|
|
|
for (j = N; j >= 1; j--) {
|
|
if (P[0] != 0.0)
|
|
P[j] /= P[0];
|
|
}
|
|
P[0] = 1.0;
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// We calculate the roots for a Butterwoth filter directly. (No root finder needed)
|
|
// We fill the array Roots[] and return the number of roots.
|
|
int ButterworthPoly(int NumPoles, std::complex<double> *Roots) {
|
|
int j, n, N;
|
|
double Theta;
|
|
|
|
N = NumPoles;
|
|
n = 0;
|
|
for (j = 0; j < N / 2; j++) {
|
|
Theta = M_PI * (double) (2 * j + N + 1) / (double) (2 * N);
|
|
Roots[n++] = std::complex<double>(cos(Theta), sin(Theta));
|
|
Roots[n++] = std::complex<double>(cos(Theta), -sin(Theta));
|
|
}
|
|
if (N % 2 == 1)
|
|
Roots[n++] = std::complex<double>(-1.0, 0.0); // The real root for odd pole counts.
|
|
return (N);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This calculates the roots for a Chebyshev filter directly. (No root finder needed)
|
|
int ChebyshevPoly(int NumPoles, double Ripple, std::complex<double> *Roots) {
|
|
int j, n, N;
|
|
double Sigma, Omega;
|
|
double Arg, Theta, Epsilon;
|
|
|
|
N = NumPoles;
|
|
Epsilon = pow(10.0, Ripple / 10.0) - 1.0;
|
|
Epsilon = sqrt(Epsilon);
|
|
if (Epsilon < 0.00001)
|
|
Epsilon = 0.00001;
|
|
if (Epsilon > 0.996)
|
|
Epsilon = 0.996;
|
|
Epsilon = 1.0 / Epsilon;
|
|
Arg = log(Epsilon + sqrt(Epsilon * Epsilon + 1.0)) / (double) N; // = asinh(Epsilon) / (double)N;
|
|
n = 0;
|
|
for (j = 0; j < N / 2; j++) {
|
|
Theta = (2 * j + 1) * M_PI_2 / (double) N;
|
|
Sigma = -sinh(Arg) * sin(Theta);
|
|
Omega = cosh(Arg) * cos(Theta);
|
|
Roots[n++] = std::complex<double>(Sigma, Omega);
|
|
Roots[n++] = std::complex<double>(Sigma, -Omega);
|
|
}
|
|
if (N % 2 == 1)
|
|
Roots[n++] = std::complex<double>(-sinh(Arg), 0.0); // The real root for odd pole counts.
|
|
return (N);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// The Gaussian Poly is simply 1 - s^2 + s^4 /2! - s^6 / 3! .... seeHumpherys p. 414.
|
|
int GaussianPoly(int NumPoles, std::complex<double> *Roots) {
|
|
int j, N, RootsCount;
|
|
double GaussCoeff[P51_ARRAY_SIZE];
|
|
|
|
N = NumPoles;
|
|
GaussCoeff[0] = 1.0;
|
|
GaussCoeff[1] = 0.0;
|
|
for (j = 2; j <= 2 * N; j += 2) {
|
|
GaussCoeff[j] = 1.0 / Factorial(j / 2);
|
|
GaussCoeff[j + 1] = 0.0;
|
|
if ((j / 2) % 2 == 1)
|
|
GaussCoeff[j] *= -1.0;
|
|
}
|
|
|
|
// The coefficients are generated in reverse order needed for P51.
|
|
ReverseCoeff(GaussCoeff, N * 2);
|
|
RootsCount = FindRoots(N * 2, GaussCoeff, Roots);
|
|
return (RootsCount);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This function starts with the Gauss and modifies the coefficients.
|
|
// The Gaussian Poly is simply 1 - s^2 + s^4 /2! - s^6 / 3! .... seeHumpherys p. 414.
|
|
int AdjustablePoly(int NumPoles, std::complex<double> *Roots, double Gamma) {
|
|
int j, N, RootsCount;
|
|
double GaussCoeff[P51_ARRAY_SIZE];
|
|
|
|
N = NumPoles;
|
|
if (Gamma > 0.0)
|
|
Gamma *= 2.0; // Gamma < 0 is the orig Gauss and Bessel responses. Gamma > 0 has an asymptotic response, so we double it, which also makes the user interface a bit nicer. i.e. -1 <= Gamma <= 1
|
|
|
|
GaussCoeff[0] = 1.0;
|
|
GaussCoeff[1] = 0.0;
|
|
for (j = 2; j <= 2 * N; j += 2) {
|
|
GaussCoeff[j] = pow(Factorial(j / 2), Gamma); // Gamma = -1 is orig Gauss poly, Gamma = 1 approaches a Butterworth response.
|
|
GaussCoeff[j + 1] = 0.0;
|
|
if ((j / 2) % 2 == 1)
|
|
GaussCoeff[j] *= -1.0;
|
|
}
|
|
|
|
// The coefficients are generated in reverse order needed for P51.
|
|
ReverseCoeff(GaussCoeff, N * 2);
|
|
RootsCount = FindRoots(N * 2, GaussCoeff, Roots);
|
|
|
|
// Scale the imag part of the root by 1.1 to get a response closer to a Butterworth when Gamma = -2
|
|
for (j = 0; j < N * 2; j++)
|
|
Roots[j] = std::complex<double>(Roots[j].real(), Roots[j].imag() * 1.10);
|
|
return (RootsCount);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// These Bessel coefficients are calc'd with the formula given in Johnson & Moore. Page 164. eq 7-26
|
|
// The highet term is 1, the rest of the terms are calc'd
|
|
int BesselPoly(int NumPoles, std::complex<double> *Roots) {
|
|
int k, N, RootsCount;
|
|
double b, PolyCoeff[P51_ARRAY_SIZE];
|
|
|
|
N = NumPoles;
|
|
for (k = N - 1; k >= 0; k--) {
|
|
// b is calc'd as a double because of all the division, but the result is essentially a large int.
|
|
b = Factorial(2 * N - k) / Factorial(k) / Factorial(N - k)
|
|
/ pow(2.0, (double) (N - k));
|
|
PolyCoeff[k] = b;
|
|
}
|
|
PolyCoeff[N] = 1.0;
|
|
|
|
// The coefficients are generated in reverse order needed for P51.
|
|
ReverseCoeff(PolyCoeff, N);
|
|
RootsCount = FindRoots(N, PolyCoeff, Roots);
|
|
return (RootsCount);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This Inverse Cheby code is identical to the Cheby code, except for two things.
|
|
// First, epsilon represents stop band atten, not ripple, so this epsilon is 1/epsilon.
|
|
// More importantly, the inverse cheby is defined in terms of F(1/s) instead of the usual F(s);
|
|
// After a bit of algebra, it is easy to see that the 1/s terms can be made s terms by simply
|
|
// multiplying the num and denom by s^2N. Then the 0th term becomes the highest term, so the
|
|
// coefficients are simply fed to the root finder in reverse order.
|
|
// Since 1 doesn't get added to the numerator, the lowest 1 or 2 terms will be 0, but we can't
|
|
// feed the root finder 0's as the highest term, so we have to be sure not to feed them to the root finder.
|
|
|
|
int InvChebyPoly(int NumPoles, double StopBanddB, std::complex<double> *ChebyPoles,
|
|
std::complex<double> *ChebyZeros, int *ZeroCount) {
|
|
int j, k, N, PolesCount;
|
|
double Arg, Epsilon, ChebPolyCoeff[P51_ARRAY_SIZE],
|
|
PolyCoeff[P51_ARRAY_SIZE];
|
|
std::complex<double> SquaredPolyCoeff[P51_ARRAY_SIZE], A, B;
|
|
|
|
N = NumPoles;
|
|
Epsilon = 1.0 / (pow(10.0, StopBanddB / 10.0) - 1.0); // actually Epsilon Squared
|
|
|
|
// This algorithm is from the paper by Richard J Mathar. It generates the coefficients for the Cheb poly.
|
|
// It stores the Nth order coefficient in ChebPolyCoeff[N], and so on. Every other Cheb coeff is 0. See Wikipedia for a table that this code will generate.
|
|
for (j = 0; j <= N / 2; j++) {
|
|
Arg = Factorial(N - j - 1) / Factorial(j) / Factorial(N - 2 * j);
|
|
if (j % 2 == 1)
|
|
Arg *= -1.0;
|
|
Arg *= pow(2.0, (double) (N - 2 * j)) * (double) N / 2.0;
|
|
ChebPolyCoeff[N - 2 * j] = Arg;
|
|
if (N - (2 * j + 1) >= 0) {
|
|
ChebPolyCoeff[N - (2 * j + 1)] = 0.0;
|
|
}
|
|
}
|
|
|
|
// Now square the Chebshev polynomial where we assume s = jw. To get the signs correct,
|
|
// we need to take j to the power. Then its a simple matter of adding powers and
|
|
// multiplying coefficients. j and k represent the exponents. That is, j=3 is the x^3 coeff, and so on.
|
|
for (j = 0; j <= 2 * N; j++)
|
|
SquaredPolyCoeff[j] = std::complex<double>(0.0, 0.0);
|
|
|
|
for (j = 0; j <= N; j++)
|
|
for (k = 0; k <= N; k++) {
|
|
A = pow(std::complex<double>(0.0, 1.0), (double) j) * ChebPolyCoeff[j];
|
|
B = pow(std::complex<double>(0.0, 1.0), (double) k) * ChebPolyCoeff[k];
|
|
SquaredPolyCoeff[j + k] = SquaredPolyCoeff[j + k] + A * B; // these end up entirely real.
|
|
}
|
|
|
|
// Denominator
|
|
// Now we multiply the coefficients by Epsilon and add 1 to the denominator poly.
|
|
k = 0;
|
|
for (j = 0; j <= 2 * N; j++)
|
|
ChebPolyCoeff[j] = SquaredPolyCoeff[j].real() * Epsilon;
|
|
ChebPolyCoeff[0] += 1.0;
|
|
for (j = 0; j <= 2 * N; j++)
|
|
PolyCoeff[k++] = ChebPolyCoeff[j]; // Note this order is reversed from the Chebyshev routine.
|
|
k--;
|
|
PolesCount = FindRoots(k, PolyCoeff, ChebyPoles);
|
|
|
|
// Numerator
|
|
k = 0;
|
|
for (j = 0; j <= 2 * N; j++)
|
|
ChebPolyCoeff[j] = SquaredPolyCoeff[j].real(); // Not using Epsilon here so the check for 0 on the next line is easier. Since the root finder normalizes the poly, it gets factored out anyway.
|
|
for (j = 0; j <= 2 * N; j++)
|
|
if (fabs(ChebPolyCoeff[j]) > 0.01)
|
|
break; // Get rid of the high order zeros. There will be eithe 0ne or two zeros to delete.
|
|
for (; j <= 2 * N; j++)
|
|
PolyCoeff[k++] = ChebPolyCoeff[j];
|
|
k--;
|
|
*ZeroCount = FindRoots(k, PolyCoeff, ChebyZeros);
|
|
|
|
return (PolesCount);
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// The complete Papouls poly is 1 + Epsilon * P(n) where P(n) is the 2*N Legendre poly.
|
|
int PapoulisPoly(int NumPoles, std::complex<double> *Roots) {
|
|
int j, N, RootsCount;
|
|
double Epsilon, PolyCoeff[P51_ARRAY_SIZE];
|
|
|
|
N = NumPoles;
|
|
for (j = 0; j < 2 * N; j++)
|
|
PolyCoeff[j] = 0.0; // so we don't have to fill all the zero's.
|
|
|
|
switch (N) {
|
|
case 1: // 1 pole
|
|
PolyCoeff[2] = 1.0;
|
|
break;
|
|
|
|
case 2: // 2 pole
|
|
PolyCoeff[4] = 1.0;
|
|
break;
|
|
|
|
case 3: // 3 pole
|
|
PolyCoeff[6] = 3.0;
|
|
PolyCoeff[4] = -3.0;
|
|
PolyCoeff[2] = 1.0;
|
|
break;
|
|
|
|
case 4:
|
|
PolyCoeff[8] = 6.0;
|
|
PolyCoeff[6] = -8.0;
|
|
PolyCoeff[4] = 3.0;
|
|
break;
|
|
|
|
case 5:
|
|
PolyCoeff[10] = 20.0;
|
|
PolyCoeff[8] = -40.0;
|
|
PolyCoeff[6] = 28.0;
|
|
PolyCoeff[4] = -8.0;
|
|
PolyCoeff[2] = 1.0;
|
|
break;
|
|
|
|
case 6:
|
|
PolyCoeff[12] = 50.0;
|
|
PolyCoeff[10] = -120.0;
|
|
PolyCoeff[8] = 105.0;
|
|
PolyCoeff[6] = -40.0;
|
|
PolyCoeff[4] = 6.0;
|
|
break;
|
|
|
|
case 7:
|
|
PolyCoeff[14] = 175.0;
|
|
PolyCoeff[12] = -525.0;
|
|
PolyCoeff[10] = 615.0;
|
|
PolyCoeff[8] = -355.0;
|
|
PolyCoeff[6] = 105.0;
|
|
PolyCoeff[4] = -15.0;
|
|
PolyCoeff[2] = 1.0;
|
|
break;
|
|
|
|
case 8:
|
|
PolyCoeff[16] = 490.0;
|
|
PolyCoeff[14] = -1680.0;
|
|
PolyCoeff[12] = 2310.0;
|
|
PolyCoeff[10] = -1624.0;
|
|
PolyCoeff[8] = 615.0;
|
|
PolyCoeff[6] = -120.0;
|
|
PolyCoeff[4] = 10.0;
|
|
break;
|
|
|
|
case 9:
|
|
PolyCoeff[18] = 1764.0;
|
|
PolyCoeff[16] = -7056.0;
|
|
PolyCoeff[14] = 11704.0;
|
|
PolyCoeff[12] = -10416.0;
|
|
PolyCoeff[10] = 5376.0;
|
|
PolyCoeff[8] = -1624.0;
|
|
PolyCoeff[6] = 276.0;
|
|
PolyCoeff[4] = -24.0;
|
|
PolyCoeff[2] = 1.0;
|
|
break;
|
|
|
|
case 10:
|
|
PolyCoeff[20] = 5292.0;
|
|
PolyCoeff[18] = -23520.0;
|
|
PolyCoeff[16] = 44100.0;
|
|
PolyCoeff[14] = -45360.0;
|
|
PolyCoeff[12] = 27860.0;
|
|
PolyCoeff[10] = -10416.0;
|
|
PolyCoeff[8] = 2310.0;
|
|
PolyCoeff[6] = -280.0;
|
|
PolyCoeff[4] = 15.0;
|
|
break;
|
|
|
|
case 11:
|
|
PolyCoeff[22] = 19404;
|
|
PolyCoeff[20] = -97020.0;
|
|
PolyCoeff[18] = 208740.0;
|
|
PolyCoeff[16] = -252840.0;
|
|
PolyCoeff[14] = 189420.0;
|
|
PolyCoeff[12] = -90804.0;
|
|
PolyCoeff[10] = 27860.0;
|
|
PolyCoeff[8] = -5320.0;
|
|
PolyCoeff[6] = 595.0;
|
|
PolyCoeff[4] = -35.0;
|
|
PolyCoeff[2] = 1.0;
|
|
break;
|
|
|
|
case 12:
|
|
PolyCoeff[24] = 60984.0;
|
|
PolyCoeff[22] = -332640.0;
|
|
PolyCoeff[20] = 790020.0;
|
|
PolyCoeff[18] = -1071840.0;
|
|
PolyCoeff[16] = 916020.0;
|
|
PolyCoeff[14] = -512784.0;
|
|
PolyCoeff[12] = 189420.0;
|
|
PolyCoeff[10] = -45360.0;
|
|
PolyCoeff[8] = 6720.0;
|
|
PolyCoeff[6] = -560.0;
|
|
PolyCoeff[4] = 21.0;
|
|
break;
|
|
|
|
case 13:
|
|
PolyCoeff[26] = 226512.0;
|
|
PolyCoeff[24] = -1359072.0;
|
|
PolyCoeff[22] = 3597264.0;
|
|
PolyCoeff[20] = -5528160.0;
|
|
PolyCoeff[18] = 5462820.0;
|
|
PolyCoeff[16] = -3632112.0;
|
|
PolyCoeff[14] = 1652232.0;
|
|
PolyCoeff[12] = -512784.0;
|
|
PolyCoeff[10] = 106380.0;
|
|
PolyCoeff[8] = -14160.0;
|
|
PolyCoeff[6] = 1128.0;
|
|
PolyCoeff[4] = -48.0;
|
|
PolyCoeff[2] = 1.0;
|
|
break;
|
|
|
|
case 14:
|
|
PolyCoeff[28] = 736164.0;
|
|
PolyCoeff[26] = -4756752.0;
|
|
PolyCoeff[24] = 13675662.0;
|
|
PolyCoeff[22] = -23063040.0;
|
|
PolyCoeff[20] = 25322220.0;
|
|
PolyCoeff[18] = -18993744.0;
|
|
PolyCoeff[16] = 9934617.0;
|
|
PolyCoeff[14] = -3632112.0;
|
|
PolyCoeff[12] = 916020.0;
|
|
PolyCoeff[10] = -154560.0;
|
|
PolyCoeff[8] = 16506.0;
|
|
PolyCoeff[6] = -1008.0;
|
|
PolyCoeff[4] = 28.0;
|
|
break;
|
|
|
|
case 15:
|
|
PolyCoeff[30] = 2760615.0;
|
|
PolyCoeff[28] = -19324305.0;
|
|
PolyCoeff[26] = 60747687.0;
|
|
PolyCoeff[24] = -113270157.0;
|
|
PolyCoeff[22] = 139378239.0;
|
|
PolyCoeff[20] = -119144025.0;
|
|
PolyCoeff[18] = 72539775.0;
|
|
PolyCoeff[16] = -31730787.0;
|
|
PolyCoeff[14] = 9934617.0;
|
|
PolyCoeff[12] = -2191959.0;
|
|
PolyCoeff[10] = 331065.0;
|
|
PolyCoeff[8] = -32655.0;
|
|
PolyCoeff[6] = 1953.0;
|
|
PolyCoeff[4] = -63.0;
|
|
PolyCoeff[2] = 1.0;
|
|
break;
|
|
|
|
case 16:
|
|
PolyCoeff[32] = 9202050.0;
|
|
PolyCoeff[30] = -68708640.0;
|
|
PolyCoeff[28] = 231891660.0;
|
|
PolyCoeff[26] = -467747280.0;
|
|
PolyCoeff[24] = 628221594.0;
|
|
PolyCoeff[22] = -592431840.0;
|
|
PolyCoeff[20] = 403062660.0;
|
|
PolyCoeff[18] = -200142800.0;
|
|
PolyCoeff[16] = 72539775.0;
|
|
PolyCoeff[14] = -18993744.0;
|
|
PolyCoeff[12] = 3515820.0;
|
|
PolyCoeff[10] = -443520.0;
|
|
PolyCoeff[8] = 35910.0;
|
|
PolyCoeff[6] = -1680.0;
|
|
PolyCoeff[4] = 36.0;
|
|
break;
|
|
|
|
case 17:
|
|
PolyCoeff[34] = 34763300.0;
|
|
PolyCoeff[32] = -278106400.0;
|
|
PolyCoeff[30] = 1012634480.0;
|
|
PolyCoeff[28] = -2221579360.0;
|
|
PolyCoeff[26] = 3276433160.0;
|
|
PolyCoeff[24] = -3431908480.0;
|
|
PolyCoeff[22] = 2629731104.0;
|
|
PolyCoeff[20] = -1496123200.0;
|
|
PolyCoeff[18] = 634862800.0;
|
|
PolyCoeff[16] = -200142800.0;
|
|
PolyCoeff[14] = 46307800.0;
|
|
PolyCoeff[12] = -7696304.0;
|
|
PolyCoeff[10] = 888580.0;
|
|
PolyCoeff[8] = -67760.0;
|
|
PolyCoeff[6] = 3160.0;
|
|
PolyCoeff[4] = -80.0;
|
|
PolyCoeff[2] = 1.0;
|
|
break;
|
|
|
|
case 18:
|
|
PolyCoeff[36] = 118195220.0;
|
|
PolyCoeff[34] = -1001183040.0;
|
|
PolyCoeff[32] = 3879584280.0;
|
|
PolyCoeff[30] = -9110765664.0;
|
|
PolyCoeff[28] = 14480345880.0;
|
|
PolyCoeff[26] = -16474217760.0;
|
|
PolyCoeff[24] = 13838184360.0;
|
|
PolyCoeff[22] = -8725654080.0;
|
|
PolyCoeff[20] = 4158224928.0;
|
|
PolyCoeff[18] = -1496123200.0;
|
|
PolyCoeff[16] = 403062660.0;
|
|
PolyCoeff[14] = -79999920.0;
|
|
PolyCoeff[12] = 11397540.0;
|
|
PolyCoeff[10] = -1119888.0;
|
|
PolyCoeff[8] = 71280.0;
|
|
PolyCoeff[6] = -2640.0;
|
|
PolyCoeff[4] = 45.0;
|
|
break;
|
|
|
|
case 19:
|
|
PolyCoeff[38] = 449141836.0;
|
|
PolyCoeff[36] = -4042276524.0;
|
|
PolyCoeff[34] = 16732271556.0;
|
|
PolyCoeff[32] = -42233237904.0;
|
|
PolyCoeff[30] = 72660859128.0;
|
|
PolyCoeff[28] = -90231621480.0;
|
|
PolyCoeff[26] = 83545742280.0;
|
|
PolyCoeff[24] = -58751550000.0;
|
|
PolyCoeff[22] = 31671113760.0;
|
|
PolyCoeff[20] = -13117232128.0;
|
|
PolyCoeff[18] = 4158224928.0;
|
|
PolyCoeff[16] = -999092952.0;
|
|
PolyCoeff[14] = 178966788.0;
|
|
PolyCoeff[12] = -23315292.0;
|
|
PolyCoeff[10] = 2130876.0;
|
|
PolyCoeff[8] = -129624.0;
|
|
PolyCoeff[6] = 4851.0;
|
|
PolyCoeff[4] = -99.0;
|
|
PolyCoeff[2] = 1.0;
|
|
break;
|
|
|
|
case 20:
|
|
PolyCoeff[40] = 1551580888.0;
|
|
PolyCoeff[38] = -14699187360.0;
|
|
PolyCoeff[36] = 64308944700.0;
|
|
PolyCoeff[34] = -172355177280.0;
|
|
PolyCoeff[32] = 316521742680.0;
|
|
PolyCoeff[30] = -422089668000.0;
|
|
PolyCoeff[28] = 422594051880.0;
|
|
PolyCoeff[26] = -323945724960.0;
|
|
PolyCoeff[24] = 192167478360.0;
|
|
PolyCoeff[22] = -88572527680.0;
|
|
PolyCoeff[20] = 31671113760.0;
|
|
PolyCoeff[18] = -8725654080.0;
|
|
PolyCoeff[16] = 1829127300.0;
|
|
PolyCoeff[14] = -286125840.0;
|
|
PolyCoeff[12] = 32458140.0;
|
|
PolyCoeff[10] = -2560272.0;
|
|
PolyCoeff[8] = 131670.0;
|
|
PolyCoeff[6] = -3960.0;
|
|
PolyCoeff[4] = 55.0;
|
|
break;
|
|
}
|
|
|
|
Epsilon = 0.1; // This controls the amount of pass band roll off. 0.01 < Epsilon < 0.250
|
|
|
|
// The poly is in terms of omega, but we need it in term of s = jw. So we need to
|
|
// multiply the approp coeff by neg 1 to account for j. Then mult by epsilon.
|
|
for (j = 0; j <= 2 * N; j++) {
|
|
if ((j / 2) % 2 == 1)
|
|
PolyCoeff[j] *= -1.0;
|
|
PolyCoeff[j] *= Epsilon;
|
|
}
|
|
|
|
// Now add 1 to the poly.
|
|
PolyCoeff[0] = 1.0;
|
|
|
|
// The coefficients are in reverse order needed for P51.
|
|
ReverseCoeff(PolyCoeff, N * 2);
|
|
RootsCount = FindRoots(N * 2, PolyCoeff, Roots);
|
|
|
|
return (RootsCount);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This code was described in "Elliptic Functions for Filter Design"
|
|
// H J Orchard and Alan N Willson IEE Trans on Circuits and Systems April 97
|
|
// The equation numbers in the comments are from the paper.
|
|
// As the stop band attenuation -> infinity, the Elliptic -> Chebyshev.
|
|
int EllipticPoly(int FiltOrder, double Ripple, double DesiredSBdB,
|
|
std::complex<double> *EllipPoles, std::complex<double> *EllipZeros, int *ZeroCount) {
|
|
int j, k, n, LastK;
|
|
double K[ELLIPARRAYSIZE], G[ELLIPARRAYSIZE], Epsilon[ELLIPARRAYSIZE];
|
|
double A, D, SBdB, dBErr, RealPart, ImagPart;
|
|
double DeltaK, PrevErr, Deriv;
|
|
std::complex<double> C;
|
|
|
|
for (j = 0; j < ELLIPARRAYSIZE; j++)
|
|
K[j] = G[j] = Epsilon[j] = 0.0;
|
|
if (Ripple < 0.001)
|
|
Ripple = 0.001;
|
|
if (Ripple > 1.0)
|
|
Ripple = 1.0;
|
|
Epsilon[0] = sqrt(pow(10.0, Ripple / 10.0) - 1.0);
|
|
|
|
// Estimate K[0] to get the algorithm started.
|
|
K[0] = (double) (FiltOrder - 2) * 0.1605 + 0.016;
|
|
if (K[0] < 0.01)
|
|
K[0] = 0.01;
|
|
if (K[0] > 0.7)
|
|
K[0] = 0.7;
|
|
|
|
// This loop calculates K[0] for the desired stopband attenuation. It typically loops < 5 times.
|
|
for (j = 0; j < MAX_ELLIP_ITER; j++) {
|
|
// Compute K with a forward Landen Transformation.
|
|
for (k = 1; k < 10; k++) {
|
|
K[k] = pow(K[k - 1] / (1.0 + sqrt(1.0 - K[k - 1] * K[k - 1])), 2.0); // eq. 10
|
|
if (K[k] <= 1.0E-6)
|
|
break;
|
|
}
|
|
LastK = k;
|
|
|
|
// Compute G with a backwards Landen Transformation.
|
|
G[LastK] = 4.0 * pow(K[LastK] / 4.0, (double) FiltOrder);
|
|
for (k = LastK; k >= 1; k--) {
|
|
G[k - 1] = 2.0 * sqrt(G[k]) / (1.0 + G[k]); // eq. 9
|
|
}
|
|
|
|
if (G[0] <= 0.0)
|
|
G[0] = 1.0E-10;
|
|
SBdB = 10.0 * log10(1.0 + pow(Epsilon[0] / G[0], 2.0)); // Current stopband attenuation dB
|
|
dBErr = DesiredSBdB - SBdB;
|
|
|
|
if (fabs(dBErr) < 0.1)
|
|
break;
|
|
if (j == 0) // Do this on the 1st loop so we can calc a derivative.
|
|
{
|
|
if (dBErr > 0)
|
|
DeltaK = 0.005;
|
|
else
|
|
DeltaK = -0.005;
|
|
PrevErr = dBErr;
|
|
} else {
|
|
// Use Newtons Method to adjust K[0].
|
|
Deriv = (PrevErr - dBErr) / DeltaK;
|
|
PrevErr = dBErr;
|
|
if (Deriv == 0.0)
|
|
break; // This happens when K[0] hits one of the limits set below.
|
|
DeltaK = dBErr / Deriv;
|
|
if (DeltaK > 0.1)
|
|
DeltaK = 0.1;
|
|
if (DeltaK < -0.1)
|
|
DeltaK = -0.1;
|
|
}
|
|
K[0] -= DeltaK;
|
|
if (K[0] < 0.001)
|
|
K[0] = 0.001; // must not be < 0.0
|
|
if (K[0] > 0.990)
|
|
K[0] = 0.990; // if > 0.990 we get a pole in the RHP. This means we were unable to set the stop band atten to the desired level (the Ripple is too large for the Pole Count).
|
|
}
|
|
|
|
// Epsilon[0] was calulated above, now calculate Epsilon[LastK] from G
|
|
for (j = 1; j <= LastK; j++) {
|
|
A = (1.0 + G[j]) * Epsilon[j - 1] / 2.0; // eq. 37
|
|
Epsilon[j] = A + sqrt(A * A + G[j]);
|
|
}
|
|
|
|
// Calulate the poles and zeros.
|
|
ImagPart = log(
|
|
(1.0 + sqrt(1.0 + Epsilon[LastK] * Epsilon[LastK]))
|
|
/ Epsilon[LastK]) / (double) FiltOrder; // eq. 22
|
|
n = 0;
|
|
for (j = 1; j <= FiltOrder / 2; j++) {
|
|
RealPart = (double) (2 * j - 1) * M_PI_2 / (double) FiltOrder; // eq. 19
|
|
C = std::complex<double>(0.0, -1.0) / cos(std::complex<double>(-RealPart, ImagPart)); // eq. 20
|
|
D = 1.0 / cos(RealPart);
|
|
for (k = LastK; k >= 1; k--) {
|
|
C = (C - K[k] / C) / (1.0 + K[k]); // eq. 36
|
|
D = (D + K[k] / D) / (1.0 + K[k]);
|
|
}
|
|
|
|
EllipPoles[n] = 1.0 / C;
|
|
EllipPoles[n + 1] = std::conj(EllipPoles[n]);
|
|
EllipZeros[n] = std::complex<double>(0.0, D / K[0]);
|
|
EllipZeros[n + 1] = std::conj(EllipZeros[n]);
|
|
n += 2;
|
|
}
|
|
*ZeroCount = n; // n is the num zeros
|
|
|
|
if (FiltOrder % 2 == 1) // The real pole for odd pole counts
|
|
{
|
|
A = 1.0 / sinh(ImagPart);
|
|
for (k = LastK; k >= 1; k--) {
|
|
A = (A - K[k] / A) / (1.0 + K[k]); // eq. 38
|
|
}
|
|
EllipPoles[n] = std::complex<double>(-1.0 / A, 0.0);
|
|
n++;
|
|
}
|
|
|
|
return (n); // n is the num poles. There will be 1 more pole than zeros for odd pole counts.
|
|
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
} // namespace
|