Simplified version of FFT code - drives on-board LED as a "Colour Organ".

Dependencies:   FastAnalogIn NVIC_set_all_priorities mbed-dsp mbed

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 // Audio Spectrum Display
00002 // Copyright 2013 Tony DiCola (tony@tonydicola.com)
00003 // Code ported from the guide at http://learn.adafruit.com/fft-fun-with-fourier-transforms?view=all
00004 // mods by Tony Abbey to simplify code to drive tri-colour LED as a "colour organ"
00005 
00006 #include "mbed.h"
00007 #include "NVIC_set_all_priorities.h"
00008 #include <ctype.h>
00009 #include "arm_math.h"
00010 #include "arm_const_structs.h"
00011 #include "FastAnalogIn.h"
00012 
00013 Serial pc(USBTX, USBRX);
00014 
00015 FastAnalogIn   Audio(PTC2);
00016 
00017 //#define RGBW_ext // Disable this line when you want to use the KL25Z on-board RGB LED.
00018 
00019 
00020 #ifndef RGBW_ext
00021 // RGB  direct output to PWM channels - on-board RGB LED
00022     PwmOut gled(LED_GREEN);
00023     PwmOut rled(LED_RED);
00024     PwmOut bled(LED_BLUE);
00025 #else
00026 // HSI to RGBW conversion with direct output to external PWM channels - RGBW LED
00027 // hsi2rgbw_pwm led(PTD4, PTA12, PTA4, PTA5); //Red, Green, Blue, White
00028 #endif
00029 
00030 // Dummy ISR for disabling NMI on PTA4 - !! DO NOT REMOVE THIS !!
00031 // More info at https://mbed.org/questions/1387/How-can-I-access-the-FTFA_FOPT-register-/
00032 extern "C" void NMI_Handler() {
00033     DigitalIn test(PTA4);
00034 }
00035 
00036 
00037 ////////////////////////////////////////////////////////////////////////////////
00038 // CONFIGURATION
00039 // These values can be changed to alter the behavior of the spectrum display.
00040 // KL25Z limitations
00041 // -----------------
00042 // - When used with the Spectrogram python script :
00043 //   There is a substantial time lag between the music and the screen output.
00044 //   Max allowed SAMPLE_RATE_HZ is 40000
00045 //   Max allowed FFT_SIZE is 64
00046 ////////////////////////////////////////////////////////////////////////////////
00047 
00048 int SLOWDOWN = 4;                       // Create an optical delay in spectrumLoop - useful when only one RGB led is used.
00049                                         // Only active when nonzero.
00050                                         // A value >= 1000 and <= 1000 + PIXEL_COUNT fixes the output to a single frequency
00051                                         // window = a single color.
00052 int SAMPLE_RATE_HZ = 20000;             // Sample rate of the audio in hertz.
00053 float SPECTRUM_MIN_DB = 20.0;           // Audio intensity (in decibels) that maps to low LED brightness.
00054 float SPECTRUM_MAX_DB = 80.0;           // Audio intensity (in decibels) that maps to high LED brightness.
00055 int LEDS_ENABLED = 1;                   // Control if the LED's should display the spectrum or not.  1 is true, 0 is false.
00056                                         // Useful for turning the LED display on and off with commands from the serial port.
00057 const int FFT_SIZE = 64;                // Size of the FFT.
00058 const int PIXEL_COUNT = 3;             // Number of pixels (RGB LED).  You should be able to increase this without
00059                                         // any other changes to the program.
00060 const int MAX_CHARS = 65;               // Max size of the input command buffer
00061 
00062 ////////////////////////////////////////////////////////////////////////////////
00063 // INTERNAL STATE
00064 // These shouldn't be modified unless you know what you're doing.
00065 ////////////////////////////////////////////////////////////////////////////////
00066 const static arm_cfft_instance_f32 *S;
00067 Ticker samplingTimer;
00068 float samples[FFT_SIZE*2];
00069 float magnitudes[FFT_SIZE];
00070 int sampleCounter = 0;
00071 char commandBuffer[MAX_CHARS];
00072 float frequencyWindow[PIXEL_COUNT+1];
00073 float hues[PIXEL_COUNT];
00074 bool commandRecv = 0;
00075 ////////////////////////////////////////////////////////////////////////////////
00076 // UTILITY FUNCTIONS
00077 ////////////////////////////////////////////////////////////////////////////////
00078 
00079 void rxisr() {
00080     char c = pc.getc();
00081     // Add any characters that aren't the end of a command (semicolon) to the input buffer.
00082     if (c != ';') {
00083         c = toupper(c);
00084         strncat(commandBuffer, &c, 1);
00085     } else {
00086         // Parse the command because an end of command token was encountered.
00087         commandRecv = 1;
00088     }
00089 }
00090 
00091 // Compute the average magnitude of a target frequency window vs. all other frequencies.
00092 void windowMean(float* magnitudes, int lowBin, int highBin, float* windowMean, float* otherMean)
00093 {
00094     *windowMean = 0;
00095     *otherMean = 0;
00096     // Notice the first magnitude bin is skipped because it represents the
00097     // average power of the signal.
00098     for (int i = 1; i < FFT_SIZE/2; ++i) {
00099         if (i >= lowBin && i <= highBin) {
00100             *windowMean += magnitudes[i];
00101         } else {
00102             *otherMean += magnitudes[i];
00103         }
00104     }
00105     *windowMean /= (highBin - lowBin) + 1;
00106     *otherMean /= (FFT_SIZE / 2 - (highBin - lowBin));
00107 }
00108 
00109 // Convert a frequency to the appropriate FFT bin it will fall within.
00110 int frequencyToBin(float frequency)
00111 {
00112     float binFrequency = float(SAMPLE_RATE_HZ) / float(FFT_SIZE);
00113     return int(frequency / binFrequency);
00114 }
00115 
00116 
00117 ////////////////////////////////////////////////////////////////////////////////
00118 // SPECTRUM DISPLAY FUNCTIONS
00119 ///////////////////////////////////////////////////////////////////////////////
00120 
00121 void spectrumSetup()
00122 {
00123     // Set the frequency window values by evenly dividing the possible frequency
00124     // spectrum across the number of neo pixels.
00125     float windowSize = (SAMPLE_RATE_HZ / 2.0) / float(PIXEL_COUNT);
00126     for (int i = 0; i < PIXEL_COUNT+1; ++i) {
00127         frequencyWindow[i] = i*windowSize;
00128     }
00129  
00130 }
00131 
00132 void spectrumLoop()
00133 {
00134     // Update each LED based on the intensity of the audio
00135     // in the associated frequency window.
00136     static int SLrpt = 0, SLpixcnt = 0;
00137     int SLpixend = 0;
00138     float intensity, otherMean;
00139     if(SLOWDOWN != 0)
00140     {
00141         if(SLOWDOWN >= 1000)
00142         {
00143             if(SLOWDOWN <= (1000 + PIXEL_COUNT-1))
00144             {
00145                 SLpixcnt = SLOWDOWN - 1000;
00146                 SLrpt = 0;
00147                 SLpixend = SLpixcnt + 1;
00148             }
00149             else
00150                 SLOWDOWN = 0;
00151         }
00152         else
00153         {
00154             SLrpt++;
00155             if (SLrpt >= SLOWDOWN)
00156             {
00157                 SLrpt = 0;
00158                 SLpixcnt = SLpixcnt < PIXEL_COUNT-1 ? ++SLpixcnt : 0;
00159             }
00160             SLpixend = SLpixcnt + 1;
00161         }
00162     }
00163     else
00164     {
00165         SLpixcnt = 0;
00166         SLrpt = 0;
00167         SLpixend = PIXEL_COUNT;
00168     }
00169     for (int i = SLpixcnt; i < SLpixend; ++i) {
00170         windowMean(magnitudes,
00171                    frequencyToBin(frequencyWindow[i]),
00172                    frequencyToBin(frequencyWindow[i+1]),
00173                    &intensity,
00174                    &otherMean);
00175         // Convert intensity to decibels.
00176         intensity = 20.0*log10(intensity);
00177         // Scale the intensity and clamp between 0 and 1.0.
00178         intensity -= SPECTRUM_MIN_DB;
00179         intensity = intensity < 0.0 ? 0.0 : intensity;
00180         intensity /= (SPECTRUM_MAX_DB-SPECTRUM_MIN_DB);
00181         intensity = intensity > 1.0 ? 1.0 : intensity;
00182         hues[i]=intensity;
00183     }
00184     rled=1.0-hues[0] ;  // onboard LED is common anode so inversion needed
00185     gled=1.0-hues[1];
00186     bled=1.0-hues[2];
00187 }
00188 
00189 
00190 ////////////////////////////////////////////////////////////////////////////////
00191 // SAMPLING FUNCTIONS
00192 ////////////////////////////////////////////////////////////////////////////////
00193 
00194 void samplingCallback()
00195 {
00196     // Read from the ADC and store the sample data
00197     samples[sampleCounter] = (1023 * Audio) - 511.0f;
00198     // Complex FFT functions require a coefficient for the imaginary part of the input.
00199     // Since we only have real data, set this coefficient to zero.
00200     samples[sampleCounter+1] = 0.0;
00201     // Update sample buffer position and stop after the buffer is filled
00202     sampleCounter += 2;
00203     if (sampleCounter >= FFT_SIZE*2) {
00204         samplingTimer.detach();
00205     }
00206 }
00207 
00208 void samplingBegin()
00209 {
00210     // Reset sample buffer position and start callback at necessary rate.
00211     sampleCounter = 0;
00212     samplingTimer.attach_us(&samplingCallback, 1000000/SAMPLE_RATE_HZ);
00213 }
00214 
00215 bool samplingIsDone()
00216 {
00217     return sampleCounter >= FFT_SIZE*2;
00218 }
00219 
00220 
00221 ////////////////////////////////////////////////////////////////////////////////
00222 // COMMAND PARSING FUNCTIONS
00223 // These functions allow parsing simple commands input on the serial port.
00224 // Commands allow reading and writing variables that control the device.
00225 //
00226 // All commands must end with a semicolon character.
00227 //
00228 // Example commands are:
00229 // GET SAMPLE_RATE_HZ;
00230 // - Get the sample rate of the device.
00231 // SET SAMPLE_RATE_HZ 400;
00232 // - Set the sample rate of the device to 400 hertz.
00233 //
00234 ////////////////////////////////////////////////////////////////////////////////
00235 
00236 void parseCommand(char* command)
00237 {
00238     if (strcmp(command, "GET MAGNITUDES") == 0) {
00239         for (int i = 0; i < FFT_SIZE; ++i) {
00240             printf("%f\r\n", magnitudes[i]);
00241         }
00242     } else if (strcmp(command, "GET SAMPLES") == 0) {
00243         for (int i = 0; i < FFT_SIZE*2; i+=2) {
00244             printf("%f\r\n", samples[i]);
00245         }
00246     } else if (strcmp(command, "GET FFT_SIZE") == 0) {
00247         printf("%d\r\n", FFT_SIZE);
00248     } else if (strcmp(command, "GET SAMPLE_RATE_HZ") == 0) {
00249         printf("%d\r\n", SAMPLE_RATE_HZ);
00250     } else if (strstr(command, "SET SAMPLE_RATE_HZ") != NULL) {
00251         SAMPLE_RATE_HZ = (typeof(SAMPLE_RATE_HZ)) atof(command+(sizeof("SET SAMPLE_RATE_HZ")-1));
00252     } else if (strcmp(command, "GET LEDS_ENABLED") == 0) {
00253         printf("%d\r\n", LEDS_ENABLED);
00254     } else if (strstr(command, "SET LEDS_ENABLED") != NULL) {
00255         LEDS_ENABLED = (typeof(LEDS_ENABLED)) atof(command+(sizeof("SET LEDS_ENABLED")-1));
00256     } else if (strcmp(command, "GET SPECTRUM_MIN_DB") == 0) {
00257         printf("%f\r\n", SPECTRUM_MIN_DB);
00258     } else if (strstr(command, "SET SPECTRUM_MIN_DB") != NULL) {
00259         SPECTRUM_MIN_DB = (typeof(SPECTRUM_MIN_DB)) atof(command+(sizeof("SET SPECTRUM_MIN_DB")-1));
00260     } else if (strcmp(command, "GET SPECTRUM_MAX_DB") == 0) {
00261         printf("%f\r\n", SPECTRUM_MAX_DB);
00262     } else if (strstr(command, "SET SPECTRUM_MAX_DB") != NULL) {
00263         SPECTRUM_MAX_DB = (typeof(SPECTRUM_MAX_DB)) atof(command+(sizeof("SET SPECTRUM_MAX_DB")-1));
00264     } else if (strcmp(command, "GET SLOWDOWN") == 0) {
00265         printf("%d\r\n", SLOWDOWN);
00266     } else if (strstr(command, "SET SLOWDOWN") != NULL) {
00267         SLOWDOWN = (typeof(SLOWDOWN)) atoi(command+(sizeof("SET SLOWDOWN")-1));
00268     }
00269 
00270     // Update spectrum display values if sample rate was changed.
00271     if (strstr(command, "SET SAMPLE_RATE_HZ ") != NULL) {
00272         spectrumSetup();
00273     } else if (strcmp(command, "GET HUES") == 0) {
00274         for (int i = 0; i < PIXEL_COUNT; ++i) {
00275             printf("%f\r\n", hues[i]);
00276         }
00277     }
00278     
00279 
00280     // Turn off the LEDs if the state changed.
00281     if (LEDS_ENABLED == 0) {
00282     }
00283 }
00284 
00285 void parserLoop()
00286 {
00287     // Process any incoming characters from the serial port
00288     while (pc.readable()) {
00289         char c = pc.getc();
00290         // (doesnt work!) printf("%c",c); // echo characters typed
00291         // Add any characters that aren't the end of a command (semicolon) to the input buffer.
00292         if (c != ';') {
00293             c = toupper(c);
00294             strncat(commandBuffer, &c, 1);
00295         } else {
00296             // Parse the command because an end of command token was encountered.
00297             parseCommand(commandBuffer);
00298             // Clear the input buffer
00299             memset(commandBuffer, 0, sizeof(commandBuffer));
00300         }
00301     }
00302 }
00303 
00304 ////////////////////////////////////////////////////////////////////////////////
00305 // MAIN FUNCTION
00306 ////////////////////////////////////////////////////////////////////////////////
00307 
00308 int main()
00309 {
00310     NVIC_set_all_irq_priorities(1);
00311     NVIC_SetPriority(UART0_IRQn, 0);
00312     // Set up serial port.
00313     pc.baud (38400);
00314     pc.attach(&rxisr);
00315 
00316     // Clear the input command buffer
00317     memset(commandBuffer, 0, sizeof(commandBuffer));
00318 
00319     // Initialize spectrum display
00320     spectrumSetup();
00321 
00322     // Begin sampling audio
00323     samplingBegin();
00324 
00325     // Init arm_ccft_32
00326     switch (FFT_SIZE)
00327     {
00328     case 16:
00329         S = & arm_cfft_sR_f32_len16;
00330         break;
00331     case 32:
00332         S = & arm_cfft_sR_f32_len32;
00333         break;
00334     case 64:
00335         S = & arm_cfft_sR_f32_len64;
00336         break;
00337     case 128:
00338         S = & arm_cfft_sR_f32_len128;
00339         break;
00340     case 256:
00341         S = & arm_cfft_sR_f32_len256;
00342         break;
00343     case 512:
00344         S = & arm_cfft_sR_f32_len512;
00345         break;
00346     case 1024:
00347         S = & arm_cfft_sR_f32_len1024;
00348         break;
00349     case 2048:
00350         S = & arm_cfft_sR_f32_len2048;
00351         break;
00352     case 4096:
00353         S = & arm_cfft_sR_f32_len4096;
00354         break;
00355     }
00356 
00357     while(1) {
00358         // Calculate FFT if a full sample is available.
00359         if (samplingIsDone()) {
00360             // Run FFT on sample data.
00361             arm_cfft_f32(S, samples, 0, 1);
00362             // Calculate magnitude of complex numbers output by the FFT.
00363             arm_cmplx_mag_f32(samples, magnitudes, FFT_SIZE);
00364 
00365             if (LEDS_ENABLED == 1) {
00366                 spectrumLoop();
00367             }
00368 
00369             // Restart audio sampling.
00370             samplingBegin();
00371         printf("this will make it work  ");
00372         }
00373 
00374         // Parse any pending commands.
00375         if(commandRecv) {
00376 //            pc.attach(NULL);
00377             parseCommand(commandBuffer);
00378             commandRecv = 0;
00379             // Clear the input buffer
00380             memset(commandBuffer, 0, sizeof(commandBuffer));
00381 //            pc.attach(&rxisr);
00382         }
00383     }
00384 }