#include <FastLED.h>
#include <math.h>
// --- CORE SETTINGS ---
#define N_PIXELS 200
#define LED_PIN 2
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define BRIGHTNESS 240
#define N_PIXELS_HALF (N_PIXELS / 2)
// --- PATTERN-SPECIFIC SETTINGS ---
#define SAMPLES 60
#define TOP (N_PIXELS + 2)
// --- VU METER PEAK SETTINGS (SLOWER) ---
#define PEAK_HANG 38
#define PEAK_FALL 19
#define PEAK_FALL2 8
#define PEAK_FALL_MILLIS 8
// --- GENERIC HELPER MACROS ---
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
// --- GLOBAL VARIABLES ---
CRGB leds[N_PIXELS];
// For VU meter calculations
byte peak = 0;
byte dotCount = 0;
byte volCount = 0;
int vol[SAMPLES];
int lvl = 10;
int minLvlAvg = 0;
int maxLvlAvg = 512;
unsigned int reading;
// For color cycling effects
float greenOffset = 30;
float blueOffset = 150;
#define SPEED 0.12f
long lastTime = 0;
// For FastLED palette effects
CRGBPalette16 currentPalette(OceanColors_p);
CRGBPalette16 targetPalette(CloudColors_p);
TBlendType currentBlending = LINEARBLEND;
// For Ripple effects
#define maxsteps 16
uint8_t colour;
uint8_t myfade = 255;
int center = 0;
int step = -1;
int peakcount = 0;
int peakspersec = 0;
uint8_t bgcol = 0;
unsigned long oldtime = 0;
unsigned long newtime = 0;
// For Sample averaging in ripple effects
#define NSAMPLES 64
unsigned int sample;
unsigned int samplearray[NSAMPLES];
unsigned long samplesum = 0;
unsigned int sampleavg = 0;
int samplecount = 0;
// For "Shatter" effect (vu8)
#define DRAW_MAX 100
int origin = 0;
int color_wait_count = 0;
int scroll_color = 0;
int last_intensity = 0;
int intensity_max = 0;
int origin_at_flip = 0;
CRGB draw[DRAW_MAX];
boolean growing = false;
boolean fall_from_left = true;
#define SEGMENTS 4
#define COLOR_WAIT_CYCLES 32
// --- FORWARD DECLARATIONS of PATTERNS ---
void vuBikeShocker();
void vu();
void vu1();
void vu2();
void Vu3();
void Vu4();
void Vu5();
void Vu6();
void vu7();
void vu8();
void vu9();
void vu10();
void vu11();
void vu12();
void vu13();
// List of patterns to cycle through.
typedef void (*SimplePatternList[])();
SimplePatternList qPatterns = {vuBikeShocker, vu, vu1, vu2, Vu3, Vu4, Vu5, Vu6, vu7, vu8, vu9, vu10, vu11, vu12, vu13};
uint8_t qCurrentPatternNumber = 0;
//===================================================================================
// SETUP
//===================================================================================
void setup() {
Serial.begin(57600);
delay(2000);
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, N_PIXELS).setCorrection(TypicalLEDStrip);
FastLED.setBrightness(BRIGHTNESS);
FastLED.clear();
FastLED.show();
}
//===================================================================================
// DYNAMIC AUDIO SIMULATION ENGINE
//===================================================================================
uint16_t getSimulatedAmplitude() {
static unsigned long nextBeatTime = 0;
static unsigned long lastBeatHitTime = 0;
static int currentBeatAmplitude = 0;
static int currentBeatDuration = 200;
const int MAX_AMPLITUDE = 450;
unsigned long now = millis();
if (now >= nextBeatTime) {
lastBeatHitTime = now;
int energyPercent = random(0, 101);
currentBeatAmplitude = map(energyPercent, 0, 100, 0, MAX_AMPLITUDE);
currentBeatDuration = random(100, 301);
unsigned long randomDelay = random(48, 481);
nextBeatTime = now + randomDelay;
}
unsigned long timeSinceBeat = now - lastBeatHitTime;
if (timeSinceBeat < currentBeatDuration) {
long amplitude = map(timeSinceBeat, 0, currentBeatDuration, currentBeatAmplitude, 0);
return amplitude;
} else {
return 0;
}
}
//===================================================================================
// MAIN LOOP
//===================================================================================
void loop() {
EVERY_N_SECONDS(16) {
qCurrentPatternNumber = (qCurrentPatternNumber + 1) % ARRAY_SIZE(qPatterns);
Serial.print("Switching to pattern #");
Serial.println(qCurrentPatternNumber);
FastLED.clear(true);
}
qPatterns[qCurrentPatternNumber]();
}
//===================================================================================
// HELPER FUNCTIONS
//===================================================================================
float mapfloat(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
CRGB Wheel(byte WheelPos) {
if (WheelPos < 85) return CRGB(WheelPos * 3, 255 - WheelPos * 3, 0);
if (WheelPos < 170) { WheelPos -= 85; return CRGB(255 - WheelPos * 3, 0, WheelPos * 3); }
WheelPos -= 170; return CRGB(0, WheelPos * 3, 255 - WheelPos * 3);
}
void processSimulatedAudio() {
int n = getSimulatedAmplitude();
lvl = ((lvl * 7) + n) >> 3;
vol[volCount] = n;
if (++volCount >= SAMPLES) volCount = 0;
uint16_t minLvl = vol[0], maxLvl = vol[0];
for (uint8_t i = 1; i < SAMPLES; i++) {
if (vol[i] < minLvl) minLvl = vol[i];
if (vol[i] > maxLvl) maxLvl = vol[i];
}
if ((maxLvl - minLvl) < N_PIXELS) maxLvl = minLvl + N_PIXELS;
minLvlAvg = (minLvlAvg * 63 + minLvl) >> 6;
maxLvlAvg = (maxLvlAvg * 63 + maxLvl) >> 6;
}
void addGlitter(fract8 chanceOfGlitter) {
if (random8() < chanceOfGlitter) {
leds[random16(N_PIXELS)] += CRGB::White;
}
}
//===================================================================================
// PATTERN 0: Bike Shocker VU Meter
//===================================================================================
void vuBikeShocker() {
const float COMPRESSION_STIFFNESS = 0.4f;
const float COMPRESSION_DAMPING = 0.15f;
const float REBOUND_STIFFNESS = 0.1f;
const float REBOUND_DAMPING = 0.35f;
static float currentLength = 0;
static float velocity = 0;
uint16_t amplitude = getSimulatedAmplitude();
float targetLength = mapfloat(amplitude, 0, 450, 0, N_PIXELS);
float stiffness, damping;
if (velocity > 0 || targetLength > currentLength) {
stiffness = COMPRESSION_STIFFNESS;
damping = COMPRESSION_DAMPING;
} else {
stiffness = REBOUND_STIFFNESS;
damping = REBOUND_DAMPING;
}
float force = targetLength - currentLength;
float acceleration = (stiffness * force) - (damping * velocity);
velocity += acceleration;
currentLength += velocity;
currentLength = constrain(currentLength, 0, N_PIXELS);
int barEnd = (int)currentLength;
fill_solid(leds, N_PIXELS, CRGB::Black);
for (int i = 0; i < barEnd; i++) {
leds[i] = Wheel(map(i, 0, N_PIXELS - 1, 40, 170));
}
FastLED.show();
FastLED.delay(16);
}
//===================================================================================
// PATTERN 1: Standard VU Meter
//===================================================================================
void vu() {
processSimulatedAudio();
int height = map(lvl, minLvlAvg, maxLvlAvg, 0, N_PIXELS);
height = constrain(height, 0, N_PIXELS);
if (height > peak) peak = height;
for (uint8_t i = 0; i < N_PIXELS; i++) {
if (i >= height) leds[i] = CRGB::Black;
else leds[i] = Wheel(map(i, 0, N_PIXELS - 1, 30, 150));
}
if (peak > 0 && peak < N_PIXELS) leds[peak] = CRGB::Magenta; // Changed to Magenta
FastLED.show();
if (++dotCount >= PEAK_FALL) {
if (peak > 0) peak--;
dotCount = 0;
}
}
//===================================================================================
// PATTERN 2: Center-Out VU Meter
//===================================================================================
void vu1() {
processSimulatedAudio();
int height = map(lvl, minLvlAvg, maxLvlAvg, 0, N_PIXELS_HALF);
height = constrain(height, 0, N_PIXELS_HALF);
if (height > peak) peak = height;
fadeToBlackBy(leds, N_PIXELS, 24);
for (uint8_t i = 0; i < height; i++) {
CRGB color = Wheel(map(i, 0, N_PIXELS_HALF - 1, 30, 150));
leds[N_PIXELS_HALF - 1 - i] = color;
leds[N_PIXELS_HALF + i] = color;
}
if (peak > 0 && peak < N_PIXELS_HALF) {
CRGB peakColor = CRGB::Magenta; // Changed to Magenta
leds[N_PIXELS_HALF - 1 - peak] = peakColor;
leds[N_PIXELS_HALF + peak] = peakColor;
}
FastLED.show();
if (++dotCount >= PEAK_FALL) {
if (peak > 0) peak--;
dotCount = 0;
}
}
//===================================================================================
// PATTERN 3: Center-In VU Meter
//===================================================================================
void vu2() {
static byte dotHangCount = 0;
float peakToPeak = getSimulatedAmplitude() * 2;
int c = map(peakToPeak, 0, 900, 0, N_PIXELS_HALF);
c = constrain(c, 0, N_PIXELS_HALF);
if (c > peak) {
peak = c;
dotHangCount = 0;
}
for (int i = 0; i < N_PIXELS_HALF; i++) {
CRGB color = Wheel(map(i, 0, N_PIXELS_HALF - 1, 30, 150));
leds[i] = color;
leds[N_PIXELS - 1 - i] = color;
}
for (int i = 0; i < N_PIXELS_HALF - c; i++) {
leds[N_PIXELS_HALF - 1 - i] = CRGB::Black;
leds[N_PIXELS_HALF + i] = CRGB::Black;
}
if (peak > 0 && peak <= N_PIXELS_HALF) {
CRGB peakColor = CRGB::Magenta; // Changed to Magenta
leds[peak - 1] = peakColor;
leds[N_PIXELS - peak] = peakColor;
}
FastLED.show();
if (dotHangCount > PEAK_HANG) {
if (++dotCount >= PEAK_FALL2) {
if (peak > 0) peak--;
dotCount = 0;
}
} else {
dotHangCount++;
}
}
//===================================================================================
// PATTERN 4: Normal Rainbow VU
//===================================================================================
void Vu3() {
processSimulatedAudio();
int height = map(lvl, minLvlAvg, maxLvlAvg, 0, N_PIXELS);
height = constrain(height, 0, N_PIXELS);
if (height > peak) peak = height;
greenOffset += SPEED; blueOffset += SPEED;
if (greenOffset >= 255) greenOffset = 0;
if (blueOffset >= 255) blueOffset = 0;
for (uint8_t i = 0; i < N_PIXELS; i++) {
if (i >= height) leds[i] = CRGB::Black;
else leds[i] = Wheel(map(i, 0, N_PIXELS - 1, (int)greenOffset, (int)blueOffset));
}
if (peak > 0 && peak < N_PIXELS) leds[peak] = CRGB::Magenta; // Changed to Magenta
FastLED.show();
if (++dotCount >= PEAK_FALL) { if (peak > 0) peak--; dotCount = 0; }
}
//===================================================================================
// PATTERN 5: Center Rainbow VU
//===================================================================================
void Vu4() {
processSimulatedAudio();
int height = map(lvl, minLvlAvg, maxLvlAvg, 0, N_PIXELS_HALF);
height = constrain(height, 0, N_PIXELS_HALF);
if (height > peak) peak = height;
greenOffset += SPEED; blueOffset += SPEED;
if (greenOffset >= 255) greenOffset = 0;
if (blueOffset >= 255) blueOffset = 0;
fadeToBlackBy(leds, N_PIXELS, 24);
for (uint8_t i = 0; i < height; i++) {
CRGB color = Wheel(map(i, 0, N_PIXELS_HALF - 1, (int)greenOffset, (int)blueOffset));
leds[N_PIXELS_HALF - 1 - i] = color;
leds[N_PIXELS_HALF + i] = color;
}
if (peak > 0 && peak < N_PIXELS_HALF) {
CRGB peakColor = CRGB::Magenta; // Changed to Magenta
leds[N_PIXELS_HALF - 1 - peak] = peakColor;
leds[N_PIXELS_HALF + peak] = peakColor;
}
FastLED.show();
if (++dotCount >= PEAK_FALL) { if (peak > 0) peak--; dotCount = 0; }
}
//===================================================================================
// PATTERN 6: Shooting Star VU
//===================================================================================
void Vu5() {
processSimulatedAudio();
int height = map(lvl, minLvlAvg, maxLvlAvg, 0, N_PIXELS);
height = constrain(height, 0, N_PIXELS);
if (height > peak) peak = height;
fadeToBlackBy(leds, N_PIXELS, 18);
for (uint8_t i = 0; i < height; i++) { leds[i] = Wheel(map(i, 0, N_PIXELS - 1, 30, 150)); }
if (peak > 0 && peak < N_PIXELS) { leds[peak] = CRGB::Magenta; } // Changed to Magenta
if (millis() - lastTime >= PEAK_FALL_MILLIS) {
lastTime = millis();
if (peak > 0) peak--;
}
FastLED.show();
}
//===================================================================================
// PATTERN 7: Falling Star VU
//===================================================================================
void Vu6() {
processSimulatedAudio();
int height = map(lvl, minLvlAvg, maxLvlAvg, 0, N_PIXELS);
height = constrain(height, 0, N_PIXELS);
if (height > peak) peak = height;
fadeToBlackBy(leds, N_PIXELS, 36);
if (peak > 0 && peak < N_PIXELS) {
leds[peak] = CRGB::Magenta; // Changed to Magenta
}
if (millis() - lastTime >= PEAK_FALL_MILLIS) {
lastTime = millis();
if (peak > 0) peak--;
}
FastLED.show();
}
//===================================================================================
// PATTERN 8: Ripple with Background
//===================================================================================
void soundmems() {
newtime = millis();
sample = getSimulatedAmplitude();
int sensitivity = 40;
samplesum = samplesum + sample - samplearray[samplecount];
sampleavg = samplesum / NSAMPLES;
samplearray[samplecount] = sample;
samplecount = (samplecount + 1) % NSAMPLES;
if ((sample > (sampleavg + sensitivity)) && (newtime > (oldtime + 96))) {
step = -1;
peakcount++;
oldtime = newtime;
}
}
void ripple3() {
for (int i = 0; i < N_PIXELS; i++) leds[i] = CHSV(bgcol, 255, sampleavg * 2);
switch (step) {
case -1: center = random(N_PIXELS); colour = (peakspersec * 10) % 255; step = 0; bgcol = bgcol + 8; break;
case 0: leds[center] = CHSV(colour, 255, 255); step++; break;
case maxsteps: break;
default:
leds[(center + step + N_PIXELS) % N_PIXELS] += CHSV(colour, 255, myfade / step * 2);
leds[(center - step + N_PIXELS) % N_PIXELS] += CHSV(colour, 255, myfade / step * 2);
step++; break;
}
}
void vu7() {
EVERY_N_MILLISECONDS(1000) { peakspersec = peakcount; peakcount = 0; }
soundmems();
EVERY_N_MILLISECONDS(32) { ripple3(); }
FastLED.show();
}
//===================================================================================
// PATTERN 9: Shatter (with Suspension-like Damping)
//===================================================================================
void updateGlobals();
void vu8() {
static float smoothed_amplitude = 0.0f;
int raw_amplitude = getSimulatedAmplitude();
smoothed_amplitude = ((smoothed_amplitude * 15) + raw_amplitude) / 16.0f;
int intensity = map(smoothed_amplitude, 0, 450, 0, DRAW_MAX - 1);
intensity = constrain(intensity, 0, DRAW_MAX - 1);
updateOrigin(intensity);
assignDrawValues(intensity);
writeSegmented();
updateGlobals();
}
void updateOrigin(int intensity) {
if (growing && intensity < last_intensity) {
growing = false; intensity_max = last_intensity; fall_from_left = !fall_from_left; origin_at_flip = origin;
} else if (intensity > last_intensity) { growing = true; origin_at_flip = origin; }
last_intensity = intensity;
if (!growing) {
if (fall_from_left) { origin = origin_at_flip + ((intensity_max - intensity) / 2); }
else { origin = origin_at_flip - ((intensity_max - intensity) / 2); }
if (origin < 0) origin = DRAW_MAX - abs(origin);
else if (origin > DRAW_MAX - 1) origin = origin - DRAW_MAX;
}
}
void assignDrawValues(int intensity) {
int min_lit = origin - (intensity / 2); int max_lit = origin + (intensity / 2);
if (min_lit < 0) min_lit += DRAW_MAX;
if (max_lit >= DRAW_MAX) max_lit -= DRAW_MAX;
for (int i = 0; i < DRAW_MAX; i++) {
if ((min_lit < max_lit && i > min_lit && i < max_lit) || (min_lit > max_lit && (i > min_lit || i < max_lit))) {
draw[i] = Wheel(scroll_color);
} else { draw[i] = CRGB::Black; }
}
}
void writeSegmented() {
int seg_len = N_PIXELS / SEGMENTS;
for (int s = 0; s < SEGMENTS; s++) {
for (int i = 0; i < seg_len; i++) { leds[i + (s * seg_len)] = draw[map(i, 0, seg_len, 0, DRAW_MAX - 1)]; }
}
FastLED.show();
}
void updateGlobals() {
color_wait_count++;
if (color_wait_count > COLOR_WAIT_CYCLES) {
color_wait_count = 0; scroll_color++; if (scroll_color > 255) scroll_color = 0;
}
}
//===================================================================================
// PATTERN 10: Pulse
//===================================================================================
void soundble() { sample = getSimulatedAmplitude(); }
void sndwave() {
leds[N_PIXELS / 2] = ColorFromPalette(currentPalette, sample, sample * 2, currentBlending);
for (int i = N_PIXELS - 1; i > N_PIXELS / 2; i--) { leds[i] = leds[i - 1]; }
for (int i = 0; i < N_PIXELS / 2; i++) { leds[i] = leds[i + 1]; }
addGlitter(sampleavg);
}
void vu9() {
currentPalette = OceanColors_p; currentBlending = LINEARBLEND;
EVERY_N_SECONDS(8) { for (int i = 0; i < 16; i++) { targetPalette[i] = CHSV(random8(), 255, 255); } }
EVERY_N_MILLISECONDS(160) { nblendPaletteTowardPalette(currentPalette, targetPalette, 14); }
EVERY_N_MILLISECONDS(32) { fadeToBlackBy(leds, N_PIXELS, 15); soundble(); sndwave(); }
FastLED.show();
}
//===================================================================================
// PATTERN 11: Stream
//===================================================================================
void soundtun() {
int n = getSimulatedAmplitude();
CRGB newcolour = ColorFromPalette(currentPalette, constrain(n, 0, 255), constrain(n, 0, 255), currentBlending);
nblend(leds[0], newcolour, 128);
for (int i = N_PIXELS - 1; i > 0; i--) { leds[i] = leds[i - 1]; }
}
void vu10() {
EVERY_N_SECONDS(8) { for (int i = 0; i < 16; i++) { targetPalette[i] = CHSV(random8(), 255, 255); } }
EVERY_N_MILLISECONDS(160) { nblendPaletteTowardPalette(currentPalette, targetPalette, 14); }
EVERY_N_MILLISECONDS(32) { soundtun(); }
FastLED.show();
}
//===================================================================================
// PATTERN 12: Ripple without Background
//===================================================================================
void soundrip() { soundmems(); }
void rippled() {
fadeToBlackBy(leds, N_PIXELS, 48);
switch (step) {
case -1: center = random(N_PIXELS); colour = (peakspersec * 10) % 255; step = 0; break;
case 0: leds[center] = CHSV(colour, 255, 255); step++; break;
case maxsteps: break;
default:
leds[(center + step + N_PIXELS) % N_PIXELS] += CHSV(colour, 255, myfade / step * 2);
leds[(center - step + N_PIXELS) % N_PIXELS] += CHSV(colour, 255, myfade / step * 2);
step++; break;
}
}
void vu11() {
EVERY_N_MILLISECONDS(1000) { peakspersec = peakcount; peakcount = 0; }
soundrip();
EVERY_N_MILLISECONDS(32) { rippled(); }
FastLED.show();
}
//===================================================================================
// PATTERN 13: Ripple with Glitter
//===================================================================================
void soundripped() { soundmems(); }
void rippvu() {
fadeToBlackBy(leds, N_PIXELS, 48);
switch (step) {
case -1: center = random(N_PIXELS); colour = (peakspersec * 10) % 255; step = 0; break;
case 0: leds[center] = CHSV(colour, 255, 255); step++; break;
case maxsteps: break;
default:
leds[(center + step + N_PIXELS) % N_PIXELS] += CHSV(colour, 255, myfade / step * 2);
leds[(center - step + N_PIXELS) % N_PIXELS] += CHSV(colour, 255, myfade / step * 2);
step++; break;
}
addGlitter(sampleavg);
}
void vu12() {
EVERY_N_MILLISECONDS(1000) { peakspersec = peakcount; peakcount = 0; }
soundripped();
EVERY_N_MILLISECONDS(32) { rippvu(); }
FastLED.show();
}
//===================================================================================
// PATTERN 14: Juggle with Glitter
//===================================================================================
void soundripper() { soundmems(); }
void jugglep() {
static uint8_t thishue = 0;
fadeToBlackBy(leds, N_PIXELS, 21);
leds[0] = ColorFromPalette(currentPalette, thishue++, sampleavg, LINEARBLEND);
for (int i = N_PIXELS - 1; i > 0; i--) leds[i] = leds[i - 1];
addGlitter(sampleavg / 2);
}
void vu13() {
EVERY_N_MILLISECONDS(1000) { peakspersec = peakcount; peakcount = 0; }
soundripper();
EVERY_N_MILLISECONDS(32) { jugglep(); }
FastLED.show();
}