Arbitrarily high-order IIR filters

Forgive me if I’m missing the point or if this comment is silly or unproductive…
I know there was some mention at some point about Biquad filters, but I’m not finding that in the linked thread, so I don’t really know what else was said or by who about it, but

Whyquad? (sorry, not sorry :smirk:)

  • Stability/Quantization Error Accumulation: Higher-order filters are generally more complex, involving more coefficients in a more complicated structure. This complexity can lead to potential stability issues, especially in higher frequencies or when the filter specifications are stringent. Biquads are simpler and inherently more stable when designed correctly, plus the poles and zeros of each section are decoupled. Higher order filters continually accumulate quantization error in coefficient calculation over a long feedback path.
  • Computational Efficiency: Implementing a single higher-order filter I believe is more computationally intensive due to the larger number of heavy operations required per sample, whereas cascading biquad filters divides the overall task into smaller, more manageable chunks. Again, I’m not certain about this, but I would be inclined to think that this could be handled rather elegantly with SIMD.
  • Frequency Response: My experience has been that it’s more difficult to get the intended frequency response from a higher order filter without oscillation/overshoot/instability/noise.
  • Phase Response: Higher-order filters can introduce significant phase distortion. Biquad cascades also introducing phase shifts, but can be configured (e.g., using Linkwitz-Riley) to maintain a more coherent phase response across the cascade.
  • Modularity/Flexibility: You can add/remove stages in a biquad cascade, but you have to redesign a higher order filter to achieve the same result.

I also can’t help thinking that this is exactly what FOS and SOS are for…
Why not make a pseudo ugen wrapper for these? Or better still, why not instantiate them as individual synths? That’s what the biquad filter suite does (the B prefix ugens). I think you could actually still just make this an SC wrapper that manages an arbitrary number of FOS/SOS Synths.

1 Like

Of course, transfer functions can be rewritten as a chain of simpler filter segments, and the biquad filter, in particular, has many practical advantages. Theoretically and mathematically, any of these filter segments can be implemented in any order without altering the overall filter behavior.

I haven’t encountered any differing opinions on this. :blush:

Some methods, such as the TDF-II (Transposed Direct Form II), can reduce memory consumption by half. My implementation of high-order filters is exceptionally efficient, using minimal memory and CPU resources (0.3% or less).

There are also some clever techniques that can be employed. For instance, while it may not be practical to change the coefficients of more complex filters in real-time due to computational costs just to calculate the new coeffs (!!!), combining them with an all-pass filter allows for very straightforward and cost-effective adjustments, with very drammatical effects. It sounds great.

They are mathematically equivalent, but the calculation is more complicated.

I want to make clear that my intention here is experimentation and exercise in technique and optimization.

Example: my original idea was to create a C++ class template that could be compiled to any order. It is just a question of simplifying the code that generates filters. That’s why I wrote the code in the first place…

BiQuad filters also can benefit from SIMD operation and loop unrolling. I think I posted an example here.

Yes, that’s the theory when we consider just the equations. I believe we’ve mentioned it once or twice here.

You are incorrect regarding performance. A higher-order filter can potentially have fewer coefficients than multiple cascaded biquads, which can reduce computational complexity. Additionally, there are techniques that can optimize memory usage.

Higher-order filters can achieve better numerical stability when implemented correctly with the necessary attention. Also, it’s a very different kind of flexibility compared to complicated cascading Biquad filters that you would need to change at once. It all depends on what you’re doing. For regular commercial use, biquad is a rule.

But let’s put them to the test. Why not? It’s the same c++ template code that generates all of them)))) that was the idea (in the beginning…)

1 Like

Nice resource: Filter Playground

Sorry if my response was redundant, I’m guessing there was a lot more discussion around this in a separate post that I missed.
I’m very interested in your design though.

Thanks for pointing this out!

Filter design is something I’m continually trying to wrap my tiny brain around.

1 Like

Yes, me too. There are so many things when those things are put into practical code… and the hardware at hand…

I learned that software is not “the” platform; there are many details to pay attention to… and none of the things in the software were designed with digital filters in mind, so in the end, you have to figure out everything by yourself…

I posted. It’s the gist link above. There is also a Haskell version, with around 50 lines of code, including imports. Just like C++, one needs to know what works better for real-time dsp. In the case of Haskell, strictness is better than laziness. Stream fusion to eliminate intermediate data structures during list and vector processing. Unlike C++, all those optimizations require less coding. SIMD in C++, you have to rewrite the loops by hand, and the resulting code is not beautiful.

And using a vector module is a kind of rule (Data.Vector.Storable) for audio. This last detail is also helpful for interoperating with C.

C++ has an experiment simd library if you’re looking for one, one day it might make it into the standard.

1 Like

That’s good to know.

Although, as that talk by SPJ I sent you, taking care of all cases is not simple, and it should be compiler work. Not just simple vectors, but more cases including nested/tree-like types.