
Simplified version of FFT code - drives on-board LED as a "Colour Organ".
Dependencies: FastAnalogIn NVIC_set_all_priorities mbed-dsp mbed
Fork of KL25Z_FFT_Demo_tony by
main.cpp
- Committer:
- tony1tf
- Date:
- 2014-07-11
- Revision:
- 2:aa24865dfef5
- Parent:
- 1:7c7539fba82b
File content as of revision 2:aa24865dfef5:
// Audio Spectrum Display // Copyright 2013 Tony DiCola (tony@tonydicola.com) // Code ported from the guide at http://learn.adafruit.com/fft-fun-with-fourier-transforms?view=all // mods by Tony Abbey to simplify code to drive tri-colour LED as a "colour organ" #include "mbed.h" #include "NVIC_set_all_priorities.h" #include <ctype.h> #include "arm_math.h" #include "arm_const_structs.h" #include "FastAnalogIn.h" Serial pc(USBTX, USBRX); FastAnalogIn Audio(PTC2); // #define RGBW_ext // Disable this line when you want to use the KL25Z on-board RGB LED. #ifndef RGBW_ext // RGB direct output to PWM channels - on-board RGB LED PwmOut gled(LED_GREEN); PwmOut rled(LED_RED); PwmOut bled(LED_BLUE); #else PwmOut gled(D6); PwmOut rled(D5); PwmOut bled(D7); #endif // Dummy ISR for disabling NMI on PTA4 - !! DO NOT REMOVE THIS !! // More info at https://mbed.org/questions/1387/How-can-I-access-the-FTFA_FOPT-register-/ extern "C" void NMI_Handler() { DigitalIn test(PTA4); } //////////////////////////////////////////////////////////////////////////////// // CONFIGURATION // These values can be changed to alter the behavior of the spectrum display. // KL25Z limitations // ----------------- // - When used with the Spectrogram python script : // There is a substantial time lag between the music and the screen output. // Max allowed SAMPLE_RATE_HZ is 40000 // Max allowed FFT_SIZE is 64 //////////////////////////////////////////////////////////////////////////////// int SAMPLE_RATE_HZ = 8000; // Sample rate of the audio in hertz - note was 20000 float SPECTRUM_MIN_DB = 33.0; // Audio intensity (in decibels) that maps to low LED brightness. float SPECTRUM_MAX_DB = 60.0; // Audio intensity (in decibels) that maps to high LED brightness. int LEDS_ENABLED = 1; // Control if the LED's should display the spectrum or not. 1 is true, 0 is false. // Useful for turning the LED display on and off with commands from the serial port. const int FFT_SIZE = 64; // Size of the FFT. const int PIXEL_COUNT = 3; // Number of pixels (RGB LED). You should be able to increase this without // any other changes to the program. const int MAX_CHARS = 65; // Max size of the input command buffer int PRINT_ON = 0; // flag to send chars continually to serial output //////////////////////////////////////////////////////////////////////////////// // INTERNAL STATE // These shouldn't be modified unless you know what you're doing. //////////////////////////////////////////////////////////////////////////////// const static arm_cfft_instance_f32 *S; Ticker samplingTimer; float samples[FFT_SIZE*2]; float magnitudes[FFT_SIZE]; int sampleCounter = 0; char commandBuffer[MAX_CHARS]; float frequencyWindow[PIXEL_COUNT+1]; float hues[PIXEL_COUNT]; float oldhues[PIXEL_COUNT]; float huescount = 1; bool commandRecv = 0; //////////////////////////////////////////////////////////////////////////////// // UTILITY FUNCTIONS //////////////////////////////////////////////////////////////////////////////// void rxisr() { char c = pc.getc(); // Add any characters that aren't the end of a command (semicolon) to the input buffer. if (c != ';') { c = toupper(c); strncat(commandBuffer, &c, 1); } else { // Parse the command because an end of command token was encountered. commandRecv = 1; } } // Compute the average magnitude of a target frequency window vs. all other frequencies. void windowMean(float* magnitudes, int lowBin, int highBin, float* windowMean, float* otherMean) { *windowMean = 0; *otherMean = 0; // Notice the first magnitude bin is skipped because it represents the // average power of the signal. for (int i = 1; i < FFT_SIZE/2; ++i) { if (i >= lowBin && i <= highBin) { *windowMean += magnitudes[i]; } else { *otherMean += magnitudes[i]; } } *windowMean /= (highBin - lowBin) + 1; *otherMean /= (FFT_SIZE / 2 - (highBin - lowBin)); } // Convert a frequency to the appropriate FFT bin it will fall within. int frequencyToBin(float frequency) { float binFrequency = float(SAMPLE_RATE_HZ) / float(FFT_SIZE); return int(frequency / binFrequency); } //////////////////////////////////////////////////////////////////////////////// // SPECTRUM DISPLAY FUNCTIONS /////////////////////////////////////////////////////////////////////////////// void spectrumSetup() { // Set the frequency window values by evenly dividing the possible frequency // spectrum across the number of neo pixels. float windowSize = (SAMPLE_RATE_HZ / 2.0) / float(PIXEL_COUNT); for (int i = 0; i < PIXEL_COUNT+1; ++i) { frequencyWindow[i] = i*windowSize; } } void spectrumLoop() { // Update each LED based on the intensity of the audio // in the associated frequency window. static int SLpixcnt = 0; float intensity, otherMean; int SLpixend = PIXEL_COUNT; for (int i = SLpixcnt; i < SLpixend; ++i) { windowMean(magnitudes, frequencyToBin(frequencyWindow[i]), frequencyToBin(frequencyWindow[i+1]), &intensity, &otherMean); // Convert intensity to decibels. intensity = 20.0*log10(intensity); // Scale the intensity and clamp between 0 and 1.0. intensity -= SPECTRUM_MIN_DB; intensity = intensity < 0.0 ? 0.0 : intensity; intensity /= (SPECTRUM_MAX_DB-SPECTRUM_MIN_DB); //intensity = intensity > 1.0 ? 1.0 : intensity; hues[i]=(intensity+oldhues[i]) / huescount; // averaging function oldhues[i]=hues[i]; huescount += 1; if (huescount > 9) huescount = 1; } rled=1.0-hues[0] ; // onboard LED is common anode so inversion needed gled=1.0-hues[1]; bled=1.0-hues[2]; } //////////////////////////////////////////////////////////////////////////////// // SAMPLING FUNCTIONS //////////////////////////////////////////////////////////////////////////////// void samplingCallback() { // Read from the ADC and store the sample data samples[sampleCounter] = (1024 * Audio) - 511.0f; // samples[sampleCounter] = Audio ; // just to see what this call actually produces // Complex FFT functions require a coefficient for the imaginary part of the input. // Since we only have real data, set this coefficient to zero. samples[sampleCounter+1] = 0.0; // Update sample buffer position and stop after the buffer is filled sampleCounter += 2; if (sampleCounter >= FFT_SIZE*2) { samplingTimer.detach(); } } void samplingBegin() { // Reset sample buffer position and start callback at necessary rate. sampleCounter = 0; samplingTimer.attach_us(&samplingCallback, 1000000/SAMPLE_RATE_HZ); } bool samplingIsDone() { return sampleCounter >= FFT_SIZE*2; } //////////////////////////////////////////////////////////////////////////////// // COMMAND PARSING FUNCTIONS // These functions allow parsing simple commands input on the serial port. // Commands allow reading and writing variables that control the device. // // All commands must end with a semicolon character. // // Example commands are: // GET SAMPLE_RATE_HZ; // - Get the sample rate of the device. // SET SAMPLE_RATE_HZ 400; // - Set the sample rate of the device to 400 hertz. // //////////////////////////////////////////////////////////////////////////////// void parseCommand(char* command) { if (strcmp(command, "GET MAGNITUDES") == 0) { for (int i = 1; i < FFT_SIZE; ++i) { printf("%4.2f,", magnitudes[i]); } } else if (strcmp(command, "GET SAMPLES") == 0) { for (int i = 0; i < FFT_SIZE*2; i+=2) { printf("%f,", samples[i]); } } else if (strcmp(command, "GET FFT_SIZE") == 0) { printf("%d\r\n", FFT_SIZE); } else if (strcmp(command, "GET SAMPLE_RATE_HZ") == 0) { printf("%d\r\n", SAMPLE_RATE_HZ); } else if (strstr(command, "SET SAMPLE_RATE_HZ") != NULL) { SAMPLE_RATE_HZ = (typeof(SAMPLE_RATE_HZ)) atof(command+(sizeof("SET SAMPLE_RATE_HZ")-1)); } else if (strcmp(command, "GET LEDS_ENABLED") == 0) { printf("%d\r\n", LEDS_ENABLED); } else if (strstr(command, "SET LEDS_ENABLED") != NULL) { LEDS_ENABLED = (typeof(LEDS_ENABLED)) atof(command+(sizeof("SET LEDS_ENABLED")-1)); } else if (strcmp(command, "GET SPECTRUM_MIN_DB") == 0) { printf("%f\r\n", SPECTRUM_MIN_DB); } else if (strstr(command, "SET SPECTRUM_MIN_DB") != NULL) { SPECTRUM_MIN_DB = (typeof(SPECTRUM_MIN_DB)) atof(command+(sizeof("SET SPECTRUM_MIN_DB")-1)); } else if (strcmp(command, "GET SPECTRUM_MAX_DB") == 0) { printf("%f\r\n", SPECTRUM_MAX_DB); } else if (strstr(command, "SET SPECTRUM_MAX_DB") != NULL) { SPECTRUM_MAX_DB = (typeof(SPECTRUM_MAX_DB)) atof(command+(sizeof("SET SPECTRUM_MAX_DB")-1)); } else if (strcmp(command, "GET HUES") == 0) { for (int i = 0; i < PIXEL_COUNT; ++i) { printf("%f\r\n", hues[i]); } } else if (strcmp(command, "GET FREQUENCIES") == 0) { for (int i = 0; i < PIXEL_COUNT; ++i) { printf("%f\r\n", frequencyWindow[i]); } } else if (strcmp(command, "PRINT_ON") == 0) { PRINT_ON = 1; } else if (strcmp(command, "PRINT_OFF") == 0) { PRINT_ON = 0; } // Update spectrum display values if sample rate was changed. if (strstr(command, "SET SAMPLE_RATE_HZ ") != NULL) { spectrumSetup(); } // Turn off the LEDs if the state changed. if (LEDS_ENABLED == 0) { } } void parserLoop() { // Process any incoming characters from the serial port while (pc.readable()) { char c = pc.getc(); // (doesnt work!) printf("%c",c); // echo characters typed // Add any characters that aren't the end of a command (semicolon) to the input buffer. if (c != ';') { c = toupper(c); strncat(commandBuffer, &c, 1); } else { // Parse the command because an end of command token was encountered. parseCommand(commandBuffer); // Clear the input buffer memset(commandBuffer, 0, sizeof(commandBuffer)); } } } //////////////////////////////////////////////////////////////////////////////// // MAIN FUNCTION //////////////////////////////////////////////////////////////////////////////// int main() { NVIC_set_all_irq_priorities(1); NVIC_SetPriority(UART0_IRQn, 0); // Set up serial port. pc.baud (38400); pc.attach(&rxisr); // Clear the input command buffer memset(commandBuffer, 0, sizeof(commandBuffer)); // Initialize spectrum display spectrumSetup(); // Begin sampling audio samplingBegin(); // Init arm_ccft_32 switch (FFT_SIZE) { case 16: S = & arm_cfft_sR_f32_len16; break; case 32: S = & arm_cfft_sR_f32_len32; break; case 64: S = & arm_cfft_sR_f32_len64; break; case 128: S = & arm_cfft_sR_f32_len128; break; case 256: S = & arm_cfft_sR_f32_len256; break; case 512: S = & arm_cfft_sR_f32_len512; break; case 1024: S = & arm_cfft_sR_f32_len1024; break; case 2048: S = & arm_cfft_sR_f32_len2048; break; case 4096: S = & arm_cfft_sR_f32_len4096; break; } while(1) { // Calculate FFT if a full sample is available. if (samplingIsDone()) { // Run FFT on sample data. arm_cfft_f32(S, samples, 0, 1); // Calculate magnitude of complex numbers output by the FFT. arm_cmplx_mag_f32(samples, magnitudes, FFT_SIZE); if (LEDS_ENABLED == 1) { spectrumLoop(); } wait_ms(10); // Restart audio sampling. samplingBegin(); if (PRINT_ON == 1) { for (int i = 0; i < PIXEL_COUNT; ++i) { printf("%f\r\n", hues[i]); } } } // Parse any pending commands. if(commandRecv) { // pc.attach(NULL); parseCommand(commandBuffer); commandRecv = 0; // Clear the input buffer memset(commandBuffer, 0, sizeof(commandBuffer)); // pc.attach(&rxisr); } } }