feat(editor): in-key highlight on the fretted lanes — capo-aware sounding pitch - #115
Conversation
…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
|
Warning Review limit reached
Next review available in: 28 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds 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. ChangesIn-key highlight for fretted lanes
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/fret_key_highlight.test.js (1)
35-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueBrace-counting extraction is fragile but low-risk for now.
extractFncounts 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
📒 Files selected for processing (4)
CHANGELOG.mdscreen.htmlscreen.jstests/fret_key_highlight.test.js
…eRabbit nitpick) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
…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>
…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>
* 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>
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._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.cc→55, 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_drawNotedoes zero per-note arrangement work.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