Skip to content

feat(editor): in-key highlight on the fretted lanes — capo-aware sounding pitch - #115

Merged
byrongamatos merged 3 commits into
mainfrom
feat/editor-key-highlight-guitar
Jul 7, 2026
Merged

feat(editor): in-key highlight on the fretted lanes — capo-aware sounding pitch#115
byrongamatos merged 3 commits into
mainfrom
feat/editor-key-highlight-guitar

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Roadmap 4.16a remainder (the guitar-lane scale-degree tint) — the top finer-detail slice from the queue plan, extending the merged #108 key/scale highlight from the piano roll to the fretted lanes.

What

  • _soundingPitchPure: sounding pitch = open string + tuning offset + capo + fret, capo added exactly once. The fret convention (chart frets are capo-relative, fret 0 = the capo) is verified against core''s single source of the formula — lib/song.py pitch_from_base, which the tuner, open-string labels, and the highway''s scale-degree derivation all share.
  • The double-count trap is pinned, not just avoided: _absolutePitch (string-moves) still deliberately omits capo — it compares two pitches on one arrangement, where the capo cancels — and both helpers now document the division of labor. A test asserts the difference between the two is exactly one capo.
  • Render treatment mirrors the piano roll: out-of-key notes dim (body alpha cc55, softened fret number) — never red, chromaticism isn''t an error (D20). Unresolvable pitches (unknown string, junk fret) stay fully lit rather than falsely flagged. The highlight context (open-string MIDI, normalized tuning, capo) is hoisted once per draw, so _drawNote does zero per-note arrangement work.
  • The Key controls now show for any pitched arrangement (they were keys-mode-only); drum grid and Parts overview still hide them. Registry label and titles generalized.

Tests

tests/fret_key_highlight.test.js (8 cases): the formula against known pitches, Drop-D + capo composition, junk-input null guards, the omits-capo pin, and the decisive capo-flips-membership case — the same chart fret is out of key uncapoed and in key with capo 1, which an uncapoed resolver gets wrong. All fail on main.

Full JS suite green except tests/section_coverage.test.js, which fails on current main itself (pre-existing — looks like an _afterEdit/#107 squash-merge interaction; flagging separately).

🤖 Generated with Claude Code

https://claude.ai/code/session_01JEoFeTPSnz4NpwwCG52hnu

Summary by CodeRabbit

  • New Features
    • Expanded in-key highlighting to apply consistently across pitched arrangement views (not just piano roll).
    • Key-highlight controls are now available for pitched arrangements while remaining hidden for drum grid and parts overview modes.
  • Bug Fixes
    • Notes outside the current key now dim (instead of turning red); unresolvable pitches remain fully lit.
    • Improved capo handling to prevent incorrect in-key/out-of-key highlighting.
  • Documentation
    • Updated changelog and the “In-key” tooltip to reflect the new dimming behavior.
  • Tests
    • Added automated coverage for capo and out-of-key highlighting behavior.

…ding pitch

Roadmap 4.16a remainder (guitar-lane scale-degree tint). Extends the merged
song key/scale highlight (#108) from the piano roll to guitar/bass lanes.

- New _soundingPitchPure: openMidi + tuning offset + CAPO + fret, capo
  added exactly ONCE. Chart frets are capo-relative — verified against
  core lib/song.py pitch_from_base, the single source of the formula the
  tuner and highway scale-degree derivation share.
- The flagged double-count trap is now pinned in code and tests:
  _absolutePitch (string-moves) still deliberately omits capo (it cancels
  when comparing two pitches on one arrangement) and both helpers document
  the division of labor.
- Out-of-key fretted notes dim (body alpha cc->55, softened fret number —
  the piano-roll treatment; never red), unresolvable pitches stay fully
  lit. Highlight context is hoisted once per draw, zero per-note
  arrangement work. Key controls now show for any pitched arrangement.

Tests: tests/fret_key_highlight.test.js (8 cases) — the formula against
known pitches, Drop-D + capo composition, the capo-flips-membership case
an uncapoed resolver gets wrong, and the omits-capo pin on _absolutePitch.
Full JS suite green except tests/section_coverage.test.js, which fails on
current MAIN itself (pre-existing _afterEdit/#107 merge interaction).

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: 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: 71f5b058-198b-4eb8-aa71-6dcc80ae3902

📥 Commits

Reviewing files that changed from the base of the PR and between 856442e and b6e0aa7.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • screen.html
  • screen.js
📝 Walkthrough

Walkthrough

Adds capo-aware sounding pitch computation for fretted lanes, broadens key-highlight visibility beyond piano roll, changes out-of-key notes to dim instead of redden, adds a Node test suite, and updates changelog and tooltip text.

Changes

In-key highlight for fretted lanes

Layer / File(s) Summary
Capo-aware pitch helpers
screen.js
_absolutePitch now omits capo and _soundingPitchPure computes sounding MIDI from open MIDI + tuning + capo + fret, applying capo once and returning null for invalid inputs.
Highlight computation and note rendering
screen.js
Key-highlight controls now show for any pitched arrangement except drum edit and Parts overview; per-draw highlight state is hoisted, _drawNote uses sounding pitch for out-of-key detection, and out-of-key notes dim.
Capo/key membership test suite
tests/fret_key_highlight.test.js
New Node tests extract pure pitch/scale logic from screen.js and verify capo handling, invalid-input handling, absolute-vs-sounding pitch separation, and capo-driven membership flips.
Documentation and tooltip updates
CHANGELOG.md, screen.html
Changelog and In-key tooltip text are updated to describe the new fretted-lane highlight behavior and dimming wording.

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

Sequence Diagram(s)

sequenceDiagram
  participant DrawLoop as Draw
  participant Highlight as ghl
  participant DrawNote as _drawNote
  participant Pitch as _soundingPitchPure
  DrawLoop->>Highlight: compute active key highlight once per draw
  DrawLoop->>DrawNote: draw note with ghl context
  DrawNote->>Pitch: compute sounding pitch tuning+capo+fret
  Pitch-->>DrawNote: sounding MIDI or null
  DrawNote-->>DrawLoop: render dimmed or lit note
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 The title clearly summarizes the main change: in-key highlighting for fretted lanes with capo-aware sounding pitch.
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-key-highlight-guitar

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

35-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Brace-counting extraction is fragile but low-risk for now.

extractFn counts braces to find the end of _absolutePitch; this would break silently (or extract too little/too much) if the function body ever contained a {/} inside a string, regex, or comment. Given the function is small and stable, this is a minor concern — worth a comment noting the assumption, or eventually exporting the pure functions from screen.js directly for test isolation instead of parsing source text.

🤖 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/fret_key_highlight.test.js` around lines 35 - 45, The source-text brace
counting in extractFn is brittle because it assumes _absolutePitch and other
functions in screen.js never contain braces in strings, regexes, or comments.
Update the test helper to either add a clear assumption comment near extractFn
or, preferably, stop parsing source text by exporting the pure functions from
screen.js and importing them directly in tests. Keep the change localized to
extractFn and the related screen.js exports.
🤖 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/fret_key_highlight.test.js`:
- Around line 35-45: The source-text brace counting in extractFn is brittle
because it assumes _absolutePitch and other functions in screen.js never contain
braces in strings, regexes, or comments. Update the test helper to either add a
clear assumption comment near extractFn or, preferably, stop parsing source text
by exporting the pure functions from screen.js and importing them directly in
tests. Keep the change localized to extractFn and the related screen.js exports.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fbc6f718-c425-4bb7-92ad-ed0325a1da13

📥 Commits

Reviewing files that changed from the base of the PR and between 2222c59 and 549521c.

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

…eRabbit nitpick)

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.

@byrongamatos
byrongamatos merged commit e7e4dff into main Jul 7, 2026
byrongamatos added a commit that referenced this pull request Jul 7, 2026
…l (read-first) (#119)

* feat(editor): in-key highlight on the fretted lanes — capo-aware sounding pitch

Roadmap 4.16a remainder (guitar-lane scale-degree tint). Extends the merged
song key/scale highlight (#108) from the piano roll to guitar/bass lanes.

- New _soundingPitchPure: openMidi + tuning offset + CAPO + fret, capo
  added exactly ONCE. Chart frets are capo-relative — verified against
  core lib/song.py pitch_from_base, the single source of the formula the
  tuner and highway scale-degree derivation share.
- The flagged double-count trap is now pinned in code and tests:
  _absolutePitch (string-moves) still deliberately omits capo (it cancels
  when comparing two pitches on one arrangement) and both helpers document
  the division of labor.
- Out-of-key fretted notes dim (body alpha cc->55, softened fret number —
  the piano-roll treatment; never red), unresolvable pitches stay fully
  lit. Highlight context is hoisted once per draw, zero per-note
  arrangement work. Key controls now show for any pitched arrangement.

Tests: tests/fret_key_highlight.test.js (8 cases) — the formula against
known pitches, Drop-D + capo composition, the capo-flips-membership case
an uncapoed resolver gets wrong, and the omits-capo pin on _absolutePitch.
Full JS suite green except tests/section_coverage.test.js, which fails on
current MAIN itself (pre-existing _afterEdit/#107 merge interaction).

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

* feat(editor): per-part view switcher — any fretted part opens in the piano roll (read-first)

EDITOR-VIEW-MODALITY-DESIGN P1 (VA.1+VA.2, decisions V1-V4/V9). The editing
view was derived from the arrangement NAME; it is now a per-part choice.

- viewFor(part): per-part pref in editor localStorage keyed song + stable
  part id (never index/display-name; keys parts piano-locked). Kind
  inference stays the default.
- isKeysMode() split: piano-SURFACE predicate (draw geometry, hit-testing,
  viewport) vs new isKeysArr() keys-DATA predicate (string moves,
  chord-sibling grouping, anchors, resize chord-expansion) — a fretted
  part in the roll still groups chords and keeps string-move machinery
  (P5 position cycling depends on exactly this).
- Read-first roll for fretted parts: one sounding-pitch mapping
  (_rollMidiForNote via _soundingPitchPure — capo once) hoisted per pass
  and shared by draw, hitNote, marquee, and updatePianoRange; null
  pitches skip, never render wrong.
- Edit-lock (V4): central gate in EditHistory.exec (typeof-guarded for
  extracted-test envs) + the live-mutating drag starts (move/resize) +
  dblclick add + EOF right-click edit; selection still works; a visible
  pill + status explain why. Lock lifts live on switching back.
- Toolbar String/Piano-roll segmented switcher + registry cycleViewMode;
  selection/drag/note-UI cleared on switch (V3).

STACKED ON #115 (feat/editor-key-highlight-guitar) — needs its
_soundingPitchPure; merge #115 first.

Tests: tests/view_switcher.test.js (11) — pure view resolution, pref
persistence/rename stability over stub localStorage, sounding-pitch roll
mapping + viewport fit (asserts NOT the wire packing), and the exec gate
(inert+notice / regression / live-unlock). 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

* feat(editor): review fixes for #119 (view switcher)

Read-only roll (fretted part in the piano roll) was enforced only at the
EditHistory exec chokepoint and the mouse/right-click-add handlers. Two
gaps:

- exec blocked ALL commands, including songScope (drum tab, tempo grid)
  edits, so switching an unrelated part into the roll froze tempo/drum
  editing. songScope commands now pass through the lock (exec + undo/redo).
- Several note-edit paths bypass EditHistory entirely and so escaped the
  lock: note-scope undo/redo, promptSlide/promptSlideUnpitch, the inspector
  setters (editorInspectorSetTech/SetFlag), and the context-menu
  editorToggleTech. The context menu opens in the roll under the default
  right-click behavior and the inspector renders for any selection, so all
  were reachable. Each is now guarded with _rollReadOnly()/_rollLockNotice.

Regression tests (tests/view_switcher.test.js) fail on pre-fix code.

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

---------

Co-authored-by: ChrisBeWithYou <chris@rifflarr.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
@byrongamatos
byrongamatos deleted the feat/editor-key-highlight-guitar branch July 8, 2026 06:07
byrongamatos added a commit that referenced this pull request Jul 8, 2026
…ows) (#126)

Roadmap VA.5 (design V13.1). A fretted part shown in the piano roll is
read-only — its Y axis is pitch, so the string axis Shift+↑/↓ walks in
String view has no meaning there. Rather than leave those keys dead, they
now cycle the selected note(s) through every {string, fret} that sounds
the SAME pitch on this arrangement's tuning.

- `_cyclePositionCandidatesPure` enumerates integer frets 0–24 on every
  string whose capo-less absolute pitch matches (the capo cancels on both
  sides — the same _absolutePitch/_soundingPitchPure pairing #115
  documents), ordered low string → high; `_cycleStepPure` walks + wraps.
- `_execCyclePosition` drives it through the existing MoveToStringCmd, so
  a cycle is one undo step that round-trips like every string move. The
  command carries `pitchPreserving` and is the ONE deliberate carve-out
  from the read-only-roll lock in EditHistory (exec/undo/redo) — it can
  never change what a note sounds like, only which string/fret plays it,
  so the "no silent pitch writes" contract is unbreakable by construction.
  Everything else stays locked until suggest-position (P6) lands.
- Multi-select cycles each note independently and skips single-position
  notes; corrupt/out-of-range positions refuse rather than guess.
- Fretted-in-roll notes now render in their string's lane color with an
  `s·f` position chip (octave color + note name are redundant with the Y
  axis), so a cycle step reads as a color flip at a fixed height.

Read-first per V4; no pitch-changing write is added. Tuning-aware:
Drop-D, capo, re-entrant all covered. Tests:
tests/roll_position_cycle.test.js (candidate enumeration, wrap, capo pair,
exec/rollback/redo deep-equality inside the locked roll, the lock still
blocks unflagged commands, multi-select independence, all-adversarial
refusals, dispatch routing) — all fail on main.


Claude-Session: https://claude.ai/code/session_01HzMBtxWnLGHYkMMXtK38Bg

Co-authored-by: ChrisBeWithYou <chris@rifflarr.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
byrongamatos added a commit that referenced this pull request Jul 8, 2026
* feat(editor): scale-degree overlay on fretted notes (in-key highlight)

DAW roadmap 4.16a (the guitar-lane scale-degree tint; the D4 tuning→pitch
resolver it needed is _soundingPitchPure, shipped in #115). With the in-key
highlight active, each String-view note shows a small degree label in its
top-right, coloured by role.

- @pure:scale-degree: _scaleDegreeSemisPure / _scaleDegreeLabelPure give the
  degree relative to the tonic (0=root, flats for the chromatics:
  1 ♭2 2 ♭3 3 4 ♭5 5 ♯5 6 ♭7 7); _scaleDegreeColorPure is a chord-tone-leaning
  palette (root gold, 3rds sky, 5th green, 7ths violet, others neutral) so the
  1/3/5/7 skeleton pops against passing tones.
- _drawNote: hoists the sounding pitch it already computes for the in-key
  shading (degMidi) and, when ghl is present, draws the degree label top-right —
  out-of-key notes dimmed, unresolvable pitches skipped. Only renders while the
  highlight is on; keys view and the piano roll are untouched. Display-only —
  no edit, no change to the authored `sd` teaching mark.

Tests: tests/scale_degree.test.js (6) — degree wrap both directions, the full
chromatic label row, a real A-major key, non-finite guard, and the role palette
(distinct 1/3/5/7, shared min/maj 3rd + 7th, neutral passing tones). All fail on
main. Full JS suite green.

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

* feat(editor): review fixes for #131 (scale-degree tint)

Fix chromatic degree label at semitone 8: ♯5 -> ♭6 to honour the
documented flat/Nashville convention shared by ♭2/♭3/♭5/♭7. Add a
regression test asserting no chromatic label uses a sharp.

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

---------

Co-authored-by: ChrisBeWithYou <chris@rifflarr.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
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