first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
# `codecs/aiff`
Native pure-JS AIFF (Audio Interchange File Format) decoder, decode-only.
## Purpose
AIFF is the big-endian counterpart to WAV used historically on Apple
hardware. The codec supports both the original AIFF container and the
later AIFC variant (which uses the same chunk structure but allows
non-PCM payloads — though this codec only decodes uncompressed integer
PCM).
Encoding is intentionally not supported: AIFF write is rarely useful
outside Apple's own tooling and the canonical archive format
is WAV/FLAC. Callers attempting to encode AIFF receive a clean
`AUDIO_CODECS.NOT_SUPPORTED` error rather than a silently-degraded
fallback.
## Features
- `canDecode(bytes)` — magic-byte sniff for `FORM` at byte 0 plus
`AIFF` or `AIFC` at byte 8; rejects buffers shorter than 12 bytes
or with non-matching magic.
- `decode(bytes)` — walks the AIFF chunk list for `COMM` and `SSND`,
decodes the 80-bit IEEE 754 extended-precision sample rate, and
returns a `DecodeResult` with normalised `Float32Array[]` PCM.
- 8-bit signed, 16-bit signed, 24-bit signed, and 32-bit signed PCM
in big-endian byte order are all decoded; the 80-bit extended-float
sample rate field is parsed in full so the codec round-trips any
sample rate AIFF can express, including the unusual ones used by old
samplers (37_800, 32_000, 24_000, etc).
- `encode` — throws `AudioCodecsError("AUDIO_CODECS.NOT_SUPPORTED")`.
## Usage: Basic Example
```ts
import { AiffCodec } from "@suaveplan/audio-codecs/codecs/aiff";
const decoded = await AiffCodec.decode(aiffBytes);
console.log(decoded.sampleRate, decoded.channelData.length);
```
## Usage: Advanced Example
Detecting AIFF vs AIFC and falling through to a separate processor:
```ts
import { AiffCodec } from "@suaveplan/audio-codecs/codecs/aiff";
async function processAudio(bytes: Uint8Array): Promise<AudioDocument> {
if (AiffCodec.canDecode(bytes)) {
const result = await AiffCodec.decode(bytes);
return buildDocumentFromResult(result);
}
return processWithFallbackCodec(bytes);
}
// Direct registry registration:
import { CodecRegistry } from "@suaveplan/audio-codecs";
const registry = new CodecRegistry({ codecs: [AiffCodec] });
const doc = await registry.decode(aiffBytes);
```
## API Reference
- `AiffCodec: AudioCodec` — frozen constant; `name === "aiff"`,
`extensions === ["aiff", "aif", "aifc"]`, `mimeTypes === ["audio/aiff",
"audio/x-aiff"]`.
- `canDecode(bytes: Uint8Array): boolean`
- `decode(bytes: Uint8Array): Promise<DecodeResult>` — throws
`AudioCodecsError("AUDIO_CODECS.DECODE_FAILED")` for:
- buffer length < 12 (`too-short`),
- non-FORM magic (`bad-form`),
- non-AIFF/AIFC type (`bad-type`),
- COMM chunk size < 18 (`bad-comm-size`),
- missing COMM chunk (`no-common`),
- missing SSND chunk (`no-ssnd`),
- `sampleSize` = 0 (`unsupported-bits`),
- `sampleSize` > 32 (`unsupported-bytes-per-sample`).
- `encode(): Promise<Uint8Array>` — always throws
`AudioCodecsError("AUDIO_CODECS.NOT_SUPPORTED")`.
- The optional streaming-factory methods are absent; `CodecRegistry`
honours that and throws `STREAM_ERROR` on attempted streaming
operations against this codec.
## Implementation Notes
- The 80-bit extended-precision IEEE 754 sample-rate field is decoded
by `parseIeee754Extended(bytes, off)` which reads 10 bytes (1 sign
bit + 15 exponent bits biased by 16383 + 64-bit explicit mantissa —
the AIFF format predates double-precision IEEE 754 and uses an
explicit-mantissa variant rather than the more common implicit-1
binary64). The function returns `0` when `off + 10 > bytes.byteLength`
so a truncated buffer produces a 0-Hz result rather than a thrown
error — the parent `decode` then fails with a meaningful chunk-level
error.
- AIFF chunk sizes are big-endian uint32; the chunk walker advances
`pos = body + size + (size & 1)` to honour the AIFF requirement that
chunks pad to even boundaries.
- The normalisation factor is `(1 << (sampleSize - 1)) >>> 0` — the
unsigned-shift handles the 32-bit case where `1 << 31` would
otherwise produce a negative number under signed arithmetic.
- The 24-bit decoder reads bytes in MSB-first order (AIFF is
big-endian) using one signed byte + two unsigned bytes to preserve
the sign bit without depending on the JavaScript runtime's
sign-extension semantics.
- `numSampleFrames` from COMM caps decoded output; the codec also
caps via `availableFrames = floor(ssndLength / frameBytes)` so a
truncated SSND chunk simply yields fewer frames rather than reading
past the buffer.
- No streaming factory exists because AIFF's `SSND` chunk is variably
positioned within the FORM block — implementing a single-pass
streaming decoder would require buffering the whole header section
anyway, which negates the streaming benefit. Callers needing
streaming PCM can pipe the AIFF through a full decode then push the
result via the registry's `encodeStream` into WAV (which is
streamable).