- 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
4.2 KiB
4.2 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-timeline-editor | 01 | frontend-canvas |
|
|
|
|
|
|
Phase 04 Plan 01: Timeline Canvas Foundation Summary
One-liner: HTML5 canvas DAW timeline with per-device tracks, waveform background, beat marks, and realtime playback cursor synced to audio.currentTime via requestAnimationFrame.
What Was Built
- CueModel.duration (float = 4.0) — block length in seconds, Pydantic default handles old show files
- AnalysisBlock.calibration_offset (float = 0.03) — compensates librosa latency bias, applied when rendering beat marks
- TimelineCanvas class (
lightsync/frontend/timeline/timeline.js) — 280+ line canvas engine:- Coordinate system:
timeToX(),xToTime(),trackIndexToY(),yToTrackIndex() - HiDPI rendering via
devicePixelRatioin_resizeCanvas() - Layer render order: background → track banding → waveform → beat marks → cue blocks → drag ghost → headers → time axis → cursor
- Auto-scroll: cursor triggers viewport shift when it reaches 80% of canvas width
- Mouse wheel horizontal scroll
- Empty state messages for no-devices and no-audio cases
- Coordinate system:
- HTML layout restructured — main-area now contains toolbar → canvas → inspector strip; ANIMATIONS panel moved to sidebar
- CSS added — timeline toolbar, canvas fill, inspector strip, snap button active state
- app.js wired — imports TimelineCanvas, creates instance, wires zoom/beat-offset/snap controls, populates animation palette dynamically
Decisions Made
| Decision | Rationale |
|---|---|
| beats API 'beats' field (not 'beat_times') | API returns {"beats": [...]} — plan expected beat_times — auto-fixed (Rule 1) |
| Auto-scroll at 80%/20% threshold | Standard DAW follow behavior — keeps cursor in view without jerky jumps |
| Empty state in canvas | Simpler than DOM overlay, no z-index conflicts with toolbar/inspector |
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] beats API field name mismatch
- Found during: Task 2 implementation
- Issue: Plan specified
data.beat_timesbut/api/audio/beatsreturns{"beats": [...]}(fromanalyze()inbeats.py) - Fix:
loadBeats()usesdata.beats || data.beat_times || []— handles both field names - Files modified:
lightsync/frontend/timeline/timeline.js - Commit:
efa6123
Known Stubs
None — timeline renders live data from /api/devices, /api/audio/waveform, and /api/audio/beats. Cue blocks will be empty (track.cues = []) until Plan 04-02 adds block drag-and-drop, but that is expected for this phase.
Self-Check: PASSED
lightsync/frontend/timeline/timeline.jsexists: YES (efa6123)lightsync/models/show.pyhasduration: float = 4.0: YES (454fddc)lightsync/models/show.pyhascalibration_offset: float = 0.03: YES (454fddc)lightsync/frontend/index.htmlhasid="timeline-canvas": YESlightsync/frontend/style.csshas#timeline-canvas,.block-inspector: YESlightsync/frontend/app.jsimports TimelineCanvas: YES- Docker build and startup: CLEAN (no errors)
- Python model import test: PASSED (
CueModel(timestamp=1.0).duration == 4.0)