Architecture Overview
Architecture Overview
Guitar Prometheus is a single-binary desktop app (JUCE 8) that imports Guitar
Pro scores from MusicXML, native .gp (Guitar Pro 7 / 8), and native .gpx
(Guitar Pro 6) sources, and produces MIDI files with keyswitches injected
so VST instruments render guitar techniques realistically.
Import Pipeline
The import side fans into the same shared GPIF reader for both native Guitar Pro variants, then funnels every format through a common intermediate score and the final builder.
---
config:
theme: base
flowchart:
curve: linear
themeVariables:
lineColor: "#c8a0b0"
primaryBorderColor: "#c8a0b0"
edgeLabelBackground: "#808080"
textColor: "#777777"
titleColor: "#777777"
---
flowchart LR
subgraph Sources["Source files"]
direction TB
MX["MusicXML<br/>(.musicxml / .xml)"]:::blue
GP["GP7 / 8 native<br/>(.gp)"]:::green
GPX["GP6 native<br/>(.gpx)"]:::amber
end
Dispatcher["ScoreImporter<br/>dispatch"]:::slate
subgraph Parsers["Format parsers"]
direction TB
MXP["MusicXmlParser"]:::blue
GPP["GpFileParser<br/>(ZIP container)"]:::green
GPXP["GpxFileParser<br/>(BCFZ / BCFS)"]:::amber
end
Gpif["GpifReader<br/>(shared GPIF XML)"]:::teal
IR["IntermediateScore"]:::purple
Builder["ScoreBuilder::build"]:::rose
Score[("Score<br/>(immutable)")]:::slate
MX --> Dispatcher
GP --> Dispatcher
GPX --> Dispatcher
Dispatcher --> MXP
Dispatcher --> GPP
Dispatcher --> GPXP
MXP --> IR
GPP --> Gpif
GPXP --> Gpif
Gpif --> IR
IR --> Builder
Builder --> Score
classDef blue fill:#4A90D9,stroke:#3570B0
classDef green fill:#4EA882,stroke:#308862
classDef amber fill:#C8A040,stroke:#A88020
classDef teal fill:#48A8A0,stroke:#288888
classDef purple fill:#7B68EE,stroke:#5B48CE
classDef rose fill:#C86888,stroke:#A84868
classDef slate fill:#808898,stroke:#606878
style Sources fill:#88888814,stroke:#888888
style Parsers fill:#4A90D914,stroke:#4A90D9
linkStyle 0 stroke:#4A90D9
linkStyle 1 stroke:#4EA882
linkStyle 2 stroke:#C8A040
linkStyle 3 stroke:#4A90D9
linkStyle 4 stroke:#4EA882
linkStyle 5 stroke:#C8A040
linkStyle 6 stroke:#4A90D9
linkStyle 7 stroke:#4EA882
linkStyle 8 stroke:#C8A040
linkStyle 9 stroke:#48A8A0
linkStyle 10 stroke:#7B68EE
linkStyle 11 stroke:#C86888
IScoreImporter is the abstract entry point; ScoreImporter::importFile
dispatches by file extension. Every concrete parser emits an
IntermediateScore (parser-internal IR with public fields), which
ScoreBuilder::build finalises into the immutable Score — minting per-Part
UUIDs and deriving cached tempo scalars from the score-wide timeline.
Export Pipeline
Once a Score exists, the export side composes pure stages over per-part event streams.
---
config:
theme: base
flowchart:
curve: linear
themeVariables:
lineColor: "#c8a0b0"
primaryBorderColor: "#c8a0b0"
edgeLabelBackground: "#808080"
textColor: "#777777"
titleColor: "#777777"
---
flowchart LR
Score[("Score")]:::slate
Profile[("Profile<br/>per part")]:::purple
ExportCfg[("ExportConfig")]:::amber
Inject["KeyswitchInjector<br/>::inject"]:::blue
Transpose["OctaveTransposer<br/>::applyOctaveOffsets"]:::green
Humanize["Humanizer<br/>::humanize"]:::rose
Write["MidiWriter<br/>::writeMerged /<br/>::writePerTrack"]:::teal
Mid[("*.mid")]:::slate
Score --> Inject
Profile --> Inject
Inject --> Transpose
ExportCfg --> Transpose
Transpose --> Humanize
ExportCfg --> Humanize
Humanize --> Write
Write --> Mid
classDef blue fill:#4A90D9,stroke:#3570B0
classDef green fill:#4EA882,stroke:#308862
classDef amber fill:#C8A040,stroke:#A88020
classDef teal fill:#48A8A0,stroke:#288888
classDef purple fill:#7B68EE,stroke:#5B48CE
classDef rose fill:#C86888,stroke:#A84868
classDef slate fill:#808898,stroke:#606878
linkStyle 0 stroke:#808898
linkStyle 1 stroke:#7B68EE
linkStyle 2 stroke:#4A90D9
linkStyle 3 stroke:#C8A040
linkStyle 4 stroke:#4EA882
linkStyle 5 stroke:#C8A040
linkStyle 6 stroke:#C86888
linkStyle 7 stroke:#48A8A0
Each stage is a pure function with explicit inputs and outputs; the orchestrator
assemblePipelineRun threads per-part state through them. Profile humanizer
settings overlay onto explicit per-part humanizer overrides before
Humanizer::humanize runs.
Core Domains
Parser
Source: src/public/parser/, src/private/parser/
MusicXmlParserreads MusicXML via pugixml. Two walker classes split the ~490-line walk:PartWalkerowns measure-spanning state (divisions, time / key, tempo, transpose, open spans) whileMeasureBodyWalkerowns the within-measure cursor and span emission.GpFileParseropens.gpas a ZIP, extractsContent/score.gpif, and hands it toGpifReader.GpxFileParserdecodes the.gpxBCFZ-compressed BCFS filesystem viaGpxFileSystem, extractsscore.gpif, thenGpifReader.GpifReaderwalks the shared GPIF XML schema. Three walker classes split the ~480-line read:GpifTempoReaderfor the<MasterTrack><Automations>timeline,GpifTrackReaderfor per-Track metadata + master-bar orchestration,GpifBeatWalkerfor the voice / beat / note traversal.
Detection rules cover standard MusicXML elements (<palm-mute/>, <tap/>),
articulation containers, Guitar Pro processing instructions
(<?GP <root>…</root>?>), the F9.5 <other-technical type="X"/> escape hatch,
the GP slide-flag bitfield (including the 0x40 / 0x80 pick-scrape encoding),
and the bit-0x01 AutoSlide variant introduced in Phase 22.
Phase 24 added ParserUtilities::xmlDepthWithin — every loaded document is
checked against a 256-level nesting cap before any walker touches it.
Pathologically nested payloads surface a Diagnostic instead of blowing the
stack during recursive descent.
Profiles
Source: src/public/profile/
A Profile is the VST keyswitch contract: per-technique keyswitch notes,
per-articulation keyswitch notes, articulation modifiers (velocity boost +
length multiplier), per-dynamic velocity, an optional drum-piece map, and
optional humanizer settings (Phase 19).
Profiles serialise to JSON and live in the user’s persistence directory
(Util::getPersistenceRoot()). The ProfileStore handles load / save /
delete plus import / export to file, clipboard, and zip archives.
Project
Source: src/public/project/
A Project is a single user session: the embedded source payload, per-track
profile bindings (scorePartId → profileUuid), tempo overrides, export config
(humanizer + octave offset per part, stack pick direction), and a list of
merged-in supplementary sources. Persisted as .gpproj JSON via
ProjectStore.
Source format is captured in the project (SourceFormat::MusicXml | Gp | Gpx)
so the project can re-materialise its score across re-opens — Phase 23 wired
ScoreImporter::importMemory so all three formats round-trip through the
project file. The schema migration framework (F9.1) lifts older .gpproj
files to the current schema version on load.
Phase 22 added a BendPoint / BendCurve model on Engraving (populated
by the GP parser from the BendOriginValue / BendMiddleValue /
BendDestinationValue properties), and an OctaveCorrectionMode enum
(Manual / Inferred) on ExportConfig. When Inferred is set,
OctaveTransposer::inferOctaveOffset derives the per-part shift from the
part’s tuning plus each note’s string / fret tags.
Pipeline (MIDI emission)
Source: src/public/midi/
Three stages composed by assemblePipelineRun:
KeyswitchInjector::injectproduces per-partMidiEventstreams. Iterates the score, emits NoteOn / NoteOff for every Engraving, then inserts keyswitch NoteOn / NoteOff pairs around runs of techniques (sustained), individual notes (per-note), or span ranges.OctaveTransposer::applyOctaveOffsetsshifts each part’s MIDI pitches by the resolved per-part octave offset (override > profile default > 0).Humanizer::humanizeadds Gaussian-jittered velocity + uniform timing noise. Per-part overrides win over the profile’s humanizer settings; when neither is set the engine is a no-op.
The result is fed to MidiWriter::writePerTrack (one Format 0 file per part)
or MidiWriter::writeMerged (single Format 1 file with a meta track plus one
track per part).
UI shell
Source: src/private/main/
JUCE-native UI rooted at MainWindow. Owns an AppController that holds the
active Project + Score and broadcasts changes to listeners. Components:
TrackListPanel (per-part profile picker), ProfileEditorPanel (per-profile
editing, including the Phase 19 humanizer controls), TempoMapPanel
(per-measure / sub-beat tempo overrides), ExportDialog (export config + run),
and UnknownTechniquesBanner (warns when the parser sees something it doesn’t
know how to handle).
Cross-platform persistence
Source: src/public/util/PersistenceRoot.h
Util::getPersistenceRoot() returns the directory profiles and prefs live in:
- Windows: the executable’s parent directory (portable mode — the app ships as an extracted ZIP and stores config next to itself).
- macOS / Linux:
~/Library/Application Support/Guitar Prometheus(or the XDG equivalent).
Key Design Principles
- Immutability —
Profile,Project,Score, and the engraving model are constructor-only. Mutators rebuild rather than edit in place. - Constructor-sink — collections are passed by value and
std::moved into the field, so call sites read top-to-bottom and never share state with the model. - Pure functional core — parser, injector, transposer, humanizer, writer are pure functions taking inputs by const-ref and returning results. The UI layer is the only one with mutable state.
- Schema-driven persistence —
ProfileandProjectJSON formats have versioned schemas. Old documents migrate forward via the F9.1 framework; the schema literal in code matches a resource file byte-for-byte (enforced by tests). - Out-parameter purge — Phase 19 removed multi-out-parameter functions in favour of return-by-value structs. New code follows the same pattern; accumulator helpers (e.g. walk-and-add) are the only exception.