Sun/NeXT audio (.au / .snd) file header encoding and decoding.
An .au file opens with a 24-byte big-endian header, an optional
variable-length annotation field, and then the raw audio samples:
0 7 8 15 16 23 24 31 +--------+--------+--------+--------+ | Magic (".snd") | +--------+--------+--------+--------+ | Data Offset | +--------+--------+--------+--------+ | Data Size | +--------+--------+--------+--------+ | Encoding | +--------+--------+--------+--------+ | Sample Rate | +--------+--------+--------+--------+ | Channels | +--------+--------+--------+--------+ | Annotation (variable) | +-----------------------------------+
Every field is big-endian regardless of the host platform — the format originates from big-endian Sun hardware and never gained a little-endian variant.
dataOffset is the byte offset from the start of the file to the first
audio sample, so the annotation field is dataOffset - 24 bytes long and is
conventionally used for a NUL-padded comment. The minimum legal
dataOffset is therefore 24 (empty annotation).
This coder covers the header and annotation only. Audio samples are left to
the caller: dataSize is permitted to be 0xffffffff
(AU_DATA_SIZE_UNKNOWN) meaning "unknown, read to end of file",
which no fixed-length coder can express.
Round-trip a 16-bit stereo header
Round-trip a 16-bit stereo header
import { assertEquals } from "@std/assert"; import { auHeader, AU_ENCODING, AU_MAGIC } from "@binstruct/au"; const coder = auHeader(); const header = { magic: AU_MAGIC, dataOffset: 24, dataSize: 1024, encoding: AU_ENCODING.LINEAR_16, sampleRate: 44100, channels: 2, annotation: new Uint8Array(0), }; const buffer = new Uint8Array(24); const written = coder.encode(header, buffer); const [decoded, read] = coder.decode(buffer); assertEquals(written, 24); assertEquals(read, 24); assertEquals(decoded.sampleRate, 44100); assertEquals(decoded.channels, 2); assertEquals(decoded.encoding, AU_ENCODING.LINEAR_16);