[music-dsp] Re: Revisiting C++ Filtering Classes

robert bristow-johnson rbj at audioimagination.com
Wed Jun 3 20:07:07 EDT 2009

On Jun 3, 2009, at 6:45 PM, Vinnie wrote:

>> From: robert bristow-johnson <rbj at audioimagination.com>
>> i'm trying to understand what your "bottom line" is.
> I want to make this a comprehensive C++ library for IIR filters. I  
> would like to keep the single-header design. The goals:

laudable goal.

okay, i will let you worry about this stuff:

>> - Arbitrary order, changable at runtime
>> - Flexibility to control the floating point types used for  
>> calculation and output
>> - Robust class hierarchy that encapsulates each concept neatly
>> - No repeated formulas or code
>> - Fast parameter changes using cached calculations
>> - Functional programming style that allows easy assembly of  
>> filters from primitives using templates

about this stuff:

> - Butterworth, Chebyshev Type I, Chebyshev Type II, Elliptic,  
> Bessel response types

there are nice closed form formulae for the s-plane coefficients (as  
"normalized s" LPF prototypes) for the first three (perhaps that is  
what you're trying to figure out, you might want to learn some neat  
stuff about the Tchebyshev polynomials to do that).  not so nice for  
the latter two, but you might be able to dig up someone's C code for  
the Jacobi elliptical function and the Bessel functions.

now, with the Chebyshev/Elliptical responses, i would recommend to  
restrict the passband ripple to be less than 3.0103 dB, the max  
stopband gain to be always lower than -3.0103 dB, so that on the  
transition slope, there is always a unique -3.0103 dB point which  
will always be the "corner frequency" when switching between response  

> - All Biquad variants

by this, i think you mean Direct Forms 1&2 and their transposes,  
Lattice and normalized ladder, i think there is a Rader/Gold form but  
i forget what it is.  maybe a "state-variable" form that has discrete- 
time integrators, 1/(z-1), as elements instead of 1/z.  you might  
want to look into stuff that Duane Wise has done with different  
forms.  sometimes there are numerical issues.  i think if you first  
go after the Direct Form coefs, you'll be able to map to the other  
forms straight-forwardly.  that's something to worry about later in  
the venture.

> - Low Pass, High Pass, Band Pass, Band Stop, Low Shelf, High Shelf,  
> Peaking, Notch of every response type

i think that Band Stop and Notch might be the same thing, no?  what i  
might call "Band Reject Filter".  a Band Stop or Notch that does not  
go down to -inf dB at the resonant frequency would be a Peaking  
filter with a negative dB gain parameter.

okay, for this, you want to always bring in the *normalized*  
frequency (that is that the corner is at w=1) LPF prototype of the  
chosen response type above.  then use the following s-plane  
substitutions for the first four shapes:

     LPF:   s <- s

     HPF:   s <- 1/s

     BPF:   s <- Q*(s + 1/s)

     BRF:   s <- Q/(s + 1/s)

note that the order of the filter will double when starting with an  
Nth-order Butterworth/Chebyshev/whatever LPF prototype and mapping  
that to a BPF or BRF.

here, the "Q" is not necessarily the biquad Q unless you started with  
a 1st-order LPF prototype.  think of it as the approximate reciprocal  
of the bandwidth parameter.  i think the good mapping should be (from  
the cookbook):

     1/Q = 2*sinh( ln(2)/2 * BW * w0/sin(w0) )

     where BW is the bandwidth in octaves and w0 = 2*pi*f0/Fs .

now, you're gonna need to think about what are the best definitions  
for your Peaking and Shelf filters.  even though i would *not*  
necessarily recommend taking exactly the definitions in the cookbook,  
i think what you *do* want are definitions so that leaving the Q and  
resonant frequency parameters untouched, a cut of N dB will look  
exactly like a upside-down mirrored reflection of the same with a  
boost of N dB.  in series, the aggregate two should be equivalent to  
a "wire".  if you simply define the Peaking, Low Shelf, or High Shelf  
to be the unscaled input added to the scaled output of a BPF, LPF, or  
HPF (respectively), you may very well get into a situation where the  
boost and cut are not complementary.  so you need to think about this  
a bit.  BW or slope parameters may need to be "fudged" a bit in some  

also, if you define your Shelves as a scaled LPF/HPF added to a wire  
(with the LPF/HPF as perhaps a higher-order Butterworth/Chebyshev/ 
whatever), then you don't have, in the definition, a shelf-slope  
control since there is no "Q" in either the LPF or HPF (to LPF)  
mapping above.  you might not like it that way.

finally, after you've done that substitution, you have an analog  
prototype with the filter shape (Low Pass, etc.) and response type  
(Butterworth, etc.), but it is *still* happening at a *normalized*  
frequency, w=1.  to move it to the desired frequency, w0 = 2*pi*f0/Fs  
(where f0 is expressed in the same units as the sampling frequency  
Fs), you take your normalized s-plane prototype and make this  
Bilinear Transform substitution:

       s   <-   ( 1/tan(w0/2) )*(1 - z^-1)/(1 + z^-1)

that takes care of the BLT substitution *and* compensating the BLT  
frequency warping effect on the resonant frequency at the same time.   
the compensation of the effect that frequency warping has on  
bandwidth is done approximately (but pretty goodly) by the w0/sin(w0)  
stretching factor applied to the octave BW above.


r b-j                  rbj at audioimagination.com

"Imagination is more important than knowledge."

More information about the music-dsp mailing list