1

I am currently running Python's Numpy fft on 44100Hz audio samples which gives me a working frequency range of 0Hz - 22050Hz (thanks Nyquist). Once I use fft on those time domain values, I have 128 points in my fft spectrum giving me 172Hz for each frequency bin size.

I would like to tighten the frequency bin to 86Hz and still keep to only 128 fft points, instead of increasing my fft count to 256 through an adjustment on how I'm creating my samples.

The question I have is whether or not this is theoretically possible. My thought would be to run fft on any Hz values between 0Hz to 11025Hz only. I don't care about anything above that anyway. This would cut my working spectrum in half and put my frequency bins at 86Hz and while keeping to my 128 spectrum bins. Perhaps this can be accomplished via a window function in the time domain?

Currently the code I'm using to create my samples and then convert to fft is:

import numpy as np

sample_rate = 44100
chunk = 128
record_seconds = 2

stream = self.audio.open(format=pyaudio.paInt16, channels=1,
                        rate=sample_rate, input=True, frames_per_buffer=6300)

sample_list = []

for i in range(0, int(sample_rate / chunk * record_seconds)):
    data = stream.read(chunk)
    sample_list.append(np.fromstring(data, dtype=np.int16))

### then later ###:

for samp in sample_list:
        samp_fft = np.fft.fft(samp) ...

I hope I worded this clearly enough. Let me know if I need to adjust my explanation or terminology.

3 Answers3

2

What you are asking for is not possible. As you mentioned in a comment you require a short time window. I assume this is because you're trying to detect when a signal arrives at a certain frequency (as I've answered your earlier question on the subject) and you want the detection to be time sensitive. However, it seems your bin size is too large for your requirements.

There are only two ways to decrease the bin size. 1) Increase the length of the FFT. Unfortunately this also means that it will take longer to acquire the data. 2) lower the sample rate (either by sample rate conversion or at the hardware level) but since the samples arrive slower it will also take longer to acquire the data.

I'm going to suggest to you a 3rd option (from what I've gleaned from this and your other questions is possibly a better solution) which is: Perform the frequency detection in the time domain. What this would require is a time-domain bandpass filter followed by an RMS meter. Implementation wise this would be one or more biquad filters that you could implement in python for the filter - there are probably implementations already available. The tricky part would be designing the filter but I'd be happy to help you in chat. The RMS meter is basically taking the square root of the sum of the squares of the output samples from the filter.

jaket
  • 9,140
  • 2
  • 25
  • 44
  • 1
    That's a starting point for designing a filter on the fly in python. If you're bandpass center frequency and sample rate are going to be constant then that code is not necessary. However it might be insightful. If you look at the code branch for BPF you'll see it ultimately produces a set of coefficients called a and b given your other input parameters. These coefficients would then be used in a realization. I can't find a python example but look at the process function in this c++ code and you'll get an idea http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ – jaket May 06 '15 at 08:25
  • @A.Wylie-DigiCoyote what's your question? – jaket Jul 07 '16 at 02:43
  • I just put a link to my LinkedIn on my profile so you can contact me there too. – jaket Jul 07 '16 at 06:45
1

Doubling the size of the FFT would be the obvious thing to do, but if there is a good reason why you can't do this then consider 2x downsampling prior to the FFT to get the effective sample rate down to 22050 Hz:

- Apply low pass filter with cut off at 11 kHz
- Discard every other sample from filtered output
- Apply FFT to down-sampled data
Paul R
  • 208,748
  • 37
  • 389
  • 560
  • I've just added my code that I use to take the incoming audio from sample creation through to fft conversion. Can you show me where to place your three steps? I'm not sure if you're using python, but I don't have access to the Scipy library for this project. I am not able to double my FFT, as the time window of my sample needs to be as tight as possible (128 chunk is fine). Also, I'm assuming that if I would like all the frequencies between 11025 to 22050, then I just apply a high-pass filter starting at 11024Hz, correct? – A. Wylie - DigiCoyote May 06 '15 at 06:44
  • Ah - you're out of luck then - you can't get information out of thin air - there's a trade off between sample window duration and frequency resolution (see Heisenberg). Down-sampling (or anything else) isn't going to help you - your only option is to increase your sample window size. – Paul R May 06 '15 at 06:49
  • Hmm, figured that might have been the case. I was hoping that I could just put a window on my sample collection to focus on collecting samples between a certain range. Thanks for the info, very much appreciated. – A. Wylie - DigiCoyote May 06 '15 at 07:45
  • 1
    No problem - for future reference, questions about DSP *theory* (as opposed to actual programming matters) will generally get a better response on http://dsp.stackexchange.com. – Paul R May 06 '15 at 07:49
0

If you are not trying to resolve between close adjacent frequency peaks or noise, then, to half the frequency bin spacing, you can zero-pad your data to double the FFT length, without having to wait for more data. Then, if you only want the lower half of the frequency range 0..Fs/2, just throw away the middle half of the FFT result vector (which is usually far more efficient than trying to compute the lower half of the frequency range via non-FFT means).

Note that zero-padding gives the same result as high-quality interpolation (as in smoothing a plot of the original FFT result points). It does not increase peak separation resolution, but might make it easier to pick out more precise peak locations in the plot if the noise level is low enough.

hotpaw2
  • 70,107
  • 14
  • 90
  • 153
  • And if I want just the upper half instead? Fs/2 to 22050? – A. Wylie - DigiCoyote May 06 '15 at 18:06
  • Do you mean Fs/4 to Fs/2 ? (11025 to 22050) That's the upper half of positive frequencies when using baseband sampling at 44.1ksps. Then just keep the middle (bins N/4 to 3N/4) of the FFT result. Of for strictly real input, half that, as the rest are just a complex conjugate mirror. – hotpaw2 May 06 '15 at 19:49