Go Down

Topic: 6 PWM LED Audio Spectrum Analyzer (Read 3375 times) previous topic - next topic

Valygar

Jul 09, 2010, 01:44 am Last Edit: Jul 09, 2010, 02:00 am by Valygar Reason: 1
This is my first project with arduino. I wanted to do an analyzer for my PC. I liked the windows media player analyzers a lot, trying to watch patterns on the screen from the music and so on...

I searched a bit and I found the minim library for Processing. Thanks to that and the examples of FFT I was able to do it with not much effort (I thought it was gonna take me at least one month).

Here's a video with the result. Now I've seen blit video and other youtube videos of analyzers is not that great, but it's one of the few made playing with the brightness rather than the number of LEDs.

[media]http://www.youtube.com/watch?v=pH9U5miKfcc[/media]

I think is a good project to try because all you need is your PC, 6 LEDs and resistors and you are settled  ;) .

Most of the time was spent trying to see which frequency bands to show and so on. Finally, I chose to do 240 mean linear bands, and afterwards to take the maximum of a certain range of them.

Here's the previous version with another song, it had some problems with syncronization (I added the bitwise operations later, and I had to change the brightness methode)
[media]http://www.youtube.com/watch?v=mxk41kob8nc[/media]

Here is the code if you want to try it. If you try it and change some values or something, please give feedback. I don't think I'll change much of the code, but the frequency bands are open to discussion.

Read the code carefully, there are some things you should change like serial ports, some recomendations and a lot of explanations.

Arduino code:
Code: [Select]
/*You'll need 6 LEDS to make it work, connected to the PWM pins on the arduino duemilanove.
If you have the arduino mega, you could put more LEDs, feel free to change the code.

I used a 100 ohm resistor, you can watch the LEDs I used (and a demo) on this video:

http://www.youtube.com/watch?v=pH9U5miKfcc

*/

const int vectledPin[]={3,5,6,9,10,11};

/*These are the values that will be given to the PWM ports on the arduino. The arduino will receive values between 0 and 30,
that measures the "volume" linearlly. However, the LED's don't bright linearlly, so I tested it a bit and put these values
as my own testing. I used the DIMMER sketch example on the arduino program to know what values to use.
Feel free to change them. One small change would be to make two vectors, as I used two different types of LEDs.*/

const int vectbright[]={0,1,2,3,4,6,8,19,14,18,22,27,32,38,47,56,66,76,86,96,105,114,122,130,138,148,162,190,222,255,255};
int i=0;

void setup()
{
 // initialize the serial communication:
 Serial.begin(9600);
 // initialize the ledPin as an output:
 for (i=0;i<6;i++)
   pinMode(vectledPin[i], OUTPUT);
 i=0;
}

void loop() {
 byte brightness;
 byte led;

 // check if data has been sent from the computer:
 if (Serial.available()) {
   // read the most recent byte (which will be from 0 to 255):
   brightness = Serial.read();
   led=(brightness>>5);
   //This reads what LED it is (it is stored on the 3 high bits)
   
   brightness=((brightness)&0x1F);
   //and this reads the 5 lower bits only. It puts a 0 in the higher ones.
   
  analogWrite(vectledPin[led], vectbright[brightness]);
  //and finally, using both vectors and the brightness index, the PWM value is sent
 }
}


In the next post, Processing code (Needed more space for it)

Valygar

#1
Jul 09, 2010, 01:46 am Last Edit: Jul 09, 2010, 01:47 am by Valygar Reason: 1
Processing code (remember, you'll need minim library)

Code: [Select]
/*A very important note. You have first to compile and upload the arduino code, otherwise
it will give you a serial error.

Also, if you want the stereo mix as an input (the sound that is going through your speakers), you
have to change it in windows (or mac and linux as well, i'm not sure) as the main input for recording.
This is easily done in windows vista and 7 right clicking the general volume button, choosing recording
options, and selecting stereo mix as the main one. You'll probably have to switch off (software) the
microphone input, too. If you don't see this options, right click your microphone, and check all
options to make sure stereo mix (or something like that) is shown. You'll have to restart this Processing
sketch in order to make it work.

The things you need to make it work are told in the arduino code.
*/
import processing.serial.*;
import ddf.minim.analysis.*;
import ddf.minim.*;

Serial port;

AudioInput in;
Minim minim;
FFT fftLin;
//I used an example of FFT as a base, and tweaked it a bit.

float vectormedias[]=new float[6]; // this keeps the averages of the 6 bands of frequency.
float magnitud=4; //general setting, it multiplies all volume signal.
float volumen;  // this measures the general volume, but it changes slowly (like a P control) to avoid noise
int volcaptado; // this is the final volume sent to arduino for a certain led. It gives the index to the brightness vector.

void setup()
{
 println(Serial.list());
 if (port==null)
   port = new Serial(this, Serial.list()[1], 9600);
   //perhaps in your case is Serial.list()[0]
 size(512, 300, P3D);
 volumen=1;

 minim = new Minim(this);
 in = minim.getLineIn(Minim.STEREO, 2048);
 fftLin = new FFT(in.bufferSize(), in.sampleRate());
 fftLin.linAverages(240);
 //It takes 240 bands of frequency (linear). This is to avoid noise or strange sounds. (perhaps not needed)
// Later, the maximum of a certain range of this bars will be taken.
 rectMode(CORNERS);
}

void draw()
{
 background(0);
 // perform a forward FFT on the samples in jingle's mix buffer
 // note that if jingle were a MONO file, this would be the same as using jingle.left or jingle.right
 fftLin.forward(in.mix);

 noStroke();
 fill(255);
 int w = int(width/6);
 //next follows a for that gets the maximum of certain ranges of bars, for 6 LEDs
 for(int i = 0; i < 120; i++)
 {
   if (i<=1)
   {
     if (vectormedias[0]<2*fftLin.getAvg(i))
       vectormedias[0]=2*fftLin.getAvg(i);
     // Each range has a multiplier, to adjust the visual response. This is based on tests, feel free to change it.
     // I changed them a lot, feel free to change what bars are included too. Some will be OK for certain music, or not.
   }
   else if (i<=5)
   {
     if (vectormedias[1]<1.2*fftLin.getAvg(i))
       vectormedias[1]=1.2*fftLin.getAvg(i);
   }
   else if (i<=12)
   {
     if (vectormedias[2]<1.6*fftLin.getAvg(i))
       vectormedias[2]=1.6*fftLin.getAvg(i);
   }
   else if (i<=22)
   {
     if (vectormedias[3]<1.75*fftLin.getAvg(i))
       vectormedias[3]=1.75*fftLin.getAvg(i);
   }
   else if (i<=55)
   {
     if (vectormedias[4]<2*fftLin.getAvg(i))
       vectormedias[4]=2*fftLin.getAvg(i);
   }
   if (i<=160)
   {
     //This is the last one. It is multiplied by the bar index, to give more weight to the final frequencies (that tend to be lower always).
     //This gives good feedback for noise, high pitch sounds, or percusion instruments.
     vectormedias[5]=i*fftLin.getAvg(i)/300+vectormedias[5];
   }
   if (i==119)
   {
     for (byte k=0;k<6;k++)
     { delay(10);
       fill(255);
         //The following number is important. It is the level that is taken as real input, if you catch noise
        //you will have to make it a bit bigger. If you don't have any noise, perhaps a lower number is better
         if (in.mix.level()>0.001)
         { /*This keeps the volume changing with the in.mix level. However, we don't want it to change
            very fast, because in.mix.level is very variable. Otherwise, you'll see low sounds as big as high sounds.
            And we don't want it to be constant, otherwise when the volume is low you won¡t see anything.
            Feel free to change the values to change faster or slower though (keeping the sum as 1.00, don't mind the *10)*/
           volumen=volumen*0.998+in.mix.level()*0.002*10; // the *10 is cause the in.mix.level is usually 0.1. I wanted it to be closer to 1.
             //println(in.mix.level());  //uncomment this if you want to try to change the volume control
            volcaptado=int(magnitud*vectormedias[k]/volumen);
            //This is the last step. It gives the amount of volume to the graphic bars and the arduino.
             if (volcaptado>300)
               volcaptado=300;
               //we don't want it to be VERY big.
             volcaptado=volcaptado/10;
             //the arduino will get a value between 0 and 30 as the volume input. A same, but it is necessary as we'll need 3 bits to select the LED,
             //and we have 5 more (up to 31). I could have used 0 to 31, but I was lazy.
           
            port.write((k<<5)|byte(volcaptado));
            //This gives the arduino wich led it is with K, and the value of volcaptado.
            // it works like this:  XXX | XXXXX
            // first the number of the LED, and last the amount of volume. The serial port only send one byte (eight bits) so it has to be like that.
            // Another option would be giving a certain number to sync arduino and processing, like 0, v1, v2, v3, v4, v5, v6 ,  (and repeat).
            // However, the LED's change so fast that with 30 values I found it enough. Feel free to change it, though.
            rect(k*w, height, k*w + w, height - magnitud*vectormedias[k]/volumen);
            //and this draws the rectangles in your processing window If you don't want this feedback, just comment the line above.
         }
         else{
           //We get here if the in.mix.level is very low. So no sound is received. You'll receive noise only.
           volumen=0.4;
           //We put a low volume as a base (so when a sound comes in, the LED will bright more than usual)
           port.write((k<<5));
           // We tell the arduino to put all LED's with a volume of 0.
           delay(100);
           // change the delay if you want, or even delete it.
         }
     
       vectormedias[k]=0;
       // we delete the previous maximums, to make new ones in the next iteration.

     }
   }

 }

}

void stop()
{
 // always close Minim audio classes when you are done with them
 in.close();
 // always stop Minim before exiting
 minim.stop();

 super.stop();
 // send led=0 before exiting
 port.write(0);
}


If you make some improvements, please reply on this thread or send me an e-mail. Thanks for the feedback!  :)

PD: I will post this also on the spanish forum, as I'm spanish and all. Feel free to answer there if you prefer that.

Neodudeman

Ah, this is awesome. Great work.

I was looking for something like this to adapt to an LED matrix.

Hopefully I'll be able to use it.

hulios

great work! can you please tell me how the leds can react directly from the pc and not from the mic? it is posible that? thanks

Go Up