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
@@ -32,6 +32,7 @@ from gp2rs_gpx import (
|
||||
_inject_tones,
|
||||
_resolve_pending_slides,
|
||||
_gpx_bend_shape,
|
||||
_gpif_left_fingering,
|
||||
)
|
||||
from gp2rs import RsNote
|
||||
|
||||
@@ -731,6 +732,32 @@ def test_note_vibrato_ignores_whammy_trembar_property():
|
||||
assert _note_has_vibrato(n, tp) is False
|
||||
|
||||
|
||||
# ── _gpif_left_fingering (GP7/GP8 per-note fret-hand finger -> fg) ───────────
|
||||
# GPIF stores a single note's fret-hand finger as a direct <LeftFingering>
|
||||
# child of <Note> (NOT a <Property>), with classical p-i-m-a-c letter codes —
|
||||
# verified against real GP8 exports (Open / I / M observed). Maps to the same
|
||||
# RS finger integers as the chord-diagram path (§6.2.2). Teaching mark only.
|
||||
|
||||
@pytest.mark.parametrize("code, expected", [
|
||||
("Open", -1), ("P", 0), ("I", 1), ("M", 2), ("A", 3), ("C", 4),
|
||||
("i", 1), ("m", 2), # case-insensitive
|
||||
("index", 1), ("ring", 3), # word forms also accepted
|
||||
])
|
||||
def test_gpif_left_fingering_letter_codes(code, expected):
|
||||
n = ET.fromstring(f'<Note id="1"><LeftFingering>{code}</LeftFingering>'
|
||||
'<Properties></Properties></Note>')
|
||||
assert _gpif_left_fingering(n) == expected
|
||||
|
||||
|
||||
def test_gpif_left_fingering_absent_or_unknown_is_unset():
|
||||
# No <LeftFingering> child, or an unrecognised value -> -1 (never fabricate).
|
||||
assert _gpif_left_fingering(ET.fromstring('<Note id="1"/>')) == -1
|
||||
assert _gpif_left_fingering(
|
||||
ET.fromstring('<Note id="1"><LeftFingering>Z</LeftFingering></Note>')) == -1
|
||||
assert _gpif_left_fingering(
|
||||
ET.fromstring('<Note id="1"><LeftFingering></LeftFingering></Note>')) == -1
|
||||
|
||||
|
||||
# ── convert_file: GP8 chord-diagram name + fingering extraction (E3) ─────────
|
||||
# GP7/GP8 GPIF carries authored chord diagrams under a track's
|
||||
# Property[@name="DiagramCollection"]. Each Item gives the chord name and a
|
||||
|
||||
Reference in New Issue
Block a user