Files
skills/docs/media/audio/audio-codecs/src/codecs/aiff.md
2026-05-29 08:20:56 +01:00

4.9 KiB

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

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:

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