AU audio format: Nim parsing library

This page hosts a formal specification of AU audio format using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Nim source code to parse AU audio format

au.nim

import kaitai_struct_nim_runtime
import options

type
  Au* = ref object of KaitaiStruct
    `magic`*: seq[byte]
    `ofsData`*: uint32
    `header`*: Au_Header
    `parent`*: KaitaiStruct
    `rawHeader`*: seq[byte]
    `lenDataInst`*: int
  Au_Encodings* = enum
    mulaw_8 = 1
    linear_8 = 2
    linear_16 = 3
    linear_24 = 4
    linear_32 = 5
    float = 6
    double = 7
    fragmented = 8
    nested = 9
    dsp_core = 10
    fixed_point_8 = 11
    fixed_point_16 = 12
    fixed_point_24 = 13
    fixed_point_32 = 14
    display = 16
    mulaw_squelch = 17
    emphasized = 18
    compressed = 19
    compressed_emphasized = 20
    dsp_commands = 21
    dsp_commands_samples = 22
    adpcm_g721 = 23
    adpcm_g722 = 24
    adpcm_g723_3 = 25
    adpcm_g723_5 = 26
    alaw_8 = 27
    aes = 28
    delta_mulaw_8 = 29
  Au_Header* = ref object of KaitaiStruct
    `dataSize`*: uint32
    `encoding`*: Au_Encodings
    `sampleRate`*: uint32
    `numChannels`*: uint32
    `comment`*: string
    `parent`*: Au

proc read*(_: typedesc[Au], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Au
proc read*(_: typedesc[Au_Header], io: KaitaiStream, root: KaitaiStruct, parent: Au): Au_Header

proc lenData*(this: Au): int


##[
The NeXT/Sun audio file format.

Sample files:

* <https://github.com/python/cpython/tree/b8a7daf077da/Lib/test/sndhdrdata>
* <ftp://ftp-ccrma.stanford.edu/pub/Lisp/sf.tar.gz>
* <http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AU/Samples.html>

@see <a href="http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AU/AU.html">Source</a>
@see <a href="http://soundfile.sapp.org/doc/NextFormat/">Source</a>
@see <a href="http://soundfile.sapp.org/doc/NextFormat/soundstruct.h">Source</a>
@see <a href="https://github.com/andreiw/polaris/blob/deb47cb/usr/src/head/audio/au.h#L87-L112">Source</a>
@see <a href="https://github.com/libsndfile/libsndfile/blob/86c9f9eb/src/au.c#L39-L74">Source</a>
@see <a href="https://github.com/chirlu/sox/blob/dd8b63bd/src/au.c#L34-L49">Source</a>
@see <a href="https://github.com/mpruett/audiofile/blob/b62c902/libaudiofile/NeXT.cpp#L65-L96">Source</a>
]##
proc read*(_: typedesc[Au], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Au =
  template this: untyped = result
  this = new(Au)
  let root = if root == nil: cast[Au](this) else: cast[Au](root)
  this.io = io
  this.root = root
  this.parent = parent

  let magicExpr = this.io.readBytes(int(4))
  this.magic = magicExpr
  let ofsDataExpr = this.io.readU4be()
  this.ofsData = ofsDataExpr
  let rawHeaderExpr = this.io.readBytes(int(((this.ofsData - 4) - 4)))
  this.rawHeader = rawHeaderExpr
  let rawHeaderIo = newKaitaiStream(rawHeaderExpr)
  let headerExpr = Au_Header.read(rawHeaderIo, this.root, this)
  this.header = headerExpr

proc lenData(this: Au): int = 
  if this.lenDataInst != nil:
    return this.lenDataInst
  let lenDataInstExpr = int((if this.header.dataSize == 4294967295'i64: (this.io.size - this.ofsData) else: this.header.dataSize))
  this.lenDataInst = lenDataInstExpr
  if this.lenDataInst != nil:
    return this.lenDataInst

proc fromFile*(_: typedesc[Au], filename: string): Au =
  Au.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Au_Header], io: KaitaiStream, root: KaitaiStruct, parent: Au): Au_Header =
  template this: untyped = result
  this = new(Au_Header)
  let root = if root == nil: cast[Au](this) else: cast[Au](root)
  this.io = io
  this.root = root
  this.parent = parent


  ##[
  don't read this field, access `_root.len_data` instead

value `0xffff_ffff` means unspecified size

  ]##
  let dataSizeExpr = this.io.readU4be()
  this.dataSize = dataSizeExpr
  let encodingExpr = Au_Encodings(this.io.readU4be())
  this.encoding = encodingExpr
  let sampleRateExpr = this.io.readU4be()
  this.sampleRate = sampleRateExpr

  ##[
  number of interleaved channels
  ]##
  let numChannelsExpr = this.io.readU4be()
  this.numChannels = numChannelsExpr

  ##[
  Most resources claim that this field must be at least 4 bytes long.
However, most programs don't enforce it, and [Audacity](
https://www.audacityteam.org/) even generates .au files with this field
being 0-byte long. According to <https://nixdoc.net/man-pages/IRIX/man4/dmedia/next.4.html>,
"NeXT files require that this chunk be at least 4 bytes (chars) long,
whereas this chunk may be zerolength in a Sun .au file."

By convention, size should be a multiple of 4 -
see <https://github.com/chirlu/sox/blob/dd8b63bd/src/au.c#L132-L133>.
Page <http://soundfile.sapp.org/doc/NextFormat/> also mentions that for some
sound playing programs, this field must have an even byte size. So a multiple
of 4 is probably best for compatibility.

Must be null-terminated. It is usually an ASCII text string, but this space
might be also used to store application-specific binary (i.e. non-ASCII) data.

  ]##
  let commentExpr = encode(this.io.readBytesFull().bytesTerminate(0, false), "ASCII")
  this.comment = commentExpr

proc fromFile*(_: typedesc[Au_Header], filename: string): Au_Header =
  Au_Header.read(newKaitaiFileStream(filename), nil, nil)