DSP 101

This article shows how to implement first-order lowpass and highpass filters.

Lowpass

Figure 1: Block diagram of a first-order digital lowpass filter

The following code implements the filter shown in Figure 1:

state = (1.0-c)*state + c*input
output = state;

The ‘state’ variable must be initialised once with 0.0 at the beginning of the program and saved for re-use after each execution. The code must be executed for each new input sample and it thereby produces a new output sample.

A slightly more efficient way of caculating the filter is as follows:

state += c*(input-state);
output = state;

The filter coefficient ‘c’ determines the cutoff frequency of the filter. Its valid range is between 0.0 and 1.0. The closer ‘c’ is to 0.0, the lower the cutoff frequency.

If the cutoff frequency is very low with respect to the sample rate, i.e. ‘c’ is less than 0.2, the cutoff frequency is well approximated by: $$ f_{c} \approx \dfrac{f_{s} \cdot c}{2\pi}$$ where $f_{s}$ is the sample rate.

This also means there is a simple approximation to get ‘c’, given a specific cutoff frequency $f_c$: $$ c \approx \dfrac{2\pi \cdot f_{c}}{f_{s}}$$

Highpass

Figure 2: Block diagram of a first-order digital highpass filter

The code for the highpass filter shown above is:

tmp = (2.0-c)/2.0*input + (1.0-c)*state;
output = tmp - state;
state = tmp;

It is more computationally efficient to precalculate $(2-c)/2$ and $1-c$ and store then as coefficients ‘c2’ and ‘c1’. Then, the code becomes:

tmp = c2*input + c1*state;
output = tmp - state;
state = tmp;

The cutoff frequency is the same as the lowpass filter: $$ c \approx \dfrac{2\pi \cdot f_{c}}{f_{s}} $$ and $$ c_1 = 1-c$$ $$ c_2 = \dfrac{2-c}{2}$$

Generic structure

The lowpass and highpass filter structures are very similar. A generic structure that can implement both of them is shown in the figure below:

Figure 3: Generic first-order filter structure

The code for the filter shown above is:

tmp = c0*input + a1*state;
output = b0*tmp + b1*state;
state = tmp;

For the lowpass filter: $$ c_0 = c $$ $$ a_1 = 1-c $$ $$ b_0 = 1 $$ $$ b_1 = 0 $$

For the highpass filter: $$ c_0 = (2-c)/2 $$ $$ a_1 = 1-c $$ $$ b_0 = 1 $$ $$ b_1 = -1 $$

Measuring average amplitude

Some algorithms, such as automatic gain control, require knowledge of the (average) signal amplitude. A simple way to determine amplitude is to rectify it and then use a lowpass filter, just like an analog circuit. The lowpass filter must have a very low cutoff frequency to remove everything that is not DC. We can’t, however, set the cutoff to zero as we’ll have to wait a literal eternity before the output stabilizes; a cutoff of a few Hz is advisable.

Full-wave rectifying a signal is very simple. It’s the fabsf() function in C or f32.abs() in rust. The rectified signal is then fed into one or more lowpass filters. The resulting signal is the average amplitude.