Commit Graph
100 Commits
Author SHA1 Message Date
Claude ec97600763 test(07-03): add firmware frame mode tests — 19 passing
- TestDRGBFrames: single pixel, 300 LEDs, all black/white
- TestDRGBWFrames: single pixel, 300 LEDs RGBW, W channel preserved
- TestDNRGBFrames: single packet, multi-packet 600 LEDs, start_index offset
- TestFrameAssembler: single chunk, two chunks, timeout, reset, round-trip
- TestFrameModeIntegration: DRGB/DRGBW pixel counts, strip type routing
2026-04-07 15:42:37 +00:00
Claude 62bc61259f feat(07-01): add firmware scaffold — boot.py, config.json, led_driver.py, protocol tests
- firmware/boot.py: WiFi connection on startup (MicroPython boot sequence)
- firmware/config.json: default device config with all 9 fields (ssid, password, led_count, strip_type, led_pin, spi_id, spi_clk_pin, spi_dat_pin, udp_port)
- firmware/led_driver.py: NeoPixelStrip (SK6812 RGBW) and WS2801Strip (RGB SPI) with unified interface (write_rgb, write_rgbw, fill, clear)
- tests/test_firmware_protocol.py: 21 host-side tests for round-trip protocol parsing (DRGB, DRGBW, DNRGB, animation_cmd)
2026-04-07 15:42:17 +00:00
Claude 2a22eeff75 feat(07-03): implement frame mode in firmware/main.py
- Import parse_drgb, parse_drgbw, parse_dnrgb, FrameAssembler
- Wire DRGB/DRGBW/DNRGB parsing in udp_listener_task
- Add frame_is_rgbw flag to route to write_rgb or write_rgbw
- Add 2s frame timeout in render_task — reverts to last animation
- Initialize FrameAssembler with led_count in main()
2026-04-07 15:41:57 +00:00
Claude 49f0b0f68c feat(07-02): add animation test suite and re-apply main.py render wiring
- Create tests/test_firmware_animations.py with 30+ tests for all 7 animations
- Re-apply animation rendering to firmware/main.py (07-01 parallel agent reverted)
- Tests cover: _hsv_to_rgb, all animation classes, create_animation factory
- Full round-trip test: server encode -> firmware parse -> create -> render
2026-04-07 15:41:45 +00:00
Claude 8e521e5bfb feat(07-02): wire animation rendering into firmware/main.py
- Add import for create_animation from animations module
- Add current_anim_instance global for animation object
- Create animation instance in udp_listener_task on anim_cmd receipt
- Implement render loop in render_task using ticks_diff for timing
2026-04-07 15:40:43 +00:00
Claude d14fa3ca78 feat(07-03): add FrameAssembler to firmware/protocol.py
- Add FrameAssembler class for multi-packet DNRGB reassembly
- 200ms timeout discards stale partial frames
- feed() returns complete frame list when all expected pixels received
- reset() clears buffer manually
2026-04-07 15:40:30 +00:00
Claude 053e1abd60 feat(07-02): add firmware animations.py with all 7 animation classes
- Port all 7 animations from lightsync/animations/*.py to MicroPython
- Inline _hsv_to_rgb (no colorsys in MicroPython)
- Use bytearray for FireAnimation heat buffer (avoids GC pressure)
- create_animation() factory maps parsed command dicts to instances
- No lightsync imports, no abc, no type hints
2026-04-07 15:40:02 +00:00
ClaudeandClaude Sonnet 4.6 9a3370e71c docs(07): create phase 7 microcontroller firmware plans
Three sequential plans for MicroPython ESP32 firmware:
- 07-01: Scaffold (UDP listener, packet discriminator, LED drivers, config)
- 07-02: Animation renderer (all 7 types ported from lightsync)
- 07-03: Frame mode (DRGB/DRGBW/DNRGB parsing, reassembly, timeout)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 12:22:49 +00:00
Claude b7b0673b43 docs(07): research microcontroller firmware phase 2026-04-07 12:16:29 +00:00
Claude 875be14a8d docs(phase-06): evolve PROJECT.md after phase completion 2026-04-07 11:53:39 +00:00
Claude 3877a00ba0 docs(phase-06): complete phase execution 2026-04-07 11:53:15 +00:00
Claude 6d8d4845c5 docs(06-03): complete AI sync plan — heuristic+LLM show generation
- 06-03-SUMMARY.md: full plan documentation
- STATE.md: position advanced, decisions logged, session updated
- ROADMAP.md: phase 06 marked complete (3/3 plans)
2026-04-07 11:48:39 +00:00
Claude 70b86f78ce feat(06-03): add AI Sync panel UI and frontend wiring
- Add AI SYNC button to timeline toolbar, panel hidden by default (D-08)
- Panel: feature toggles (BEATS, ONSETS, CHROMA, RMS), GENERATE button, USE LLM toggle
- GENERATE disabled until audio loaded; re-enables on loadedmetadata event
- LLM toggle enabled/disabled based on /api/sync/llm-available at page load
- D-07: confirmation dialog before replacing existing timeline blocks
- D-11: section_range passed when a segment is selected in the overlay
- Segment selection watcher (200ms interval) updates info text dynamically
- Add AI sync panel styles — flat terminal aesthetic, no border-radius
2026-04-07 11:47:14 +00:00
Claude b6aacad9ce feat(06-03): add AI sync backend — heuristic mapper, LLM generator, API endpoint
- Create lightsync/api/sync.py with POST /api/sync/generate and GET /api/sync/llm-available
- heuristic_generate: intensity-to-animation mapping, chroma-to-color, beat-snapped block placement
- llm_generate: Anthropic claude-3-haiku with lazy init, JSON parse + validation
- section_range filtering for per-section generation (D-11)
- Register sync.router with prefix /api/sync in main.py
2026-04-07 11:45:49 +00:00
Claude 2fef43dcc3 docs(06-02): complete audio feature extraction and segment overlay plan
- Create 06-02-SUMMARY.md with full execution record
- Update STATE.md: plan advanced to 3/3, progress 95%, decisions logged
- Update ROADMAP.md: phase 6 now 2/3 summaries
- Mark SYNC-03 complete in REQUIREMENTS.md
2026-04-07 11:42:44 +00:00
Claude 48e15ee013 feat(06-02): add segment overlay canvas rendering and click interaction
- Create lightsync/frontend/timeline/segments.js with SegmentOverlay class
- Integrate SegmentOverlay into timeline.js render pipeline (between row banding and waveform)
- Add loadSegments method to TimelineCanvas
- Wire loadSegments call in app.js loadedmetadata handler
2026-04-07 11:41:41 +00:00
Claude d339ba588d feat(06-02): add feature extraction and segmentation backend modules + API
- Create lightsync/audio/features.py with extract_features and extract_features_async
- Create lightsync/audio/segments.py with detect_segments via agglomerative clustering
- Add /api/audio/segments and /api/audio/features endpoints to audio.py
- Initialize app.state.segments cache in main.py lifespan
2026-04-07 11:40:29 +00:00
Claude 8c1f0a0fbc docs(06-01): complete YouTube audio loading plan
- Create 06-01-SUMMARY.md with full execution details
- Update STATE.md: advance to plan 2, 90% progress, add decisions
- Update ROADMAP.md: phase 6 showing 1/3 plans complete
2026-04-07 11:38:19 +00:00
Claude dca1cb837b feat(06-01): add YouTube URL input with terminal progress bar
- Add yt-url input, FETCH button, and yt-progress span to transport bar
- Add fetchWithProgress() SSE ReadableStream reader function
- Add renderProgressBar() terminal-style block fill renderer (DOWNLOADING ████)
- Wire FETCH button click handler with D-02 disable-during-download behavior
- Auto-load audio into player after download (D-01 same pipeline as upload)
- Add .yt-url-input and .yt-progress CSS styles (flat, monospace, no border-radius)
- Enable yt-url input on page load via JS
2026-04-07 11:37:10 +00:00
Claude d58ea8c633 feat(06-01): add YouTube download endpoint with SSE progress streaming
- POST /api/audio/fetch accepts {url: string}
- Spawns yt-dlp subprocess via sys.executable -m yt_dlp
- Streams SSE progress events with PROGRESS_RE line-by-line parsing
- Uses --newline flag for reliable progress output
- Uses yt_%(id)s.%(ext)s template for predictable filenames
- Runs beat analysis on completion and caches in app.state.beats
- Returns done event with path and filename on success
2026-04-07 11:36:19 +00:00
Claude 165cdb6f23 feat(06-01): add yt-dlp and anthropic dependencies
- Add yt-dlp>=2025.0.0 and anthropic>=0.40.0 to pyproject.toml
- Wire ANTHROPIC_API_KEY env var in docker-compose.prod.yml (empty default)
- Rebuilt Docker image, both packages importable in container
2026-04-07 11:35:33 +00:00
Claude 57bd91d4e7 docs(06-ai-sync): create phase plan 2026-04-07 11:31:04 +00:00
Claude b88bd98c40 docs(06): research phase AI sync domain 2026-04-07 11:17:32 +00:00
Claude b0e13bfab8 docs(06): capture phase context 2026-04-07 10:57:18 +00:00
Claude c715369a65 docs(phase-05): evolve PROJECT.md after phase completion 2026-04-07 10:42:22 +00:00
Claude c706d4c8dd docs(phase-05): complete phase execution 2026-04-07 10:41:54 +00:00
Claude a6b8a2d591 docs(05-04): complete mouse play/pause WS fix plan — SUMMARY, STATE, ROADMAP updated
- 05-04-SUMMARY.md: gap closure complete, UDP cues fire for mouse playback
- STATE.md: plan advanced to 2/4, progress 100%, decision logged
- ROADMAP.md: Phase 05 marked Complete (4/4 plans)
2026-04-07 10:38:24 +00:00
Claude ac0f3d52db feat(05-04): add WebSocket play/pause messages to wireAudio() event listeners
- audio 'play' listener now sends client.send({ type: 'play', position }) to server
- audio 'pause' listener now sends client.send({ type: 'pause', position }) to server
- Server is_playing flag now set correctly on mouse-initiated playback
- UDP cue scheduler fires for both mouse and keyboard playback
2026-04-07 10:37:18 +00:00
ClaudeandClaude Sonnet 4.6 11cd0fce7b docs(05): add phase 5 verification report (9/11 truths, gaps identified)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:31:43 +00:00
Claude 4543f9b38d docs(05): add gap closure plan 05-04 for mouse play/pause WS fix 2026-04-07 10:31:12 +00:00
Claude 448bd34eb2 docs(05-03): complete keyboard shortcuts plan — SUMMARY, STATE, ROADMAP updated 2026-04-07 10:19:12 +00:00
Claude 3562652f2b feat(05-03): add wireKeyboard() with all 7 keyboard shortcuts
- Space toggles play/pause, sends play/pause msg to server
- ArrowLeft/ArrowRight seek +/-5s, send seek msg, clear preview strips
- Ctrl+Z calls timeline.history.undo()
- Ctrl+Shift+Z and Ctrl+Y both call timeline.history.redo()
- Ctrl+S prevents browser dialog, fetches show and POSTs to save
- All shortcuts suppressed when focus is on input/select/textarea
- Space skips when active element is a button (prevents double-toggle)
- wireKeyboard() called at init after wireAudio()
2026-04-07 10:18:19 +00:00
Claude 1499736fde docs(05-02): complete live preview panel plan — SUMMARY, STATE, ROADMAP updated 2026-04-07 10:16:48 +00:00
Claude e064954b28 feat(05-02): wire preview_update handler, show selector, and preview strip builder
- Import setCurrentShowId from commands.js
- Add preview_update WebSocket message handler that calls updatePreviewStrip
- Add updatePreviewStrip() function to update device strip color and animation
- Add buildPreviewStrips() function to populate #live-preview from device list
- Call buildPreviewStrips() inside device fetch callback in initTimeline
- Add async refreshShows() to populate show-select dropdown from /api/shows
- Add btn-load-show click handler: loads show, populates tracks, sends WebSocket load msg
- Clear preview strips on seek (remove active class, reset color and anim label)
2026-04-07 10:15:34 +00:00
Claude e0e412fcd1 feat(05-02): add live preview panel HTML and CSS styles
- Add #live-preview div between timeline-canvas and block-inspector
- Add show selector (show-select + btn-load-show) to transport bar
- Add keyboard-hint span at far right of transport bar
- Append .live-preview, .device-strip, .strip-label, .strip-color, .strip-anim, .keyboard-hint CSS styles
2026-04-07 10:14:16 +00:00
Claude 7809402d54 docs(05-01): complete cue scheduler plan — SUMMARY, STATE, ROADMAP updated
- 05-01-SUMMARY.md created with task commits, decisions, next-phase readiness
- STATE.md advanced to plan 2/3, progress 88%, decisions logged
- ROADMAP.md updated: phase 5 in progress (1/3 summaries)
- REQUIREMENTS.md: SHW-03 marked complete
2026-04-07 10:12:09 +00:00
Claude 64c49c9e64 feat(05-01): add cue scheduling loop and preview_update broadcast to tick handler
- LOOKAHEAD = 0.060 (60ms window per D-02)
- Scheduler iterates show.tracks/cues on each tick, gated on is_playing and show
- Cues within lookahead window fire UDP via encode_animation_cmd + UDPSender.send
- Past cues (>100ms behind) silently marked fired without UDP dispatch
- active_cue tracking per track enables preview state for already-fired blocks
- preview_update messages broadcast to all clients on each tick (D-07)
- Clear preview_update (animation: null) sent when no active cue on a device
2026-04-07 10:11:03 +00:00
Claude 3d36cc5754 feat(05-01): add per-connection show state and load handler to ws.py
- Import encode_animation_cmd and ShowModel at module level
- Add show, fired_ids, is_playing per-connection state variables
- Extend load handler to load show from show_store by show_id (D-04)
- play handler sets is_playing=True
- pause handler sets is_playing=False without touching fired_ids
- seek handler rebuilds fired_ids from show cues before seek position (D-03)
2026-04-07 10:10:23 +00:00
Claude 3c9575f915 docs(05): phase 5 execution plans — cue scheduler, live preview, keyboard shortcuts 2026-04-07 09:16:45 +00:00
Claude 6a1ec4e3b6 docs(05): create phase plan — 3 plans across 3 waves 2026-04-07 09:16:17 +00:00
Claude 02826b2c89 docs(05): fix UI-SPEC checker blocks — typography, spacing 2026-04-07 09:05:57 +00:00
ClaudeandClaude Sonnet 4.6 f739c8fe19 docs(05): UI design contract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 09:02:33 +00:00
Claude 2ac23c3297 docs(05): research phase domain 2026-04-07 08:59:07 +00:00
Claude 7b37f014d0 docs(state): record phase 5 context session 2026-04-07 08:48:39 +00:00
Claude 4ce9759055 docs(05): capture phase context 2026-04-07 08:48:34 +00:00
Claude 34077018a9 docs(phase-04): evolve PROJECT.md after phase completion 2026-04-07 00:00:51 +00:00
Claude 399b7a1e05 docs(phase-04): complete phase execution 2026-04-07 00:00:33 +00:00
Claude 3483de871e docs(04-04): complete block inspector plan — SUMMARY, STATE, ROADMAP updated
- BlockInspector with color/speed/direction/length param controls
- UpdateParamsCommand for full undo/redo param editing
- Phase 04 complete (4/4 plans), TL-04 requirement marked done
- Progress: 14/14 plans (100%)
2026-04-06 23:56:25 +00:00
Claude 8e084ca8dc feat(04-04): wire BlockInspector into timeline and app.js
- app.js imports BlockInspector and DeleteBlockCommand
- Inspector created with #block-inspector element and timeline.history
- timeline._onSelectBlock callback shows/hides inspector on block select
- inspector._onDelete fires DeleteBlockCommand and hides inspector
- inspector.tracks kept in sync after loadTracks() resolves
- timeline.js block rendering uses blockColor + '1a' for color accent fill
- undo/redo handlers re-trigger _onSelectBlock to refresh inspector values
2026-04-06 23:54:22 +00:00
Claude e82fc4a775 feat(04-04): add UpdateParamsCommand + BlockInspector module
- UpdateParamsCommand with execute/undo for color/speed/direction/length
- Uses module-level currentShowId (not this._showId) matching existing pattern
- BlockInspector renders compact strip with animation label, color swatch,
  speed input, direction select, length input, and remove button
- Color change committed on 'change' event (not 'input') to avoid command flood
- _onDelete callback wired to remove button for app.js to handle
2026-04-06 23:51:48 +00:00
Claude d1965dcefc docs(04-03): complete BeatGrid plan summary and update state 2026-04-06 23:49:44 +00:00
Claude 287fe16a56 feat(04-03): BeatGrid module + enhanced beat rendering + loading states
- Create beats.js with BeatGrid class: load(), getCalibratedBeats(), getCalibratedOnsets(), snap(), hasBeats getter
- BeatGrid manages calibrationOffset and snapEnabled state
- snap() uses 100ms threshold against calibrated beat positions
- Handles API field variants: beats/beat_times, tempo/tempo_bpm
- Refactor timeline.js: replace beatTimes/calibrationOffset/snapEnabled with this.beatGrid
- Add getters/setters for calibrationOffset and snapEnabled (backward compat with app.js)
- render() now shows DETECTING BEATS... loading indicator and error state
- render() draws beat marks via getCalibratedBeats(), onset ticks via getCalibratedOnsets()
- snapToBeat() delegates to beatGrid.snap()
- app.js chains BPM label update after loadBeats() resolves
2026-04-06 23:48:27 +00:00
Claude c37fa6d1f6 docs(04-02): complete block interactions plan — SUMMARY, STATE, ROADMAP updated
- CommandHistory + 4 command classes + backend CRUD endpoints
- Full mouse interactions: drag-place, move, resize, delete, undo/redo
- Requirements TL-02, TL-03, TL-06 marked complete
- Progress: 86% (12/14 plans)
2026-04-06 23:46:05 +00:00
Claude 7e18ac6c84 feat(04-02): wire block interactions into TimelineCanvas
- Import CommandHistory and all 4 command classes from new modules
- hitTest() with 8px EDGE for resize-left/resize-right/move zones
- _hasOverlap() check for time range collision on same track
- snapToBeat() with 0.1s threshold and calibrationOffset
- _setupInteractions() with mousedown/mousemove/keydown listeners
- _startMove(): live drag preview, snap on mouseup, overlap rejection
- _startResize(): left/right edge handles, 0.5s min duration, snap
- handleDrop(): place block from palette with crypto.randomUUID()
- Keyboard: Delete, Ctrl+Z (undo), Ctrl+Shift+Z / Ctrl+Y (redo)
- app.js: dragstart on palette, dragover/dragleave/drop on canvas
- Ghost block rendered during dragover for visual feedback
- Resize handle indicators drawn on selected block
2026-04-06 23:44:45 +00:00
Claude 658c92f94d feat(04-02): command pattern + CommandHistory + block CRUD backend
- CommandHistory class with execute/undo/redo, 50-step max, redo-clear on new action
- PlaceBlockCommand, MoveBlockCommand, ResizeBlockCommand, DeleteBlockCommand
- setCurrentShowId() module-level function for backend sync routing
- syncBlock() fire-and-forget fetch helper for backend sync
- POST/PATCH/DELETE /api/shows/{show_id}/tracks/{device_id}/blocks endpoints
- timeline router registered in main.py with prefix=/api
2026-04-06 23:37:29 +00:00
Claude 96d7825c34 docs(04-01): complete timeline canvas foundation plan — SUMMARY, STATE, ROADMAP updated
- SUMMARY: timeline canvas with per-device tracks, waveform, beat marks, playback cursor
- STATE: plan advanced to 2/4, progress 79%, decisions and metrics recorded
- ROADMAP: Phase 4 progress updated (1/4 plans complete, In Progress)
- Requirements TL-01, TL-05 marked complete
2026-04-06 23:35:29 +00:00
Claude efa612380c feat(04-01): TimelineCanvas class + CSS + app.js wiring
- Create lightsync/frontend/timeline/timeline.js with TimelineCanvas class
  - Per-device horizontal tracks with track headers and time axis
  - HiDPI canvas rendering with devicePixelRatio handling
  - Waveform peaks background layer, beat marks overlay, cue block rendering
  - Playback cursor synced to audio.currentTime via requestAnimationFrame
  - Auto-scroll when cursor approaches right edge
  - Mouse wheel scrolling on timeline canvas
  - loadTracks(), loadWaveform(), loadBeats() data loading methods
  - Empty state messages when no devices or audio loaded
- Add timeline CSS to style.css (toolbar, canvas, inspector strip, snap button)
- Wire timeline into app.js: import, initTimeline(), zoom/beat-offset/snap controls
- Remove old animation-grid click listener (element no longer exists in main-area)
- Populate animation palette dynamically in sidebar with 7 animation types
2026-04-06 23:34:32 +00:00
Claude 454fddc35e feat(04-01): data model changes + HTML layout restructure
- Add CueModel.duration field (float = 4.0) for block length in seconds
- Add AnalysisBlock.calibration_offset field (float = 0.03) for librosa bias
- Replace main-area animation grid with timeline canvas layout (toolbar, canvas, inspector)
- Move ANIMATIONS panel into sidebar below DEVICES panel
- Add timeline-toolbar with zoom slider, beat offset input, snap toggle
2026-04-06 23:30:55 +00:00
Claude 22f31cdad0 docs(phase-04): mark planning complete 2026-04-06 23:25:04 +00:00
Claude 78c5d6256d docs(phase-04): add phase plans (4 plans, 3 waves) 2026-04-06 23:24:46 +00:00
Claude e8e0532972 docs(04): create phase plan 2026-04-06 23:24:11 +00:00
Claude 5f46f6f8a1 docs(04): UI design contract 2026-04-06 23:11:02 +00:00
ClaudeandClaude Sonnet 4.6 f2abc04dcc docs(04): UI design contract for timeline editor phase
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:09:42 +00:00
ClaudeandClaude Sonnet 4.6 9d9cd935b6 docs(phase-04): capture context for timeline editor phase
Inspector panel below timeline (above transport) confirmed.
Block data model, canvas tech, and undo/redo architecture left to Claude's discretion with recommendations noted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 22:14:06 +00:00
Claude 5a84e55059 docs(phase-03): evolve PROJECT.md after phase completion 2026-04-06 21:56:19 +00:00
Claude 73d50d04a8 docs(phase-03): complete phase execution 2026-04-06 21:55:48 +00:00
Claude 299612589b docs(03-03): complete UDP simulator and FastAPI integration plan
- Create 03-03-SUMMARY.md: simulator, e2e tests, lifespan integration
- Update STATE.md: advance plan, 100% progress, record decisions
- Update ROADMAP.md: phase 03 marked Complete (3/3 plans)
- Mark UDP-04 requirement complete in REQUIREMENTS.md
2026-04-06 21:52:37 +00:00
Claude 509911f5c5 feat(03-03): integrate UDPSender into FastAPI lifespan
- Import UDPSender in main.py
- Start UDPSender on app boot, assign to app.state.udp_sender
- Stop UDPSender cleanly on shutdown (before registry.save)
- All existing lifespan behavior unchanged (beats, registry, show_store)
2026-04-06 21:51:40 +00:00
Claude f3f0baff51 feat(03-03): implement UDP simulator with packet logging — GREEN
- SimulatorProtocol parses DRGB, DRGBW, DNRGB, ANIM_CMD packets
- PacketLog dataclass stores led_count, start_index, decoded cmd
- run_simulator() async factory for test and standalone use
- Runnable as __main__ standalone server on WLED_PORT
- All 7 e2e tests pass
2026-04-06 21:51:16 +00:00
Claude 3829f81a5f test(03-03): add failing e2e protocol tests — RED
- 7 e2e tests covering DRGB, DRGBW, DNRGB, ANIM_CMD, full pipeline, SK6812, standalone import
- Uses asyncio.run() pattern (no pytest-asyncio dependency)
- All tests fail with ModuleNotFoundError: simulator not yet implemented
2026-04-06 21:50:40 +00:00
Claude 99cde0331d docs(03-02): complete animation library plan
- Add 03-02-SUMMARY.md with 7 animation types documented
- Advance STATE.md to plan 3/3 in phase 03
- Mark ANI-01, ANI-02 complete in REQUIREMENTS.md
- Update ROADMAP.md phase 3 progress (2/3 plans done)
2026-04-06 21:48:43 +00:00
Claude f4da9564d4 feat(03-02): implement animation library — 7 types with registry and factory
- AnimationBase ABC with render(t, led_count) and from_params() contract
- SolidColorAnimation: time-independent uniform fill
- ChaseAnimation: moving lit segment with forward/reverse support
- PulseAnimation: sine-wave breathing effect (speed maps to period)
- RainbowAnimation: HSV hue cycle across strip via colorsys.hsv_to_rgb
- StrobeAnimation: on/off flashing at 1-20 Hz with configurable duty cycle
- ColorWipeAnimation: progressive fill from start or end
- FireAnimation: Fire2012 algorithm with cooling/sparking/heat-to-color mapping
- ANIMATION_REGISTRY + create_animation() factory function
- All 24 tests passing
2026-04-06 21:47:39 +00:00
Claude 920243fcae test(03-02): add failing tests for animation library
- Tests for all 7 animation types (solid_color, chase, pulse, rainbow, strobe, color_wipe, fire)
- Tests for AnimationBase ABC contract
- Tests for ANIMATION_REGISTRY and create_animation() factory
- Tests for from_params() on all types
- Length invariant test covering all animations
2026-04-06 21:45:54 +00:00
Claude af530cfe60 docs(03-01): complete protocol encoders plan — SUMMARY, STATE, ROADMAP updated
- 03-01-SUMMARY.md: WLED encoders, animation cmd, UDP sender, 27 passing tests
- STATE.md: advanced to plan 2/3, progress 80%, decisions recorded
- ROADMAP.md: phase 3 plan progress updated (1/3 complete)
- REQUIREMENTS.md: marked UDP-01/02/03, ANI-03/04 complete
- pyproject.toml: pytest added as dev dependency
- uv.lock: updated lockfile
2026-04-06 21:43:59 +00:00
Claude f17e77f7bb feat(03-01): UDP sender and device stub completion
- UDPSender: single shared asyncio DatagramTransport, start/stop lifecycle, fire-and-forget send()
- _NullProtocol: swallows OS send errors (UDP delivery is best-effort for LEDs)
- SK6812Device.encode_animation_cmd: replaces b'' stub with real 16-byte 0xAC packet
- WS2801Device.encode_animation_cmd: replaces b'' stub with real 16-byte 0xAC packet
- Both devices delegate to encode_animation_cmd from lightsync.protocol.animation_cmd
2026-04-06 21:42:29 +00:00
Claude 5e13da3967 feat(03-01): implement WLED protocol encoders and animation command packet
- encode_drgb/encode_drgbw: 2-byte header + RGB/RGBW body, truncate at MTU max
- encode_dnrgb_packets: splits large frames into 489-LED chunks with big-endian start index
- encode_animation_cmd: 16-byte 0xAC packet with speed (float32), color, flags, density
- decode_animation_cmd: full inverse round-trip for all 7 animation types
- All 27 tests pass
2026-04-06 21:40:52 +00:00
Claude ffc4ef6dc6 test(03-01): add failing tests for protocol encoders and animation command
- DRGB/DRGBW/DNRGB encoder behavior tests (byte layout, MTU, truncation)
- DNRGB start index big-endian byte order tests
- Animation command 0xAC packet tests (all 7 types, roundtrip, flags)
- Tests fail: lightsync.protocol.drgb and animation_cmd modules do not exist yet
2026-04-06 21:40:02 +00:00
Claude 75ec1158b7 docs(03): create phase plan — protocol encoders, animation library, simulator 2026-04-06 21:33:27 +00:00
Claude 32ce615a63 docs(03): research communication protocol — WLED UDP spec, async sender, animation library 2026-04-06 21:28:01 +00:00
Claude 37f483e880 feat(02): controller UI — vertical anim list, play/pause, browser audio, terminal aesthetic
- Replace waveform+DAW layout with animation controller
- Animations as vertical list with > cursor, no icons
- Transport: play/pause button, seek slider, time display
- Browser <audio> handles playback, 10Hz ticks to server
- Sidebar: devices only (200px)
- python:3.11-slim base (replaces Alpine, fixes librosa/numba build)
- libsndfile symlink kept for soundfile
- Remove python-mpv, add librosa for beat detection
2026-04-06 17:32:11 +00:00
Claude 9c1ca03957 docs(02-03): complete architecture redesign — browser audio + librosa beat detection
- 02-03-SUMMARY.md: full redesign summary, decisions, deviations documented
- STATE.md: decisions updated, session recorded, progress at 100% (7/7 plans)
- ROADMAP.md: Phase 2 confirmed Complete (3/3 plans)
2026-04-06 15:40:55 +00:00
Claude 0963ad4a70 fix(02-03): fix upload file selection — pass abs path to refreshFileList
- Options use /app/shows/filename as value
- Upload response returns /app/shows/filename as path
- Pass path directly to refreshFileList so pre-selection matches
2026-04-06 15:39:21 +00:00
Claude f1941a90db feat(02-03): redesign frontend — browser audio element, beat markers, animation selector
- index.html: add <audio id=player> (hidden), beat-indicator dot in header
- index.html: animation selector panel with Beat Pulse/Strobe/Color Wave/Rainbow options
- index.html: remove seek slider + time display (browser audio element has native controls)
- index.html: upload button + file selector remain in transport bar
- app.js: wire <audio> element — play/pause/seeked/timeupdate events send WS ticks
- app.js: 10Hz setInterval tick loop (supplements native timeupdate ~4Hz)
- app.js: WS beat message -> flashBeatIndicator() animation
- app.js: loadWaveformAndBeats() draws waveform peaks + beat marker lines on canvas
- app.js: loadSelectedFile() sets audio.src=/shows/filename, fetches beats+waveform
- app.js: upload response caches beat data returned from server immediately
- style.css: .beat-indicator with .beat-flash (yellow glow animation)
- style.css: .animation-select-input for the animations panel dropdown
2026-04-06 15:38:30 +00:00
Claude 1f1ff92446 chore(02-03): delete lightsync/audio/engine.py (MPV engine removed)
- Browser plays audio now; server-side MPV engine is no longer used
- Beat detection via librosa replaces position-polling engine
2026-04-06 15:37:10 +00:00
Claude 6366d8468f feat(02-03): remove MPVEngine from main.py, add beats cache and shows static route
- Remove MPVEngine import, engine module-level var, and lifespan start/stop logic
- Remove broadcast_loop task (no server-side position polling)
- Add app.state.beats = {} dict (filepath -> beat data cache) in lifespan startup
- Mount /shows as StaticFiles so browser <audio src=/shows/file.mp3> works
- Simplified lifespan: registry + show_store + beats init only
2026-04-06 15:37:06 +00:00
Claude 2c9ca0d6b4 feat(02-03): redesign WebSocket protocol — browser tick-driven beat detection
- Remove server-side broadcast_loop (MPV position polling eliminated)
- Browser sends {type: tick, position: N} at ~10Hz
- Server looks up nearest beat within 50ms window, sends {type: beat, position, tempo}
- Browser sends play/pause/seek events (logged, no UDP yet)
- Browser sends {type: load, path} to switch beat cache to specific file
- _find_nearest_beat() uses bisect for O(log n) lookup
- Per-connection beat state: current_file, beats list, tempo, last_beat_reported
2026-04-06 15:36:50 +00:00
Claude 636160e9a1 feat(02-03): redesign audio.py — browser-audio architecture, beat endpoint
- Remove engine dependency entirely (no MPVEngine, no app.state.engine)
- POST /api/audio/upload: saves file, runs librosa beat analysis, caches in app.state.beats
- GET /api/audio/beats?path=...: returns cached or on-demand beat analysis
- GET /api/audio/waveform?path=...: now takes path query param (no engine state lookup)
- GET /api/audio/files: unchanged
- Removed POST /api/audio/load and GET /api/audio/state (engine-only endpoints)
2026-04-06 15:36:21 +00:00
Claude 92cc09e908 feat(02-03): add librosa beat detection module (lightsync/audio/beats.py)
- analyze(path) -> {tempo: float, beats: [float, ...]} using librosa.beat.beat_track
- Returns beat timestamps in seconds converted from librosa frame indices
- analyze_async() wraps in asyncio.to_thread for non-blocking use from FastAPI
- Supports MP3/WAV/FLAC/OGG via librosa's ffmpeg/soundfile fallback chain
2026-04-06 15:35:58 +00:00
Claude cce76a2b1c chore(02-03): remove mpv-libs from Dockerfile, keep ffmpeg and libsndfile
- Remove mpv-libs and libmpv.so symlink (browser plays audio now)
- Keep ffmpeg/ffmpeg-libs (used by librosa and waveform extraction)
- Keep libsndfile and its symlink (used by soundfile package)
2026-04-06 15:35:43 +00:00
Claude 5d93294b97 chore(02-03): replace python-mpv with librosa>=0.10.0 in pyproject.toml
- Remove python-mpv>=1.0.5 (server-side playback replaced by browser audio)
- Add librosa>=0.10.0 for beat detection and tempo analysis
- Keep soundfile, numpy, python-multipart (still needed)
2026-04-06 15:35:35 +00:00
Claude 9239988d38 fix(02): Alpine libmpv/libsndfile symlinks, upload endpoint, browser file picker
- Dockerfile: add libmpv.so + libsndfile.so symlinks (Alpine/musl has no ldconfig)
- engine.py: monkey-patch ctypes.util.find_library as fallback for Alpine
- audio.py: add /upload (multipart) and /files endpoints
- pyproject.toml: add python-multipart
- frontend: replace path input with upload button + file selector dropdown
2026-04-06 15:34:11 +00:00
Claude 7f9efee47a docs(02-03): complete waveform display and transport UI plan — AUD-05 fulfilled
- SUMMARY.md for 02-03 (waveform extractor + transport UI)
- STATE.md: checkpoint at human-verify task 3, decisions recorded
- ROADMAP.md: phase 2 progress updated (3/3 plans)
- REQUIREMENTS.md: AUD-05 marked complete
2026-04-06 13:15:17 +00:00
Claude 148f16ba5e feat(02-03): build transport UI and waveform canvas
- Replace timeline placeholder with waveform canvas + playback cursor
- Replace transport placeholder with full transport bar controls
- Add play/pause/stop buttons, seek slider, time display, audio path input
- Add drawWaveform(), loadWaveform(), updatePosition(), formatTime() to app.js
- Dispatch msg.type=tick to updatePosition in handleMessage
- Wire transport buttons via WebSocket commands
- Add transport/waveform styles to style.css using existing CSS variables
- Deploy via ~/bin/deploy.sh
2026-04-06 13:14:05 +00:00
Claude da474d978d feat(02-03): add waveform extraction module and HTTP endpoint
- Create lightsync/audio/waveform.py with extract_peaks() (AUD-05)
- MP3 via ffmpeg pipe, WAV/FLAC/OGG via soundfile+numpy
- Add GET /api/audio/waveform endpoint with asyncio.to_thread
- peaks param clamped 100-5000, returns {peaks, count, file}
2026-04-06 13:11:38 +00:00
Claude 3de7f43b3f docs(02-02): complete audio engine plan 02 — WebSocket broadcast + REST audio endpoints
- SUMMARY: WebSocket hub wired to broadcast MPV position at 10Hz, command dispatch
- STATE: plan advanced to 3/3, progress 86%, decisions added
- ROADMAP: phase 2 progress updated (2/3 summaries)
- REQUIREMENTS: AUD-02, AUD-04 marked complete
2026-04-06 13:08:54 +00:00
Claude de455400d2 feat(02-02): add REST audio load endpoint with file validation
- Create lightsync/api/audio.py with POST /load and GET /state endpoints
- Validate file existence and extension (.mp3/.wav/.flac/.ogg)
- Use asyncio.to_thread for engine.load to avoid blocking event loop
- Return 404/400/503 on error conditions
- Register audio router at /api/audio prefix in main.py
- AUD-02 (YouTube) noted as Phase 6 deferral
2026-04-06 13:07:45 +00:00
Claude 5e400a6834 feat(02-02): wire WebSocket broadcast loop and command dispatch
- Add broadcast_loop sending position ticks at 10Hz to all clients
- Replace echo stub with play/pause/seek/load command dispatch to MPVEngine
- Start broadcast_loop as asyncio task in lifespan, cancel on shutdown
- Use get_engine callable to avoid circular import at module level
2026-04-06 13:07:18 +00:00
Claude 3173fa2228 docs(02-01): complete audio engine plan 01 — MPVEngine, SUMMARY, STATE, ROADMAP updated
- Create 02-01-SUMMARY.md with task commits, decisions, self-check
- Advance plan counter to plan 2 of 3
- Update progress to 71% (5/7 plans complete)
- Add 3 key decisions to STATE.md
- Mark AUD-01, AUD-03, INF-03 complete in REQUIREMENTS.md
- Update ROADMAP.md phase 2 progress
2026-04-06 13:05:02 +00:00
Claude 5f75a19868 feat(02-01): implement MPVEngine with position polling and lifespan integration
- Create lightsync/audio/__init__.py package marker
- Create lightsync/audio/engine.py with MPVEngine class
  - Headless mpv wrapper (vo=null, ao from MPV_AO env var default null)
  - 10Hz polling loop in daemon thread (time.sleep(0.1), not observe_property)
  - Thread-safe get_state() returning position, paused, duration, loaded, file
  - load/play/pause/seek/stop methods (AUD-01, AUD-03)
  - apply_timer_fix() with sys.platform == win32 guard (INF-03)
- Update main.py lifespan to create/start/stop MPVEngine
  - MPV_AO env var controls audio output (default null for server mode)
  - engine exposed via app.state.engine for API routes
2026-04-06 13:03:43 +00:00
Claude 474b4a71d5 chore(02-01): add audio dependencies to Dockerfile and pyproject.toml
- Add mpv-libs, ffmpeg, ffmpeg-libs, libsndfile to Alpine Dockerfile
- Add python-mpv>=1.0.5, soundfile>=0.12.0, numpy>=1.26.0 to pyproject.toml
2026-04-06 13:02:41 +00:00