Translate GP6/GP7 note effects#63
Closed
knoguchi wants to merge 8 commits into
Closed
Conversation
GP6 and GP7 store the score as a score.gpif XML document inside a container: GP6 uses a BCFZ-compressed BCFS virtual filesystem, while GP7 is a plain ZIP archive. This adds a reader for both. - gpx.py: BCFZ bitstream decompression (based on the algorithm from the feature/gpx branch contributed by J. Jorgen von Bargen), the BCFS sector archive reader, and GP7 ZIP extraction. - gpif.py: maps the score.gpif XML into the existing Song model, resolving the format's cross-referenced bars/voices/beats/notes/rhythms lists. Covers song info, tracks, tunings, master bars and time signatures, voices, beats, durations and notes. - io.py: parse() now detects the BCFZ/BCFS/ZIP magic and dispatches to the new reader; the binary GP3/4/5 path is unchanged. Writing GP6/GP7 is not supported. Advanced note/beat effects are not yet translated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The BCFZ payload may end mid-byte, so the last compression token can require bits beyond the end of the stream. Yield zero padding bits past end-of-stream instead of raising IndexError, matching the reference decoder's EOF handling. Add "Dear Song.gpx" which exercises this path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Synthesize a GP7 archive from an existing .gpx score's score.gpif and assert it parses into an identical Song, covering the ZIP extraction branch without committing a binary GP7 fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
guitarpro.write can now produce GP6 and GP7 files, completing the read/write parity the library offers for GP3/4/5. - gpif.py: GPIFWriter serializes a Song into a score.gpif document, hoisting the measure/voice/beat/note tree into the format's flat, cross-referenced Bars/Voices/Beats/Notes/Rhythms lists. - gpx.py: BCFZ compression (literal-run encoding, which is valid BCFZ and keeps the encoder linear) and a BCFS image builder; GP7 is written as a ZIP archive. - io.py: write() dispatches to the GPX writer for version (6,0,0)/(7,0,0) or a .gpx/.gp extension. Round-trips parse -> write -> parse with full Song equality. Writes cover the same subset of the model that reading populates; advanced effects are not yet serialized. Also tightened voice reading to skip unused (-1) voice slots so the voice count is deterministic across a round-trip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Buffer non-seekable streams before the magic peek so parse() works on pipes and other non-seekable file-like objects. - Raise GPException (not KeyError) when a GP6 container has no score.gpif. - Emit one Bar per track in every MasterBar so the Bars reference list has a consistent length. - Drop the internal buildBCFS helper from the module's public __all__. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Map the common note and beat effects between the score.gpif XML and the Song model, using alphaTab's GpifParser as the reference for the flag and type encodings: - hammer-on/pull-off (HopoOrigin) - slides (Slide/Flags bitfield -> SlideType list) - harmonics: natural, artificial, pinch, tap, semi (Harmonic + HarmonicType + HarmonicFret) - left/right-hand fingering (LeftFingering/RightFingering letter codes) - accentuation: accent, heavy accent, staccato (Accent flag bits) - beat text (FreeText) Effects read from the sample files round-trip with full Song equality, and each effect branch is covered by a synthetic parse -> write -> parse test. Bends, grace notes and chord diagrams remain unmapped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The declared uncompressed length is only an upper bound: real GP6 streams that use back-references end before reaching it, with the final byte zero-padded. The previous code padded past end-of-stream with zero bits, which decode to empty literal runs and made decompression loop forever on any such file (the literal-heavy sample files happened to hit the declared length exactly and so were unaffected). Raise at end-of-stream and stop, matching the reference decoder. Verified against the alphaTab GP6 test corpus: all 35 files now decompress, parse and round-trip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Maps the common GP6/GP7 note and beat effects between the
score.gpifXML and theSongmodel, in both directions. The flag/type encodings follow alphaTab'sGpifParseras the reference.Effects covered:
HopoOrigin→NoteEffect.hammerSlide/Flagsbitfield →NoteEffect.slides(shift, legato, out-down/up, into-from-below/above)Harmonic+HarmonicType+HarmonicFret→ natural / artificial / pinch / tap / semiLeftFingering/RightFingeringletter codes (P/I/M/A/C) →FingeringAccentflag bits → accent (0x08), heavy accent (0x04), staccato (0x01)FreeText→Beat.textTests
Songequality.parse → write → parsetest (so write↔read symmetry is verified even for effects absent from the sample file).All 221 tests pass.
Not yet covered
Bends / whammy bar (point-based, needs careful encoding), grace notes (structural — stored as separate beats), trill / tremolo picking, and chord diagrams (a large separate subsystem). These can follow in another pass.
Note on stacking
Same two caveats as #62 apply: literal-only BCFZ compression, and round-trips verified through PyGuitarPro rather than against the Guitar Pro application.
🤖 Generated with Claude Code