Simple C code for resonant LPF / HPF filters, and high / low-shelving EQs

Lately, as part of a very ambitious project based on a Teensy / Arduino, I’ve had to code LPF and HPF filters – the resonant kind found in subtractive synthesizers – and also high- and low-shelving EQs.

I was surprised that an exhaustive search of the web didn’t turn up a simple, stock-standard way of doing this, and even many hours spent attempting to utilise what I did find yielded effectively nothing. Most documentation concerned using mathematical objects called Infinite Impulse Responses (IIRs) or Finite Impulse Responses (FIRs), but despite tearing my hair out playing with library implementations of these, I wasn’t able to make any usable sense of them whatsoever, and no amount of Googling seemed to help.

There’s actually a very easy way, though, as I was eventually forced to discover for myself.

The code snippets in this post are to be fed one audio sample at a time, and are in C code which will work on an Arduino / Teensy. I’ve used floats in the code, but these can be replaced with large integers for dramatically improved efficiency (this is probably mandatory for such code to run in realtime on an Arduino or Teensy prior to Teensy 3.0).

Non-resonant LPF

This is the simplest filter. All it needs to do is roll off the treble on an incoming stream of audio. The function achieves this by giving its output a “reluctance” to jump around fast. High-pitched sounds, with their quick oscillation, will be attenuated or nearly obscured, while slow-changing bass sounds will be all but unaffected. For each audio sample passing through the function, the function implements this reluctance by calculating how “far” its output will need to “move” (compared to what it outputted last time) in order to match its input – but instead of moving this whole “distance”, it just moves a certain fraction of the way. What this fraction is determines the cutoff frequency.

float lastOutput;

float doLPF(float input) {
    float distanceToGo = input - lastOutput;
    return lastOutput += distanceToGo * 0.125; // Lower / higher number here will lower / raise the cutoff frequency

Resonant LPF

This is what a subtractive synthesizer uses to get that congested, present sound, which with added modulation can create those classic “zappy” effects. What we want is to have the input signal’s treble chopped off, but to also accentuate / resonate a bit of the frequencies that reside just a little lower. To do this, our function is given a concept of “momentum”. Rather than simply place a dampener on the speed with which the output may move, as in the non-resonant LPF above, we instead give the output a sense of “inertia” – it will be resistant to moving, but once it gets moving, it will keep moving until it eventually gets pulled back in the other direction: it will resonate. This resonance can be lessened, though, if we wish, by allowing the output to retain some of the tendency, from the previous example, to move part-way towards the input at each sample, regardless of momentum.

float lastOutput;
float momentum;

float doResonantLPF(float input) {
    float distanceToGo = input - lastOutput;
    momentum += distanceToGo * 0.125; // Lower / higher number here will lower / raise the cutoff frequency
    return lastOutput += momentum + distanceToGo * 0.125; // Higher number here (max 1) lessens resonance

I was amazed when this simple code actually sounded like a classic synth-style resonant LPF! Sure, it’s not going to quite give you the analog warmth of some vintage unit, but at three lines of code, it’s more than acceptable.

Resonant HPF

Coding an HPF is slightly less intuitive. Basically, you make an LPF, and then subtract the output of that from the input signal, leaving only the higher frequencies. The momentum, required to make the filter resonant, has to be dealt with a bit differently. Here, I make the momentum dissipate over time (think friction). The rest… to be honest I’ve forgotten exactly why the code below is as it is – I arrived at it after much simplification of what earlier resembled far more explainable concepts. It still works great though, give it a shot!

float lastInput;
float lastOutput;
float momentum;

float doResonantHPF(float input) {
    lastOutput += momentum - lastInput + input;
    lastInput = input;
    momentum = momentum * 0.125 - lastOutput * 0.125; // First number controls resonance; second controls cutoff frequency
    return lastOutput;

High / low-shelving EQ

AKA the classic bass and treble controls. This is easy – we chop the treble off with an LPF, chop the bass off with an HPF (which again is a “backwards” LPF), and then mix back in however much of these we wish.

float withoutTreble;
float bassOnly;

float doShelvingEQ(float input) {

    // Treble calculations
    float distanceToGo = input - withoutTreble;
    withoutTreble += distanceToGo * 0.125; // Number controls treble frequency
    float trebleOnly = input - withoutTreble;

    // Bass calculations
    distanceToGo = withoutTreble - bassOnly;
    bassOnly += distanceToGo * 0.125; // Number controls bass frequency

    return withoutTreble + trebleOnly * 1 + bassOnly * 0;
    // The "1" controls treble. 0 = none; 1 = untouched; 2 = +6db
    // The "0" controls bass. -1 = none; 0 = untouched; 1 = +6db

Happy synthesizing!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s