# `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 { 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` — 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` — 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).