diff --git a/include/BiQuad.h b/include/BiQuad.h index 5e8aaf7..b085c3a 100644 --- a/include/BiQuad.h +++ b/include/BiQuad.h @@ -13,10 +13,15 @@ namespace stk { Methods are provided for creating a resonance or notch in the frequency response while maintaining a constant filter gain. + Formulae used calculate coefficients for lowpass, highpass, + bandpass, bandreject and allpass are found on pg. 55 of + Udo Zölzer's "DAFX - Digital Audio Effects" (2011 2nd ed). + by Perry R. Cook and Gary P. Scavone, 1995--2021. */ /***************************************************/ +const StkFloat RECIP_SQRT_2 = static_cast( M_SQRT1_2 ); class BiQuad : public Filter { public: @@ -74,12 +79,72 @@ public: */ void setNotch( StkFloat frequency, StkFloat radius ); + //! Set the filter coefficients for a low-pass with cutoff frequency \e fc (in Hz) and Q-factor \e Q. + /*! + This method determines the filter coefficients corresponding to a + low-pass filter with cutoff placed at \e fc, where sloping behaviour + and resonance are determined by \e Q. The default value for \e Q is + 1/sqrt(2), resulting in a gradual attenuation of frequencies higher than + \e fc without added resonance. Values greater than this will more + aggressively attenuate frequencies above \e fc while also adding a + resonance at \e fc. Values less than this will result in a more gradual + attenuation of frequencies above \e fc, but will also attenuate + frequencies below \e fc as well. Both \e fc and \e Q must be positive. + */ + void setLowPass( StkFloat fc, StkFloat Q=RECIP_SQRT_2 ); + + //! Set the filter coefficients for a high-pass with cutoff frequency \e fc (in Hz) and Q-factor \e Q. + /*! + This method determines the filter coefficients corresponding to a high-pass + filter with cutoff placed at \e fc, where sloping behaviour and resonance + are determined by \e Q. The default value for \e Q is 1/sqrt(2), resulting + in a gradual attenuation of frequencies lower than \e fc without added + resonance. Values greater than this will more aggressively attenuate + frequencies below \e fc while also adding a resonance at \e fc. Values less + than this will result in a more gradual attenuation of frequencies below + \e fc, but will also attenuate frequencies above \e fc as well. + Both \e fc and \e Q must be positive. + */ + void setHighPass( StkFloat fc, StkFloat Q=RECIP_SQRT_2 ); + + //! Set the filter coefficients for a band-pass centered at \e fc (in Hz) with Q-factor \e Q. + /*! + This method determines the filter coefficients corresponding to a band-pass + filter with pass-band centered at \e fc, where band width and slope a + determined by \e Q. Values for \e Q that are less than 1.0 will attenuate + frequencies above and below \e fc more gradually, resulting in a convex + slope and a wider band. Values for \e Q greater than 1.0 will attenuate + frequencies above and below \e fc more aggressively, resulting in a + concave slope and a narrower band. Both \e fc and \e Q must be positive. + */ + void setBandPass( StkFloat fc, StkFloat Q ); + + //! Set the filter coefficients for a band-reject centered at \e fc (in Hz) with Q-factor \e Q. + /*! + This method determines the filter coefficients corresponding to a + band-reject filter with stop-band centered at \e fc, where band width + and slope are determined by \e Q. Values for \e Q that are less than 1.0 + will yield a wider band with greater attenuation of \e fc. Values for \e Q + greater than 1.0 will yield a narrower band with less attenuation of \e fc. + Both \e fc and \e Q must be positive. + */ + void setBandReject( StkFloat fc, StkFloat Q ); + + //! Set the filter coefficients for an all-pass centered at \e fc (in Hz) with Q-factor \e Q. + /*! + This method determines the filter coefficients corresponding to + an all-pass filter whose phase response crosses -pi radians at \e fc. + High values for \e Q will result in a more instantaenous shift in phase + response at \e fc. Lower values will result in a more gradual shift in + phase response around \e fc. Both \e fc and \e Q must be positive. + */ + void setAllPass( StkFloat fc, StkFloat Q ); + //! Sets the filter zeroes for equal resonance gain. /*! When using the filter as a resonator, zeroes places at z = 1, z = -1 will result in a constant gain at resonance of 1 / (1 - R), where R is the pole radius setting. - */ void setEqualGainZeroes( void ); @@ -114,6 +179,14 @@ public: protected: virtual void sampleRateChanged( StkFloat newRate, StkFloat oldRate ); + + // Helper function to update the three intermediate values for the predefined filter types + // along with the feedback filter coefficients. Performs the debug check for fc and Q-factor arguments. + void setCommonFilterValues( StkFloat fc, StkFloat Q ); + + StkFloat K_; + StkFloat kSqr_; + StkFloat denom_; }; inline StkFloat BiQuad :: tick( StkFloat input ) diff --git a/src/BiQuad.cpp b/src/BiQuad.cpp index 024a65f..64e5fe4 100644 --- a/src/BiQuad.cpp +++ b/src/BiQuad.cpp @@ -24,6 +24,10 @@ BiQuad :: BiQuad() : Filter() inputs_.resize( 3, 1, 0.0 ); outputs_.resize( 3, 1, 0.0 ); + K_ = 0.0; + kSqr_ = 0.0; + denom_ = 1.0; + Stk::addSampleRateAlert( this ); } @@ -73,6 +77,11 @@ void BiQuad :: setResonance( StkFloat frequency, StkFloat radius, bool normalize b_[1] = 0.0; b_[2] = -b_[0]; } + else { + b_[0] = 1.0; + b_[1] = 0.0; + b_[2] = 0.0; + } } void BiQuad :: setNotch( StkFloat frequency, StkFloat radius ) @@ -89,8 +98,57 @@ void BiQuad :: setNotch( StkFloat frequency, StkFloat radius ) #endif // This method does not attempt to normalize the filter gain. - b_[2] = radius * radius; + b_[0] = 1.0; b_[1] = (StkFloat) -2.0 * radius * cos( TWO_PI * (double) frequency / Stk::sampleRate() ); + b_[2] = radius * radius; + + a_[1] = 0.0; + a_[2] = 0.0; +} + +void BiQuad :: setLowPass( StkFloat fc, StkFloat Q ) +{ + setCommonFilterValues(fc, Q); + + b_[0] = kSqr_ * Q * denom_; + b_[1] = 2 * b_[0]; + b_[2] = b_[0]; +} + +void BiQuad :: setHighPass( StkFloat fc, StkFloat Q ) +{ + setCommonFilterValues(fc, Q); + + b_[0] = Q * denom_; + b_[1] = -2 * b_[0]; + b_[2] = b_[0]; +} + +void BiQuad :: setBandPass( StkFloat fc, StkFloat Q ) +{ + setCommonFilterValues(fc, Q); + + b_[0] = K_ * denom_; + b_[1] = 0.0; + b_[2] = -b_[0]; +} + +void BiQuad :: setBandReject( StkFloat fc, StkFloat Q ) +{ + setCommonFilterValues(fc, Q); + + b_[0] = Q * (kSqr_ + 1) * denom_; + b_[1] = 2 * Q * (kSqr_ - 1) * denom_; + b_[2] = b_[0]; +} + +void BiQuad :: setAllPass( StkFloat fc, StkFloat Q ) +{ + setCommonFilterValues(fc, Q); + + b_[0] = a_[2]; + b_[1] = a_[1]; + b_[2] = 1; } void BiQuad :: setEqualGainZeroes( void ) @@ -100,4 +158,25 @@ void BiQuad :: setEqualGainZeroes( void ) b_[2] = -1.0; } +void BiQuad :: setCommonFilterValues( StkFloat fc, StkFloat Q) +{ +#if defined(_STK_DEBUG_) + if ( fc < 0.0 ) { + oStream_ << "BiQuad::updateKValues: fc argument (" << fc << ") is negative!"; + handleError( StkError::WARNING ); return; + } + if ( Q < 0.0 ) { + oStream_ << "BiQuad::updateKValues: Q argument (" << Q << ") is negative!"; + handleError( StkError::WARNING ); return; + } +#endif + + K_ = tan(PI * fc / Stk::sampleRate()); + kSqr_ = K_ * K_; + denom_ = 1 / (kSqr_ * Q + K_ + Q); + + a_[1] = 2 * Q * (kSqr_ - 1) * denom_; + a_[2] = (kSqr_ * Q - K_ + Q) * denom_; +} + } // stk namespace