Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,304 @@
# OpenSpec Change: enterprise-audio-codecs
- **Change ID**: enterprise-audio-codecs
- **Package**: `@suaveplan/audio-codecs`
- **Category**: universal
- **Target path**: `packages/media/audio/audio-codecs/`
- **Status**: PROPOSED
- **Date**: 2026-05-14
- **Design**: `./design.md`
- **Spec delta**: `./specs/audio-codecs/spec.md`
---
## Summary
Deliver `@suaveplan/audio-codecs` on the genesis branch from scratch at enterprise quality. The package provides a universal format decode/encode registry for the genesis monorepo: a single entry point for loading audio from any source (file, URL, buffer, stream) and saving to any supported format. Codecs are registered as plugins and loaded on demand via dynamic `import()`. Format detection uses `@suaveplan/audio-metadata`'s magic-byte detection. Progressive streaming decode fills `AudioDocument.pages` as codec chunks arrive, enabling editing before full decode completes.
---
## Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build. No prior source, no test results, no coverage data.
---
## Available workspace deps on genesis
| Package | Role |
|---|---|
| `@suaveplan/audio-core` (`workspace:^`) | `AudioDocument`, `AudioPage`, `AudioStats` for decoded output |
| `@suaveplan/audio-metadata` (`workspace:^`) | Magic-byte format detection and audio metadata extraction |
| `@suaveplan/streams` (`workspace:^`) | WHATWG `TransformStream` / `ReadableStream` / `WritableStream` primitives |
| `@suaveplan/error` (`workspace:^`) | Base class for `AudioCodecError`; `AUDIO_CODEC.*` codes |
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock` |
| `zod` (`catalog:forms`) | Zod schemas for all public option types |
| `@opentelemetry/api` (`catalog:observability`) | Spans + counters on decode, encode, and detect ops |
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata` (all `catalog:testing`).
---
## Public API
### Core types
```ts
class AudioCodecError extends SuaveplanError
type Source =
| string
| URL
| ArrayBuffer
| Uint8Array
| ReadableStream<Uint8Array>
type Destination =
| string
| WritableStream<Uint8Array>
| ((chunk: Uint8Array) => void)
```
### CodecRegistry
```ts
class CodecRegistry {
register(codec: AudioCodec): void
unregister(name: string): void
detect(bytes: Uint8Array): string | undefined
decode(source: Source, opts?: DecodeOpts): Promise<AudioDocument>
encode(doc: AudioDocument, format: string, opts?: EncodeOpts): Promise<Uint8Array>
decodeStream(source: ReadableStream<Uint8Array>, format: string): ReadableStream<AudioPage>
encodeStream(doc: AudioDocument, format: string): ReadableStream<Uint8Array>
}
```
### AudioCodec interface
```ts
interface AudioCodec {
readonly name: string
readonly extensions: string[]
readonly mimeTypes: string[]
canDecode(bytes: Uint8Array): boolean
decode(bytes: Uint8Array | ArrayBuffer): Promise<DecodeResult>
encode(pcm: Float32Array[], opts: EncodeOpts): Promise<Uint8Array>
createDecoder?(): StreamingDecoder
createEncoder?(): StreamingEncoder
}
interface DecodeResult {
channelData: Float32Array[]
sampleRate: number
format: string
}
interface StreamingDecoder {
write(chunk: Uint8Array): Promise<DecodeResult | null>
flush(): Promise<DecodeResult | null>
}
interface StreamingEncoder {
write(pcm: Float32Array[]): Promise<Uint8Array | null>
flush(): Promise<Uint8Array | null>
}
```
### Universal entry points
```ts
export async function decodeSource(
source: Source,
opts?: DecodeSourceOpts
): Promise<AudioDocument>
interface DecodeSourceOpts {
sampleRate?: number
channels?: number
format?: string
pages?: AudioPage[]
notify?(): void
ondata?(delta: AudioStats): void
storage?: 'memory' | 'persistent'
cache?: PageCache
budget?: number
}
export async function encodeTo(
doc: AudioDocument,
destination: Destination,
format: string,
opts?: EncodeOpts
): Promise<void>
```
### Format detection
```ts
const HEADER_SIZE = 256 * 1024
async function detectSource(source: Source): Promise<{
format: string | undefined
bytes?: Uint8Array
path?: string
reader?: ReadableStreamDefaultReader<Uint8Array>
}>
```
### Progressive streaming decode
```ts
async function progressiveDecode(
source: Source,
opts: DecodeSourceOpts,
registry: CodecRegistry
): Promise<AudioDocument>
```
Key feature: returned `AudioDocument` is usable immediately before decode completes; edits can be chained and extend automatically as more pages arrive.
### Built-in codecs
| Format | Decode | Encode | Streaming | Notes |
|---|---|---|---|---|
| WAV | ✅ | ✅ | ✅ | Native, no deps |
| MP3 | ✅ | ✅ | ✅ | WASM (`@audio/decode-mp3`) |
| FLAC | ✅ | ✅ | ✅ | WASM (`@audio/decode-flac`) |
| Ogg Vorbis | ✅ | ❌ | ✅ | WASM |
| Opus | ✅ | ✅ | ✅ | WASM |
| AAC | ✅ | ❌ | ❌ | WASM |
| AIFF | ✅ | ❌ | ❌ | Native |
| CAF | ✅ | ❌ | ❌ | Native |
| WebM | ✅ | ❌ | ❌ | WASM |
| QOA | ✅ | ✅ | ✅ | WASM |
WASM modules are loaded on demand via dynamic `import()` and declared as `optionalDependencies`.
### Error codes
```ts
type AudioCodecErrorCode =
| 'AUDIO_CODECS.UNKNOWN_FORMAT'
| 'AUDIO_CODECS.DECODE_FAILED'
| 'AUDIO_CODECS.ENCODE_FAILED'
| 'AUDIO_CODECS.STREAM_ERROR'
| 'AUDIO_CODECS.NOT_SUPPORTED'
| 'AUDIO_CODECS.INVALID_SOURCE'
| 'AUDIO_CODECS.BUDGET_EXCEEDED'
const AUDIO_CODEC_ERROR_METADATA: Readonly<Record<AudioCodecErrorCode, CodeMetadata>>
function registerAudioCodecErrorCodes(): void
```
### Zod schemas
```ts
DecodeOptsSchema
EncodeOptsSchema
DecodeSourceOptsSchema
AudioCodecSchema
StreamingDecoderSchema
StreamingEncoderSchema
```
---
## New additions for genesis
1. **Zod boundary validation** — every public options object validated at entry.
2. **OTel instrumentation** — spans on `codec.decode`, `codec.encode`, `codec.detect`; counters for `audio_codec_decode_total`, `audio_codec_encode_total`; histograms for `audio_codec_decode_duration_ms`, `audio_codec_encode_duration_ms`.
3. **Memory budget enforcement** — `budget` option in `DecodeSourceOpts` aborts decode if projected memory exceeds limit.
4. **Source resolution** — automatic `fetch()` vs `fs/promises.readFile()` resolution based on source type and runtime.
5. **Page pre-allocation** — `pages` option allows callers to supply a pre-allocated page array for zero-copy progressive decode.
6. **Property-based tests** — decode-then-encode round-trip for lossless formats (WAV, FLAC) yields identical PCM; format detection never returns undefined for known magic bytes; stream decoder yields same total samples as non-streaming decode.
7. **Benchmark suite** — decode 5-minute WAV, encode 5-minute WAV, progressive stream decode 5-minute MP3.
---
## File layout
```text
packages/media/audio/audio-codecs/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
registry/
registry.ts
registry.md
registry.types.ts
registry.test.ts
index.ts
decode/
decode-source.ts
decode-source.md
decode-source.types.ts
decode-source.test.ts
index.ts
encode/
encode-to.ts
encode-to.md
encode-to.types.ts
encode-to.test.ts
index.ts
detect/
detect-format.ts
detect-format.md
detect-format.test.ts
index.ts
stream/
stream-decoder.ts
stream-decoder.md
stream-decoder.test.ts
stream-encoder.ts
stream-encoder.md
stream-encoder.test.ts
index.ts
codecs/
wav.ts
wav.md
wav.test.ts
mp3.ts
mp3.md
mp3.test.ts
flac.ts
flac.md
flac.test.ts
index.ts
__benchmarks__/
decode.bench.ts
encode.bench.ts
stream-decode.bench.ts
tests/
integration.test.ts
```
---
## Impact
**Breaking changes**: none — fresh build, package does not yet exist on genesis.
**Prerequisites**: `@suaveplan/audio-core`, `@suaveplan/audio-metadata`, `@suaveplan/streams`, `@suaveplan/error`, `@suaveplan/types` published to Verdaccio. All five already exist on genesis.
### Affected dependents
`@suaveplan/audio-codecs` sits directly above `@suaveplan/audio-core` and below every rendering
engine and CLI in the audio family. At the time of this change none of the dependents exist on
genesis yet — they land in later changes in this same batch — so there is no existing dependent
surface to test or migrate. Each dependent is created against this package's spec delta, not
retrofitted.
- `@suaveplan/audio-cli` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: does not exist yet; created in `enterprise-audio-cli`)
- `@suaveplan/audio-engine-server` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: does not exist yet; created in `enterprise-audio-engine-server`)
- `@suaveplan/audio-engine-browser` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: does not exist yet; created in `enterprise-audio-engine-browser`)
Once landed, any subsequent change to `@suaveplan/audio-codecs`' public surface MUST run
`bunx turbo test --filter=...@suaveplan/audio-codecs` per `openspec/AGENTS.md` §13 step 10 to sweep
these dependents.