first commit
This commit is contained in:
117
docs/media/audio/audio-codecs/src/codecs/aiff.md
Normal file
117
docs/media/audio/audio-codecs/src/codecs/aiff.md
Normal 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).
|
||||
Reference in New Issue
Block a user