Skip to content

feat(editor): loop A/B compare — alternate recording and guide per loop pass - #122

Merged
byrongamatos merged 4 commits into
mainfrom
feat/editor-loop-ab
Jul 7, 2026
Merged

feat(editor): loop A/B compare — alternate recording and guide per loop pass#122
byrongamatos merged 4 commits into
mainfrom
feat/editor-loop-ab

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Roadmap 1.6 — the last "Hear your chart" slice, unblocked by #109 merging (it mutes the reference through the mixer''s transparent _refGain). With this, Phase 1 is complete except the wafonts-gated pitched synth (1.2/1.5).

What

The ear-training loop: while looping, each pass alternates between the recording (reference audible, claps off) and the guide (reference muted, claps on — overriding the claps pref in both directions so a guide pass is never silent and a recording pass is never cluttered). A charter hears what they charted against what the artist played, one pass apart.

  • Phase flips ride the loop wrap, applied before the restart so the mixer''s ~20 ms ramp lands with the wrap — never a pop.
  • Every (re)start — including seeks, which route through startPlayback — begins on the recording pass, so the real thing always comes first from a fresh position.
  • Hearing-safety edges: stopping mid-guide-pass restores the reference to its fader level (a stop can never leave the recording silently muted); clearing the loop region disarms A/B; session-only state, reset on song load — a persisted mute would read as a playback bug in a later session.
  • Arming A/B with a region set but looping off arms the loop too (A/B is meaningless without it).
  • _guideTimerSync schedules whenever A/B is active, so guide passes clap even with the claps pref off.
  • Toolbar A/B button next to Loop (disabled without a region) + registry command toggleLoopAB (Alt+B, both profiles, collision-checked).

Tests

tests/loop_ab.test.js (5): the pref-override truth table, the strict two-cycle phase flip, mute-only-during-an-active-playing-guide-pass (including the stop-restores case), and a composed 4-pass alternation asserting each pass is exactly one surface — never both, never neither. Full suite green except the pre-existing CRLF section_coverage failure (#116 fixes it).

Fresh region vs the open queue — guide scheduler + loop controls + one toolbar button.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JEoFeTPSnz4NpwwCG52hnu

Summary by CodeRabbit

  • New Features
    • Added an A/B loop compare button to alternate each loop pass between a reference-recording pass and a guide pass.
    • Added Alt+B shortcut to toggle A/B loop compare.
  • Bug Fixes
    • Improved A/B loop state handling when starting, stopping, clearing/disarming, or switching songs, ensuring reference muting/unmuting stays in sync.
  • Documentation
    • Updated the changelog with the A/B loop compare behavior.
  • Tests
    • Added automated tests covering the multi-pass A/B alternation and related runtime behavior.

…op pass

Roadmap 1.6, the last Phase-1 slice that was waiting on #109 (it mutes
the reference through the mixer transparent _refGain). The ear-training
loop: while looping, each pass alternates RECORDING (reference audible,
claps off) and GUIDE (reference muted, claps on — overriding the claps
pref in both directions), so a charter hears their chart against the
artist one pass apart.

- Phase flips ride the loop wrap (before _restartPlaybackAt so the ramp
  lands with the wrap); every (re)start/seek begins on the recording
  pass; stop mid-guide-pass restores the fader level; clearing the loop
  region disarms A/B; song load resets it (session-only by design).
- Arming A/B with a region set but looping off arms the loop too.
- _guideTimerSync now schedules whenever A/B is active (guide passes
  need claps even with the pref off).
- Toolbar A/B button next to Loop + registry toggleLoopAB (Alt+B).

Tests: tests/loop_ab.test.js (5) — pref-override truth table, strict
two-cycle, mute-only-during-active-playing-guide (incl. the stop-restores
case), and a composed 4-pass alternation. Full suite green except the
pre-existing CRLF section_coverage failure (#116).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JEoFeTPSnz4NpwwCG52hnu
@coderabbitai

coderabbitai Bot commented Jul 7, 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: 45 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: 70683946-87b5-4314-af63-f6bbb6144995

📥 Commits

Reviewing files that changed from the base of the PR and between 7fa616b and f112506.

📒 Files selected for processing (1)
  • screen.js
📝 Walkthrough

Walkthrough

Adds a Loop A/B compare transport mode that alternates each loop pass between recording and guide behavior, with new UI/shortcut wiring, playback integration, validation, and changelog notes.

Changes

Loop A/B compare feature

Layer / File(s) Summary
Core A/B state and helpers
screen.js
Adds A/B session state, pure helpers for claps enablement, phase cycling, and reference-gain targeting, plus reference-gain automation and button refresh logic.
Button and shortcut wiring
screen.html, screen.js
Adds the A/B loop button, Preview toggle entry, keyboard shortcut mappings, and dispatcher routing to the toggle handler.
Playback and loop integration
screen.js
Resets A/B state on playback start and stop, flips phase on loop restart, routes guide claps through A/B-aware logic, clears A/B when the loop region is disarmed, and resets session state on song load.
Tests and changelog
tests/loop_ab.test.js, CHANGELOG.md
Adds a Node test script that extracts A/B helpers and runtime glue, builds a sandbox, and asserts phase, claps, reference-gain, arming, and loop-disable behavior, plus a changelog entry.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI as A/B button or shortcut
  participant screenjs as screen.js
  participant Scheduler as guide scheduler
  participant RefGain as reference gain

  User->>UI: toggle A/B
  UI->>screenjs: _editorToggleLoopAB()
  screenjs->>screenjs: set _abOn and _abPhase
  screenjs->>RefGain: apply reference-gain automation
  loop each loop pass
    Scheduler->>screenjs: loop wrap
    screenjs->>screenjs: _abNextPhasePure()
    screenjs->>RefGain: update target
    screenjs->>Scheduler: _abClapsEnabledPure()
  end
  User->>UI: clear or stop loop
  UI->>screenjs: disarm / stop
  screenjs->>screenjs: clear _abOn and reset phase
  screenjs->>RefGain: reapply reference-gain automation
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Clear and specific: it describes the editor loop A/B compare feature and its alternating recording/guide behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-loop-ab

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.

Actionable comments posted: 2

🤖 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 6014-6018: The A/B state is being reset too late in the
restart/seek flow, allowing playbackTick() and _guideTimerSync() to run while
_abPhase still reflects the previous guide state. Update the restart/seek logic
around _guideTimerSync() and _abTick()/playback startup so _abPhase is set to
'recording' before any timer sync or _guideTick() can fire, ensuring the first
playback tick always starts from the recording pass.
- Around line 6773-6776: The song-load reset in the A/B state handling only
clears the session flags, but it leaves the reference gain automation and A/B
button UI stale after a guide pass. Update the same reset path around _abOn and
_abPhase to also refresh the audio/UI state by reapplying the reference gain
automation and synchronizing the A/B button styling/state whenever a new song is
loaded.
🪄 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: ee2936c8-8603-4949-a788-c5723e53a9b3

📥 Commits

Reviewing files that changed from the base of the PR and between ba37430 and b9a2794.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • screen.html
  • screen.js
  • tests/loop_ab.test.js

Comment thread screen.js Outdated
Comment thread screen.js
- ref fader no longer un-mutes an active A/B guide pass (route ref bus
  moves through the A/B-aware target; guarded for the @pure sandbox)
- mid-play A/B arming delegates to _setLoopRegionEnabled so the cursor
  seeks into the loop region instead of riding pre-loop audio
- disabling the loop mid-guide-pass restores the recording to its fader
  level instead of leaving it silently muted
- regression tests drive the real stateful runtime; all 3 fail pre-fix

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

- reset A/B phase + ref gain BEFORE the first playbackTick/_guideTimerSync
  so a fresh start/seek can't schedule a guide pass off a stale phase (and
  the first-play fade stays the last ref-gain automation)
- on song load, also reapply ref gain + sync scheduler + refresh controls
  so a load mid-guide-pass never leaves a stale mute or button styling

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos

Copy link
Copy Markdown
Collaborator

Addressed in f112506: reset _abPhase='recording' + _abApplyRefGain() now run before playbackTick()/_guideTimerSync(), so a fresh start/seek can never schedule a guide pass off a stale phase; this also keeps the first-play fade as the last ref-gain automation.

@byrongamatos

Copy link
Copy Markdown
Collaborator

Addressed in f112506: the song-load reset now also calls _abApplyRefGain(), _guideTimerSync(), and _updateLoopRegionControls(), so loading mid-guide-pass never leaves a stale ref mute or A/B button styling.

@byrongamatos

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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)
tests/loop_ab.test.js (1)

154-168: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for _guideTimerSync gating.

The PR explicitly changes _guideTimerSync to run whenever A/B is active (so guide passes clap even with claps disabled in prefs), but this sandbox only ever wires it as a no-op (() => {}, line 168) with no spy. None of the added tests assert that it's actually invoked when A/B is active, so a regression here (e.g. it stops being called, or is called at the wrong time) would go undetected by this suite.

♻️ Suggested spy wiring
-    const spies = { setLoopRegionEnabled: [] };
+    const spies = { setLoopRegionEnabled: [], guideTimerSync: 0 };
@@
-        () => {},   // _guideTimerSync
+        () => { spies.guideTimerSync++; },   // _guideTimerSync

Then assert spies.guideTimerSync increments at the expected call sites (e.g. after _editorToggleLoopAB() turns A/B on).

🤖 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 `@tests/loop_ab.test.js` around lines 154 - 168, Add test coverage for the
`_guideTimerSync` gating behavior in the A/B sandbox. Wire the `_guideTimerSync`
dependency in the `new Function` harness to a spy instead of a no-op, then
assert it is called when `_editorToggleLoopAB` enables A/B and at any other
expected A/B-active call sites. Use the existing `spies` object and the
`_editorToggleLoopAB`, `_guideTimerSync`, and `setPhase` helpers to verify the
invocation timing.
🤖 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 `@tests/loop_ab.test.js`:
- Around line 154-168: Add test coverage for the `_guideTimerSync` gating
behavior in the A/B sandbox. Wire the `_guideTimerSync` dependency in the `new
Function` harness to a spy instead of a no-op, then assert it is called when
`_editorToggleLoopAB` enables A/B and at any other expected A/B-active call
sites. Use the existing `spies` object and the `_editorToggleLoopAB`,
`_guideTimerSync`, and `setPhase` helpers to verify the invocation timing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 78685b62-9d98-4bdc-aca8-6551d33ed222

📥 Commits

Reviewing files that changed from the base of the PR and between b9a2794 and 7fa616b.

📒 Files selected for processing (2)
  • screen.js
  • tests/loop_ab.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • screen.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