#include <Adafruit_NeoPixel.h>
// --- CONFIGURATION ---
#define LED_PIN 2
#define LED_COUNT 60
#define BRIGHTNESS 150 // Set brightness (0-255). Be mindful of power consumption.
// --- GLOBAL VARIABLES ---
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// --- Beat Simulation Variables ---
unsigned long lastBeatTime = 0;
int beatInterval = 200; // Time between simulated beats, in milliseconds
int level = 0; // Current length of the VU bar
int peak = 0; // Highest point the bar has reached
// --- Effect-specific variables ---
int theaterChaseCounter = 0; // Tracks the position of the "chase"
// =================================================================
// SETUP & LOOP
// =================================================================
void setup() {
strip.begin();
strip.setBrightness(BRIGHTNESS);
strip.show(); // Initialize strip to all 'off'
randomSeed(analogRead(0)); // Seed the random number generator for variety
}
void loop() {
// --- 1. SIMULATE A BEAT ---
// Periodically trigger a new "beat" which sets the VU meter level.
if (millis() - lastBeatTime > beatInterval) {
lastBeatTime = millis();
beatInterval = random(120, 350); // The next beat will be after a random interval
triggerBeat();
}
// --- 2. DECAY THE LEVELS ---
// Every frame, make the bar and the peak indicator fall slightly.
decayLevels();
// --- 3. RUN THE VISUAL EFFECT ---
runTheaterChaseMeter();
// --- 4. UPDATE THE LED STRIP ---
strip.show();
delay(30); // Controls the overall animation speed.
}
// =================================================================
// EFFECT IMPLEMENTATION
// =================================================================
/**
* @brief Theater Chase VU Meter
* The VU meter bar is drawn with a "chasing lights" effect. The length of
* the bar is determined by the 'level' variable, and the bar itself animates
* with a moving pattern, creating a marquee-style look.
*/
void runTheaterChaseMeter() {
strip.clear(); // Clear the strip to draw the new frame
// This counter increments each frame, creating the movement for the chase effect.
theaterChaseCounter++;
// Draw the main VU bar with the chase effect
for (int i = 0; i < level; i++) {
// The modulo operator (%) creates the repeating pattern.
// (i + counter) % 4 creates a sequence 0, 1, 2, 3, 0, 1, ...
// The condition `< 2` lights up pixels for sequence values 0 and 1,
// and leaves them off for 2 and 3. This creates a "2 pixels on, 2 pixels off" chase.
if ((i + theaterChaseCounter) % 4 < 2) {
setPixelColorByLevel(i, LED_COUNT);
}
}
// Draw the peak hold pixel. This is a single white pixel at the highest
// point the bar has reached, which then slowly falls.
if (peak > 0) {
strip.setPixelColor(peak, strip.Color(255, 255, 255));
}
}
// =================================================================
// HELPER FUNCTIONS
// =================================================================
/**
* @brief Simulates a new beat by setting the bar level to a new random height.
*/
void triggerBeat() {
// Set the bar to a random height, mostly in the upper range of the strip.
level = random(LED_COUNT * 0.7, LED_COUNT + 1);
// If the new level is higher than the current peak, update the peak.
if (level > peak) {
peak = level;
}
}
/**
* @brief Makes the VU meter bar and peak fall over time.
*/
void decayLevels() {
// Reduce the level with a multiplier. A value like 0.90 provides a smooth fall.
level *= 0.90;
// The peak falls by one pixel per frame.
if (peak > 0) {
peak--;
}
}
/**
* @brief Sets a pixel's color based on its position (Green -> Yellow -> Red).
* @param position The pixel index to set.
* @param totalLength The total length of the meter (usually LED_COUNT).
*/
void setPixelColorByLevel(int position, int totalLength) {
// Use a helper function to get the correct color and then set the pixel.
strip.setPixelColor(position, getPixelColorByLevel(position, totalLength));
}
/**
* @brief Returns a color based on the pixel's position in the bar.
* @param position The pixel's position.
* @param totalLength The total length of the meter.
* @return A 32-bit color value (e.g., green, yellow, or red).
*/
uint32_t getPixelColorByLevel(int position, int totalLength) {
if (position < totalLength * 0.60) {
return strip.Color(0, 200, 0); // Green
} else if (position < totalLength * 0.85) {
return strip.Color(200, 200, 0); // Yellow
} else {
return strip.Color(200, 0, 0); // Red
}
}