Skip to content

Preserve Guitar Pro strum direction - #2

Merged
carochacs merged 10 commits into
mainfrom
feat/gp-strum-direction-import
Jul 31, 2026
Merged

Preserve Guitar Pro strum direction#2
carochacs merged 10 commits into
mainfrom
feat/gp-strum-direction-import

Conversation

@carochacs

Copy link
Copy Markdown
Collaborator

Summary

  • import GP3-5 chord stroke and pick-stroke direction
  • import GP6-8 Brush/Arpeggio direction from GPIF
  • serialize direction on every note in a chord and preserve it through wire conversion
  • cover down, up, unset, precedence, and chord-wide behavior with regression tests

Why

Authored up/down strumming direction was detected inconsistently and then lost during Guitar Pro conversion, reducing chart fidelity.

User impact

Imported guitar chords retain their authored stroke direction while charts without an explicit direction remain unset.

Validation

  • targeted strum-direction suite: 12 passed

Related feedpakr issue: get-flashbacks/feedBack-plugin-feedpakr#18

carochacs and others added 7 commits July 29, 2026 20:25
Note.pick_direction (song.py) has been a fully-wired wire-format field
all along (pkd on the wire, pickDirection in RS-XML), but neither GP
importer ever populated it — every imported note landed at -1 (unset)
even when the source file authored an explicit up/down stroke.

- gp2rs.py: read beat.effect.pickStroke (PyGuitarPro already exposes it)
  once per beat, applied to every note/chordNote built from that beat —
  a stroke is one pick gesture across the whole chord, not a per-note
  choice. Guitar/bass path only; the piano and drum note-building paths
  reuse the same RsNote/XML writer but never set it (no strum concept
  for keys/drums).
- gp2rs_gpx.py: same idea from GPIF's <Beat><Stroke><Direction> for
  GP6/7/8, guarded off for is_drum/is_keys.
- Dropped the chord-level strum="down" XML attribute — it was hardcoded
  unconditionally and never read by song.py's parser at all (confirmed
  via grep); real direction now lives per-note on pickDirection instead,
  so the dead attribute was just misleading output.

211/211 tests pass (test_gp2rs.py, test_gp2rs_gpx.py), including 9 new
tests covering both formats, the chord-wide single-stroke-direction
behavior, and the missing-attribute/unset fallback paths.

Fixes got-feedBack#1054.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
capo was hardcoded to "0" in the shared RS-XML writer (_build_xml)
regardless of what the source file actually declared. feedpakr_pipeline.py
had already patched around it downstream (_capo_for_track/_gpif_capo_lookup,
applied post-conversion) precisely because this was never fixed at the
source — but the editor plugin's own GP-import route never applied the
same patch, so it silently imported every Guitar Pro file at capo=0
(found while comparing what metadata feedpaker vs. the editor pull from
the same GP import).

Fixed at the actual source instead of adding a third downstream patch:
- gp2rs.py: Track.offset (PyGuitarPro's own capo field for GP3-5).
- gp2rs_gpx.py: GPIF's Track/Staves/Staff/Properties/
  Property[@name='CapoFret']/Fret for GP6/7/8.

Both guarded off for piano/drum tracks, which have no capo concept.
Since both feedpakr and the editor call these same shared converters,
this fixes the editor's capo import for free with no editor-repo change
needed. feedpakr's own downstream patch becomes redundant but harmless
(re-reads the now-correct value and reassigns it) — left in place rather
than touching a third repo in the same pass.

5 new tests (2 gp2rs.py, 3 gp2rs_gpx.py); 379/379 passing across
test_gp2rs.py, test_gp2rs_gpx.py, test_song.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Standardize terminology across the project. The library is the folder
where songs are stored, so LIBRARY_PATH (host env var) and LIBRARY_DIR
(container/app env var) are clearer than DLC_PATH/DLC_DIR.

Changes:
- docker-compose.yml: LIBRARY_PATH env var, mount to /library, set LIBRARY_DIR=/library
- docker-compose.nas.yml: mount to /library, set LIBRARY_DIR=/library
- server.py: read LIBRARY_DIR env var instead of DLC_DIR
- dlc_paths.py: update comments to reference LIBRARY_DIR
- docs/skills/: update examples to use LIBRARY_PATH
- tests/browser/: update examples to use LIBRARY_PATH

Internal app variable names (dlc_dir, dlc_dir_env) kept unchanged to
minimize refactor scope.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Both /library and /dlc are local folders for song collections and
should never be committed to the repo.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…ickStroke

GP4/5 binary actually encodes two independent fields on a beat
(guitarpro/gp4.py's readBeatEffects, gated by separate flag bits):
`effect.stroke` (a BeatStroke(direction, value) — the "brush"/arpeggio
strum Guitar Pro's own UI writes when you drag across a chord, `value`
being its speed) and `effect.pickStroke` (a separate, bare-enum field).
The earlier fix (previous commit on this branch) only read `pickStroke`.

Verified against real chart files: a sample GP5 with 24 authored chord
strums had all 24 on `.stroke.direction` and zero on `.pickStroke` — so
in practice "chord strumming" IS `.stroke`, and the prior fix silently
imported every one of them as unset. The unit tests all passed because
they mocked `.pickStroke` directly rather than the real BeatEffect
shape, so nothing caught it until checking against an actual file.

`_gp_pick_direction` now takes the whole beat.effect and checks
`.stroke.direction` first (falling back to `.pickStroke` when `.stroke`
is unset, since it's still a real, distinct field some files may use).
Both share the same BeatStrokeDirection enum, so one mapping covers
either source.

4 new tests, including a real-file check that 72 chord notes in the
"Rehab" sample now carry pickDirection where 0 did before. 382/382
passing across test_gp2rs.py, test_gp2rs_gpx.py, test_song.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…troke

Same class of bug as the GP4/5-side fix on this branch: the original
_gpif_pick_direction guessed a <Beat><Stroke><Direction> shape that
doesn't exist in any real GPIF export. Verified against real GP8 files:
the actual chord-strum marking is <Properties><Property name="Brush">
<Direction>Down|Up</Direction></Property></Properties> — the direct
GPIF equivalent of GP4/5's beat.effect.stroke (see gp2rs.py's analogous
fix). <Arpeggio>Down|Up</Arpeggio> (a direct <Beat> child, not a
Property) is a related broken-chord articulation, checked as a fallback
when Brush is absent.

Verified end-to-end against 12 real GP8 sample files: 7,865 chord notes
across them now carry pickDirection, where the Stroke-based guess
produced zero on every one of them.

7 new/rewritten tests; 387/387 passing across test_gp2rs.py,
test_gp2rs_gpx.py, test_song.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@carochacs carochacs added the enhancement New feature or request label Jul 31, 2026
@carochacs
carochacs marked this pull request as ready for review July 31, 2026 20:00
@carochacs
carochacs merged commit 830a708 into main Jul 31, 2026
5 checks passed
@carochacs
carochacs deleted the feat/gp-strum-direction-import branch July 31, 2026 20:01
@carochacs
carochacs restored the feat/gp-strum-direction-import branch July 31, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant