A filter is just a weighted sum of samples
Picture a noisy temperature reading that jitters up and down every second, even though the room is barely changing. The oldest trick in the book is to average the last few readings: today's number, smoothed by yesterday's. That instinct — *combine nearby samples to smooth or sharpen a signal* — is the entire idea of a digital filter. A filter takes an input stream of discrete-time samples x[n] and produces an output y[n] by adding up scaled, delayed copies of the input (and sometimes of its own past output).
Every filter we care about in this track is linear and time-invariant — an LTI system — which is a wonderful constraint. It means the filter is *completely* described by one object: its impulse response h[n], the output you get when you poke it with a single sample of value 1. Hand the filter any input and the output is the convolution of x[n] with h[n]. So the whole game is choosing the right h[n].
Difference equation (the recipe a filter actually runs):
y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] + ... (feed-forward: input taps)
- a1*y[n-1] - a2*y[n-2] - ... (feedback: output taps)
b coefficients -> ZEROS a coefficients -> POLES
If all a's are zero -> no feedback -> FIR (finite memory)
If any a is nonzero -> feedback -> IIR (infinite memory)FIR: bulletproof, honest, and a little expensive
A finite impulse response filter has no feedback at all. Its impulse response is literally the list of b coefficients — feed in a single 1 and the output is b0, b1, b2, …, then dead zeros forever after. That finiteness buys you two superpowers. First, a FIR filter is always stable: with no feedback loop there is nothing to run away, so a bounded input can never produce an unbounded output. Second, if you make the coefficients symmetric, the filter has exactly linear phase — every frequency is delayed by the same number of samples, so the signal's shape is preserved with no smearing.
Look at the pole-zero picture from rung 3 and the story is clean: a FIR filter has only zeros (plus a trivial pile of poles sitting at the origin, which don't affect stability). With nothing but zeros, you carve notches into the spectrum but you can never build a sharp resonant peak — so getting a steep cutoff means using *many* coefficients. A brick-wall low-pass at audio rates can easily need 100 or more taps, and each tap is a multiply-and-add on every single sample. That is the FIR tax: rock-solid behaviour, but a lot of arithmetic.
5-point moving-average FIR (all b = 1/5, no feedback):
y[n] = (x[n] + x[n-1] + x[n-2] + x[n-3] + x[n-4]) / 5
Impulse response h = [0.2, 0.2, 0.2, 0.2, 0.2]
Frequency response H(e^jw) magnitude:
|H|
1.0 *._
| \._ nulls at f = fs/5, 2fs/5 (zeros on unit circle)
| \.
| \. .-'\. low-pass, but GENTLE roll-off
| '-' '-.__
0.0 +------------------------> frequencyIIR: cheap, sharp, and dangerous
Now switch on the feedback. An infinite impulse response filter feeds its own past outputs back into the sum, and that loop is the whole difference. Because each output lingers in the loop and decays away slowly, a single input poke rings on essentially forever — hence *infinite* impulse response. The payoff is staggering efficiency: an IIR filter can match the selectivity of a 100-tap FIR with just 4 to 8 coefficients, because feedback lets you place poles.
On the pole-zero map, poles are peaks: drag a pole close to the unit circle and the frequency response spikes up at that angle, giving you a sharp resonance or a steep band edge almost for free. But here is the knife's edge — a pole on or outside the unit circle is an unstable filter. That feedback loop, the source of all the efficiency, will then amplify its own output without limit and the numbers blow up. So IIR design lives and dies by keeping every pole strictly inside the unit circle.
One-pole IIR low-pass (the workhorse smoother):
y[n] = a * y[n-1] + (1 - a) * x[n] with 0 < a < 1
- Single pole at z = a, on the real axis, inside the unit circle.
- a near 1 -> pole hugs the circle -> very smooth, slow, long memory.
- a near 0 -> pole near origin -> barely filters at all.
Impulse response (set x = 1 at n=0):
h = (1-a) * [1, a, a^2, a^3, a^4, ...] <- decays forever (INFINITE)
Two adds, two multiplies, one stored value -> a whole low-pass filter.Designing a FIR: the window method
How do you actually pick the coefficients? For FIR, the most intuitive route is the window method, and it runs straight off the Fourier ideas from earlier rungs. Start by writing down the frequency response you *wish* you had — say, a perfect brick-wall low-pass, 1 in the passband and 0 in the stopband. Take its inverse Fourier transform and you get the ideal impulse response: a sinc function that, frustratingly, stretches infinitely in both directions. You can't run an infinite filter, so you have to chop it.
- Specify the ideal response (cutoff frequency, low-pass / high-pass / band-pass) and inverse-transform it to the ideal sinc impulse response h_ideal[n].
- Truncate it to N taps. Chopping abruptly is the same as multiplying by a rectangular window — and that hard edge causes spectral leakage, showing up as wiggly ripples in the passband and stopband (the Gibbs phenomenon).
- Apply a smoother window instead — Hamming, Hann, Blackman, Kaiser. Multiplying h_ideal[n] by a tapered window gently fades the coefficients to zero at the edges, trading a slightly wider transition band for dramatically lower ripple.
- Verify the resulting frequency response. If the stopband isn't deep enough or the transition is too wide, increase N or switch windows, and iterate. (A Kaiser window lets you dial ripple and transition independently.)
Designing an IIR: borrow from analog
IIR design takes a completely different and rather elegant shortcut: engineers spent a century perfecting *analog* filters, so why reinvent that? The standard flow is to start from a classic analog prototype — Butterworth (maximally flat passband), Chebyshev (steeper, at the cost of passband ripple), or elliptic (steepest of all, ripple in both bands) — design it in the continuous s-domain where its poles and zeros are well known, then map it onto the discrete z-plane.
The workhorse mapping is the bilinear transform, s ← (2/T)·(1−z⁻¹)/(1+z⁻¹). It has one beautiful property: it maps the entire stable left half of the s-plane to the inside of the unit circle, so a stable analog filter becomes a stable digital filter automatically — no pole can sneak outside. The price is frequency warping: the mapping squeezes the infinite analog frequency axis into the finite digital one, bending frequencies near the top of the band. You correct for it by pre-warping the critical frequencies before designing, so your cutoff lands exactly where you want it.
The two design flows side by side:
FIR (window method) IIR (analog-prototype mapping)
------------------- -----------------------------
desired |H(f)| spec choose Butterworth/Chebyshev/elliptic
| |
inverse FT -> sinc h[n] design analog poles/zeros in s-plane
| |
truncate to N taps pre-warp critical frequencies
| |
apply window (Hamming...) bilinear transform s -> z
| |
-> linear-phase FIR -> cascade of biquads (stable IIR)Convolution: how an FIR actually runs
Strip away the design theory and a running FIR filter is just one operation repeated forever: convolution. Think of the coefficient list h[] as a little stencil that slides along the input. At every new sample, you line the flipped stencil up over the most recent inputs, multiply each pair, sum them, and that sum is your next output. Slide one step, repeat. Engineers call this the *multiply-accumulate* (MAC) loop, and it is the single most executed instruction in all of DSP.
// Direct-form FIR in C: convolution as a MAC loop
float fir(float x_new, const float h[], float state[], int N) {
// shift the delay line (oldest sample falls off the end)
for (int i = N - 1; i > 0; i--) state[i] = state[i - 1];
state[0] = x_new;
float y = 0.0f;
for (int i = 0; i < N; i++)
y += h[i] * state[i]; // multiply-accumulate: the heart of DSP
return y;
}This is also where the track's loose ends tie together. Direct convolution costs N multiplies per output sample, which gets brutal for long filters — so for very long FIRs, engineers compute the convolution in the frequency domain instead, multiplying spectra with the FFT (fast convolution), because multiplication in frequency *is* convolution in time. Meanwhile the same convolution viewed through the z-transform becomes a simple polynomial multiply, and that polynomial's roots are precisely the filter's zeros. Sampling, the z-transform, the frequency response, convolution — they were never separate topics. They are four windows onto the same machine.