feat(editor): MusicXML keys imports keep their authored hand splits — the per-note hand field arrives - #299
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds validated per-note ChangesMusicXML authored notation and hand metadata
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant MusicXMLImport
participant AddArrangement
participant SidecarPersistence
MusicXMLImport->>MusicXMLImport: align notes and notation by audio offset
MusicXMLImport->>AddArrangement: register Keys arrangement
AddArrangement->>SidecarPersistence: persist stamped MusicXML notation
SidecarPersistence->>SidecarPersistence: retain or invalidate by source_notes_fp
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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 `@src/import.js`:
- Around line 86-89: Update the nested iteration in the import flow to verify
that staff.voices and voice.beats are arrays before iterating, defaulting
malformed values such as objects to empty arrays. Preserve the existing voice,
beat, and timestamp validation behavior while preventing malformed plugin
payloads from throwing.
🪄 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: 808d011d-059e-488a-8e45-2242bc6398a2
📒 Files selected for processing (5)
CHANGELOG.mdroutes.pysrc/import.jstests/musicxml_hand_import.test.mjstests/test_musicxml_notation.py
|
@coderabbitai review |
✅ Action performedReview finished.
|
e92816d to
52ca211
Compare
…efresh the notation The hand arc, Step B (stacked on feat/keys-hand-import / #299). The per-note `hand` field becomes authorable and visible: - Note ▸ Hand (keys): Left / Right / Clear on the selection — one undo step via SetTechScalarCmd. Runtime-guarded to keys arrangements. - Track ▸ Assign hands by split…: the split point is a STAMPING generator, never a live layer (the piano-pedagogy call): pick a split note (default C4, note names or MIDI accepted — new @pure _parseSplitPitchPure), and every targeted note takes lh below / rh at-or-above in ONE undoable command. New SetTechScalarPerNoteCmd — SetTechScalarCmd's per-note-values sibling (no bend special-casing; plain scalar marks only). Selection scopes the stamp; per-note edits win from then on. - Hand shading on the piano roll (View ▸, default ON): LH warm (#e8965a) / RH cool (#5a9de8) body color; unassigned keeps the octave palette so "no call made" never reads as an assignment. Same color-job argument as fretted-in-roll: Y already says the pitch, so color says WHO PLAYS IT. - `hand` joins `_notes_fingerprint`'s identity tuple — the ONE technique that does — closing the tracked trap where a hand EDIT couldn't invalidate a preserved authored sidecar (it would freeze the old hands forever). Editor/wire shapes stay fingerprint-identical; junk enums read unassigned on both. One-time effect: previously stamped payloads re-fingerprint on next save and take the measure-granular merge (unedited bars keep authored hands). The relift honors per-note hands via core split_hands (core PR #992). New src/hand.js module; menus in menu-bar.js; USER-GUIDE "Hands on keys tracks" section. Tests: tests/hand_authoring.test.mjs (4 — parser incl. accidentals and junk, stamp orientation matching core, per-note command exec→undo→redo round-trip incl. a bare-techniques note) + a fingerprint-invalidation pytest (hand flips it; other techniques and junk don't; shapes agree). Gates: 326 pytest, JS suite green (song_fit env failure = pristine main), lint 0 errors / 3 baseline warnings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
…efresh the notation The hand arc, Step B (stacked on feat/keys-hand-import / #299). The per-note `hand` field becomes authorable and visible: - Note ▸ Hand (keys): Left / Right / Clear on the selection — one undo step via SetTechScalarCmd. Runtime-guarded to keys arrangements. - Track ▸ Assign hands by split…: the split point is a STAMPING generator, never a live layer (the piano-pedagogy call): pick a split note (default C4, note names or MIDI accepted — new @pure _parseSplitPitchPure), and every targeted note takes lh below / rh at-or-above in ONE undoable command. New SetTechScalarPerNoteCmd — SetTechScalarCmd's per-note-values sibling (no bend special-casing; plain scalar marks only). Selection scopes the stamp; per-note edits win from then on. - Hand shading on the piano roll (View ▸, default ON): LH warm (#e8965a) / RH cool (#5a9de8) body color; unassigned keeps the octave palette so "no call made" never reads as an assignment. Same color-job argument as fretted-in-roll: Y already says the pitch, so color says WHO PLAYS IT. - `hand` joins `_notes_fingerprint`'s identity tuple — the ONE technique that does — closing the tracked trap where a hand EDIT couldn't invalidate a preserved authored sidecar (it would freeze the old hands forever). Editor/wire shapes stay fingerprint-identical; junk enums read unassigned on both. One-time effect: previously stamped payloads re-fingerprint on next save and take the measure-granular merge (unedited bars keep authored hands). The relift honors per-note hands via core split_hands (core PR #992). New src/hand.js module; menus in menu-bar.js; USER-GUIDE "Hands on keys tracks" section. Tests: tests/hand_authoring.test.mjs (4 — parser incl. accidentals and junk, stamp orientation matching core, per-note command exec→undo→redo round-trip incl. a bare-techniques note) + a fingerprint-invalidation pytest (hand flips it; other techniques and junk don't; shapes agree). Gates: 326 pytest, JS suite green (song_fit env failure = pristine main), lint 0 errors / 3 baseline warnings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
…ing (CodeRabbit) - CHANGELOG: the #299 'known follow-ups' claimed a hand edit doesn't invalidate the notation sidecar; this PR implements exactly that (hand joins the fingerprint), so drop the now-false clause and note it's done — keeping the still-open split_hands (core #992) and sloppak-loader follow-ups. - USER-GUIDE: 'Hands on keys tracks' -> 'Hands on keyboard tracks' (clearer). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…resh the notation (#303) * feat(editor): keys hand authoring — set/stamp/shade, and hand edits refresh the notation The hand arc, Step B (stacked on feat/keys-hand-import / #299). The per-note `hand` field becomes authorable and visible: - Note ▸ Hand (keys): Left / Right / Clear on the selection — one undo step via SetTechScalarCmd. Runtime-guarded to keys arrangements. - Track ▸ Assign hands by split…: the split point is a STAMPING generator, never a live layer (the piano-pedagogy call): pick a split note (default C4, note names or MIDI accepted — new @pure _parseSplitPitchPure), and every targeted note takes lh below / rh at-or-above in ONE undoable command. New SetTechScalarPerNoteCmd — SetTechScalarCmd's per-note-values sibling (no bend special-casing; plain scalar marks only). Selection scopes the stamp; per-note edits win from then on. - Hand shading on the piano roll (View ▸, default ON): LH warm (#e8965a) / RH cool (#5a9de8) body color; unassigned keeps the octave palette so "no call made" never reads as an assignment. Same color-job argument as fretted-in-roll: Y already says the pitch, so color says WHO PLAYS IT. - `hand` joins `_notes_fingerprint`'s identity tuple — the ONE technique that does — closing the tracked trap where a hand EDIT couldn't invalidate a preserved authored sidecar (it would freeze the old hands forever). Editor/wire shapes stay fingerprint-identical; junk enums read unassigned on both. One-time effect: previously stamped payloads re-fingerprint on next save and take the measure-granular merge (unedited bars keep authored hands). The relift honors per-note hands via core split_hands (core PR #992). New src/hand.js module; menus in menu-bar.js; USER-GUIDE "Hands on keys tracks" section. Tests: tests/hand_authoring.test.mjs (4 — parser incl. accidentals and junk, stamp orientation matching core, per-note command exec→undo→redo round-trip incl. a bare-techniques note) + a fingerprint-invalidation pytest (hand flips it; other techniques and junk don't; shapes agree). Gates: 326 pytest, JS suite green (song_fit env failure = pristine main), lint 0 errors / 3 baseline warnings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q * fix(editor): keep notation fingerprint sort crash-safe on stacked unison hands The `hand` field added to `_notes_fingerprint`'s identity tuple used a None sentinel for unassigned. Two notes identical in (t,s,f,sus) but differing in hand — a doubled unison (both hands on one pitch, real in piano writing) with one note assigned and the other not — reach a None-vs-str compare in the sort and raise TypeError, crashing the save. Use "" for unassigned instead: still distinct from "lh"/"rh" (the hand still counts toward invalidation) and totally ordered against them. Regression test crashes pre-fix, passes after. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(editor): reconcile hand-authoring changelog + clarify guide heading (CodeRabbit) - CHANGELOG: the #299 'known follow-ups' claimed a hand edit doesn't invalidate the notation sidecar; this PR implements exactly that (hand joins the fingerprint), so drop the now-false clause and note it's done — keeping the still-open split_hands (core #992) and sloppak-loader follow-ups. - USER-GUIDE: 'Hands on keys tracks' -> 'Hands on keyboard tracks' (clearer). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: ChrisBeWithYou <chris@rifflarr.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: byrongamatos <xasiklas@gmail.com>
What
The keys LH/RH hand-assignment arc, slice A. MusicXML grand-staff scores carry authored per-stave hand assignments; until now the import deleted them (
delete arr.notation, src/import.js) and every save re-derived hand splits with core's mean-pitch heuristic — the documented "produces wrong hand splits" path. Edited keys parts silently teach the wrong hands; this slice makes the authored data survive.Authored notation rides the existing GP rail end to end
@pure_shiftNotationTimes, the JS mirror of_warp_notation_sidecar's walk) — an unshifted payload would be born stale against the fingerprint.source:"musicxml"+source_notes_fpat the one moment payload and notes are known in sync (new_stamp_musicxml_notation, the counterpart of_attach_gp_notation) and returns it; the client carries it as_gp_notation— the field the save body already ships._AUTHORED_NOTATION_SOURCES({"gp","musicxml"}) and now preserves the ORIGINAL provenance on every re-stamp (kept-verbatim and measure-merge paths) instead of rewriting it to"gp". Fingerprint invalidation ("edits win") and the measure-granular merge behave identically for both sources.The per-note
handfield ('lh'/'rh', absent = unassigned)_NOTE_TECH_FIELDS— string-valued, never in the bool set, absent defaultNone; the content-signature builders now share_note_tech_defaultso object- and dict-shaped notes can't drift.handkey (rhis taken by right_hand), strictly validated to the enum — junk ("LH","left",true, …) never rides.reconstructChordsbecause it ridestechniques(pinned by test).rh, 2→lh; an organ-pedal staff stays unassigned rather than guessed).Companion + follow-ups
parse-arrangementendpoint producing the arrangement + payload). Without it the MusicXML path stays "plugin not installed" exactly as before — the PRs are independently mergeable.handfor reload round-trips (small core PR, next in the arc); a hand EDIT doesn't yet invalidate a preserved authored sidecar (the fingerprint deliberately ignores techniques);split_handsrespecting per-note overrides. Then the editor surface: roll shading by hand, movable split-point stamping command, hands-separate practice.Tests
tests/test_musicxml_notation.py— 23 new: registry invariants, wire validation (incl. parametrized junk rejection), chord-member ride-along, stamping semantics, and the rail honoring / reopening / invalidatingsource:"musicxml"against the real core lift (same core-lib discovery + skip contract as test_notation_save.py). Fails on main by construction.tests/musicxml_hand_import.test.mjs— 7 new: notation shift semantics (measure+beat, 1 ms grid, dur untouched, malformed tolerated) and hand surviving reconstructChords/flatten round-trips.🤖 Generated with Claude Code
https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Summary by CodeRabbit
New Features
lh/rh), including chord/member propagation.Bug Fixes
Tests