* fix: correctly import and notate multi-staff (piano/keys) tracks from GP8
Fixes bass stave being dropped on import (bar-column enumeration bug)
and wrong hand-split heuristic in notation_lift for chords straddling
middle C.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: byrongamatos <xasiklas@gmail.com>
* fix(gp-import): fold all grand-staff staves, per-stave tuning, playable hand-splits
Addresses review on #692 (topkoa):
- split_hands: only use the middle-C boundary when both resulting hands are
within HAND_SPLIT_SPAN_SEMITONES, else fall back to the largest-gap
heuristic — a hard middle-C split otherwise put a 19-semitone (unplayable)
span in one hand for bass-under-treble voicings (e.g. E2+B3 under an Em7
shape).
- Treat any multi-stave (grand-staff) track as keys end-to-end, so the
stave-0 and folded stave-1+ notes share one encoding and note_count (which
sums every stave column) matches what actually imports — closing the
phantom-count case for grand-staff instruments the name/program heuristics
miss (harp, celesta, marimba).
- Fold *every* extra stave (stave_columns[1:]), not just stave 1.
- Per-staff tuning fall-back to the track-level Tuning property so an untuned
staff never yields an empty pitch list (silent note loss); via a shared
_parse_tuning helper.
- Extract _collect_column_notes / _merge_lh_notes so the GPX LH/RH pair merge
and the GP8 grand-staff fold share one implementation and can't drift in
tie/timing/dedup handling.
- Rebuild filtered_to_raw from the already-computed stave_columns (one source
of truth for the counting rule) and drop the dead num_raw_tracks/raw_tracks.
Tests: grand-staff fold + bar-column offset (test_gp2notation.py); both
middle-C split cases (test_notation_lift.py). CHANGELOG updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: byrongamatos <xasiklas@gmail.com>
---------
Signed-off-by: byrongamatos <xasiklas@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
Core never read or emitted the manifest `feedpak_version` field. Adopt it:
- sloppak.py: `FEEDPAK_VERSION = "1.2.0"` constant (the format version this build
targets); `LoadedSloppak.feedpak_version` read from the manifest on load
(string, else None for legacy/absent).
- Stamp the version on the two core manifest-rewrite paths, without downgrading
an existing (possibly higher) declared version:
- gp2notation: `setdefault` before its notation-add rewrite.
- songmeta: opportunistically when a metadata field is supplied (gated on the
existing `dirty` flag, so never a standalone rewrite).
Core has no create-from-scratch path (RS-free repo) — the editor plugin's
create-mode save stamping FEEDPAK_VERSION is a follow-up in that repo. Internal
"sloppak" naming is intentionally left as-is (a rename is out of scope / risky).
Codex-reviewed: no P1/P2. +6 tests (read present/absent/non-string; metadata-write
stamp-when-absent / preserve-existing / no-op-no-stamp) + updated the gp2notation
key-order test for the appended version. 197 sloppak/songmeta/gp2notation tests pass.
Closes#527. Part of got-feedback/feedback#334.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>