Using IIR filters
The IIRFilterNode
interface of the Web Audio API is an AudioNode
processor that implements a general infinite impulse response (IIR) filter; this type of filter can be used to implement tone control devices and graphic equalizers, and the filter response parameters can be specified, so that it can be tuned as needed. This article looks at how to implement one, and use it in a simple example.
Demo
Our simple example for this guide provides a play/pause button that starts and pauses audio play, and a toggle that turns an IIR filter on and off, altering the tone of the sound. It also provides a canvas on which is drawn the frequency response of the audio, so you can see what effect the IIR filter has.
You can check out the full demo here on Codepen. Also see the source code on GitHub. It includes some different coefficient values for different lowpass frequencies — you can change the value of the filterNumber
constant to a value between 0 and 3 to check out the different available effects.
Browser support
IIR filters are supported well across modern browsers, although they have been implemented more recently than some of the more longstanding Web Audio API features, like Biquad filters.
The IIRFilterNode
The Web Audio API now comes with an IIRFilterNode
interface. But what is this and how does it differ from the BiquadFilterNode
we have already?
An IIR filter is a infinite impulse response filter. It's one of two primary types of filters used in audio and digital signal processing. The other type is FIR — finite impulse response filter. There's a really good overview to IIF filters and FIR filters here.
A biquad filter is actually a specific type of infinite impulse response filter. It's a commonly-used type and we already have it as a node in the Web Audio API. If you choose this node the hard work is done for you. For instance, if you want to filter lower frequencies from your sound, you can set the type to highpass
and then set which frequency to filter from (or cut off). There's more information on how biquad filters work here.
When you are using an IIRFilterNode
instead of a BiquadFilterNode
you are creating the filter yourself, rather than just choosing a pre-programmed type. So you can create a highpass filter, or a lowpass filter, or a more bespoke one. And this is where the IIR filter node is useful — you can create your own if none of the alaready available settings is right for what you want. As well as this, if your audio graph needed a highpass and a bandpass filter within it, you could just use one IIR filter node in place of the two biquad filter nodes you would otherwise need for this.
With the IIRFIlter node it's up to you to set what feedforward
and feedback
values the filter needs — this determines the characteristics of the filter. The downside is that this involves some complex maths.
If you are looking to learn more there's some information about the maths behind IIR filters here. This enters the realms of signal processing theory — don't worry if you look at it and feel like it's not for you.
If you want to play with the IIR filter node and need some values to help along the way, there's a table of already calculated values here; on pages 4 & 5 of the linked PDF the an values refer to the feedForward
values and the bn values refer to the feedback
. musicdsp.org is also a great resource if you want to read more about different filters and how they are implemented digitally.
With that all in mind, let's take a look at the code to create an IIR filter with the Web Audio API.
Setting our IIRFilter co-efficients
When creating an IIR filter, we pass in the feedforward
and feedback
coefficients as options (coefficients is how we describe the values). Both of these parameters are arrays, neither of which can be larger than 20 items.
When setting our coefficients, the feedforward
values can't all be set to zero, otherwise nothing would be sent to the filter. Something like this is acceptable:
let feedForward = [0.00020298, 0.0004059599, 0.00020298];
Our feedback
values cannot start with zero, otherwise on the first pass nothing would be sent back:
let feedBackward = [1.0126964558, -1.9991880801, 0.9873035442];
Note: These values are calculated based on the lowpass filter specified in the filter characteristics of the Web Audio API specification. As this filter node gains more popularity we should be able to collate more coefficient values.
Using an IIRFilter in an audio graph
Let's create our context and our filter node:
const AudioContext = window.AudioContext || window.webkitAudioContext; const audioCtx = new AudioContext(); const iirFilter = audioCtx.createIIRFilter(feedForward, feedBack);
We need a sound source to play. We set this up using a custom function, playSoundNode()
, which creates a buffer source from an existing AudioBuffer
, attaches it to the default destination, starts it playing, and returns it:
function playSourceNode(audioContext, audioBuffer) { const soundSource = audioContext.createBufferSource(); soundSource.buffer = audioBuffer; soundSource.connect(audioContext.destination); soundSource.start(); return soundSource; }
This function is called when the play button is pressed. The play button HTML looks like this:
<button class="button-play" role="switch" data-playing="false" aria-pressed="false">Play</button>
And the click
event listener starts like so:
playButton.addEventListener('click', function() { if (this.dataset.playing === 'false') { srcNode = playSourceNode(audioCtx, sample); ... }, false);
The toggle that turns the IIR filter on and off is set up in the similar way. First, the HTML:
<button class="button-filter" role="switch" data-filteron="false" aria-pressed="false" aria-describedby="label" disabled></button>
The filter button's click
handler then connects the IIRFilter
up to the graph, between the source and the detination:
filterButton.addEventListener('click', function() { if (this.dataset.filteron === 'false') { srcNode.disconnect(audioCtx.destination); srcNode.connect(iirfilter).connect(audioCtx.destination); ... }, false);
Frequency response
We only have one method available on IIRFilterNode
instances, getFrequencyResponse()
, this allows us to see what is happening to the frequencies of the audio being passed into the filter.
Let's draw a frequency plot of the filter we've created with the data we get back from this method.
We need to create three arrays. One of frequency values for which we want to receive the magnitude response and phase response for, and two empty arrays to receive the data. All three of these have to be of type float32array
and all be of the same size.
// arrays for our frequency response const totalArrayItems = 30; let myFrequencyArray = new Float32Array(totalArrayItems); let magResponseOutput = new Float32Array(totalArrayItems); let phaseResponseOutput = new Float32Array(totalArrayItems);
Let's fill our first array with frequency values we want data to be returned on:
myFrequencyArray = myFrequencyArray.map(function(item, index) { return Math.pow(1.4, index); });
We could go for a linear approach, but it's far better when working with frequencies to take a log approach, so let's fill our array with frequency values that get larger further on in the array items.
Now let's get our response data:
iirFilter.getFrequencyResponse(myFrequencyArray, magResponseOutput, phaseResponseOutput);
We can use this data to draw a filter frequency plot. We'll do so on a 2d canvas context.
// create a canvas element and append it to our dom const canvasContainer = document.querySelector('.filter-graph'); const canvasEl = document.createElement('canvas'); canvasContainer.appendChild(canvasEl); // set 2d context and set dimesions const canvasCtx = canvasEl.getContext('2d'); const width = canvasContainer.offsetWidth; const height = canvasContainer.offsetHeight; canvasEl.width = width; canvasEl.height = height; // set background fill canvasCtx.fillStyle = 'white'; canvasCtx.fillRect(0, 0, width, height); // set up some spacing based on size const spacing = width/16; const fontSize = Math.floor(spacing/1.5); // draw our axis canvasCtx.lineWidth = 2; canvasCtx.strokeStyle = 'grey'; canvasCtx.beginPath(); canvasCtx.moveTo(spacing, spacing); canvasCtx.lineTo(spacing, height-spacing); canvasCtx.lineTo(width-spacing, height-spacing); canvasCtx.stroke(); // axis is gain by frequency -> make labels canvasCtx.font = fontSize+'px sans-serif'; canvasCtx.fillStyle = 'grey'; canvasCtx.fillText('1', spacing-fontSize, spacing+fontSize); canvasCtx.fillText('g', spacing-fontSize, (height-spacing+fontSize)/2); canvasCtx.fillText('0', spacing-fontSize, height-spacing+fontSize); canvasCtx.fillText('Hz', width/2, height-spacing+fontSize); canvasCtx.fillText('20k', width-spacing, height-spacing+fontSize); // loop over our magnitude response data and plot our filter canvasCtx.beginPath(); for(let i = 0; i < magResponseOutput.length; i++) { if (i === 0) { canvasCtx.moveTo(spacing, height-(magResponseOutput[i]*100)-spacing ); } else { canvasCtx.lineTo((width/totalArrayItems)*i, height-(magResponseOutput[i]*100)-spacing ); } } canvasCtx.stroke();
Summary
That's it for our IIRFilter demo. This should have shown you how to use the basics, and helped you to understand what it's useful for and how it works.
© 2005–2021 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Using_IIR_filters