2
$\begingroup$

I am attempting to design a custom filter for equalizing an audio track using the DFT. I am newish at this, but my understanding from the DSP Guide is that you can do this with the knowledge that multiplication in the frequency domain equals convolution in the time domain. To make a custom filter you:

  1. Create an array that describes how you want to multiply the magnitudes of each bin in the frequency domain to achieve the desired response
  2. Create a corresponding phase array, usually all zeros
  3. Take the inverse DFT using the magnitude and phase arrays to get the impulse response
  4. Roll and taper the impulse response to get the filter kernel
  5. Convolve the filter kernel with the audio signal you want to equalize

I followed exactly these steps. Here's a plot of the ideal frequency response I wanted for the filter, and the actual response of the final filter kernel:

enter image description here

Looks good to me so far. However, when I convolve this filter kernel with the audio signal (a kick drum hit, in this case), the equalization sounds correct but it seems to add this extra low frequency whomph sound just before the kick drum. You can clearly see this in the time domain signal:

enter image description here

The top is the original sound, and the bottom is after being convolved with the kernel I made. It's almost like the low frequency part of the kick drum has been reflected around the onset. When I use an equalizer plugin in ProTools, for example, and try to achieve this same filtering by hand, this weird extra sound does not occur, and the audio sounds like I expect. What would be causing this? Are typical multiband eqs like you might find ProTools/Reaper/etc using something other than this kind of custom DFT-base filter design to avoid this effect?

$\endgroup$
6
  • 2
    $\begingroup$ This is not a live, real-time filter, is it? That's because the displayed input/output signals show that this filter is not causal. It has responded to the non-zero part of your input before that non-zero part happened. So then, this must be a process you did to a recorded sound (where the algorithm is allowed to look ahead into the "future" a little). It is likely some kinda "zero-phase" filter, which is a linear phase filter with the constant delay (that is needed for causality) removed. $\endgroup$ Jun 1 at 22:27
  • $\begingroup$ You are correct, this is not real time, I'm just processing the audio in a numpy array, and my filter kernel is rolled so that the peak is in the middle of the kernel. Now that I think about it, this would naturally be pulling energy backward in time and causing this weird effect I'm seeing. Now to figure out what to do about it... $\endgroup$ Jun 2 at 3:56
  • $\begingroup$ well, as Prof. K suggested, maybe go minimum-phase filter instead of linear-phase filter. because your numpy program is thinking it's a zero-phase filter. Zero-phase filters are symmetrical about the $t=0$ axis. There is just as much impulse response before $t=0$ as there is after. $\endgroup$ Jun 2 at 5:28
  • $\begingroup$ @robertbristow-johnson, do you know if I can achieve a linear-phase filter that is causal by setting the phase array in the workflow I outlined here somehow? Right now, I'm definitely explicitly setting the phase to zero everywhere, so I guess I've made a zero-phase filter, which I see cannot be causal. The minimum-phase thing works great (thanks @peter-k!), but it does require an extra step and I'm wondering if its necessary. $\endgroup$ Jun 2 at 19:06
  • $\begingroup$ yes. just make the delay 1/2 of your FIR length. Then both the phase delay and the group delay should be set to this value. That forces phase itself to be linear and the negative of the slope of the phase is the group delay. $\endgroup$ Jun 3 at 0:51

1 Answer 1

2
$\begingroup$

The problem is that the impulse response starts well before its main peak. That means the output of the filter seems to start well before the onset (attack) of the original signal.

One way to mitigate this is to try to find the minimum phase equivalent of your impulse response. Another way to think about minimum phase is that this gives the minimum energy delay.

For example, in the picture below, the blue plot is the "ideal" sinc response of a brickwall filter. The orange response is an attempt to find the minimum phase equivalent impulse response.

Original and minimum phase equivalent response.

The frequency responses of the two filters are quite close, even though this technique isn't great for long filters.

Frequency response of the two filters.

Because the onset (attack) of the minimum phase filter starts much earlier, the effect you're seeing / hearing should be lessened.

More information here.

Code Below

import numpy as np
import matplotlib.pyplot as plt 
import scipy

T = 256
omega = 0.5
t_range = omega*np.arange(-T/2, T/2)

zero_index = np.where(t_range==0)[0][0]

sinc = np.divide(np.sin(list(map(float,t_range))), list(map(float,t_range)))
sinc[zero_index] = 1
sinc_min = scipy.signal.minimum_phase(sinc)
sinc_again = np.convolve(sinc_min, sinc_min)

plt.figure(1)
plt.plot(sinc)
plt.plot(sinc_again)


w, H1 = scipy.signal.freqz(sinc)
w, H2 = scipy.signal.freqz(sinc_again)

plt.figure(2)
plt.plot(w,20*np.log10(np.abs(H1)))
plt.plot(w,20*np.log10(np.abs(H2)))
$\endgroup$
2
  • $\begingroup$ Ok awesome, this seems very promising, but I think I lack the background to fully understand those links. It is intuitive to me that a filter kernel rolled like the DSP Guide suggests so that its peak is in the middle would be drawing signal backward in time. The bit about "inverting zeros outside the unit circle" in your link already has me lost haha. Will do some more reading on this. I suspect its not as simple as just rolling my filter less, so that the peak is closer to the beginning of the kernel... $\endgroup$ Jun 2 at 3:53
  • $\begingroup$ @RobAllsopp The DSP guide has some info on minimum phase filters, if that's more to your liking. Added that link to the answer, too. $\endgroup$
    – Peter K.
    Jun 2 at 12:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.