How to Create Simple Band-Pass and Band-Reject Filters

Summary: This article shows how to create a simple band-pass filter that passes frequencies between the cutoff frequencies \(f_L\) and \(f_H\), and rejects frequencies outside of that interval. It also shows how to create a band-reject filter for those cutoff frequencies. The article is complemented by a Filter Design tool that allows you to create your own custom versions of the example filters that are shown below, and download the resulting filter coefficients.

Band-pass and band-reject filters can be created by combining low-pass and high-pass filters. To create these in the first place, have a look at How to Create a Simple Low-Pass Filter and How to Create a Simple High-Pass Filter.

To create band-pass and band-reject filters, you need two cutoff frequencies, a lower limit \(f_L\) and a higher limit \(f_H\). The combined filters inherit the transition bandwidth (or roll-off), which might be different at each end, from the low-pass and high-pass filters that were used to build it.

The windowed-sinc filters that are described in this article are both examples of Finite Impulse Response (FIR) filters.

Band-Pass Filter

A band-pass filter passes frequencies between the lower limit \(f_L\) and the higher limit \(f_H\), and rejects other frequencies. If you don’t create a specific filter for this, you can get this result in two steps. In the first step, you apply a low-pass filter with cutoff frequency \(f_H\),

\[x_\mathrm{lpf,H}[n]=x[n]*h_\mathrm{lpf,H}[n],\]

where \(x[n]\) is the original signal, \(h_\mathrm{lpf,H}[n]\) is the low-pass filter with cutoff frequency \(f_H\), and \(x_\mathrm{lpf,H}[n]\) is the low-pass-filtered signal. The asterisk represents convolution. The result is a signal in which the rejection of frequencies larger than \(f_H\) has been taken care of. You can then filter that signal again, with a high-pass filter with cutoff frequency \(f_L\),

\[x_\mathrm{bp,LH}[n]=x_\mathrm{lpf,H}[n]*h_\mathrm{hpf,L}[n],\]

where \(h_\mathrm{hpf,L}[n]\) is the high-pass filter with cutoff frequency \(f_L\), and \(x_\mathrm{bp,LH}[n]\) is the required band-pass-filtered signal.

However, you can do better and combine both of these filters into a single one. How does that work? You can write

\[x_\mathrm{bp,LH}[n]=(x[n]*h_\mathrm{lpf,H}[n])*h_\mathrm{hpf,L}[n]=x[n]*(h_\mathrm{lpf,H}[n]*h_\mathrm{hpf,L}[n]),\]

where the last step follows from the associative property of convolution. This means that the required band-pass filter is

\[h_\mathrm{bp,LH}[n]=h_\mathrm{lpf,H}[n]*h_\mathrm{hpf,L}[n].\]

Hence, a band-pass filter can be created from a low-pass and a high-pass filter with appropriate cutoff frequencies by convolving the two filters. The example band-pass filter of Figure 1 has \(f_L=0.1\) and \(f_H=0.4\), with \(b=0.08\) as in the articles on low-pass and high-pass filters.

Figure 1. Band-pass filter frequency response on a linear (left) and logarithmic (right) scale.Figure 1. Band-pass filter frequency response on a linear (left) and logarithmic (right) scale.

Band-Reject Filter

A band-reject filter rejects frequencies between the lower limit \(f_L\) and the higher limit \(f_H\), and passes other frequencies. As for the band-pass filter, you can get this result in two steps. In the first step, you apply a low-pass filter with cutoff frequency \(f_L\),

\[x_\mathrm{lpf,L}[n]=x[n]*h_\mathrm{lpf,L}[n],\]

where \(x[n]\) is the original signal, \(h_\mathrm{lpf,L}[n]\) is the low-pass filter with cutoff frequency \(f_L\), and \(x_\mathrm{lpf,L}[n]\) is the low-pass-filtered signal.

The result is a signal in which the frequencies in the rejection interval have been eliminated, but in which the frequencies higher than \(f_H\) are also gone. This can be corrected by filtering the original signal again, with a high-pass filter with cutoff frequency \(f_H\), and adding the result to the first signal,

\[x_\mathrm{br,LH}[n]=x_\mathrm{lpf,L}+x[n]*h_\mathrm{hpf,H}[n],\]

where \(h_\mathrm{hpf,H}[n]\) is the high-pass filter with cutoff frequency \(f_H\), and \(x_\mathrm{br,LH}[n]\) is the required band-reject-filtered signal.

You can again to better and combine both operations into a single filter. You can write

\[x_\mathrm{br,LH}[n]=x[n]*h_\mathrm{lpf,L}[n]+x[n]*h_\mathrm{hpf,H}[n]=x[n]*(h_\mathrm{lpf,L}[n]+h_\mathrm{hpf,H}[n],\]

where the last step follows from the distributive property of convolution. This means that the required band-reject filter is

\[h_\mathrm{br,LH}[n]=h_\mathrm{lpf,L}[n]+h_\mathrm{hpf,H}[n].\]

Hence, a band-reject filter can be created from a low-pass and a high-pass filter with appropriate cutoff frequencies by adding the two filters. The example band-reject filter of Figure 2 has \(f_L=0.1\) and \(f_H=0.4\), with again \(b=0.08\).

Figure 2. Band-reject filter frequency response on a linear (left) and logarithmic (right) scale.Figure 2. Band-reject filter frequency response on a linear (left) and logarithmic (right) scale.

Python Code

In Python, all these formulas can be implemented concisely. The first code fragment shows how to implement a band-pass filter.

import numpy as np
 
fL = 0.1  # Cutoff frequency as a fraction of the sampling rate (in (0, 0.5)).
fH = 0.4  # Cutoff frequency as a fraction of the sampling rate (in (0, 0.5)).
b = 0.08  # Transition band, as a fraction of the sampling rate (in (0, 0.5)).
N = int(np.ceil((4 / b)))
if not N % 2: N += 1  # Make sure that N is odd.
n = np.arange(N)
 
# Compute a low-pass filter with cutoff frequency fH.
hlpf = np.sinc(2 * fH * (n - (N - 1) / 2.))
hlpf *= np.blackman(N)
hlpf = hlpf / np.sum(hlpf)
 
# Compute a high-pass filter with cutoff frequency fL.
hhpf = np.sinc(2 * fL * (n - (N - 1) / 2.))
hhpf *= np.blackman(N)
hhpf = hhpf / np.sum(hhpf)
hhpf = -hhpf
hhpf[(N - 1) / 2] += 1
 
# Convolve both filters.
h = np.convolve(hlpf, hhpf)

The second code fragment shows how to implement a band-reject filter.

import numpy as np
 
fL = 0.1  # Cutoff frequency as a fraction of the sampling rate (in (0, 0.5)).
fH = 0.4  # Cutoff frequency as a fraction of the sampling rate (in (0, 0.5)).
b = 0.08  # Transition band, as a fraction of the sampling rate (in (0, 0.5)).
N = int(np.ceil((4 / b)))
if not N % 2: N += 1  # Make sure that N is odd.
n = np.arange(N)
 
# Compute a low-pass filter with cutoff frequency fL.
hlpf = np.sinc(2 * fL * (n - (N - 1) / 2.))
hlpf *= np.blackman(N)
hlpf /= np.sum(hlpf)
 
# Compute a high-pass filter with cutoff frequency fH.
hhpf = np.sinc(2 * fH * (n - (N - 1) / 2.))
hhpf *= np.blackman(N)
hhpf /= np.sum(hhpf)
hhpf = -hhpf
hhpf[(N - 1) / 2] += 1
 
# Add both filters.
h = hlpf + hhpf

Applying the filter \(h\) to a signal \(s\) is done by convolution, as for the low-pass and high-pass filters, and can again be as simple as writing the single line:

s = np.convolve(s, h)

Filter Design Tool

This article is complemented with a Filter Design tool. Experiment with different values for \(f_L\) and \(f_H\), visualize the resulting filters, and download the filter coefficients. Try it now!

Filter designer.Filter designer.
Submitted by Tom Roelandts on 10 May 2014

Add new comment