I have introduced the difference between FIR and IIR filters in a previous article and if you are new to filter design then I implore you to read my other DSP articles. In other DSP articles, I have shown you the Z-Transform and how it can be used as a tool to analyze the characteristics of a system. In this article, I will show you how we can create a custom FIR filter via a technique called Windowing. As with all of my articles, all images and relevant code can be found on my GitHub!
Using the Windowing Method to Design an FIR Filter
The windowing approach to FIR filter design is as follows:
- Specify a frequency response for a system
- Use a Fourier Transform to obtain a corresponding system impulse response (If you know the system impulse response just skip to step 2…)
- Multiply the impulse response by a windowing function to achieve your filter coefficients
This is a straight-forward process and therefore one of the most common methods of FIR filter design.  For the rest of this article, we will focus on using this method to create an FIR filter based off of an ideal system. We will start with a basic low-pass filter.
Step 1: Specify a frequency response for a system (The Ideal Filter)
A filter, such as a low-pass, has a few components:
- a pass band, the frequencies we allow
- a stop band, the frequencies we do not allow
- a transition band, the bandwidth as we go from pass band to stop band
In an ideal system, a pass band has a linear gain of 1 while the stop band has a linear gain of 0. Furthermore, an ideal system has a transition band bandwidth of 0 Hz. Such a system is not possible for causal system. Perhaps we can examine why in the future. It should suffice (for now) to say that these are the guidelines we use when designing a low-pass filter. If you want some code that helps with this process feel free to explore my Python script here.
Step 2: Get the Impulse Response of Your Ideal System
By taking the Fourier Transform of the frequency domain response, I can get the time-domain response (aka impulse response) of the system. The impulse response for an ideal low-pass filter is shown below. Please note that the lower image is just a zoomed in view.As the ideal frequency response is non-causal, the impulse response has a non-casual portion. This image only shows the positive time portion. Note that the time-domain response extends of towards infinity (those ripples can’t stop won’t stop). The function above is also known as a sinc function (or at least half of one). Also, it looks a lot like a Kronecker Delta Function for continuous systems as introduced in my earlier article on impulse responses. 
The Fourier Transform has given us a 24000 sample impulse response (instead of infinity). For the purposes of computation and practicality, we need to limit the impulse response. Therefore, we will truncate the impulse response to 2048 samples (for now). This gives us the following impulse response and frequency response.
As you can see, we now have some slight deviation from our ideal response as we do not have an impulse response of infinite length and precision. The deviations manifest as ripples in the pass band, stop band and a transition bandwidth that is greater than 0 Hz.
The following video of mine shows what happens when we truncate this impulse response starting from 512 samples down to 16 samples.
As you can see, we get farther and farther from the ideal filter response when we use less coefficients. Furthermore, it it is not normally computationally feasible to use 128+ coefficients. How can we fix this?
3) Apply a Windowing Function
Recall from my previous article, the coefficients of our FIR filter are the values of the impulse response. If we use a filter of 4096 coefficients in length, the computation (a discrete convolution) will be too taxing for many processors. It is technically possible to design a chip that can do that very efficiently but this is not common. Therefore, let us assume we keep a 64 point impulse response for 64 coefficients.  
The frequency response of our impulse response shows us that:
- The Pass Band varies from about 0.6 to 1.09 due to the ripple.
- The Stop Band varies from about -0.8 to 0.12 due to the ripple. Also, that stop band is no longer 0.
- The Transition Band bandwidth is about 370 Hz.
Depending on your purposes, this may or may not be acceptable for you. It really depends on what this filter is designed for. At this point, you have two choices:
- Increase the impulse response length (if computational resources allow)
- Use a windowing function. Applying a windowing function will allow you to decrease the magnitude of the ripples in the pass band and stop band at the expense of decreasing the rate of transition between the two.
As with many things in engineering, design trade-offs ensure your choice is always wrong. But, I first want to introduce what a windowing function is.
What is a Windowing Function?
Broadly speaking, a windowing function is a function that allows one to look at a range of values and exclude what is outside of that range. These can be applied in the time domain (which is what we are doing) or in the frequency domain.
In the time domain, you can think of windowing as a sort of amplitude envelope. A windowing function of length n samples is multiplied to an impulse response of length n samples. Anything outside of that range of values is 0, anything within that range of values is not 0. 
Most windowing functions gradually reduce the range to 0 in some manner. The only exception is the rectangular window which is simply a hard truncation as he have done. In a rectangular window (also called boxcar), either the sample is within the window (multiply by 1 or outside the window (multiply by 0).
There are important implications when choosing a windowing function but the important thing to know is that they all have design trade-offs. I think in a future article, I will have to talk more about windowing itself and the associated mathematics. In this article, it is important to have a brief look at them.
Some Example Windowing Functions
Here are some example windowing functions with comparisons of the shapes in the time domain which have direct implications in the frequency domain. Notice how there is a design trade-off between the width of the main lobe and the side lobes widths as well as side-lobe magnitudes in the frequency domain. This behavior is determined by the rate of the transition between 1 (inclusion) and 0 (exclusion).
The Effect of Different Windows on The Filter Response
By applying a Windowing Function to the impulse response, we can get slightly different candidates for a low-pass filter. You can apply a windowing function by multiplying the values of the window to your impulse response.  The image below is large just so that you can thoroughly compare the filters yourself. If you want some code that helps with this process feel free to explore my Python script here.
boxcar [ 8.33333333e-02 8.23853705e-02 7.95802622e-02 7.50324607e-02 6.89265270e-02 6.15081870e-02 5.30724783e-02 4.39496137e-02 3.44893028e-02 2.50443484e-02 1.59543676e-02 7.53047011e-03 4.16666667e-05 -6.29632328e-03 -1.13293315e-02 -1.49697024e-02 -1.71977717e-02 -1.80598695e-02 -1.76630411e-02 -1.61668525e-02 -1.37727978e-02 -1.07119427e-02 -7.23151946e-03 -3.58122919e-03 0.00000000e+00 3.29609337e-03 6.12412919e-03 8.34235545e-03 9.85556449e-03 1.06176265e-02 1.06311492e-02 9.94439694e-03 8.64575140e-03 6.85613227e-03 4.71989554e-03 2.39480012e-03 4.16666667e-05 -2.18565131e-03 -4.14940381e-03 -5.73568118e-03 -6.86034545e-03 -7.47287221e-03 -7.55795439e-03 -7.13485938e-03 -6.25466710e-03 -4.99564091e-03 -3.45708848e-03 -1.75214972e-03 0.00000000e+00 1.68202372e-03 3.18587864e-03 4.41940083e-03 5.31163558e-03 5.81662064e-03 5.91543631e-03 5.61645107e-03 4.95380738e-03 3.98430218e-03 2.78291309e-03 1.43730003e-03 4.16666667e-05 -1.30960425e-03 -2.52812559e-03 -3.53709882e-03] triangle [ 8.26822917e-02 8.04544634e-02 7.64716582e-02 7.09291230e-02 6.40801306e-02 5.62223272e-02 4.76823047e-02 3.87992684e-02 2.99086923e-02 2.13268279e-02 1.33368541e-02 6.17733876e-03 3.35286458e-05 -4.96819259e-03 -8.76252984e-03 -1.13442276e-02 -1.27639712e-02 -1.31216239e-02 -1.25573182e-02 -1.12410146e-02 -9.36119854e-03 -7.11339944e-03 -4.68918840e-03 -2.26624660e-03 0.00000000e+00 1.98280617e-03 3.58835695e-03 4.75774959e-03 5.46675843e-03 5.72356427e-03 5.56474218e-03 5.04988907e-03 4.25533076e-03 3.26737554e-03 2.17557685e-03 1.06643443e-03 1.79036458e-05 -9.04996247e-04 -1.65327808e-03 -2.19569045e-03 -2.51903309e-03 -2.62718164e-03 -2.53900030e-03 -2.28538465e-03 -1.90571888e-03 -1.44405245e-03 -9.45297630e-04 -4.51726099e-04 0.00000000e+00 3.81083500e-04 6.72021275e-04 8.63164224e-04 9.54434518e-04 9.54289324e-04 8.78072577e-04 7.45934907e-04 5.80524302e-04 4.04655690e-04 2.39156594e-04 1.01060158e-04 2.27864583e-06 -5.11564159e-05 -5.92529436e-05 -2.76335845e-05] cosine [ 8.33270585e-02 8.23295450e-02 7.94305012e-02 7.47557869e-02 6.85065556e-02 6.09486246e-02 5.23985331e-02 4.32071064e-02 3.37414840e-02 2.43666471e-02 1.54274980e-02 7.23249332e-03 3.97210850e-05 -5.95384942e-03 -1.06194244e-02 -1.38994597e-02 -1.58067102e-02 -1.64194551e-02 -1.58733240e-02 -1.43503388e-02 -1.20659363e-02 -9.25482772e-03 -6.15659164e-03 -3.00187478e-03 0.00000000e+00 2.67134260e-03 4.87380802e-03 6.51318747e-03 7.54115533e-03 7.95405041e-03 7.78895690e-03 7.11751143e-03 6.03798743e-03 4.66629046e-03 3.12653327e-03 1.54184786e-03 2.60358120e-05 -1.32343600e-03 -2.43071186e-03 -3.24485730e-03 -3.74111780e-03 -3.92019166e-03 -3.80572014e-03 -3.44031341e-03 -2.88051632e-03 -2.19116922e-03 -1.43963383e-03 -6.90333041e-04 0.00000000e+00 5.86048485e-04 1.03639911e-03 1.33468534e-03 1.47939509e-03 1.48245686e-03 1.36680952e-03 1.16323091e-03 9.06744348e-04 6.32938846e-04 3.74526416e-04 1.58422382e-04 3.57488801e-06 -8.03058966e-05 -9.30532823e-05 -4.34056436e-05] blackman-harris [ 8.33037996e-02 8.21229326e-02 7.88779180e-02 7.37395839e-02 6.69734088e-02 5.89213996e-02 4.99791471e-02 4.05698880e-02 3.11176032e-02 2.20211990e-02 1.36316579e-02 6.23373331e-03 3.33181507e-05 -4.84889506e-03 -8.37739256e-03 -1.05959058e-02 -1.16164672e-02 -1.16046988e-02 -1.07627910e-02 -9.31169559e-03 -7.47398756e-03 -5.45865998e-03 -3.44882801e-03 -1.59297436e-03 0.00000000e+00 1.26200877e-03 2.16369267e-03 2.70964332e-03 2.93172162e-03 2.88131447e-03 2.62134894e-03 2.21878569e-03 1.73816169e-03 1.23656858e-03 7.60259525e-04 3.42894369e-04 5.27762657e-06 -2.43672212e-04 -4.05054955e-04 -4.87581336e-04 -5.04970016e-04 -4.73445815e-04 -4.09566862e-04 -3.28528348e-04 -2.43010328e-04 -1.62565281e-04 -9.34842114e-05 -3.90415758e-05 0.00000000e+00 2.47459789e-05 3.74952886e-05 4.11204895e-05 3.85607640e-05 3.24574309e-05 2.49363304e-05 1.75216222e-05 1.11526310e-05 6.26955790e-06 2.93435172e-06 9.58309303e-07 1.61793017e-08 -2.64263011e-07 -2.39738272e-07 -2.12225929e-07] hamming_window [ 8.33216055e-02 8.22810636e-02 7.93006142e-02 7.45163058e-02 6.81440042e-02 6.04671717e-02 5.18209799e-02 4.25737699e-02 3.31070333e-02 2.37951610e-02 1.49862015e-02 6.98477395e-03 3.81168716e-05 -5.67398029e-03 -1.00448877e-02 -1.30423146e-02 -1.47050842e-02 -1.51359159e-02 -1.44908694e-02 -1.29662954e-02 -1.07842641e-02 -8.17749015e-03 -5.37475565e-03 -2.58773745e-03 0.00000000e+00 2.24128308e-03 4.03053788e-03 5.30582446e-03 6.04781975e-03 6.27607690e-03 6.04305739e-03 5.42655154e-03 4.52116964e-03 3.42959676e-03 2.25426183e-03 1.08998433e-03 1.80371570e-05 -8.98082773e-04 -1.61504309e-03 -2.11024570e-03 -2.38073942e-03 -2.44075186e-03 -2.31819862e-03 -2.05057252e-03 -1.68062299e-03 -1.25220750e-03 -8.06639316e-04 -3.79776049e-04 0.00000000e+00 3.12855560e-04 5.47671872e-04 7.01418271e-04 7.77909569e-04 7.86159178e-04 7.38551695e-04 6.49046998e-04 5.31596044e-04 3.98901557e-04 2.61600683e-04 1.27888162e-04 3.54405733e-06 -1.07714971e-04 -2.03672999e-04 -2.82967906e-04]
There you have it! You now have the coefficients for your windowed FIR filter with a cutoff of 1000 Hz at a sampling rate of 48000 Hz! Oddly specific but useful. Of course, you can choose to do this with any arbitrary frequency response you would like.
I hope this article has clearly outlined the process of creating a custom FIR filter by the Windowing Technique. You have learned the windowing method works and how to apply it. You have also been introduced to windows with just enough depth to use them for practical purposes. If you are a user of the JUCE framework, then I hope the Window method in the Filter Design Class makes more sense. If you have liked this article, please share it with others that may find it useful. Until next time:
Be good to each other and take it easy…