Audio filter and equalizers ed

Problems with a naive equalizer ed

Task
You want to change the volume of an audio signal, but only within a certain frequency range. All other frequencies should stay untouched.

Naive idea ed

This already screams to be solved via Fast Fourier Transforms (FFT) - the signal (an array of real numbers) is transformed into the frequency domain, i.e. you get a new array of complex numbers, where each entry represents the intensity (and phase information) for a specific frequency inside the original signal.

It is then trivial to scale all entries inside the desired frequency range up or down and then transforming the signal back into its time domain.

Problem ed

Yes, this works perfectly when applied to the whole audio array at once (e.g. the whole 5 min song).

But what if you want a more flexible "live" audio effect. Then the signal comes in smaller chunks, let's say 1024 numbers at a time. If you simply try to apply the naive equalizer to each chunk separately, the result will have stutter, because each chunk still gives a smooth signal, but the end of one chunk might not fit to the start of the next.

There might be fixed using additional "smoothening" afterwards to make chunks fit together. But this further degrades the signal and and increases complexity.

Filter ed

Actually, we can do without expensive FFTs...

Basic idea ed

What happens, when taking the mean of neighbouring signal values? Smoothing! - For a low frequency signal, almost nothing changes. But a high frequency signal looses intensity by self-cancellation.

The most extreme cancellation happens for a sine wave at half the sampling rate, when one value is a high peak, and the next value is a low peak, etc.

Crappy low-pass filter
Instead of the mean, we can also take a weighted ("unfair") mean, i.e. when calling the input array \(x_i\) and the output \(y_i\), instead of\[ y_i = \frac{x_i + x_{i-1}}{2} = \frac{1}{2} x_i + \frac{1}{2} x_{i-1} \]we could define\[ y_i = \alpha x_i + (1 - \alpha) x_{i-1} \]with a mixing parameter \(\alpha \in [0,1]\). For \(\alpha=1\) nothing happens, the output is unchanged from the input. And for \(\alpha=\frac{1}{2}\), the previous smoothing happens.

Slightly better low-pass filter
To make things more fancy, we can reuse the previous output \(y_{i-1}\) value when calculating the new output:\[ y_i = \alpha x_i + (1-\alpha) y_{i-1} \]with \(\alpha = \frac{\omega \, \Delta t}{\omega \, \Delta t + 1}\) for the cut-off frequency \(\omega = 2 \pi f\) and the sampling rate \(\Delta t\).

Note, that this does not perfectly mute all frequencies above \(\omega\). Rather, there is a smooth transition around \(\omega\).

High-pass filter
Similarly, we can define a decent high-pass filter via\[ y_i = \alpha \left( y_{i-1} + x_i - x_{i-1} \right) \]and tuning the cut-off frequency \(f\) with \(\alpha = \frac{1}{\omega \, \Delta t + 1} \).

General filter ed

By linearly mixing inputs and previous outputs, we can define\begin{align}y_i &= \alpha_0 \, x_i + \alpha_1 \, x_{i-1} + \alpha_2 \, x_{i-2} + \dots - \beta_1 \, y_{i-1} - \beta_2 \, y_{i-2} - \dots \\&= \sum_{k=0}^{M} \alpha_k \, x_{i-k} - \sum_{k=1}^{N} \, \beta_k y_{i-k}\end{align}

Categories: Blog, Audio programming