Calibrating a VU meter using a pot/ dynamic calibration

Hi Folks,
The following is some code I adopted from a website for my LED VU meter. However, one of the issues is that the Sensitivity is sort of HARD-CODED. Is there a way to incorporate a more dynamic calibration going on from the audio signal depending on the song. Imagine a variable standing for 20 (the calibration factor) and changing depending on the signal strength of the cube.

You can view the "signal" strength coming through from A0 from the serial print screen and values usually are around 200 ish for me when driven by an Ipod.

int led[6] = { 3, 4, 5, 6, 7, 8}; // Assign the pins for the leds
int leftChannel = A0; // left channel input
int left, i;

void setup()
{
for (i = 0; i < 6; i++) // Tell the arduino that the leds are digital outputs
pinMode(led*, OUTPUT);*
Serial.begin(9600);
}
void loop()
{
left = analogRead(leftChannel); // read the left channel
Serial.println(left);
Serial.print("\t");
left = left / 20; // adjusts the sensitivity
Serial.println(left);
Serial.print("\t");

  • if (left == 0) // if the volume is 0 then turn off all leds*
  • {*
  • for(i = 0; i < 6; i++)*
  • {*
    _ digitalWrite(led*, LOW);_
    _
    }_
    _
    }*_

* else*
* {*
* for (i = 0; i < left; i++) // turn on the leds up to the volume level*
* {*
_ digitalWrite(led*, HIGH);
}*_

* for(i = i; i < 6; i++) // turn off the leds above the voltage level*
* {*
_ digitalWrite(led*, LOW);
}
}
}*_

This code uses a linear mapping of the analog value to the number of active LEDs. A logarithmic mapping is more common.

What exactly do you want? Usually a VU meter is intended as an indicator of the absolute levels. Thus adopting it to specific songs is somewhat pointless. Unless you want to have it as a kind of mini light show. But then this is pretty hard to do. You would need to know in advance the peak level of the song. Unless the time machine gets invented this is somewhat tricky.

Can you please direct me to a source or improvise on the code to do a logarithmic mapping? It will be great if you could kindly show me how that is done. Complete newb here; hence, no idea how that can be done.

Thanks for asking that question. I guess I was not able to make it as clear as I wanted it to.

The problem I am envision is: let's say that the highest value of the signal I get is around 500ish, and I scale the program to react as much. I am using an Ipod to drive the VU meter at FULL volume. However, should I decrease the ipod volume, the signal strength goes down and so does the number of LEDs that light up. All I want is the user to have control over the sensitivity of the lighting. Does that make sense? Or put it the other way: is there a way to ensure that the VU meter behaves independently of the volume of the ipod?

Also, one other problem is: I could not drive my VU meter with my laptop!! Any reason?

Something wrong with your code:

 for (i = 0; i < left; i++) // turn on the leds up to the volume level
    {
     digitalWrite(led, HIGH);  <<<< Do you mean digitalWrite( i, HIGH)?
    }

What you are looking, is "automatic gain control" loop, or AGC. To make it works, you need a low pass filter in software, look on this web site, should be running average or similar example somewhere in playground / library section. Arduino IDE has smoothing in analog examples code, should be o'k.
Next, change your bargraph to :

   int ledLevelKr = map( left_input , 0, PeakHold_smoothedValue, 0, ledCountKr);

  for (int thisLed = 0; thisLed < ledCountKr; thisLed++) {
    if (thisLed < ledLevelKr) 
      digitalWrite(ledPinsKr[thisLed], HIGH);
    else
      digitalWrite(ledPinsKr[thisLed], LOW); 
  }

You should average you levels.
Because if you sample sound you have a wave and you never know where you sampled the wave.
If you want a good responsive bargraph use something like this:

for (int num=0; num < 4; num++) {
sound[num]= analogRead (inputPIN);
if(num==3) {
soundav=(sound[0]+sound[1]+sound[2]+sound[3])/4; // average sound levels

For you pot controle, use a lineaire pot meter a logarithmic will work to but won't give you a fine control.
You have to determine a max and minimum value. I choose between 15 and 25 because of your 20.

potControl = analogRead(potPIN);   // connect a linear potmeter on potPIN 
levelControl = map(potControl, 0, 255,15,25); // you map the values of your pot to a more desired level between 15 and 25 
left = left / levelControl;     // youre code

Use Magician his bargraph code, which is much better and more efficient.

Magician:

 for (i = 0; i < left; i++) // turn on the leds up to the volume level

{
    digitalWrite(led, HIGH);  <<<< Do you mean digitalWrite( i, HIGH)?>>>>>> Yes, extremely sorry for this. Somehow, the original code has digitalWrite (led[i], HIGH).
   }



What you are looking, is "automatic gain control" loop, or AGC. To make it works, you need a low pass filter in software, look on this web site, should be running average or similar example somewhere in playground / library section. Arduino IDE has smoothing in analog examples code, should be o'k.
Next, change your bargraph to :


int ledLevelKr = map( left_input , 0, PeakHold_smoothedValue, 0, ledCountKr);

for (int thisLed = 0; thisLed < ledCountKr; thisLed++) {
    if (thisLed < ledLevelKr)
      digitalWrite(ledPinsKr[thisLed], HIGH);
    else
      digitalWrite(ledPinsKr[thisLed], LOW);
  }

Thank you so much for putting this in. I was wondering whether you can elaborate / comment on the code. I get the overall picture, but am having a hard time reading what this does:

int ledLevelKr = map( left_input , 0, PeakHold_smoothedValue, 0, ledCountKr);

where did you get the variables? So I am looking for a AGC? Is the code that you put in part of it, or something attached to it. When you are talking about AGC, are you talking about smoothing as described here: http://arduino.cc/en/Tutorial/Smoothing

Thanks and looking forward to your reply.

Cinezaster:
You should average you levels.
Because if you sample sound you have a wave and you never know where you sampled the wave.
If you want a good responsive bargraph use something like this:

for (int num=0; num < 4; num++) {

sound[num]= analogRead (inputPIN);
if(num==3) {
soundav=(sound[0]+sound[1]+sound[2]+sound[3])/4; // average sound levels

So this part averaging every 4 samples it takes. Am I right? Moreover, will it quick enough to response to the music? Extremely sorry for not being able to understand. Providing a little more context will help me.

Cinezaster:
For you pot controle, use a lineaire pot meter a logarithmic will work to but won't give you a fine control.
You have to determine a max and minimum value. I choose between 15 and 25 because of your 20.

potControl = analogRead(potPIN);   // connect a linear potmeter on potPIN 

levelControl = map(potControl, 0, 255,15,25); // you map the values of your pot to a more desired level between 15 and 25
left = left / levelControl;     // youre code




Use Magician his bargraph code, which is much better and more efficient.

If I do NOT go for a pot control, can you help me devise the code such that the calibration is logarithmic, instead of linear?

Also, when you guys are talking about the bargraph, which portion of the code are you referring to: the one where the LEDs are toggled?

In your arduino IDE open: File/Examples/Display->bargraph.
I copy/paste code from there. Play, and try to understand how it works.
There is a line in example sketch:

  // map the result to a range from 0 to the number of LEDs:
  int ledLevel = map(sensorReading, 0, 1023, 0, ledCount);

which does the main function of the bargraph, it's scale numbers of lighting leds according to sensorReading value. The only issue with this, that a range from 0 to 1023 is hardcoded, and as it can't adapt itself to different music levels, it's not what you want. This is where AGC steps in.
Upper limit (1023) you should replace with another variable (peakHolder or whatever). Than, arduino should keep a track of the peak values (they call it envelope detector, wikipedia it), and change peaKHolder accordingly. One more things, to make it moves with different speed up and down, like real peak detector does, you would need second variable to store previous value, plus coefficient to regulate a falling speed.

 int peakHolder, old_peakHolder;
 float falling_s = 0.95;   //<<<- closer to 1 slower

Than in main loop()

old_peakHolder = peakHolder;  //Save old data  for smoothing;
 
 peakHolder = analogRead(...);  //What is pin number
 
//Smoothing, so it fall down gradually.  
  if ( peakHolder  <  ( old_peakHolder * falling_s ) )
    peakHolder = ( old_peakHolder * falling_s );

  int ledLevel = map(sensorReading, 0, peakHolder, 0, ledCount);  // And here comes BarGraph

you rock magician! thanks a ton. will do it tomorrow. too sleepy now!

I have a quick question:

When I am sampling the audio, does it need to come from a MONO audio jack, or can I get it from a stereo? If I am to get it from a stereo do I need 2 audio inputs, aka 2 analog inputs? I don't want to go for that. However, I have a female audio jack that is stereo. Will it matter if I put in a mono male jack in it?

I am really confused.

It's up to you. Two channels stereo are independent, but very close in most audio program. They differ in phase, that isn't important for your project.
So you can take just one left channel, or connect two in parallel. Have you considered to build two channel display?
Btw, you need a DC offset for analog input,.

Awesome. Fixed that part. Now I am facing a power issue. I have a few op amps that amplify my signal; however, I am just running of a +12V DC supply! How can I get the -V that is need for my opamp to work? Any ideas apart from going through a virtual ground, which is too complicated for me to understand atm?

Hey Guys,

So When I am running the following program:

int bassPins[] = { 3, 4, 5}; // Assign the pins for the leds
int treblePins[] = {6, 7, 8};
int bassInput = A1; // left channel input
int trebleInput = A2;
int bass, treble, i;

void setup() {
for (i = 0; i < 3; i++) { // Tell the arduino that the leds are digital outputs
pinMode(bassPins*, OUTPUT);*
_ pinMode(treblePins*, OUTPUT);_
_
}_
_
Serial.begin(9600); // Uncomment to enable troubleshooting over serial._
_
}*_

void loop() {
* //read in voltages*
* bass = analogRead(bassInput);*
* treble = analogRead(trebleInput);*
* String b = "bass: ";*
* String bout = "";*
* bout = b + bass;*
* String tout = "";*
* String t = "treble: ";*
* tout = t + treble;*

* Serial.print(bout);*
* Serial.print("\t");*
* Serial.println(tout);*

* bass = bass / 80; // adjusts the sensitivity*
* treble = treble/200;*

* //bass*
* if (bass == 0) { // if the volume is 0 then turn off all leds*
* for(i = 0; i < 3; i++) {*
_ digitalWrite(bassPins*, LOW);
}
}
else {
for (i = 0; i < bass; i++) { // turn on the leds up to the volume level*
digitalWrite(bassPins*, HIGH);
}*_

* for(i = i; i < 3; i++) { // turn off the leds above the voltage level*
_ digitalWrite(bassPins*, LOW);
}
}*_

* //treble*
* if (treble == 0) { // if the volume is 0 then turn off all leds*
* for(i = 0; i < 3; i++) {*
_ digitalWrite(treblePins*, LOW);
}
}
else {
for (i = 0; i < treble; i++) {// turn on the leds up to the volume level*

digitalWrite(treblePins*, HIGH);
}
for(i = i; i < 3; i++) {// turn off the leds above the voltage level*

digitalWrite(treblePins*, LOW);
}
}
}*

Even when there is nothing connected to A1 and A2, the serial monitor reads 1021 or 1023. Why? Is there a way to stop it?
Moreover, whenever the song ends or if I stop the song, all the LEDs are HIGH. I could not figure our why this will be the case._

any help guys??? :frowning: