Weird VU Meter Problem.

I'm trying to make a VU meter inputting audio from my computer, analyzing it and then outputting it to LEDs. It sorta works...but it's as if it has a huge delay in it or something because when I stop the music, it takes about 10 seconds for the LEDs to gradually fade back down. I'd appreciate your input! Thanks. The code was modified from a PIC

int SoundLevel;
unsigned int Counter=0;
unsigned char BarGraph;
unsigned char i = 0;
unsigned int SoundMin = 1023;
unsigned int SoundMax = 0;
unsigned int SoundValue;
unsigned char j;

void setup()
{
  Serial.begin(9600);
  pinMode(2,OUTPUT);
  pinMode(3,OUTPUT);
  pinMode(4,OUTPUT);
  pinMode(5,OUTPUT);
  pinMode(6,OUTPUT);
  pinMode(7,OUTPUT);
  pinMode(8,OUTPUT);
  pinMode(9,OUTPUT);  
}
  
void loop ()
{
      SoundValue=analogRead(0);

            //Since we are trying to measure amplitude, track the max and min values

            if(SoundValue > SoundMax)
            SoundMax = ((7*SoundMax) + SoundValue) >> 3;
//Here we are filtering the max value so it doesn't update too fast, and to discard
//the momentary spikes. The >> 3 is the same as /8, but faster

            if(SoundValue < SoundMin)
            SoundMin = ((7*SoundMin) + SoundValue) >> 3;
//Here we are filtering the min value so it doesn't update too fast and to discard
//the momentary dips. The >> 3 is the same as /8, but faster

            SoundLevel = (SoundMax - SoundMin) - 98;
//Take the amplitude, minus a little, because there will be a little background noise
            if(SoundLevel < 0) SoundLevel = 0;

//Calculate the number of bars to light up.
            if(SoundLevel > 600)//If it's too big
                  BarGraph = 9;      //just use the maximum 9 bars
            else
                  BarGraph = SoundLevel / 60;

//Make the amplitude smaller by decreasing the max and increasing the min over time
//This will allow the displayed sound level to come back down, after a loud noise
            if(Counter++ >= 4){
                  if(SoundMax > SoundMin) SoundMax--;
                  if(SoundMin < SoundMax) SoundMin++;
                  Counter = 0;
            }//if

    /*   
      A less code intensive method to do the following would be using port manipulation:
      PORTD = B10101000; // sets digital pins 7,5,3 HIGH
      PORTD maps to Arduino digital pins 0 to 7
      PORTB maps to Arduino digital pins 8 to 13 
      
      http://www.arduino.cc/en/Reference/PortManipulation
      To make the following code easier to follow/modify, I've used digitalWrite instead.   
  */

      
  if (BarGraph==1){
     PORTD = B00000000; 
     PORTB = B00000000; 
           
  }   
  
  else if (BarGraph==2){  
     PORTD = B00000100; 
     PORTB = B00000000;
    
  }
  
   else if (BarGraph==3){  
     PORTD = B00001100; 
     PORTB = B00000000;
    
  }
  
   else if(BarGraph==4){  
     PORTD = B00011100; 
     PORTB = B00000000;
    
  }
  
   else if (BarGraph==5){  
     PORTD = B00111100; 
     PORTB = B00000000;
    
  }
  
    else if (BarGraph==6){  
     PORTD = B01111100; 
     PORTB = B00000000;
    
  }
  
    else if (BarGraph==7){  
     PORTD = B11111100; 
     PORTB = B00000000;
    
  }
  
    else if (BarGraph==8){  
     PORTD = B11111100; 
     PORTB = B00000001;
    
  }
  
    else if(BarGraph==9){  
     PORTD = B11111100; 
     PORTB = B00000011;
    
  }
 //   Serial.print(BarGraph);
    delay(10);
    
}//main

What is the analog pin connected to?

This:

            if(Counter++ >= 4){
                  if(SoundMax > SoundMin) SoundMax--;
                  if(SoundMin < SoundMax) SoundMin++;
                  Counter = 0;
            }//if

Can reduce SoundMax and increase SoundMin by 20 each second. If SoundMax is over 200 than it would take 10 seconds or more to bring it down to zero. Perhaps that is the delay you are seeing?

Remember, premature optimization is the root of all evil.

The compiler does a good job of optimizing things for you, especially easy things like shifting for division by powers of 2. Write your code to be easy to understand, then if you run into something that is too slow, optimize it.

Hey Dave,

Thanks for the reply. Analog pin is connected to pin 0.

So...I'm not too sure I understand what you mean by "increase it by 20 each second." Can you give an example please?

Also, what do you mean by premature optimization?

Thanks for your help.

Here is a version of your code that I've cut down so we can see what's happening a little easier.

void loop ()
{
      SoundValue=analogRead(0);

      if(SoundValue > SoundMax)
            SoundMax = ((7*SoundMax) + SoundValue) >> 3;

      if(SoundValue < SoundMin)
            SoundMin = ((7*SoundMin) + SoundValue) >> 3;

      if(Counter++ >= 4){
            if(SoundMax > SoundMin) SoundMax--;
            if(SoundMin < SoundMax) SoundMin++;
            Counter = 0;
      }//if

    delay(10);
    
}//main

You can see that it reads SoundValue and adjusts SoundMax and SoundMin. At the end of the loop it delays for 10mS. On every 5th pass through the loop (once every 5*10ms = 50mS) it gets into the decay code where it reduces SoundMax by no more than one.

If the audio is off then SoundValue will always be zero and so SoundMax will be reduced every time through the decay code.

If the delay code is executed once every 50mS and the decay reduces the value by 1 each time, then one second (1000mS) will see the value of SoundMax change by no more than 20 (1000mS / 50mS = 20).

So if SoundMax has a value of 200, it will take over 10 seconds for it to decay to zero.

There are other issues as well. Arduino's analog inputs measure from 0 to 5v. Audio is AC and so may apply a negative voltage to the Arduino pin. Sampling the waveform at 10mS intervals (100Hz) will not give you an accurate representation of the audio power (you will likely miss short but powerful impulses that should contribute to the reading).

You should condition the input first before reading it with analogRead. Usually this consists of rectification and filtering so that the voltage represents average power. You can find some samples of this in other Arduino VU meter projects.

Premature optimization is the practice of writing code in ways that you believe will be faster or smaller, but before you have any indication that you need the code to be faster or smaller. The result is code that is often harder to read but that provides no actual benefit as far as being faster or smaller.

Programming is hard enough to get right when it's easy to read, making it harder when it doesn't need to be just makes it frustrating. So, instead of doing things like this:

if(SoundValue > SoundMax)
      SoundMax = ((7*SoundMax) + SoundValue) >> 3;

Just write what you mean in the simplest way:

if(SoundValue > SoundMax)
      SoundMax = ((7*SoundMax) + SoundValue) / 8;

The compiler you are using is smart enough to know that division by 8 can be done through shifts (plus many other such optimizations that you probably don't know), just tell it what you want to do and let it decide what the best way is.

If you later discover that some part of the code is too slow or too big or whatever, you can make some changes to fix it. This is related to the rule "If it ain't broke, don't fix it".

Alright, I changed those >>3 to /8 as you suggested. What can I do to my audio input to adjust for the negative voltage?

Below is the original PIC code which worked great with the pic so...perhaps I just converted it wrong? Thanks again for your help.

#include 
#include "adc.h"

//These compiler directives set the compiler options to work with the RE-SBC100.
//They should be included in every RE-SBC100 project.

#pragma config OSC = HS                  //This selects the High Speed oscillator, using the
                                                //external crystal.
#pragma config FCMEN = OFF            //This turns off the Fail-Safe Clock Monitor, which
                                                //would switch from the external crystal oscillator
                                                //circuit to the internal RC oscillator circuit,
                                                //should the HS oscillator fail.
#pragma config PWRT = OFF            //This turns off the Power-up Timer, which holds the
                                                //chip in reset mode for about 65ms, making sure
                                                //supply voltage is stable before executing
                                                //instructions. Turning this on will cause an "Unable
                                                //to enter debug mode" error, on the PICKit 2.
#pragma config BOREN = SBORDIS      //This enables Brown-Out Reset, which resets the chip
                                                //if the supply voltage gets too low.
#pragma config BORV = 3                  //This sets the Brown-Out voltage to the minimum
                                                //setting.
#pragma config WDT = OFF            //This turns off the WatchDog Timer.
#pragma config MCLRE = ON            //This disables the RE3 input put and enables MCLR
                                                //functionality.
#pragma config LPT1OSC = OFF      //Timer1 configured for high power operation (no
                                                //external 32 KHz crystal).
#pragma config PBADEN = OFF            //PORTB<4:0> pins configured as DIO on reset, instead
                                                //of analog inputs.
#pragma config CCP2MX = PORTBE       //CCP2 Module multiplexed with RB3, instead ofRC1.
#pragma config STVREN = ON            //This causes the processor to reset in the event of
                                                //a stack overflow or underflow.
#pragma config LVP = OFF            //This disables single supply ICSP disabled.
#pragma config XINST = OFF            //Instruction set extension and indexed addressing
                                                //mode disabled.
#pragma config DEBUG = ON            //Enable background debug mode on pins RB6 and RB7.
#pragma config CP0 = OFF            //Code Block 0 protection disabled.
#pragma config CP1 = OFF            //Code Block 1 protection disabled.
#pragma config CPB = OFF            //Code Boot Block protection disabled.
#pragma config WRT0 = OFF            //Block 0 write protection disabled.
#pragma config WRT1 = OFF            //Block 1 write protection disabled.
#pragma config WRTB = OFF            //Boot Block write protection disabled.
#pragma config WRTC = OFF            //Configuration register write protection disabled.
#pragma config EBTR0 = OFF            //Block 0 table read protection disabled.
#pragma config EBTR1 = OFF            //Block 1 table read protection disabled.
#pragma config EBTRB = OFF            //Boot Block table read protection disabled.

//These are the bit codes for the bar graph display
unsigned int BarGraphCodes[11] = {
0x0000,
0x0001,
0x0003,
0x0007,
0x000F,
0x001F,
0x003F,
0x007F,
0x00FF,
0x01FF,
0x03FF
};

void WaitMilliSeconds(unsigned int MilliSeconds);

void main (void)
{
       unsigned int SoundMax, SoundMin, SoundValue;
      int SoundLevel;
      unsigned int Counter;
      unsigned char BarGraph;
      unsigned int MilliSeconds = 50;
      unsigned char i = 0;

      SoundMin = 1023;
      SoundMax = 0;

      //Set PORTC bits 0-7 and PORTB bits 0 and 1 as outputs
      TRISC = 0x00;
      PORTC = 0x00;
      TRISB = 0xFC;
      PORTB = 0x00;

      //This opens and configures the ADC
      OpenADC(ADC_FOSC_16 & ADC_RIGHT_JUST & ADC_12_TAD, ADC_CH0 & ADC_INT_OFF  & ADC_VREFPLUS_VDD & ADC_VREFMINUS_VSS, 14);
      SetChanADC(ADC_CH0);
      Counter = 0;

while (1)      //Continue forever
      {

            ConvertADC();                  //Start an ADC conversion
            while(BusyADC());            //Wait until finished
            SoundValue = ReadADC();      //Read value

            //Since we are trying to measure amplitude, track the max and min values

            if(SoundValue > SoundMax)
            SoundMax = ((7*SoundMax) + SoundValue) >> 3;
//Here we are filtering the max value so it doesn't update too fast, and to discard
//the momentary spikes. The >> 3 is the same as /8, but faster

            if(SoundValue < SoundMin)
            SoundMin = ((7*SoundMin) + SoundValue) >> 3;
//Here we are filtering the min value so it doesn't update too fast and to discard
//the momentary dips. The >> 3 is the same as /8, but faster

            SoundLevel = (SoundMax - SoundMin) - 98;
//Take the amplitude, minus a little, because there will be a little background noise
            if(SoundLevel < 0) SoundLevel = 0;

//Calculate the number of bars to light up.
            if(SoundLevel > 600)//If it's too big
                  BarGraph = 10;      //just use the maximum 10 bars
            else
                  BarGraph = SoundLevel / 60;

//Make the amplitude smaller by decreasing the max and increasing the min over time
//This will allow the displayed sound level to come back down, after a loud noise
            if(Counter++ >= 4){
                  if(SoundMax > SoundMin) SoundMax--;
                  if(SoundMin < SoundMax) SoundMin++;
                  Counter = 0;
            }//if

            PORTC = BarGraphCodes[BarGraph] & 0xFF;
            PORTB = BarGraphCodes[BarGraph] >> 8;
      }//while

}//main

//This function delays the program for the specified number of milliseconds
void WaitMilliSeconds(unsigned int MilliSeconds){
      unsigned int i, j;
      for(i = 0; i < MilliSeconds; i++){
            for(j = 0; j < 284; j++);
      }//for
}//WaitMilliSeconds

Any help from anyone is much appreciated.

Anyone, anyone at all? anyone?

If you are waiting for help with :

What can I do to my audio input to adjust for the negative voltage?

then DaveK has given you an answer in reply#3. If you want to adjust for a negative voltage you need to do use external hardware. Try a search on rectification and filtering. You may also want to look at how others solved this in other sound level measuring arduino projects.

You don't say what board you have but if has a serial port then your output to port D is writing to this hardware. Why not use the Arduino digitalWrite to access the pins. Something like this should do what you want:

// function to light LEDs on consecutive pins based on given value void lightLEDs( int value, int firstPin, int nbrLEDs) { for( int i = 0; i < nbrLEDs; i++) { if( value > i ) digitalWrite(firstPin + i, HIGH); else digitalWrite(firstPin + i, LOW); } }

Try this one ;)

/*
      Arduino (simple) LED VU Meter

      Just a simple VU meter without any external component (except LEDs).

      You'll need some LEDs, of course. You can connect them at any port
        of the board you want, but you'll have to modify the code.

      Created at 27/7/09 by giannoug 

*/
#define LEDNUMBER 7
#define LINEINPIN 0

int val = 0; // Variable to store the analogRead() value.
int ledPins[] = { 2, 3, 4, 5, 6, 7, 8 }; // You can change the Pins here ;)

void setup() {
 for (int thisLed = 0; thisLed < LEDNUMBER; thisLed++) {
     pinMode(ledPins[thisLed], OUTPUT); 
 }
}

void loop() {
  val = analogRead(LINEINPIN);
  int ledLevel = map(val, 0, 500, -1, ledCount); // Maps the read value to the LEDs.

   for (int thisLed = 0; thisLed < LEDNUMBER; thisLed++) {
     if (thisLed < ledLevel) {
       digitalWrite(ledPins[thisLed], HIGH);
     } else {
       digitalWrite(ledPins[thisLed], LOW); 
     }
   }
}

Nice use of the map function, which I don't think I ever noticed, and thus haven't used. I'll remember it now though.

One question, analogRead() returns a value from 0 to 1023 (10 bit ADC), so why did you use 500 as the upper limit on the map fromHigh parameter? Maybe 1000 would give a better peak reading.