feat(editor): buffer-less compose-mode transport (Phase A3) - #136
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR adds buffer-less compose-mode playback in ChangesCompose-mode transport clock
Estimated code review effort: 4 (Complex) | ~40 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@screen.js`:
- Around line 6864-6867: _ensureAudioCtx() currently instantiates the audio
context unconditionally, so it throws a TypeError when neither AudioContext nor
webkitAudioContext exists and breaks the no-op fallback in startPlayback. Update
_ensureAudioCtx() to detect unsupported Web Audio before constructing the
context, leave S.audioCtx unset when unavailable, and return null so callers
like startPlayback and loadAudio can safely bail out via their existing guard.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d8a45bdc-0873-4670-8fae-53a690057259
📒 Files selected for processing (3)
CHANGELOG.mdscreen.jstests/compose_transport.test.js
|
@coderabbitai review |
✅ Action performedReview finished.
|
…ith no recording (Phase A3) startPlayback() used to early-return without S.audioBuffer, so a from-scratch song had no clock, no metronome, no guide. Add a buffer-less transport: the playhead advances off the AudioContext clock with no BufferSource, the grid (not a buffer) bounds the song via the A1 converter (S.duration = timeOf(lastBeat), extended for content past the last bar, or S.composeLength), and the already-ctx-time-based metronome/guide scheduler is the only sound. - _ensureAudioCtx(): compose mode never decodes audio, so create the shared context on the play gesture (the decode path is the only other creation site). - _anchorTransportAtCursor(): the buffer-less clock anchor, extracted from _startAudioSourceAtCursor; startPlayback / _restartPlaybackAt take the buffer-less branch when there is no S.audioBuffer. - _composeSongDuration() + pure _composeSongDurationPure(): grid-defined length. - _transportChartTimePure(): the one clock-advance formula, previously copy-pasted at four sites (playbackTick, guide tick, guide sync, chartTimeNow) — now shared. Audio-present playback is unchanged. Tests: tests/compose_transport.test.js (15, all would-fail-on-main). Full JS suite green (72/0). No routes.py / no wire change. Independent of A2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013JgxKh99UAeQqmhzSc73tv
…view) FIX 1: _composeSongDurationPure pads a 0.25s tail past the last authored onset when the duration is content-bound, so the final guide clap rings out instead of being cut by stopPlayback(). Explicit composeLength and grid-bound lengths stay exact. FIX 2: _abActive() now requires S.audioBuffer, so A/B compare is inert in compose mode (no reference recording) and every loop clap fires instead of half each pass being gated to silence. Regression tests extended (compose_transport, loop_ab) — both new paths fail on pre-fix code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Rabbit) Detect a missing AudioContext/webkitAudioContext before constructing, so _ensureAudioCtx returns null and leaves S.audioCtx unset instead of throwing a `new undefined()` TypeError. startPlayback/loadAudio already guard on !S.audioCtx, which was dead code while the constructor threw first. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0951584 to
ee29476
Compare
Phase A3 — buffer-less compose-mode transport
Third step of the workspace-shell time-model foundation (§1.7). Independent of A1/A2 — builds directly on main.
The problem
startPlayback()early-returned withoutS.audioBuffer:So a from-scratch song (compose mode — no recording) had no clock, no metronome, no guide claps. You could place notes but not hear time move.
What A3 adds
A buffer-less transport. When there's no recording:
_anchorTransportAtCursor()pinsplayStartWall/playStartTime;playbackTick()advancescursorTimefrom the ctx clock exactly as before — just with no audio node.S.duration= the time of the last grid beat via the A1 converter (timeOf(lastBeat)), extended if authored content runs past the last bar, or an explicitS.composeLength. No audio buffer needed to know how long the song is._guideTickwas already ctx-time-based and gates onS.audioCtx(neverS.audioBuffer), so it fires off the beat map with no changes._ensureAudioCtx()creates the shared context on the play gesture, since compose mode never hits the decode path that used to be the only creation site._restartPlaybackAt()(loop wrap / seek) takes the same buffer-less branch. Audio-present playback is unchanged — buffered and buffer-less share one clock; a recording rides the clock, it isn't the source of time.Incidental cleanup
The clock-advance formula
playStartTime + (ctxNow − playStartWall)was copy-pasted at four sites (playbackTick, guide tick, guide-timer sync,chartTimeNow). A3 extracts it to one pure_transportChartTimePureand rewires all four — no behaviour change, less drift for the Logic-style ruler/LCD (B2/B3) to trip over later.Tests
tests/compose_transport.test.js— 15 cases, all would-fail-on-main (every A3 symbol is absent on main):_transportChartTimePure— advance / stationary-at-anchor / monotonic_composeSongDurationPure— user length wins · max(grid, content) · junk → 0_composeSongDuration(live, realtimeOf, no buffer) — grid-derived length · extends for late content · honourscomposeLength· degenerate grid → 0_anchorTransportAtCursorpins the anchor and creates no BufferSourcecursorTimewithaudioBuffer = null(the core A3 claim)cursorTime;_guideTickgates onaudioCtx, neveraudioBufferVerification
node --check screen.jscleanroutes.pychange (no pytest); no wire/spec changeOrdering
Independent of A2 (#135) — both branch off main and can merge in any order. A4 (loop-as-beats) and A5 (beat-lock) require A2 merged; Phase B waits on the feature-PR backlog draining.
Design source:
SONG-EDITOR-WORKSPACE-SHELL-CHARRETTE.md§1.7.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests