# Filter Design and Analysis

## Contents

# 10.3. Filter Design and Analysis#

In the previous section, we saw first how the frequency domain view of convolutional filters lets us reason about their effects. We then saw that the ideal low-pass filter produced some less-than-ideal results when applied to real signals, notably ringing artifacts which arise from using finite-length approximations to the ideal filter.

In this section, we’ll develop this idea further, and see how to construct better convolutional filters.

## 10.3.1. Terminology#

Before we go further into filter design, it will help to establish some terminology.

For a low-pass filter with cutoff \(f_c\), the **pass band** is the set of frequencies \(0 \leq f \leq f_c\) that pass through the filter.

The set of frequencies which are blocked by the filter is referred to as the **stop band**.

In general, there is a region between the pass and stop bands where frequencies are attenuated, but still audible, which is known as the **transition band**.

These regions are illustrated below for an example filter in Fig. 10.5.

Within the pass-band, a filter may not have perfectly flat response.
The amount of variation within the pass-band (max-min) is known as **pass-band ripple**.
An ideal filter would have 0 pass-band ripple, which amounts to no change in amplitude for passing frequencies.

Similarly, the difference from the peak response of the pass-band to the peak of the stop-band is the **stop-band attenuation**.
This measures how audible the presumably stopped frequencies will be in the output.
An ideal filter would have infinite attenuation, but the filter in Fig. 10.5 has only about 16dB, which is well within the range of human hearing. (This is probably not a good filter.)

### 10.3.1.1. What makes a good filter?#

The diagram above gives us a way to compare different filters. In general, the desirable properties of a filter are:

High stop-band attenuation.

*The filter stops frequencies when it should.*Small pass-band ripple.

*Passing frequencies aren’t too distorted.*Narrow transition band.

*The filter efficiently moves from pass to stop behavior.*

These three properties often present a trade-off, and can depend on the order of the filter in complicated ways. Designing filters isn’t easy, but this section will give you some pointers to standard methods.

## 10.3.2. The window method#

One of the most common approaches to making low-pass filters is known as the *window method*, and it works by applying a window to the impulse response of the ideal low-pass filter.
This is similar to the approach taken earlier to combat spectral leakage, though for a slightly different reason here.
Rather than force the filter to be periodic, windowing here simply forces the filter to depend only on a finite number of samples, and reduces ringing artifacts.

The window method proceeds by first determining the order \(K\) of the filter, which is typically an integer \(p\) multiple of the cutoff frequency \(f_c\) (measured in samples):

Next, an ideal LPF is constructed for length \(K\) and its impulse response \(\blue{h}\) is computed by the inverse DFT. Finally, a window \(\red{w}\) (e.g., Hann) is constructed for length \(K\) and multiplied by \(\blue{h}\) to produce the windowed filter:

This process is visually demonstrated by Fig. 10.6.

The `scipy`

package helpfully implements all of this (and more) for us in one place: `scipy.signal.firwin`

(finite impulse response + window).
For example, we could construct a Hann-windowed low-pass filter at \(f_c=500\) by the following code:

```
f_cutoff = 500
# Two cycles of the cutoff frequency (in samples)
order = 2 * fs // f_cutoff
# Build the filter
hw = scipy.signal.firwin(order, f_cutoff, window='hann', fs=fs)
# Apply the filter to a signal
y = scipy.signal.convolve(x, hw)
```

The examples below demonstrate this process for Hann-windowed filters with \(p=1,2,\) and \(4\) cycles of the cutoff frequency, and the filters are visualized in Fig. 10.7.

```
# Make the filter long enough to catch two cycles of our cutoff frequency
order1 = 2 * fs // f_cutoff
hw1 = scipy.signal.firwin(order1, f_cutoff, window='hann', fs=fs)
y_hw1 = scipy.signal.convolve(x, hw1, mode='same')
# Or four cycles
order2 = 4 * fs // f_cutoff
hw2 = scipy.signal.firwin(order2, f_cutoff, window='hann', fs=fs)
y_hw2 = scipy.signal.convolve(x, hw2, mode='same')
# Or 8 cycles
order3 = 8 * fs // f_cutoff
hw3 = scipy.signal.firwin(order3, f_cutoff, window='hann', fs=fs)
y_hw3 = scipy.signal.convolve(x, hw3, mode='same')
# Or 16 cycles
order4 = 16 * fs // f_cutoff
hw4 = scipy.signal.firwin(order4, f_cutoff, window='hann', fs=fs)
y_hw4 = scipy.signal.convolve(x, hw4, mode='same')
display(f'Hann-windowed low-pass filter, order={order1}')
display(Audio(data=y_hw1, rate=fs))
display(f'Hann-windowed low-pass filter, order={order2}')
display(Audio(data=y_hw2, rate=fs))
display(f'Hann-windowed low-pass filter, order={order3}')
display(Audio(data=y_hw3, rate=fs))
display(f'Hann-windowed low-pass filter, order={order4}')
display(Audio(data=y_hw4, rate=fs))
```

```
'Hann-windowed low-pass filter, order=88'
```

```
'Hann-windowed low-pass filter, order=176'
```

```
'Hann-windowed low-pass filter, order=352'
```