#include "startup_anim.h" #include "pipe_client.h" #include "daemon_log.h" #include #include #include // D-16 startup animation. Runs synchronously on the caller's thread AFTER // PipeClient::Connect() succeeds and BEFORE the OSC/Writer worker threads // spawn, so the writer sees a clean post-animation state on its first tick. void RunStartupAnimation(PipeClient& pipe) { if (!pipe.IsConnected()) { DaemonLog("startup anim skipped — pipe not connected"); return; } DaemonLog("startup anim begin"); // Phase 1: fill all 10 LEDs with white. pipe.SendCommand("backglow fill FFFFFF FFFFFF FFFFFF FFFFFF FFFFFF " "FFFFFF FFFFFF FFFFFF FFFFFF FFFFFF"); auto emitBri = [&](int n) { char line[32]; std::snprintf(line, sizeof(line), "backglow bri %d", n); pipe.SendCommand(line); }; // IN-04 (re-review) fix: drive tick timing with accumulated sleep_until // deadlines rather than sleep_for(1000/N) per step. The old form truncated // 1000/90 -> 11 ms/step (true total ~1001 ms) and 3000/270 -> 11 ms/step // (true total ~2981 ms). Using the phase-start time plus a rational // (i * phaseMs / N) offset keeps the final step landing exactly on the // phase boundary regardless of integer truncation. using clock = std::chrono::steady_clock; using ms = std::chrono::milliseconds; // Phase 2: ramp bri 0 -> 128 over 1 s (~90 ticks). const int kRampUpSteps = 90; const int kRampUpMs = 1000; auto phase2Start = clock::now(); for (int i = 0; i <= kRampUpSteps; ++i) { std::this_thread::sleep_until(phase2Start + ms(static_cast(i) * kRampUpMs / kRampUpSteps)); const int bri = (128 * i) / kRampUpSteps; emitBri(bri); } // Phase 3: hold ~250 ms at 128. std::this_thread::sleep_for(ms(250)); // Phase 4: ramp bri 128 -> 0 over 3 s (~270 ticks). const int kRampDownSteps = 270; const int kRampDownMs = 3000; auto phase4Start = clock::now(); for (int i = 0; i <= kRampDownSteps; ++i) { std::this_thread::sleep_until(phase4Start + ms(static_cast(i) * kRampDownMs / kRampDownSteps)); const int bri = 128 - (128 * i) / kRampDownSteps; emitBri(bri); } // Phase 5: off. pipe.SendCommand("backglow off"); DaemonLog("startup anim end"); }