mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-10 18:59:56 +00:00
feat(song): per-note keys hand assignment (hand) on the Note wire (#990)
The editor's keys LH/RH hand arc needs a per-note hand assignment
('lh'/'rh', from a MusicXML grand staff import today, hand-editable
later) to survive a sloppak save → reload: the editor already emits it
on the wire, but note_from_wire dropped unknown keys, so the field died
on every reopen.
- Note gains `hand: str | None = None` (None = unassigned; the
heuristic hand split keeps owning unassigned notes). Distinct from
`right_hand` (the bass plucking finger) — hence the spelled-out
`hand` wire key, since `rh` is taken.
- note_to_wire emits it default-omitted and validates on emit; older
readers ignore it (feedpak: unknown note keys are permitted).
- note_from_wire decodes it as a strict enum — anything but 'lh'/'rh'
(junk, wrong case, bools) falls back to unassigned rather than
poisoning downstream hand-split / hands-separate practice logic.
Groundwork consumers land separately: notation_lift.split_hands
respecting per-note overrides, and the editor's hand surface.
Editor counterpart: feedBack-plugin-editor #299.
Tests: three new wire round-trip tests in tests/test_song.py (literal
key, default-omitted, junk rejection both directions), matching the
teaching-marks test style.
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Signed-off-by: ChrisBeWithYou <chris@rifflarr.local>
Co-authored-by: ChrisBeWithYou <chris@rifflarr.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
ChrisBeWithYou
Claude Opus 4.8
parent
1745b13ba7
commit
00fce2772d
+15
@@ -56,6 +56,13 @@ class Note:
|
||||
strum_group: int = -1
|
||||
scale_degree: int = -1
|
||||
ignore: bool = False
|
||||
# Keys hand assignment ('lh'/'rh', None = unassigned) — authored per-note,
|
||||
# e.g. from a MusicXML grand staff import in the editor. Lets the notation
|
||||
# hand split and hands-separate practice honor the author instead of the
|
||||
# mean-pitch heuristic. Distinct from `right_hand` (the bass plucking
|
||||
# finger); spelled-out `hand` on the wire because `rh` is taken.
|
||||
# Default-omitted on the wire; older readers ignore it.
|
||||
hand: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -272,6 +279,10 @@ def note_to_wire(n: Note) -> dict:
|
||||
out["ch"] = n.strum_group
|
||||
if n.scale_degree != -1:
|
||||
out["sd"] = n.scale_degree
|
||||
# Keys hand assignment — default-omitted; validated on emit so a
|
||||
# directly-constructed Note can't put junk ('LH', True, …) on the wire.
|
||||
if n.hand in ("lh", "rh"):
|
||||
out["hand"] = n.hand
|
||||
return out
|
||||
|
||||
|
||||
@@ -532,6 +543,10 @@ def note_from_wire(d: dict, time: float | None = None) -> Note:
|
||||
strum_group=_wire_int_optional(d.get("ch"), -1),
|
||||
scale_degree=_wire_int_optional(d.get("sd"), -1),
|
||||
ignore=bool(d.get("ig", False)),
|
||||
# Keys hand assignment — strict enum decode: anything but 'lh'/'rh'
|
||||
# (junk, wrong case, bools) falls back to unassigned rather than
|
||||
# poisoning downstream hand-split/practice logic.
|
||||
hand=d.get("hand") if d.get("hand") in ("lh", "rh") else None,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user