54 lines
3.4 KiB
Markdown
54 lines
3.4 KiB
Markdown
# Add `@suaveplan/audio-ir-core`
|
||
|
||
**Package**: `@suaveplan/audio-ir-core`
|
||
**Status**: PROPOSED
|
||
**Type**: new-package
|
||
|
||
## Why
|
||
|
||
The IR ecosystem (`audio-ir-loader`, `audio-ir-{mic,speaker,room}-plugin-<name>` packages) needs a single foundation that owns the IR contract, manifest schema, and in-memory registry. Without it, every IR content package would reinvent the entry shape, the registry collision rule, and the ID-derivation convention — guaranteed drift. This package is the foundation every sibling depends on.
|
||
|
||
## What changes
|
||
|
||
- ADDED: `ImpulseResponse` type — `{ id: string; channelData: readonly Float32Array[]; channels: number; sampleRate: number; lengthFrames: number; durationSec: number; gainDb: number; family: "mic" | "speaker" | "room"; manifest: IrManifestEntry }`. Frozen, validated.
|
||
- ADDED: `IrManifestEntry` Zod schema — `{ id, family, uri, channels, sampleRate, gainDb, license, source }`. `id` matches `/^[a-z][a-z0-9-]*$/` (no consecutive `-`, starts with a letter, length 1–128). `uri` is any non-empty string (deferred validation to `storage-uri` at load time).
|
||
- ADDED: `IrManifest` Zod schema — `{ packageSlug: <slug grammar>, family: "mic" | "speaker" | "room", entries: readonly IrManifestEntry[] }`. Every `entry.id` SHALL begin with `<packageSlug>-` (enforced by schema refinement). `entry.family` SHALL equal manifest-level `family`.
|
||
- ADDED: `IrRegistry` — in-memory `Map`-backed registry with `register(entry)`, `has(id)`, `get(id)`, `list({ family? })`, `clear()`. `register` throws `AUDIO_IR.DUPLICATE_ID` when an ID is registered twice.
|
||
- ADDED: `createIrRegistry()` factory.
|
||
- ADDED: `AUDIO_IR.*` codes via `defineCodes`: `DUPLICATE_ID`, `NOT_REGISTERED`, `INVALID_MANIFEST`, `ID_PREFIX_MISMATCH`, `FAMILY_MISMATCH`.
|
||
- ADDED: OTel — `audio_ir.registry_size{family}` observable gauge; `audio_ir.registrations_total{family, outcome}` counter; `audio_ir.lookups_total{family, outcome}` counter; `audio_ir.errors_total{code}` counter.
|
||
|
||
## Out of scope
|
||
|
||
Nothing functional within the foundation's scope. The following are explicitly NOT here, because they belong to siblings:
|
||
|
||
- Loading + decoding — `audio-ir-loader` owns the resolver-driven walk.
|
||
- Concrete IR data — `audio-ir-{mic,speaker,room}-plugin-<name>` packages own the files and the per-package `register(registry, resolver)` call.
|
||
|
||
## Source mapping
|
||
|
||
Genesis-original. No upstream `enterprise/audio-ir-core.md` exists.
|
||
|
||
### Requirements with no source line
|
||
|
||
| Requirement | Justification |
|
||
|---|---|
|
||
| every spec requirement | Genesis-original foundation; no upstream baseline |
|
||
|
||
## Verification approach
|
||
|
||
- `src/types/manifest-entry.test.ts` — Zod parsing + refinement (id grammar, id-prefix rule, family rule).
|
||
- `src/types/manifest.test.ts` — manifest-level Zod validation.
|
||
- `src/registry/registry.test.ts` — register / has / get / list / clear semantics; `DUPLICATE_ID` throw.
|
||
- `src/registry/registry.spec-conformance.test.ts` — strict-mode behaviors, lookup invariants under concurrency.
|
||
- `src/otel/otel.test.ts` — counter + gauge assertions with real in-memory `MeterProvider`.
|
||
- `src/error-codes/error-codes.test.ts` — exhaustion.
|
||
|
||
## Impact
|
||
|
||
### Affected dependents
|
||
|
||
No dependents yet (the package is brand new). Verified by `bunx turbo ls --filter=...@suaveplan/audio-ir-core` returning only the package itself.
|
||
|
||
The first downstream consumers will be `@suaveplan/audio-ir-loader` and every `@suaveplan/audio-ir-{family}-plugin-<name>` package, each landing in its own change.
|