# NAudio real-time equalizer

I have designed 9 IIR bandpass filters (chebyshev type 1, order 4) using fdatool in Matlab. Then I use a and b filter coefficients to apply it on differential equation. So, my question is how to apply (real-time) gain of each bandpass filter using sliders? ( how to add gain or loss to certain filter?)

Here is my code in C#:

public void initializeFilters(){
filters.Clear();
//9 Band pass filters
for (int i = 0; i < 9; i++)

//F1=40hz Fc=50Hz F2=60Hz
float k = (float)Math.Pow(1.0, -5);
filters[0].B = new float[] { k * 0.1992f, 0, k * -0.3983f, 0, k * 0.1992f };
filters[0].A = new float[] { 1.0000f, -3.9968f, 5.9904f, -3.9905f, 0.9969f };

//60Hz 80Hz 100Hz
k = (float)Math.Pow(1.0, -4);
filters[1].B = new float[] { k * 0.0795f, 0, k * -0.1591f, 0, k * 0.0795f };
filters[1].A = new float[] { 1.0000f, -3.9935f, 5.9807f, -3.9810f, 0.9938f };

//100Hz 130Hz 160Hz
filters[2].B = new float[] { k * 0.1787f, 0, k * -0.3574f, 0, k * 0.1787f };
filters[2].A = new float[] { 1.0000f, -3.9899f, 5.9705f, -3.9713f, 0.9907f };

//160Hz 350Hz 600Hz
filters[3].B = new float[] { 0.0009f, 0, -0.0019f, 0, 0.0009f };
filters[3].A = new float[] { 1.0000f, -3.9255f, 5.7847f, -3.7927f, 0.9335f };

//600Hz 1300Hz 2000Hz
filters[4].B = new float[] { 0.0088f, 0, -0.0176f, 0, 0.0088f };
filters[4].A = new float[] { 1.0000f, -3.7187f, 5.2467f, -3.3315f, 0.8040f };

//2000Hz 4000Hz 6000Hz
filters[5].B = new float[] { 0.0595f, 0, -0.1191f, 0, 0.0595f };
filters[5].A = new float[] { 1.0000f, -2.8765f, 3.4528f, -2.0797f, 0.5459f };

//6000Hz 8000Hz 10000Hz
filters[6].B = new float[] { 0.0595f, 0, -0.1191f, 0, 0.0595f };
filters[6].A = new float[] { 1.0000f, -1.4273f, 1.8140f, -1.0319f, 0.5459f };

//10000Hz 12000Hz 14000Hz
filters[7].B = new float[] { 0.0595f, 0, -0.1191f, 0, 0.0595f };
filters[7].A = new float[] { 1.0000f, 0.4731f, 1.3375f, 0.3420f, 0.5459f };

//14000Hz 16000Hz 18000Hz
filters[8].B = new float[] { 0.0595f, 0, -0.1191f, 0, 0.0595f };
filters[8].A = new float[] { 1.0000f, 2.2239f, 2.5782f, 1.6078f, 0.5459f };
}
public override int Read(byte[] buffer, int offset, int count)
{
float[] f_read = new float[read / 4], y = new float[read / 4];
Buffer.BlockCopy(buffer, offset, f_read, 0, count);

for (int n = 0; n < read / 4; n++)
y[n] = 0;

for (int i = 0; i < filters.Count; i++)
{
for (int n = 0; n < read / 4; n++)
{
for (int k = 0; k < filters[i].B.Length; k++)
{
if (n - k >= 0)
y[n] = y[n] + filters[i].B[k] * f_read[n - k];
}
for (int k = 1; k < filters[i].A.Length; k++)
{
if (n - k >= 0)
y[n] = y[n] - filters[i].A[k] * y[n - k];
}
}
}

for (int n = 0; n < read / 4; n++)
y[n]= Math.Min(1, Math.Max(-1, y[n]));

Buffer.BlockCopy(y, 0, buffer, offset, read / 4);
}


Here is GUI of the sliders: (0% - certain filter does not effect the signal, 100% - fully effect the signal) gui sliders

EDIT 1 I have redesigned my bandpass filters, using IIR chebyshev type I, order 2:

    Fc Flow Fhigh [in Hz]
50 35 71  b = [0.0050,0,-0.0050] a= [1.0000,-1.9899,0.9900]
80 57 113 b=[0.0078,0,-0.0078] a=[1.0000,-1.9843,0.9844]
130 92 184 b=[0.0127,0,-0.0127] a=[1.0000,-1.9742,0.9746]
350 247 495 b=[0.0336,0,-0.0336] a=[1.0000,-1.9305,0.9329]
1300 919 1838 b=[0.1141,0,-0.1141] a=[1.0000,-1.7414,0.7717]
4000 2828 5657 b=[0.2865,0,-0.2865] a=[1.0000,-1.1984,0.4270]
8000 5657 11314 b=[0.4559,0,-0.4559] a=[1.0000,-0.4188,0.0882]
12000 8485 16971 b=[ 0.5758,0,-0.5758] a=[1.0000,0.2477,-0.1517]
16000 11314 22040 b=[0.6532,0,-0.6532] a=[1.0000,0.6927,-0.3063]


and here is my new code:

        private void initializeFilters()
{
filters = new List<Filter>();
filters.Add(new Filter(new float[] { 0.0050f, 0f, -0.0050f }, new float[] { 1.0000f, -1.9899f, 0.9900f }));
filters.Add(new Filter(new float[] { 0.0078f, 0f, -0.0078f }, new float[] { 1.0000f, -1.9843f, 0.9844f }));
filters.Add(new Filter(new float[] { 0.0127f, 0f, -0.0127f }, new float[] { 1.0000f, -1.9742f, 0.9746f }));
filters.Add(new Filter(new float[] { 0.0336f, 0f, -0.0336f }, new float[] { 1.0000f, -1.9305f, 0.9329f }));
filters.Add(new Filter(new float[] { 0.1141f, 0f, -0.1141f }, new float[] { 1.0000f, -1.7414f, 0.7717f }));
filters.Add(new Filter(new float[] { 0.2865f, 0f, -0.2865f }, new float[] { 1.0000f, -1.1984f, 0.4270f }));
filters.Add(new Filter(new float[] { 0.4559f, 0f, -0.4559f }, new float[] { 1.0000f, -0.4188f, 0.0882f }));
filters.Add(new Filter(new float[] { 0.5758f, 0f, -0.5758f }, new float[] { 1.0000f, 0.2477f, -0.1517f }));
filters.Add(new Filter(new float[] { 0.6532f, 0f, -0.6532f }, new float[] { 1.0000f, 0.6927f, -0.3063f }));
}
public void process(float[] x, ref float[] y, float value, int i)
{
float f = ((100 * value) / 20)/100; //mapping values to 0.0 - 1.0
float[] tmp = new float[y.Length];
for (int n = 0; n < x.Length; n++)
{
for (int k = 0; k < filters[i].B.Length; k++)
{
if (n - k >= 0)
{
tmp[n] += filters[i].B[k] * x[n - k];
y[n] += f * tmp[n];
}
}
for (int k = 1; k < filters[i].A.Length; k++)
{
if (n - k >= 0)
{
tmp[n] -= filters[i].A[k] * tmp[n - k];
y[n] -= f * tmp[n];
}
}
}
}
public override int Read(byte[] buffer, int offset, int count)
{
float[] f_read = new float[read / 4], y = new float[read / 4];
Buffer.BlockCopy(buffer, offset, f_read, 0, count);

int st = 1;
for (int i = 0; i < Form1.trackBarValue.Length; i++)
{
if (Form1.trackBarValue[i] > 0)
{
process(f_read, ref y, Form1.trackBarValue[i], i);
st++;
}
}
if (st == 1)
process(f_read, ref y, 20, 0); //20 is just a maximum slider value

for (int n = 0; n < read / 4; n++)
y[n] = Math.Min(1, Math.Max(-1, y[n] / st));

Buffer.BlockCopy(y, 0, buffer, offset, read);
}


I'm hearing a little noise in signal(a little bangs), can anybody tell me what am I doing wrong?

Bunch of things:

1. Your band definition is really odd. Some of the lower bands are really small while some of the mid bands are huge. Ideally you want octaves where the band edges are 1/sqrt(2) and sqrt(2) around the center frequency
2. You have designed bandpass filters in parallel. These are not "reconstructive" so you don't end up with a flat frequency response if you add the outputs of the band passes together.
3. You need a "parametric" or "peaking EQ" filters in cascade. These filters are defined by center frequency, gain (or cut) and Q. Q controls the bandwidth and Q=2 should give you something similar to ocatve bandwidth. See http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt .
4. You will have to update the filter coefficients every time the slider is moved. This may results in pops and clicks and there are various ways to deal with this. Post another questions if this is a problem

RE: EDIT 1

1. You are still using parallel bandpasses, so you will never get a perfectly falt frequency response if the all the band gains are "neutral"
2. The slider feels off. Typical graphical equalizers would do -15dB .. +15dB in logarithmic steps. Your slider is linear and it only provides cut
3. You have a "hard clip" to prevent overflow. That will sound pretty bad if overflow actually happens. There are better ways of dealing with that.
4. I think the main problem is your state management (or lack thereof). There is a conditional: $if (n - k >= 0)$. That's wrong, you always need to do the summation over the whole loop. In order to do this, you need to keep the last two input and output samples of the previous data block around. So x[n-1] for block K becomes x[-1] for block K+1. You need to keep track of x[-1], x[-2], y[-1] and y[-2].
• +1. Rather than recompute the filter coefficients, couldn't he just do the gain on the output of each filter? May 30 '13 at 14:31
• No. In order to do this you'd have to have a parallel bandpass filter bank that's perfectly reconstructing. That is quite difficult. The parametrics in cascade approach is a lot easier but the "neutral" filters are simply flat so adding gain just raises the overall gain. The filter coefficient calculation is not complicated. It's spelled out in the cookbook May 30 '13 at 15:54
• I'm gonna have to do it with pre-designed filters. What if I make 9 filtered signals (signal per certain filter) and then sum the signals together with the slider values(x [0.0-1.0] for each signal)? Jun 11 '13 at 18:47
• I just edited first post with my new code. Can anybody tell me why am I getting noise in signal? Jun 17 '13 at 20:08
• thnx, the noise is finally gone! Jun 19 '13 at 14:19

Running 9 fifth order IIR filters in cascade can potentially have noise problems. But if you can do with floating point then maybe it is not a problem. Then there is the potential issue of MIPS, it is not cheap to run 9 fifth order IIR filters pr channel.

The parallel structure provides a feasible alternative to the above two problems. This paper "Low-Delay Signal Processing for Digital Hearing Aids" gives the explanation of designing the filters. In this parallel structure the gains sits directly on the outputs of the biquads.

• You don't need fifth order. A single biquad per stage is typically enough. I would think that the parallel structure is more expensive since you do need higher order filters there. I would not expect a lot of noise problems, since it's all biquads and the stages with low or 0 gain or very close to unity. May 31 '13 at 10:36
• Can you be more specific on what condition(s) require higher order filters in a parallel structure? May 31 '13 at 11:14
• See mathworks.com/help/dsp/examples/… octave band filters typically require 6th order Jun 18 '13 at 16:17
• OK, nice link, thanks. However, the parallel structure described in the paper referenced in my post is different. It does not require 6th order, it does achieve a perfectly flat frequency response if the all the band gains are "neutral" and it very nicely handles the case where there are fewer internal bands than there are gain handles (to save MIPS). Your comments are valid for the plain parallel structure but they don't apply to the parallel structure in the above paper. Jun 20 '13 at 5:37