Go Down

Topic: Audio input analyzed w/ Arduino FFT (Read 13933 times) previous topic - next topic

rexhex

Thank you for coming up with a for loop for me. J goes to 127 and I am not sure what fft_log_out is printing in relation to j. I got frequencies printed from this code written by hongp (forum.arduino.cc/index.php?topic=175679.new#new) but his code is made for a specific school project involving pumps. His code prints just one freq at a time and its kinda slow, but it pushes out a freq.

When I first tried your for loop, tmd3, I got the same type of characters on the output. I was trying to take out pieces of hongp's code to see if I could get a freq value. Still garbage. I realized that the Serial.begin was different than the examples so I changed it too 9600, which is the usual. then your for loop and many other things I tried gave real numbers rather than strange characters. 

115200 = no good for reading serial print ---- 9600 = readable serial print

doing a frequency test that goes through all of the audible frequencies through hongp's code pushes this.

37hz - 111hz - 148hz -185hz - 222hz - 259hz - 296hz - 333hz - 370hz - 407 hz - 444 hz - 481hz - 518hz - 555hz - 592hz -629hz - 666hz - 703hz - 740hz - 777hz - 814hz - 851hz - 888hz - 925hz - 962hz - 999 hz - 1036hz - 1073hz - 1110hz - 1147hz - 1184hz - 1221hz - 1221hz - 1258hz - 1295hz - 1369hz - 1406hz - 1480hz- 1517hz - 1554hz

until hitting 4699 and dropping back down. This probably has to do with his int resolution, removing this does not give good results.

I think I will need to spend a good amount of time learning FFT and how it works to really be able to get into this code. I was thinking that I was going to easily find frequencies with it, at least by simply going through a frequency test.

Code: [Select]
#define LOG_OUT 1 // use the log output function
#define FFT_N 256 // set to 256 point fft
#include <FFT.h> // include the library

//UNO clock Frequency = 16 MHz
//It takes 13 clock cycles to finish analog reading
int refFrequency = 0;
int currentFrequency = 0;
int previousFrequency = 0;
int errorFrequency = 0;
int refState = 0;
int currentState = 0;
int tunningState = 0;
boolean inRange = false;
float P = 0;
float I = 0.0;
float D = 0.0;
float drive = 0;
int pwmDrive = 0;
const int refInput = 8;  //reference pin
const int currentInput = 9;  //final pin
const int ForMotorControl = 10;  //pump control pin PWM
const int BacMotorControl = 11;  //pump control pin PWM
const int tunning = 12;  //tuning control pin
int bin = 0;
int frequency = 0;
int resolution = 9615 / 256;
int maxLogOut = 0;


void setup() {
  Serial.begin(9600); // use the serial port
  pinMode(refInput, INPUT);
  pinMode(currentInput, INPUT);
  pinMode(tunning, INPUT);
  pinMode(ForMotorControl, OUTPUT);
  pinMode(BacMotorControl, OUTPUT);
  ADCSRA |= (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); //set adc prescaler=128
  ADMUX = 0x40; // use adc0
  ADCSRA |= (1<<ADATE);  //enable auto trigger
  ADCSRA |= (1<<ADEN);  //enable ADC
  ADCSRA |= (1<<ADSC);  //start ADC measurements
}

void loop() {
  while(1) { // reduces jitter
    cli();  // UDRE interrupt slows this way down on arduino1.0
    //sampling
    for (int i = 0 ; i < 512 ; i += 2) { // save 256 samples
      while(!(ADCSRA & 0x10)); // wait for adc to be ready
      ADCSRA |=(1<<ADSC); //start ADC measurements
      byte m = ADCL; // fetch adc data
      byte j = ADCH;
      int k = (j << 8) | m; // form into an int
      k -= 0x0200; // form into a signed int
      k <<= 6; // form into a 16b signed int
      fft_input[i] = k; // put real data into even bins
      fft_input[i+1] = 0; // set odd bins to 0
    }//end of for
   
    //FFT
    fft_window(); // window the data for better frequency response
    fft_reorder(); // reorder the data before doing the fft
    fft_run(); // process the data in the fft
    fft_mag_log(); // take the output of the fft
    sei();
    maxLogOut = 0;
    for(int i = 1; i < 128; i++){
      if(fft_log_out[i] > maxLogOut && fft_log_out[i] > 150){
          maxLogOut = fft_log_out[i];
          bin = i;
      }//end of if
    }//end of for
    frequency = bin * resolution;
    if(frequency > 1500 && frequency < 2700){
      inRange = true;
    } else {
      inRange = false;
    }
   
    //if digital pin 8 is high, set the reference frequency
    //if digital pin 9 is high, set the current frequency
    //if digital pin 12 is high, set tunning state = high
    refState = digitalRead(refInput);
    currentState = digitalRead(currentInput);
    tunningState = digitalRead(tunning);
    if(refState == HIGH && inRange == true){
      refFrequency = frequency;
    } else if(currentState == HIGH && inRange == true){
      currentFrequency = frequency;
    } //end of else if
   
    //error tracking
    //highest frequency = (2405)2479 hz  lowest frequency = (1517) 1628 hz
    errorFrequency = refFrequency - currentFrequency;
    P = errorFrequency;  //proportional term
   // I = Integral * 0;  //intergral term
   // D = (previousFrequency - currentFrequency) * 0;  //derivative term
   // previousFrequency = currentFrequency; 
    drive = P * (255.0/888.0); //scaleFactor = 255 * 888;

    //start tuning if tunning state = high
    if(tunningState == HIGH){
    //motor control
      if(errorFrequency > 30){      //pump liquid out
        //digitalWrite();  //pump in one direction
        digitalWrite(BacMotorControl, HIGH);
        analogWrite(ForMotorControl, 0);
      } else if (errorFrequency < -30){  //pump liquid in
        //digitalWrite();  //pump in another direction
        digitalWrite(BacMotorControl, LOW);
        pwmDrive = (int)abs(drive);
        if(pwmDrive < 80){ pwmDrive = pwmDrive + 80;}
        analogWrite(ForMotorControl, pwmDrive);
      } else {  //stop pumping
        analogWrite(ForMotorControl, 0);  //stop the motor
        digitalWrite(BacMotorControl, LOW);
      }
    } else {
      analogWrite(ForMotorControl, 0);  //stop the motor
      digitalWrite(BacMotorControl, LOW);
    }
   
    //debug
    Serial.print("frequency: ");
    Serial.print(frequency);
    Serial.print(" | ");
    Serial.print("reference frequency: ");
    Serial.print(refFrequency);
    Serial.print(" | ");
    Serial.print("Current frequency: ");
    Serial.print(currentFrequency);
    Serial.print(" | ");
    Serial.print("error frequency: ");
    Serial.println(errorFrequency);
  }//end of while
}//end of loop

tmd3

115200 = no good for reading serial print ---- 9600 = readable serial print
Look at the lower right corner of the serial monitor window.  You'll see an indication of the baud rate.  That rate needs to match the value that you used in the Serial.begin() in your sketch.  If you select that pulldown menu, you'll see a list of baud rates that the Arduino is friendly with.

Your code has this line:
Code: [Select]
 Serial.begin(9600); // use the serial port

That means that you'll have to use 9600 on the serial monitor as well.  If you want to use
Code: [Select]
 Serial.begin(115200);
you'll have to select "115200 baud" in the serial monitor.

You can see how to use Serial.begin here.  You can find links to Serial.write(), Serial.print(), and other Serial methods here.


rexhex

I will read up on the serial.___ so i hopefully don't make any more mistakes there. I have never needed to change that up until now. In my free time today I have been reading about FFT and its pretty interesting stuff.

This is in the readme in the fft library folder. A good source for sure.

http://www.alwayslearn.com/dft%20and%20fft%20tutorial/DFTandFFT_BasicIdea.html

tmd3, thank you for responding and helping me out with this.  :)

rexhex

So I have been going through hongp's forum and a lot of my questions have been answered. It looks like FFT with the arduino is not powerful enough for my goal. "For each 'bin' N the frequency is N * Sample_Rate / FFT_Size" - johnwasser. We are limited to the number of frequencies we can look at because we can only really look at 127 different frequencies because the bin represents the frequency it is measuring.

Im attempting to get frequencies out of music for a light show. I want create code that reacts to different frequency ranges. The first step I want to get done is something like 20hz - 60hz = sub bass response : 60hz - 250hz = bass response : 250hz - 2khz = midrange response : 4khz - 6khz = high midrange response : 6khz - 20khz = high frequency response.

With the arduino and FFT it looks like I would be able to get a response from these 5 categories if the artist hit specific frequencies in these catagories. "...the highest number of elements that is "guaranteed" to work is, in fact, 445..." (http://playground.arduino.cc/Main/CorruptArrayVariablesAndMemory#Max_size_of_array.).

This dose not make for an organic feel. Maybe I will need to go analog to digital? I hope that I am not seeing this right but am pretty sure its true. FFT with the arduino only looks at very limited frequency ranges and ignores all of the rest.

Grumpy_Mike

Quote
FFT with the arduino only looks at very limited frequency ranges and ignores all of the rest.
No, read about FFTs again, that is wrong. In fact it is the total opposite an FFT looks at all frequencies but only gives you the result in bins.

rexhex

Right..and fft with arduino is limited in the number of results it can produce because of the limited number of bins. Am I right or would you like to explain how I am wrong.

Grumpy_Mike

Quote
would you like to explain how I am wrong.
Well explaining this to someone who does not know that the baud rate at both ends of a link has to be the same is going to be tricky.

The number of bins is defined by the sample length. The frequency range of the bins is defined by the sample rate. The Arduino is limited by the amount of memory available to store the sample and the results. Also it is limited by the processing power it has. This in turn limits the refresh rate you can do an FFT.
Just like a half inflated balloon you squeeze one part only for another part to swell up. The same is true of the number of bins / refresh rate you can achieve.
The contents of a bin only represent a specific frequency if the sample length is a multiple of the input waveform. So your simplistic idea of a bin corresponding to a single frequency is wrong.

In practice any real wave being sampled has discontinuities at each end and this produces spill over into all adjacent bins. So, no mater what the processor you will never get just one bin responding to just one frequency for any real signal.

Look at it this way, what you are trying to do is to construct a man powered aircraft when you have not yet learned to stand up. This is not meant as an insult, it is to show you just how far over your head this project is with your current state of knowledge.

For example did you know the FFT produces numbers that are complex. Do you know what complex numbers are? These are numbers with two parts, a real an an imaginary part, often expressed as X, iY where i is the square root of -1.

rexhex

I believe it was you a few years ago that said I needed to take a computer science class before you could help me out. I did well in two on C++ and one on assembly language college classes. My math background is that I have gone through trig and some of calc. It took me a lot of work to get through these math classes but I managed it and plan to do the same with this project with or without FFT.

"For each 'bin' N the frequency is N * Sample_Rate / FFT_Size" - johnwasser.

In practice any real wave being sampled has discontinuities at each end and this produces spill over into all adjacent bins. So, no mater what the processor you will never get just one bin responding to just one frequency for any real signal.
[/quote

I am aware of this but I dont think that means that 23hz, for examle, would be strongly recognized by a bin looking at 37hz. This is what I am talking about. Because I don't understand why you think this is not true I am going to figure out how to write a processing program that lets me look at the data visually so I can understand its behavior better.

I appreciate that you say you don't mean this as an insult but I would not say your analogy compares to my situation.

jremington

#23
Nov 14, 2015, 01:15 am Last Edit: Nov 14, 2015, 01:28 am by jremington
@rexhex: How many  "bins" do you need?

Each bin represents a range of frequencies, not just one, and you can add adjacent bins together to have the range as wide as you like.

I think you need to experiment a bit with the FFT. Here is a sample program that generates a known signal consisting of two distinct frequencies, then transforms that.

Change the frequency values and the FFT_N parameter to see how that affects the results.

Note: the calculated amplitudes are off by a factor of two as an artifact of ignoring "negative frequencies".

Code: [Select]
/*
 fft_test_sine
 example sketch for testing the fft library.
 This generates a simple sine wave data set consisting
 of two frequences f1 and f2, transforms it, calculates
 and prints the amplitude of the transform.
 */

// do #defines BEFORE #includes
#define LIN_OUT 1 // use the lin output function
#define FFT_N 32 // set to 32 point fft

#include <FFT.h> // include the library

void setup() {
  Serial.begin(9600); // output on the serial port
}

void loop() {
  int i,k;
  float f1=2.0,f2=5.0;  //the two input frequencies (bin values)
  for (i = 0 ; i < FFT_N ; i++) { // create 32 samples
    // amplitudes are 1000 for f1 and 500 for f2
    k=1000*sin(2*PI*f1*i/FFT_N)+500.*sin(2*PI*f2*i/FFT_N);
    fft_input[2*i] = k; // put real data into even bins
    fft_input[2*i+1] = 0; // set odd bins to 0
  }
  fft_reorder(); // reorder the data before doing the fft
  fft_run(); // process the data using the fft
  fft_mag_lin(); // calculate the magnitude of the output
  // print the frequency index and amplitudes
  Serial.println("bin  amplitude");
  for (i=0; i<FFT_N/2; i++) {
    Serial.print(i);
    Serial.print("       ");
    Serial.println(fft_lin_out[i]);
  }
  Serial.println("Done");
  while(1); //wait here
}

rexhex

JRemington, thank you for your informative response. I have been out of town for the past few days but I will hopefully get a chance to test this out tonight. If bins can have a wide frequency range then I probably need only five. 20hz - 60hz = sub bass response : 60hz - 250hz = bass response : 250hz - 2khz = midrange response : 4khz - 6khz = high midrange response : 6khz - 20khz = high frequency response.

You are  right that I need to experiment with FFT. I am going to learn how to get "processing" to graph the data output from the FFT on the arduino so I can visually see whats going on in real time while experimenting.

pjrc

Maybe this can help with understanding the FFT limitations? 

https://youtu.be/wqt55OAabVs?t=34m55s

Skip forward to 40:24 for detailed section which explains the windows and spectral leakage that Grumpy_Mike talks about.  If you're not applying a window function before the music data goes into the FFT, you'll have spectral leakage over all the bins from every frequency which isn't exactly lined up with each bin.

The FFT isn't magic.  It analyzes for the spectrum of infinitely repeating waveforms.  For real signals that aren't perfectly aligned to the time period of the FFT, spectral leakage or frequency smearing due to windows functions is an unavoidable reality of Fourier analysis.

The solution is multiplying the waveform by a window function before the FFT computation.  That causes some smearing of the frequencies, which means you want more bins.  As you can see in the video, a 1024 point FFT is easily achievable on more powerful 32 bit boards, due to the faster CPU and larger memory.

jremington

#26
Nov 20, 2015, 10:34 pm Last Edit: Nov 20, 2015, 10:48 pm by jremington
Good point about windowing.

The OpenMusicLabs library has a selection of window functions, but I left the call out of my simple example as it tends to confuse beginners. Unfortunately they are poorly documented, and the default seems to be a Hann window.

To try it out in the posted example, just before the line
fft_reorder();
add the line
fft_window();

rexhex

jremington,

 I have a question about the sample code you posted.

Code: [Select]

for (i = 0 ; i < FFT_N ; i++) { // create 32 samples
    // amplitudes are 1000 for f1 and 500 for f2
    k=1000*sin(2*PI*f1*i/FFT_N)+500.*sin(2*PI*f2*i/FFT_N);
/*
Amplitude = 1000 or 500
2pi*frequency = f->2.0 or f->5.0
the next part of the equation is where my confusion is.
why do you multiply 2pi*f by i/FFT_N This changes the sine wave over time as i changes in the for loop.
Im looking at this on a graphing calculator and what confuses me the most here is that if I remove i/FFT_N keeping the sine wave the same over time nothing appears in the 32 bins, just 0's. With the sine wave changing over time bin 2 and 5 have values relating to their amplitude.
*/
    fft_input[2*i] = k; // put real data into even bins
    fft_input[2*i+1] = 0; // set odd bins to 0
  }


I noticed that changing FFT_N to 256 gave me 127 workable bins which I already knew. But what was interesting to see was that in working on 32 bins having 15 workable if I push a frequency of 19 into the sine wave it goes into bin 13. When expanding it to 127 workable bins the amplitude appears in bin 19. This was interesting to observe and I will continue working on understanding this.

Note: the calculated amplitudes are off by a factor of two as an artifact of ignoring "negative frequencies".
Why is this? Is it because the arduino does not look at negative numbers?

To try it out in the posted example, just before the line
fft_reorder();
add the line
fft_window();
An interesting thing about adding the window function is that it reduced the amplitude data going from 496 to 254 in bin 2 and created dramatically more noise.


Paul Stoffregen,

Thank you very much for that valuable resource. That explains the window function very well. It also shows me some challenges to overcome in the future. When I have some more spare time I would like to watch your whole video. What board would you recommend that lets me still use the arduino language?

Grumpy_Mike

Quote
Why is this? Is it because the arduino does not look at negative numbers?
Nothing to do with the Arduino. A negative frequency does not exist, it would be a waveform propergating backwards in time. It is a result of the math you have to do.

jremington

Quote
in working on 32 bins having 15 workable if I push a frequency of 19 into the sine wave it goes into bin 13.
That effect is very well understood, and is called "aliasing".

You are violating one of the assumptions for using the discrete Fourier transform, which is that frequencies of more than half the sample frequency do not appear in the sample.

Go Up