iOS FFT Draw spectrum
core-audio, fft, ios, spectrum, vdsp
Solution
- Do I need to average magnitude values from a range of frequencies? Or do they just show you a magnitude for the specific frequency bar?
Yes, you definitely need to average values across the bands you've defined. Showing just one FFT bin is madness.
- Also, do I need to convert my magnitude values to db?
Yes: dB is a log scale. Not coincidentally, human hearing also works (roughly) on a log scale. The values will therefore look more natural to humans if you take log2() of the values before plotting them.
- How do I map my data to a certain range. Do I map against the max db range for my sounds bitdepth? Getting the max Value for a bin will lead to jumping max mapping values.
I find the easiest thing to do (conceptually at least) is to convert your values from whatever format into a `0..1`, i.e. 'normalised and scaled' float value. Then from there you can convert if necessary to something you need to plot. For example
SInt16 rawValue = fft[0]; // let's say this comes back as 12990
float scaledValue = rawValue/32767.; // This is MAX_INT for 16-bit;
// dividing we get .396435438 which is much easier for most people
// to see conceptually as 39% of our max possible value
float displayValue = log2(scaledValue);
my_fft[0] = displayValue;
Problem
I've read these question: Using the Apple FFT and Accelerate Framework How do I set up a buffer when doing an FFT using the Accelerate framework? iOS FFT Accerelate.framework draw spectrum during playback They all describe how to setup fft with the accelerate framework. With their help I was able to setup fft and get a basic spectrum analyzer. Right now, I'm displaying all values I got from the fft. However, I only want to show 10-15, or a variable number, of bars respreseting certain frequencies. Just like the iTunes or WinAmp Level Meter. 1. Do I need to average magnitude values from a range of frequencies? Or do they just show you a magnitude for the specific frequency bar? 2. Also, do I need to convert my magnitude values to db? 3. How do I map my data to a certain range. Do I map against the max db range for my sounds bitdepth? Getting the max Value for a bin will lead to jumping max mapping values. My RenderCallback: ``` static OSStatus PlaybackCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { UInt32 maxSamples = kAudioBufferNumFrames; UInt32 log2n = log2f(maxSamples); //bins UInt32 n = 1 << log2n; UInt32 stride = 1; UInt32 nOver2 = n/2; COMPLEX_SPLIT A; float *originalReal, *obtainedReal, *frequencyArray, *window, *in_real; in_real = (float *) malloc(maxSamples * sizeof(float)); A.realp = (float *) malloc(nOver2 * sizeof(float)); A.imagp = (float *) malloc(nOver2 * sizeof(float)); memset(A.imagp, 0, nOver2 * sizeof(float)); obtainedReal = (float *) malloc(n * sizeof(float)); originalReal = (float *) malloc(n * sizeof(float)); frequencyArray = (float *) malloc(n * sizeof(float)); //-- window UInt32 windowSize = maxSamples; window = (float *) malloc(windowSize * sizeof(float)); memset(window, 0, windowSize * sizeof(float)); // vDSP_hann_window(window, windowSize, vDSP_HANN_DENORM); vDSP_blkman_window(window, windowSize, 0); vDSP_vmul(ioBuffer, 1, window, 1, in_real, 1, maxSamples); //-- window vDSP_ctoz((COMPLEX*)in_real, 2, &A, 1, maxSamples/2); vDSP_fft_zrip(fftSetup, &A, stride, log2n, FFT_FORWARD); vDSP_fft_zrip(fftSetup, &A, stride, log2n, FFT_INVERSE); float scale = (float) 1.0 / (2 * n); vDSP_vsmul(A.realp, 1, &scale, A.realp, 1, nOver2); vDSP_vsmul(A.imagp, 1, &scale, A.imagp, 1, nOver2); vDSP_ztoc(&A, 1, (COMPLEX *) obtainedReal, 2, nOver2); vDSP_zvmags(&A, 1, obtainedReal, 1, nOver2); Float32 one = 1; vDSP_vdbcon(obtainedReal, 1, &one, obtainedReal, 1, nOver2, 0); for (int i = 0; i < nOver2; i++) { frequencyArray[i] = obtainedReal[i]; } // Extract the maximum value double fftMax = 0.0; vDSP_maxmgvD((double *)obtainedReal, 1, &fftMax, nOver2); float max = sqrt(fftMax); } ``` Playing some music, I get values from -96db to 0db. Plotting a point at: ``` CGPointMake(i, kMaxSpectrumHeight * (1 - frequencyArray[i]/-96.)); ``` is giving my a rather rounded curve: plot1 If I don't convert to db I can plot by multiplying my array value by 10000 and get nice peaks. plot2 Am I doing something totally wrong? And how do I get to showing a variable number of bars?