One Pole Low Pass

Overview

Basic one pole low-pass filter serves as an important building block due to its utility in a vast number of applications including noise reduction, signal smoothing, and extracting relevant information from a signal. The 6dB/octave slope of the single pole filter is suitable for many non-critical use-cases and its simplicity makes it computationally efficient.

q/fx/lowpass.hpp also holds fixed_pt_leaky_integrator, which does the work in integers, for targets where floating point is slow or absent. See Fixed Point Leaky Integrator.

Include

#include <q/fx/lowpass.hpp>

Declaration

struct one_pole_lowpass
{
                     one_pole_lowpass(float a);
                     one_pole_lowpass(frequency f, float sps);

   float             operator()(float s);
   float             operator()() const;
   one_pole_lowpass& operator=(float y);
   void              cutoff(frequency f, float sps);

   float             y = 0.0f, a;
};

Expressions

Notation

lp, a, b

Objects of type one_pole_lowpass.

f

Object of type frequency representing the cutoff frequency.

sps

Floating point value representing samples per second.

s

Input sample.

c

Floating point coefficient, 0 to 1.

Constructors and Assignment

Expression Semantics

one_pole_lowpass(c)

Construct a one_pole_lowpass with coefficient c. Each sample moves the output c of the way toward the input: 1 passes the input through, values near 0 smooth heavily.

one_pole_lowpass(f, sps)

Construct a one_pole_lowpass with specified cutoff frequency, f, and samples per second, sps. The coefficient is 1 - exp(-2π f / sps).

one_pole_lowpass(b)

Copy construct a one_pole_lowpass from b.

a = b

Assign b to a.

a = s

Set the latest result to s.

C++ brace initialization may also be used.

Function Call

Expression Semantics Return Type

lp()

Return the latest result.

float

lp(s)

Process the input sample, s: y += a * (s - y).

float

Mutators

Expression Semantics Return Type

lp.cutoff(f, sps)

Set the cutoff frequency given frequency f, and samples per second sps.

void

Accessors

Expression Semantics Return Type

lp.y

The latest result.

float

lp.a

The coefficient.

float

Example

Taming zipper noise on a gain control. A control that arrives in steps, say once per audio block, is heard as a buzz at the block rate. A low-pass at a few tens of hertz turns each step into a short glide:

q::one_pole_lowpass  smooth{30_Hz, sps};

// ... per sample:
float out = s * smooth(gain);

Fixed Point Leaky Integrator

fixed_pt_leaky_integrator<k, T> computes y += s - y / k in the integer type T, int by default. Its pole is 1 - 1 / k, so k sets the cutoff, roughly sps / (2π k) for large k. At 48 kHz, k = 16 gives the pole of a 493 Hz cutoff. Make k a power of two and the division compiles to a shift.

The filter’s gain is k: a steady input s settles at k * s (within rounding). Divide the result by k, published as gain, to read it at the input’s scale. For the same reason, a state assigned to y must be multiplied by k first.

A noisy 12-bit reading smoothed by fixed_pt_leaky_integrator<16>
Figure 1. A knob read through a 12-bit converter with plus or minus 60 counts of noise, smoothed by fixed_pt_leaky_integrator<16> in integer arithmetic. The output starts from 0 and settles within about 50 readings.

Declaration

template <int k, typename T = int>
struct fixed_pt_leaky_integrator
{
   using self_type = fixed_pt_leaky_integrator;

   typedef T               result_type;
   static constexpr int    gain = k;

   T                       operator()(T s);
   T                       operator()() const;
   self_type&              operator=(float y);

   T                       y = 0;
};

Expressions

Notation

F

A fixed_pt_leaky_integrator<k, T> type.

fi

Object of type F.

s

Input sample of type T.

v

A state, already scaled by k.

Function Call and Assignment

Expression Semantics Return Type

fi(s)

Process s and return the result, scaled by k.

T

fi()

Return the latest result, scaled by k.

T

fi = v

Set the state to v.

F&

F::gain

The gain, k.

int

Example

Smoothing a 12-bit ADC reading on a microcontroller without floating point:

q::fixed_pt_leaky_integrator<16>  pot;

// ... per reading:
int level = pot(adc_read()) / pot.gain;   // 0 to 4095