3.9 KiB
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 universalAudioDecodercontract. - ADDED:
createWavStreamDecoder(options?: AudioDecodeOptions): WavStreamDecoder— chunked decode forReadableStream<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 — iftargetSampleRateis supplied and differs, decode throwsAUDIO_DECODE.WAV.RESAMPLE_UNSUPPORTEDso callers know to add a resampler step. - ADDED:
AUDIO_DECODE.WAV.*codes viadefineCodes:INVALID_INPUT,UNSUPPORTED_FORMAT,CORRUPT_HEADER,TRUNCATED_BODY,ABORTED,RESAMPLE_UNSUPPORTED,STREAM_INVARIANT_VIOLATION. - ADDED: OTel spans on
@suaveplan/audio-decode-wavtracer; countersaudio_decode_wav.bytes_in_total,audio_decode_wav.frames_out_total,audio_decode_wav.errors_total{code}; histogramaudio_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-resamplepackage; this package surfacesRESAMPLE_UNSUPPORTEDso callers know to insert that step. - Non-WAV formats — sibling packages
audio-decode-{flac,mp3,opus,…}will wrap their respective upstream decoders behind the sameAudioDecodercontract. - The format-dispatching
audio-decodeumbrella — 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— coversdecodeWavhappy 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— coverscreateWavStreamDecoderchunk feed, flush, free,STREAM_INVARIANT_VIOLATIONwhen chunks arrive afterfree().src/otel/otel.test.ts— covers span emission, counter increments, histogram observations.src/error-codes/error-codes.test.ts— exhaustion check: everyAUDIO_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.