Skip to content

feat(editor): Shift Audio — slide the recording in time (chart fixed, non-destructive) - #236

Merged
byrongamatos merged 3 commits into
mainfrom
feat/editor-audio-shift
Jul 13, 2026
Merged

byrongamatos merged 3 commits into
mainfrom
feat/editor-audio-shift

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

The editor only ever let you offset the chart against a fixed recording — nothing moved the audio itself. This adds a non-destructive audio placement shift: slide the recording earlier/later while the chart/grid/notes stay put. It's the fix for a recording that starts late, has leading silence, or was swapped via Replace Audio and no longer lines up with the chart you already built — move the audio, not every note.

How it works

  • New S.audioShift (seconds): at playhead chart-time T the audio plays buffer-time T − audioShift. The samples are never stretched — only the buffer read position moves. _audioBufferStartPure handles the pre-audio delay (chart ahead of where the audio begins) and the past-the-end no-source case; the transport clock / cursor / guide-metronome are untouched, so only the audio slides.
  • The waveform and onset strip render shifted to match (what you see = what you hear), and onset snap / Suggest / Sync read a shifted-onset accessor (_ensureOnsetsShifted, which returns the raw cached array untouched when there's no shift — zero allocation on hot paths).
  • One value for the whole audio group, so stems (when they land in the editor) move together — as specified.
  • Undoable (AudioShiftCmd, song-scoped). A Shift Audio… button next to Replace Audio prompts for the shift; reset to 0 on load; the value round-trips on the save/load wire (data.audio_shift) and is honored on load.

Divergence note

This is a rigid shift, not a warp — fully consistent with the editor's "audio is immutable, the grid fits the audio" rule (the opposite of Ableton's destructive warp). The samples are never re-timed; playback just reads from a shifted offset.

Tests / gates

tests/audio_shift.test.mjs (9: the buffer-start boundary pure + the AudioShiftCmd/editorSetAudioShift round-trip); onset_snap.test.js updated for the shifted-onset accessor. 129 JS suites green, lint 0 errors (3 pre-existing warnings). routes.py untouched.

Verified live

On Test Artist — Test Track: Shift Audio… → +0.5s → the waveform slid right (a flat lead-in appeared) while the notes/grid stayed put; status "Audio shifted +500ms — recording moved, chart unchanged"; undo restored. No page errors.

Follow-up (scoped, not in this PR)

Persisting audio_shift into the built pack (a feedpak-spec/routes.py field + the final-app's consumption of it) is deferred — the value already round-trips on the wire and is honored on load; the pack write is a focused next step needing the format-field decision.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Shift Audio… to move recordings earlier or later while keeping the chart unchanged.
    • Waveforms and onset markers move with the audio.
    • Snapping, tempo suggestions, synchronization, playback, undo, and redo now account for the shift.
    • Audio shift settings are saved and restored with the session.
  • Bug Fixes

    • Improved playback timing when shifted audio starts before or after the chart cursor.

Christian's ask: be able to shift the audio track(s). The editor only ever let
you offset the CHART against a fixed recording; nothing moved the audio itself.
This adds a non-destructive audio placement shift — the inverse of the chart
offset — for when a recording starts late, has leading silence, or was swapped
via Replace Audio and no longer lines up with the chart you already built.

- New `S.audioShift` (seconds): at playhead chart-time T the audio plays
  buffer-time (T - audioShift). The samples are never stretched; only the buffer
  read position moves (`_audioBufferStartPure` handles the pre-audio delay and
  the past-the-end no-source case — the transport clock/cursor/guide are
  untouched, so only the audio slides). The waveform and onset strip render
  shifted to match; onset snap / Suggest / Sync read a shifted-onset accessor
  (`_ensureOnsetsShifted`, zero-alloc when unshifted) so they follow the audio.
- One value for the whole audio group, so stems (when they land in the editor)
  will move together — per Christian's spec.
- Undoable via `AudioShiftCmd` (song-scoped); a **Shift Audio…** button next to
  Replace Audio prompts for the shift; reset to 0 on song load, and the value is
  wired onto the save/load path (`data.audio_shift`) so a persisted shift is
  honored on load.

`tests/audio_shift.test.mjs` (9: the buffer-start boundary pure + the command
round-trip); `onset_snap.test.js` updated for the shifted-onset accessor. 129 JS
suites green, lint 0-err (3 pre-existing warnings). routes.py untouched.

Verified live on AC/DC — Back In Black: Shift Audio… → +0.5s → the waveform
slid right (a flat lead-in appeared) while the notes/grid stayed put, status
"Audio shifted +500ms — recording moved, chart unchanged"; undo restored.

Follow-up: persisting audio_shift INTO the built pack (a feedpak-spec/routes
field + the final-app consumption) is deferred — the value already round-trips
on the wire and is honored on load; the pack write is a focused next step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@byrongamatos, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 15 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ba6d2ed-82fe-48d6-a149-1e66037e25a2

📥 Commits

Reviewing files that changed from the base of the PR and between 50ec032 and c997332.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src/audio.js
  • src/loop.js
  • tests/audio_shift.test.mjs
📝 Walkthrough

Walkthrough

Adds an undoable audio-placement shift that moves playback, waveform, onset analysis, snapping, tempo tools, and persistence while keeping the chart fixed. A toolbar prompt and session save/load support are included.

Changes

Audio shift

Layer / File(s) Summary
Shift state and playback command
src/state.js, src/audio.js
Adds S.audioShift, shifted onset access, shifted WebAudio scheduling, undo/redo support, 1 ms quantization, and editor shift APIs.
Shifted visual and analysis paths
src/create.js, src/input.js, src/loop.js, src/sync-tempo.js, src/waveform.js
Applies shifted onset data to create nudging, tempo suggestion, snapping, and tempo sync, and moves waveform/onset rendering by the configured shift.
Session persistence and editor wiring
src/file-ops.js, src/create.js, src/main.js, screen.html
Loads and saves audio_shift, initializes it after imports, exposes prompt and adjustment handlers, and displays the shift control when audio exists.
Shift behavior validation and documentation
tests/audio_shift.test.mjs, tests/onset_snap.test.js, CHANGELOG.md
Tests playback timing, command history, rounding, no-op behavior, and shifted onset injection; documents the feature.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  actor Editor
  participant Prompt
  participant AudioShiftCmd
  participant Playback
  participant Renderer
  Editor->>Prompt: enter audio shift
  Prompt->>AudioShiftCmd: apply shift
  AudioShiftCmd->>Playback: restart at chart cursor
  AudioShiftCmd->>Renderer: redraw shifted audio
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a non-destructive Shift Audio feature that moves the recording while keeping the chart fixed.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-audio-shift

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/waveform.js (1)

98-113: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication: shift/visible-range computation repeated.

The sh/xLo/xHi computation here mirrors the one in drawWaveform (Lines 36-39). Could be factored into a small shared helper (e.g. _visibleAudioXRange(w, dur, sh)), though the duplication is small and low-risk as-is.

🤖 Prompt for 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.

In `@src/waveform.js` around lines 98 - 113, Factor the duplicated shifted
visible-range calculation from drawWaveform and this onset-rendering block into
a shared helper such as _visibleAudioXRange(w, dur, sh). Update both call sites
to reuse the helper’s xLo and xHi values while preserving the existing LABEL_W,
timeToX, and rounding behavior.
🤖 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.

Nitpick comments:
In `@src/waveform.js`:
- Around line 98-113: Factor the duplicated shifted visible-range calculation
from drawWaveform and this onset-rendering block into a shared helper such as
_visibleAudioXRange(w, dur, sh). Update both call sites to reuse the helper’s
xLo and xHi values while preserving the existing LABEL_W, timeToX, and rounding
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 925ed6a1-f728-46c6-8390-a8e380a19512

📥 Commits

Reviewing files that changed from the base of the PR and between 33521a4 and 50ec032.

📒 Files selected for processing (13)
  • CHANGELOG.md
  • screen.html
  • src/audio.js
  • src/create.js
  • src/file-ops.js
  • src/input.js
  • src/loop.js
  • src/main.js
  • src/state.js
  • src/sync-tempo.js
  • src/waveform.js
  • tests/audio_shift.test.mjs
  • tests/onset_snap.test.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants