mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-16 16:10:06 +02:00
Consistently use double within Biquad filter coefficient calcultations.
This avoids catastrophic rounding errors which can even lead to filter instability in some cases. This is a real problem in the coming IIR envelope detector.
This commit is contained in:
parent
86ae0460c9
commit
e439e8f4c7
@ -64,20 +64,20 @@ ArrayOf<Biquad> Biquad::CalcButterworthFilter(int order, double fn, double fc, i
|
|||||||
{
|
{
|
||||||
ArrayOf<Biquad> pBiquad(size_t((order+1) / 2), true);
|
ArrayOf<Biquad> pBiquad(size_t((order+1) / 2), true);
|
||||||
// Set up the coefficients in all the biquads
|
// Set up the coefficients in all the biquads
|
||||||
float fNorm = fc / fn;
|
double fNorm = fc / fn;
|
||||||
if (fNorm >= 0.9999)
|
if (fNorm >= 0.9999)
|
||||||
fNorm = 0.9999F;
|
fNorm = 0.9999F;
|
||||||
float fC = tan (PI * fNorm / 2);
|
double fC = tan (PI * fNorm / 2);
|
||||||
float fDCPoleDistSqr = 1.0F;
|
double fDCPoleDistSqr = 1.0F;
|
||||||
float fZPoleX, fZPoleY;
|
double fZPoleX, fZPoleY;
|
||||||
|
|
||||||
if ((order & 1) == 0)
|
if ((order & 1) == 0)
|
||||||
{
|
{
|
||||||
// Even order
|
// Even order
|
||||||
for (int iPair = 0; iPair < order/2; iPair++)
|
for (int iPair = 0; iPair < order/2; iPair++)
|
||||||
{
|
{
|
||||||
float fSPoleX = fC * cos (PI - (iPair + 0.5) * PI / order);
|
double fSPoleX = fC * cos (PI - (iPair + 0.5) * PI / order);
|
||||||
float fSPoleY = fC * sin (PI - (iPair + 0.5) * PI / order);
|
double fSPoleY = fC * sin (PI - (iPair + 0.5) * PI / order);
|
||||||
BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
|
BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
|
||||||
pBiquad[iPair].fNumerCoeffs [B0] = 1;
|
pBiquad[iPair].fNumerCoeffs [B0] = 1;
|
||||||
if (subtype == kLowPass) // LOWPASS
|
if (subtype == kLowPass) // LOWPASS
|
||||||
@ -96,8 +96,8 @@ ArrayOf<Biquad> Biquad::CalcButterworthFilter(int order, double fn, double fc, i
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Odd order - first do the 1st-order section
|
// Odd order - first do the 1st-order section
|
||||||
float fSPoleX = -fC;
|
double fSPoleX = -fC;
|
||||||
float fSPoleY = 0;
|
double fSPoleY = 0;
|
||||||
BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
|
BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
|
||||||
pBiquad[0].fNumerCoeffs [B0] = 1;
|
pBiquad[0].fNumerCoeffs [B0] = 1;
|
||||||
if (subtype == kLowPass) // LOWPASS
|
if (subtype == kLowPass) // LOWPASS
|
||||||
@ -113,8 +113,8 @@ ArrayOf<Biquad> Biquad::CalcButterworthFilter(int order, double fn, double fc, i
|
|||||||
fDCPoleDistSqr = fZPoleX + 1; // dist from Nyquist
|
fDCPoleDistSqr = fZPoleX + 1; // dist from Nyquist
|
||||||
for (int iPair = 1; iPair <= order/2; iPair++)
|
for (int iPair = 1; iPair <= order/2; iPair++)
|
||||||
{
|
{
|
||||||
float fSPoleX = fC * cos (PI - iPair * PI / order);
|
double fSPoleX = fC * cos (PI - iPair * PI / order);
|
||||||
float fSPoleY = fC * sin (PI - iPair * PI / order);
|
double fSPoleY = fC * sin (PI - iPair * PI / order);
|
||||||
BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
|
BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
|
||||||
pBiquad[iPair].fNumerCoeffs [B0] = 1;
|
pBiquad[iPair].fNumerCoeffs [B0] = 1;
|
||||||
if (subtype == kLowPass) // LOWPASS
|
if (subtype == kLowPass) // LOWPASS
|
||||||
@ -146,22 +146,22 @@ ArrayOf<Biquad> Biquad::CalcChebyshevType1Filter(int order, double fn, double fc
|
|||||||
{
|
{
|
||||||
ArrayOf<Biquad> pBiquad(size_t((order+1) / 2), true);
|
ArrayOf<Biquad> pBiquad(size_t((order+1) / 2), true);
|
||||||
// Set up the coefficients in all the biquads
|
// Set up the coefficients in all the biquads
|
||||||
float fNorm = fc / fn;
|
double fNorm = fc / fn;
|
||||||
if (fNorm >= 0.9999)
|
if (fNorm >= 0.9999)
|
||||||
fNorm = 0.9999F;
|
fNorm = 0.9999F;
|
||||||
float fC = tan (PI * fNorm / 2);
|
double fC = tan (PI * fNorm / 2);
|
||||||
float fDCPoleDistSqr = 1.0F;
|
double fDCPoleDistSqr = 1.0F;
|
||||||
float fZPoleX, fZPoleY;
|
double fZPoleX, fZPoleY;
|
||||||
float fZZeroX;
|
double fZZeroX;
|
||||||
float beta = cos (fNorm*PI);
|
double beta = cos (fNorm*PI);
|
||||||
|
|
||||||
double eps; eps = sqrt (pow (10.0, wxMax(0.001, ripple) / 10.0) - 1);
|
double eps; eps = sqrt (pow (10.0, wxMax(0.001, ripple) / 10.0) - 1);
|
||||||
double a; a = log (1 / eps + sqrt(1 / square(eps) + 1)) / order;
|
double a; a = log (1 / eps + sqrt(1 / square(eps) + 1)) / order;
|
||||||
// Assume even order to start
|
// Assume even order to start
|
||||||
for (int iPair = 0; iPair < order/2; iPair++)
|
for (int iPair = 0; iPair < order/2; iPair++)
|
||||||
{
|
{
|
||||||
float fSPoleX = -fC * sinh (a) * sin ((2*iPair + 1) * PI / (2 * order));
|
double fSPoleX = -fC * sinh (a) * sin ((2*iPair + 1) * PI / (2 * order));
|
||||||
float fSPoleY = fC * cosh (a) * cos ((2*iPair + 1) * PI / (2 * order));
|
double fSPoleY = fC * cosh (a) * cos ((2*iPair + 1) * PI / (2 * order));
|
||||||
BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
|
BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
|
||||||
if (subtype == kLowPass) // LOWPASS
|
if (subtype == kLowPass) // LOWPASS
|
||||||
{
|
{
|
||||||
@ -185,7 +185,7 @@ ArrayOf<Biquad> Biquad::CalcChebyshevType1Filter(int order, double fn, double fc
|
|||||||
}
|
}
|
||||||
if ((order & 1) == 0)
|
if ((order & 1) == 0)
|
||||||
{
|
{
|
||||||
float fTemp = DB_TO_LINEAR(-wxMax(0.001, ripple)); // at DC the response is down R dB (for even-order)
|
double fTemp = DB_TO_LINEAR(-wxMax(0.001, ripple)); // at DC the response is down R dB (for even-order)
|
||||||
pBiquad[0].fNumerCoeffs [B0] *= fTemp;
|
pBiquad[0].fNumerCoeffs [B0] *= fTemp;
|
||||||
pBiquad[0].fNumerCoeffs [B1] *= fTemp;
|
pBiquad[0].fNumerCoeffs [B1] *= fTemp;
|
||||||
pBiquad[0].fNumerCoeffs [B2] *= fTemp;
|
pBiquad[0].fNumerCoeffs [B2] *= fTemp;
|
||||||
@ -193,8 +193,8 @@ ArrayOf<Biquad> Biquad::CalcChebyshevType1Filter(int order, double fn, double fc
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Odd order - now do the 1st-order section
|
// Odd order - now do the 1st-order section
|
||||||
float fSPoleX = -fC * sinh (a);
|
double fSPoleX = -fC * sinh (a);
|
||||||
float fSPoleY = 0;
|
double fSPoleY = 0;
|
||||||
BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
|
BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
|
||||||
if (subtype == kLowPass) // LOWPASS
|
if (subtype == kLowPass) // LOWPASS
|
||||||
{
|
{
|
||||||
@ -228,17 +228,17 @@ ArrayOf<Biquad> Biquad::CalcChebyshevType2Filter(int order, double fn, double fc
|
|||||||
{
|
{
|
||||||
ArrayOf<Biquad> pBiquad(size_t((order+1) / 2), true);
|
ArrayOf<Biquad> pBiquad(size_t((order+1) / 2), true);
|
||||||
// Set up the coefficients in all the biquads
|
// Set up the coefficients in all the biquads
|
||||||
float fNorm = fc / fn;
|
double fNorm = fc / fn;
|
||||||
if (fNorm >= 0.9999)
|
if (fNorm >= 0.9999)
|
||||||
fNorm = 0.9999F;
|
fNorm = 0.9999F;
|
||||||
float fC = tan (PI * fNorm / 2);
|
double fC = tan (PI * fNorm / 2);
|
||||||
float fDCPoleDistSqr = 1.0F;
|
double fDCPoleDistSqr = 1.0F;
|
||||||
float fZPoleX, fZPoleY;
|
double fZPoleX, fZPoleY;
|
||||||
float fZZeroX, fZZeroY;
|
double fZZeroX, fZZeroY;
|
||||||
float beta = cos (fNorm*PI);
|
double beta = cos (fNorm*PI);
|
||||||
|
|
||||||
float fSZeroX, fSZeroY;
|
double fSZeroX, fSZeroY;
|
||||||
float fSPoleX, fSPoleY;
|
double fSPoleX, fSPoleY;
|
||||||
double eps = DB_TO_LINEAR(-wxMax(0.001, ripple));
|
double eps = DB_TO_LINEAR(-wxMax(0.001, ripple));
|
||||||
double a = log (1 / eps + sqrt(1 / square(eps) + 1)) / order;
|
double a = log (1 / eps + sqrt(1 / square(eps) + 1)) / order;
|
||||||
|
|
||||||
@ -304,22 +304,23 @@ ArrayOf<Biquad> Biquad::CalcChebyshevType2Filter(int order, double fn, double fc
|
|||||||
return std::move(pBiquad);
|
return std::move(pBiquad);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Biquad::ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI)
|
void Biquad::ComplexDiv (double fNumerR, double fNumerI, double fDenomR, double fDenomI,
|
||||||
|
double* pfQuotientR, double* pfQuotientI)
|
||||||
{
|
{
|
||||||
float fDenom = square(fDenomR) + square(fDenomI);
|
double fDenom = square(fDenomR) + square(fDenomI);
|
||||||
*pfQuotientR = (fNumerR * fDenomR + fNumerI * fDenomI) / fDenom;
|
*pfQuotientR = (fNumerR * fDenomR + fNumerI * fDenomI) / fDenom;
|
||||||
*pfQuotientI = (fNumerI * fDenomR - fNumerR * fDenomI) / fDenom;
|
*pfQuotientI = (fNumerI * fDenomR - fNumerR * fDenomI) / fDenom;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Biquad::BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY)
|
bool Biquad::BilinTransform (double fSX, double fSY, double* pfZX, double* pfZY)
|
||||||
{
|
{
|
||||||
float fDenom = square (1 - fSX) + square (fSY);
|
double fDenom = square (1 - fSX) + square (fSY);
|
||||||
*pfZX = (1 - square (fSX) - square (fSY)) / fDenom;
|
*pfZX = (1 - square (fSX) - square (fSY)) / fDenom;
|
||||||
*pfZY = 2 * fSY / fDenom;
|
*pfZY = 2 * fSY / fDenom;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Biquad::Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2)
|
float Biquad::Calc2D_DistSqr (double fX1, double fY1, double fX2, double fY2)
|
||||||
{
|
{
|
||||||
return square (fX1 - fX2) + square (fY1 - fY2);
|
return square (fX1 - fX2) + square (fY1 - fY2);
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,9 @@ struct Biquad
|
|||||||
|
|
||||||
inline float ProcessOne(float fIn)
|
inline float ProcessOne(float fIn)
|
||||||
{
|
{
|
||||||
float fOut = fIn * fNumerCoeffs[B0] +
|
// Biquad must use double for all calculations. Otherwise some
|
||||||
|
// filters may have catastrophic rounding errors!
|
||||||
|
double fOut = double(fIn) * fNumerCoeffs[B0] +
|
||||||
fPrevIn * fNumerCoeffs[B1] +
|
fPrevIn * fNumerCoeffs[B1] +
|
||||||
fPrevPrevIn * fNumerCoeffs[B2] -
|
fPrevPrevIn * fNumerCoeffs[B2] -
|
||||||
fPrevOut * fDenomCoeffs[A1] -
|
fPrevOut * fDenomCoeffs[A1] -
|
||||||
@ -47,12 +49,12 @@ struct Biquad
|
|||||||
return fOut;
|
return fOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
float fNumerCoeffs[3]; // B0 B1 B2
|
double fNumerCoeffs[3]; // B0 B1 B2
|
||||||
float fDenomCoeffs[2]; // A1 A2, A0 == 1.0
|
double fDenomCoeffs[2]; // A1 A2, A0 == 1.0
|
||||||
float fPrevIn;
|
double fPrevIn;
|
||||||
float fPrevPrevIn;
|
double fPrevPrevIn;
|
||||||
float fPrevOut;
|
double fPrevOut;
|
||||||
float fPrevPrevOut;
|
double fPrevPrevOut;
|
||||||
|
|
||||||
enum kSubTypes
|
enum kSubTypes
|
||||||
{
|
{
|
||||||
@ -65,9 +67,10 @@ struct Biquad
|
|||||||
static ArrayOf<Biquad> CalcChebyshevType1Filter(int order, double fn, double fc, double ripple, int type);
|
static ArrayOf<Biquad> CalcChebyshevType1Filter(int order, double fn, double fc, double ripple, int type);
|
||||||
static ArrayOf<Biquad> CalcChebyshevType2Filter(int order, double fn, double fc, double ripple, int type);
|
static ArrayOf<Biquad> CalcChebyshevType2Filter(int order, double fn, double fc, double ripple, int type);
|
||||||
|
|
||||||
static void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI);
|
static void ComplexDiv (double fNumerR, double fNumerI, double fDenomR, double fDenomI,
|
||||||
static bool BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY);
|
double* pfQuotientR, double* pfQuotientI);
|
||||||
static float Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2);
|
static bool BilinTransform (double fSX, double fSY, double* pfZX, double* pfZY);
|
||||||
|
static float Calc2D_DistSqr (double fX1, double fY1, double fX2, double fY2);
|
||||||
|
|
||||||
static const double s_fChebyCoeffs[MAX_Order][MAX_Order + 1];
|
static const double s_fChebyCoeffs[MAX_Order][MAX_Order + 1];
|
||||||
static double ChebyPoly(int Order, double NormFreq);
|
static double ChebyPoly(int Order, double NormFreq);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user