A one-pole filter

Here’s a very simple workhorse of DSP applications—the one-pole filter. By comparison, biquads implement two zeros and two poles. You can see that our one-pole simply discards the zeros (the feed-forward delay paths) and the second pole (feedback path):

We keep the input gain control, a0, in order to compensate for the gain of the feedback path due to b1, which sets the corner frequency of the filter. Specifically, b1 = -e-2πFc and a0 = 1 – |b1|. (In the implementation, we’ll roll the minus sign in the summation into b1 and make it positive, for convenience.)

Filter types

A pole is a single-frequency point of pushing gain up, and a zero is a single-frequency point of pulling the gain down; with a single pole, you are not going to get complex response curves such as bandpass, peak, and shelving filters that you can get with the two poles and zeros of a biquad. That leave us with lowpass and highpass, which have a single point of change.

However, a one-pole makes a poor highpass filter for cases in which we might be likely to use it—in particular, a DC blocker. That’s because it makes a highpass by pushing response up at high frequencies—we really need a zero to pull the response down at very low frequencies. So, we’ll only implement coefficient calculation for lowpass response here. However, we can still make a highpass filter suitable for a DC blocker by subtracting the output of a one-pole lowpass filter, set to a low frequency, from the direct signal.

Typical uses

What are typical uses of this one-pole lowpass filter? Sometimes, we do need the gentle slope of a first order frequency roll-off. And often, we use this simple filter on parameters instead of one the audio directly—some gentle smoothing of a calculated signal in an automatic gain control, for instance.

The 6 dB per octave slope of the one-pole filter—a halving of amplitude for every doubling of frequency—is gentle and natural. It’s extremely cheap, computationally, so it’s the perfect choice when you need just a bit of smoothing. And it doesn’t “ring” (overshoot), so it’s an excellent choice for filtering knob changes. Run each of your user interface sliders and knobs through a one-pole lowpass set to a low (sub-audio) frequency, and your glitchy, zippered control changes turn into smooth parameter changes.

Note that if you feed the one-pole lowpass an impulse, it will yield a perfect exponential decay—the same decay that we use on typical ADSR envelope generators on synthesizers. To look at it another way, the feedback path is an iterative solution to calculating an exponential curve.

The source code

The one-pole filter is so simple that we’ll make it inline, entirely in the header file:

//
//  OnePole.h
//

#ifndef OnePole_h
#define OnePole_h

#include <math.h>

class OnePole {
public:
    OnePole() {a0 = 1.0; b1 = 0.0; z1 = 0.0;};
    OnePole(double Fc) {z1 = 0.0; setFc(Fc);};
    ~OnePole();
    void setFc(double Fc);
    float process(float in);
    
protected:    
    double a0, b1, z1;
};

inline void OnePole::setFc(double Fc) {
    b1 = exp(-2.0 * M_PI * Fc);
    a0 = 1.0 - b1;
}

inline float OnePole::process(float in) {
    return z1 = in * a0 + z1 * b1;
}

#endif

Examples

Here’s a DC blocker:

OnePole *dcBlockerLp = new OnePole(10.0 / sampleRate);
...
// for each sample:
sample -= dcBlockerLp->process(sample);

Need highpass?

It’s trivial to add one-pole highpass response, if you’d like—just negate the lowpass calculation; this inverts the spectrum, so you’ll need to flip the frequency range by subtracting Fc from 0.5:

b1 = -exp(-2.0 * M_PI * (0.5 - Fc));
a0 = 1.0 + b1;

Pole position

You can experiment with how the location of the pole affects frequency response by using the pole-zero placement app. Click the Pair checkbox near the Poles sliders to disable it. Move both Poles sliders to center to move the poles to the origin. (The zeros are already at the origin by default when you open the page. At the origin, poles and zeros have no effect.) Now, move just one pole slider; you’ll see highpass response when the pole is to the left of the origin, and lowpass to the right.

This entry was posted in DC Blocker, Digital Audio, IIR Filters, Source Code. Bookmark the permalink.

40 Responses to A one-pole filter

  1. Harold M says:

    Hi Nigel,

    This is great for me as I am only beginning to understand filter design. Thank you very much for the great resource!

    • Nigel Redmon says:

      Thank you for your comment, Harold—glad this is a help. I added an additional paragraph about using the pole-zero applet to experiment. While you’re moving the slider and watching the graph, keep in mind that as the pole nears the unit circle, it pushes the frequency response higher and higher. The input gain parameter, a0, compensates by pulling the overall amplitude down to keep the peak amplitude constant.

      Nigel

  2. shashank says:

    Hey Nigel,

    Although I am still trying to learn the intricacies of digital filter designing and I have experimented with this idea myself, but I still wonder about a few things regarding the implementation.

    In the blog above, we have a function OnePole::process(…) which processes just 1 sample at a time (opposed to a frame of samples).

    Won’t the function calling for every sample make the processing slow ?
    Why not rather pass address of an entire frame to OnePole::process(…), let it do the DSP on that piece of memory, wont this be faster ?

    Please clarify.

    Thanks
    Shashank

    • Nigel Redmon says:

      Hi Shashank,

      In the end, yes, you generate frames of output. However, you still start with “atomic” DSP objects. Consider that if you need a OnePole inside a reverb process. The reverb may generate its output a frame at a time, but it may require a OnePole in a feedback within the reverb (a common requirement). If you had designed OnePole to work on frames, it would be useless for the Reverb object. On the other had, it’s trivial to use OnePole to do an entire frame—either stick it in a for loop outright, or create a trivial OnePoleFrame object that uses OnePole (perhaps using template meta programming to effectively create unrolled loops for zero loop overhead).

      Note that OnePole::process is inline and computationally cheap—there is no function call overhead at all—no extra cost for it to be atomic.

      Nigel

      PS—Need to sleep now, but I’ll check out your blog—looks interesting.

  3. Alan says:

    Nice article – this filter is indeed the “workhorse” of DSP!

    If you don’t mind a nitpicking comment though: you’ve labelled your filter coefficients a and b as is usual, but unusually, the b coefficients are applied to the output signal and the a coefficients are applied to the input. Convention dictates it should be the other way around.

    Obviously this isn’t a problem in the context of this blog post, but as it’s a beginner-type article this switch-around may be confusing for those DSP novices who will generally see the opposite with other DSP resources.

    In any case, I look forward to continuing to follow your musings. Cheers!

    • Nigel Redmon says:

      Hi Alan,

      Actually, there is no convention (or there are two). A little more than half of the DSP books on my bookshelf favor the orientation I use, where a is in the numerator of the transfer function. I wish there were a convention—on top of the a/b confusion, some people roll the minus signs in the feedback into the coefficients, other don’t. However, since almost everyone normalizes, you need only look to see whether a0 or b0 is missing to determine the orientation. It’s not always so obvious whether the minus signs are included.

      Thanks for reading!

      Nigel

  4. Jerry Gregoire says:

    Nice description. Just curious- why did you switch the traditional nomenclature for the coeff, A and B. I have always seen A in the feedback, (denominator) and B(numerator) in the forward.

    • Nigel Redmon says:

      Hi Jerry—there is no standard, no tradition. It was natural for me to have A coefficients on top of the transfer function. Some might start with A on the left of the direct form I block diagram, which yields the opposite relationship. I once did a survey of the DSP books on my bookshelf, and A-on-top was used more often (Zölzer uses that convention, for example).

      • Saf says:

        I only wish more people used b for feedBack, and a for feedforward. 🙂

      • David says:

        I’m looking at Zölzer DAFX 2nd Ed right now and he uses b in the numerator, a in the denominator in Eq 2.3. Most of my books use b in the numerator. But your point stands – it doesn’t matter as long as it’s clear and consistent across this article / website.

        • Nigel Redmon says:

          Yes, I noticed that too—I suspect to be consistent with DAFX publications. His Digital Audio Signal Processing is the other way.

  5. David Billen says:

    You can eliminate a multiply by using z1 += (in – z1)*a0;

    • David Billen says:

      …I guess that’s * a0…

    • Nigel Redmon says:

      Good spot, and I like the elegance. However, it’s the same number of operations, and I think most processors these days have single-cycle multiply. It does reduce a fetch, which might not be zero cost in the pipeline in all cases. I recall considering using a single variable, but I think I went with two with the idea of also having a highpass version of setFrequency (which is of marginal utility), as noted in the article but not the code, and after finding no difference in execution time. Testing, the code execution time is consistently identical between the two versions with compiler optimizations enabled (Xeon); for some reason, the single-coefficient version is consistently slower by a trivial amount (2%) without optimizations—I can’t think of a reason for that, but it’s unimportant.

  6. Pankaj says:

    Very nice article. I am naïve at DSP and trying to learn. One question that comes to my mind is that in determining the coefficients based on cut-off frequency, there is no dependence on the sample rate. Won’t sample rate have impact on the digital filter performance?

    • Nigel Redmon says:

      I could have made this more clear, sorry: “Fc” is normalized frequency. That is, it’s frequency divided by sample rate. For instance, look at the example for a DC blocker—Fc is set to “10.0 / sampleRate”, or 10 Hz.

  7. Josh says:

    Interestingly, if this is used to try and dc block a signal from a fast waveform digitizer (500MS/s) with a baseline of the max channel values, 16k, there is a significant distortion at the beginning due to z being set as 0 to start. I was able to get reasonable behavior by starting z as the beginning value of the function for the first sample.

  8. Dmitry says:

    Hi, nice article thank you. Can you explain can i can get resonance?
    I know that i must to use feedback. I had fails make.

  9. Dario Sanfilippo says:

    Hello, Nigel. Thank you so much for your post.

    Would you be so kind to provide the exact reference for the formulae you’re using in your implementation?

    I’ve been testing different approaches for the coefficient calculation of 1-pole low-pass filters and it’s not so easy to find a good compromise. Your implementation is so far the best I’ve found.

    In the Audio Programming book (Boulanger and Lazzarini), they use trigonometric functions for the coefficient calculation. The resulting filter is quite consistent from about .004 Fc (normalised freq.) up, with an attenuation of ~-3dB at cutoff. Below that point, attenuation at cutoff starts decreasing and there is little or no resolution in the coefficients when we reach 4.5e-05 Fc (about 2Hz at 44.1kHz SR).

    Cliff Sparks also uses trigonometric functions and his filters behave very similarly to those from the AP book. http://www.arpchord.com/pdf/coeffs_first_order_filters_0p1.pdf

    Another implementation is the 1-pole LP filter in Pure Data. Miller Puckette uses the following approximation to calculate the coefficients: b1 = 1 – (Hz_cutoff * 2 * pi / SR). This filter behaves very similarly to yours in the bottom range, with a good resolution for the very low frequencies, although the coefficient would change sign after about .159157 so it is clipped to 0 from that point on. Besides, the ~-3dB attenuation expected starts dropping after about .0024 Fc (normalised freq.).

    In your implementation, the filter has a rather good response from the very bottom up until ~.07 Fc, which seems to be quite a great result for such a computationally efficient calculation of the coefficients. In fact, this is the one I will use for my time-variant filter.

    Thanks!

    Dario Sanfilippo

    • Nigel Redmon says:

      Hi Dario,

      At this point I’m going from memory rather than reference, but I can tell you where I first saw this filter, which answers your question better than looking for a reference that fits the bill, or deriving from scratch. In Musical Applications of Microprocessors, Hal Chamberlin derives the filter. He notes that a one-pole filter is a leaky integrator, which is equivalent to an analog R-C lowpass filter. He also notes that it’s well-known that capacitor voltage discharge follows an inverse exponential curve: E = E0exp(-T/RC), where E is the next voltage after period T, given the initial voltage E0, R and C are the resistor and capacitor values in ohms and farads. The cutoff frequency is defined by Fc = 0.5πRC; substituting we get E = E0exp(-2πFcT). The time change, T, is the sample rate in the digital system, so converting to sampling frequency, Fs = 1/T, we get E = E0exp(-2πFc/Fs). To adjust for a gain of 1 at DC (0 Hz), we set the input multiplier to 1 – that value.

      • Dario Sanfilippo says:

        Thank you so much for your answer, Nigel.

        I have to step back a little bit here. I have done the tests using the Pure Data real-time audio programming environment. Pure Data essentially works on two domains: one domain is that of signals, where calculations take place at samplerate; the other one is that of “messages”, where the calculations take place “on event”, i.e., clicking on a button or similar.

        What runs underneath PD is C, and in order to reduce the computational load the signal cosine function is based on a table. The calculation is thus not as accurate as calling cos() in C, and that’s the reason why I was getting weird coefficients for the implementations using trigonometric functions.

        I’ll check the coefficients back using more precise calculations and I’ll let you know what I found out.

        Thanks a lot.

        Dario

  10. Robert Thompson says:

    Hey Nigel,

    early-ish in the piece above you wrote “However, we can still make a highpass filter suitable for a DC blocker by subtracting the output of a one-pole lowpass filter, set to a low frequency, from the direct signal.”

    To me this implies that the phase must remain linear or, I expect, the result would be somewhat tragic – my expectation being that if the output of the one-pole lowpass filter is not in phase with the direct signal then it can hardly cancel well enough to use at all, let alone as a high pass filter – should I have tried this before asking? 😀

    • Nigel Redmon says:

      Good question! OK, we start with a one-pole lowpass filter. The passband will have unity gain, and zero radians phase until from 0 Hz, dropping as it starts into the rolloff, towards -π/2. Of course, you’ll get perfect cancellation at DC when you subtract it from the original signal. At frequencies higher than the cutoff, the phase won’t be convenient, but the lowpassed signal will be smaller and smaller (cutting in half in amplitude for each doubling of frequency). You can set the lowpass cutoff very low, so by the time you get to the audio range, there’s not enough lowpassed signal to matter.

      Try the response grapher. In the right side (b coefficients), put “1,-0.99999”, or however many 9s you want—the closer to -1, the lower the cutoff. In the left side, but “0.00001”, or 1 added to whatever the other number was. View in linear mode so you can see 0 Hz. And remember, it’s not just “100 dB down”, or whatever, at a given point, it’s that far below the input signal.

      But your sense that we don’t want to add and subtract filter outputs without regard to phase, normally, is correct (particularly with second order and up). But in this case, there is no perceptible penalty.

  11. Pete says:

    Hi Nigel,
    I came across this article while endeavouring to learn more about building synths in Native Instrument’s Reaktor. It’s well-written, clear, and just at the right level for someone with moderate physics, maths and coding skills like me – thankyou! The next challenge for me is properly understanding the z-transform, haven’t got my head around that yet. Thanks again…
    Pete

  12. Malcolm rest says:

    Hi,

    I tried it and got some weird result, I think I got confused with the ‘minus’ sign mentioned at the beginning of the (great) article.
    So, here is what I have done:

    1.
    Assume: Fc = 0.25 (normalized frequency, equivalant to ~11 KHz at 44100 sample rate)

    2.
    For 1st order LPF, use the mentioned:

    b1 = exp(-2.0 * M_PI * Fc);
    a0 = 1.0 – b1;

    To receive:
    b1 = 0.20787957635076193
    a0 = 0.7921204236492381

    3.
    Copy paste these two params to the “filter frequency response grapher”:
    http://www.earlevel.com/main/2016/12/08/filter-frequency-response-grapher/

    And one receives a weird result.

    4.
    Still on the “filter frequency response grapher”, change b1 to -b1
    One receives the correct graph

    5.
    To follow that , the LPF calculation might simply be:
    b1 = -exp(-2.0 * M_PI * Fc); // add minus
    a0 = 1.0 + b1; //change minus to plus

    Is this correct or have I misunderstood the first explanation:
    “…Specifically, b1 = -e-2πFc and a0 = 1 – |b1|. (In the implementation, we’ll roll the minus sign in the summation into b1 and make it positive, for convenience.)…”

    • Nigel Redmon says:

      Hi Malcolm. Yes, as I noted, I rolled the minus sign from the evaluation into the b1 coefficient. There are only plus signs in Process.

      • Leigh says:

        I am also getting a strange result from the highpass version (the lowpass version is working perfectly).

        For a 50Hz highpass, I’m getting:
        a = 0.956502
        b = -0.0434977

        When the response of this filter is measured (using FuzzMeasure), it is like a strange low shelf, where gain is 0dB at Nyquist (22k in this case) and then drops down to -0.86 dB by 1k, remaining flat from there down to 10Hz. (I’d be happy to email you a screenshot of that.)

        This is when using this bit of code (the highpass variation) for the coefficients:
        b1 = -exp(-2.0 * M_PI * (0.5 – Fc));
        a0 = 1.0 + b1;

        And keeping this for the process:
        return z1 = in * a0 + z1 * b1;

        Thanks for a great article. I’ll hope you might be able to shed a light on what’s going on with the highpass calculation.

        • Nigel Redmon says:

          Note my comment in the article, “a one-pole makes a poor highpass filter for cases in which we might be likely to use it”. Put a different way, they work well for lowpass filters that have a corner frequency closer to 0 Hz, and highpass filters that have a corner near half the sample rate. But we usually have different expectations for lowpass and highpass—if you set a one-pole lowpass to 16 kHz, you don’t expect much effect on the audio. But if you set a highpass to 50 Hz, you wonder why it isn’t having much effect on the audio.

          For a useful low frequency highpass, you really need a zero at DC to actively pull the frequency response down there (a one-pole/one-zero filter, or a biquad for second-order). As I noted with the DC blocker example, you can also subtract a lowpass filter from the input signal to get highpass. That’s not something you’d normally do with higher order filters, due to the phase response of the filter relative to the input (unless that’s the response you want). But it’s close enough for rock ‘n’ roll with a one-pole.

          • Leigh says:

            Indeed you did note in the article that “a one-pole makes a poor highpass filter”, but I think I misunderstood the context, or at least I thought that your subsequent manipulations of the lowpass code had found a clever workaround. Thanks for explaining.

            And I do now get a reasonable highpass by following the DC blocker example. I guess **that’s** the clever workaround!

  13. Sylvain Asselin says:

    Nigel,

    very interesting site!

    I would like to know if you have a “C” implementation for the DC blocker?
    Kindest Regards

    • Nigel Redmon says:

      No, but it’s pretty simple. You just need to decide how to how you want to save the “state”, the contents of the delay element. C++ is more convenient for that sort of thing.

  14. Josh says:

    Wonderful article. Thank you so much.

    I’m trying to use this filter in a waveguide and need to be able to calculate the phase delay of this filter at a specific frequency (of the incoming audio, not the filter’s corner frequency). Could you show how to calculate that? I’m not a higher math guy, unfortunately.

    Thanks again!

  15. Ian Benton says:

    I use the low-pass version of this all the time, but I’ve never tried the high-pass. However, when I put it in the pole-zero plotter, I don’t get a nice 6dB/octave rolloff below the cutoff frequency – it all seems to happen at the high-frequency end.

    • Nigel Redmon says:

      See where I say, “However, a one-pole makes a poor highpass filter for cases in which we might be likely to use it”. A one-pole lowpass has poor response when set to a high frequency (but we don’t care), while a one-pole highpass has poor response at low frequencies (where we are most likely to use it). That’s because they lack a zero at Nyquist (lowpass) and 0 (highpass). Just subtract the lowpass output from the original signal to use it at low frequencies.

  16. Flac Pack says:

    return z1 = in * a0 + last_input * b1;

    would that make a oneZero?

  17. john jenwty says:

    How to make one pole bandpass?
    Thanks

    • Nigel Redmon says:

      Good question. You can’t. You can experiment with this by using the Pole-Zero widget. Click the two Pair checkboxes to disable them. Set Pole 2 and Zero 2 to the value 0, where they have no effect. Now move Pole 1 and Zero 1 anywhere you want. For instance, if you put the pole anywhere in the middle, then move the zero to -1, you’ll have a lowpass filter; if you move the zero to 1, you’ll have a highpass filter. But you can’t pull both ends down at the same time to get a bandpass. But if you switch the two checkboxes on, giving you to have two poles and zeros that can be placed anywhere in the unit circle, you can make a bandpass, and also control the Q. You need at least a second order filter for this.

Leave a Reply to john jenwty Cancel reply

Your email address will not be published. Required fields are marked *