Input: TabInput. Output: ArrangementSet (opaque handle).
TabInput
────────
input: String │ tuning_name: String │ guitar_num_frets, guitar_capo: u8
│ │ │
▼ ▼ │
parse_lines parse_tuning │
│ │ │
│ ▼ │
│ [i8; 6] │
│ │ │
│ ▼ │
│ create_string_tuning_offset │
│ │ │
│ ▼ │
│ BTreeMap<StringNumber, Pitch> │
│ │ │
│ └────────┬───────────────┘
│ ▼
│ Guitar::new
│ │
│ ▼
│ Guitar
│ │
▼ │ num_arrangements: u8 lifted to NumArrangements at the boundary
Vec<Line<BeatVec<Pitch>>> │ difficulty_weights: Option<DifficultyWeightsInput> lifted to DifficultyWeights at the boundary
│ │ max_fret_span_filter: Option<u8>
│ │ │
└──────────────┬────────────┴────────────────────────┘
▼
create_arrangements
│
▼
Vec<Arrangement>
─────────────────────────────────────────────────
lines : Vec<Line<BeatVec<PitchFingering>>>
difficulty : f64
max_fret_span: u8
│
▼
ArrangementSet (handle)
────────────────────────
arrangements : Vec<Arrangement>
guitar : Guitar
normalized_input : Vec<NormalizedBeat>
per-arrangement reach: set.render(i, width, padding, playback) -> String
set.max_fret_span(i) -> u8
set.difficulty(i) -> f64
parse_linesis a public re-export from the crate root.parse_tuningandcreate_string_tuning_offsetare crate-internal stages, not part of the stable public API; they are surfaced only through the#[doc(hidden)]__bench_internalsmodule, for benchmarks.
Vec<Line<BeatVec<Pitch>>> <- parser output
| | +- Pitch (C0..B9, one semitone each)
| +- BeatVec<T> = Vec<T> (one beat's worth)
+- Line<T> = Playable(T) | Rest | MeasureBreak
Vec<Line<BeatVec<PitchFingering>>> <- arrangement output
| | +- PitchFingering { pitch, string_number, fret }
| +- BeatVec<T> = Vec<T>
+- Line<T> = Playable(T) | Rest | MeasureBreak
Line<T>has the same shape in both stages. Only the leaf insidePlayablechanges:Pitchafter parsing,PitchFingeringafter arranging.
NormalizedBeat <- ArrangementSet.normalizedInput element
{ kind: "playable", pitches: string[] }
| { kind: "rest" }
| { kind: "measureBreak" }
TabError <- thrown by generate_arrangements (JS: generateArrangements)
kind: "parse" + errors: ParseError[]
kind: "inputTooManyLines" + max: number
kind: "numFretsTooHigh" + numFrets: number, max: number
kind: "capoTooHigh" + capo: number, max: number
kind: "capoExceedsFrets" + capo: number, numFrets: number
kind: "stringNumberOutOfRange" + value: number, max: number (lower-level Rust API only)
kind: "openPitchOutOfRange" + string: number, semitones: number (lower-level Rust API only)
kind: "fretRangeExceedsPitchRange" + openPitch: string, playableFrets: number (lower-level Rust API only)
kind: "unplayablePitches" + pitches: UnplayablePitch[]
kind: "noArrangementsFound"
kind: "numArrangementsOutOfRange" + value: number, max: number
kind: "tuningNameUnknown" + value: string
kind: "indexOutOfBounds" + index: number, len: number
kind: "renderWidthTooSmall" + width: number, min: number (thrown by ArrangementSet.render, not generateArrangements)
ParseError
line: number
text: string
TuningName <- enum returned by getTuningNames()
"openG" | "openD" | "c6" | "dsus4" | "dropD"
| "dropC" | "openC" | "dropB" | "openE"
The entry points are:
generate_arrangements(tab_input: TabInput) -> Result<ArrangementSet, TabError>-- entry point for both Rust and WASM callers (JS name:generateArrangements). Validates, builds the guitar, runs the pathfinder, returns the opaque handle.getTuningNames() -> TuningName[]-- enumerates the supported tuning presets, typed for JS via tsify.
ArrangementSet is a #[wasm_bindgen] opaque handle, not a serde-cloned value, so JS callers own the underlying allocation. Release it with set.free() when done, or use using set = generateArrangements(input) on TS 5.2+ runtimes that wire [Symbol.dispose] to free(). Relying on FinalizationRegistry alone leaks the handle on runtimes whose GC isn't prompt.