guitar-tab-generator 2.0.0

Generate fingerstyle guitar tabs based on the difficulty of different finger positions
Documentation
# Types and Data Flow

Input: `TabInput`. Output: `ArrangementSet` (opaque handle).

## Pipeline

```
                        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>>>             │              max_fret_span_filter: Option<u8>
          │                           │                        │
          └──────────────┬────────────┴────────────────────────┘
                 create_arrangements
                Vec<Arrangement>
                ─────────────────────────────────────────────────
                 lines        : Vec<Line<BeatVec<PitchFingering>>>
                 difficulty   : i32
                 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) -> i32
```

> `parse_lines` is a public re-export from the crate root. `parse_tuning` and
> `create_string_tuning_offset` are crate-internal stages, not part of the stable public API;
> they are surfaced only through the `#[doc(hidden)]` `__bench_internals` module, for benchmarks.

## Types Up Close

```
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 inside `Playable`
> changes: `Pitch` after parsing, `PitchFingering` after 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.

## Lifecycle (JS only)

`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.