Okay the FFT and spectrum analyzer code is working tickety-boo now, thanks to libfftw. My own FFT code wasn't as fast as libfftw so I used it instead.
If you decide to use libfftw, there are a couple of things you'll need to keep in mind:
Use a reasonably large sample size, or the FFT will not be very indicative of the spectrum. A sample size of about 5k seems to work fairly well but may be slight overkill. At 44.1kHz a sample size of say 10k yields a much less snappier response and increases the CPU usage a fair bit.
The other is you'll want to use a one-dimensional real to complex DFT on the data since the originating audio data doesn't have an imaginary part (or you can use complex to complex with a null imaginary part as the input but what would be the point?). The last thing is not to scale the input data as long as you keep the input data as a signed type. This is convenient as well since you can use the same signed data you're passing to the hardware (assuming you're doing that). Since libmad uses fixed point integers, I'm using the same scaling to signed short integers for feeding the hardware as I am for the input of the FFT. The only difference is that the data sent to the hardware is interleaved and the input to the FFT is not.
Other than some simple linear scaling of the resulting spectrum, the dB conversion I'm doing on the resultant FFT (real and imaginary) is quite accurate. You can see the same result in Xine's simple spectrum analyzer visualization where they aren't doing any scaling and the data is skewed too high on the low frequencies. The linear scaling I'm doing in the visualization simpy starts fractional, reaches unity at the Niquist frequency, and becomes multiplicative at the high end.
Below are snippets directly from SW2's fft.cpp (the visualization code is in another class obviously):
Code:
/*
This function expects signed data. The fact that I'm using short integers
is just a matter of convenience (or inconvenience as the case may be). The
input array is simply filled with the input data casted to double.
*/
void FFT::Process( signed short *p_siData, int iNewFFTLen, int iChan )
{
int i;
iFFTLen = (iNewFFTLen / 2) + 1;
bInProcess = TRUE;
// We are doing a real to complex transform so we simply scale the real data
for( i = 0; i < iNewFFTLen; i++ )
inSound[i] = (double)p_siData[i];
// Let libfftw3 do the heavy lifting; do a real-to-complex DFT
fftw_execute( plan );
// Do the frequency and amplitude domain transforms (I don't care about phase, at least not right now)
for( i = 0; i <= (iNewFFTLen / 2); i++ )
{
// Frequency spread across the array
dFreq[i] = (double)i * (double)iSampRate / (double)iNewFFTLen;
// Magnitude of the given frequency in dB (RMS of the real and imaginary result)
dMag[iChan][i] = 20.0 * log10( 2.0 * sqrt( (outFFT[i][1] * outFFT[i][1]) + (outFFT[i][0] * outFFT[i][0]) ) / (double)iNewFFTLen );
}
bInProcess = FALSE;
}
/*
Break up the spectrum into specific bands according to a full spectrum of 20Hz~18kHz; the more bars the narrower
each bandwidth will be. The bands are simply an average of the magnitudes within that band.
NOTE: An RMS averaging might yield slightly more "intuitive" results at the expense of more CPU time.
*/
void FFT::Spectrum( double *p_dLBars, double *p_dRBars, int iNumBars )
{
int iSamp, iBar, iBandSamp;
double dBandS = 20.0; // Starts at 20Hz
double dBandW = 17980.0 / (double)iNumBars; // 17980=18kHz-20Hz
// Left
for( iBar = 0; iBar < iNumBars; iBar++ )
{
iBandSamp = 0;
for( iSamp = 0; iSamp < iFFTLen; iSamp++ )
{
if( (dFreq[iSamp] >= dBandS) && (dFreq[iSamp] < dBandS + dBandW) )
{
p_dLBars[iBar] += dMag[0][iSamp];
iBandSamp++;
}
}
p_dLBars[iBar] /= (float)iBandSamp;
dBandS += dBandW;
}
// Right
dBandS = 20.0;
for( iBar = 0; iBar < iNumBars; iBar++ )
{
iBandSamp = 0;
for( iSamp = 0; iSamp < iFFTLen; iSamp++ )
{
if( (dFreq[iSamp] >= dBandS) && (dFreq[iSamp] < dBandS + dBandW) )
{
p_dRBars[iBar] += dMag[1][iSamp];
iBandSamp++;
}
}
p_dRBars[iBar] /= (float)iBandSamp;
dBandS += dBandW;
}
}
Bookmarks