1
votes

My question is related to the explanation here by A. Levy: Analyze audio using Fast Fourier Transform

How can I produce a bandpass filter on these complex numbers...

[-636.00000000 +0.00000000e+00j  -47.84161618 -2.80509841e+02j
30.69754505 -1.30624718e+01j -109.94022791 +7.58155488e+00j
-3.18538186 +1.44880882e+01j -120.36687555 +5.45225425e+00j
50.48671763 +1.69504204e+01j   31.56751791 -7.22728042e+01j
-17.96079093 -3.17853727e+01j  -19.25527276 +5.08151876e+00j
18.38143611 -2.60879726e+01j  -27.15617871 -4.39789289e+01j...

... knowing already that I will then convert my complex array back into the time domain using ifft. My band pass should only allow 18500Hz to 19500Hz through (super-sonic, I know. I'm specifically looking for those tones to arrive from a tone generator across the room). My sampling rate it 44100Hz and the number of points in my FFT is 128.

I will then be using the frequency detection code here (which already works on my non-filterd samples) Python frequency detection in order to give me the pitch of the audio tone when it arrives. If there is no tone played at 19000HZ (for example), then the output of the pitch detection function should return 0.

My current filter code (which is not giving me the correct result):

samp = [-10 -16 -15 -11 -8 -10 -9  -12 -7 -13 -4 -10 
        -1  -4  -11 -6 -8 -8 -10 -6 -9 -7 -16 -11 5 -14   
        -9 -3 -9 -7 -7 -6 -3 -11 -13 -9 -10 -4 -6 -7 
        -11 -15 -15 -3 -5 -15 -11 -8 -13 -9 -12 -10 -8 -16  
        -13 -5 -4 -12 -14 -8 -14 -6 -7 -7 -4 -6 -9 -4 -4  
        -4  1 -10 -3 -9 -9  -1 -5 -2 -5 -3 -3  2 -3  2  
        -5 -4 -6  1 -2 -6 -8  -3 -10 -11 -6 -2 -5  -3   
         0  3  0  1  1 -1 -3 -3  1  3 -3 -3  3 -3 -1  
        -3  -1  2  1  0 -8  0  6 -3 -4 -7 -5 -10 -4 -4]

sample_time = 0.000022675   # 1/44100

low_cut = 18500
high_cut = 19500

float_samp = np.float32(samp)
fft_spectrum = np.fft.fft(float_samp)

freqs = np.fft.fftfreq(len(fft_spectrum), d=sample_time)

### wrong approach ###
for i in range(len(freqs)): # (H-red)

    if abs(freqs[i]) >= high or abs(freqs[i]) <= low:
        fft_spectrum[i] = 0.0 
### -------------- ###

time_domain = np.fft.ifft(fft_spectrum)
converted = np.int16(time_domain)

It's currently only returning values between 18500Hz and 19500Hz after pitch detection, whether or not the tone is present. I believe this is the case because I'm knocking out all the information from my fft_spectrum list except those within with my filter values. Even though the tone is not there, the only information left is the within that band, so that's what the pitch detector is reading. This is my assumption.

Please Note: I can not use Scipy as I'm deploying this on Android and that library is not available on that platform. I'm hoping there is a way to do this through Numpy.

1

1 Answers

1
votes

The frequency detection code you linked is performing an FFT and then finding the bin with the largest magnitude. The only way it's going to return a zero is if the largest magnitude is in the 0th bin. Even though you may not be producing a tone at the frequency you are interested in there is most certainly energy there and therefore is a candidate for being the biggest. To do you what you're asking you'll need to modify the referenced code to provide the desired behavior. For example you could apply some kind of minimum threshold.

which = fftData[1:].argmax() + 1
if (fftData[which] < threshold)
    return -1; // no peak found

While you're at it: I don't understand why you are going through the process of bandpass filtering the signal when you could just limit the frequency detection to search the bins of interest:

binMin = floor(low_cut / 22050.0 * 128)
binMax = ceil(high_cut / 22050.0 * 128)
which = fftData[binMin:binMax].argmax() + 1