52 lines
3.9 KiB
Markdown
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`.
|