A feedBack plugin that imports Guitar Pro
files into .feedpak at maximum fidelity, and batch-upgrades existing .sloppak
libraries to .feedpak.
Plugin id: feedpakr. Install into plugins/feedpakr/ (folder name must match the id).
- Upload a
.gp3-.gp5(pyguitarpro) or.gp6/.gpx/.gp(GPIF, GP6-GP8) file, pick which tracks to include and name their arrangements, optionally combine compatible same-name layered parts into one arrangement, and attach cover art. - Audio: synthesize with FluidSynth (.gp3-.gp5 only), use a GP8 file's embedded backing track, or sync a user-uploaded/YouTube-fetched recording to the chart via chroma-DTW autosync — or skip audio entirely.
- Every enrichment step is best-effort: a failure degrades that one feature (recorded
as a warning) rather than aborting the import. Output is self-validated against the
vendored feedpak-spec JSON Schemas (
assets/schemas/) before being written. The build result reports which features actually landed (features: {...}), shown in the UI as a "Captured: …" summary rather than promising more than the source actually had. - A pack built without audio is a valid authoring intermediate (feedpak spec §5.3.2)
but will not pass strict validation (
stemsis schema-required) until audio is added.
| Fidelity gap (from the original data-loss audit) | Status | Source |
|---|---|---|
| Capo | Fixed, both source families | GP3-5 Track.offset; GPIF CapoFret property — gp2rs/gp2rs_gpx hardcode/ignore this |
| Sections, beats | Fixed | song_timeline.json, via song.load_song() on the converted (playback-schedule-accurate) XML |
| Hand shapes | Derived, both families | gp2rs never populates these ("empty for now") — feedpakr groups consecutive same-chord hits into contiguous spans |
| Chord names | Optional Chordr enhancement | When selected during import, Chordr's shared chart-analysis bridge names unnamed templates from played voicings on fretted arrangements and carries names across partial strums; piano/Keys arrangements are skipped because their wire encoding uses MIDI pitches; existing names are preserved |
| Tone changes | Fixed where the source has them | GPIF: read back from XML gp2rs_gpx already injects but song.parse_arrangement silently drops; GP3-5: scanned from every selected track's mixTableChange, not just the first (unlike the legacy importer this project replaces) |
| Key signature | New capability | keys.json, GP3-5 MeasureHeader.keySignature / GPIF MasterBar/Key, spelled via the standard circle-of-fifths table |
| Lyrics | Fixed (GPIF), approximated (GP3-5, labeled as such) | GPIF vocal track's own <vocals> XML (exact timing); GP3-5's single per-measure song.lyrics blob has no per-syllable timing to work with |
| Vocal pitch | New capability (GPIF only) | Read directly from the same <vocals> XML's note= attribute |
| Drums | Promoted to real drum arrangements (GP3-5 only) | gp2rs.convert_drum_track_to_drumtab → type: drums + drum_tab.json, instead of the legacy string*24+fret fretted encoding. GPIF drums have no host-side equivalent yet, so they stay fret-encoded |
| Standard notation | New capability (GPIF melodic tracks) | Per-track opt-in notation_<id>.json via gp2notation.convert_track_to_notation; enabled by default for keys, single-hand only |
| Repeats/D.S./D.C./Coda | Fixed (GP3-5), known limitation (GPIF) | gp2rs already expands these; gp2rs_gpx doesn't yet (host TODO) — feedpakr detects repeat/volta markup up front and warns rather than silently shipping drifted timing |
| Rigs/gear chains, harmony | Out of scope | GP carries no rig/signal-chain data; harmony (chord intent vs. what's played) needs separate work |
Batch-converts existing .sloppak files to .feedpak without touching the originals.
Verified against every sample sloppak this project has (21 real packs spanning a
GP-import pipeline, CDLC-derived packs, and minimal tutorial packs): 21/21 upgrade to
a fully spec-valid .feedpak. Per file, the upgrade:
- Promotes arrangement-embedded
beats/sections(the shape most existing packs actually use) into a propersong_timeline.jsonside file — accepting both thetimeandstart_timesection-key variants seen in the wild, and never overwriting asong_timeline.jsonthat's already genuinely there. - Ensures
stems[]has anid: fullentry: promotes the deprecatedoriginal_audiokey when present, or renames a pack's only stem tofullwhen it isn't separated (a single unseparated mix is the complete mix, whatever id an older tool gave it). A pack with multiple separated stems and no full mix is left alone and reported honestly — nothing is fabricated. - Normalizes non-spec
lyrics_sourcevalues this ecosystem's own earlier tools wrote:xmlandsngboth meant "authored" (chart/CDLC-sourced, not machine-transcribed); an engine name written directly (e.g.whisperx) is corrected totranscribedwhen alyric_transcriptionblock already names that engine properly. An unrecognized value with nothing to infer from is left alone and reported, never guessed at. - Preserves every other file and every other manifest key byte-for-byte (spec §9) — an upgrade pass only adds data, never removes or reinterprets what's already there.
After a successful import or upgrade, the result panel offers Generate Preview, Split Stems, and Generate Difficulty Ladder buttons when those plugins are installed (probed via their own REST endpoints — feedpakr never generates a preview, splits stems, or authors difficulty ladders itself, it only calls the dedicated plugins that own those features). Buttons for a plugin that isn't installed simply don't appear. Split Stems is only offered for embedded or synced recordings, not synthesized or audio-free builds. Generate Difficulty Ladder is only offered when the new pack does not already report phrase-level difficulty data.
feedBack's existing GP import pipeline (documented in a data-loss audit predating this
plugin) silently drops sections, beats, tones, capo, key/time signatures, and more.
The .feedpak format (spec ≥1.19.0) can hold all of it; feedpakr's job is to actually
put it there, without needing any changes to the feedBack core.
routes.py— thin FastAPI routes (upload / upload-cover / upload-audio / youtube-audio / autosync-preview /WS build/validate/sloppaks/WS upgrade), following the upload-token / streaming-build pattern used byfeedBack-plugin-musicxml-import.feedpakr_pipeline.py— orchestrates parsing, conversion (via the host'sgp2rs/gp2rs_gpx/song), and all fidelity enrichment (capo, timeline, lyrics, keys, tones, handshapes, drums-as-arrangements, notation, vocal pitch, and optional Chordr-based chord naming).feedpakr_audio.py— MIDI synthesis, GP8 embedded audio extraction, autosync, YouTube fetch, and OGG normalization. Every function degrades to(None, ..., error)rather than raising.feedpakr_lyrics.py— lyrics + vocal pitch, both read from the same GPIF<vocals>XML where available; GP3-5 approximation otherwise.feedpakr_tones.py— tone-change extraction for both source families.feedpakr_keys.py— key/scale extraction (keys.json) for both source families.feedpakr_handshapes.py— hand-shape derivation from chord data.feedpakr_notation.py— opt-in GPIF melodic-track notation sidecars.feedpakr_upgrade.py— the.sloppak→.feedpakbatch upgrade path (see above). Reads via the host'ssloppak.load_manifest/read_member_byteswhen available, with a small standalone YAML/zip fallback so its tests don't require the host lib either.feedpakr_pack.py— manifest assembly and.feedpakzip writing. Pure dicts, no pyguitarpro dependency — easy to unit test.feedpakr_validate.py— validates every payload shape (manifest, arrangement, song_timeline, keys, vocal_pitch, drum_tab, notation) against the vendored feedpak-spec schemas, always returning a report rather than raising. The validation route accepts a DLC-relative.feedpakpath for checking existing packs without rebuilding them.
Handoffs to song-preview/stem-splitter/difficulty_ladder are invoked by
screen.js (fprProbeHandoffs / fprRunHandoff). Optional Chordr naming runs
during the build through Chordr's versioned chart-analysis bridge; it fills only
missing names and reports a warning if the capability is unavailable or fails. The backend exposes
GET /api/plugins/feedpakr/handoffs as a read-only availability probe for server
sibling routes; feedpakr still leaves the actual preview generation, stem
splitting, and difficulty-ladder generation work to the dedicated plugins.
routes.py loads its siblings via context['load_sibling'] (not bare imports) to avoid
the plugin-module-name collisions that mechanism exists to prevent.
feedpakr calls the host's own core helpers (gp2rs/song/sloppak) rather than
vendoring them, so which host can run which feature is declared and checked rather than
assumed:
feedpakr_compat.pyrecords the tested host baseline by exact commit (d876ded00f9254b13bf4a0587c5645a1edaccf67). The host'sVERSIONstring is not a unique identifier — the same0.3.0-alpha.1appears on checkouts both with and withoutsloppak.read_member_bytes()— so the pin stays at commit granularity until a published release uniquely identifies the baseline.- The compatibility matrix separates concerns: each of the five features (GP import, existing-pack reuse, upgrade, duplicate scan, output playback) lists the core modules/helpers it needs, independent of the external binaries (FFmpeg/ffprobe, FluidSynth, yt-dlp) and of the optional sibling plugins (song-preview, stem-splitter, lyrics-sync, difficulty-ladder, Chordr). A missing binary or sibling is not a core-version problem and isn't reported as one.
GET /api/plugins/feedpakr/compat(backend) + a banner (frontend) report each feature as available, degraded (a compatible fallback covers the missing helper — e.g. upgrade/duplicate-scan/lyrics-text on a host withoutread_member_bytes()), or unavailable, with an actionable message.feedpakr_upgradechecks forread_member_bytes()/load_manifest()explicitly and falls back to its own bounded reader rather than assuming an importablesloppakis complete.
gp2rs.list_tracks / gp2rs.convert_file already dispatch to gp2rs_gpx internally
based on file extension, so most of the pipeline is written against gp2rs's uniform
surface. A few things needed format-specific handling because pyguitarpro has no model
for them on the GPIF side at all:
- Capo lives at
Track/Staves/Staff/Properties/Property[@name='CapoFret']/Fretin the raw GPIF XML — nothing in the host reads it, so feedpakr does, viagp2rs_gpx._load_gpif+_gpif_tracks(private but stable — the module's only way to reach a raw<Track>element). - Vocal tracks produce a
<vocals>XML (viagp2rs_gpx.convert_vocal_trackinternally), not the<song>shapesong.parse_arrangementexpects. feedpakr peeks at each output XML's root tag and routes<vocals>files tofeedpakr_lyrics.pyinstead — routing them throughsong.parse_arrangementby mistake was a real bug caught while building this (an empty-named ghost arrangement, no lyrics produced at all; now a regression test intests/test_pipeline.py). The same<vocals>XML also feedsvocal_pitch.jsondirectly (itsnote=attribute is already the sung MIDI pitch) — no need for gp2rs_gpx's separate, much more involved private pitch-sidecar function. - Piano LH/RH pairing (
gp2rs_gpx._find_piano_pairs) can silently drop a track index fromconvert_file's output when it's merged into its RH partner — feedpakr independently computes the same filtered order up front (_output_track_order) so the capo/lyrics/notation routing can still zip() output XMLs to the right source track. <tones>is written but never read:gp2rs_gpx.convert_filealready injects a<tonebase>/<tones>block into GPIF-converted XML (via its own internal_collect_tone_events/_inject_tones) — butsong.parse_arrangementhas no code path that reads those elements back out, so the data was being silently discarded on the way to the wire format.feedpakr_tones.parse_tones_xmlcloses that gap.
Build a real Guitar Pro file through the production pipeline and report whether metadata and playable features survive in the resulting feedpak:
py compare_gp_to_feedpak.py "path/to/song.gp" --jsonUse --audio-mode embedded for GP8 embedded audio or --audio-mode midi for a
synthesized reference stem. The command exits nonzero when an authored field is
missing or only partially preserved; warnings remain visible separately.
python -m pip install -r requirements.txt -r requirements-test.txt \
-r ../pakr/feedBack/requirements.txt
python -m pytest -qThe third -r is the feedBack host's own requirements, and it is not optional:
PyYAML is imported at module scope by feedpakr_pack, which tests/conftest.py
imports unconditionally, so a missing PyYAML fails collection whether or not the
host checkout is present. That -r needs a sibling pakr/feedBack checkout to
exist — pip exits nonzero on a missing requirements file — so a plugin-only
checkout installs PyYAML directly instead and still runs everything that
doesn't need the host.
python -m pytest -ra is the default (pytest.ini sets addopts), so the
short test summary always lists every skip with its reason. The full skip
inventory and the policy for what may skip in CI is in
tests/SKIPS.md.
The release-gating import tests run against fixtures generated in-repo by
tests/portable_fixtures.py — a hand-authored GPIF (GP7/8) .gp and a real
GP4 .gp4 written with pyguitarpro. They need no song data, so they run on
every CI run and cover sections, chord/template consistency, drums, and the
GPIF-vs-GP3-5 format/tuning differences (tests/test_portable_conversion.py).
Tests that lock in behavior against real, copyrighted songs/sloppaks read
them from an optional personal sample library, never from the repo. Point
FEEDPAKR_LOCAL_FIXTURES at it; with no env var the historical
c:\Users\PC\Downloads\Alll path is used if it exists. When it is absent the
dependent tests self-skip with a reason naming the env var, while the portable
suite (and everything else) still runs. A set-but-missing path warns and skips
rather than silently falling back to the historical folder.
tests/test_pipeline.py needs the host's lib/ (for guitarpro, gp2rs,
gp2rs_gpx, song) on sys.path — tests/conftest.py looks for a sibling
pakr/feedBack checkout and self-skips those tests if it isn't found.
.github/workflows/tests.yml checks out the host and installs ffmpeg, then
sets FEEDPAKR_REQUIRE=host,ffmpeg. tests/conftest.py::pytest_configure
aborts the session if any required capability is missing, and
pytest_sessionfinish fails the run if a required regression still skipped
for an unexpected reason — so a broken provisioning step fails loudly instead
of producing a green run full of skips. Locally (FEEDPAKR_REQUIRE unset) the
same tests skip cleanly when the host lib or ffmpeg isn't installed.
Two mixdown tests run a real ffmpeg amix
(test_extract_pack_assets_separated_stems_builds_mixdown_with_valid_audio,
..._corrupt_audio_errors); the same-name-shape bookkeeping test
(test_extract_pack_assets_separated_stems_no_full_mix) mocks the mixdown
boundary and so runs everywhere.
Notable regression tests:
test_build_feedpak_extracts_all_16_sections— "Money (J).gp5" was documented as losing all 16 of its section markers through the legacy sloppak pipeline; must extract all 16.test_build_feedpak_gpif_vocal_track_becomes_lyrics_not_arrangement— locks in the<vocals>-routing fix described above, against the GP8 companion file.test_gpif_capo_lookup_reads_capo_fret— capo extraction against a real capo'd fixture, not just an empty-dict check.test_build_feedpak_gp345_drums_become_type_drums_arrangement,test_build_feedpak_handshapes_derived_from_chords,test_build_feedpak_gpif_keys_track_gets_notation,test_build_feedpak_gpif_vocal_track_produces_vocal_pitch— Phase 3 fidelity features, each checked against real converted output, not just unit-level logic.tests/test_portable_conversion.py— the portable, always-on counterparts of the real-tab section/chord checks plus GP4 drums-as-drum-tab.tests/test_upgrade.py::test_real_fixture_batch_all_fully_valid— every sample.sloppakthis project has upgrades to a fully spec-valid.feedpak(21/21).
MIT