Audio Spectrum analyser - FFT using mbed-dsp - driving RGB(W) LED or PC python script.

Dependencies:   HSI2RGBW_PWM NVIC_set_all_priorities mbed-dsp mbed FastAnalogIn

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