Problem 533288 · medium · Level 05 Advanced Algorithms & Graphs

A Band-Pass for the Birdsong Recorder

band-pass filter · FIR filter design · windowed sinc · frequency response · decibels

A wildlife recorder in a wood samples its microphone fs times per second. The song of the bird being studied lies between f_low and f_high hertz; below it is the rumble of wind and distant traffic, above it insects and hiss. The recorder needs a band-pass filter.

A neat way to make one from low-pass filters: a low-pass with cutoff f_high keeps everything below f_high; a low-pass with cutoff f_low keeps everything below f_low. Subtracting the second set of weights from the first (h[k] = high[k] - low[k], with the same number of weights) keeps what is in the first but not in the second: the band from f_low to f_high.

Each low-pass is the windowed-sinc design with the Hamming window. With M = taps - 1, t = k - M / 2 for k = 0 .. taps - 1, and cutoff fc:

ideal[k] = 2 * fc / fs                                if t == 0
ideal[k] = sin(2 * pi * fc * t / fs) / (pi * t)       otherwise
w[k]     = 0.54 - 0.46 * cos(2 * pi * k / M)
lowpass[k] = ideal[k] * w[k] / (sum over j of ideal[j] * w[j])        (the weights add up to 1)

To check the design, compute its gain at each frequency f in freqs: with w0 = 2 * pi * f / fs the response is H = sum over k of h[k] * exp(-1j * w0 * k), and the gain in decibels is 20 * log10(|H|), or -120.0 when |H| < 1e-6 (the gain at 0 Hz is exactly 0, since both low-passes have weights adding up to 1).

Write bandpass_gains(taps, f_low, f_high, fs, freqs) that returns the list of gains in decibels, one per frequency in freqs. Press Run with plot(grid, bandpass_gains(101, 2000, 6000, 22050, grid)) for a list grid of frequencies to see the band.

Examples

Input:  taps = 3, f_low = 1000, f_high = 3000, fs = 8000, freqs = [0, 2000, 4000]
Output: [-120.0, -21.927793329609177, -15.907193416329552]
Explanation: M = 2 and the Hamming window is 0.08, 1, 0.08. The low-passes are
[0.0630, 0.8741, 0.0630] (1000 Hz) and [0.0229, 0.9542, 0.0229] (3000 Hz); their difference
[-0.0400, 0.0801, -0.0400] adds up to 0, but three weights are far too few: even the middle
of the band is 22 dB down.

Input:  taps = 51, f_low = 1000, f_high = 3000, fs = 8000, freqs = [0, 200, 2000, 3800]
Output: [-120.0, -50.00842590788459, -0.02421622122750006, -57.27635662202601]
Explanation: with 51 weights the middle of the band passes almost unchanged (-0.02 dB),
while 200 Hz and 3800 Hz are cut by 50 dB and more.

Constraints

  • 3 <= taps <= 201, taps odd; 0 < f_low < f_high < fs / 2; 1 <= len(freqs) <= 50, each 0 <= f <= fs / 2
  • answers are compared with a tolerance of 1e-6

Goals

  • Build a band-pass FIR filter as the difference of two low-pass filters
  • Explain why the difference passes the band between the two cutoffs
  • Check the design by computing its gain in decibels at chosen frequencies
Starting Python…