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
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 }
Generated on Thu Jul 14 2022 01:02:10 by 1.7.2