Compiler Reference
The Gen compiler is written in Rust and transforms .gen source files into MusicXML.
Pipeline
Source → Lexer → Tokens → Parser → AST → Semantic Analysis → MusicXML- Lexer tokenizes the input into notes, modifiers, and metadata
- Parser builds an Abstract Syntax Tree (AST)
- Semantic Analysis validates measure durations and structure
- MusicXML Generator outputs valid MusicXML 4.0
Public API
Basic Compilation
use gen::compile;
let source = r#"---title: My Song---C D E FG A B ^C"#;
let musicxml = compile(source)?;API Functions
| Function | Description |
|---|---|
compile(source) | Full compilation with validation |
compile_unchecked(source) | Skip validation (for incomplete scores) |
compile_with_options(source, clef, octave, transposition) | Custom clef and transposition |
compile_with_mod_points(source, clef, octave, group, key) | Instrument-specific mod points |
compile_to_tab(source) | Guitar TAB MusicXML |
compile_to_ascii_tab(source) | ASCII tablature for terminal |
compile_to_tab_data(source) | Structured TAB data |
compile_with_display_pitch(source, mode) | Concert or transposed display |
compile()
Standard compilation with full validation:
use gen::compile;
let musicxml = compile("C D E F")?;Validates:
- Measure durations match time signature
- Repeat markers are matched
- Endings follow correct structure
compile_unchecked()
Skip validation for incomplete or in-progress scores:
use gen::compile_unchecked;
// Incomplete measure (3 beats in 4/4)let musicxml = compile_unchecked("C D E")?;compile_with_options()
Custom clef, octave shift, and transposition:
use gen::{compile_with_options, Transposition};
// Bass clef, down one octave, for Bb instrumentlet musicxml = compile_with_options( "C D E F", "bass", // clef: "treble", "bass", or "tab" -1, // octave_shift: -2 to +2 Transposition::for_key("Bb"))?;compile_with_mod_points()
For instrument-specific octave adjustments:
use gen::compile_with_mod_points;
let source = "C D @Eb:^ E F";
// Compile for Eb alto saxlet musicxml = compile_with_mod_points( source, "treble", 0, Some("eb"), // instrument_group: "eb", "bb", "f", or None Some("Eb") // transpose_key: "C", "Bb", "Eb", "F")?;compile_to_tab()
Guitar tablature as MusicXML:
use gen::compile_to_tab;
let musicxml = compile_to_tab("C D E F")?;// Outputs MusicXML with TAB clef and string/fret notationcompile_to_ascii_tab()
ASCII tablature for terminal display:
use gen::compile_to_ascii_tab;
let tab = compile_to_ascii_tab("C D E F")?;println!("{}", tab);// e|--------|// B|--------|// G|--------|// D|-----2--|// A|--3-----|// E|--------|compile_to_tab_data()
Structured TAB data for programmatic access:
use gen::compile_to_tab_data;
let tab = compile_to_tab_data("C D E F")?;
for measure in &tab.measures { for note in &measure.notes { if let Some(pos) = note.position { println!("String {}, Fret {}", pos.string, pos.fret); } }}compile_with_display_pitch()
Control concert vs. transposed display:
use gen::compile_with_display_pitch;
// Concert pitch (notes shown as they sound)let musicxml = compile_with_display_pitch(source, "concert")?;
// Transposed (notes shown as player reads)let musicxml = compile_with_display_pitch(source, "transposed")?;Modules
lexer.rs
Tokenizes source into a stream of tokens:
| Token | Description |
|---|---|
NoteName | Note letters A-G |
Rest | Rest indicator $ |
Accidental | Sharp #, flat b, natural % |
OctaveModifier | ^, ^^, _, __ |
RhythmModifier | /, //, ///, p, o |
Dot | Dotted note * |
Tie | Tie - |
Slur | Slur ~ |
BracketOpen/Close | Group brackets [] |
ParenOpen/Close | Chord parentheses () |
PartDef | Part definition @part: |
PartContinue | Continuation \\ |
KeyChange | Key change @key: |
ModPoint | Instrument modifier @Eb:^ |
parser.rs
Converts tokens into AST. Handles:
- YAML metadata extraction
- Single-part and multi-part scores
- Note grouping and tuplets
- Repeat and ending structures
ast.rs
Core type definitions:
Score { metadata: Metadata, measures: Vec<Measure>, // Single-part parts: Vec<Part>, // Multi-part}
Metadata { title: Option<String>, composer: Option<String>, time_signature: TimeSignature, key_signature: KeySignature, tempo: Option<Tempo>, swing: Option<Swing>, written_notation: WrittenNotation,}
Measure { elements: Vec<Element>, repeat_start: bool, repeat_end: bool, ending: Option<Ending>, is_pickup: bool,}
Element { Note { ... }, Rest { ... }, Chord { ... }, // Simultaneous notes ChordSymbol { ... }, // Lead sheet annotation}semantic.rs
Validates the parsed score:
- Measure duration: Total duration matches time signature
- Pickup measures: Skipped for
@pickupannotation - Multi-part alignment: All parts have same number of measures
musicxml.rs
Generates MusicXML 4.0 output:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"><score-partwise version="4.0"> <work><work-title>...</work-title></work> <part-list>...</part-list> <part id="P1"> <measure number="1">...</measure> </part></score-partwise>tab.rs
Guitar tablature generation:
- 6-string standard tuning (E A D G B E)
- Automatic fingering algorithm
- Prefers open strings
- Optimizes hand position
Duration System
Internal divisions: 840 per quarter note (LCM for tuplets)
| Duration | Divisions |
|---|---|
| Whole | 3360 |
| Half | 1680 |
| Quarter | 840 |
| Eighth | 420 |
| Sixteenth | 210 |
| 32nd | 105 |
Tuplet divisions calculated as: base_duration * normal_count / actual_count
Octave Mapping
| Gen | MusicXML Octave |
|---|---|
__ | 2 |
_ | 3 |
| (none) | 4 |
^ | 5 |
^^ | 6 |
Middle C = octave 4 in both systems.
Error Types
pub enum GenError { LexerError { message, line, column }, ParseError { message, line, column }, SemanticError { message, line }, MetadataError(String),}All errors include source location for debugging.
Transposition
Supported instrument keys:
| Key | Semitones | Instruments |
|---|---|---|
| C | 0 | Flute, Oboe, Trombone, etc. |
| Bb | -2 | Trumpet, Clarinet, Tenor Sax |
| Eb | -9 | Alto Sax, Baritone Sax |
| F | -7 | French Horn |
Building
cd packages/gen-compilercargo build --releaseTesting
cargo testRun specific test:
cargo test test_triplets