Files
2026-05-27 23:29:53 +01:00

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 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.