// Larry Athey, 12-03-2022: this is not my original code. This was originally the Adafruit
// VU Meter Necktie which I cleaned up and modified so it had two Neopixel data channels and
// used two concentric Neopixel rings per channel. The slider was added so I could create a
// variable signal on the audio input to test the meters.
# include <Adafruit_NeoPixel.h>
#define N_PIXELS 40 // Number of pixels you are using
#define LED_PIN_A 2 // NeoPixel LED strip A is connected to GPIO #0
#define LED_PIN_B 3 // NeoPixel LED strip B is connected to GPIO #1
#define AUDIO_PIN 2 // Audio feed is attached to GPIO #2
#define DC_OFFSET 0 // DC offset in audio signal - if unusure, leave 0
#define NOISE 100 // Noise/hum/interference in audio signal
#define SAMPLES 60 // Length of buffer for dynamic level adjustment
#define TOP (N_PIXELS + 1) // Add values to allow dot to go slightly off scale
#define PEAK_FALL 40 // Speed of peak falling dot
byte
peak = 0, // Used for falling dot
dotCount = 0, // Frame counter for delaying dot-falling speed
volCount = 0; // Frame counter for storing past volume data
int
vol[SAMPLES], // Collection of prior volume samples
lvl = 10, // Current "dampened" audio level
minLvlAvg = 0, // For dynamic adjustment of graph low & high
maxLvlAvg = 512;
Adafruit_NeoPixel strip_a = Adafruit_NeoPixel(N_PIXELS,LED_PIN_A,NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip_b = Adafruit_NeoPixel(N_PIXELS,LED_PIN_B,NEO_GRB + NEO_KHZ800);
void setup() {
memset(vol,0,sizeof(vol));
strip_a.begin();
strip_b.begin();
strip_a.setBrightness(255);
strip_b.setBrightness(255);
Serial.begin(9600);
}
void loop() {
uint8_t i;
uint16_t minLvl,maxLvl;
int n,height;
n = analogRead(AUDIO_PIN); // Raw reading from audio input
n = abs(n - 512 - DC_OFFSET); // Center on zero
n = (n <= NOISE) ? 0 : (n - NOISE); // Remove noise/hum
lvl = ((lvl * 7) + n) >> 3; // "Dampened" reading (else looks twitchy)
Serial.print("n = ");
Serial.println(n);
// Calculate bar height based on dynamic min/max levels (fixed point):
height = TOP * (lvl - minLvlAvg) / (long)(maxLvlAvg - minLvlAvg);
// 0L is a long integer value with all the bits set to zero - that's generally the definition of 0
// Why not just check if (height < 0) ???
if (height < 0L) height = 0; // Clip output
else if (height > TOP) height = TOP;
if (height > peak) peak = height; // Keep 'peak' dot at top
// Color pixels based on rainbow gradient
for (i = 0; i < N_PIXELS; i ++) {
if (i >= height) {
strip_a.setPixelColor(i,0,0,0);
strip_b.setPixelColor(i,0,0,0);
} else {
strip_a.setPixelColor(i,Wheel(map(i,0,strip_a.numPixels()-1,30,150)));
strip_b.setPixelColor(i,Wheel(map(i,0,strip_b.numPixels()-1,30,150)));
}
}
// Draw peak dot
if ((peak > 0) && (peak <= (N_PIXELS - 1))) {
strip_a.setPixelColor(peak,Wheel(map(peak,0,strip_a.numPixels()-1,30,150)));
strip_b.setPixelColor(peak,Wheel(map(peak,0,strip_b.numPixels()-1,30,150)));
}
strip_a.show(); // Update strips
strip_b.show();
// Every few frames, make the peak pixel drop by 1:
if (++dotCount >= PEAK_FALL) { // fall rate
if (peak > 0) peak--;
dotCount = 0;
}
vol[volCount] = n; // Save sample for dynamic leveling
if (++volCount >= SAMPLES) volCount = 0; // Advance/rollover sample counter
// Get volume range of prior frames
minLvl = maxLvl = vol[0];
for (i = 1; i < SAMPLES; i ++) {
if (vol[i] < minLvl) minLvl = vol[i];
else if (vol[i] > maxLvl) maxLvl = vol[i];
}
// minLvl and maxLvl indicate the volume range over prior frames, used
// for vertically scaling the output graph (so it looks interesting
// regardless of volume level). If they're too close together though
// (e.g. at very low volume levels) the graph becomes super coarse
// and 'jumpy'...so keep some minimum distance between them (this
// also lets the graph go to zero when no sound is playing):
if ((maxLvl - minLvl) < TOP) maxLvl = minLvl + TOP;
minLvlAvg = (minLvlAvg * 63 + minLvl) >> 6; // Dampen min/max levels
maxLvlAvg = (maxLvlAvg * 63 + maxLvl) >> 6; // (fake rolling average)
}
// Input a value 0 to 255 to get a color value.
// The colors are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
if (WheelPos < 85) {
return strip_a.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
} else if (WheelPos < 170) {
WheelPos -= 85;
return strip_a.Color(255 - WheelPos * 3, 0, WheelPos * 3);
} else {
WheelPos -= 170;
return strip_a.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
}