1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-09-28 07:46:37 -04:00
sdrangel/kitiirfir/NewParksMcClellan.cpp

628 lines
20 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
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.
This is a C translation of the Parks McClellan algorithm originally done in Fortran.
The original fortran code came from the Parks McClellan article on Wikipedia.
http://en.wikipedia.org/wiki/Parks%E2%80%93McClellan_filter_design_algorithm
This code is quite different from the original. The original code had 69 goto statements,
which made it nearly impossible to follow. And of course, it was Fortran code, so many changes
had to be made regardless of style.
Apparently, Fortran doesn't use global variables. Instead, is uses something called
common memory space. e.g. COMMON PI2,AD,DEV,X,Y,GRID,DES,WT,ALPHA,IEXT,NFCNS,NGRID
I simply converted these to globals. It isn't pretty, but for this purpose, who cares?
The first step was to get a C version of the code working with as few changes as possible.
That version is also available on: http://www.iowahills.com/A7ExampleCodePage.html
Then, in our desire to see if the code could be made more understandable, we decided to
remove as many goto statements as possible. We checked our work by comparing the coefficients
between this code and our original translation on more than 1000 filters while varying all the parameters.
Ultimately, we were able to reduce the goto count from 69 to 7, all of which are in the Remez
function. Of the 7 remaining, 3 of these are at the very bottom of the function, and go
back to the very top of the function. These could have been removed, but our goal was to
clarify the code, not restyle it, and since they are clear, we let them be.
The other 4 goto statements are intertwined in a rather nasty way. We recommend you print out
the Remez code, tape the sheets end to end, and trace out the goto's. It wasn't apparent to
us that they can be removed without an extensive study of the code.
For better or worse, we also removed any code that was obviously related to Hilbert transforms
and Differentiators. We did this because we aren't interested in these, and we also don't
believe this algorithm does a very good job with them (far too much ripple).
We added the functions CalcCoefficients() and ErrTest() as a way to simplify things a bit.
We also found 3 sections of code that never executed. Two of the sections were just a few lines
that the goto's always went around. The third section represented nearly half of the CalcCoefficients()
function. This statement always tested the same, which never allowed the code to execute.
if(GRID[1] < 0.01 && GRID[NGRID] > 0.49) KKK = 1;
This may be due to the 0.01 minimum width limit we set for the bands.
Note our use of MIN_TEST_VAL. The original code wasn't guarding against division by zero.
Limiting the return values as we have also helped the algorithm's convergence behavior.
In an effort to improve readability, we made a large number of variable name changes and also
deleted a large number of variables. We left many variable names in tact, in part as an aid when
comparing to the original code, and in part because a better name wasn't obvious.
This code is essentially straight c, and should compile with few, if any changes. Note the error
message in CalcParkCoeff2. It warns of the possibility of convergence failure, but you will
find that the iteration count NITER, isn't always an indicator of convergence problems when
it is less than 3, as stated in the original Fortran code comments.
If you find a problem with this code, please leave us a note on:
http://www.iowahills.com/feedbackcomments.html
*/
#include "NewParksMcClellan.h"
#include <math.h>
#define M_2PI 6.28318530717958647692
namespace kitiirfir
{
//---------------------------------------------------------------------------
// Global variables.
int HalfTapCount, ExchangeIndex[PARKS_SMALL];
double LeGrangeD[PARKS_SMALL], Alpha[PARKS_SMALL], CosOfGrid[PARKS_SMALL],
DesPlus[PARKS_SMALL];
double Coeff[PARKS_SMALL], Edge[PARKS_SMALL], BandMag[PARKS_SMALL],
InitWeight[PARKS_SMALL];
double DesiredMag[PARKS_BIG], Grid[PARKS_BIG], Weight[PARKS_BIG];
void NewParksMcClellan(double *FirCoeff, int NumTaps, TFIRPassTypes PassType,
double OmegaC, double BW, double ParksWidth) {
if (NumTaps > MAX_NUM_PARKS_TAPS)
return;
int j, NumBands;
// Note: There is no feedback to the caller if ParksWidth or NumTaps are modified here.
if (PassType == firBPF || PassType == firNOTCH || NumTaps > 70) {
if (ParksWidth > 0.15)
ParksWidth = 0.15; // Else we have covergence problems.
}
if (PassType == firNOTCH || PassType == firHPF) {
if (NumTaps % 2 == 0)
NumTaps++; // High pass and notch filters must have odd tap counts.
}
if (NumTaps > MAX_NUM_PARKS_TAPS)
NumTaps = MAX_NUM_PARKS_TAPS;
// It helps the algorithm a great deal if each band is at least 0.01 wide.
// The weights used here came from the orig PM code.
if (PassType == firLPF) {
NumBands = 2;
Edge[1] = 0.0; // Omega = 0
Edge[2] = OmegaC; // Pass band edge
if (Edge[2] < 0.01)
Edge[2] = 0.01;
if (Edge[2] > 0.98)
Edge[2] = 0.98;
Edge[3] = Edge[2] + ParksWidth; // Stop band edge
if (Edge[3] > 0.99)
Edge[3] = 0.99;
Edge[4] = 1.0; // Omega = Pi
BandMag[1] = 1.0;
BandMag[2] = 0.0;
InitWeight[1] = 1.0;
InitWeight[2] = 10.0;
}
if (PassType == firHPF) {
NumBands = 2;
Edge[1] = 0.0; // Omega = 0
Edge[3] = OmegaC; // Pass band edge
if (Edge[3] > 0.99)
Edge[3] = 0.99;
if (Edge[3] < 0.02)
Edge[3] = 0.02;
Edge[2] = Edge[3] - ParksWidth; // Stop band edge
if (Edge[2] < 0.01)
Edge[2] = 0.01;
Edge[4] = 1.0; // Omega = Pi
BandMag[1] = 0.0;
BandMag[2] = 1.0;
InitWeight[1] = 10.0;
InitWeight[2] = 1.0;
}
if (PassType == firBPF) {
NumBands = 3;
Edge[1] = 0.0; // Omega = 0
Edge[3] = OmegaC - BW / 2.0; // Left pass band edge.
if (Edge[3] < 0.02)
Edge[3] = 0.02;
Edge[2] = Edge[3] - ParksWidth; // Left stop band edge
if (Edge[2] < 0.01)
Edge[2] = 0.01;
Edge[4] = OmegaC + BW / 2.0; // Right pass band edge
if (Edge[4] > 0.98)
Edge[4] = 0.98;
Edge[5] = Edge[4] + ParksWidth; // Right stop band edge
if (Edge[5] > 0.99)
Edge[5] = 0.99;
Edge[6] = 1.0; // Omega = Pi
BandMag[1] = 0.0;
BandMag[2] = 1.0;
BandMag[3] = 0.0;
InitWeight[1] = 10.0;
InitWeight[2] = 1.0;
InitWeight[3] = 10.0;
}
// This algorithm tends to have problems with narrow band notch filters.
if (PassType == firNOTCH) {
NumBands = 3;
Edge[1] = 0.0; // Omega = 0
Edge[3] = OmegaC - BW / 2.0; // Left stop band edge.
if (Edge[3] < 0.02)
Edge[3] = 0.02;
Edge[2] = Edge[3] - ParksWidth; // Left pass band edge
if (Edge[2] < 0.01)
Edge[2] = 0.01;
Edge[4] = OmegaC + BW / 2.0; // Right stop band edge
if (Edge[4] > 0.98)
Edge[4] = 0.98;
Edge[5] = Edge[4] + ParksWidth; // Right pass band edge
if (Edge[5] > 0.99)
Edge[5] = 0.99;
Edge[6] = 1.0; // Omega = Pi
BandMag[1] = 1.0;
BandMag[2] = 0.0;
BandMag[3] = 1.0;
InitWeight[1] = 1.0;
InitWeight[2] = 10.0;
InitWeight[3] = 1.0;
}
// Parks McClellan's edges are based on 2Pi, we are based on Pi.
for (j = 1; j <= 2 * NumBands; j++)
Edge[j] /= 2.0;
CalcParkCoeff2(NumBands, NumTaps, FirCoeff);
}
//---------------------------------------------------------------------------
void CalcParkCoeff2(int NumBands, int TapCount, double *FirCoeff) {
int j, k, GridCount, GridIndex, BandIndex, NumIterations;
double LowFreqEdge, UpperFreq, TempVar, Change;
bool OddNumTaps;
GridCount = 16; // Grid Density
if (TapCount % 2)
OddNumTaps = true;
else
OddNumTaps = false;
HalfTapCount = TapCount / 2;
if (OddNumTaps)
HalfTapCount++;
Grid[1] = Edge[1];
LowFreqEdge = GridCount * HalfTapCount;
LowFreqEdge = 0.5 / LowFreqEdge;
j = 1;
k = 1;
BandIndex = 1;
while (BandIndex <= NumBands) {
UpperFreq = Edge[k + 1];
while (Grid[j] <= UpperFreq) {
TempVar = Grid[j];
DesiredMag[j] = BandMag[BandIndex];
Weight[j] = InitWeight[BandIndex];
j++;
;
Grid[j] = TempVar + LowFreqEdge;
}
Grid[j - 1] = UpperFreq;
DesiredMag[j - 1] = BandMag[BandIndex];
Weight[j - 1] = InitWeight[BandIndex];
k += 2;
BandIndex++;
if (BandIndex <= NumBands)
Grid[j] = Edge[k];
}
GridIndex = j - 1;
if (!OddNumTaps && Grid[GridIndex] > (0.5 - LowFreqEdge))
GridIndex--;
if (!OddNumTaps) {
for (j = 1; j <= GridIndex; j++) {
Change = cos(M_PI * Grid[j]);
DesiredMag[j] = DesiredMag[j] / Change;
Weight[j] = Weight[j] * Change;
}
}
TempVar = (double) (GridIndex - 1) / (double) HalfTapCount;
for (j = 1; j <= HalfTapCount; j++) {
ExchangeIndex[j] = (int) ((double) (j - 1) * TempVar + 1.0);
}
ExchangeIndex[HalfTapCount + 1] = GridIndex;
NumIterations = Remez2(GridIndex);
CalcCoefficients();
// Calculate the impulse response.
if (OddNumTaps) {
for (j = 1; j <= HalfTapCount - 1; j++) {
Coeff[j] = 0.5 * Alpha[HalfTapCount + 1 - j];
}
Coeff[HalfTapCount] = Alpha[1];
} else {
Coeff[1] = 0.25 * Alpha[HalfTapCount];
for (j = 2; j <= HalfTapCount - 1; j++) {
Coeff[j] =
0.25
* (Alpha[HalfTapCount + 1 - j]
+ Alpha[HalfTapCount + 2 - j]);
}
Coeff[HalfTapCount] = 0.5 * Alpha[1] + 0.25 * Alpha[2];
}
// Output section.
for (j = 1; j <= HalfTapCount; j++)
FirCoeff[j - 1] = Coeff[j];
if (OddNumTaps)
for (j = 1; j < HalfTapCount; j++)
FirCoeff[HalfTapCount + j - 1] = Coeff[HalfTapCount - j];
else
for (j = 1; j <= HalfTapCount; j++)
FirCoeff[HalfTapCount + j - 1] = Coeff[HalfTapCount - j + 1];
// Display the iteration count.
if (NumIterations < 3) {
// ShowMessage("Parks McClellan unable to coverge");
}
}
//---------------------------------------------------------------------------------------
int Remez2(int GridIndex) {
int j, JET, K, k, NU, JCHNGE, K1, KNZ, KLOW, NUT, KUP;
int NUT1 = 0, LUCK, KN, NITER;
double Deviation, DNUM, DDEN, TempVar;
double DEVL, COMP = 0.0, YNZ = 0.0, Y1 = 0.0, ERR;
LUCK = 0;
DEVL = -1.0;
NITER = 1; // Init this to 1 to be consistent with the orig code.
TOP_LINE: // We come back to here from 3 places at the bottom.
ExchangeIndex[HalfTapCount + 2] = GridIndex + 1;
for (j = 1; j <= HalfTapCount + 1; j++) {
TempVar = Grid[ExchangeIndex[j]];
CosOfGrid[j] = cos(TempVar * M_2PI);
}
JET = (HalfTapCount - 1) / 15 + 1;
for (j = 1; j <= HalfTapCount + 1; j++) {
LeGrangeD[j] = LeGrangeInterp2(j, HalfTapCount + 1, JET);
}
DNUM = 0.0;
DDEN = 0.0;
K = 1;
for (j = 1; j <= HalfTapCount + 1; j++) {
k = ExchangeIndex[j];
DNUM += LeGrangeD[j] * DesiredMag[k];
DDEN += (double) K * LeGrangeD[j] / Weight[k];
K = -K;
}
Deviation = DNUM / DDEN;
NU = 1;
if (Deviation > 0.0)
NU = -1;
Deviation = -(double) NU * Deviation;
K = NU;
for (j = 1; j <= HalfTapCount + 1; j++) {
k = ExchangeIndex[j];
TempVar = (double) K * Deviation / Weight[k];
DesPlus[j] = DesiredMag[k] + TempVar;
K = -K;
}
if (Deviation <= DEVL)
return (NITER); // Ouch
DEVL = Deviation;
JCHNGE = 0;
K1 = ExchangeIndex[1];
KNZ = ExchangeIndex[HalfTapCount + 1];
KLOW = 0;
NUT = -NU;
//Search for the extremal frequencies of the best approximation.
j = 1;
while (j < HalfTapCount + 2) {
KUP = ExchangeIndex[j + 1];
k = ExchangeIndex[j] + 1;
NUT = -NUT;
if (j == 2)
Y1 = COMP;
COMP = Deviation;
if (k < KUP && !ErrTest(k, NUT, COMP, &ERR)) {
L210: COMP = (double) NUT * ERR;
for (k++; k < KUP; k++) {
if (ErrTest(k, NUT, COMP, &ERR))
break; // for loop
COMP = (double) NUT * ERR;
}
ExchangeIndex[j] = k - 1;
j++;
KLOW = k - 1;
JCHNGE++;
continue; // while loop
}
k--;
L225: k--;
if (k <= KLOW) {
k = ExchangeIndex[j] + 1;
if (JCHNGE > 0) {
ExchangeIndex[j] = k - 1;
j++;
KLOW = k - 1;
JCHNGE++;
continue; // while loop
} else // JCHNGE <= 0
{
for (k++; k < KUP; k++) {
if (ErrTest(k, NUT, COMP, &ERR))
continue; // for loop
goto L210;
}
KLOW = ExchangeIndex[j];
j++;
continue; // while loop
}
}
// Can't use a do while loop here, it would outdent the two continue statements.
if (ErrTest(k, NUT, COMP, &ERR) && JCHNGE <= 0)
goto L225;
if (ErrTest(k, NUT, COMP, &ERR)) {
KLOW = ExchangeIndex[j];
j++;
continue; // while loop
}
COMP = (double) NUT * ERR;
L235: for (k--; k > KLOW; k--) {
if (ErrTest(k, NUT, COMP, &ERR))
break; // for loop
COMP = (double) NUT * ERR;
}
KLOW = ExchangeIndex[j];
ExchangeIndex[j] = k + 1;
j++;
JCHNGE++;
} // end while(j<HalfTapCount
if (j == HalfTapCount + 2)
YNZ = COMP;
while (j <= HalfTapCount + 2) {
if (K1 > ExchangeIndex[1])
K1 = ExchangeIndex[1];
if (KNZ < ExchangeIndex[HalfTapCount + 1])
KNZ = ExchangeIndex[HalfTapCount + 1];
NUT1 = NUT;
NUT = -NU;
k = 0;
KUP = K1;
COMP = YNZ * 1.00001;
LUCK = 1;
for (k++; k < KUP; k++) {
if (ErrTest(k, NUT, COMP, &ERR))
continue; // for loop
j = HalfTapCount + 2;
goto L210;
}
LUCK = 2;
break; // break while(j <= HalfTapCount+2) loop
} // end while(j <= HalfTapCount+2)
if (LUCK == 1 || LUCK == 2) {
if (LUCK == 1) {
if (COMP > Y1)
Y1 = COMP;
K1 = ExchangeIndex[HalfTapCount + 2];
}
k = GridIndex + 1;
KLOW = KNZ;
NUT = -NUT1;
COMP = Y1 * 1.00001;
for (k--; k > KLOW; k--) {
if (ErrTest(k, NUT, COMP, &ERR))
continue; // for loop
j = HalfTapCount + 2;
COMP = (double) NUT * ERR;
LUCK = 3; // last time in this if(LUCK == 1 || LUCK == 2)
goto L235;
}
if (LUCK == 2) {
if (JCHNGE > 0 && NITER++ < ITRMAX)
goto TOP_LINE;
else
return (NITER);
}
for (j = 1; j <= HalfTapCount; j++) {
ExchangeIndex[HalfTapCount + 2 - j] = ExchangeIndex[HalfTapCount + 1
- j];
}
ExchangeIndex[1] = K1;
if (NITER++ < ITRMAX)
goto TOP_LINE;
} // end if(LUCK == 1 || LUCK == 2)
KN = ExchangeIndex[HalfTapCount + 2];
for (j = 1; j <= HalfTapCount; j++) {
ExchangeIndex[j] = ExchangeIndex[j + 1];
}
ExchangeIndex[HalfTapCount + 1] = KN;
if (NITER++ < ITRMAX)
goto TOP_LINE;
return (NITER);
}
//-----------------------------------------------------------------------
// Function to calculate the lagrange interpolation coefficients for use in the function gee.
double LeGrangeInterp2(int K, int N, int M) // D
{
int j, k;
double Dee, Q;
Dee = 1.0;
Q = CosOfGrid[K];
for (k = 1; k <= M; k++)
for (j = k; j <= N; j += M) {
if (j != K)
Dee = 2.0 * Dee * (Q - CosOfGrid[j]);
}
if (fabs(Dee) < MIN_TEST_VAL) {
if (Dee < 0.0)
Dee = -MIN_TEST_VAL;
else
Dee = MIN_TEST_VAL;
}
return (1.0 / Dee);
}
//-----------------------------------------------------------------------
// Function to evaluate the frequency response using the Lagrange interpolation
// formula in the barycentric form.
double GEE2(int K, int N) {
int j;
double P, C, Dee, XF;
P = 0.0;
XF = Grid[K];
XF = cos(M_2PI * XF);
Dee = 0.0;
for (j = 1; j <= N; j++) {
C = XF - CosOfGrid[j];
if (fabs(C) < MIN_TEST_VAL) {
if (C < 0.0)
C = -MIN_TEST_VAL;
else
C = MIN_TEST_VAL;
}
C = LeGrangeD[j] / C;
Dee = Dee + C;
P = P + C * DesPlus[j];
}
if (fabs(Dee) < MIN_TEST_VAL) {
if (Dee < 0.0)
Dee = -MIN_TEST_VAL;
else
Dee = MIN_TEST_VAL;
}
return (P / Dee);
}
//-----------------------------------------------------------------------
bool ErrTest(int k, int Nut, double Comp, double *Err) {
*Err = GEE2(k, HalfTapCount + 1);
*Err = (*Err - DesiredMag[k]) * Weight[k];
if ((double) Nut * *Err - Comp <= 0.0)
return (true);
else
return (false);
}
//-----------------------------------------------------------------------
// Calculation of the coefficients of the best approximation using the inverse discrete fourier transform.
void CalcCoefficients(void) {
int j, k, n;
double GTempVar, OneOverNumTaps;
double Omega, TempVar, FreqN, TempX, GridCos;
double GeeArray[PARKS_SMALL];
GTempVar = Grid[1];
CosOfGrid[HalfTapCount + 2] = -2.0;
OneOverNumTaps = 1.0 / (double) (2 * HalfTapCount - 1);
k = 1;
for (j = 1; j <= HalfTapCount; j++) {
FreqN = (double) (j - 1) * OneOverNumTaps;
TempX = cos(M_2PI * FreqN);
GridCos = CosOfGrid[k];
if (TempX <= GridCos) {
while (TempX <= GridCos && (GridCos - TempX) >= MIN_TEST_VAL) // MIN_TEST_VAL = 1.0E-6
{
k++;
;
GridCos = CosOfGrid[k];
}
}
if (TempX <= GridCos || (TempX - GridCos) < MIN_TEST_VAL) {
GeeArray[j] = DesPlus[k]; // Desired Response
} else {
Grid[1] = FreqN;
GeeArray[j] = GEE2(1, HalfTapCount + 1);
}
if (k > 1)
k--;
}
Grid[1] = GTempVar;
for (j = 1; j <= HalfTapCount; j++) {
TempVar = 0.0;
Omega = (double) (j - 1) * M_2PI * OneOverNumTaps;
for (n = 1; n <= HalfTapCount - 1; n++) {
TempVar += GeeArray[n + 1] * cos(Omega * (double) n);
}
TempVar = 2.0 * TempVar + GeeArray[1];
Alpha[j] = TempVar;
}
Alpha[1] = Alpha[1] * OneOverNumTaps;
for (j = 2; j <= HalfTapCount; j++) {
Alpha[j] = 2.0 * Alpha[j] * OneOverNumTaps;
}
}
//-----------------------------------------------------------------------
} // namespace