Skip to content

fix(editor): auto anchors look ahead and stop thrashing on lone notes#353

Merged
ChrisBeWithYou merged 2 commits into
mainfrom
feat/anchor-lookahead
Jul 22, 2026
Merged

fix(editor): auto anchors look ahead and stop thrashing on lone notes#353
ChrisBeWithYou merged 2 commits into
mainfrom
feat/anchor-lookahead

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

Rewrites the auto fret-hand anchor generator _compute_anchors (routes.py) to add look-ahead run placement and lone-outlier tolerance. This is the "Anchor look-ahead" card (the flagged START-HERE next-win): auto anchors silently drive position-suggest, chord-grip, fingering, and the stretch/legato lints, so weak anchors degraded all of them at once.

Why the old engine was weak

_compute_anchors was a single forward-greedy pass — fixed width=4, no look-ahead, relocating one fret below any note that fell outside the current window. Two concrete failures:

  • Over-splitting: frets 3, 1, 5 (all within a 4-fret span) became two anchors because it committed to a position on the 3 before it ever saw the 1.
  • Thrash on a lone note: a single high grace note in an otherwise low passage (3 3 3 12 3 3 3) forced an up-then-back pair of phantom shifts (3 → 11 → 3) for one reached note.

It also walked chord notes individually, so a wide chord could "relocate" the hand in the middle of itself.

What changed

  • Look-ahead: each run is grown to the longest span of consecutive note-groups that still fits one width-wide window before its fret is chosen — so 3, 1, 5 is one anchor at fret 1.
  • Outlier tolerance (anti-thrash): a lone out-of-window group whose next note returns to the window is charted as a reach, not a position change. A real ≥2-note excursion still moves the hand; a lone final note (no evidence of return) is a genuine new position, not a phantom reach.
  • Chord/coincident grouping: notes sharing a time collapse into one (lo, hi) group, so the hand can never relocate mid-chord.

The {time, fret, width} output shape and the inclusive [fret, fret+width] width semantics (matching the stretch lint and the highway) are unchanged. First anchor still covers t=0; adjacent same-fret shifts are still dropped.

Scope note

src/position.js::_suggestPositionPure uses a half-open [fret, fret+width) window — one fret narrower than the generator + stretch lint. That off-by-one is pre-existing and left untouched here to keep this PR focused on the generator; worth a follow-up to unify the width predicate across consumers.

Tests

  • New tests/test_anchor_compute.py (~20 cases): the two headline wins (both fail on the old engine), placement, chord grouping, the excursion-vs-reach boundary, monotonic scale shifts, structural invariants, determinism, and coverage.
  • Full pytest green (401 passed, 5 skipped). The existing test_xml_export.py anchor tests (which compare dynamically against _compute_anchors) still pass.
  • npm run lint clean (0 errors). JS suite unchanged (Python-only diff); the only red suites are the two known-on-main failures (mixer_meter_teardown, song_fit).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Enhancements

    • Improved automatic fret-hand positioning with look-ahead placement for smoother transitions.
    • Chords now remain covered by a single hand position, preventing mid-chord relocations.
    • Isolated out-of-range notes may be handled as reaches when the hand can promptly return.
    • Anchor placement now consistently covers the start of a song and avoids redundant shifts.
  • Tests

    • Added comprehensive coverage for boundary cases, chords, ascending passages, outliers, and anchor consistency.

The auto fret-hand anchor generator (`_compute_anchors`) was a single
forward-greedy pass: fixed width 4, no look-ahead, relocating one fret
below any note outside the current window. It over-split positions it
didn't need to (frets 3, 1, 5 -> two anchors instead of one at fret 1)
and thrashed on a single high grace note, emitting an up-then-back pair
of shifts for one reached note. Auto anchors feed position-suggest,
chord-grip, fingering and the stretch/legato lints, so weak anchors
quietly degraded all of them.

It now grows each run to the longest span of consecutive note-groups
that still fits one window before choosing the fret, and tolerates a
lone out-of-window group whose next note returns to the window as a
reach rather than a position change (a real >=2-note excursion still
moves). Chord and coincident notes are grouped by time, so the hand can
no longer relocate mid-chord. The {time, fret, width} shape and
inclusive-width semantics are unchanged.

Pure and deterministic; covered by tests/test_anchor_compute.py. Full
pytest green (401 passed); lint clean; the only JS failures are the two
known-on-main ones (mixer_meter_teardown, song_fit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xGPjDBF8NTwTK7VQvizix
Signed-off-by: ChrisBeWithYou <chris@rifflarr.local>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 27 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: a0ee6a98-e857-4a34-a430-5f61957e71e1

📥 Commits

Reviewing files that changed from the base of the PR and between 16bd875 and 089eb9e.

📒 Files selected for processing (2)
  • routes.py
  • tests/test_anchor_compute.py
📝 Walkthrough

Walkthrough

The fret-hand anchor engine now groups simultaneous fretted events, computes anchors with look-ahead and reach handling, preserves time-zero coverage, and avoids redundant shifts. New tests cover placement, chords, outliers, monotonicity, robustness, and window invariants.

Changes

Fret-hand anchor engine

Layer / File(s) Summary
Time-based fretted event grouping
routes.py, tests/test_anchor_compute.py
Fretted notes and chord notes are grouped by rounded time into fret spans, excluding open strings and keeping coincident chord events in one anchor window.
Look-ahead anchor computation
routes.py, tests/test_anchor_compute.py
_compute_anchors uses fixed-width run growth, reach absorption for eligible outliers, time-zero initialization, and redundant-shift suppression.
Anchor behavior validation and documentation
tests/test_anchor_compute.py, CHANGELOG.md
Tests cover boundary placement, look-ahead, outlier handling, chord behavior, monotonicity, robustness, determinism, and inclusive-window coverage; the changelog documents the update.

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

Sequence Diagram(s)

sequenceDiagram
  participant Notes
  participant FrettedGroups
  participant ComputeAnchors
  participant XMLConversion
  Notes->>FrettedGroups: provide notes and chord notes
  FrettedGroups->>ComputeAnchors: provide grouped fret spans
  ComputeAnchors->>ComputeAnchors: grow runs and handle reaches
  ComputeAnchors-->>XMLConversion: return deduplicated anchors
Loading

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 captures the main change: auto anchor generation now uses look-ahead and avoids thrashing on lone notes.
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/anchor-lookahead

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 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 `@routes.py`:
- Around line 2835-2848: Update the return check in the anchor computation loop
around reachable and returns to compare nxt against the effective window
produced by _place(win_lo, win_hi), not the raw win_lo/win_hi bounds. Preserve
the existing reachability and run-absorption behavior, including accepting close
return notes that fall within the eventual placed anchor window.
🪄 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: 929079cd-8491-4e39-8f91-887a0f54db5e

📥 Commits

Reviewing files that changed from the base of the PR and between ba4f924 and 16bd875.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • routes.py
  • tests/test_anchor_compute.py

Comment thread routes.py
@ChrisBeWithYou
ChrisBeWithYou merged commit e78eac2 into main Jul 22, 2026
4 checks passed
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.

1 participant