Click here to Skip to main content
15,900,907 members
Articles / Desktop Programming / MFC
Article

FFT of waveIn audio signals

Rate me:
Please Sign up or sign in to vote.
4.58/5 (82 votes)
3 Apr 2007CPOL2 min read 681K   21.7K   267   179
An article on using the Fast Fourier Transform on audio signals.

Sample Image - waveInFFT.jpg

Sample Image - waveInFFT_microeq.jpg

Sample Image - waveInFFT_Oscilliscope.jpg

Sample Image - waveInFFT_peak.jpg

Sample Image - waveInFFT_peakalt.jpg

Sample Image - waveInFFT_PixelGram.jpg

Sample Image - waveInFFT_spectrum.jpg

Introduction

The Fast Fourier Transform (FFT) allows users to view the spectrum content of an audio signal. The FFT code presented here was written by Don Cross, his homepage appears to have subsequently been taken down. Rather than explain the mathematical theory of the FFT, I will attempt to explain its usefulness as it relates to audio signals.

The FFT allows users to obtain the spectral makeup of an audio signal, obtain the decibels of its various frequencies, or obtain the intensity of its various frequencies. Spectral viewers (shown in the image above), Equalizers, or VU-Meters may all use the FFT in order to display their results. The difference between them then depends upon one of a couple of equations that take the real and imaginary components of the FFT, and return either the intensity or decibel levels to be used in the graphed result. The following code takes both the real and imaginary components of the FFT result, and returns the intensity and decibels.

inline double GetFrequencyIntensity(double re, double im)
{
    return sqrt((re*re)+(im*im));
}
#define mag_sqrd(re,im) (re*re+im*im)
#define Decibels(re,im) ((re == 0 && im == 0) ? (0) : 
        10.0 * log10(double(mag_sqrd(re,im))))
#define Amplitude(re,im,len) (GetFrequencyIntensity(re,im)/(len))
#define AmplitudeScaled(re,im,len,scale) ((int)Amplitude(re,im,len)%scale)

The FFT uses the audio signal as its real component, and uses a NULL pointer for its imaginary component indicating that the imaginary data does not exist. Upon its return, the FFT will return both the real and imaginary data components based upon the data given as the real component. The is mirrored with the return samples so that 0-FFT_LEN/2 contains the data, and FFT_LEN/2 to FFT_LEN contains a reverse of the data. This mistake was corrected in my code. The code that performs the FFT follows:

DWORD nCount = 0;
for (DWORD dw = 0; dw < FFT_LEN; dw++)
{
    {
        //copy audio signal to fft real component for left channel
        finleft[nCount] = (double)((short*)pwh->lpData)[dw++];
        //copy audio signal to fft real component for right channel
        finright[nCount++] = (double)((short*)pwh->lpData)[dw];
    }
}
//Perform FFT on left channel
fft_double(FFT_LEN/2,0,finleft,NULL,fout,foutimg);
float re,im,fmax=-99999.9f,fmin=99999.9f;
for(int i=1;i < FFT_LEN/4-1;i++)
//Use FFT_LEN/4 since the data is mirrored within the array.
{
    re = fout[i];
    im = foutimg[i];
    //get amplitude and scale to 0..256 range
    fdraw[i]=AmplitudeScaled(re,im,FFT_LEN/2,256);
    if (fdraw[i] > fmax)
    {
        fmax = fdraw[i];
    }
    if (fdraw[i] < fmin)
    {
        fmin = fdraw[i];
    }
}
//Use this to send the average band amplitude to something
int nAvg, nBars=16, nCur = 0;
for(int i=1;i < FFT_LEN/4;i++)
{
    nAvg = 0;
    for (int n=0; n < nBars; n++)
    {
        nAvg += (int)fdraw[i];
    }
    nAvg /= nBars;
    //Send data here to something,
    //nothing to send it to so we print it.
    TRACE("Average for Bar#%d is %d\n",nCur++,nAvg);
    i+=nBars-1;
}
DataHolder* pDataHolder = (DataHolder*)lpData;
// Draw left channel
CFrequencyGraph* pPeak = (CFrequencyGraph*)pDataHolder->pData;
if (::IsWindow(pPeak->GetSafeHwnd()))
{
    pPeak->SetYRange(0,256);
    pPeak->Update(FFT_LEN/4,fdraw);
}

// Perform FFT on right channel
fmax=-99999.9f,fmin=99999.9f;
fft_double(FFT_LEN/2,0,finright,NULL,fout,foutimg);
fdraw[0] = fdraw[FFT_LEN/4] = 0;
for(i=1;i < FFT_LEN/4-1;i++)
//Use FFT_LEN/4 since the data is mirrored within the array.
{
    re = fout[i];
    im = foutimg[i];
    //get Decibels in 0-110 range
    fdraw[i] = Decibels(re,im);
    if (fdraw[i] > fmax)
    {
        fmax = fdraw[i];
    }
    if (fdraw[i] < fmin)
    {
        fmin = fdraw[i];
    }
}
//Draw right channel
CFrequencyGraph* pPeak2 = (CFrequencyGraph*)pDataHolder->pData2;
if (::IsWindow(pPeak2->GetSafeHwnd()))
{
    pPeak2->SetNumberOfSteps(50);
    //Use updated dynamic range for scaling
    pPeak2->SetYRange((int)fmin,(int)fmax);
    pPeak2->Update(FFT_LEN/4,fdraw);
}

This code is contained in a callback function that is called every time the waveIn functions return with updated audio signal data. The code that actually performs the FFT looks like:

void fft_double (unsigned int p_nSamples, bool p_bInverseTransform, 
    double *p_lpRealIn, double *p_lpImagIn, 
    double *p_lpRealOut, double *p_lpImagOut)
{

    if(!p_lpRealIn || !p_lpRealOut || !p_lpImagOut) return;


    unsigned int NumBits;
    unsigned int i, j, k, n;
    unsigned int BlockSize, BlockEnd;

    double angle_numerator = 2.0 * PI;
    double tr, ti;

    if( !IsPowerOfTwo(p_nSamples) )
    {
        return;
    }

    if( p_bInverseTransform ) angle_numerator = -angle_numerator;

    NumBits = NumberOfBitsNeeded ( p_nSamples );


    for( i=0; i < p_nSamples; i++ )
    {
        j = ReverseBits ( i, NumBits );
        p_lpRealOut[j] = p_lpRealIn[i];
        p_lpImagOut[j] = (p_lpImagIn == NULL) ? 0.0 : p_lpImagIn[i];
    }


    BlockEnd = 1;
    for( BlockSize = 2; BlockSize <= p_nSamples; BlockSize <<= 1 )
    {
        double delta_angle = angle_numerator / (double)BlockSize;
        double sm2 = sin ( -2 * delta_angle );
        double sm1 = sin ( -delta_angle );
        double cm2 = cos ( -2 * delta_angle );
        double cm1 = cos ( -delta_angle );
        double w = 2 * cm1;
        double ar[3], ai[3];

        for( i=0; i < p_nSamples; i += BlockSize )
        {

            ar[2] = cm2;
            ar[1] = cm1;

            ai[2] = sm2;
            ai[1] = sm1;

            for ( j=i, n=0; n < BlockEnd; j++, n++ )
            {

                ar[0] = w*ar[1] - ar[2];
                ar[2] = ar[1];
                ar[1] = ar[0];

                ai[0] = w*ai[1] - ai[2];
                ai[2] = ai[1];
                ai[1] = ai[0];

                k = j + BlockEnd;
                tr = ar[0]*p_lpRealOut[k] - ai[0]*p_lpImagOut[k];
                ti = ar[0]*p_lpImagOut[k] + ai[0]*p_lpRealOut[k];

                p_lpRealOut[k] = p_lpRealOut[j] - tr;
                p_lpImagOut[k] = p_lpImagOut[j] - ti;

                p_lpRealOut[j] += tr;
                p_lpImagOut[j] += ti;

            }
        }

        BlockEnd = BlockSize;

    }


    if( p_bInverseTransform )
    {
        double denom = (double)p_nSamples;

        for ( i=0; i < p_nSamples; i++ )
        {
            p_lpRealOut[i] /= denom;
            p_lpImagOut[i] /= denom;
        }
    }

}

And it requires the following supporting functions:

///////////////////////////////////////////////////////////
// check is a number is a power of 2
///////////////////////////////////////////////////////////

bool IsPowerOfTwo( unsigned int p_nX )
{

    if( p_nX < 2 ) return false;

    if( p_nX & (p_nX-1) ) return false;

    return true;

}


///////////////////////////////////////////////////////////
// return needed bits for fft
///////////////////////////////////////////////////////////

unsigned int NumberOfBitsNeeded( unsigned int p_nSamples )
{

    int i;

    if( p_nSamples < 2 )
    {
        return 0;
    }

    for ( i=0; ; i++ )
    {
        if( p_nSamples & (1 << i) ) return i;
    }

}



///////////////////////////////////////////////////////////
// ?
///////////////////////////////////////////////////////////

unsigned int ReverseBits(unsigned int p_nIndex, unsigned int p_nBits)
{

    unsigned int i, rev;

    for(i=rev=0; i < p_nBits; i++)
    {
        rev = (rev << 1) | (p_nIndex & 1);
        p_nIndex >>= 1;
    }

    return rev;
}



///////////////////////////////////////////////////////////
// return a frequency from the basefreq and num of samples
///////////////////////////////////////////////////////////

double Index_to_frequency(unsigned int p_nBaseFreq, 
    unsigned int p_nSamples, unsigned int p_nIndex)
{

    if(p_nIndex >= p_nSamples)
    {
        return 0.0;
    }
    else if(p_nIndex <= p_nSamples/2)
    {
        return ( (double)p_nIndex / 
                 (double)p_nSamples * p_nBaseFreq );
    }
    else
    {
        return ( -(double)(p_nSamples-p_nIndex) / 
                  (double)p_nSamples * p_nBaseFreq );
    }

}

The included sample class, CFrequencyGraph, will draw a sample EQ, peak meter, and spectral graph using the frequency intensity. Hopefully, this serves as a decent introduction to the uses and basics of the Fast Fourier Transform for audio signals. Other functions of the FFT include using it in combination with a Beat Detection Algorithm to detect beats in an audio signal. Another page with useful FFT information is located at FFT Spectrum Analyser.

This article uses waveIn* functions to retrieve the soundcard's data from the playing source. Therefore, you must manually open your soundcard properties and change the recording source to use mono/stereo mix or waveIn as the selected recording line. Also, if you set the playback wave option to false, this will override the recording options and no sound will appear. For information about doing an inverse FFT for transforming frequencies into an audio signal, please do a Google search on "Inverse FFT" or "IFFT".

History

  • Version 1.3: Fixed Spectrum drawing, Changed Pixelgram color, changed Process routine to match new Recording parameters.
  • Version 1.2: Fixed many drawing bugs, changed to use amplitude scaled and decibels.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
Programming using MFC and ATL for almost 12 years now. Currently studying Operating System implementation as well as Image processing. Previously worked on DSP and the use of FFT for audio application. Programmed using ADO, ODBC, ATL, COM, MFC for shell interfacing, databasing tasks, Internet items, and customization programs.

Comments and Discussions

 
AnswerRe: bars move w/o audio Pin
AnalogKid16-Mar-06 12:28
AnalogKid16-Mar-06 12:28 
GeneralHelp needed for speech capture project! Pin
student_eng24-Oct-05 14:54
student_eng24-Oct-05 14:54 
QuestionWaveOUT? Pin
Anonymous24-Jul-05 14:16
Anonymous24-Jul-05 14:16 
AnswerRe: WaveOUT? Pin
Anonymous10-Aug-05 4:53
Anonymous10-Aug-05 4:53 
GeneralRe: WaveOUT? Pin
Member 1456594-Sep-05 18:01
Member 1456594-Sep-05 18:01 
AnswerRe: WaveOUT? Pin
Member 14877688-Sep-05 17:22
Member 14877688-Sep-05 17:22 
QuestionHow to reverse FTT ? Pin
piotreq15-Apr-05 6:20
piotreq15-Apr-05 6:20 
AnswerRe: How to reverse FTT ? Pin
Fred Ackers29-Mar-06 3:17
Fred Ackers29-Mar-06 3:17 
piotreq wrote:
When you do this :

fft_double(FFT_LEN,0,finBefore,NULL,fout,foutimg);

you get real and imaginary part of signal in fout[] and foutimg[].
Should the reversal look like this ?

fft_double(FFT_LEN/2,1,fout,foutimg,finAfter,NULL);

Will the reverse FFT return an imaginary part?


Its been a long time since I looked at this code so forgive me if my memory is a bit rusty. Confused | :confused: Your code:
fft_double(FFT_LEN,0,finBefore,NULL,fout,foutimg);
will take FFT_LEN samples from fin, and return exactly that many samples of real data in fout, and exactly the same amount in foutimg. Therefore if you want to do the inverse fft, sometimes shortened to IFFT Then you need to pass the fft the real and imaginary parts of the signal, along with the number of samples in each. Then you will get a real part result with the original signal. The imaginary portion of the output will simply be zero so you can pass null for it. So instead of:
fft_double(FFT_LEN/2,1,fout,foutimg,finAfter,NULL);
It should be:
fft_double(FFT_LEN,1,fout,foutimg,finAfter,NULL);
And if the code is working correctly, finAfter should equal finBefore...

Hope that helps... sorry for the late reply...

Nothing is impossible, It's merely a matter of finding an answer to the question of HOW? ... And answering that question is usually the most difficult part of the job!!!
GeneralSound data Pin
mmc1822-Mar-05 22:39
mmc1822-Mar-05 22:39 
Generaldirectshow spectrum analyser Pin
7-Mar-05 10:19
suss7-Mar-05 10:19 
Generalfft Pin
Syukur21-Feb-05 9:55
Syukur21-Feb-05 9:55 
GeneralSame does not do anything Pin
AghaKhan27-Jan-05 4:13
AghaKhan27-Jan-05 4:13 
GeneralRe: Same does not do anything Pin
Fred Ackers29-Mar-06 3:28
Fred Ackers29-Mar-06 3:28 
GeneralThanks Pin
AmitMankikar24-Jan-05 18:51
AmitMankikar24-Jan-05 18:51 
GeneralVC++.NET error.. Pin
ysyoon17-Jan-05 12:25
ysyoon17-Jan-05 12:25 
GeneralRe: VC++.NET error.. Pin
AghaKhan27-Jan-05 3:31
AghaKhan27-Jan-05 3:31 
GeneralDirectShow filters in Borland 6 Pin
Member 157793012-Jan-05 4:50
Member 157793012-Jan-05 4:50 
GeneralDFT for DTFM Pin
dsusmel15-Sep-04 17:59
dsusmel15-Sep-04 17:59 
GeneralSuper! Pin
Georgi Petrov2-Sep-04 4:55
Georgi Petrov2-Sep-04 4:55 
GeneralSpectrum Analyzer &amp; FFT Pin
tunafish244-Jul-04 18:01
tunafish244-Jul-04 18:01 
GeneralRe: Spectrum Analyzer &amp; FFT Pin
Fred Ackers6-Jul-04 13:38
Fred Ackers6-Jul-04 13:38 
GeneralRe: Spectrum Analyzer &amp; FFT Pin
tunafish246-Jul-04 16:02
tunafish246-Jul-04 16:02 
GeneralRe: Spectrum Analyzer &amp; FFT Pin
Fred Ackers7-Jul-04 16:08
Fred Ackers7-Jul-04 16:08 
GeneralRe: Spectrum Analyzer &amp; FFT Pin
tunafish249-Jul-04 19:11
tunafish249-Jul-04 19:11 
GeneralRe: Spectrum Analyzer &amp; FFT Pin
meiyueh24-Sep-05 22:53
meiyueh24-Sep-05 22:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.