# 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` — whole-buffer decode behind the universal `AudioDecoder` contract. - ADDED: `createWavStreamDecoder(options?: AudioDecodeOptions): WavStreamDecoder` — chunked decode for `ReadableStream` 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/.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`.