first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
# soundfont-loader
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<LoadedSoundFont>`.
- `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.