Arduino VU Meter Issues

So I made a VU meter by cutting an audio cable, amping up the signal with a simple amp and then connecting the output of the amp to an arduino input pin. Based on the magnitude of the volume (beat) input, a certain amount of LEDs in my meter would light up.

The problem: The lights would jump rather quickly (for example between lighting up 7 LEDs and then down to 2 without any gradual fade from 7 down to 2). I tried adding some delays, that didn’t really help though. Perhaps if I re-did my code…or…added some smoothing, that would help?

Advice is much appreciated. Thanks!

This is a good circuit for detecting audio power:

So what you would be displaying is instantaneous amplitude of the audio at relatively random times, which will not give you a very nice display. There's quite an art to making nice audio meters, and it takes some tweaking to get it right. The hardest part to sort out is getting the ballistics of the meter behaving well. Meters usually fall into the following categories:

  • Peak - which displays the maximum amplitude of the signal over the past few milliseconds - often these will have a separate segment that also show the maximum peak from the past several seconds (or maybe forever). To get one of these easily, take maybe 10 (or more) readings of the input pin over a period of time and choose the biggest value to light your LEDs from. The only downside of a peak meter is that it doesn't always correspond to how loud the audio actually sounds, especially for commercial recordings. Anyway, this will help smooth out your display.

  • Power - this meter tries to measure something closer to the "loudness" of the sound by keeping a kind of running average of the audio (technically the RMS - square root of the average of the squared signal values over a moving window of time), This might be hard to measure on the arduino without an external circuit to do the averaging for you, since you probably can't sample fast enough to calculate it yourself well. Still the idea of keeping a running average is still useful.

  • VU - Volume Units; I suspect you don't really want volume units, which you really need a needle indicator to have look right. These keep a kind of average of the signal, but use the inertia of the meter's needle to smooth things out, overshoot, and other strange behaviors that some folks like. Anyway, it'd look strange on LED's to try to simulate these.

But a peak meter is easiest, and probably a good place to start.

With all of these, the decay ballistics are important, and give a nice smooth decay effect when the signal stops. I think you'd get decent results by first getting the peak of several readings, then apply an averaging filter to slow the decay. You could try something like:

static int lastLEDLevel = 0;

int newReading = analog.Read();
int levelForLEDs;

if( newReading > lastReading )
    levelForLEDs = newReading; // Never miss a new peak
else
    levelForLEDs = (lastReading*7 + newReading) / 8; // But smooth out the decay

lastLEDLevel = levelForLEDs;

To get a slower decay, you could use 15 and 16 instead of 7 and 8, or if you wanted a faster decay, you could use 3 and 4 there.

Anyway, I hope that helps.

-Paul

@macegr - thanks for your thorough reply. Jeeze, it seems something as easy as making LEDs blink to a certain brightness/threshold would be much simpler that such an in depth external circuit.

@Paul V - Thanks for your thorough reply as well and explanation of the different types of circuits. Thanks for the code as well. So that causes a gradual fade based on the numbers 7 and 8...So if it jumps to level 10 for example, it'll fade down to 9 then 8 then 7...and then get the next reading?

Wouldn't that cause a delay in the overall display?

Thanks.

In non of the above is the mention of the scale. Uniform or liner scale is not how the ear perceives things. To get a reading that looks like it follows the sound you have to calibrate it in dBs (deci bells). This is a logarithmic scale and takes a bit more processing power to get it right.

can't you just use an capcitor?