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 Tony Abbey

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);
        }
    }
}