4.1 Design approaches
As before, we will first understand the problem and design (various) solutions in Python. We will then implement a real-time version in Python so that implementing it on the microcontroller will be primarily that of porting Python to C. Let's get started!
In the following analysis, we assume a sampling frequency of , as this is the sampling rate of our test recording. This parameter can be changed in the referenced scripts.
Analysis of our current (simple) filter
In the previous chapter, we implemented a very simple high pass filter by subtraction the previous sample from the current one, namely:
From this difference equation, we could perform an analysis of its frequency response by taking the Z-transform:
from which we obtain the transfer function:
For certain system, we can also compute the Fourier Transform, which may be more intuitive to understand as we can "read-off" the attenuation at various frequencies. For our simple HPF, the frequency response is shown below.
Figure: Frequency response of our simple HPF (log scale).
As noted in the last chapter, this filter does not have a sharp cutoff, which means frequencies of interest within the voice spectrum will be attenuated.
We can observe this with a simple example: we will take a zero-mean audio signal and add an artificial offset to it; we will then apply our simple HPF and compare the filtered signal with the zero-mean one.
Figure: Original zero-mean signal (blue) and signal with artificial offset (orange).
Figure: Signal with artificial offset (blue) and HPF'ed signal (orange).
Figure: Original zero-mean signal (blue) and HPF'ed signal (orange).
If you would be just looking at the resulting plots, you would think the high pass filtered (HPF'ed) signal is completely different from the original! However, if you listen to the output, you will hear the same sentence but with the lower frequencies severely attenuated.
Nonetheless, looking at this plot and hearing the output, we can conclude that this simple filter will simply not cut it.
FIR filters
Our simple filter from before is a special case of a causal discrete-time Finite Impulse Response (FIR) filter. Causal means that only current and previous samples are used, while FIR implies only current and past input samples are used. For such a filter of order , we can write the output as:
where are often called the filter coefficients. A common way to visualize such filters is with a block diagram as seen below.
For our simple HPF, we had with .
Figure: Comparing FIR filters of different order with our simple HPF filter from before.
Although our simple HPF from before attenuates DC more than most of the FIR filters considered above, the other filters would preserve important signal in the voice spectrum , i.e. (unit gain / passband) above . However, using taps barely attenuates the DC component while taps demands a large amount of memory.
Let's take a look at the resulting output when we pass the artificial DC-biased signal from before.
Figure: Signal with artificial offset filtered by -tap FIR filter.
Figure: Signal with artificial offset filtered by -tap FIR filter.
As expected from the frequency response curves we saw earlier, the -tap filter is unable to attenuate the DC offset, while the -tap filter is able to do so (but at the cost of more memory and computation). Nonetheless, both filters preserve the original signal much better than what we saw with the simple -tap HPF we were using earlier. The resulting filtered signals should sound nearly identical to the original (zero-mean) file. The -tap output, on the other hand, will begin with a small click due to the DC offset.
Moving average filter
Recalling our original motivation of the HPF, we discussed a simple solution for removing the DC offset by subtracting the average/mean of the signal. This can be done with a single line of Python when we have a recording, but is not possible when we are streaming an input, as we do not have access to future values!
The average at time index can also be computed using the previous average at index in order to reduce the number of computations, particularly if is large.
The above form falls under the category of infinite impulse response (IIR) filters, which will be discussed shortly, as the output depends on past output value(s).
With this estimate of the average, we can remove it from the current sample towards the goal of removing the DC offset:
We can observe a tradeoff between a large number of coefficients and a desirable (low) cutoff frequency versus a small number of coefficients with a better attenuation at DC but an undesirable (high) cutoff frequency. Nonetheless, we can obtain desirable performance without using as many coefficients as we saw earlier with the window method.
IIR filters
So are we stuck with using FIR filters with lots of coefficients? Fortunately not!
With recursion, we can compute filters that theoretically have an infinite impulse response (IIR). The filters we saw earlier have a finite impulse response since they rely on a finite number of past input samples. With IIR filters, we also use past output values, and this recursion is what creates an infinite impulse response. The difference equation of an IIR filter is typically written as such:
Notice that starts with ; in literature you might see an coefficient but this will be typically set to . In total, we have coefficients, meaning this many multiplies for the output.
Looking at the "Time Domain" row, as we are interested in DC removal, we can observe two approaches of interest:
Moving average (which as we saw earlier could be implemented more efficiently as an IIR filter).
Single pole IIR filter.
We will now look into this second approach, as we will be implementing this filter for DC removal.
where specifies the location of the pole. By taking the Z-transform:
we can obtain the following transfer function:
Although produces a filter with a more desirable cutoff frequency, the roll-off is still the same, as we can observe roughly parallel lines with a decay of roughly . We can produce a sharper filter by cascading multiple single pole filters. A cascade of stages corresponds to raising the frequency response to the power of , namely:
As increases, more coefficients (and samples) will be needed to compute the output, which means more computations and memory needed to store the coefficients and past samples. An -stage filter would require coefficients, past input samples, and past output samples.
For example, for :
from which we can read off the coefficients and .
For , we could use the filter from to compute the coefficients:
yielding the coefficients and .
For a general , the transfer function (and thus coefficients) can be computed in this recursive fashion:
We can observe this sharper decay for higher values of . Quantitavely, it is around for stages. Moreover, comparing to our FIR analysis from before, we need much less coefficients in order to obtain a filter with a desirable performance!
Last updated
Was this helpful?