# Transfer functions

## Contents

# 12.3. Transfer functions#

When we previously analyzed FIR filters, it became useful to examine the discrete Fourier transform \(\red{H[m]}\) of the impulse response \(\red{h[k]}\). The frequency domain view of convolutional filters immediately exposes how a filter will delay and gain (or attenuate) different frequencies. However, as noted earlier in this chapter, the DFT cannot be applied directly to feedback systems with infinite impulse responses, and this led to our derivation of the z-Transform.

In this section, we’ll return to our initial motivation, and derive something similar to \(\red{H[m]}\), except from the perspective of the z-Transform rather than the DFT.
The resulting object is known as the *transfer function* of a filter, and is denoted by \(\red{H(z)}\).

## 12.3.1. Defining a transfer function#

Let’s start with a linear IIR filter in standard form:

where \(\red{b[k]}\) and \(\cyan{a[k]}\) denote the feed-forward and feed-back coefficients of the filter.

If we move all feedback terms to the left-hand side of the equation, we obtain an equivalent equation:

While this form is not useful for *computing* \(\purple{y}\), it is useful for **analyzing** \(\purple{y}\)!

In particular, you might recognize that both sides of the equation are convolutions:

This means that if we take the z-transform of both sides, we can use the z-transform convolution theorem:

where \(\cyan{A(z)}, \magenta{Y(z)}, \red{B(z)}, \darkblue{X(z)}\) denote the z-transforms of \(\cyan{a}\), \(\purple{y}\), \(\red{b}\), and \(\blue{x}\) respectively.

As long as \(\cyan{A(z)}\) is not zero — and it generally is non-zero except for at most \(K\) specific choices of \(\purple{z}\) — we can divide through to isolate \(\magenta{Y(z)}\):

This gives us something highly reminiscent of the convolution theorem: filtering in the time domain has again be expressed as multiplication, except now in the \(z\)-plane instead of the frequency domain.

(Transfer function)

Let \(\cyan{a}\) and \(\red{b}\) denote the feed-back and feed-forward coefficients of a linear IIR filter:

The **transfer function** of this filter is defined as follows

## 12.3.2. Using transfer functions#

As mentioned previously, transfer functions can be thought of as providing a generalization of the convolution theorem to support feedback filters. Specifically, we have

Note that as a special case, if \(\cyan{a = [1]}\) (so that there is no feedback in the filter), we get the z-transform

In this case, the transfer function simplifies to \(\red{H(z)} = \red{B(z)} / \cyan{1} = \red{B(z)}\), and we recover the convolution theorem.

More generally, we can still reason about \(\red{H(z)}\) as the object that transforms the input signal \(\blue{x}\) into the output signal \(\purple{y}\).
Evaluating \(\red{H(z)}\) at values of \(\purple{z}\) with unit magnitude — i.e. \(\purple{z= e^{\mathrm{j}\cdot \theta}}\) — produces the *frequency response* of the filter.
In Python, this is provided by the function `scipy.signal.freqz`

(frequency response via z-transform).

### 12.3.2.1. Example: analyzing a Butterworth filter#

In Using IIR filters, we constructed a Butterworth filter and rather crudely analyzed its effect on an impulse input by truncating the output \(y\) and taking the DFT. Let’s revisit this example, but instead analyze it using the z-transform and transfer functions.

```
fs = 44100 # 44.1K sampling rate
fc = 500 # 500 Hz cutoff frequency
order = 10 # order-10 filter
b, a = scipy.signal.butter(order, fc, fs=fs)
freq, H = scipy.signal.freqz(b, a, fs=fs)
```

The result of this computation is an array `freq`

of frequencies (spaced uniformly between `0`

and `fs/2`

), and an array `H`

that evaluates the transfer function at each specified frequency.

The figure below illustrates the response curve by plotting `freq`

on the horizontal axis and `abs(h)`

(in decibel scale) on the vertical axis, i.e.:

```
import matplotlib.pyplot as plt
# Convert |H| to decibels, with a -120dB noise floor
H_dB = 20 * np.log10(np.abs(H) + 1e-6)
plt.plot(freq, H_dB)
```

Note that we never had to construct a test signal to generate this curve: all of the information was inferred from the filter coefficients `b`

and `a`

!

### 12.3.2.2. Example: phase response of an elliptic filter#

Just like with convolutional filters, we can infer delay properties of IIR filters by looking at the phase spectrum contained in \(\red{H(z)}\).
This can be done directly, e.g., by calling `scipy.signal.freqz`

and then using `np.unwrap(np.angle(h))`

to compute the unwrapped phase.
Alternatively, it is usually more convenient to use the already provided `group_delay`

function, as demonstrated below for an elliptic filter.

```
fs = 44100 # Sampling rate
fc = 500 # Cutoff frequency
fstop = 600
attenuation = 40 # we'll require 80 dB attenuation in the stop band
ripple = 3 # we'll allow 3 dB ripple in the passband
order_ell, wn = scipy.signal.ellipord(fc, fstop, ripple, attenuation, fs=fs)
b_ell, a_ell = scipy.signal.ellip(order_ell, ripple, attenuation, wn, fs=fs)
# scipy's group delay measures in samples
# we'll convert to seconds by dividing out the sampling rate
freq, delay = scipy.signal.group_delay([b_ell, a_ell], fs=fs)
delay_sec = delay / fs
```

Note that `scipy.signal.group_delay`

returns the delay for each measured frequency in `freq`

in units of `[samples]`

.
To convert the delay measurements to `[seconds]`

, we divide by the sampling rate `fs`

.
A visualization of the resulting group delay (along with the frequency response) is provided below.

## 12.3.3. Why not use the DTFT?#

Everything we’ve done so far only depends on \(z\) with unit magnitude: frequency response and phase response are both properties of \(\red{H\left(e^{j\cdot \theta}\right)}\), which we can think of as computing the discrete-time Fourier transform (DTFT). At this point, it is completely reasonable to wonder why we needed to define the z-transform to support any complex number \(\purple{z}\), instead of just those points on the unit circle.

As we will see in the next section, expanding the definition to include all complex \(z\) allows us to study the *stability* of a filter, and this would not be possible with just the DTFT.