#include <Adafruit_NeoPixel.h>
// --- NEOPIXEL CONFIGURATION ---
#define LED_PIN 6 // Which pin on the Arduino is connected to the NeoPixels.
#define NUM_LEDS 60 // The number of LEDs in your strip.
// --- VU METER CONFIGURATION ---
#define ANALOG_PIN A0 // The microphone's analog output is connected to this pin.
#define SAMPLES 100 // How many samples to take to determine the sound level.
// --- CALIBRATION ---
// These values will need to be adjusted for your specific microphone and environment.
// See the "How to Calibrate" section below.
#define NOISE 10 // A baseline noise level. Anything below this is considered silence.
#define MAX_LEVEL 500 // The maximum peak-to-peak signal you expect.
// Create a NeoPixel object
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
int peak = 0; // Stores the peak LED position
unsigned long peak_millis = 0; // Timestamp for when the peak was last updated
#define PEAK_FALL_DELAY 30 // How many milliseconds before the peak falls one step
void setup() {
Serial.begin(9600); // For debugging and calibration
strip.begin(); // Initialize the NeoPixel strip
strip.show(); // Initialize all pixels to 'off'
strip.setBrightness(150); // Set brightness (0-255). 150 is a good starting point.
}
void loop() {
unsigned int signalMax = 0;
unsigned int signalMin = 1024;
unsigned int sample;
// 1. COLLECT SAMPLES
// Find the peak-to-peak amplitude of the audio signal over a short period.
for (int i = 0; i < SAMPLES; i++) {
sample = analogRead(ANALOG_PIN);
if (sample < 1024) { // Guard against spurious readings
if (sample > signalMax) {
signalMax = sample; // Save the maximum value
} else if (sample < signalMin) {
signalMin = sample; // Save the minimum value
}
}
}
// 2. CALCULATE PEAK-TO-PEAK AMPLITUDE
// This is a better measure of volume than a single reading.
int p2p_level = signalMax - signalMin;
// Optional: Apply some smoothing to make the display less "jumpy"
// This is a simple low-pass filter.
static int last_level = 0;
p2p_level = (last_level * 0.7) + (p2p_level * 0.3);
last_level = p2p_level;
// For calibration, uncomment the line below:
// Serial.println(p2p_level);
// 3. MAP THE AUDIO LEVEL TO THE NUMBER OF LEDS
// Subtract the noise floor and constrain the value.
int level = map(p2p_level, NOISE, MAX_LEVEL, 0, NUM_LEDS);
level = constrain(level, 0, NUM_LEDS);
// 4. UPDATE THE PEAK INDICATOR
if (level > peak) {
peak = level;
peak_millis = millis(); // Reset the peak fall timer
}
// 5. DRAW THE VU METER BAR
for (int i = 0; i < NUM_LEDS; i++) {
if (i < level) {
// Create a color gradient from green to red.
// Hue values: 0 = Red, 85 = Green. We map from green to red.
int hue = map(i, 0, NUM_LEDS - 1, 85, 0);
strip.setPixelColor(i, strip.ColorHSV(hue * 256));
} else {
// Turn off LEDs beyond the current level
strip.setPixelColor(i, strip.Color(0, 0, 0));
}
}
// 6. DRAW THE PEAK PIXEL
// Draw the peak pixel in a distinct color (e.g., white or blue)
if (peak > 0 && peak < NUM_LEDS) {
strip.setPixelColor(peak, strip.Color(200, 200, 255)); // A light blueish-white
}
// 7. SHOW THE CHANGES ON THE STRIP
strip.show();
// 8. MAKE THE PEAK FALL
// After a short delay, make the peak LED fall by one position.
if (millis() - peak_millis > PEAK_FALL_DELAY) {
if (peak > 0) {
peak--;
}
peak_millis = millis(); // Reset the timer for the next fall step
}
}