Here's something I've been working on for just a bit. It's a code that takes an analog input (music) and outputs information to led's connected to the digital outputs.
But wait! There's more.
This code also has build in smoothing, to give you a nicer visual output.
The code is fully explained below, with notation at every line.
/*
* A optimised VU driver for arduino with analog smoothing
*
* Created By:
* Dox Drakes
*/
//settings for leds and smoothing
const int NUMLED = 5; //number of outputs
const int NUMSAMPLE = 20; //Samples taken. More = Smoother but also slower.
const int DELAY = 5; //Delay to keep it stable
//which pins are you using for inputs?
const int refPIN = A0; //this value should always be zero
const int voltPIN = A2; //this is the audio input pin
//which pins are you using for outputs?
const int ledPIN[NUMLED] = {2,3,4,5,6}; //Number of each output pin
//value scaling. change these to make it more or less sensitive.
//the analog input signal ranges from 0 to 1024, so find some numbers in between there.
const int voltTHRESH[NUMLED] = {50, 200, 350, 600, 850}; //Value asscociated on an ON led
int vREF = 0; //Sets reference voltage to 0 (leave this alone)
//sample variables for smoothing (leave these alone)
int SAMP[NUMSAMPLE] = {0}; //value of sample
int INDEX = 0; //number of sample from 1-[NUMSAMPLE]
int TOTAL = 0; //running total of sample values
int AVG = 0; //average of sample values over [NUMSAMPLE]
void setup() {
//Setup for the smoother
for (int READ = 0; READ < NUMSAMPLE; READ++){ //initalizes readings
SAMP[READ] = 0; //sets readings to 0
}
//setup for the led outputs
for (int p = 2; p<=6; p++){ //cycles through pins 2-6
pinMode(p, OUTPUT); //sets the current pin to OUTPUT
}
//setup for analogue inputs
pinMode(refPIN, INPUT);
pinMode(voltPIN, INPUT);
}
void loop() {
//Here is the start of the smoother
TOTAL -= SAMP[INDEX]; //Subtracts the last sample
SAMP[INDEX] = analogRead(voltPIN); //sets the sample to the analoge reading (0-1024)
TOTAL += SAMP[INDEX++]; //adds the sample to the total and moves INDEX to the next value
if (INDEX >= NUMSAMPLE){ //checks if limit of samples is reached
INDEX = 0; //resets INDEX to 0, restarting the process
}
AVG = TOTAL / NUMSAMPLE; //Calculates the average
//Here is where the led control starts
int vREF = analogRead(refPIN); //sets vREF to the value of our reference pin
for (int i=0; i<NUMLED; i++){ //runs through each led until all have been checked
if (AVG >= vREF + voltTHRESH[i]){ //finds out if the led should be on
digitalWrite(ledPIN[i], HIGH); //sets the current PIN to HIGH if true
}
else{
digitalWrite(ledPIN[i], LOW); //sets the current PIN to LOW if false
}
}
delay(DELAY); //delay for stability
}
Looks good except the perfectionist in me is forcing this comment. I think for an accurate representation of the audio you want the average of the last (in your case) 20 samples. I don't think your code does that.
With each new sample you subtract the previous sample then add the new one. I don't think that will best represent what the audio is doing. What you should do is just average the 20 most recent samples. Perhaps like this ...
For each sample:
SAMP[INDEX] = analogRead(voltPIN); //sets the sample to the analoge reading (0-1024)
TOTAL =0; // start total at zero with each new sample
for (int j =0; j <NUMSAMPLE; j++) // calculate total of last 20 samples
{
TOTAL += SAMP[INDEX];
}
AVG = TOTAL / NUMSAMPLE; //Calculates the average
INDEX ++;
[put your check for INDEX == NUMSAMPLE here]
You have a ring buffer - generally a binary size such as 16, so that the index can be controlled by a mask.
The ring buffer is zeroed in setup(), and the tally zeroed.
When each value is taken, the old value in the buffer is subtracted from the tally; the new value is added to the tally and also stored in the buffer; the index is incremented and masked. A copy of the value in the tally is right-shifted to match the mask; that is the running average. For 16 values, the mask is 0x0F and the right shift is four.
This is straight-line code; no loops, no "if"s to manage the index, one subtraction, one addition and one bit shift.
Thanks Paul. I'm very interested in reducing code without loosing functionality. If you would be so kind to check if this is correct for what you described:
int audioSamples[16];
byte index =0;
unsigned int tally =0;
int avg =0;
const byte mask =0x0F;
// Read audio level and calculate average of last 16 samples
tally -= audioSamples[index]; // subtract the oldest sample
audioSamples[index] = analogRead(audioRIGHT_PIN); //read new sample
tally += audioSamples[index];
avg = tally >>4; // average value of last 16 samples
index ++;
index &= mask; // limits index to 0-15
My only issue now is I'm having trouble understanding how the average can be correct with a less than full buffer. I would like the average to be correct as the buffer is filling and I'm thinking I'll need more code. My project is currently using 52 samples which I'll change to 64 (to use this code).
Your thoughts?
-Dennis
My project uses the average of 52 samples, which I'll change to 64 to use this code.
Thanks Paul, I'll try this code in my project later today.
I'm very interested in all things that will allow the program to "run faster" because I'm processing stereo audio channels to show both peak and average levels on different sets of LEDs. In addition, the frequency spectrum (from MSGEQ7 chips) is displayed on two 8x8 matrices. The program has been working for awhile but each change to shorten execution time produces a perceived faster audio response. The poor Uno R3 is working it's ass off!