#include <FastLED.h>
// --- LED Configuration ---
#define N_PIXELS 60
#define PIN 2
#define COLOR_ORDER GRB
#define LED_TYPE WS2812B
#define BRIGHTNESS 255
// --- Effect Sequencer ---
const int NUM_EFFECTS = 9;
const unsigned long EFFECT_DURATION = 8000; // 8 seconds per effect
int g_currentEffectIndex = 0;
unsigned long g_lastEffectSwitchTime = 0;
// --- Global LED Array ---
CRGB leds[N_PIXELS];
// --- DYNAMIC MUSIC SIMULATOR VARIABLES ---
uint8_t g_masterEnergy = 0; // The master "energy" (0-100) that controls everything
uint8_t g_currentLevel = 0; // The final "audio level" for the VU meters (0-255)
uint16_t g_beatInterval = 200;
unsigned long g_lastBeatTime = 0;
const uint8_t DECAY_RATE = 4;
// --- Other Effect Variables ---
uint8_t g_hue = 0; // For cycling colors
// Bouncing Balls
#define GRAVITY -9.81
#define h0 1.0
#define NUM_BALLS 7
float h[NUM_BALLS];
float vImpact0 = sqrt(-2 * GRAVITY * h0);
float vImpact[NUM_BALLS];
long tLast[NUM_BALLS];
float COR[NUM_BALLS];
void setup() {
delay(2000);
FastLED.addLeds<LED_TYPE, PIN, COLOR_ORDER>(leds, N_PIXELS).setCorrection(TypicalLEDStrip);
FastLED.setBrightness(BRIGHTNESS);
for (int i = 0; i < NUM_BALLS; i++) {
tLast[i] = millis(); h[i] = h0; vImpact[i] = vImpact0;
COR[i] = 0.92 - float(i) / pow(NUM_BALLS, 2);
}
g_lastEffectSwitchTime = millis();
}
void loop() {
// 1. Update the Dynamic Music Simulator
simulateMusic();
// 2. Check for Effect Change
if (millis() - g_lastEffectSwitchTime > EFFECT_DURATION) {
g_currentEffectIndex = (g_currentEffectIndex + 1) % NUM_EFFECTS;
g_lastEffectSwitchTime = millis();
FastLED.clear();
}
// 3. Run the Current Animation
switch (g_currentEffectIndex) {
case 0: vuMeterCenterSim(); break;
case 1: vuMeterSim(); break;
case 2: strobeParty(); break;
case 3: bouncingBalls(); break;
case 4: fastRainbow(); break;
case 5: vuMeterCenterSim(); break;
case 6: fastSinelon(); break;
case 7: fastJuggle(); break;
case 8: vuMeterSim(); break;
}
g_hue++;
FastLED.show();
FastLED.delay(16); // ~60 FPS
}
//==============================================================
// DYNAMIC MUSIC SIMULATOR
//==============================================================
void simulateMusic() {
// 1. MASTER ENERGY: A very slow sine wave (0-100) to simulate song structure (verse/chorus)
// TWEAK THIS: The first number (bpm) controls how fast the energy changes. (2-4 is good).
g_masterEnergy = beatsin8(3, 0, 100);
// 2. DYNAMIC PARAMETERS: Map the master energy to all simulation parameters.
// --- Beat Intensity ---
// TWEAK THESE: Controls how "hard" the beat hits at low vs. high energy.
uint8_t minBeat = map(g_masterEnergy, 0, 100, 20, 100);
uint8_t maxBeat = map(g_masterEnergy, 0, 100, 60, 255);
// --- Tempo --- (Inverted: high energy = low ms between beats)
// TWEAK THESE: Controls the tempo range from slow to fast.
uint16_t minTempo = map(g_masterEnergy, 0, 100, 600, 120); // ms
uint16_t maxTempo = map(g_masterEnergy, 0, 100, 1000, 250); // ms
// --- Baseline Volume ---
// TWEAK THIS: The ambient "hum" of the music.
uint8_t baseLevel = map(g_masterEnergy, 0, 100, 0, 40);
// 3. BEAT TRIGGER: Check if it's time for a new beat based on the dynamic tempo.
if (millis() - g_lastBeatTime > g_beatInterval) {
g_lastBeatTime = millis();
// Add a new beat using the dynamic intensity.
g_currentLevel = qadd8(g_currentLevel, random8(minBeat, maxBeat));
// Set the time for the *next* beat using the dynamic tempo.
g_beatInterval = random16(minTempo, maxTempo);
}
// 4. DECAY: Apply decay, ensuring level doesn't fall below the dynamic baseline.
g_currentLevel = max(baseLevel, g_currentLevel - DECAY_RATE);
}
//==============================================================
// EFFECT FUNCTIONS (Now using the fully dynamic simulator)
//==============================================================
// --- VU Meter (from Center) ---
void vuMeterCenterSim() {
uint16_t level = map(g_currentLevel, 0, 255, 0, N_PIXELS / 2);
FastLED.clear();
for (int i = 0; i < level; i++) {
uint8_t hue = map(i, 0, (N_PIXELS / 2) - 1, 85, 0); // Green to Red
leds[N_PIXELS/2 + i] = CHSV(hue, 255, 255);
leds[N_PIXELS/2 - i] = CHSV(hue, 255, 255);
}
}
// --- VU Meter (Bottom Up) ---
void vuMeterSim() {
uint16_t level = map(g_currentLevel, 0, 255, 0, N_PIXELS);
for (int i = 0; i < N_PIXELS; i++) {
if (i < level) {
leds[i] = CHSV(map(i, 0, N_PIXELS - 1, 85, 0), 255, 255); // Green to Red
} else {
leds[i] = CRGB::Black;
}
}
}
// --- Strobe Party ---
void strobeParty() {
fadeToBlackBy(leds, N_PIXELS, 120);
// Only do a full white strobe on the hardest hits during the highest energy phases.
if (g_masterEnergy > 80 && g_currentLevel > 220) {
fill_solid(leds, N_PIXELS, CRGB::White);
} else {
// Otherwise, just add some random sparkles.
if(random8() < g_masterEnergy * 2) { // More sparkles at high energy
leds[random8(N_PIXELS)] = CHSV(g_hue, 200, 255);
}
}
}
// --- Bouncing Balls ---
void bouncingBalls() {
fadeToBlackBy(leds, N_PIXELS, 80);
for (int i = 0; i < NUM_BALLS; i++) {
float tCycle = millis() - tLast[i];
h[i] = 0.5 * GRAVITY * pow(tCycle / 1000, 2.0) + vImpact[i] * tCycle / 1000;
if (h[i] < 0) {
h[i] = 0; vImpact[i] = COR[i] * vImpact[i]; tLast[i] = millis();
if (vImpact[i] < 0.01) vImpact[i] = vImpact0;
}
int pos = round(h[i] * (N_PIXELS - 1) / h0);
leds[pos] += CHSV(i * (255 / NUM_BALLS), 255, 255);
}
}
// --- Fast Rainbow ---
void fastRainbow() {
// Make rainbow speed dependent on master energy
uint8_t rainbowSpeed = map(g_masterEnergy, 0, 100, 1, 5);
fill_rainbow(leds, N_PIXELS, g_hue * rainbowSpeed, 5);
}
// --- Fast Sinelon (Cylon) ---
void fastSinelon() {
fadeToBlackBy(leds, N_PIXELS, 120);
int pos = beatsin16(90, 0, N_PIXELS - 1);
leds[pos] += CHSV(g_hue, 255, 255);
}
// --- Fast Juggle ---
void fastJuggle() {
fadeToBlackBy(leds, N_PIXELS, 80);
for (int i = 0; i < 7; i++) {
leds[beatsin16(20 + (i * 4), 0, N_PIXELS - 1)] += CHSV(g_hue + i * 32, 200, 255);
}
}