3.6 KiB
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 insidedefaultModuleLoader. - Injectable
moduleLoaderandfetchImplfor hermetic tests. - Magic-byte sniff (
RIFF…sfbkorRIFF…DLS) — rejects non-SoundFont inputs withUNSUPPORTED_FORMATbefore invoking the parser. AbortSignalpropagation to the fetch.- Returns parsed
SoundBankLike+ resolvedSpessaSynthModuleLike+ 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
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
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<LoadedSoundFont>.LoadSoundFontOptions = { soundFontUrl?, soundFontBytes?, signal?, fetchImpl?, moduleLoader? }. Exactly one ofsoundFontUrl/soundFontBytesis 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.