We get a lot of questions about DSP almost every day and filters are a common point of confusion and interest. In this article, I will break down Digital Filters, their functions, how they work, their mathematical descriptions and how they can be principally outlined in code.

There Are Two Kinds Of Filters In This World…

In the digital domain, we can create filters in two main families:

  • Finite Impulse Response – FIR
  • Infinite Impulse Response – IIR

A key question some of you might have is: What is an Impulse Response? Luckily for you, I have answered that question in this other article. Essentially, an impulse response of any system (filter, digital or analog system, room or concert hall) allows us to understand all aspects of that system’s magnitude, frequency, and phase response for the given settings (state) that it was in at the time of measurement (observation).

FIR Filters

FIR Filters have a finite impulse response. That is to say, that the impulse response only goes on for a set number of samples. It will never have more or less samples than that number of samples. The following picture is an example of the impulse response for a hypothetical FIR filter.

As shown in this picture, the filter (for the given settings of cutoff, q and gain) will always yield a linear gain of 1 at sample 1, -0.5 at sample 2 and so on and so forth. We also know that this filter impulse response is 5 samples long. The number of taps in an FIR filter is always N-1 where N is the impulse response length in samples. This means that this FIR filter is 4 taps long. This is equivalent to the following diagram:

This type of diagram above is known as a signal flow diagram (specifically known as direct form). There are a couple of different ways of drawing these diagrams but, the essential point is that we can define a system this way. The diagram shows which way a sample is travelling by the arrows. The diagram shows that linear gain is denoted by multipliers a (or b) and delays of an amount of samples are denoted by z^-t where t is time in samples. In this case every delay is a delay of 1 sample. This is also known as a unit delay. A tap is outlined in red. Just remember that a single tap is the delay with corresponding gain. The diagram uses + to denote the points where signals are mixed (added) together.

Now that we have an understanding of how this system works, let’s try and write this mathematically. The mathematics will help us write better code. 

Let’s assume we have a digital input signal x. In digital audio our signal x is a buffer of nsamples. So we can write this as x[n] which is read as sample at index n of buffer x. This signal will go into our system and produce a corresponding output signal y[n].

We know that we can write this output signal as a sum of a set of delays (shifts of sample n) multiplied by corresponding gains a. Each set of delay and gain is a tap. So for our example filter we have N = 4 taps.

This can also be written as an equivalent summation:

This process is known as a discrete convolution. For an FIR Filter, the values of gain are also known as our filter coefficients. [3]

Let’s write some basic C++ code to demonstrate what this would look like. I am only going to show the basic form of this equation. This is not how I would implement this in a broader program or real-time application. This code is not lock free in any way and is meant to just illustrate the form that our equation takes. All code can be found on my GitHub. I suggest using my code sketchbook to see how the filter responds for a given impulse.

std::vector<float> processFIRFilter(std::vector<float> &inputBuffer, std::vector<double> &coefficientsVector)
{
	/*this function takes our input
	and applies the coefficients via a discrete convolution.*/

	/*create an empty output vector, resize and initialize*/
	std::vector<float> outputBuffer;
	outputBuffer.resize(inputBuffer.size() + coefficientsVector.size() + 1);
	for (size_t i = 0; i < outputBuffer.size(); i++)
	{
		outputBuffer[i] = 0;
	}

	/*discrete convolution*/
	for (size_t i = 0; i < inputBuffer.size(); i++)
	{
		outputBuffer[i] = 0;
		for (size_t j = 0; j < coefficientsVector.size(); j++)
		{
			/*check index is in range*/
			int index = i - j;
			if (index >= 0)
			{
				outputBuffer[i] += inputBuffer[i - j] * coefficientsVector[j];
			}
		}
	}
	return outputBuffer;
}

IIR Filters

IIR filters have an infinite impulse response. This means that the impulse response never becomes exactly 0 but rather approaches it. This is controlled via a feed-back loop with a defined gain a (or feed-forward loop with defined gain b). Imagine if we have a feed-back loop of a 1 sample delay and a gain of 0.5 as portrayed below. [2]

This means that the sample is always halfway towards 0. And the impulse response is as follows.

This signal flow diagram above shows a simple first order low pass filter. We can take an input signal x[n] and delay that signal by 1 sample, apply a gain and add it to the next sample and output it. That is to say that our output y[n] is some combination of our current sample x[n] and the previous output sample y[n-1].

We can write this mathematically as the following difference equation:

The upside is that this process takes less multiply-add operations and is more computationally efficient. This formulation also takes less memory as less states are explicitly defined.

In practice, IIR filters work fairly well. However, we must check for stability. Stability means that the filter does not return y[n] = ∞ or 1 for any given state (set of delay times and gain values).

In fact, this filter it is only stable as long as the gain a < 1. This is known as a stability condition. Every IIR filter, no matter how simple or complicated the arrangement, will have stability conditions. You must remember to define and set these limits and exceptions in code. Larger interconnected networks of IIR filters can be treated as complicated sets of simultaneous equations and therefore you must check for these conditions.

Now that we understand IIR Filters, let’s outline some code. All code can be found on my GitHub. I suggest using my code sketchbook to see how the filter responds for a given impulse.

std::vector<float> processIIRFilter(std::vector<float> &inputBuffer, float &gain)
{
	/*this function takes our input and applies the coefficients via a difference equation.*/

	/*make an output buffer using a vector*/
	std::vector<float> outputBuffer = inputBuffer;
	/*initialize state*/
	float outputPrev = 0;
	/*enforce stability conditions*/
	if (gain >= 1)
	{
		gain = 0.999;
	}
	else if (gain <= -1) {
		gain = -0.999;
	}

	/*loop through samples.
	this implementation would be more efficient if it used iterators.
	but, i think the math is clearer this way.*/
	for (size_t n = 0; n < inputBuffer.size(); n++)
	{
		/*function y[n] = x[n] + (g*y[n-1])*/
		outputBuffer[n] = inputBuffer[n] + (gain*outputPrev);
		/*reset y[n-1]*/
		outputPrev = outputBuffer[n];
	}
	return outputBuffer;
}

FIR vs IIR Filters

People on the Discord also ask why would I use one or the other? The answer is that it depends on your constraints and intended use. Let me attempt to outline a few guidelines:

In short, FIR and IIR filters fit in to different audio processing needs for different purposes. For example, an FIR filter is guaranteed to be stable so I may consider it for adaptive filters. An FIR filter is also useful for applications where linear phase is a requirement. An IIR filter is computationally efficient so it is a prime candidate if my filters are the least important process in my entire signal processing chain. An IIR filter is probably our de-facto formulation if we are working on analog filter emulation in the digital domain due to the feedback component.

Conclusion:

I hope this article helped others understand FIR and IIR filters. Filter design is a key aspect in audio signal processing and serves for creative purposes as well as utilitarian and control purposes. I hope this helps guide your understanding so that you can create and troubleshoot things with greater capability on your next project.

Be good to each other and take it easy…

-Will ☜(゚ヮ゚☜)


Will Fehlhaber is an Acoustics Engineer and Audio Programmer from the UK and Bay Area.


Bibliography/Citations/Resources:

[1] The Scientist and Engineer’s Guide to Digital Signal Processing

[2] EECS 206: IIR Filters IV: Case Study of IIR Filters

[3] FIR Filters

[4] Schaums Outline of Digital Signal Processing, 2nd Edition (Schaum’s Outlines)


Related Posts

Liked it? Take a second to support William Fehlhaber on Patreon!

Shopping cart

Subtotal
Shipping and discount codes are added at checkout.
Checkout