Files
suaveplan/openspec/archive/2026-05-19-add-audio-decode-wav/proposal.md
2026-05-27 23:29:53 +01:00

52 lines
3.9 KiB
Markdown

# Add `@suaveplan/audio-decode-wav`
**Package**: `@suaveplan/audio-decode-wav`
**Status**: PROPOSED
**Type**: new-package
## Why
The audio ecosystem (IR loader, sprite packer, future codec family) needs a uniform `AudioDecoder` contract over WAV bytes. `../audio-decode/packages/decode-wav` exists upstream (audiojs `@audio/decode-wav` 1.2.0, MIT) and decodes correctly, but it has no error-code namespace, no Zod validation of inputs, no OTel, no `AbortSignal` threading, and returns its own ad-hoc `{ channelData: Float32Array[], sampleRate: number }` shape with no conformance guarantees about channel-count consistency, sample-rate validity, or sample range. This package wraps it behind a stable contract so every downstream consumer (starting with `audio-ir-loader`) treats WAV the same way it will treat MP3, FLAC, Opus, etc. once those siblings land.
## What changes
- ADDED: `decodeWav(bytes: Uint8Array, options?: AudioDecodeOptions): Promise<DecodedAudio>` — whole-buffer decode behind the universal `AudioDecoder` contract.
- ADDED: `createWavStreamDecoder(options?: AudioDecodeOptions): WavStreamDecoder` — chunked decode for `ReadableStream<Uint8Array>` sources.
- ADDED: `DecodedAudio` — `{ channelData: readonly Float32Array[]; sampleRate: number; channels: number; durationSec: number; bytesDecoded: number }`. Frozen, validated.
- ADDED: `AudioDecodeOptions` — `{ signal?: AbortSignal; targetSampleRate?: number }`. Resampling is out of scope here — if `targetSampleRate` is supplied and differs, decode throws `AUDIO_DECODE.WAV.RESAMPLE_UNSUPPORTED` so callers know to add a resampler step.
- ADDED: `AUDIO_DECODE.WAV.*` codes via `defineCodes`: `INVALID_INPUT`, `UNSUPPORTED_FORMAT`, `CORRUPT_HEADER`, `TRUNCATED_BODY`, `ABORTED`, `RESAMPLE_UNSUPPORTED`, `STREAM_INVARIANT_VIOLATION`.
- ADDED: OTel spans on `@suaveplan/audio-decode-wav` tracer; counters `audio_decode_wav.bytes_in_total`, `audio_decode_wav.frames_out_total`, `audio_decode_wav.errors_total{code}`; histogram `audio_decode_wav.op_duration_ms{outcome}`.
## Out of scope
Nothing functional — package lands at full functional completeness per the no-deferrals rule. The following are explicitly NOT in this package, because they belong to siblings:
- Resampling — belongs in a future `@suaveplan/audio-resample` package; this package surfaces `RESAMPLE_UNSUPPORTED` so callers know to insert that step.
- Non-WAV formats — sibling packages `audio-decode-{flac,mp3,opus,…}` will wrap their respective upstream decoders behind the same `AudioDecoder` contract.
- The format-dispatching `audio-decode` umbrella — separate package, separate change.
## Source mapping
Genesis-original wrapper over the upstream `@audio/decode-wav` library at `../audio-decode/packages/decode-wav/`.
### Requirements with no source line
| Requirement | Justification |
|---|---|
| every spec requirement | Upstream `@audio/decode-wav` has no `enterprise/<pkg>.md` — every requirement is genesis-original wrapper contract |
## Verification approach
- `src/decode-wav/decode-wav.test.ts` — covers `decodeWav` happy path (mono/stereo/16-bit PCM/24-bit PCM/32-bit float fixtures), `INVALID_INPUT`, `CORRUPT_HEADER`, `TRUNCATED_BODY`, `UNSUPPORTED_FORMAT`, `RESAMPLE_UNSUPPORTED`, `ABORTED`.
- `src/stream/stream.test.ts` — covers `createWavStreamDecoder` chunk feed, flush, free, `STREAM_INVARIANT_VIOLATION` when chunks arrive after `free()`.
- `src/otel/otel.test.ts` — covers span emission, counter increments, histogram observations.
- `src/error-codes/error-codes.test.ts` — exhaustion check: every `AUDIO_DECODE.WAV.*` code reachable.
## Impact
### Affected dependents
No dependents yet (the package is brand new). Verified by `bunx turbo ls --filter=...@suaveplan/audio-decode-wav` returning only the package itself.
The first downstream consumer will be `@suaveplan/audio-ir-loader`, landing in a separate change `add-audio-ir-loader`. That change carries its own integration test against `audio-decode-wav`.