Digital Signal Processing
By the end of this lesson, you should be able to:
freqz() to obtain magnitude and phase responses from a given transfer function \(H(z)\).Filters answer one big question: > Which frequency components do we want to keep, and which do we want to suppress?
Four basic types:
Real‑world ECE analogy:
Key regions of a filter’s magnitude response:
Design parameters:
Tip
In design specifications, you often see something like:
Normalized Lowpass Filter

Typical ECE uses:
Key design trade‑offs:
Normalized Highpass Filter

Typical ECE uses:
Note: A preemphasis filter in speech (later) is essentially a highpass filter.
Normalized Bandpass Filter

Parameters:
Typical ECE uses:
Normalized Bandstop Filter

Special case:
Typical ECE uses:
Design challenge:
To analyze a digital filter, we often start from its transfer function:
\[ H(z) = \frac{B(z)}{A(z)} = \frac{b_0 + b_1 z^{-1} + \dots + b_M z^{-M}}{1 + a_1 z^{-1} + \dots + a_N z^{-N}}. \]
We want:
MATLAB tool: freqz()
B – vector of numerator coefficients \([b_0 \; b_1 \; \dots \; b_M]\).A – vector of denominator coefficients \([1 \; a_1 \; \dots \; a_N]\).N – number of frequency samples between \(0\) and \(\pi\).h – complex frequency response at each w.w – corresponding frequency samples in radians/sample.Given transfer functions:
\(H(z) = \dfrac{z}{z - 0.5}\)
\(H(z) = 1 - 0.5 z^{-1}\)
\(H(z) = \dfrac{0.5 z^2 - 0.32}{z^2 - 0.5 z + 0.25}\)
\(H(z) = \dfrac{1 - 0.9 z^{-1} + 0.81 z^{-2}}{1 - 0.6 z^{-1} + 0.36 z^{-2}}\)
Tasks:
freqz() to plot magnitude and phase.Before using freqz(), rewrite in standard delay form (negative powers of \(z\)):
From the original forms:
B = [1], A = [1 -0.5].\[ H(z) = 1 - 0.5 z^{-1} \]
B = [0.5 0 -0.32], A = [1 -0.5 0.25].\[ H(z) = \dfrac{1 - 0.9 z^{-1} + 0.81 z^{-2}}{1 - 0.6 z^{-1} + 0.36 z^{-2}} \]
Pole–zero plots are shown in Fig. 6.20:

The textbook Program 6.2 (cleaned up and corrected) looks like:
% Example 6.12
N = 1024;
% Case (a)
B = [1];
A = [1 -0.5];
[h, w] = freqz(B, A, N);
phi = 180*unwrap(angle(h))/pi;
figure(1);
subplot(2,1,1); plot(w, abs(h)); grid;
xlabel('Frequency (rad/sample)'); ylabel('Magnitude');
title('Case (a)');
subplot(2,1,2); plot(w, phi); grid;
xlabel('Frequency (rad/sample)'); ylabel('Phase (degrees)');
% Case (b)
B = [1 -0.5];
A = [1];
[h, w] = freqz(B, A, N);
% ... (similar plotting)Magnitude + phase plots for each case are shown in Figs. 6.21(a–d).




From the magnitude responses (Figs. 6.21(a–d)):
Important
Being able to look at a magnitude response and classify the filter type is core. Practice mentally: - Does it pass low, high, a band, or “everything except a band”?
Given a transfer function
\[ H(z) = \frac{B(z)}{A(z)} = \frac{b_0 + b_1 z^{-1} + \dots + b_M z^{-M}}{1 + a_1 z^{-1} + \dots + a_N z^{-N}}, \]
we can implement it in several equivalent ways:
All realize the same \(H(z)\) (ideally), but differ in:
Start from difference equation (time domain):
\[ \begin{aligned} y(n) &= b_0 x(n) + b_1 x(n-1) + \dots + b_M x(n-M) \\ &\quad - a_1 y(n-1) - a_2 y(n-2) - \dots - a_N y(n-N). \end{aligned} \]
Interpretation:
Direct‑Form I structure implements these two parts in parallel, then sums them.

Second‑order example (M = N = 2):


Starting from
\[ Y(z) = H(z) X(z) = \frac{B(z)}{A(z)} X(z), \]
with \(M = N\), rearrange:
\[ Y(z) = B(z) \left( \frac{X(z)}{A(z)} \right). \]
Define an intermediate signal:
\[ W(z) = \frac{X(z)}{1 + a_1 z^{-1} + \dots + a_M z^{-M}}. \]
Then
\[ Y(z) = (b_0 + b_1 z^{-1} + \dots + b_M z^{-M}) W(z). \]
Corresponding difference equations:
Internal state update: \[ w(n) = x(n) - a_1 w(n-1) - \dots - a_M w(n-M) \]
Output equation: \[ y(n) = b_0 w(n) + b_1 w(n-1) + \dots + b_M w(n-M) \]
Structure:

Second‑order example:

Note
Direct‑Form II uses fewer delay elements than Direct‑Form I (roughly half when \(M = N\)), which can save memory and hardware.
Factor \(H(z)\) into first‑ and second‑order sections:
\[ H(z) = H_1(z) H_2(z) \cdots H_K(z). \]
Each section \(H_k(z)\) is usually:
Block diagram:

Tip
In practice, high‑order IIR filters are almost always implemented as a cascade of biquads (second‑order sections), each realized by Direct‑Form II (or other numerically robust forms like lattices).
Express \(H(z)\) as a sum of sections:
\[ H(z) = H_1(z) + H_2(z) + \dots + H_K(z). \]
Typically using partial fraction expansion:
First order: \[ H_k(z) = \frac{b_{k0}}{1 + a_{k1} z^{-1}} \]
Or second order: \[ H_k(z) = \frac{b_{k0} + b_{k1} z^{-1}}{1 + a_{k1} z^{-1} + a_{k2} z^{-2}} \]
Block diagram:

Given:
\[ H(z) = \frac{0.5(1 - z^{-2})}{1 + 1.3 z^{-1} + 0.36 z^{-2}}. \]
Rewrite numerator:
\[ H(z) = \frac{0.5 - 0.5 z^{-2}}{1 + 1.3 z^{-1} + 0.36 z^{-2}}. \]
Identify coefficients:
We will realize it in:
Difference equation (plug coefficients into general form):
\[ \begin{aligned} y(n) &= 0.5 x(n) + 0 \cdot x(n-1) - 0.5 x(n-2) \\ &\quad - 1.3 y(n-1) - 0.36 y(n-2) \\ &= 0.5 x(n) - 0.5 x(n-2) - 1.3 y(n-1) - 0.36 y(n-2). \end{aligned} \]
Direct‑Form I block diagram:

Recall Direct‑Form II equations:
Internal state: \[ w(n) = x(n) - 1.3 w(n-1) - 0.36 w(n-2) \]
Output: \[ y(n) = 0.5 w(n) + 0 \cdot w(n-1) - 0.5 w(n-2) = 0.5 w(n) - 0.5 w(n-2). \]
Direct‑Form II structure:

Factor \(H(z)\) into two first‑order sections:
\[ \begin{aligned} H(z) &= \frac{0.5(1 - z^{-2})}{1 + 1.3 z^{-1} + 0.36 z^{-2}} \\ &= \frac{0.5 - 0.5 z^{-1}}{1 + 0.4 z^{-1}} \cdot \frac{1 + z^{-1}}{1 + 0.9 z^{-1}} \\ &= H_1(z) \cdot H_2(z), \end{aligned} \]
with one possible choice:
(Other factorizations are possible; this is not unique.)
Cascade structure using Direct‑Form II in each section:

Let \(x(n)\) → Section 1 → intermediate \(y_1(n)\) → Section 2 → final \(y(n)\).
Section 1 (\(H_1(z)\)):
\[ \begin{aligned} w_1(n) &= x(n) - 0.4 w_1(n-1) \\ y_1(n) &= 0.5 w_1(n) - 0.5 w_1(n-1) \end{aligned} \]
Section 2 (\(H_2(z)\)):
\[ \begin{aligned} w_2(n) &= y_1(n) - 0.9 w_2(n-1) \\ y(n) &= w_2(n) + w_2(n-1) \end{aligned} \]
Use partial fraction expansion. Start by writing:
\[ \frac{H(z)}{z} = \frac{0.5(z^2 - 1)}{z(z + 0.4)(z + 0.9)} = \frac{A}{z} + \frac{B}{z + 0.4} + \frac{C}{z + 0.9}. \]
Solving for \(A, B, C\) (as in the text):
So:
\[ \begin{aligned} H(z) &= -1.39 + \frac{2.1 z}{z + 0.4} - \frac{0.21 z}{z + 0.9} \\ &= -1.39 + \frac{2.1}{1 + 0.4 z^{-1}} + \frac{-0.21}{1 + 0.9 z^{-1}}. \end{aligned} \]
Parallel structure: three sections in parallel, summed at the output.

Three parallel paths:
Constant‑gain path: \[ y_1(n) = -1.39\, x(n) \]
First IIR path: \[ \begin{aligned} w_2(n) &= x(n) - 0.4 w_2(n-1) \\ y_2(n) &= 2.1\, w_2(n) \end{aligned} \]
Second IIR path: \[ \begin{aligned} w_3(n) &= x(n) - 0.9 w_3(n-1) \\ y_3(n) &= -0.21\, w_3(n) \end{aligned} \]
Total output:
\[ y(n) = y_1(n) + y_2(n) + y_3(n). \]
We now apply these concepts to real‑world signals:
Think in terms of:
Speech spectra typically have more energy at low frequencies and less at high frequencies.
In some applications (e.g., speech coding, recognition):
Solution:
Preemphasis filter – roughly a highpass filter that boosts high frequencies and slightly attenuates low ones.
Simple digital preemphasis filter:
\[ y(n) = x(n) - \alpha x(n-1), \quad 0 < \alpha < 1. \]
Transfer function:
\[ H(z) = 1 - \alpha z^{-1}. \]
For \(\alpha = 0.9\), \(f_s = 8000\) Hz:
freqz([1 -alpha], 1, 512, fs) to plot frequency response.
Effect on speech waveform:

Spectra comparison (FFT + Hamming window):

Observation:
% Program 6.3: Preemphasis of speech
close all; clear all
fs = 8000; % Sampling rate
alpha = 0.9; % Degree of pre-emphasis
% Frequency response
figure(1);
freqz([1 -alpha], 1, 512, fs);
% Load speech signal
load speech.dat % Vector 'speech'
% Apply preemphasis filter
y = filter([1 -alpha], 1, speech);
% Time-domain plots
figure(2);
subplot(2,1,1); plot(speech); grid;
ylabel('Speech samples');
title('Original speech');
subplot(2,1,2); plot(y); grid;
ylabel('Filtered samples');
xlabel('Sample index');
title('Preemphasized speech');
% Spectral analysis
figure(3);
N = length(speech);
Axk = abs(fft(speech .* hamming(N))) / N;
Ayk = abs(fft(y .* hamming(N))) / N;
f = (0:N/2)*fs/N;
Axk(2:N) = 2*Axk(2:N);
Ayk(2:N) = 2*Ayk(2:N);
subplot(2,1,1); plot(f, Axk(1:N/2+1)); grid;
ylabel('Amplitude spectrum');
title('Original speech');
subplot(2,1,2); plot(f, Ayk(1:N/2+1)); grid;
ylabel('Amplitude spectrum');
xlabel('Frequency (Hz)');
title('Preemphasized speech');Goal: isolate a narrow band of speech frequencies for analysis or feature extraction.
Given a 4th‑order digital Bandpass Butterworth filter:
Transfer function:
\[ H(z) = \frac{0.0201 - 0.0402 z^{-2} + 0.0201 z^{-4}}{1 - 2.1192 z^{-1} + 2.6952 z^{-2} - 1.6924 z^{-3} + 0.6414 z^{-4}}. \]
Difference equation:
\[ \begin{aligned} y(n) &= 0.0201 x(n) - 0.0402 x(n-2) + 0.0201 x(n-4) \\ &\quad + 2.1192 y(n-1) - 2.6952 y(n-2) \\ &\quad + 1.6924 y(n-3) - 0.6414 y(n-4). \end{aligned} \]
Frequency response via freqz:

Time‑domain speech before and after filtering:

Spectra comparison:

Observation:
% Program 6.4: Bandpass filtering of speech
fs = 8000; % Sampling rate
% Bandpass filter coefficients (4th-order)
B = [0.0201 0 -0.0402 0 0.0201];
A = [1 -2.1192 2.6952 -1.6924 0.6414];
% Frequency response
figure(1);
freqz(B, A, 512, fs);
axis([0 fs/2 -40 1]); % Adjust axis if needed
% Load speech
load speech.dat
% Filter speech
y = filter(B, A, speech);
% Time-domain plots
figure(2);
subplot(2,1,1); plot(speech); grid;
ylabel('Original Samples');
title('Original speech');
subplot(2,1,2); plot(y); grid;
xlabel('Sample index'); ylabel('Filtered Samples');
title('Bandpass filtered speech.');
% Spectral analysis
figure(3);
N = length(speech);
Axk = abs(fft(speech .* hamming(N)))/N;
Ayk = abs(fft(y .* hamming(N)))/N;
f = (0:N/2)*fs/N;
Axk(2:N) = 2*Axk(2:N);
Ayk(2:N) = 2*Ayk(2:N);
subplot(2,1,1); plot(f, Axk(1:N/2+1)); grid;
ylabel('Amplitude spectrum');
title('Original speech');
subplot(2,1,2); plot(f, Ayk(1:N/2+1)); grid;
ylabel('Amplitude spectrum');
xlabel('Frequency (Hz)');
title('Bandpass filtered speech');Problem: ECG signal contaminated by 60 Hz power‑line interference during acquisition.
Given:
Designed second‑order notch filter:
\[ H(z) = \frac{1 - 1.4579 z^{-1} + z^{-2}}{1 - 1.3850 z^{-1} + 0.9025 z^{-2}}. \]
Difference equation:
\[ y(n) = x(n) - 1.4579 x(n-1) + x(n-2) + 1.3850 y(n-1) - 0.9025 y(n-2). \]
Frequency response:

Time‑domain ECG:

Frequency‑domain view:

Important
This is a classic and practical example of DSP in biomedical engineering: a simple, well‑designed digital notch filter significantly improves diagnostic signal quality.
freqz(B, A, N) computes complex frequency response of \(H(z) = B(z)/A(z)\).General IIR transfer function:
\[ H(z) = \frac{B(z)}{A(z)} = \frac{b_0 + b_1 z^{-1} + \dots + b_M z^{-M}}{1 + a_1 z^{-1} + \dots + a_N z^{-N}}. \]
Input–output relation (Direct‑Form I):
\[ \begin{aligned} y(n) &= b_0 x(n) + b_1 x(n-1) + \dots + b_M x(n-M) \\ &\quad - a_1 y(n-1) - \dots - a_N y(n-N). \end{aligned} \]
Direct‑Form II internal state and output:
\[ w(n) = x(n) - a_1 w(n-1) - \dots - a_M w(n-M) \]
\[ y(n) = b_0 w(n) + b_1 w(n-1) + \dots + b_M w(n-M) \]
Preemphasis filter:
\[ y(n) = x(n) - \alpha x(n-1), \quad H(z) = 1 - \alpha z^{-1}. \]
Example bandpass IIR difference equation:
\[ \begin{aligned} y(n) &= 0.0201 x(n) - 0.0402 x(n-2) + 0.0201 x(n-4) \\ &\quad + 2.1192 y(n-1) - 2.6952 y(n-2) \\ &\quad + 1.6924 y(n-3) - 0.6414 y(n-4). \end{aligned} \]
Example ECG notch filter:
\[ H(z) = \frac{1 - 1.4579 z^{-1} + z^{-2}}{1 - 1.3850 z^{-1} + 0.9025 z^{-2}} \]
\[ y(n) = x(n) - 1.4579 x(n-1) + x(n-2) + 1.3850 y(n-1) - 0.9025 y(n-2). \]
Suppose you see a magnitude response that is nearly 1 from 0 to \(0.3\pi\), then drops to nearly 0 after \(0.4\pi\). What filter type is this?
You are given a speech signal sampled at 8 kHz and want to remove low‑frequency background hum below 200 Hz. What basic filter type would you design?
In an ECG system, you see strong narrowband interference at 50 Hz with \(f_s = 500\) Hz. What kind of filter and approximate normalized notch frequency would you choose?
freqz vs Direct ComputationIn MATLAB we used freqz(B, A, N) to get the frequency response. Here, we’ll do the equivalent in Python using scipy.signal.freqz‑style logic.
We’ll:
B and denominator A.Experiment with a simple IIR filter (like Example 6.12).
Try:
B = [1.0], A = [1.0, -0.5] (Example 6.12(a): lowpass).B = [1.0, -0.5], A = [1.0] (Example 6.12(b): highpass).Use sliders to set a simple 2‑tap FIR filter: \(H(z) = b_0 + b_1 z^{-1}\). Observe the magnitude response and guess: lowpass or highpass?
Try:
Given the general IIR difference equation:
\[ y(n) = \sum_{k=0}^{M} b_k x(n-k) - \sum_{k=1}^{N} a_k y(n-k). \]
Let’s implement this manually and compare with the vector form.
Try:
x to a step input (x[n] = 1 for all n).We now implement the Direct‑Form II state update equations with interactive coefficients.
For a second‑order IIR with \(M=2\):
\[ \begin{aligned} w(n) &= x(n) - a_1 w(n-1) - a_2 w(n-2) \\ y(n) &= b_0 w(n) + b_1 w(n-1) + b_2 w(n-2). \end{aligned} \]
viewof a1 = Inputs.range([-2, 2], {step: 0.1, label: "a1 (feedback 1)"})
viewof a2 = Inputs.range([-2, 2], {step: 0.1, label: "a2 (feedback 2)"})
viewof b0_df2 = Inputs.range([-1, 1], {step: 0.1, label: "b0 (feedforward 0)"})
viewof b1_df2 = Inputs.range([-1, 1], {step: 0.1, label: "b1 (feedforward 1)"})
viewof b2_df2 = Inputs.range([-1, 1], {step: 0.1, label: "b2 (feedforward 2)"})Try to make the filter unstable by choosing \(a_1, a_2\) so that poles move outside the unit circle (e.g., magnitudes > 1). Observe the impulse response growing without bound.
Let’s approximate a pole–zero plot for one of the Example 6.12 filters.
Try replacing B and A with other examples ((a), (b), or (d)) and see how poles and zeros move.
Recall:
Use the slider to change \(\alpha\) and observe the magnitude response.
Observe: as \(\alpha\) → 1, the low‑frequency attenuation increases and high‑frequency boost increases.
Apply preemphasis to a simple synthetic “speech‑like” signal: mixture of low and high frequencies.
Try increasing the amplitude of the high‑frequency component in x and observe how preemphasis modifies the signal.
We’ll create a simple FIR bandpass by subtracting a lowpass response from a highpass response. To keep it simple, use two moving-average filters and explore the transition region.
Change L1 and L2 to see how the passband region and transition widths change.
Design a simple second‑order notch:
\[ H(z) = \frac{1 - 2r\cos\Omega_0 z^{-1} + r^2 z^{-2}}{1 - 2\cos\Omega_0 z^{-1} + z^{-2}}, \]
where \(\Omega_0\) is the notch frequency and \(0 < r < 1\) controls the width.
Try:
f0 = 60 Hz, r ≈ 0.95 (similar to ECG example).f0 around and watch the notch move along the frequency axis.Create a synthetic ECG‑like waveform (simplified) plus a sinusoidal interference at f0.
Observe how the oscillatory 60 Hz component is suppressed while the slower ECG‑like shape remains.
Think about:
Use any of the Pyodide cells above as a starting point and modify coefficients, input signals, or sampling rates.