Standard library · Encoding and data

audio/wav

Reading and writing WAVE files: PCM, floating point, A-law and mu-law.

import audio.wav

WAVE is the format to reach for when nothing should be lost. It is a RIFF container holding a fmt chunk that says what the samples are and a data chunk holding them, and this package reads and writes it without any outside library.

Integer samples at 8, 16, 24 and 32 bits are all handled, as are IEEE floating point samples and the two eight bit companding schemes from telephony, A-law and mu-law. OpenWAV and SaveWAV are the short way in; audio/codec is shorter still when you do not want to name the format at all.

Things to know

  • 8, 16 and 24 bit PCM makes the round trip through a clip exactly. A true 32 bit integer file loses its lowest 8 bits, because a clip holds 32 bit floats; write such a file with EncodeWAVFloat instead, which keeps every bit.
  • EncodeWAV writes at the depth the clip remembers in bits, and 16 bit when the clip has none. EncodeWAVBits takes the depth from you.
  • A companded file is a quarter the size of 24 bit PCM and does lose a little, most of it where the sound is loud. Near silence the error is smaller than one step of 16 bit PCM.
  • ProbeWAV reads the header alone, so a listing of a thousand files does not have to decode any of them.
  • Chunks hands back a vector you must free. FindChunk allocates nothing, and is what the decoder uses.

Types

Format

struct Format
What a WAVE file's fmt chunk says: which codec, how many channels, the rate and the depth.
NameTypeDescription
codecint = 0Which codec the samples use, one of the Codec numbers.
channelsint = 0How many channels.
rateint = 0Frames per second.
bitsint = 0Bits per sample.
block_alignint = 0How many bytes one frame takes.
byte_rateint = 0Bytes per second of audio.
wav-Read.salam
import audio
import audio.wav
import compress

func main:
    mut c := audio.New(22050, 2, 100)
    audio.SetSample(c, 7, 1, 0.5)
    buf := wav.EncodeWAVBits(c, 24)
    println wav.IsWAV(buf.data, buf.len), wav.IsWAV(buf.data, 4)
    mut f := wav.Format {}
    println wav.ReadFormat(buf.data, buf.len, f)
    println f.codec == wav.CodecPCM, f.channels, f.rate, f.bits, f.block_align, f.byte_rate
    chunks := wav.Chunks(buf.data, buf.len)
    println chunks.len()
    mut ck := wav.Chunk {}
    println wav.FindChunk(buf.data, buf.len, wav.Tag("data"), ck), ck.offset, ck.size
    mut miss := wav.Chunk {}
    println wav.FindChunk(buf.data, buf.len, wav.Tag("cue "), miss)
    mut ok := false
    info := wav.ProbeWAV(buf.data, buf.len, ok)
    println ok, info.rate, info.channels, info.bits, info.frames, info.lossless
    mut dok := false
    back := wav.DecodeWAV(buf.data, buf.len, dok)
    println dok, back.frames, back.bits, audio.Sample(back, 7, 1)
    compress.BufFree(buf)
    audio.Free(c)
end
Outputtrue false true true 2 22050 24 6 132300 2 true 44 600 false true 22050 2 24 100 true true 100 24 0.5

Chunk

struct Chunk
One chunk of a RIFF file: its four letter tag and where its body is.
NameTypeDescription
tagint = 0The four letter tag packed into an integer. Tag builds one from a string.
offsetint = 0Where the body starts in the file.
sizeint = 0How many bytes the body holds, never past the end of the file.

The example for this item is shown with the ones above: wav-Read.salam

Constants and variables

Codec numbers Codec…

The codec numbers a WAVE fmt chunk can carry.
NameValue
CodecPCM1
CodecFloat3
CodecALaw6
CodecMuLaw7
CodecExtensible65534
wav-Law.salam
import audio
import audio.wav
import compress
import math

func main:
    println audio.PcmToMuLaw(0), audio.MuLawToPcm(audio.PcmToMuLaw(0))
    println audio.PcmToMuLaw(16000), audio.MuLawToPcm(144)
    println audio.PcmToALaw(16000), audio.ALawToPcm(186)
    println audio.PcmToALaw(0), audio.ALawToPcm(213)

    mut near := 0
    mut far := 0
    mut v := -32768
    until v < 32768:
        d := math.AbsI(audio.MuLawToPcm(audio.PcmToMuLaw(v)) - v)
        if math.AbsI(v) <= 256:
            if d > near:
                near = d
            end
        end
        if d > far:
            far = d
        end
        v += 7
    end
    println near, far

    mut c := audio.New(8000, 2, 50)
    audio.SetSample(c, 10, 0, 0.5)
    mu := wav.EncodeMuLaw(c)
    al := wav.EncodeALaw(c)
    println compress.BufLen(mu), compress.BufLen(al), compress.BufLen(wav.EncodeWAVBits(c, 16))
    mut ok := false
    back := wav.DecodeWAV(mu.data, mu.len, ok)
    println ok, back.frames, back.channels, back.bits
    println math.Abs(audio.Sample(back, 10, 0) - 0.5) < 0.01
    compress.BufFree(mu)
    compress.BufFree(al)
    audio.Free(c)
end
Output255 0 144 15996 186 16128 213 8 11 644 144 144 244 true 50 2 8 false

Functions

EncodeWAVBits

func EncodeWAVBits(c: audio.Clip, bits: int): compress.ByteBuf
Encodes a clip at a depth you choose, one of 8, 16, 24 or 32.
wav-Write.salam
import audio
import audio.wav
import compress
import filepath
import mem
import os

func main:
    mut c := audio.New(8000, 1, 100)
    audio.SetSample(c, 0, 0, 0.5)
    audio.SetSample(c, 1, 0, -0.25)
    println compress.BufLen(wav.EncodeWAVBits(c, 8)), compress.BufLen(wav.EncodeWAVBits(c, 16))
    println compress.BufLen(wav.EncodeWAVBits(c, 24)), compress.BufLen(wav.EncodeWAVBits(c, 32))
    println compress.BufLen(wav.EncodeWAVFloat(c))
    println compress.BufLen(wav.EncodeWAV(c))
    c.bits = 24
    println compress.BufLen(wav.EncodeWAV(c))
    println compress.BufLen(wav.EncodeWAVBits(c, 12)), compress.BufLen(wav.EncodeWAV(audio.Clip {}))

    path := filepath.Join(os.TempDir(), "salam_wav_write.wav")
    defer mem.Free(path as void*)
    println wav.SaveWAVBits(c, path, 24)
    mut ok := false
    disk := wav.OpenWAV(path, ok)
    println ok, disk.frames, disk.bits, audio.Sample(disk, 0, 0), audio.Sample(disk, 1, 0)
    mut iok := false
    info := wav.ProbeFileWAV(path, iok)
    println iok, info.frames, info.bits

    fpath := filepath.Join(os.TempDir(), "salam_wav_write_f32.wav")
    defer mem.Free(fpath as void*)
    println wav.SaveWAVFloat(c, fpath)
    mut fok := false
    fdisk := wav.OpenWAV(fpath, fok)
    println fok, fdisk.bits, audio.Sample(fdisk, 1, 0)

    mut gone := false
    missing := filepath.Join(os.TempDir(), "salam_wav_nope.wav")
    defer mem.Free(missing as void*)
    wav.OpenWAV(missing, gone)
    println gone
    audio.Free(c)
end
Output144 244 344 444 444 244 344 0 0 true true 100 24 0.5 -0.25 true 100 24 true true 32 -0.25 false

EncodeWAV

func EncodeWAV(c: audio.Clip): compress.ByteBuf
Encodes a clip as a WAVE file, at the depth the clip remembers, or 16 bit when it has none.

The example for this item is shown with the ones above: wav-Write.salam

EncodeWAVFloat

func EncodeWAVFloat(c: audio.Clip): compress.ByteBuf
Encodes a clip as 32 bit floating point samples, which keeps them exactly as they are.

The example for this item is shown with the ones above: wav-Write.salam

EncodeMuLaw

func EncodeMuLaw(c: audio.Clip): compress.ByteBuf
Encodes a clip as 8 bit mu-law, which halves a 16 bit file and loses a little.

The example for this item is shown with the ones above: wav-Law.salam

EncodeALaw

func EncodeALaw(c: audio.Clip): compress.ByteBuf
Encodes a clip as 8 bit A-law.

The example for this item is shown with the ones above: wav-Law.salam

SaveWAVBits

func SaveWAVBits(c: audio.Clip, path: str, bits: int): bool
Writes a clip to a WAVE file at a depth you choose.

The example for this item is shown with the ones above: wav-Write.salam

SaveWAV

func SaveWAV(c: audio.Clip, path: str): bool
Writes a clip to a WAVE file.

The example for this item is shown with the ones above: wav-Write.salam

SaveWAVFloat

func SaveWAVFloat(c: audio.Clip, path: str): bool
Writes a clip to a floating point WAVE file.

The example for this item is shown with the ones above: wav-Write.salam

Tag

func Tag(s: str): int
Packs a four letter chunk name such as data into the integer a Chunk carries.

The example for this item is shown with the ones above: wav-Read.salam

IsWAV

func IsWAV(p: void*, n: int): bool
True when these bytes start with a RIFF WAVE header.

The example for this item is shown with the ones above: wav-Read.salam

Chunks

func Chunks(p: void*, n: int): Vector<Chunk>
Every chunk in the file, in order. Free the vector when you are done with it.

The example for this item is shown with the ones above: wav-Read.salam

FindChunk

func FindChunk(p: void*, n: int, tag: int, out &: Chunk): bool
Finds one chunk by tag without allocating anything.

The example for this item is shown with the ones above: wav-Read.salam

ReadFormat

func ReadFormat(p: void*, n: int, out &: Format): bool
Reads the fmt chunk, following an extensible header to the real codec number.

The example for this item is shown with the ones above: wav-Read.salam

ProbeWAV

func ProbeWAV(p: void*, n: int, ok &: bool): audio.Info
Reads only the header, so you learn the length and the rate without decoding the sound.

The example for this item is shown with the ones above: wav-Read.salam

DecodeWAV

func DecodeWAV(p: void*, n: int, ok &: bool): audio.Clip
Decodes a whole WAVE file from memory into a clip.

The example for this item is shown with the ones above: wav-Read.salam

OpenWAV

func OpenWAV(path: str, ok &: bool): audio.Clip
Reads a WAVE file from disk into a clip.

The example for this item is shown with the ones above: wav-Read.salam

ProbeFileWAV

func ProbeFileWAV(path: str, ok &: bool): audio.Info
Reads only the header of a file on disk.

The example for this item is shown with the ones above: wav-Read.salam