# soundfont-loader _(no submodules)_ ## Purpose Lazy entry point for the SF2/SF3/DLS SoundFont pipeline. The loader is the **only** place in the package where `spessasynth_lib` is referenced — the import is a dynamic `import("spessasynth_lib")` so consumers who instantiate the plugin but never call `init()` pay zero bundle cost. The loader also accepts an injectable `moduleLoader` so tests can swap in a deterministic stub without touching the real upstream library. The loader is responsible for three things: (1) lazy-resolving the optional upstream module and surfacing `SPESSASYNTH.MODULE_UNAVAILABLE` if it isn't installed; (2) fetching the SoundFont bytes from a URL or accepting them inline; (3) sniffing the RIFF magic to reject obviously-wrong inputs before handing them to `SoundBankLoader.fromArrayBuffer`. Parse failures from the upstream parser are caught and re-raised as `SPESSASYNTH.SOUNDFONT_PARSE_FAILED` with the underlying error on `cause` so log aggregation can correlate. ## Features - Dynamic `import("spessasynth_lib")` — the only static reference in the codebase, ring-fenced inside `defaultModuleLoader`. - Injectable `moduleLoader` and `fetchImpl` for hermetic tests. - Magic-byte sniff (`RIFF…sfbk` or `RIFF…DLS `) — rejects non-SoundFont inputs with `UNSUPPORTED_FORMAT` before invoking the parser. - `AbortSignal` propagation to the fetch. - Returns parsed `SoundBankLike` + resolved `SpessaSynthModuleLike` + raw bytes so callers can construct the processor and (optionally) persist the bytes. - Strict typed error surface: `SOUNDFONT_LOAD_FAILED`, `UNSUPPORTED_FORMAT`, `SOUNDFONT_PARSE_FAILED`, `MODULE_UNAVAILABLE`. ## Usage: Basic ```ts import { loadSoundFont } from "@suaveplan/audio-fx-plugin-spessasynth/loader"; const { bank, module } = await loadSoundFont({ soundFontUrl: "/assets/gm.sf2", }); const processor = new module.SpessaSynthProcessor(48_000); processor.soundBankManager.addSoundBank(bank, "main", 0); ``` ## Usage: Advanced ```ts import { loadSoundFont } from "@suaveplan/audio-fx-plugin-spessasynth/loader"; const ac = new AbortController(); setTimeout(() => ac.abort(), 5_000); const { bank, bytes, module } = await loadSoundFont({ soundFontUrl: "https://cdn.example.com/orchestra.sf3", signal: ac.signal, fetchImpl: customFetcher, moduleLoader: async () => upstreamStubModule, }); // Persist bytes for offline reuse. await driveStore.put("orchestra.sf3", bytes); ``` ## API Reference - `loadSoundFont(opts: LoadSoundFontOptions): Promise`. - `LoadSoundFontOptions = { soundFontUrl?, soundFontBytes?, signal?, fetchImpl?, moduleLoader? }`. Exactly one of `soundFontUrl` / `soundFontBytes` is required. - `LoadedSoundFont = { bank, module, bytes }`. - `SpessaSynthModuleLike`, `SpessaSynthProcessorLike`, `SoundBankLike` — structural views of the upstream surface used elsewhere in the package. ## Implementation Notes The structural module / processor / bank types are declared inside the package rather than imported from `spessasynth_lib` so the optional dependency does not leak into the static type graph — the only place the upstream is named is inside `defaultModuleLoader`'s dynamic `import()`. The magic sniff is intentionally cheap (12 bytes) and runs before the parser, so consumers that hand the loader random Uint8Arrays (e.g. a corrupt cache entry) get a fast, specific error rather than a deep stack from the parser. The `ArrayBuffer` is sliced from the supplied `Uint8Array.buffer` using the `byteOffset`/`byteLength` window so callers can pass a `Uint8Array` view of a larger buffer without leaking the surrounding bytes into the parser.