14 Commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
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
a21962cbcf feat(01-03): integration wiring, Docker deployment, lightsync.groll.cloud
- Fix API routing: redirect_slashes=False + strip trailing slashes from route paths
- Fix StaticFiles catch-all intercepting /api/* before FastAPI redirect
- Add [tool.hatch.build.targets.wheel] to pyproject.toml for Docker builds
- Install docker-compose plugin (v2.34.0) as CLI plugin
- Create Dockerfile (python:3.11-alpine, CMD python -m lightsync)
- Create docker-compose.prod.yml (Traefik + Authelia, edge network)
- Deploy lightsync container to lightsync.groll.cloud
- Add LightSync entry to start/projects.json
- Update frontend app.js to use /api/devices (no trailing slash)
2026-04-05 18:59:34 +00:00
Claude
5e10272c69 feat(01-01): create device registry, show store, FastAPI app, API routes, and frontend shell
- DeviceRegistry with load/save/add/remove/instantiate and _DEVICE_CLASSES dict
- ShowStore with save/load/list_ids using aiofiles for async I/O
- FastAPI app with lifespan hook (startup: load registry/store, shutdown: save registry)
- API routes: GET/POST /api/devices/, DELETE /api/devices/{id}, GET/POST /api/shows/, GET /api/shows/{id}
- WebSocket stub at /ws with ConnectionManager and ack echo
- Frontend shell: DAW layout with header, sidebar (DEVICES + ANIMATIONS), TIMELINE, TRANSPORT
- Terminal aesthetic: dark theme, cyan accents, monospace fonts, no drop shadows
- .gitignore for runtime data (devices.json, shows/, __pycache__, .venv)
2026-04-05 18:47:25 +00:00