Devices & Components
1
Arduino® UNO R4 WiFi
Software & Tools
Arduino IDE
Project description
Code
Nautilus_Light
1#include <FastLED.h> 2 3// Hardware setup 4#define NUM_LEDS 144 5#define DATA_PIN 4 6CRGB leds[NUM_LEDS]; 7 8const int soundSensor = A0; 9 10// Global tweaks 11const int soundThreshold = 72; // How loud the room needs to be to trigger a new light 12const int ledBrightness = 230; // Master brightness (0-255) 13const unsigned long interval = 10; // Main loop update rate in milliseconds (~100 FPS) 14 15// Sensor state 16unsigned long lastTime = 0; 17float smoothedSound = 0.0; // Keeps track of the smoothed audio signal to ignore mic static 18 19// Breathing animation settings 20const uint8_t BREATHE_MIN_V = 50; 21const uint8_t BREATHE_MAX_V = 200; 22const uint16_t BREATHE_PERIOD_MS = 8000; // A full breath takes 8 seconds 23 24uint8_t quietHue = 0; 25uint8_t breatheV = 0; 26uint8_t lastBreatheV = 0; 27uint8_t targetV = 0; 28 29static uint16_t breathPhase = 0; 30static unsigned long lastBreathTick = 0; 31 32// Running light animation settings 33struct Runner { 34 int pos; 35 CRGB color; 36 bool active; 37}; 38 39const uint8_t MAX_RUNNERS = 8; 40Runner runners[MAX_RUNNERS]; 41 42const uint8_t fadeAmt = 12; // How quickly the trails fade out 43unsigned long lastSpawnMs = 0; 44const unsigned long spawnCooldownMs = 60; // Minimum time between spawning new lights 45 46// Transition states between running and breathing 47static bool wasRunning = false; 48static bool inHandover = false; 49static unsigned long handoverStart = 0; 50const unsigned long HANDOVER_MS = 420; // Crossfade duration 51 52// Checks if there are any light runners currently moving across the strip 53bool anyRunnerActive() { 54 for (uint8_t i = 0; i < MAX_RUNNERS; i++) { 55 if (runners[i].active) return true; 56 } 57 return false; 58} 59 60// Spawns a new light runner at the start of the LED strip 61void spawnRunner() { 62 int slot = -1; 63 64 // Look for an empty slot 65 for (uint8_t i = 0; i < MAX_RUNNERS; i++) { 66 if (!runners[i].active) { 67 slot = i; 68 break; 69 } 70 } 71 72 // If all slots are full, hijack the one that has traveled the furthest 73 if (slot == -1) { 74 int bestPos = -9999; 75 for (uint8_t i = 0; i < MAX_RUNNERS; i++) { 76 if (runners[i].pos > bestPos) { 77 bestPos = runners[i].pos; 78 slot = i; 79 } 80 } 81 } 82 83 runners[slot].pos = 0; 84 runners[slot].color = CHSV(random8(), 255, 255); 85 runners[slot].active = true; 86} 87 88// Moves all active runners forward and fades their trails 89void stepRunners() { 90 fadeToBlackBy(leds, NUM_LEDS, fadeAmt); 91 92 // Draw current runners 93 for (uint8_t i = 0; i < MAX_RUNNERS; i++) { 94 if (runners[i].active && runners[i].pos < NUM_LEDS) { 95 leds[runners[i].pos] = runners[i].color; 96 } 97 } 98 99 // Move them forward 100 for (uint8_t i = 0; i < MAX_RUNNERS; i++) { 101 if (runners[i].active) { 102 runners[i].pos++; 103 } 104 } 105 106 // Retire runners that have reached the end of the strip 107 for (uint8_t i = 0; i < MAX_RUNNERS; i++) { 108 if (runners[i].active && runners[i].pos >= NUM_LEDS) { 109 runners[i].active = false; 110 } 111 } 112} 113 114// Calculates the smooth, sine-wave based breathing effect for idle time 115void doBreathing() { 116 unsigned long now = millis(); 117 unsigned long dt = (lastBreathTick == 0) ? 0 : (now - lastBreathTick); 118 lastBreathTick = now; 119 120 // Progress the sine wave phase based on elapsed time 121 uint16_t inc = (uint32_t(65536UL) * (dt ? dt : interval)) / BREATHE_PERIOD_MS; 122 breathPhase += inc; 123 124 int16_t s16 = sin16(breathPhase); 125 uint8_t wave8 = (uint16_t(s16) + 32768) >> 8; 126 uint8_t eased = ease8InOutQuad(wave8); 127 128 targetV = map(eased, 0, 255, BREATHE_MIN_V, BREATHE_MAX_V); 129 breatheV = lerp8by8(breatheV, targetV, 10); 130 131 // Pick a new random color when the breath reaches its dimmest point 132 if (breatheV <= BREATHE_MIN_V + 1 && lastBreatheV > BREATHE_MIN_V + 1) { 133 quietHue = random8(); 134 } 135 136 fill_solid(leds, NUM_LEDS, CHSV(quietHue, 255, breatheV)); 137 lastBreatheV = breatheV; 138} 139 140void setup() { 141 Serial.begin(9600); 142 pinMode(soundSensor, INPUT); 143 144 FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS); 145 FastLED.setBrightness(ledBrightness); 146 147 // Start with a nice Tiffany blue before any sounds are detected 148 quietHue = 127; 149 fill_solid(leds, NUM_LEDS, CHSV(127, 80, 255)); 150 FastLED.show(); 151 152 // Prime the smoothing filter with an initial reading 153 smoothedSound = analogRead(soundSensor); 154 randomSeed(analogRead(soundSensor)); 155 156 // Ensure all runners start inactive 157 for (uint8_t i = 0; i < MAX_RUNNERS; i++) { 158 runners[i].active = false; 159 runners[i].pos = 0; 160 runners[i].color = CRGB::Black; 161 } 162 163 lastTime = millis(); 164 lastBreathTick = millis(); 165 breathPhase = 0; 166 breatheV = BREATHE_MIN_V; 167 lastBreatheV = BREATHE_MIN_V + 2; 168 169 Serial.println("Setup complete. Sound reactive mode only."); 170} 171 172void loop() { 173 unsigned long currentTime = millis(); 174 175 if (currentTime - lastTime > interval) { 176 177 // Smooth out the raw audio signal to prevent flickering or false triggers 178 int rawSound = analogRead(soundSensor); 179 smoothedSound = (smoothedSound * 0.8) + (rawSound * 0.2); 180 bool isLoud = (smoothedSound > soundThreshold); 181 182 // If it's loud enough and the cooldown has passed, shoot a new light 183 if (isLoud && (currentTime - lastSpawnMs > spawnCooldownMs)) { 184 inHandover = false; 185 spawnRunner(); 186 lastSpawnMs = currentTime; 187 } 188 189 if (anyRunnerActive()) { 190 stepRunners(); 191 wasRunning = true; 192 inHandover = false; 193 } else { 194 // Transition from running state back to breathing state 195 if (wasRunning && !inHandover) { 196 breathPhase = 0; 197 breatheV = BREATHE_MIN_V; 198 lastBreatheV = BREATHE_MIN_V + 2; 199 inHandover = true; 200 handoverStart = currentTime; 201 } 202 203 // Handle the gentle crossfade between modes 204 if (inHandover) { 205 fadeToBlackBy(leds, NUM_LEDS, 32); 206 207 CHSV baseHSV(quietHue, 255, BREATHE_MIN_V); 208 CRGB baseRGB; 209 hsv2rgb_rainbow(baseHSV, baseRGB); 210 211 // Blend the running trail leftovers with the new breathing base color 212 for (int i = 0; i < NUM_LEDS; i++) { 213 nblend(leds[i], baseRGB, 40); 214 } 215 216 if (currentTime - handoverStart >= HANDOVER_MS) { 217 inHandover = false; 218 wasRunning = false; 219 } 220 } else { 221 doBreathing(); 222 } 223 } 224 225 FastLED.show(); 226 lastTime = currentTime; 227 } 228}
Comments
Only logged in users can leave comments