Back to main

Lab 7: Building Filters

Exercises:

Exercise 17
Manipulating buffers.

In this short exercise, we'll practice writing classes which can manipulate buffers as a whole

Remember that a class definition tells the computer how to create objects which are instances of a class. Each object has a constructor which initialises its state, and (usually) several methods which can be thought of as functions which operate on the object, or perform operations on supplied data using the object

Delay class and Gain classes

We'll write a Delay class. A delay object is told how many samples delay (n) it should introduce. Then, when its delay method is passed an array of floating-point samples, it returns an array of the same size as the input with the contents delayed by n samples. Subsequent calls to the delay method return the remaining n samples from the previous input followed by all but n samples from the current input. Overall, across multiple calls, the first n samples of the output are set to zero, and the last n input samples are discarded.

For example:
>>> d = Delay(2)
>>> d.delay([1,2,3,4,5])
[0, 0, 1, 2, 3]
>>> d.delay([-1,-2,-3,-4,-5])
[4, 5, -1, -2, -3]

Also write a class Gain which returns the samples in the input buffer scaled by a specified amount.

Plot the result of using Delay and Gain classes on a sine wave. Plot the original waveform on the same axes for comparison.


Exercise 18
Filtering with pole and zero placement in the z-plane.

Background

Filters change the statistics of a signal.

In engineering, we usually assume systems are linear. We go to great lenths to linearise the response of electronic systems, and non-linear systems are still very much regarded as "specialist" compared with LTI (Linear, Time-Invariant) ones. Usually, when we refer to a filter, we mean a device which changes the spectral characteristics of a presented signal.

This lab falls into two parts: designing and implementing a filter directly by placing poles and zeros on the z-plane, and then converting a classical, continuous-time design such as the maximally-flat Butterworth or maximally-steep Tchebychev filters to operate as a sampled system.

First of all, though, we'll need some tools to measure the response of the systems we build. This can be done in the frequency domain by taking the Fourier Tranform of filtered white noise, or in the time domain by passing a varying-frequency sine-wave ("chirp") through the filter and seeing how its amplitude changes as the freqency sweeps across the frequency range of interest.

Maths homework

Look at the coefficients from the Exercise 16 Maths Homework

Write down the transfer function for this system in the form

( z - z 1 ) ( z - z 2 ) ... ( z - z n ) ( z - p 1 ) ( z - p 2 ) ... ( z - p n )

Expand this out to obtain the numerator and denominator as polynomials in z-1, resulting in this form:

G 1 + a 1 z - 1 + a 2 z - 2 1 + b 1 z - 1 + b 2 z - 2

This can be implemented using a second order, bi-quadratic section like this:

On paper, write out the values for a second-order filter, at the output of each delay and the output of the entire filter.


Your task...

There are a few sub-tasks. Do them in order, and it would be good to get them checked by Nick or Graham at each step.

Sub-goal 1

Noise generation: filters aren't so fascinating on a sine wave, so make a class that generates white noise. This is like your sine-wave oscillator classes, but instead of returning an array of the next 2048 samples of a sine wave, you return 2048 samples of random values.

for fun: after doing white noise, make some other colors of noise, like pink, brown, grey, blue, violet, orange, or black noise. (wikipedia: colors [sic] of noise)

Check the spectrum of the noise is what you expect by plotting the FFT of your noise generator's(') output(s). Hint: numpy provides a routine to take the FFT of real values which you might find useful.

Sub-goal 2

Enhance your Delay class. The improved class will contain a process(audio_sample) method, which will return its arguments delayed by up to N samples. N should be passed in via the constructor.

For example:

d = Delay(2)
d.process(2.0)
[2.0, 0]
d.process(3.5)
[3.5, 2.0]
d.process(-1.0)
[-1.0, 3.5]

Sub-goal 3

Write a Filter class. For now, you may hard-code the filter coefficients in the __init__() function (i.e. you do not need to pass them as arguments).

Use the coefficients of the filter in Example 16 to test it.

... show your work to a demonstrator


Exercise 19
Filtering to a specification

Background

Filters designed to specification rely on determining the required order and type which will best satisfy the requirements in hand.

The following types of filters are commonly used as prototypes:

Bessel or Bessel-Thomson Filters
Phase linear (i.e. constant group delay) filters. Because the delay is constant at all frequencies, the phase coherence (shape) of the waveform is preserved. This is the optimal phase response but the cut-off rate of these filters, to say the least, could be better. In audio processing, one is generally less concerned with phase accuracy (except in very high-end audio applications).
Butterworth Filters
The "default choice" in many situations, combining ease of design with respectable performance. Maximally sharp transition between pass and stop bands given that the pass-band has a maximally flat amplitude response.
Tchebysheff type I filters
Improved sharpness of cut-off over Butterworth designs achieved by allowing a maximum ripple in the pass-band. Even a small amount of ripple (0.5dB or even 0.1dB) can improve performance considerably, although phase response is worsened. Harder to design than Butterworth filters, although computer design techniques makes this considerably less of a problem. Also harder to spell: often written "Chebyshev" (acutally named after the Russian mathematician Пафну́тий Льво́вич Чебышёв (approximately "Pafnuty Lvovich Chebyshev")
Chebyshov type II filters
As Chebysheff type I, but the pass-band is flat and ripple allowed in the stop-band.
Elliptic filters
The fastest possible transition between pass- and stop-bands, obtained by allowing ripple in both. Traditionally very difficult to design, requiring the use of elliptic integrals, but now frequently used (see "Your Task" below).

With all of this development having been put into analogue filter design, one of the best ways of constructing a digital filter "to spec" is to start off with the analogue design and then to transform it into the digital domain.

Maths homework

The design process for a digital filter based on one of the "classical" polynomials is covered in detail in the Making Computer Music PDF file. In (extreme) summary,

To practise this process using a simple filter design. calulate the coefficients of a 1kHz low-pass, second-order Butterworth filter operating at 48000Hz sample rate. The calculation is much simplified if you normalise the sample rate to 1Hz (T=1) and adjust the cutoff frequency appropriately (keeping it the same proportion of the Nyquist rate).


Your task...

The algebra necessary to design higher-order filters isn't particularly difficult but is very tedious. For this reason, you might think it likely that functions have been written to calculate the polynomial coefficients and/or pole-zero placements necessary to realise a filter of given type and specification. And you would be right.

Read and understand the documentation of the python scipy.signal.iirdesign() function. Use it to generate a filter which delivers an agressive high-pass response removing frequencies below 1.5kHz.

Implement the filter you have designed, and test it both with white noise and by listening to what it does to a voice signal. You may implement the filter directly, or by using a standard filter function (for example, scipy.signal.lfilter())

... show your work to a demonstrator

Back to main


Creative Commons License Unless otherwise noted, all materials on these pages are licenced under a Creative Commons Licence .