I am working on a project that uses an FFT library along with Processing to visualize an audio signal from a freetronics microphone.
The Example Sketch I Uploaded to the Arduino:
/*
This Example acquire analog signal from A0 of Arduino, and Serial out to Processing application to visualize.
Tested with preamplified audio data. Take a look at http://www.youtube.com/watch?v=drYWullBWcI
Analog signal is captured at 9.6 KHz, 64 spectrum bands each 150Hz which can be change from adcInit()
Load the this file to Arduio, run Processing application.
Original Fixed point FFT library is from ELM Chan, http://elm-chan.org/works/akilcd/report_e.html
A way to port it to the Arduino library and most demo codes are from AMurchick http://arduino.cc/forum/index.php/topic,37751.0.html
Processing app is based on codes from boolscott http://boolscott.wordpress.com/2010/02/04/arduino-processing-analogue-bar-graph-2/
*/
#include <stdint.h>
#include <ffft.h>
#define IR_AUDIO 0 // ADC channel to capture
volatile byte position = 0;
volatile long zero = 0;
int16_t capture[FFT_N]; /* Wave captureing buffer */
complex_t bfly_buff[FFT_N]; /* FFT buffer */
uint16_t spektrum[FFT_N/2]; /* Spectrum output buffer */
void setup()
{
Serial.begin(57600);
adcInit();
adcCalb();
establishContact(); // send a byte to establish contact until Processing respon
}
void loop()
{
if (position == FFT_N)
{
fft_input(capture, bfly_buff);
fft_execute(bfly_buff);
fft_output(bfly_buff, spektrum);
for (byte i = 0; i < 64; i++){
Serial.write(spektrum[i]);
}
position = 0;
}
}
void establishContact() {
while (Serial.available() <= 0) {
Serial.write('A'); // send a capital A
delay(300);
}
}
// free running ADC fills capture buffer
ISR(ADC_vect)
{
if (position >= FFT_N)
return;
capture[position] = ADC + zero;
if (capture[position] == -1 || capture[position] == 1)
capture[position] = 0;
position++;
}
void adcInit(){
/* REFS0 : VCC use as a ref, IR_AUDIO : channel selection, ADEN : ADC Enable, ADSC : ADC Start, ADATE : ADC Auto Trigger Enable, ADIE : ADC Interrupt Enable, ADPS : ADC Prescaler */
// free running ADC mode, f = ( 16MHz / prescaler ) / 13 cycles per conversion
ADMUX = _BV(REFS0) | IR_AUDIO; // | _BV(ADLAR);
// ADCSRA = _BV(ADSC) | _BV(ADEN) | _BV(ADATE) | _BV(ADIE) | _BV(ADPS2) | _BV(ADPS1) //prescaler 64 : 19231 Hz - 300Hz per 64 divisions
ADCSRA = _BV(ADSC) | _BV(ADEN) | _BV(ADATE) | _BV(ADIE) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // prescaler 128 : 9615 Hz - 150 Hz per 64 divisions, better for most music
sei();
}
void adcCalb(){
Serial.println("Start to calc zero");
long midl = 0;
// get 2 meashurment at 2 sec
// on ADC input must be NO SIGNAL!!!
for (byte i = 0; i < 2; i++)
{
position = 0;
delay(100);
midl += capture[0];
delay(900);
}
zero = -midl/2;
Serial.println("Done.");
}
Example Code I Used With Processing:
// Feel Free to edit these variables ///////////////////////////
String xLabel = "Frequency";
String yLabel = "Values";
String Heading = "Arduino Audio FFT";
String URL = "01/02/2010";
float Vcc = 255.0; // the measured voltage of your usb
int NumOfVertDivisions=5; // dark gray
int NumOfVertSubDivisions=10; // light gray
int NumOfBars=64; // you can choose the number of bars, but it can cause issues
// since you should change what the arduino sends
// if these are changed, backgroung image has problems
// a plain background solves the problem
int ScreenWidth = 800, ScreenHeight=600;
/////////////////////////////////////////////////////////
// Serial port stuff ///////////////////////
import processing.serial.*;
Serial myPort;
boolean firstContact = false;
int[] serialInArray = new int[NumOfBars];
int serialCount = 0;
///////////////////////////////////////////////
int LeftMargin=100;
int RightMArgin=80;
int TextGap=50;
int GraphYposition=80;
float BarPercent = 0.4;
int value;
PFont font;
PImage bg;
int temp;
float yRatio = 0.58;
int BarGap, BarWidth, DivisounsWidth;
int[] bars = new int[NumOfBars];
void setup(){
bg = loadImage("BG.jpg");
/// NB SETTINGS ////////////////////////////////////////////////////////
myPort = new Serial(this, Serial.list()[0], 57600);
////////////////////////////////////////////////////////////////////////
DivisounsWidth = (ScreenWidth-LeftMargin-RightMArgin)/(NumOfBars);
BarWidth = int(BarPercent*float(DivisounsWidth));
BarGap = DivisounsWidth - BarWidth;
size(ScreenWidth,ScreenHeight);
font = createFont("Arial",12);
textAlign(CENTER);
textFont(font);
}
void draw(){
// background(bg); // My one used a background image, I've
background(250); // commented it out and put a plain colour
// Headings(); // Displays bar width, Bar gap or any variable.
Axis();
Labels();
PrintBars();
// Line();
// Dots();
}
// Send Recieve data //
void serialEvent(Serial myPort) {
// read a byte from the serial port:
int inByte = myPort.read();
if (firstContact == false) {
if (inByte == 'A') {
myPort.clear(); // clear the serial port buffer
firstContact = true; // you've had first contact from the microcontroller
myPort.write('A'); // ask for more
}
}
else {
// Add the latest byte from the serial port to array:
serialInArray[serialCount] = inByte;
serialCount++;
// If we have 6 bytes:
if (serialCount > NumOfBars -1 ) {
for (int x=0;x<NumOfBars;x++){
bars[x] = int (yRatio*(ScreenHeight)*(serialInArray[x]/256.0));
}
// Send a capital A to request new sensor readings:
myPort.write('A');
// Reset serialCount:
serialCount = 0;
}
}
}
/////// Display any variables for testing here//////////////
void Headings(){
fill(0 );
text("BarWidth",50,TextGap );
text("BarGap",250,TextGap );
text("DivisounsWidth",450,TextGap );
text(BarWidth,100,TextGap );
text(BarGap,300,TextGap );
text(DivisounsWidth,520,TextGap );
}
void PrintBars(){
int c=0;
for (int i=0;i<NumOfBars;i++){
fill((0xe4+c),(255-bars[i]+c),(0x1a+c));
stroke(90);
rect(i*DivisounsWidth+LeftMargin, ScreenHeight-GraphYposition, BarWidth, -bars[i]);
fill(0x2e,0x2a,0x2a);
// text(float(bars[i])/(yRatio*(ScreenHeight))*Vcc, i*DivisounsWidth+LeftMargin+BarWidth/2, ScreenHeight-bars[i]-5-GraphYposition );
// text("A", i*DivisounsWidth+LeftMargin+BarWidth/2 -5, ScreenHeight-GraphYposition+20 );
// text(i, i*DivisounsWidth+LeftMargin+BarWidth/2 +5, ScreenHeight-GraphYposition+20 );
}
}
void Axis(){
strokeWeight(1);
stroke(220);
for(float x=0;x<=NumOfVertSubDivisions;x++){
int bars=(ScreenHeight-GraphYposition)-int(yRatio*(ScreenHeight)*(x/NumOfVertSubDivisions));
line(LeftMargin-15,bars,ScreenWidth-RightMArgin-DivisounsWidth+50,bars);
}
strokeWeight(1);
stroke(180);
for(float x=0;x<=NumOfVertDivisions;x++){
int bars=(ScreenHeight-GraphYposition)-int(yRatio*(ScreenHeight)*(x/NumOfVertDivisions));
line(LeftMargin-15,bars,ScreenWidth-RightMArgin-DivisounsWidth+50,bars);
}
strokeWeight(2);
stroke(90);
line(LeftMargin-15, ScreenHeight-GraphYposition+2, ScreenWidth-RightMArgin-DivisounsWidth+50, ScreenHeight-GraphYposition+2);
line(LeftMargin-15,ScreenHeight-GraphYposition+2,LeftMargin-15,GraphYposition+80);
strokeWeight(1);
}
void Labels(){
textFont(font,18);
fill(50);
rotate(radians(-90));
text(yLabel,-ScreenHeight/2,LeftMargin-45);
textFont(font,10);
for(float x=0;x<=NumOfVertDivisions;x++){
int bars=(ScreenHeight-GraphYposition)-int(yRatio*(ScreenHeight)*(x/NumOfVertDivisions));
text(round(x),-bars,LeftMargin-20);
}
textFont(font,18);
rotate(radians(90));
text(xLabel,LeftMargin+(ScreenWidth-LeftMargin-RightMArgin-50)/2,ScreenHeight-GraphYposition+40);
textFont(font,24);
fill(50);
text(Heading,LeftMargin+(ScreenWidth-LeftMargin-RightMArgin-50)/2,70);
textFont(font);
fill(150);
text(URL,ScreenWidth-RightMArgin-40,ScreenHeight-15);
textFont(font);
}
The graph produced by Processing is very useful for real-time frequency analysis, but I am assuming that it would be possible to save the unsigned 16 bit integer 'spektrum', which I believe is the output of the Fourier Transform, to a file on an SD card. I was able to get an SD Card reader module working with the SD library CardInfo/Read&Write examples. Does anyone know how I can save the FFT output for an analysis at a later time?
Thanks!