← All tutorials

How FIR and IIR Filters Shape Magnitude and Phase

What filters do to gain and timing.

On this page

1 A filter scales and shifts each sine 2 FIR filters average recent samples 3 IIR filters feed the output back 4 The frequency response 5 Linear phase keeps the shape 6 Zero phase when you can wait 7 FIR or IIR 8 In practice 9 When a filter misbehaves 10 References

A filter keeps some frequencies of a signal and turns others down. Each frequency gets a gain (how much it is scaled) and a phase shift (how much it is delayed). FIR filters build each output from recent inputs only; IIR filters also feed back their past outputs. Both act once per sample, and designing one means choosing its weights so the gain is right where it matters, paid for in delay and computation.

1 A filter scales and shifts each sine

The filters here are LTI systems, linear and time-invariant (double the input and the output doubles; the filter never changes), so a sine goes in and the same sine comes out, scaled by the magnitude response |H(f)| and shifted by the phase response ∠H(f).

shift ∠H(f) in out gain out ÷ in
Figure 1. Same frequency in and out. 1Phase ∠H(f) shifts it. 2Gain |H(f)|, out ÷ in, scales it.

2 FIR filters average recent samples

A finite impulse response (FIR) filter makes each output a weighted sum of the newest input and a few past inputs; the weights are called taps.

An FIR filter with M + 1 taps.
y[n] = Σi=0M bi x[n − i]
Each output adds the newest input and the M before it, tap i weighing the input from i samples ago. No output sees more than M samples back.
bi = 14, M = 3
Four equal taps average the last four inputs, so fast wiggles cancel and slow changes pass. A short bump comes out lower and wider. It builds on the convolution, and Section 4 turns these taps into a frequency response.

Figure 2. 1The newest four samples, each times ¼. 2Their sum is y[n].

This sliding weighted sum is a convolution, the same operation a convolution layer computes (CNN Basics: Convolution, Stride and Pooling).

3 IIR filters feed the output back

An infinite impulse response (IIR) filter also feeds its own output back in, like an echo that adds weaker and weaker copies of the sound. Each copy is the last one times a feedback weight a between 0 and 1.

x[n] × (1 − a) Σ y[n] delay y[n − 1] × a fed back
Figure 3. 1Input times 1 − a. 2The output waits a sample. 3Times a, it joins the next input.

A level meter’s smoother keeps half of its last output (a = 0.5) and adds half of the new sample (b = 0.5). It turns one click into 0.5, 0.25, 0.125 and on, halving every sample.

A one-pole IIR low-pass, the simplest IIR filter, with one feedback term, called a pole.
y[n] = b x[n] + a y[n − 1]
Each output takes a share b = 1 − a of the new input x[n].
It adds a share a of the last output, so it feeds back on itself. One click comes out as an echo that shrinks by a every sample and never quite ends, hence "infinite". With a near 1 the output changes slowly and only low frequencies pass. a between 0 and 1 gives a low-pass; |a| = 1 never fades, and |a| > 1 grows without end. It builds on the FIR sum of Section 2 and adds feedback; Section 4 gives its frequency response.

The one-pole low-pass from the card, run on noise with lfilter.

import numpy as np
from scipy import signal

FS = 8000
a = 0.8                                   # a in the card
x = np.random.default_rng(0).standard_normal(FS)
y = signal.lfilter([1 - a], [1, -a], x)
  1. 1Sets the feedback a of the one-pole card in Section 3 and makes one second of noise.
  2. 2Runs the card: the first list holds the input weight 1 − a, the second the feedback weights [1, −a], the sign flipped because SciPy keeps the feedback on the left side.

A low-pass passes slow changes and cuts fast ones. Its cutoff is where the gain bends down, by convention the −3 dB point, where the power has halved and the amplitude is about 0.71 of the input’s.

Figure 4. 1Each echo is a times the last. 2The −3 dB point falls as a grows.

4 The frequency response

A 2-tap average, b = [0.5, 0.5], passes a steady level whole and removes the fastest wiggle, at the Nyquist limit, where its two taps cancel.

The frequency response of an FIR filter.
H(f) = Σi=0M bi e−j2πfi/fs
For a sine at frequency f, tap i sees it i samples late, an arrow turned by e−j2πfi/fs and scaled by bi. H(f) is the sum of those arrows; |H| is its length, the gain, and ∠H its angle, the phase shift. Where the arrows line up the frequency passes, and where they cancel it is cut. This is the DFT of Fourier Transform and the FFT applied to the taps.

The frequency response of a 31-tap FIR low-pass with freqz.

import numpy as np
from scipy import signal

FS, N_TAPS, CUTOFF_HZ = 16000, 31, 1000
b = signal.firwin(N_TAPS, CUTOFF_HZ, fs=FS)    # taps b_i
f, H = signal.freqz(b, worN=1024, fs=FS)
gain_db = 20 * np.log10(np.abs(H))
phase = np.unwrap(np.angle(H))
  1. 1Designs the 31 taps bi of the FIR card in Section 2.
  2. 2Evaluates H(f), the frequency response card in Section 4.
  3. 3Turns H(f) into the gain |H(f)| in dB and the phase ∠H(f).

The smoother of Section 3, a = b = 0.5, has gain 1 (0 dB) on a steady level and 1/3 (−9.5 dB) at the Nyquist limit.

The frequency response of an IIR filter.
H(f) = Σi bi e−j2πfi/fs1 − Σk ak e−j2πfk/fs
The top is the FIR sum over the input weights bi; the bottom is the same sum over the feedback weights ak, the weight on the output from k samples ago. The one-pole card has b0 = b = 1 − a and a1 = a. SciPy's freqz evaluates this ratio for either kind.

The IIR filter used from here on is a Butterworth low-pass, the IIR design with the flattest possible passband before the gain falls away smoothly. Its order is its number of poles, the past outputs it feeds back: the filter of Section 3 has one, and order 4 has four, which cuts more steeply. SciPy builds it from second-order sections, a chain of small two-pole filters, which keeps the arithmetic stable. Figure 5 runs a 4th-order Butterworth with its cutoff at 1 kHz, on audio sampled at 16 kHz.

Figure 5. 1The dot reads the gain. The output lags, and shrinks above 1 kHz.

5 Linear phase keeps the shape

A click is many sines whose peaks meet at one instant. In Figure 6 the thin lines are three such sines, ticks mark their peaks and the thick line is their sum. Delay them all equally and the click survives; delay them unequally and it smears.

same delay, the peak survives different delays, the peak smears
Figure 6. Thick line, the sum. 1Equal delays align the peaks. 2Unequal ones spread them.

Symmetric FIR taps delay every frequency equally, so the phase is a straight line in frequency, called linear phase. A 5-tap symmetric filter delays everything by 2 samples, 125 µs at 16 kHz; a 101-tap one by 50 samples, 3.125 ms.

The delay of a symmetric FIR filter with M + 1 taps.
delay = M2 samples
Symmetric taps mirror around the middle tap, so each mirrored pair adds to an arrow at the middle tap's angle. The sum always points as if it came from the middle, M2 samples late, at every frequency, which keeps every part of a wave aligned. With 31 taps, M = 30, so at fs = 16 kHz everything is 15 samples, 0.94 ms, late. It builds on the FIR response of Section 4, and the table in Section 7 lists this delay.

The Butterworth of Figure 5 cannot do this; its delay changes with frequency, most near the cutoff. A straight phase line delays every frequency equally, so the ringing at a pulse’s edges stays even; a bent one delays some frequencies longer and makes it lopsided (Figure 7).

Figure 7. 1Ringing at each edge. 2Straight phase, even; bent phase, lopsided.

6 Zero phase when you can wait

Offline, the whole recording is there, future included, so the filter can run forward and then backward over the result, which needs the signal reversed in time (Anushiravani 2016). A 4th-order Butterworth low-pass at 1 kHz shows what the second pass changes.

PassesGain at 1 kHzWhere the gain is −3 dB
Forward only−3 dB1 kHz
Forward, then backward−6 dBabout 898 Hz
Forward then backward, the zero-phase filter.
|Hzp(f)| = |H(f)|2
The filter acts twice, so its gain is squared and its dB double: a 4th-order Butterworth low-pass at 1 kHz is −3 dB at its cutoff once and −6 dB after both passes.
∠Hzp(f) = 0
The backward pass delays every frequency by exactly what the forward pass delayed it, in the other direction, so no delay is left. It needs the future, so it cannot run live. It builds on the phase of Section 5, and Section 8 runs it as sosfiltfilt.

7 FIR or IIR

FIRIIR
Taps for a sharp edgeManyFew
PhaseCan be exactly linearNot linear
StabilityAlways stableNeeds the feedback to fade (|a| < 1 here)
DelayM/2 samples, the same for all frequenciesSmall, but changes with frequency
Pick it whenThe waveform’s shape mattersCompute is tight and timing does not matter

8 In practice

Filters run per sample on a live stream, such as a hum notch or the low-pass before decimation (keeping only every second or fourth sample), or over a whole file offline.

StepHow often, on what dataState it keepsCost
Design (firwin, butter)once at design time, on the specificationnoneone-off
FIR filteronce per sample, on the newest inputsone input per tapone multiply per tap
IIR filteronce per sample, on the input and past outputspast outputs, the feedback statea few multiplies per section
Zero phase (filtfilt)once per recording, offline, on the whole recordingnone between recordingstwo passes of the filter

Rates, state and cost come from the sources in References.

One noise signal through a 4th-order Butterworth low-pass, live with sosfilt and offline with sosfiltfilt.

import numpy as np
from scipy import signal

FS, ORDER, CUTOFF_HZ = 16000, 4, 1000
x = np.random.default_rng(0).standard_normal(FS)
sos = signal.butter(ORDER, CUTOFF_HZ, fs=FS, output='sos')
y_live = signal.sosfilt(sos, x)
y_off = signal.sosfiltfilt(sos, x)
  1. 1Makes one second of noise and a 4th-order Butterworth low-pass in second-order sections.
  2. 2Filters live, sample by sample, with the delay of Figure 5.
  3. 3Filters forward and backward offline, the zero phase of Section 6.
SettingDefaultSource
butter output'ba' by default; 'sos' recommendedscipy.signal.butter
firwin window'hamming'; the cutoff is the −6 dB pointscipy.signal.firwin
sosfilt startzi=None, starting from silencescipy.signal.sosfilt
Knob, where it entersRaise itLower itDefault
Order M, the index of the last tap in the FIR cardSharper edge, more delay and cost per sampleGentler edge; M = 0 is a plain gainNo default; numtaps = M + 1 is required (scipy.signal.firwin)
Cutoff fc, where the gain bends downMore of the band passes; at the Nyquist frequency nothing is cutMore is cut; near 0 only the slow trend survivesNo default; Wn is required. butter puts it at the −3 dB point, firwin at −6 dB (scipy.signal.butter)
Feedback a in the one-pole cardLonger echo, lower −3 dB point; a = 1 never fadesShorter echo; a = 0 passes the input unchangedNone; lfilter takes the given weights (scipy.signal.lfilter)

SciPy documents butter, firwin and sosfilt.

8.1 Gotchas

9 When a filter misbehaves

What you seeLikely causeWhat to do
Ripples near sharp edges in the outputA short FIR with a sharp edge rings (ripples on both sides of each edge)More taps, or a gentler edge
Events look shifted in time after filteringThe filter's delayShift back by M/2 for an FIR, or use sosfiltfilt offline
The cutoff lands far from where you set itThe cutoff was read in the wrong unitsPass fs= with the cutoff in hertz, or give the cutoff as a fraction of the Nyquist frequency

Spectral Subtraction for Denoising and The Wiener Filter set a gain per frequency from the noise; the anti-alias filter on Aliasing and Anti-Aliasing Filters is this page’s Butterworth.

How does the spectrum change over time? That is The STFT and Spectrograms.

10 References