Skip to content

feat(editor): audition trainer — loop-and-step-up + grid-locked click subdivision (P2-10) - #260

Merged
byrongamatos merged 3 commits into
mainfrom
feat/editor-audition-trainer
Jul 14, 2026
Merged

feat(editor): audition trainer — loop-and-step-up + grid-locked click subdivision (P2-10)#260
byrongamatos merged 3 commits into
mainfrom
feat/editor-audition-trainer

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What

The fast-follow on merged #247 (audition speed) that turns the rate transform into a real practice trainer:

  • Step↑ toggle (transport bar, beside A/B): with a bar range selected and Loop on, arming plays the passage slowed (drops to 50% if at full speed) and steps the rate up the ladder (50% → 75% → 100%) after every 3 completed loop passes, announcing progress in the status line ("Trainer: pass 2/3 at 75%"). Reaching 100% disarms with congratulations. Session-only state, exactly like A/B.
  • Count-in before every pass: when the user's count-in pref is armed, a trainer wrap routes through the full start path instead of the bare re-anchor, so the pre-roll clicks precede each pass — at the slowed tempo, since the count-in scheduler already rides the rate transform. (Recording never takes this path — the wrap branch is skipped while recording, unchanged.)
  • Metronome subdivides while slowed — 8ths below 90%, 16ths at ≤55% — and the subdivision times are a pure function of S.beats (_metroSubdivClicksPure): the click is locked to the grid at every speed, so the student hears micro-timing against the intended pulse, never a wobbling click. Rate only enters through the same chart→ctx mapping every scheduled voice uses.
  • The rate change lands exactly at the loop wrap (which re-anchors the clock), so there is no mid-pass clock jump.

Tests

tests/audition_trainer.test.mjs (8): ladder climb + clamp at 1.0 + off-ladder tolerance; pass counting/reset and the earned step; top-of-ladder proposes nothing; subdivision selector thresholds; subdivision clicks bisect each span exactly with beats excluded; grid-locked pin (an uneven rubato bar subdivides unevenly with its own spans — rate is not even an input); window clipping; arming refuses without an enabled loop and starts the ladder at its slowest step. All fail on main. Full suite green, lint 0 errors (3 pre-existing warnings), routes.py untouched.

Live-verified

On a real project (Insomnium — Where the Last Wave Broke), 2-bar loop with metronome on: armed at 50% → passes wrap at 8.2s intervals (4s loop ÷ 0.5 — the transform math visible in wall-clock), steps to 75% after 3 (passes tighten to ~5.4s), reaches 100%, auto-disarms, playback continues — zero page errors. Second run with a 1-bar count-in armed: each pass restarts through the pre-roll, still wrapping cleanly, zero errors.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q

Summary by CodeRabbit

  • New Features
    • Added an Audition Trainer toggle (“Step↑”) to the transport bar.
    • Loops a selected range and increases playback speed from 50% to 75% to 100% after every three passes.
    • Supports optional count-in clicks before each pass.
    • Adds finer, grid-locked metronome subdivisions during slowed playback.
    • Displays pass progress and automatically stops training at full speed.
    • Trainer is available only for enabled loop regions and is session-only.

@coderabbitai

coderabbitai Bot commented Jul 14, 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: 28 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: 0e391098-1989-459e-8868-086195a6260f

📥 Commits

Reviewing files that changed from the base of the PR and between 10fb20a and 2b1fcc0.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • src/audio.js
  • src/loop.js
  • src/main.js
  • src/transport-bar.js
  • tests/audition_trainer.test.mjs
📝 Walkthrough

Walkthrough

Adds a session-scoped Audition trainer activated from the transport bar. It loops an enabled range, increases playback from 50% to 75% to 100% after completed passes, optionally uses count-in restarts, and schedules finer grid-locked metronome subdivisions while slowed.

Changes

Audition trainer

Layer / File(s) Summary
Trainer state and scheduling helpers
src/audio.js, tests/audition_trainer.test.mjs
Defines rate progression, pass counting, trainer arming, metronome subdivision calculations, and tests for these behaviors.
Loop and metronome integration
src/audio.js
Applies trainer updates on loop wraps, routes configured passes through count-in playback, and schedules grid-locked subdivision clicks.
Transport control and editor API
src/main.js, src/transport-bar.js, CHANGELOG.md
Adds the Step↑ control, exposes its editor handler, and documents the feature.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TransportBar
  participant EditorAPI
  participant Playback
  participant GuideScheduler
  TransportBar->>EditorAPI: toggle audition trainer
  Playback->>Playback: count loop wrap and advance rate
  Playback->>Playback: restart with optional count-in
  GuideScheduler->>GuideScheduler: schedule grid-locked subdivisions
Loading

Possibly related PRs

Suggested reviewers: byrongamatos

🚥 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 The title clearly summarizes the main editor change: adding an audition trainer with loop stepping and grid-locked subdivision.
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-audition-trainer

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 `@src/audio.js`:
- Around line 777-790: Preserve the A/B phase in the count-in restart branch
after _trainerOnLoopWrap() flips it: because startPlayback() resets _abPhase to
'recording', restore the flipped phase after startPlayback() or use an existing
restart path that retains A/B state. Keep the current stop, cursor reset,
display update, and scheduled-tick behavior unchanged.

In `@src/transport-bar.js`:
- Line 467: Update the buildBar rerender flow around the editor-tp-trainer
handler so the recreated trainer button immediately reflects the shared armed
state, preserving aria-pressed and visual state without waiting for
_trainerRefreshBtn(). Reapply the trainer state after rebuilding, or have
buildBar render it from the existing trainer state.
🪄 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: 89286406-c7d6-4606-952b-6259d7e48f62

📥 Commits

Reviewing files that changed from the base of the PR and between a866705 and 10fb20a.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • src/audio.js
  • src/main.js
  • src/transport-bar.js
  • tests/audition_trainer.test.mjs

Comment thread src/audio.js
Comment thread src/transport-bar.js
@byrongamatos
byrongamatos force-pushed the feat/editor-audition-trainer branch from 10fb20a to f2c2278 Compare July 14, 2026 18:05
@byrongamatos

Copy link
Copy Markdown
Collaborator

Both CodeRabbit findings were live — fixed in f2c2278, alongside two more from my own read:

  • A/B phase across the count-in restart (CodeRabbit, src/audio.js): confirmed. startPlayback() reseeds _abPhase = 'recording', so with A/B + trainer + count-in all armed, A/B never reached a guide pass. The wrap now carries the flipped phase across the restart and re-applies the ref gain.
  • Trainer lamp lost on buildBar() (CodeRabbit, src/transport-bar.js): confirmed, and worse than reported — _trainerRefreshBtn() was toggling an editor-tp-on class that exists in no stylesheet; the bar styles "on" off .editor-transport-btn[aria-pressed="true"]. _transportBarTick now re-seats the lamp from _trainerActive() every tick, like every other toggle. That is what the export was for.

Two more from the review:

  • No exit disarmed the trainer. A/B disarms when the loop region is cleared, on song load, and on screen teardown; the trainer rode along on none of them — a new song came up with the trainer still armed at 100%, the top of the ladder, where every pass can only spam the status line. _trainerDisarm() now hangs off the same three exits, and _trainerActive() means armed AND looping (like _abActive()), so turning Loop off can't leave a lit button over a trainer that will never see a wrap.
  • The rate step re-seated playback past the loop end. editorSetAuditionRate() restarts live playback at S.cursorTime, which at the wrap still sits beyond the loop — the step burst a few ms of audio from past the loop until the wrap's own restart landed. The cursor is now seated at the wrap before the step.

Gates green: lint 0 errors (3 pre-existing warnings), node --test 148/148, pytest 257/257, trainer suite 9/9.

@byrongamatos
byrongamatos force-pushed the feat/editor-audition-trainer branch from f2c2278 to 2c22dd2 Compare July 14, 2026 18:07
@byrongamatos

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 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.

@byrongamatos

Copy link
Copy Markdown
Collaborator

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your plan includes PR reviews subject to rate limits. More reviews will be available in 7 minutes.

ChrisBeWithYou and others added 3 commits July 14, 2026 21:05
P2-10: Step-up toggle in the transport bar counts completed loop passes
and climbs the audition-rate ladder (50-75-100) every N passes; a wrap
routes through the full start path when a count-in is armed so the
pre-roll precedes each pass. Metronome subdivides (8ths/16ths) while
slowed, click times a pure function of S.beats — never rate-warped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Review of the audition trainer found four real defects, all in the
lifecycle around the feature rather than in the ladder itself:

- The Step↑ lamp toggled a `editor-tp-on` class that exists in no
  stylesheet. The transport bar styles "on" off
  `.editor-transport-btn[aria-pressed="true"]` — and it re-seats every
  other button's pressed state from the source of truth each tick, so a
  bar rebuild (audio arriving/leaving flips the Tempo cell's species and
  rebuilds) silently dropped the trainer's. Now `_transportBarTick` reads
  the lamp from `_trainerActive()`, which is what that export was for.
- `_trainerActive()` now means armed AND looping, like `_abActive()`.
  Turning Loop off under an armed trainer left a lit button over a
  trainer that could never see another wrap.
- No exit disarmed it. A/B disarms when the loop region is cleared
  (`_updateLoopRegionControls`), on song load (`_resetAuditionForNewSong`)
  and on screen teardown; the trainer rode along on none of them, so a
  new song came up with the trainer armed at 100% — the top of the
  ladder, where it can only spam the status line on every pass, forever.
  `_trainerDisarm()` now hangs off the same three exits.
- The rate step re-seated live playback at S.cursorTime, which at the
  wrap still sits PAST the loop end: the step restarted the recording
  beyond the loop for the few ms until the wrap's own restart. Seat the
  cursor at the wrap before stepping.

Also: with A/B + trainer + count-in all armed, the count-in wrap path's
startPlayback reset the A/B pass to 'recording' every wrap, so A/B never
reached a guide pass. Carry the phase across that restart.

And the subdivision window guard (`t1 <= from - (t1 - t0)`) said, at
length, "this span ends before the window" — say that instead.

Test: the trainer reads OFF when its loop does, and a disarm sticks.
@byrongamatos
byrongamatos force-pushed the feat/editor-audition-trainer branch from 2c22dd2 to 2b1fcc0 Compare July 14, 2026 19:05
@byrongamatos
byrongamatos merged commit a3a610e into main Jul 14, 2026
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