More precise frequency from FFT with EZAudio?

209 Views Asked by At

I've generated a few pure sine tones with Audacity at different frequencies to test with. The issue I'm seeing is that the code is returning the same frequency for two different sine tones that are relatively close in value.

For example: A sine tone generated at 19255Hz will show up from FFT as 19293.750000Hz. So will a sine tone generated at 19330Hz.

The same problem with low and high frequencies ... for example 93hz will show up from FFT as 96.899414hz

Something must be off in the calculations. Buffer size is 4096.

Any assistance in how I can modify the above code to get a more precise FFT frequency reading for pure sine tones is greatly appreciated. Thank you!

//
// Initialize FFT
//
float maximumBufferSizeBytes = self.maximumBufferSize * sizeof(float);
self.info = (EZAudioFFTInfo *)calloc(1, sizeof(EZAudioFFTInfo));
vDSP_Length log2n = log2f(self.maximumBufferSize);
self.info->fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2);
long nOver2 = maximumBufferSizeBytes / 2;
size_t maximumSizePerComponentBytes = nOver2 * sizeof(float);
self.info->complexA.realp = (float *)malloc(maximumSizePerComponentBytes);
self.info->complexA.imagp = (float *)malloc(maximumSizePerComponentBytes);
self.info->outFFTData = (float *)malloc(maximumSizePerComponentBytes);
memset(self.info->outFFTData, 0, maximumSizePerComponentBytes);
self.info->inversedFFTData = (float *)malloc(maximumSizePerComponentBytes);

//
// Calculate real + imaginary components and normalize
//
vDSP_Length log2n = log2f(bufferSize);
long nOver2 = bufferSize / 2;
float mFFTNormFactor = 10.0 / (2 * bufferSize);
vDSP_ctoz((COMPLEX*)buffer, 2, &(self.info->complexA), 1, nOver2);
vDSP_fft_zrip(self.info->fftSetup, &(self.info->complexA), 1, log2n, FFT_FORWARD);
vDSP_vsmul(self.info->complexA.realp, 1, &mFFTNormFactor, self.info->complexA.realp, 1, nOver2);
vDSP_vsmul(self.info->complexA.imagp, 1, &mFFTNormFactor, self.info->complexA.imagp, 1, nOver2);
vDSP_zvmags(&(self.info->complexA), 1, self.info->outFFTData, 1, nOver2);
vDSP_fft_zrip(self.info->fftSetup, &(self.info->complexA), 1, log2n, FFT_INVERSE);
vDSP_ztoc(&(self.info->complexA), 1, (COMPLEX *) self.info->inversedFFTData , 2, nOver2);
self.info->outFFTDataLength = nOver2;

//
// Calculate max freq
//
if (self.sampleRate > 0.0f)
{
    vDSP_maxvi(self.info->outFFTData, 1, &self.info->maxFrequencyMangitude, &self.info->maxFrequencyIndex, nOver2);
   self.info->maxFrequency = [self frequencyAtIndex:self.info->maxFrequencyIndex];

    float nyquistMaxFreq = self.sampleRate / 2.0;
    NSLog(@"FREQ: %f", (((float)self.info->maxFrequencyIndex / (float)self.info->outFFTDataLength) * nyquistMaxFreq));

}

EZAudio code here: https://github.com/syedhali/EZAudio/blob/master/EZAudio/EZAudioFFT.m

1

There are 1 best solutions below

0
On

You are looking at just the frequency of one magnitude FFT result bin, which quantizes frequencies to dF = sample_rate/N. You can use a larger N (longer FFT) to get a smaller quantization. In low noise and interference, zero padding your sample data to allow a longer FFT might work to increase plot resolution.

To get an even better estimate of frequency, you have to estimate between the FFT result bins using something like windowed-Sinc interpolation, plus perhaps successive approximation, to find the spectral frequency peak nearest the maximum magnitude FFT result bin.