2

I have an app that selects a song from the iPod Library then copies that song into the app's directory as a '.caf' file. I now need to play and at the same time read that file into Apples FFT from the Accelerate framework so I can visualize the data like a spectrogram. Here is the code for the FFT:

void FFTAccelerate::doFFTReal(float samples[], float amp[], int numSamples)
{
int i;
vDSP_Length log2n = log2f(numSamples);

//Convert float array of reals samples to COMPLEX_SPLIT array A
vDSP_ctoz((COMPLEX*)samples,2,&A,1,numSamples/2);

//Perform FFT using fftSetup and A
//Results are returned in A
vDSP_fft_zrip(fftSetup, &A, 1, log2n, FFT_FORWARD);

//Convert COMPLEX_SPLIT A result to float array to be returned
amp[0] = A.realp[0]/(numSamples*2);
for(i=1;i<numSamples;i++)
    amp[i]=sqrt(A.realp[i]*A.realp[i]+A.imagp[i]*A.imagp[i])/numSamples;
}

//Constructor
FFTAccelerate::FFTAccelerate (int numSamples)
{
vDSP_Length log2n = log2f(numSamples);
fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2);
int nOver2 = numSamples/2;
A.realp = (float *) malloc(nOver2*sizeof(float));
A.imagp = (float *) malloc(nOver2*sizeof(float));
}

My question is how to I loop through the '.caf' audio file to feed the FFT while at the same time playing the song? I only need one channel. Im guessing I need to get 1024 samples of the song, process that in the FTT and then move further down the file and grab another 1024 samples. But I dont understand how to read an audio file to do this. The file has a sample rate of 44100.0 hz, is in linear PCM format, 16 Bit and I believe is also interleaved if that helps...

Snick
  • 115
  • 1
  • 10

1 Answers1

2

Try the ExtendedAudioFile API (requires AudioToolbox.framework).

#include <AudioToolbox/ExtendedAudioFile.h>

NSURL   *urlToCAF = ...;

ExtAudioFileRef caf;
OSStatus    status;

status = ExtAudioFileOpenURL((__bridge CFURLRef)urlToCAF, &caf);
if(noErr == status) {
    // request float format
    const UInt32 NumFrames = 1024;
    const int ChannelsPerFrame = 1;  // Mono, 2 for Stereo

    // request float format
    AudioStreamBasicDescription clientFormat;
    clientFormat.mChannelsPerFrame = ChannelsPerFrame;
    clientFormat.mSampleRate = 44100;

    clientFormat.mFormatID = kAudioFormatLinearPCM;
    clientFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved; // float
    int cmpSize = sizeof(float);
    int frameSize = cmpSize*ChannelsPerFrame;
    clientFormat.mBitsPerChannel = cmpSize*8;
    clientFormat.mBytesPerPacket = frameSize;
    clientFormat.mFramesPerPacket = 1;
    clientFormat.mBytesPerFrame = frameSize;

    status = ExtAudioFileSetProperty(caf, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat);
     if(noErr != status) { /* handle it */ }

    while(1) {
        float   buf[ChannelsPerFrame*NumFrames];
        AudioBuffer ab = { ChannelsPerFrame, sizeof(buf), buf };
        AudioBufferList abl;
        abl.mNumberBuffers = 1;
        abl.mBuffers[0] = ab;

        UInt32  ioNumFrames = NumFrames;
        status = ExtAudioFileRead(caf, &ioNumFrames, &abl);

        if(noErr == status) {
            // process ioNumFrames here in buf
            if(0 == ioNumFrames) {
                // EOF!
                break;
            } else if(ioNumFrames < NumFrames) {
                // TODO: pad buf with zeroes out to NumFrames 
            } else {
                 float amp[NumFrames]; // scratch space
                 doFFTReal(buf, amp, NumFrames);
            }
        }
    }

    // later
    status = ExtAudioFileDispose(caf);
    if(noErr != status) { /* hmm */ }
}
Rhythmic Fistman
  • 34,352
  • 5
  • 87
  • 159
  • Thank you...this looks promising! How would I loop around and get the next 1024 frames? How would I also play the audio at the same time? – Snick Dec 02 '13 at 21:32
  • added loop. just realized you need float samples. need to fix that. to play the audio at the same time, you should probably ask another question. there are many ways to do it. – Rhythmic Fistman Dec 02 '13 at 21:56
  • Im confused what is ioNumFrames? Dont I want an array of float samples? Is the 'ab'what hold this information?? – Snick Dec 02 '13 at 22:09
  • added float conversion – Rhythmic Fistman Dec 02 '13 at 22:09
  • ioNumFrames is how many frames you actually got, could be shorter than what you requested due to EOF. ab holds pointers to your buffers. it handles floats now – Rhythmic Fistman Dec 02 '13 at 22:10
  • How do I used 'ab' to access those floats? What do I plug into: fftAccel->doFFTReal(samples[], frequency[], numSamples); – Snick Dec 02 '13 at 22:21
  • Hate to ask for more help, but why are some of the amplitudes returned by the FFT so large? I was expecting the FFT AMP bins to stay under 1.0 but some are returning INF... – Snick Dec 02 '13 at 23:18
  • Maybe the samples read from the audio file are not normalized. You may need to that by hand. – Rhythmic Fistman Dec 02 '13 at 23:41
  • I forgot to mention that the caf file is in stereo but I only want to process 1 channel to make things simple. That wouldn't be what is throwing off the FFT? Also, what is pushing the buffer to the next set of samples? Is ExtAudioFileRead pushing the buffer down the file? – Snick Dec 02 '13 at 23:50
  • I think the ExtAudioFile should be converting to mono for you. Yes, ExtAudioFileRead is advancing through the file. – Rhythmic Fistman Dec 02 '13 at 23:58