docs(04-03): complete BeatGrid plan summary and update state
This commit is contained in:
@@ -36,8 +36,8 @@
|
|||||||
- [ ] **TL-04**: Color picker per animation block
|
- [ ] **TL-04**: Color picker per animation block
|
||||||
- [x] **TL-05**: Playback cursor (vertical line) tracking audio position in realtime
|
- [x] **TL-05**: Playback cursor (vertical line) tracking audio position in realtime
|
||||||
- [x] **TL-06**: Undo/redo for all timeline mutations (min 20 steps, command pattern)
|
- [x] **TL-06**: Undo/redo for all timeline mutations (min 20 steps, command pattern)
|
||||||
- [ ] **TL-07**: Beat detection marks overlaid on timeline (librosa beat_track)
|
- [x] **TL-07**: Beat detection marks overlaid on timeline (librosa beat_track)
|
||||||
- [ ] **TL-08**: Snap-to-beat when placing/resizing blocks
|
- [x] **TL-08**: Snap-to-beat when placing/resizing blocks
|
||||||
|
|
||||||
### Show Engine
|
### Show Engine
|
||||||
|
|
||||||
@@ -54,8 +54,8 @@
|
|||||||
|
|
||||||
### Automatic Sync
|
### Automatic Sync
|
||||||
|
|
||||||
- [ ] **SYNC-01**: Beat detection via librosa — produces beat timestamps and onset marks from loaded audio
|
- [x] **SYNC-01**: Beat detection via librosa — produces beat timestamps and onset marks from loaded audio
|
||||||
- [ ] **SYNC-02**: Beat calibration offset (UI control) — compensate for librosa's systematic 20-60ms latency bias
|
- [x] **SYNC-02**: Beat calibration offset (UI control) — compensate for librosa's systematic 20-60ms latency bias
|
||||||
- [ ] **SYNC-03**: AI-assisted show generation — given beat/segment analysis, auto-fill timeline with matched animations (basic heuristic mapping in v1; LLM enhancement optional)
|
- [ ] **SYNC-03**: AI-assisted show generation — given beat/segment analysis, auto-fill timeline with matched animations (basic heuristic mapping in v1; LLM enhancement optional)
|
||||||
|
|
||||||
### UI / UX
|
### UI / UX
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ Plans:
|
|||||||
Plans:
|
Plans:
|
||||||
- [x] 04-01: Timeline canvas — per-device tracks, time axis, block rendering, playback cursor
|
- [x] 04-01: Timeline canvas — per-device tracks, time axis, block rendering, playback cursor
|
||||||
- [x] 04-02: Block interactions — drag-and-place, move, resize, delete; command pattern undo/redo (min 20 steps)
|
- [x] 04-02: Block interactions — drag-and-place, move, resize, delete; command pattern undo/redo (min 20 steps)
|
||||||
- [ ] 04-03: Beat detection + snap — librosa beat_track + plp + onset_detect in ProcessPoolExecutor, beat mark overlay, snap-to-beat, calibration offset UI
|
- [x] 04-03: Beat detection + snap — librosa beat_track + plp + onset_detect in ProcessPoolExecutor, beat mark overlay, snap-to-beat, calibration offset UI
|
||||||
- [ ] 04-04: Block editor — animation library panel, color picker, parameter controls per block, changes reflected in show file
|
- [ ] 04-04: Block editor — animation library panel, color picker, parameter controls per block, changes reflected in show file
|
||||||
**UI hint**: yes
|
**UI hint**: yes
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
|
|||||||
| 1. Foundation | 4/4 | Complete | 2026-04-05 |
|
| 1. Foundation | 4/4 | Complete | 2026-04-05 |
|
||||||
| 2. Audio Engine | 3/3 | Complete | 2026-04-06 |
|
| 2. Audio Engine | 3/3 | Complete | 2026-04-06 |
|
||||||
| 3. Communication Protocol | 3/3 | Complete | 2026-04-06 |
|
| 3. Communication Protocol | 3/3 | Complete | 2026-04-06 |
|
||||||
| 4. Timeline Editor | 2/4 | In Progress| |
|
| 4. Timeline Editor | 3/4 | In Progress| |
|
||||||
| 5. Live Show Execution | 0/3 | Not started | - |
|
| 5. Live Show Execution | 0/3 | Not started | - |
|
||||||
| 6. AI Sync | 0/3 | Not started | - |
|
| 6. AI Sync | 0/3 | Not started | - |
|
||||||
| 7. Microcontroller Firmware | 0/3 | Not started | - |
|
| 7. Microcontroller Firmware | 0/3 | Not started | - |
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: executing
|
status: executing
|
||||||
stopped_at: Completed 04-timeline-editor/04-02-PLAN.md
|
stopped_at: Completed 04-timeline-editor/04-03-PLAN.md
|
||||||
last_updated: "2026-04-06T23:45:55.310Z"
|
last_updated: "2026-04-06T23:49:36.485Z"
|
||||||
last_activity: 2026-04-06
|
last_activity: 2026-04-06
|
||||||
progress:
|
progress:
|
||||||
total_phases: 7
|
total_phases: 7
|
||||||
completed_phases: 3
|
completed_phases: 3
|
||||||
total_plans: 14
|
total_plans: 14
|
||||||
completed_plans: 12
|
completed_plans: 13
|
||||||
percent: 0
|
percent: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ See: .planning/PROJECT.md (updated 2026-04-05)
|
|||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 04 (timeline-editor) — EXECUTING
|
Phase: 04 (timeline-editor) — EXECUTING
|
||||||
Plan: 3 of 4
|
Plan: 4 of 4
|
||||||
Status: Ready to execute
|
Status: Ready to execute
|
||||||
Last activity: 2026-04-06
|
Last activity: 2026-04-06
|
||||||
|
|
||||||
@@ -64,6 +64,7 @@ Progress: [░░░░░░░░░░] 0%
|
|||||||
| Phase 03-communication-protocol P03 | 2min | 2 tasks | 3 files |
|
| Phase 03-communication-protocol P03 | 2min | 2 tasks | 3 files |
|
||||||
| Phase 04-timeline-editor P01 | 4min | 2 tasks | 5 files |
|
| Phase 04-timeline-editor P01 | 4min | 2 tasks | 5 files |
|
||||||
| Phase 04-timeline-editor P02 | 9 | 2 tasks | 6 files |
|
| Phase 04-timeline-editor P02 | 9 | 2 tasks | 6 files |
|
||||||
|
| Phase 04-timeline-editor P03 | 2 | 1 tasks | 3 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
@@ -104,6 +105,8 @@ Recent decisions affecting current work:
|
|||||||
- [Phase 04-timeline-editor]: TimelineCanvas auto-scroll at 80%/20% threshold — standard DAW follow behavior
|
- [Phase 04-timeline-editor]: TimelineCanvas auto-scroll at 80%/20% threshold — standard DAW follow behavior
|
||||||
- [Phase 04-timeline-editor]: Revert-then-command pattern in _startMove: live drag updates block.timestamp for visual feedback; mouseup reverts to oldTimestamp then MoveBlockCommand executes — history always has correct before/after state
|
- [Phase 04-timeline-editor]: Revert-then-command pattern in _startMove: live drag updates block.timestamp for visual feedback; mouseup reverts to oldTimestamp then MoveBlockCommand executes — history always has correct before/after state
|
||||||
- [Phase 04-timeline-editor]: setCurrentShowId() module-level function routes backend sync without coupling commands to app state
|
- [Phase 04-timeline-editor]: setCurrentShowId() module-level function routes backend sync without coupling commands to app state
|
||||||
|
- [Phase 04-timeline-editor]: BeatGrid stores raw beat_times unmodified — calibrationOffset subtracted only at getCalibratedBeats()/snap() time — offset slider works without corrupting base data
|
||||||
|
- [Phase 04-timeline-editor]: BeatGrid.load() normalizes API field variants: beats/beat_times and tempo/tempo_bpm — actual API returns 'beats'/'tempo' not the plan's expected field names
|
||||||
|
|
||||||
### Pending Todos
|
### Pending Todos
|
||||||
|
|
||||||
@@ -117,8 +120,8 @@ None yet.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-06T23:45:55.305Z
|
Last session: 2026-04-06T23:49:36.477Z
|
||||||
Stopped at: Completed 04-timeline-editor/04-02-PLAN.md
|
Stopped at: Completed 04-timeline-editor/04-03-PLAN.md
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|
||||||
Next action: `/gsd:plan-phase 1`
|
Next action: `/gsd:plan-phase 1`
|
||||||
|
|||||||
145
.planning/phases/04-timeline-editor/04-03-SUMMARY.md
Normal file
145
.planning/phases/04-timeline-editor/04-03-SUMMARY.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
---
|
||||||
|
phase: 04-timeline-editor
|
||||||
|
plan: 03
|
||||||
|
subsystem: frontend-canvas
|
||||||
|
tags: [canvas, timeline, beat-detection, snap-to-beat, daw-ui, librosa]
|
||||||
|
|
||||||
|
dependency_graph:
|
||||||
|
requires:
|
||||||
|
- phase: 04-timeline-editor/04-01
|
||||||
|
provides: TimelineCanvas class with beat mark rendering, coordinate helpers, render loop
|
||||||
|
- phase: 04-timeline-editor/04-02
|
||||||
|
provides: Block interactions with snapToBeat() wired into move/resize/drop operations
|
||||||
|
provides:
|
||||||
|
- BeatGrid class with load(), getCalibratedBeats(), getCalibratedOnsets(), snap(), hasBeats getter
|
||||||
|
- Beat mark rendering via calibrated beat positions (cyan lines full height)
|
||||||
|
- Onset tick rendering (short 8px ticks at top of track area, when onset data available)
|
||||||
|
- Loading indicator DETECTING BEATS... shown during async beat analysis
|
||||||
|
- Error state display when beat detection fails
|
||||||
|
- BPM label update in header after beat analysis completes
|
||||||
|
affects:
|
||||||
|
- 04-04 (playback engine reads beats from timeline.beatGrid for sync)
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "BeatGrid module pattern: raw times stored unmodified, calibration applied only at display/snap time — calibrationOffset is never baked into stored data"
|
||||||
|
- "Getter/setter proxy pattern for backward compatibility: TimelineCanvas.snapEnabled and .calibrationOffset proxy to beatGrid without breaking existing app.js wiring"
|
||||||
|
- "loading/error state management in data loader class (BeatGrid.loading, BeatGrid.error) — UI reads state each render frame, no imperative show/hide"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- lightsync/frontend/timeline/beats.js
|
||||||
|
modified:
|
||||||
|
- lightsync/frontend/timeline/timeline.js
|
||||||
|
- lightsync/frontend/app.js
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "BeatGrid stores raw beat_times unmodified — calibrationOffset is subtracted only in getCalibratedBeats()/getCalibratedOnsets() — ensures offset slider works without corrupting base data"
|
||||||
|
- "API field normalization in BeatGrid.load(): supports beats/beat_times and tempo/tempo_bpm variants — the existing API returns 'beats' and 'tempo' (not the plan's 'beat_times'/'tempo_bpm')"
|
||||||
|
- "onset_times gracefully absent: onsets.length=0 skips onset tick rendering silently — current API does not return onset data, future API extension will just work"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "BeatGrid.snap() threshold: 0.1s (100ms) — consistent with existing SNAP_THRESHOLD constant in timeline.js"
|
||||||
|
- "Canvas text alignment reset: ctx.textAlign restored to 'left' after centered text renders — prevents layout corruption for subsequent draw calls"
|
||||||
|
|
||||||
|
requirements-completed: [TL-07, TL-08, SYNC-01, SYNC-02]
|
||||||
|
|
||||||
|
duration: 2min
|
||||||
|
completed: 2026-04-06
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 04 Plan 03: BeatGrid Module + Beat Rendering Summary
|
||||||
|
|
||||||
|
**BeatGrid ES module encapsulating librosa beat data with calibration offset management, loading/error states, and snap-to-beat logic — extracted from TimelineCanvas for clean separation of concerns.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** ~2 min
|
||||||
|
- **Started:** 2026-04-06T23:47:10Z
|
||||||
|
- **Completed:** 2026-04-06T23:49:30Z
|
||||||
|
- **Tasks:** 1
|
||||||
|
- **Files modified:** 3 (1 created, 2 modified)
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- `BeatGrid` class in `beats.js`: async `load()` fetching from `/api/audio/beats`, `getCalibratedBeats()`, `getCalibratedOnsets()`, `snap()` with 100ms threshold, `loading`/`error` state, `hasBeats` getter
|
||||||
|
- Beat mark rendering refactored to use `beatGrid.getCalibratedBeats()` — calibration offset applied at render time, raw data never mutated
|
||||||
|
- Loading state indicator "DETECTING BEATS..." displayed in canvas during async analysis
|
||||||
|
- Error state displayed when beat detection fails
|
||||||
|
- BPM label updates in header toolbar after beat analysis resolves via `.then()`
|
||||||
|
- Getters/setters on TimelineCanvas proxy `snapEnabled` and `calibrationOffset` to beatGrid — existing app.js toolbar wiring works without changes
|
||||||
|
- `snapToBeat()` delegates to `beatGrid.snap()` — block placement, move, and resize all continue to snap correctly
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
1. **Task 1: BeatGrid module + enhanced timeline integration** - `287fe16` (feat)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `lightsync/frontend/timeline/beats.js` — BeatGrid class (load, getCalibratedBeats, getCalibratedOnsets, snap, hasBeats, loading/error state)
|
||||||
|
- `lightsync/frontend/timeline/timeline.js` — Imports BeatGrid, creates this.beatGrid in constructor, render() uses beatGrid for beat marks + loading indicator, snapToBeat() delegates to beatGrid.snap(), getters/setters for calibrationOffset/snapEnabled
|
||||||
|
- `lightsync/frontend/app.js` — loadBeats() chained with .then() to update BPM label from beatGrid.tempoBpm
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
|----------|-----------|
|
||||||
|
| API field normalization in load() | Existing API returns `beats`/`tempo` not `beat_times`/`tempo_bpm` as plan expected — BeatGrid.load() handles both variants with fallback |
|
||||||
|
| onset_times silently absent | Current API has no onset_times field — getCalibratedOnsets() returns [] and rendering skips onset ticks — future API extension will work automatically |
|
||||||
|
| textAlign reset after centered text | Drawing loading/error text centered then resetting to 'left' prevents layout corruption for subsequent draw calls in same render frame |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. [Rule 1 - Bug] API field name mismatch for tempo and beat times**
|
||||||
|
- **Found during:** Task 1 implementation (reading audio.py + beats.py)
|
||||||
|
- **Issue:** Plan's BeatGrid code expected `data.beat_times` and `data.tempo_bpm` but the actual `/api/audio/beats` endpoint returns `{"beats": [...], "tempo": float}` (confirmed in `lightsync/audio/beats.py`)
|
||||||
|
- **Fix:** BeatGrid.load() uses `data.beats || data.beat_times` for beat times and `data.tempo_bpm || data.tempo` for BPM — handles both field name variants
|
||||||
|
- **Files modified:** `lightsync/frontend/timeline/beats.js`
|
||||||
|
- **Committed in:** 287fe16
|
||||||
|
|
||||||
|
**2. [Rule 2 - Missing Critical] onset_times not in API response**
|
||||||
|
- **Found during:** Task 1 implementation
|
||||||
|
- **Issue:** Plan called for rendering onset marks from `onset_times` but the API has no such field — would silently break if array access attempted on undefined
|
||||||
|
- **Fix:** `this.onsetTimes = data.onset_times || []` — fallback to empty array; onset tick rendering guarded by `if (onsets.length > 0)` — no errors, graceful no-op
|
||||||
|
- **Files modified:** `lightsync/frontend/timeline/beats.js`, `lightsync/frontend/timeline/timeline.js`
|
||||||
|
- **Committed in:** 287fe16
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Total deviations:** 2 auto-fixed (1 Rule 1 - API field names, 1 Rule 2 - missing null guard)
|
||||||
|
**Impact on plan:** Both fixes necessary for correctness. No scope creep. Core functionality (beat marks, snap, loading state, BPM display) works as designed.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
None beyond the field name deviations documented above.
|
||||||
|
|
||||||
|
## Known Stubs
|
||||||
|
|
||||||
|
None — BeatGrid loads live data from `/api/audio/beats`. Onset ticks are gracefully absent (no onset data in current API) but will render automatically when API is extended.
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
- `lightsync/frontend/timeline/beats.js` exists: YES (287fe16)
|
||||||
|
- `export class BeatGrid` in beats.js: YES
|
||||||
|
- `getCalibratedBeats`, `getCalibratedOnsets`, `snap()`, `SNAP_THRESHOLD`: YES
|
||||||
|
- `import { BeatGrid }` in timeline.js: YES
|
||||||
|
- `this.beatGrid = new BeatGrid()` in constructor: YES
|
||||||
|
- `DETECTING BEATS...` in render(): YES
|
||||||
|
- `getCalibratedBeats` and `getCalibratedOnsets` used in render(): YES
|
||||||
|
- `get snapEnabled`, `set snapEnabled`, `get calibrationOffset`, `set calibrationOffset`: YES
|
||||||
|
- `loadBeats()` delegates to `beatGrid.load()`: YES
|
||||||
|
- Docker build: SUCCESS (no errors)
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- BeatGrid is available as `timeline.beatGrid` — playback engine (Plan 04-04) can read `timeline.beatGrid.getCalibratedBeats()` for live sync
|
||||||
|
- Beat snap works for all block operations (place, move, resize) via beatGrid.snap()
|
||||||
|
- Calibration offset and snap toggle continue working through existing app.js toolbar wiring
|
||||||
|
- No blockers
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 04-timeline-editor*
|
||||||
|
*Completed: 2026-04-06*
|
||||||
Reference in New Issue
Block a user