I'm trying to understand how to take the results of an FFT calculation and bin them to one-third octave bands. As my questions aren't about the code but about fundamentally understanding the arguments to and results from FFT, I assume this question fits better in electronics than in stackoverflow.
I start by calculating the center point and lower/upper range of each one-third octave band in Python:
freqs = [10**(0.1 * i) for i in range(12, 43)] # center frequency for 31 bands
fd = 10 ** 0.05 # 1.12201845
freqs_l = [freq / fd for freq in freqs] # lower
freqs_u = [freq * fd for freq in freqs] # upper
The minimum (freqs_l[0] == 14.125375446227551
) and maximum (freqs_u[-1] == 17782.794100389234
) values here give me the overall range of signals I'm interested in. Then I read in samples from my 16-bit, 48kHz WAV file using hound
and run rustfft
's forward-FFT on one second (48000 samples) of data:
const SAMPLE_RATE : usize = 48_000;
const NUM_BANDS : usize = 31; // this is the len(freqs) of the Python freqs above
let mut planner = FFTplanner::new(false);
let fft = planner.plan_fft(SAMPLE_RATE);
let reader = hound::WavReader::open(filename).unwrap();
let mut signal = reader
.samples::<i16>()
.take(SAMPLE_RATE) // just take one second of data for now
.map(|samp| num::complex::Complex::new(samp.unwrap() as f32, 0f32))
.collect::<Vec<_>>();
assert_eq!(signal.len(), SAMPLE_RATE);
// Convert time-domain signal to frequency-domain spectrum
let mut spectrum = signal.clone();
self.fft.process(&mut signal[..], &mut spectrum);
My main question here is around the frequency granuarlity of spectrum
and how I can bucket these values into one-third octave bands. Both signal
and spectrum
are of size 48000, but does this cover the full Nyquist frequency range (0 - 24000 Hz), making each value 0.5Hz in width? For each input, I set an imaginary value of 0 (because all examples I've seen do this), but I have no idea if this affects the output in any important way.
Trying to find info on this, I read this SO question on finding the average FFT value of each frequency band and saw some code that pointed to numpy.fft.fftfreq
that generates FFT frequencies for the sample length and sample spacing. From the former, It looks like I would calculate:
.
If I plug this into NumPy, numpy.fft.fftfreq(n=48000, d=0.14129655308810293)
, I get 48000 values, the first half non-negative (because it's symmetrical); the max value here is 3.538; I was expecting it would be closer to the Nyquist frequency.
I'm sure I'm way off here. How should I understand and adjust the FFT output frequencies?
fftfreq
that you're taking a length-48000 FFT on a signal that's sampled at about 7.077Hz, and it's told you that the bins would then run from 1.4 millihertz to 3.538 hertz, but that's garbage in garbage out. \$\endgroup\$rfft
which is slightly more efficient for real-valued time-domain signals (it doesn't require you to save the imaginary pats of a bunch of complex numbers whose real parts all are zero, and it only outputs the positive part of the spectrum because the negative part is known to be just the c.conjugate of the positive part) \$\endgroup\$