Initial Commit
This commit is contained in:
105
packages/media/audio-ir/audio-ir-loader/src/loader/loader.md
Normal file
105
packages/media/audio-ir/audio-ir-loader/src/loader/loader.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# `loader.ts` — `createIrLoader` pipeline orchestration
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module is the entry point for the package. `createIrLoader(options)`
|
||||
returns a typed `IrLoader` that composes the IR family pipeline:
|
||||
`IrRegistry.get(id)` → `cache.get(id)` (on hit, fast path) →
|
||||
`StorageUriResolver.read(uri)` → `decoders[ext](bytes)` →
|
||||
`validateAndFinalize(entry, decoded)` → `cache.put` →
|
||||
return frozen `ImpulseResponse`. Every layer's errors pass through
|
||||
verbatim — the loader only owns the six `AUDIO_IR_LOADER.*` codes from
|
||||
its own contract.
|
||||
|
||||
## Features
|
||||
|
||||
- Zod-validated options at construction (`createIrLoader` throws
|
||||
`ZodError` on misuse).
|
||||
- Decoder keys normalised to lowercase so callers can write
|
||||
`{ WAV: decodeWav }` and have it work.
|
||||
- Optional in-process LRU cache controlled by `options.cache.maxBytes`.
|
||||
- `AbortSignal` threading: short-circuits a pre-aborted signal,
|
||||
passes the signal through to both `resolver.read` and the decoder.
|
||||
- Real OpenTelemetry instrumentation: span + duration histogram + bytes /
|
||||
frames / cache / error counters.
|
||||
- Bounded-concurrency `preload(ids, { concurrency })` with aggregate
|
||||
failure reporting (delegated to `preload.ts`).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createStorageUriResolver } from "@suaveplan/storage-uri";
|
||||
import { bundleUriPlugin } from "@suaveplan/storage-uri-plugin-bundle";
|
||||
import { decodeWav } from "@suaveplan/audio-decode-wav";
|
||||
import { createIrLoader } from "@suaveplan/audio-ir-loader";
|
||||
|
||||
const resolver = createStorageUriResolver([
|
||||
bundleUriPlugin({ packageSlug: "club", rootUrl: new URL("file:///opt/ir/club/") }),
|
||||
]);
|
||||
const loader = createIrLoader({
|
||||
registry,
|
||||
resolver,
|
||||
decoders: { wav: decodeWav },
|
||||
});
|
||||
const ir = await loader.load("club-club1");
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
The loader composes well with an LRU cache and with bounded-concurrency
|
||||
preload for app bootstrap:
|
||||
|
||||
```ts
|
||||
const loader = createIrLoader({
|
||||
registry,
|
||||
resolver,
|
||||
decoders: { wav: decodeWav, flac: decodeFlac },
|
||||
cache: { maxBytes: 64 * 1024 * 1024 },
|
||||
});
|
||||
const controller = new AbortController();
|
||||
const [a, b, c] = await loader.preload(
|
||||
["club-a", "club-b", "club-c"],
|
||||
{ signal: controller.signal, concurrency: 2 }
|
||||
);
|
||||
// Later, free a specific IR's slot:
|
||||
loader.evict("club-c");
|
||||
console.log(loader.cacheStats());
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `createIrLoader(options): IrLoader`
|
||||
|
||||
- `options.registry: IrRegistry`
|
||||
- `options.resolver: StorageUriResolver`
|
||||
- `options.decoders: Record<string, AudioDecoder>` — must be non-empty;
|
||||
keys are case-insensitive.
|
||||
- `options.cache?: { maxBytes: number }` — positive integer; enables LRU.
|
||||
|
||||
### `IrLoader`
|
||||
|
||||
- `load(id, { signal? }?): Promise<ImpulseResponse>`
|
||||
- `preload(ids, { signal?, concurrency? }?): Promise<readonly ImpulseResponse[]>`
|
||||
- `evict(id): boolean`
|
||||
- `cacheStats(): { entries, bytes, hits, misses }`
|
||||
|
||||
### `AudioDecoder`
|
||||
|
||||
`(bytes: Uint8Array, opts?: { signal?: AbortSignal }) => Promise<DecodedAudioLike>`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The OTel span for every `load` is started before the cache hit-check so
|
||||
even cache hits emit a span; the `outcome=cache_hit` histogram label
|
||||
distinguishes those from the `outcome=success` "did real work" path. The
|
||||
`SuaveplanError`-cast on errors lets the loader's `errors_total` counter
|
||||
attribute the throw to its originating namespace (`AUDIO_IR.*`,
|
||||
`STORAGE_URI.*`, `BUNDLE_URI.*`, `FALLBACK_URI.*`, `AUDIO_DECODE.<FMT>.*`,
|
||||
or `AUDIO_IR_LOADER.*`) rather than collapsing every miss into a single
|
||||
opaque bucket. The pre-abort check throws synchronously before any
|
||||
registry / resolver / decoder call, satisfying the spec's "never invoked
|
||||
when signal is already aborted" requirement.
|
||||
Reference in New Issue
Block a user