Skip to content

Instantly share code, notes, and snippets.

@bigjosh
Created October 30, 2025 17:50
Show Gist options
  • Save bigjosh/d2186b7ed3242e472312ae262f89fc44 to your computer and use it in GitHub Desktop.
Save bigjosh/d2186b7ed3242e472312ae262f89fc44 to your computer and use it in GitHub Desktop.
AA LED animation for Room Service
#include <FastLED.h>
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
// Configuration for 3 LED strings
const int NUM_STRINGS = 3;
// String 1 - Pin D4
#define LED_PIN_1 4
#define NUM_LEDS_1 120
CRGB leds1[NUM_LEDS_1];
// String 2 - Pin D5
#define LED_PIN_2 5
#define NUM_LEDS_2 120
CRGB leds2[NUM_LEDS_2];
// String 3 - Pin D6
#define LED_PIN_3 6
#define NUM_LEDS_3 120
CRGB leds3[NUM_LEDS_3];
// Parameters for each string
struct StringParams {
CRGB* leds;
int numLeds;
int numWaves;
float* wavePositions;
float waveWidth;
float waveSpacing;
float baseSpeed;
float speedModPeriod;
float speedVariation;
uint8_t baseBrightness;
float brightnessModPeriod;
float brightnessVariation;
uint8_t hue;
uint8_t saturation;
};
// Wave position arrays for each string
float wavePositions1[4];
float wavePositions2[4];
float wavePositions3[4];
// String 1 Parameters (Original - organic motion)
StringParams string1 = {
leds1, NUM_LEDS_1,
4, wavePositions1,
12.0, 30.0, // wave width, spacing
20.0, 5.0, 0.8, // base speed, speed mod period, speed variation (±80%, can go backward)
180, 3.0, 0.3, // base brightness, brightness mod period, brightness variation
0, 255 // hue (red), saturation
};
// String 2 Parameters (Faster, more erratic)
StringParams string2 = {
leds2, NUM_LEDS_2,
4, wavePositions2,
10.0, 28.0, // wave width, spacing
13.0, 3.5, 0.9, // base speed, speed mod period, speed variation (±90%, more erratic)
200, 2.5, 0.4, // base brightness, brightness mod period, brightness variation
0, 255 // hue (red), saturation
};
// String 3 Parameters (Slower, more organic)
StringParams string3 = {
leds3, NUM_LEDS_3,
4, wavePositions3,
15.0, 35.0, // wave width, spacing
15.0, 6.5, 0.85, // base speed, speed mod period, speed variation (±85%)
160, 3.5, 0.25, // base brightness, brightness mod period, brightness variation
0, 255 // hue (red), saturation
};
StringParams strings[NUM_STRINGS] = {string1, string2, string3};
// Scene management
enum Scene {
SCENE_DOTS,
SCENE_FLASH,
SCENE_THEATER_CHASE,
SCENE_BLOBS,
SCENE_FINALE
};
const uint32_t SCENE_DURATIONS[] = {
30000, // dots - 30s
15000, // flash - 15s
30000, // theater chase - 30s
60000, // blobs - 60s
5000 // finale - 5s
};
Scene currentScene = SCENE_DOTS;
unsigned long sceneStartTime = 0;
unsigned long lastUpdate = 0;
float timeAccumulator = 0.0;
// Global color (red, but can be changed)
const uint8_t GLOBAL_HUE = 0; // Red
const uint8_t GLOBAL_SAT = 255;
// Scene-specific variables
unsigned long lastFlashTime = 0;
int theaterChasePosition = 0;
unsigned long finaleStartTime = 0;
bool finaleFlashDone = false;
void setup() {
// Initialize FastLED for all 3 strings
FastLED.addLeds<LED_TYPE, LED_PIN_1, COLOR_ORDER>(leds1, NUM_LEDS_1);
FastLED.addLeds<LED_TYPE, LED_PIN_2, COLOR_ORDER>(leds2, NUM_LEDS_2);
FastLED.addLeds<LED_TYPE, LED_PIN_3, COLOR_ORDER>(leds3, NUM_LEDS_3);
FastLED.setCorrection(TypicalLEDStrip); // Apply color correction
//FastLED.setMaxRefreshRate(400); // Set explicit refresh rate for multiple outputs
// Initialize wave positions for each string
for (int s = 0; s < NUM_STRINGS; s++) {
StringParams& str = strings[s];
for (int i = 0; i < str.numWaves; i++) {
str.wavePositions[i] = i * str.waveSpacing - str.waveWidth;
}
}
lastUpdate = millis();
sceneStartTime = millis();
randomSeed(analogRead(0)); // Seed random for dots and sparkles
}
// Scene: Flickering dots randomly along strings
void sceneDots() {
// Clear all strings
for (int s = 0; s < NUM_STRINGS; s++) {
fill_solid(strings[s].leds, strings[s].numLeds, CRGB::Black);
}
// Randomly light dots on each string
for (int s = 0; s < NUM_STRINGS; s++) {
StringParams& str = strings[s];
for (int i = 0; i < str.numLeds; i++) {
// ~5% chance per LED to flicker
if (random(100) < 5) {
uint8_t brightness = random(50, 255);
str.leds[i] = CHSV(GLOBAL_HUE, GLOBAL_SAT, brightness);
}
}
}
}
// Scene: Full string flashes
void sceneFlash() {
unsigned long currentTime = millis();
// Random delay between flashes (~200ms average = ~5 flashes/sec)
if (currentTime - lastFlashTime > random(100, 300)) {
// Flash all strings
for (int s = 0; s < NUM_STRINGS; s++) {
fill_solid(strings[s].leds, strings[s].numLeds, CHSV(GLOBAL_HUE, GLOBAL_SAT, 255));
}
lastFlashTime = currentTime;
} else {
// Turn off
for (int s = 0; s < NUM_STRINGS; s++) {
fill_solid(strings[s].leds, strings[s].numLeds, CRGB::Black);
}
}
}
// Scene: Theater chase
void sceneTheaterChase() {
static unsigned long lastChaseUpdate = 0;
unsigned long currentTime = millis();
// Update every 100ms
if (currentTime - lastChaseUpdate > 100) {
theaterChasePosition++;
if (theaterChasePosition >= 3) theaterChasePosition = 0;
lastChaseUpdate = currentTime;
}
// Clear and draw theater chase
for (int s = 0; s < NUM_STRINGS; s++) {
StringParams& str = strings[s];
fill_solid(str.leds, str.numLeds, CRGB::Black);
for (int i = theaterChasePosition; i < str.numLeds; i += 3) {
str.leds[i] = CHSV(GLOBAL_HUE, GLOBAL_SAT, 200);
}
}
}
// Scene: Blobs (original peristaltic animation)
void sceneBlobs(float deltaTime) {
for (int s = 0; s < NUM_STRINGS; s++) {
StringParams& str = strings[s];
// Calculate speed modulation for this string
float speedMultiplier = 1.0 + str.speedVariation * sin(TWO_PI * timeAccumulator / str.speedModPeriod);
float currentSpeed = str.baseSpeed * speedMultiplier;
// Calculate brightness modulation
float brightnessMultiplier = 1.0 + str.brightnessVariation * sin(TWO_PI * timeAccumulator / str.brightnessModPeriod);
uint8_t globalBrightness = constrain(str.baseBrightness * brightnessMultiplier, 0, 255);
// Calculate wave width modulation
float widthModPeriod = str.speedModPeriod * 1.3;
float widthMultiplier = 1.0 + 0.4 * sin(TWO_PI * timeAccumulator / widthModPeriod);
float currentWaveWidth = str.waveWidth * widthMultiplier;
// Clear the strip
fill_solid(str.leds, str.numLeds, CRGB::Black);
// Pre-calculate constants
const float invWaveWidth = 1.0 / currentWaveWidth;
const int rangeLimit = (int)(currentWaveWidth * 2.0 + 0.5);
// Update and render each wave
for (int w = 0; w < str.numWaves; w++) {
str.wavePositions[w] += currentSpeed * deltaTime;
if (str.wavePositions[w] > str.numLeds + currentWaveWidth * 2) {
str.wavePositions[w] = -currentWaveWidth * 2;
}
int waveCenter = (int)str.wavePositions[w];
int startLED = max(0, waveCenter - rangeLimit);
int endLED = min(str.numLeds, waveCenter + rangeLimit + 1);
for (int i = startLED; i < endLED; i++) {
float distance = abs(i - str.wavePositions[w]);
float normalizedDist = distance * invWaveWidth;
uint8_t distInt = (uint8_t)(normalizedDist * 64.0);
uint16_t intensity;
if (distInt > 128) {
intensity = 0;
} else {
uint8_t oneMinusDist = 128 - distInt;
uint16_t temp = oneMinusDist * 2;
intensity = ((uint32_t)temp * temp * temp) >> 16;
}
uint8_t brightness = (intensity * globalBrightness) >> 8;
if (brightness > 3) {
CRGB waveColor = CHSV(GLOBAL_HUE, GLOBAL_SAT, brightness);
str.leds[i] = blend(str.leds[i], waveColor, brightness);
}
}
}
}
}
// Scene: Finale - big flash decay + white sparkles
void sceneFinale() {
unsigned long currentTime = millis();
unsigned long elapsed = currentTime - finaleStartTime;
if (!finaleFlashDone) {
// Flash decay over 200ms
if (elapsed < 200) {
// Exponential decay
uint8_t brightness = 255 * (1.0 - (elapsed / 200.0));
for (int s = 0; s < NUM_STRINGS; s++) {
fill_solid(strings[s].leds, strings[s].numLeds, CHSV(GLOBAL_HUE, GLOBAL_SAT, brightness));
}
} else {
finaleFlashDone = true;
}
} else {
// Random white sparkles
for (int s = 0; s < NUM_STRINGS; s++) {
StringParams& str = strings[s];
fill_solid(str.leds, str.numLeds, CRGB::Black);
// ~3% of LEDs sparkle white per frame
for (int i = 0; i < str.numLeds; i++) {
if (random(100) < 3) {
str.leds[i] = CRGB::White;
}
}
}
}
}
void loop() {
// Calculate delta time
unsigned long currentTime = millis();
float deltaTime = (currentTime - lastUpdate) / 1000.0;
lastUpdate = currentTime;
timeAccumulator += deltaTime;
// Check if scene should transition
if (currentTime - sceneStartTime >= SCENE_DURATIONS[currentScene]) {
// Move to next scene
currentScene = (Scene)((currentScene + 1) % 5);
sceneStartTime = currentTime;
// Reset scene-specific variables
if (currentScene == SCENE_FINALE) {
finaleStartTime = currentTime;
finaleFlashDone = false;
}
if (currentScene == SCENE_THEATER_CHASE) {
theaterChasePosition = 0;
}
}
// Render current scene
switch (currentScene) {
case SCENE_DOTS:
sceneDots();
break;
case SCENE_FLASH:
sceneFlash();
break;
case SCENE_THEATER_CHASE:
sceneTheaterChase();
break;
case SCENE_BLOBS:
sceneBlobs(deltaTime);
break;
case SCENE_FINALE:
sceneFinale();
break;
}
// Display with smooth updates
FastLED.show();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment