mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-05 16:31:15 -05:00
402 lines
14 KiB
C++
402 lines
14 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 yor compiler.
|
|
|
|
RectWinFIR() generates the impulse response for a rectangular windowed low pass, high pass,
|
|
band pass, or notch filter. Then a window, such as the Kaiser, is applied to the FIR coefficients.
|
|
See the FilterKitMain.cpp file for an example on how to use this code.
|
|
|
|
double FirCoeff[MAXNUMTAPS];
|
|
int NumTaps; NumTaps can be even or odd and < MAXNUMTAPS
|
|
TPassTypeName PassType; PassType is defined in the header file. firLPF, firHPF, firBPF, firNOTCH, firALLPASS
|
|
double OmegaC 0.0 < OmegaC < 1.0 The corner freq, or center freq if BPF or NOTCH
|
|
double BW 0.0 < BW < 1.0 The band width if BPF or NOTCH
|
|
*/
|
|
|
|
#include "FIRFilterCode.h"
|
|
#include <math.h>
|
|
|
|
namespace kitiirfir
|
|
{
|
|
|
|
// Rectangular Windowed FIR. The equations used here are developed in numerous textbooks.
|
|
void RectWinFIR(double *FirCoeff, int NumTaps, TFIRPassTypes PassType,
|
|
double OmegaC, double BW) {
|
|
int j;
|
|
double Arg, OmegaLow, OmegaHigh;
|
|
|
|
switch (PassType) {
|
|
case firLPF: // Low Pass
|
|
for (j = 0; j < NumTaps; j++) {
|
|
Arg = (double) j - (double) (NumTaps - 1) / 2.0;
|
|
FirCoeff[j] = OmegaC * Sinc(OmegaC * Arg * M_PI);
|
|
}
|
|
break;
|
|
|
|
case firHPF: // High Pass
|
|
if (NumTaps % 2 == 1) // Odd tap counts
|
|
{
|
|
for (j = 0; j < NumTaps; j++) {
|
|
Arg = (double) j - (double) (NumTaps - 1) / 2.0;
|
|
FirCoeff[j] = Sinc(Arg * M_PI)
|
|
- OmegaC * Sinc(OmegaC * Arg * M_PI);
|
|
}
|
|
}
|
|
|
|
else // Even tap counts
|
|
{
|
|
for (j = 0; j < NumTaps; j++) {
|
|
Arg = (double) j - (double) (NumTaps - 1) / 2.0;
|
|
if (Arg == 0.0)
|
|
FirCoeff[j] = 0.0;
|
|
else
|
|
FirCoeff[j] = cos(OmegaC * Arg * M_PI) / M_PI / Arg
|
|
+ cos(Arg * M_PI);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case firBPF: // Band Pass
|
|
OmegaLow = OmegaC - BW / 2.0;
|
|
OmegaHigh = OmegaC + BW / 2.0;
|
|
for (j = 0; j < NumTaps; j++) {
|
|
Arg = (double) j - (double) (NumTaps - 1) / 2.0;
|
|
if (Arg == 0.0)
|
|
FirCoeff[j] = 0.0;
|
|
else
|
|
FirCoeff[j] = (cos(OmegaLow * Arg * M_PI)
|
|
- cos(OmegaHigh * Arg * M_PI)) / M_PI / Arg;
|
|
}
|
|
break;
|
|
|
|
case firNOTCH: // Notch, if NumTaps is even, the response at Pi is attenuated.
|
|
OmegaLow = OmegaC - BW / 2.0;
|
|
OmegaHigh = OmegaC + BW / 2.0;
|
|
for (j = 0; j < NumTaps; j++) {
|
|
Arg = (double) j - (double) (NumTaps - 1) / 2.0;
|
|
FirCoeff[j] = Sinc(Arg * M_PI)
|
|
- OmegaHigh * Sinc(OmegaHigh * Arg * M_PI)
|
|
- OmegaLow * Sinc(OmegaLow * Arg * M_PI);
|
|
}
|
|
break;
|
|
|
|
case firALLPASS: // All Pass, this is trivial, but it shows how an fir all pass (delay) can be done.
|
|
for (j = 0; j < NumTaps; j++)
|
|
FirCoeff[j] = 0.0;
|
|
FirCoeff[(NumTaps - 1) / 2] = 1.0;
|
|
break;
|
|
case firNOT_FIR:
|
|
default:
|
|
break;
|
|
}
|
|
// Now use the FIRFilterWindow() function to reduce the sinc(x) effects.
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Used to reduce the sinc(x) effects on a set of FIR coefficients. This will, unfortunately,
|
|
// widen the filter's transition band, but the stop band attenuation will improve dramatically.
|
|
void FIRFilterWindow(double *FIRCoeff, int N, TWindowType WindowType,
|
|
double Beta) {
|
|
if (WindowType == wtNONE)
|
|
return;
|
|
|
|
int j;
|
|
double dN, *WinCoeff;
|
|
|
|
if (Beta < 0.0)
|
|
Beta = 0.0;
|
|
if (Beta > 10.0)
|
|
Beta = 10.0;
|
|
|
|
WinCoeff = new double[N + 2];
|
|
if (WinCoeff == 0) {
|
|
// ShowMessage("Failed to allocate memory in WindowData() ");
|
|
return;
|
|
}
|
|
|
|
// Calculate the window for N/2 points, then fold the window over (at the bottom).
|
|
dN = N + 1; // a double
|
|
if (WindowType == wtKAISER) {
|
|
double Arg;
|
|
for (j = 0; j < N; j++) {
|
|
Arg = Beta * sqrt(1.0 - pow(((double) (2 * j + 2) - dN) / dN, 2.0));
|
|
WinCoeff[j] = Bessel(Arg) / Bessel(Beta);
|
|
}
|
|
}
|
|
|
|
else if (WindowType == wtSINC) // Lanczos
|
|
{
|
|
for (j = 0; j < N; j++)
|
|
WinCoeff[j] = Sinc((double) (2 * j + 1 - N) / dN * M_PI);
|
|
for (j = 0; j < N; j++)
|
|
WinCoeff[j] = pow(WinCoeff[j], Beta);
|
|
}
|
|
|
|
else if (WindowType == wtSINE) // Hanning if Beta = 2
|
|
{
|
|
for (j = 0; j < N / 2; j++)
|
|
WinCoeff[j] = sin((double) (j + 1) * M_PI / dN);
|
|
for (j = 0; j < N / 2; j++)
|
|
WinCoeff[j] = pow(WinCoeff[j], Beta);
|
|
}
|
|
|
|
else // Error.
|
|
{
|
|
// ShowMessage("Incorrect window type in WindowFFTData");
|
|
delete[] WinCoeff;
|
|
return;
|
|
}
|
|
|
|
// Fold the coefficients over.
|
|
for (j = 0; j < N / 2; j++)
|
|
WinCoeff[N - j - 1] = WinCoeff[j];
|
|
|
|
// Apply the window to the FIR coefficients.
|
|
for (j = 0; j < N; j++)
|
|
FIRCoeff[j] *= WinCoeff[j];
|
|
|
|
delete[] WinCoeff;
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This implements an FIR filter. The register shifts are done by rotating the indexes.
|
|
void FilterWithFIR(double *FirCoeff, int NumTaps, double *Signal,
|
|
double *FilteredSignal, int NumSigPts) {
|
|
int j, k, n, Top = 0;
|
|
double y, Reg[MAX_NUMTAPS];
|
|
|
|
for (j = 0; j < NumTaps; j++)
|
|
Reg[j] = 0.0;
|
|
|
|
for (j = 0; j < NumSigPts; j++) {
|
|
Reg[Top] = Signal[j];
|
|
y = 0.0;
|
|
n = 0;
|
|
|
|
// The FirCoeff index increases while the Reg index decreases.
|
|
for (k = Top; k >= 0; k--) {
|
|
y += FirCoeff[n++] * Reg[k];
|
|
}
|
|
for (k = NumTaps - 1; k > Top; k--) {
|
|
y += FirCoeff[n++] * Reg[k];
|
|
}
|
|
FilteredSignal[j] = y;
|
|
|
|
Top++;
|
|
if (Top >= NumTaps)
|
|
Top = 0;
|
|
}
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This code is equivalent to the code above. It uses register shifts, which makes it
|
|
// less efficient, but it is easier to follow (i.e. compare to a FIR flow chart).
|
|
void FilterWithFIR2(double *FirCoeff, int NumTaps, double *Signal,
|
|
double *FilteredSignal, int NumSigPts) {
|
|
int j, k;
|
|
double y, Reg[MAX_NUMTAPS];
|
|
|
|
for (j = 0; j < NumTaps; j++)
|
|
Reg[j] = 0.0; // Init the delay registers.
|
|
|
|
for (j = 0; j < NumSigPts; j++) {
|
|
// Shift the register values down and set Reg[0].
|
|
for (k = NumTaps; k > 1; k--)
|
|
Reg[k - 1] = Reg[k - 2];
|
|
Reg[0] = Signal[j];
|
|
|
|
y = 0.0;
|
|
for (k = 0; k < NumTaps; k++)
|
|
y += FirCoeff[k] * Reg[k];
|
|
FilteredSignal[j] = y;
|
|
}
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This function is used to correct the corner frequency values on FIR filters.
|
|
// We normally specify the 3 dB frequencies when specifing a filter. The Parks McClellan routine
|
|
// uses OmegaC and BW to set the 0 dB band edges, so its final OmegaC and BW values are not close
|
|
// to -3 dB. The Rectangular Windowed filters are better for high tap counts, but for low tap counts,
|
|
// their 3 dB frequencies are also well off the mark.
|
|
|
|
// To use this function, first calculate a set of FIR coefficients, then pass them here, along with
|
|
// OmegaC and BW. This calculates a corrected OmegaC for low and high pass filters. It calcultes a
|
|
// corrected BW for band pass and notch filters. Use these corrected values to recalculate the FIR filter.
|
|
|
|
// The Goertzel algorithm is used to calculate the filter's magnitude response at the single
|
|
// frequency defined in the loop. We start in the pass band and work out to the -20dB freq.
|
|
|
|
void FIRFreqError(double *Coeff, int NumTaps, int PassType, double *OmegaC,
|
|
double *BW) {
|
|
int j, J3dB, CenterJ;
|
|
double Omega, CorrectedOmega, CorrectedBW, Omega1, Omega2, Mag;
|
|
|
|
// In these loops, we break at -20 dB to ensure that large ripple is ignored.
|
|
if (PassType == firLPF) {
|
|
J3dB = 10;
|
|
for (j = 0; j < NUM_FREQ_ERR_PTS; j++) {
|
|
Omega = (double) j / dNUM_FREQ_ERR_PTS;
|
|
Mag = Goertzel(Coeff, NumTaps, Omega);
|
|
if (Mag > 0.707)
|
|
J3dB = j; // J3dB will be the last j where the response was > -3 dB
|
|
if (Mag < 0.1)
|
|
break; // Stop when the response is down to -20 dB.
|
|
}
|
|
Omega = (double) J3dB / dNUM_FREQ_ERR_PTS;
|
|
}
|
|
|
|
else if (PassType == firHPF) {
|
|
J3dB = NUM_FREQ_ERR_PTS - 10;
|
|
for (j = NUM_FREQ_ERR_PTS - 1; j >= 0; j--) {
|
|
Omega = (double) j / dNUM_FREQ_ERR_PTS;
|
|
Mag = Goertzel(Coeff, NumTaps, Omega);
|
|
if (Mag > 0.707)
|
|
J3dB = j; // J3dB will be the last j where the response was > -3 dB
|
|
if (Mag < 0.1)
|
|
break; // Stop when the response is down to -20 dB.
|
|
}
|
|
Omega = (double) J3dB / dNUM_FREQ_ERR_PTS;
|
|
}
|
|
|
|
else if (PassType == firBPF) {
|
|
CenterJ = (int) (dNUM_FREQ_ERR_PTS * *OmegaC);
|
|
J3dB = CenterJ;
|
|
for (j = CenterJ; j >= 0; j--) {
|
|
Omega = (double) j / dNUM_FREQ_ERR_PTS;
|
|
Mag = Goertzel(Coeff, NumTaps, Omega);
|
|
if (Mag > 0.707)
|
|
J3dB = j;
|
|
if (Mag < 0.1)
|
|
break;
|
|
}
|
|
Omega1 = (double) J3dB / dNUM_FREQ_ERR_PTS;
|
|
|
|
J3dB = CenterJ;
|
|
for (j = CenterJ; j < NUM_FREQ_ERR_PTS; j++) {
|
|
Omega = (double) j / dNUM_FREQ_ERR_PTS;
|
|
Mag = Goertzel(Coeff, NumTaps, Omega);
|
|
if (Mag > 0.707)
|
|
J3dB = j;
|
|
if (Mag < 0.1)
|
|
break;
|
|
}
|
|
Omega2 = (double) J3dB / dNUM_FREQ_ERR_PTS;
|
|
}
|
|
|
|
// The code above starts in the pass band. This starts in the stop band.
|
|
else // PassType == firNOTCH
|
|
{
|
|
CenterJ = (int) (dNUM_FREQ_ERR_PTS * *OmegaC);
|
|
J3dB = CenterJ;
|
|
for (j = CenterJ; j >= 0; j--) {
|
|
Omega = (double) j / dNUM_FREQ_ERR_PTS;
|
|
Mag = Goertzel(Coeff, NumTaps, Omega);
|
|
if (Mag <= 0.707)
|
|
J3dB = j;
|
|
if (Mag > 0.99)
|
|
break;
|
|
}
|
|
Omega1 = (double) J3dB / dNUM_FREQ_ERR_PTS;
|
|
|
|
J3dB = CenterJ;
|
|
for (j = CenterJ; j < NUM_FREQ_ERR_PTS; j++) {
|
|
Omega = (double) j / dNUM_FREQ_ERR_PTS;
|
|
Mag = Goertzel(Coeff, NumTaps, Omega);
|
|
if (Mag <= 0.707)
|
|
J3dB = j;
|
|
if (Mag > 0.99)
|
|
break;
|
|
}
|
|
Omega2 = (double) J3dB / dNUM_FREQ_ERR_PTS;
|
|
}
|
|
|
|
// This calculates the corrected OmegaC and BW and error checks the values.
|
|
if (PassType == firLPF || PassType == firHPF) {
|
|
CorrectedOmega = *OmegaC * 2.0 - Omega; // This is usually OK.
|
|
if (CorrectedOmega < 0.001)
|
|
CorrectedOmega = 0.001;
|
|
if (CorrectedOmega > 0.99)
|
|
CorrectedOmega = 0.99;
|
|
*OmegaC = CorrectedOmega;
|
|
}
|
|
|
|
else // PassType == firBPF || PassType == firNOTCH
|
|
{
|
|
CorrectedBW = *BW * 2.0 - (Omega2 - Omega1); // This routinely goes neg with Notch.
|
|
if (CorrectedBW < 0.01)
|
|
CorrectedBW = 0.01;
|
|
if (CorrectedBW > *BW * 2.0)
|
|
CorrectedBW = *BW * 2.0;
|
|
if (CorrectedBW > 0.98)
|
|
CorrectedBW = 0.98;
|
|
*BW = CorrectedBW;
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// This shows how to adjust the delay of an FIR by a fractional amount.
|
|
// We take the FFT of the FIR coefficients to get to the frequency domain,
|
|
// then apply the Laplace delay operator, and then do an inverse FFT.
|
|
|
|
// Use this function last. i.e. After the window was applied to the coefficients.
|
|
// The Delay value is in terms of a fraction of a sample (not in terms of sampling freq).
|
|
// Delay may be pos or neg. Typically a filter's delay can be adjusted by +/- NumTaps/20
|
|
// without affecting its performance significantly. A typical Delay value would be 0.75
|
|
void AdjustDelay(double *FirCoeff, int NumTaps, double Delay) {
|
|
int j, FFTSize;
|
|
double *FFTInputR, *FFTInputI, Arg, Temp;
|
|
FFTSize = RequiredFFTSize(NumTaps + (int) fabs(Delay) + 1); // Zero pad by at least Delay + 1 to prevent the impulse response from wrapping around.
|
|
|
|
FFTInputR = new double[FFTSize]; // Real part
|
|
FFTInputI = new double[FFTSize]; // Imag part
|
|
if (FFTInputR == 0 || FFTInputI == 0) {
|
|
//ShowMessage("Unable to allocate memory in AdjustDelay");
|
|
return;
|
|
}
|
|
for (j = 0; j < FFTSize; j++)
|
|
FFTInputR[j] = FFTInputI[j] = 0.0; // A mandatory init.
|
|
for (j = 0; j < NumTaps; j++)
|
|
FFTInputR[j] = FirCoeff[j]; // Fill the real part with the FIR coeff.
|
|
|
|
FFT(FFTInputR, FFTInputI, FFTSize, FORWARD); // Do an FFT
|
|
for (j = 0; j <= FFTSize / 2; j++) // Apply the Laplace Delay operator e^(-j*omega*Delay).
|
|
{
|
|
Arg = -Delay * (double) j / (double) FFTSize * M_2PI; // This is -Delay * (the FFT bin frequency).
|
|
Temp = cos(Arg) * FFTInputR[j] - sin(Arg) * FFTInputI[j];
|
|
FFTInputI[j] = cos(Arg) * FFTInputI[j] + sin(Arg) * FFTInputR[j];
|
|
FFTInputR[j] = Temp;
|
|
}
|
|
for (j = 1; j < FFTSize / 2; j++) // Fill the neg freq bins with the conjugate values.
|
|
{
|
|
FFTInputR[FFTSize - j] = FFTInputR[j];
|
|
FFTInputI[FFTSize - j] = -FFTInputI[j];
|
|
}
|
|
|
|
FFT(FFTInputR, FFTInputI, FFTSize, INVERSE); // Inverse FFT
|
|
for (j = 0; j < NumTaps; j++) {
|
|
FirCoeff[j] = FFTInputR[j];
|
|
}
|
|
|
|
delete[] FFTInputR;
|
|
delete[] FFTInputI;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
|
|
} //namedspace
|