mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 03:09:57 +00:00
feat(core): teaching marks fg/ch/sd — wire + GP import + sd derivation (§6.2.2) (#536)
Add the three OPTIONAL per-note feedpak 1.5.0 teaching marks — fg (fret-hand finger), ch (strum-group key), sd (scale degree) — to the Note model and wire format, mirroring the bend-shape work (#531). These are DISPLAY/TEACHING ONLY: nothing in the scoring / note-verification path reads them. - lib/song.py: Note.fret_finger / strum_group / scale_degree, default-omitted on the wire (fg/ch/sd) and decoded via _wire_int_optional; _parse_note reads the GP-written fretFinger XML attr. Pure helpers key_to_tonic_pc (§7.7 key name -> tonic pitch class) + scale_degree_for_pitch, plus base_open_string_midis / pitch_from_base / note_pitch_midi (tuning offsets + capo + fret -> MIDI, mirroring app.js _TUNING_BASE_MIDI). - lib/gp2rs.py: GP5 note.effect.leftHandFinger -> fg (RsNote field + fretFinger XML attr), reusing the chord Fingering value convention. - lib/gp2rs_gpx.py: GP8/GPIF per-note <LeftFingering> (p-i-m-a-c letter codes, verified against real GP8 exports) -> fg. - server.py highway_ws: derive sd for notes + chord notes from the active keys.json key + sounding pitch when the author didn't author one (author value wins); base hoisted out of the per-note loop. Tests: round-trip + omit-when-default + malformed-tolerance for fg/ch/sd; key_to_tonic_pc + scale_degree_for_pitch + note_pitch_midi (standard/drop-D/ capo/bass) units; GP5 leftHandFinger and GP8 <LeftFingering> import. Part of got-feedback/feedback#334 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a858617d71
commit
6ee5da3d8b
@@ -1220,6 +1220,33 @@ def test_chord_diagram_fingers_extracted():
|
||||
assert [ct.get(f"finger{i}") for i in range(0, 4)] == ["-1"] * 4
|
||||
|
||||
|
||||
def test_single_note_left_hand_finger_imports_as_fg():
|
||||
"""A GP single note's leftHandFinger imports as the `fg` teaching mark and
|
||||
survives convert_track XML → _parse_note → note_to_wire (§6.2.2)."""
|
||||
from song import _parse_note, note_to_wire
|
||||
note = _ct_note(guitarpro.NoteType.normal, gp_string=2, fret=5)
|
||||
note.effect.leftHandFinger = guitarpro.Fingering.middle # -> 2
|
||||
beat = _ct_beat(tick=0, dur_value=4, notes=[note])
|
||||
|
||||
root = ET.fromstring(convert_track(_ct_song([beat]), track_index=0)) # noqa: S314
|
||||
xn = root.findall(".//notes/note")[0]
|
||||
assert xn.get("fretFinger") == "2"
|
||||
assert note_to_wire(_parse_note(xn))["fg"] == 2
|
||||
|
||||
|
||||
def test_single_note_open_finger_omits_fg():
|
||||
"""Open/unset leftHandFinger leaves fg unset — no fabricated finger."""
|
||||
from song import _parse_note, note_to_wire
|
||||
note = _ct_note(guitarpro.NoteType.normal, gp_string=2, fret=5)
|
||||
note.effect.leftHandFinger = guitarpro.Fingering.open # -1 -> unset
|
||||
beat = _ct_beat(tick=0, dur_value=4, notes=[note])
|
||||
|
||||
root = ET.fromstring(convert_track(_ct_song([beat]), track_index=0)) # noqa: S314
|
||||
xn = root.findall(".//notes/note")[0]
|
||||
assert xn.get("fretFinger") is None
|
||||
assert "fg" not in note_to_wire(_parse_note(xn))
|
||||
|
||||
|
||||
def test_chord_without_diagram_has_blank_fingers():
|
||||
# A plain two-note chord (effect.chord is None) is unchanged: blank name,
|
||||
# all-(-1) fingers — no regression for diagram-less charts.
|
||||
|
||||
Reference in New Issue
Block a user