Compare commits

...
Author SHA1 Message Date
zagatozeeandBret Mogilefsky 2d79e216b3 Change staff parameter to use _wire_int_optional
(cherry picked from commit 64656bda1edc3fe03b6ad5e747c7afa3abca31d6)
2026-06-17 23:42:08 -07:00
zagatozeeandBret Mogilefsky bd22951874 feed the rabbit
Updated the docstring for _load_gpif to clarify supported file formats and improved error messages for unrecognized containers.L

(cherry picked from commit 1866c72e53339a10d6f184d21c2261e9f0b21395)
2026-06-17 23:42:08 -07:00
zagatozeeandBret Mogilefsky eb2121b4bc Piano LH/RH seperation work
Add LH/RH piano chart seperation ground work

(cherry picked from commit 4c227f30347d7f0cf2f3a8c4f81f9ba7ec2dee68)
2026-06-17 23:42:07 -07:00
zagatozeeandBret Mogilefsky 297778864c Add staff attribute to note representation
Par tof the LH/RH seperation future schema

(cherry picked from commit 56ad235a1ad4875527abb69836bcace79fdadfdc)
2026-06-17 23:42:07 -07:00
zagatozeeandBret Mogilefsky 3ab57977c4 Update song.py
Groundwork for piano charts keeping LH and RH designations intact, but seperated, so they can be toggled via menu options etc in future

(cherry picked from commit 606b69a75eb5202c6fb2978f496bf97db0ec65e4)
2026-06-17 23:42:07 -07:00
3 changed files with 28 additions and 30 deletions
+3
View File
@@ -69,6 +69,7 @@ class RsNote:
tremolo: bool = False tremolo: bool = False
tap: bool = False tap: bool = False
link_next: bool = False link_next: bool = False
staff: int = -1 # Piano staff: -1=absent, 0=LH/bass clef, 1=RH/treble clef
@dataclass @dataclass
@@ -1021,6 +1022,8 @@ def _build_xml(
"tap": "1" if n.tap else "0", "tap": "1" if n.tap else "0",
"ignore": "0", "ignore": "0",
} }
if getattr(n, 'staff', -1) != -1:
attrs["staff"] = str(n.staff)
ET.SubElement(notes_el, "note", **attrs) ET.SubElement(notes_el, "note", **attrs)
# Chords # Chords
+16 -26
View File
@@ -279,7 +279,6 @@ def _gpif_tracks(root: ET.Element) -> list[dict]:
midi_channel = 0 midi_channel = 0
is_drums = False is_drums = False
if gm is not None: if gm is not None:
# GP6 (.gpx): <GeneralMidi table="Percussion"><Program>...</Program>
try: midi_program = int(gm.findtext('Program') or 0) try: midi_program = int(gm.findtext('Program') or 0)
except (ValueError, TypeError): pass except (ValueError, TypeError): pass
try: try:
@@ -289,25 +288,6 @@ def _gpif_tracks(root: ET.Element) -> list[dict]:
except (ValueError, TypeError): pass except (ValueError, TypeError): pass
if gm.get('table') == 'Percussion': if gm.get('table') == 'Percussion':
is_drums = True is_drums = True
else:
# GP7/GP8 (.gp): <InstrumentSet><Type>drumKit</Type>
# and <MidiConnection><PrimaryChannel>9</PrimaryChannel>
inst_set = t.find('InstrumentSet')
if inst_set is not None:
inst_type = (inst_set.findtext('Type') or '').lower()
if inst_type == 'drumkit':
is_drums = True
midi_conn = t.find('MidiConnection')
if midi_conn is not None:
try:
ch_text = (midi_conn.findtext('PrimaryChannel') or '').strip()
if ch_text:
ch = int(ch_text)
midi_channel = ch
if ch == 9:
is_drums = True
except (ValueError, TypeError):
pass
# String tuning # String tuning
string_pitches: list[int] = [] string_pitches: list[int] = []
@@ -624,17 +604,16 @@ def list_tracks(gp_path: str) -> list[dict]:
result = [] result = []
for i, t in enumerate(tracks): for i, t in enumerate(tracks):
is_bass = bool( is_bass = bool(
not t['is_drums'] # drums always have low/zero string pitches — must exclude
and (
( (
not t['string_pitches'] # no string tuning = not guitar-family not t['is_drums']
and not t['string_pitches'] # no string tuning = not guitar-family
and 32 <= t['midi_program'] <= 39 and 32 <= t['midi_program'] <= 39
) or ( ) or (
t['string_pitches'] not t['is_drums'] # explicit guard: drums can have low string pitches
and t['string_pitches']
and max(t['string_pitches']) <= 48 # bass top string ≤ C3 and max(t['string_pitches']) <= 48 # bass top string ≤ C3
) )
) )
)
is_piano = ( is_piano = (
not t['is_drums'] not t['is_drums']
and not t['string_pitches'] and not t['string_pitches']
@@ -1339,7 +1318,7 @@ def convert_file(
if bid != '-1' and bid: if bid != '-1' and bid:
bar = bars_by_id.get(bid) bar = bars_by_id.get(bid)
if bar is not None: if bar is not None:
for vid in bar.findtext('Voices', '').split(): for voice_pos, vid in enumerate(bar.findtext('Voices', '').split()):
if vid == '-1': if vid == '-1':
continue continue
voice = voices_dict.get(vid) voice = voices_dict.get(vid)
@@ -1440,6 +1419,13 @@ def convert_file(
fret=rs_fret, fret=rs_fret,
sustain=sustain, sustain=sustain,
) )
# Staff assignment for keys/piano (slopsmith staff schema).
# Derived from voice position as authored in the GP tab —
# not inferred from pitch — preserving hand crossings.
# Voice 0 = treble/RH staff (staff=1),
# Voice 1+ = bass/LH staff (staff=0).
if is_keys:
rn.staff = 1 if voice_pos == 0 else 0
# Techniques — GPIF stores these as <Property> # Techniques — GPIF stores these as <Property>
# elements (NOT child tags), so the old # elements (NOT child tags), so the old
@@ -1674,6 +1660,10 @@ def convert_file(
fret=_lh_midi % 24, fret=_lh_midi % 24,
sustain=_lh_dur if _lh_dur > 0.2 else 0.0, sustain=_lh_dur if _lh_dur > 0.2 else 0.0,
) )
# LH track notes are always bass clef (staff=0).
# The LH track as a whole IS the left hand —
# voice position within the LH track is irrelevant.
_lh_rn.staff = 0
_lh_notes.append(_lh_rn) _lh_notes.append(_lh_rn)
_lh_last_per_key[_lh_midi] = _lh_rn _lh_last_per_key[_lh_midi] = _lh_rn
_lh_vt += _lh_dur _lh_vt += _lh_dur
+5
View File
@@ -37,6 +37,7 @@ class Note:
right_hand: int = -1 right_hand: int = -1
pick_direction: int = -1 pick_direction: int = -1
ignore: bool = False ignore: bool = False
staff: int = -1 # Piano staff: -1=absent, 0=LH/bass clef, 1=RH/treble clef
@dataclass @dataclass
@@ -217,6 +218,8 @@ def note_to_wire(n: Note) -> dict:
out["pkd"] = n.pick_direction out["pkd"] = n.pick_direction
if n.ignore: if n.ignore:
out["ig"] = True out["ig"] = True
if n.staff != -1:
out["stf"] = n.staff
return out return out
@@ -307,6 +310,7 @@ def note_from_wire(d: dict, time: float | None = None) -> Note:
right_hand=_wire_int_optional(d.get("rh"), -1), right_hand=_wire_int_optional(d.get("rh"), -1),
pick_direction=_wire_int_optional(d.get("pkd"), -1), pick_direction=_wire_int_optional(d.get("pkd"), -1),
ignore=bool(d.get("ig", False)), ignore=bool(d.get("ig", False)),
staff=_wire_int_optional(d.get("stf"), -1),
) )
@@ -762,6 +766,7 @@ def _parse_note(n) -> Note:
right_hand=_int_optional(n, "rightHand", -1), right_hand=_int_optional(n, "rightHand", -1),
pick_direction=_int_optional(n, "pickDirection", -1), pick_direction=_int_optional(n, "pickDirection", -1),
ignore=_bool(n, "ignore"), ignore=_bool(n, "ignore"),
staff=_int_optional(n, "staff", -1),
) )