---
title: "audio"
description: "The audio namespace — the engine's Luau API reference for audio."
section: "API Reference"
slug: "api-audio"
canonical: "https://origozero.ai/docs/api-audio"
updated: "2026-09-06T15:21:31.818305344+00:00"
tags: ["api", "reference"]
---

# audio

The `audio` namespace — 61 functions.

## globals/audio/decode {#globals-audio-decode}

```lua
audio.decode(zaud: buffer | string) -> (string?, number?, number?)
```

Decode a ZAUD payload into interleaved f32 PCM. A PCM payload comes
back as the frames its header accounts for, held to the whole frames the
bytes behind it fill, so the sample count is always a whole number of
`channels` and a consumer walking it `channels` at a time ends on a frame.

**Parameters**

- `zaud` `buffer | string` — A ZAUD payload — a buffer or a binary string.

**Returns** `(string?, number?, number?)` — `(pcm, sampleRate, channels)`, or `(nil, err)`.

## globals/audio/device {#globals-audio-device}

```lua
audio.device() -> AudioDeviceStatus
```

What the engine's audio output is doing: `state` is `"open"` while a
stream is running on an output device and `"silent"` while none is, and
`device` names the device an open stream runs on. The counters record
what the engine has been through keeping one open — `faults` a live
stream reported, `changes` of the host's default output, `reopens` the
engine made, `failedOpens` the platform refused, and the `glitches` a
listener heard as dropouts, with `lastError` carrying what the platform
said. A device that goes away leaves the mixer running and the engine
opening a stream again as soon as one is there.

**Returns** `AudioDeviceStatus` — An `AudioDeviceStatus`.

```lua
local d = audio.device(); print(d.state, d.device, d.reopens)
```

## globals/audio/encode {#globals-audio-encode}

```lua
audio.encode(sourceBytes: buffer | string, opts: { [string]: any }?) -> (string?, string?)
```

Encode container audio bytes (ogg / mp3 / wav / flac) into a ZAUD
payload. Every decoded sample must be finite; a source whose samples carry
a NaN or an infinity comes back as `(nil, err)` naming how many fail and
where the first one sits.

**Parameters**

- `sourceBytes` `buffer | string` — Encoded source audio bytes — a buffer or a binary string.
- `opts` `{ [string]: any }` _(optional)_ — `{ codec: "opus"|"pcm"?, bitrateKbps: number?, vbr: boolean?, sampleRate: number?, forceMono: boolean?, loopStart: number?, loopEnd: number? }`

**Returns** `(string?, string?)` — The ZAUD bytes, or `(nil, err)`.

```lua
local zaud = audio.encode(oggBytes, { bitrateKbps = 96 })
```

## globals/audio/encodePcm {#globals-audio-encodepcm}

```lua
audio.encodePcm(pcm: any?, sampleRate: number, channels: number, opts: { [string]: any }?) -> (string?, string?)
```

Encode raw interleaved f32 PCM into a ZAUD payload. Every sample must
be finite; a buffer carrying a NaN or an infinity comes back as
`(nil, err)` naming how many fail and where the first one sits, so a
filter that diverged over part of a bake is caught before it is written.
The sample count is a whole number of `channels`: a buffer with a tail
over comes back as `(nil, err)` naming the whole frames it holds and the
samples past them.

**Parameters**

- `pcm` `any` _(optional)_ — Interleaved f32 samples — a buffer or a binary string of
little-endian f32, the shape `microphone.samples` and `audio.decode` hand
back, or a flat number array. A byte payload's samples are its 4-byte
lanes, and a length that stops partway through one comes back as
`(nil, err)` naming the whole samples it holds and the bytes past them.
- `sampleRate` `number` — Source sample rate in Hz.
- `channels` `number` — 1 or 2, and a divisor of the sample count.
- `opts` `{ [string]: any }` _(optional)_ — Same shape as `audio.encode`.

**Returns** `(string?, string?)` — The ZAUD bytes, or `(nil, err)`.

```lua
local s = microphone.status(); local zaud = audio.encodePcm(microphone.samples(), s.sampleRate, 1)
```

## globals/audio/info {#globals-audio-info}

```lua
audio.info(zaud: buffer | string) -> (AudioInfo?, string?)
```

Read a ZAUD payload's header. A PCM payload's samples are its bytes, and
the header is read against them: a sample count differing from
`frames * channels` comes back as `(nil, err)` naming both counts, and a
sample carrying a NaN or an infinity comes back as `(nil, err)` naming how
many fail and where the first one sits, so the header handed back describes
a clip that is as long as it says and can sound. The header describes the
clip's shape — rate, channels, frames, duration, codec, loop points. What
the samples do where a whole-clip loop wraps is a reading of its own,
`audio.loopSeam`, which is the call that answers whether a bed cycles
without a click.

**Parameters**

- `zaud` `buffer | string` — A ZAUD payload — a buffer or a binary string.

**Returns** `(AudioInfo?, string?)` — An `AudioInfo` table, or `(nil, err)`.

```lua
local info = audio.info(zaud); print(info.durationMs)
```

## globals/audio/levels {#globals-audio-levels}

```lua
audio.levels() -> AudioLevels
```

The master mix's peak and RMS over the meter's most recent closed
window, measured without recording anything.

**Returns** `AudioLevels` — An `AudioLevels`.

```lua
local l = audio.levels(); print(l.peak, l.rms, l.windowMs)
```

## globals/audio/listener {#globals-audio-listener}

```lua
audio.listener() -> AudioListenerState
```

Where the scene is heard from, how many active listeners exist, and
which entity's listener drives the ears.

**Returns** `AudioListenerState` — An `AudioListenerState`.

```lua
local l = audio.listener(); print(l.present, l.count, l.entity)
```

## globals/audio/loopSeam {#globals-audio-loopseam}

```lua
audio.loopSeam(zaud: buffer | string) -> (AudioLoopSeam?, string?)
```

Measure what a clip's samples do where a whole-clip loop wraps, so a bed
can be judged before anyone hears it tick. The wrap's own step
(`|x[1] - x[frames]|`) is reported against the step the signal ordinarily
makes between neighbouring samples, as `ratio = step / meanStep` — a figure
in the units the signal itself moves in, so a quiet ambience and a loud
drone are read the same way. A bed whose partials wrap reads near 1; one
carrying a strike at its head and silence at its tail reads in the tens.
`ratio` and the `step` / `meanStep` / `maxStep` beside it belong to the
worst channel, `channel` names it, and `channels` carries every channel's
own reading. `seamless` is `ratio <= threshold`, the same threshold
`asset.create("soundClip", ...)` warns past. The reading is taken on the
DECODED samples, so it answers for what the codec left behind and for a
clip that arrived already encoded and whose source buffer nobody holds.
Costs a decode of the whole payload; `audio.info` reads a header without
one.

**Parameters**

- `zaud` `buffer | string` — A ZAUD payload — a buffer or a binary string.

**Returns** `(AudioLoopSeam?, string?)` — An `AudioLoopSeam` table, or `(nil, err)`.

```lua
local seam = audio.loopSeam(clipRef:getBytes()); print(seam.ratio, seam.seamless)
```

## globals/audio/mixer {#globals-audio-mixer}

```lua
audio.mixer() -> AudioMixerLevels
```

The levels the mixer is applying to the mix right now: the master
level, whether the mix is muted, and the level of every channel one has
been set on. A channel absent from `channels` plays at unity, so a
source naming it is heard at the volume it asks for.

**Returns** `AudioMixerLevels` — An `AudioMixerLevels`.

```lua
local m = audio.mixer(); print(m.master, m.muted, m.channels.music)
```

## globals/audio/observe {#globals-audio-observe}

```lua
audio.observe() -> AudioObservation
```

Report what the mixer is making audible right now, and why a source
is not. One read covering every live voice with the mixer's own playback
state and effective gain, the master mix's level, the mixer's voice
accounting, the listener, the output device the mix is reaching, and what
the subsystem costs. Answers in edit mode as well as play mode.

**Returns** `AudioObservation` — An `AudioObservation`.

```lua
local a = audio.observe(); print(a.audibleCount, a.levels.rms)
for _, v in audio.observe().voices do print(v.entity, v.mixerState, v.silence) end
```

## globals/audio/peakSince {#globals-audio-peaksince}

```lua
audio.peakSince(window: number) -> number?
```

The loudest peak the master mix reached across the meter's windows
that closed after its `windows` count stood at `window`. `audio.levels()`
carries the window that closed last, so a reader sees the windows its own
frames happen to land on; this spans all of them, which is what measuring
a sound shorter than the gap between two reads takes. Take the mark from
`audio.levels().windows` before the sound starts, wait until `windows` has
advanced past the sound's length, then read the span.

**Parameters**

- `window` `number` — A `windows` count taken from `audio.levels()` earlier.

**Returns** `number?` — The loudest window peak in the span, or nil when the meter holds no peak for it — nothing has closed since `window`, or the span reaches further back than the meter's history of recent windows, so a reader that came back too late learns that instead of reading the maximum of the part that survived.

```lua
local mark = audio.levels().windows
local peak = audio.peakSince(mark)
```

## globals/audio/profile {#globals-audio-profile}

```lua
audio.profile() -> AudioProfile
```

What the audio subsystem has cost since the profiling window opened —
the streaming pump, clip decode, clip encode, voice starts, and building
the observation itself. Every total is a SUM across that window rather
than a per-frame figure, and the window runs from the last
`audio.resetProfile()` or from engine start. For what a frame costs now,
reset, let frames pass, then divide by the `frames` the window reports.

**Returns** `AudioProfile` — An `AudioProfile`.

```lua
audio.resetProfile(); task.wait(1); local p = audio.profile()
print("per frame:", (p.pump.totalMs + p.observe.totalMs) / p.frames)
```

## globals/audio/resetProfile {#globals-audio-resetprofile}

```lua
audio.resetProfile()
```

Open a new audio profiling window, discarding what the previous one
measured. Call this before timing a stretch of frames: without it
`audio.profile()` reports totals reaching back to engine start.

```lua
audio.resetProfile()
```

## globals/audio/setChannelVolume {#globals-audio-setchannelvolume}

```lua
audio.setChannelVolume(channel: string, volume: number)
```

Set the level of one mixer channel — the `channel` an `Audio`
component names, such as "sfx", "music" or "ambient", or any name the
scene invents. It scales every voice on that channel and nothing else,
reaches voices that are already playing, and comes back per voice as
`gain.channel`. A channel no level has been set on plays at unity.

**Parameters**

- `channel` `string` — The channel name, matching `Audio.channel`.
- `volume` `number` — Channel level, 0..1.

```lua
audio.setChannelVolume("music", 0.3)
for _, v in audio.voices() do print(v.channel, v.gain.channel) end
```

## globals/audio/setMasterVolume {#globals-audio-setmastervolume}

```lua
audio.setMasterVolume(volume: number)
```

Set the master level of the mix, on the engine's 0..1 amplitude
scale. It scales every voice whatever channel it plays on, reaches
voices that are already playing, and comes back per voice as
`gain.master`.

**Parameters**

- `volume` `number` — Master level, 0..1.

```lua
audio.setMasterVolume(0.5)
```

## globals/audio/setMuted {#globals-audio-setmuted}

```lua
audio.setMuted(muted: boolean)
```

Silence or unsilence the whole mix. A muted mix sounds nothing
whatever its master and channel levels read, every voice reports
`masterSilent`, and unmuting hands the levels back untouched.

**Parameters**

- `muted` `boolean` — Whether the mix is silenced.

```lua
audio.setMuted(true)
```

## globals/audio/voice {#globals-audio-voice}

```lua
audio.voice(entityId: string) -> AudioVoice?
```

The voice on one entity, or nil when that entity carries no audio
source.

**Parameters**

- `entityId` `string` — The entity's stable id.

**Returns** `AudioVoice?` — An `AudioVoice`, or nil.

```lua
local v = audio.voice(e.id); print(v and v.mixerState)
```

## globals/audio/voiceAccounting {#globals-audio-voiceaccounting}

```lua
audio.voiceAccounting() -> AudioVoiceAccounting
```

How many voices the mixer can hold, how many are in use, how many
are free — read off the mixer's own tracks, so the free count is the one
a play call is granted or refused against. The two pools are reported
apart: `capacity` / `inUse` / `free` are the main track, which carries
the NON-spatial voices, while a spatial voice plays through its own
sub-track and is counted by `spatialInUse` instead. `sourcesHolding`
counts both pools from the sources that own them, so it equals
`inUse + spatialInUse` while every voice answers to a source.

**Returns** `AudioVoiceAccounting` — An `AudioVoiceAccounting`.

```lua
local v = audio.voiceAccounting(); print(v.inUse .. "/" .. v.capacity)
local v = audio.voiceAccounting(); print(v.sourcesHolding - (v.inUse + v.spatialInUse))
```

## globals/audio/voices {#globals-audio-voices}

```lua
audio.voices() -> { AudioVoice }
```

Every live audio source with the mixer's opinion of it.

**Returns** `{ AudioVoice }` — An array of `AudioVoice`.

```lua
for _, v in audio.voices() do print(v.clip, v.gain.effective) end
```

## globals/audio/whySilent {#globals-audio-whysilent}

```lua
audio.whySilent(entityId: string) -> (string?, string?)
```

Why the source on an entity is making no sound. Returns nil when it
IS sounding, and one of `noBackend`, `noDevice`, `notResident`, `neverStarted`,
`refused`, `paused`, `ended`, `gainZero`, `channelSilent`,
`masterSilent`, `outOfRange` when it is not — the nearest cause, so the
answer names the thing to change. A second
return carries the mixer's own words when it refused the source, and
`"no audio source on this entity"` when nothing there plays at all.

**Parameters**

- `entityId` `string` — The entity's stable id.

**Returns** `(string?, string?)` — `(reason, detail)`.

```lua
local why = audio.whySilent(e.id); if why then print(why) end
```

## modules/audio/README {#modules-audio-readme}

```lua
require("@builtin/modules/api/engine/audio") -- audio (also available as global 'audio')
```

Engine-native audio: encode audio (or raw PCM) into the ZAUD compressed payload, decode/inspect it, set what the mix is heard at — a level per named channel, a master level and a mute — and observe what the mixer is making audible right now: live voices, why a source is silent, master levels, mixer voice accounting, listener state and subsystem cost.

Usage: local audio = require("@builtin/modules/api/engine/audio")
Also available as global: audio

## modules/audio/decode {#modules-audio-decode}

```lua
decode(zaud: buffer | string): (string?, number?, number?)
```

Decode a ZAUD payload into interleaved f32 PCM. A PCM payload comes
back as the frames its header accounts for, held to the whole frames the
bytes behind it fill, so the sample count is always a whole number of
`channels` and a consumer walking it `channels` at a time ends on a frame.

**Parameters**

- `zaud` `buffer | string` — A ZAUD payload — a buffer or a binary string.

## modules/audio/device {#modules-audio-device}

```lua
device(): AudioDeviceStatus
```

What the engine's audio output is doing: `state` is `"open"` while a
stream is running on an output device and `"silent"` while none is, and
`device` names the device an open stream runs on. The counters record
what the engine has been through keeping one open — `faults` a live
stream reported, `changes` of the host's default output, `reopens` the
engine made, `failedOpens` the platform refused, and the `glitches` a
listener heard as dropouts, with `lastError` carrying what the platform
said. A device that goes away leaves the mixer running and the engine
opening a stream again as soon as one is there.

```lua
local d = audio.device(); print(d.state, d.device, d.reopens)
```

## modules/audio/encode {#modules-audio-encode}

```lua
encode(sourceBytes: buffer | string, opts: { [string]: any }?): (string?, string?)
```

Encode container audio bytes (ogg / mp3 / wav / flac) into a ZAUD
payload. Every decoded sample must be finite; a source whose samples carry
a NaN or an infinity comes back as `(nil, err)` naming how many fail and
where the first one sits.

**Parameters**

- `sourceBytes` `buffer | string` — Encoded source audio bytes — a buffer or a binary string.
- `opts` `{ [string]: any }?` _(optional)_ — `{ codec: "opus"|"pcm"?, bitrateKbps: number?, vbr: boolean?, sampleRate: number?, forceMono: boolean?, loopStart: number?, loopEnd: number? }`

```lua
local zaud = audio.encode(oggBytes, { bitrateKbps = 96 })
```

## modules/audio/encodePcm {#modules-audio-encodepcm}

```lua
encodePcm(pcm: any, sampleRate: number, channels: number, opts: { [string]: any }?): (string?, string?)
```

Encode raw interleaved f32 PCM into a ZAUD payload. Every sample must
be finite; a buffer carrying a NaN or an infinity comes back as
`(nil, err)` naming how many fail and where the first one sits, so a
filter that diverged over part of a bake is caught before it is written.
The sample count is a whole number of `channels`: a buffer with a tail
over comes back as `(nil, err)` naming the whole frames it holds and the
samples past them.

**Parameters**

- `pcm` `any` _(optional)_ — Interleaved f32 samples — a buffer or a binary string of
little-endian f32, the shape `microphone.samples` and `audio.decode` hand
back, or a flat number array. A byte payload's samples are its 4-byte
lanes, and a length that stops partway through one comes back as
`(nil, err)` naming the whole samples it holds and the bytes past them.
- `sampleRate` `number` — Source sample rate in Hz.
- `channels` `number` — 1 or 2, and a divisor of the sample count.
- `opts` `{ [string]: any }?` _(optional)_ — Same shape as `audio.encode`.

```lua
local s = microphone.status(); local zaud = audio.encodePcm(microphone.samples(), s.sampleRate, 1)
```

## modules/audio/info {#modules-audio-info}

```lua
info(zaud: buffer | string): (AudioInfo?, string?)
```

Read a ZAUD payload's header. A PCM payload's samples are its bytes, and
the header is read against them: a sample count differing from
`frames * channels` comes back as `(nil, err)` naming both counts, and a
sample carrying a NaN or an infinity comes back as `(nil, err)` naming how
many fail and where the first one sits, so the header handed back describes
a clip that is as long as it says and can sound. The header describes the
clip's shape — rate, channels, frames, duration, codec, loop points. What
the samples do where a whole-clip loop wraps is a reading of its own,
`audio.loopSeam`, which is the call that answers whether a bed cycles
without a click.

**Parameters**

- `zaud` `buffer | string` — A ZAUD payload — a buffer or a binary string.

```lua
local info = audio.info(zaud); print(info.durationMs)
```

## modules/audio/levels {#modules-audio-levels}

```lua
levels(): AudioLevels
```

The master mix's peak and RMS over the meter's most recent closed
window, measured without recording anything.

```lua
local l = audio.levels(); print(l.peak, l.rms, l.windowMs)
```

## modules/audio/listener {#modules-audio-listener}

```lua
listener(): AudioListenerState
```

Where the scene is heard from, how many active listeners exist, and
which entity's listener drives the ears.

```lua
local l = audio.listener(); print(l.present, l.count, l.entity)
```

## modules/audio/loopSeam {#modules-audio-loopseam}

```lua
loopSeam(zaud: buffer | string): (AudioLoopSeam?, string?)
```

Measure what a clip's samples do where a whole-clip loop wraps, so a bed
can be judged before anyone hears it tick. The wrap's own step
(`|x[1] - x[frames]|`) is reported against the step the signal ordinarily
makes between neighbouring samples, as `ratio = step / meanStep` — a figure
in the units the signal itself moves in, so a quiet ambience and a loud
drone are read the same way. A bed whose partials wrap reads near 1; one
carrying a strike at its head and silence at its tail reads in the tens.
`ratio` and the `step` / `meanStep` / `maxStep` beside it belong to the
worst channel, `channel` names it, and `channels` carries every channel's
own reading. `seamless` is `ratio <= threshold`, the same threshold
`asset.create("soundClip", ...)` warns past. The reading is taken on the
DECODED samples, so it answers for what the codec left behind and for a
clip that arrived already encoded and whose source buffer nobody holds.
Costs a decode of the whole payload; `audio.info` reads a header without
one.

**Parameters**

- `zaud` `buffer | string` — A ZAUD payload — a buffer or a binary string.

```lua
local seam = audio.loopSeam(clipRef:getBytes()); print(seam.ratio, seam.seamless)
```

## modules/audio/mixer {#modules-audio-mixer}

```lua
mixer(): AudioMixerLevels
```

The levels the mixer is applying to the mix right now: the master
level, whether the mix is muted, and the level of every channel one has
been set on. A channel absent from `channels` plays at unity, so a
source naming it is heard at the volume it asks for.

```lua
local m = audio.mixer(); print(m.master, m.muted, m.channels.music)
```

## modules/audio/observe {#modules-audio-observe}

```lua
observe(): AudioObservation
```

Report what the mixer is making audible right now, and why a source
is not. One read covering every live voice with the mixer's own playback
state and effective gain, the master mix's level, the mixer's voice
accounting, the listener, the output device the mix is reaching, and what
the subsystem costs. Answers in edit mode as well as play mode.

```lua
local a = audio.observe(); print(a.audibleCount, a.levels.rms)
for _, v in audio.observe().voices do print(v.entity, v.mixerState, v.silence) end
```

## modules/audio/peakSince {#modules-audio-peaksince}

```lua
peakSince(window: number): number?
```

The loudest peak the master mix reached across the meter's windows
that closed after its `windows` count stood at `window`. `audio.levels()`
carries the window that closed last, so a reader sees the windows its own
frames happen to land on; this spans all of them, which is what measuring
a sound shorter than the gap between two reads takes. Take the mark from
`audio.levels().windows` before the sound starts, wait until `windows` has
advanced past the sound's length, then read the span.

**Parameters**

- `window` `number` — A `windows` count taken from `audio.levels()` earlier.

```lua
local mark = audio.levels().windows
local peak = audio.peakSince(mark)
```

## modules/audio/profile {#modules-audio-profile}

```lua
profile(): AudioProfile
```

What the audio subsystem has cost since the profiling window opened —
the streaming pump, clip decode, clip encode, voice starts, and building
the observation itself. Every total is a SUM across that window rather
than a per-frame figure, and the window runs from the last
`audio.resetProfile()` or from engine start. For what a frame costs now,
reset, let frames pass, then divide by the `frames` the window reports.

```lua
audio.resetProfile(); task.wait(1); local p = audio.profile()
print("per frame:", (p.pump.totalMs + p.observe.totalMs) / p.frames)
```

## modules/audio/resetProfile {#modules-audio-resetprofile}

```lua
resetProfile()
```

Open a new audio profiling window, discarding what the previous one
measured. Call this before timing a stretch of frames: without it
`audio.profile()` reports totals reaching back to engine start.

```lua
audio.resetProfile()
```

## modules/audio/setChannelVolume {#modules-audio-setchannelvolume}

```lua
setChannelVolume(channel: string, volume: number)
```

Set the level of one mixer channel — the `channel` an `Audio`
component names, such as "sfx", "music" or "ambient", or any name the
scene invents. It scales every voice on that channel and nothing else,
reaches voices that are already playing, and comes back per voice as
`gain.channel`. A channel no level has been set on plays at unity.

**Parameters**

- `channel` `string` — The channel name, matching `Audio.channel`.
- `volume` `number` — Channel level, 0..1.

```lua
audio.setChannelVolume("music", 0.3)
for _, v in audio.voices() do print(v.channel, v.gain.channel) end
```

## modules/audio/setMasterVolume {#modules-audio-setmastervolume}

```lua
setMasterVolume(volume: number)
```

Set the master level of the mix, on the engine's 0..1 amplitude
scale. It scales every voice whatever channel it plays on, reaches
voices that are already playing, and comes back per voice as
`gain.master`.

**Parameters**

- `volume` `number` — Master level, 0..1.

```lua
audio.setMasterVolume(0.5)
```

## modules/audio/setMuted {#modules-audio-setmuted}

```lua
setMuted(muted: boolean)
```

Silence or unsilence the whole mix. A muted mix sounds nothing
whatever its master and channel levels read, every voice reports
`masterSilent`, and unmuting hands the levels back untouched.

**Parameters**

- `muted` `boolean` — Whether the mix is silenced.

```lua
audio.setMuted(true)
```

## modules/audio/voice {#modules-audio-voice}

```lua
voice(entityId: string): AudioVoice?
```

The voice on one entity, or nil when that entity carries no audio
source.

**Parameters**

- `entityId` `string` — The entity's stable id.

```lua
local v = audio.voice(e.id); print(v and v.mixerState)
```

## modules/audio/voiceAccounting {#modules-audio-voiceaccounting}

```lua
voiceAccounting(): AudioVoiceAccounting
```

How many voices the mixer can hold, how many are in use, how many
are free — read off the mixer's own tracks, so the free count is the one
a play call is granted or refused against. The two pools are reported
apart: `capacity` / `inUse` / `free` are the main track, which carries
the NON-spatial voices, while a spatial voice plays through its own
sub-track and is counted by `spatialInUse` instead. `sourcesHolding`
counts both pools from the sources that own them, so it equals
`inUse + spatialInUse` while every voice answers to a source.

```lua
local v = audio.voiceAccounting(); print(v.inUse .. "/" .. v.capacity)
local v = audio.voiceAccounting(); print(v.sourcesHolding - (v.inUse + v.spatialInUse))
```

## modules/audio/voices {#modules-audio-voices}

```lua
voices(): { AudioVoice }
```

Every live audio source with the mixer's opinion of it.

```lua
for _, v in audio.voices() do print(v.clip, v.gain.effective) end
```

## modules/audio/whySilent {#modules-audio-whysilent}

```lua
whySilent(entityId: string): (string?, string?)
```

Why the source on an entity is making no sound. Returns nil when it
IS sounding, and one of `noBackend`, `noDevice`, `notResident`, `neverStarted`,
`refused`, `paused`, `ended`, `gainZero`, `channelSilent`,
`masterSilent`, `outOfRange` when it is not — the nearest cause, so the
answer names the thing to change. A second
return carries the mixer's own words when it refused the source, and
`"no audio source on this entity"` when nothing there plays at all.

**Parameters**

- `entityId` `string` — The entity's stable id.

```lua
local why = audio.whySilent(e.id); if why then print(why) end
```

## typed/builtin//modules/api/engine/audio/audio/decode {#typed-builtin-modules-api-engine-audio-audio-decode}

```lua
audio.decode(zaud: buffer | string) -> (string?, number?, number?)
```

Decode a ZAUD payload into interleaved f32 PCM. A PCM payload comes
back as the frames its header accounts for, held to the whole frames the
bytes behind it fill, so the sample count is always a whole number of
`channels` and a consumer walking it `channels` at a time ends on a frame.

**Parameters**

- `zaud` `buffer | string` — A ZAUD payload — a buffer or a binary string.

**Returns** `(string?, number?, number?)` — `(pcm, sampleRate, channels)`, or `(nil, err)`.

## typed/builtin//modules/api/engine/audio/audio/device {#typed-builtin-modules-api-engine-audio-audio-device}

```lua
audio.device() -> AudioDeviceStatus
```

What the engine's audio output is doing: `state` is `"open"` while a
stream is running on an output device and `"silent"` while none is, and
`device` names the device an open stream runs on. The counters record
what the engine has been through keeping one open — `faults` a live
stream reported, `changes` of the host's default output, `reopens` the
engine made, `failedOpens` the platform refused, and the `glitches` a
listener heard as dropouts, with `lastError` carrying what the platform
said. A device that goes away leaves the mixer running and the engine
opening a stream again as soon as one is there.

**Returns** `AudioDeviceStatus` — An `AudioDeviceStatus`.

```lua
local d = audio.device(); print(d.state, d.device, d.reopens)
```

## typed/builtin//modules/api/engine/audio/audio/encode {#typed-builtin-modules-api-engine-audio-audio-encode}

```lua
audio.encode(sourceBytes: buffer | string, opts: { [string]: any }?) -> (string?, string?)
```

Encode container audio bytes (ogg / mp3 / wav / flac) into a ZAUD
payload. Every decoded sample must be finite; a source whose samples carry
a NaN or an infinity comes back as `(nil, err)` naming how many fail and
where the first one sits.

**Parameters**

- `sourceBytes` `buffer | string` — Encoded source audio bytes — a buffer or a binary string.
- `opts` `{ [string]: any }` _(optional)_ — `{ codec: "opus"|"pcm"?, bitrateKbps: number?, vbr: boolean?, sampleRate: number?, forceMono: boolean?, loopStart: number?, loopEnd: number? }`

**Returns** `(string?, string?)` — The ZAUD bytes, or `(nil, err)`.

```lua
local zaud = audio.encode(oggBytes, { bitrateKbps = 96 })
```

## typed/builtin//modules/api/engine/audio/audio/encodePcm {#typed-builtin-modules-api-engine-audio-audio-encodepcm}

```lua
audio.encodePcm(pcm: any?, sampleRate: number, channels: number, opts: { [string]: any }?) -> (string?, string?)
```

Encode raw interleaved f32 PCM into a ZAUD payload. Every sample must
be finite; a buffer carrying a NaN or an infinity comes back as
`(nil, err)` naming how many fail and where the first one sits, so a
filter that diverged over part of a bake is caught before it is written.
The sample count is a whole number of `channels`: a buffer with a tail
over comes back as `(nil, err)` naming the whole frames it holds and the
samples past them.

**Parameters**

- `pcm` `any` _(optional)_ — Interleaved f32 samples — a buffer or a binary string of
little-endian f32, the shape `microphone.samples` and `audio.decode` hand
back, or a flat number array. A byte payload's samples are its 4-byte
lanes, and a length that stops partway through one comes back as
`(nil, err)` naming the whole samples it holds and the bytes past them.
- `sampleRate` `number` — Source sample rate in Hz.
- `channels` `number` — 1 or 2, and a divisor of the sample count.
- `opts` `{ [string]: any }` _(optional)_ — Same shape as `audio.encode`.

**Returns** `(string?, string?)` — The ZAUD bytes, or `(nil, err)`.

```lua
local s = microphone.status(); local zaud = audio.encodePcm(microphone.samples(), s.sampleRate, 1)
```

## typed/builtin//modules/api/engine/audio/audio/info {#typed-builtin-modules-api-engine-audio-audio-info}

```lua
audio.info(zaud: buffer | string) -> (AudioInfo?, string?)
```

Read a ZAUD payload's header. A PCM payload's samples are its bytes, and
the header is read against them: a sample count differing from
`frames * channels` comes back as `(nil, err)` naming both counts, and a
sample carrying a NaN or an infinity comes back as `(nil, err)` naming how
many fail and where the first one sits, so the header handed back describes
a clip that is as long as it says and can sound. The header describes the
clip's shape — rate, channels, frames, duration, codec, loop points. What
the samples do where a whole-clip loop wraps is a reading of its own,
`audio.loopSeam`, which is the call that answers whether a bed cycles
without a click.

**Parameters**

- `zaud` `buffer | string` — A ZAUD payload — a buffer or a binary string.

**Returns** `(AudioInfo?, string?)` — An `AudioInfo` table, or `(nil, err)`.

```lua
local info = audio.info(zaud); print(info.durationMs)
```

## typed/builtin//modules/api/engine/audio/audio/levels {#typed-builtin-modules-api-engine-audio-audio-levels}

```lua
audio.levels() -> AudioLevels
```

The master mix's peak and RMS over the meter's most recent closed
window, measured without recording anything.

**Returns** `AudioLevels` — An `AudioLevels`.

```lua
local l = audio.levels(); print(l.peak, l.rms, l.windowMs)
```

## typed/builtin//modules/api/engine/audio/audio/listener {#typed-builtin-modules-api-engine-audio-audio-listener}

```lua
audio.listener() -> AudioListenerState
```

Where the scene is heard from, how many active listeners exist, and
which entity's listener drives the ears.

**Returns** `AudioListenerState` — An `AudioListenerState`.

```lua
local l = audio.listener(); print(l.present, l.count, l.entity)
```

## typed/builtin//modules/api/engine/audio/audio/loopSeam {#typed-builtin-modules-api-engine-audio-audio-loopseam}

```lua
audio.loopSeam(zaud: buffer | string) -> (AudioLoopSeam?, string?)
```

Measure what a clip's samples do where a whole-clip loop wraps, so a bed
can be judged before anyone hears it tick. The wrap's own step
(`|x[1] - x[frames]|`) is reported against the step the signal ordinarily
makes between neighbouring samples, as `ratio = step / meanStep` — a figure
in the units the signal itself moves in, so a quiet ambience and a loud
drone are read the same way. A bed whose partials wrap reads near 1; one
carrying a strike at its head and silence at its tail reads in the tens.
`ratio` and the `step` / `meanStep` / `maxStep` beside it belong to the
worst channel, `channel` names it, and `channels` carries every channel's
own reading. `seamless` is `ratio <= threshold`, the same threshold
`asset.create("soundClip", ...)` warns past. The reading is taken on the
DECODED samples, so it answers for what the codec left behind and for a
clip that arrived already encoded and whose source buffer nobody holds.
Costs a decode of the whole payload; `audio.info` reads a header without
one.

**Parameters**

- `zaud` `buffer | string` — A ZAUD payload — a buffer or a binary string.

**Returns** `(AudioLoopSeam?, string?)` — An `AudioLoopSeam` table, or `(nil, err)`.

```lua
local seam = audio.loopSeam(clipRef:getBytes()); print(seam.ratio, seam.seamless)
```

## typed/builtin//modules/api/engine/audio/audio/mixer {#typed-builtin-modules-api-engine-audio-audio-mixer}

```lua
audio.mixer() -> AudioMixerLevels
```

The levels the mixer is applying to the mix right now: the master
level, whether the mix is muted, and the level of every channel one has
been set on. A channel absent from `channels` plays at unity, so a
source naming it is heard at the volume it asks for.

**Returns** `AudioMixerLevels` — An `AudioMixerLevels`.

```lua
local m = audio.mixer(); print(m.master, m.muted, m.channels.music)
```

## typed/builtin//modules/api/engine/audio/audio/observe {#typed-builtin-modules-api-engine-audio-audio-observe}

```lua
audio.observe() -> AudioObservation
```

Report what the mixer is making audible right now, and why a source
is not. One read covering every live voice with the mixer's own playback
state and effective gain, the master mix's level, the mixer's voice
accounting, the listener, the output device the mix is reaching, and what
the subsystem costs. Answers in edit mode as well as play mode.

**Returns** `AudioObservation` — An `AudioObservation`.

```lua
local a = audio.observe(); print(a.audibleCount, a.levels.rms)
for _, v in audio.observe().voices do print(v.entity, v.mixerState, v.silence) end
```

## typed/builtin//modules/api/engine/audio/audio/peakSince {#typed-builtin-modules-api-engine-audio-audio-peaksince}

```lua
audio.peakSince(window: number) -> number?
```

The loudest peak the master mix reached across the meter's windows
that closed after its `windows` count stood at `window`. `audio.levels()`
carries the window that closed last, so a reader sees the windows its own
frames happen to land on; this spans all of them, which is what measuring
a sound shorter than the gap between two reads takes. Take the mark from
`audio.levels().windows` before the sound starts, wait until `windows` has
advanced past the sound's length, then read the span.

**Parameters**

- `window` `number` — A `windows` count taken from `audio.levels()` earlier.

**Returns** `number?` — The loudest window peak in the span, or nil when the meter holds no peak for it — nothing has closed since `window`, or the span reaches further back than the meter's history of recent windows, so a reader that came back too late learns that instead of reading the maximum of the part that survived.

```lua
local mark = audio.levels().windows
local peak = audio.peakSince(mark)
```

## typed/builtin//modules/api/engine/audio/audio/profile {#typed-builtin-modules-api-engine-audio-audio-profile}

```lua
audio.profile() -> AudioProfile
```

What the audio subsystem has cost since the profiling window opened —
the streaming pump, clip decode, clip encode, voice starts, and building
the observation itself. Every total is a SUM across that window rather
than a per-frame figure, and the window runs from the last
`audio.resetProfile()` or from engine start. For what a frame costs now,
reset, let frames pass, then divide by the `frames` the window reports.

**Returns** `AudioProfile` — An `AudioProfile`.

```lua
audio.resetProfile(); task.wait(1); local p = audio.profile()
print("per frame:", (p.pump.totalMs + p.observe.totalMs) / p.frames)
```

## typed/builtin//modules/api/engine/audio/audio/resetProfile {#typed-builtin-modules-api-engine-audio-audio-resetprofile}

```lua
audio.resetProfile()
```

Open a new audio profiling window, discarding what the previous one
measured. Call this before timing a stretch of frames: without it
`audio.profile()` reports totals reaching back to engine start.

```lua
audio.resetProfile()
```

## typed/builtin//modules/api/engine/audio/audio/setChannelVolume {#typed-builtin-modules-api-engine-audio-audio-setchannelvolume}

```lua
audio.setChannelVolume(channel: string, volume: number)
```

Set the level of one mixer channel — the `channel` an `Audio`
component names, such as "sfx", "music" or "ambient", or any name the
scene invents. It scales every voice on that channel and nothing else,
reaches voices that are already playing, and comes back per voice as
`gain.channel`. A channel no level has been set on plays at unity.

**Parameters**

- `channel` `string` — The channel name, matching `Audio.channel`.
- `volume` `number` — Channel level, 0..1.

```lua
audio.setChannelVolume("music", 0.3)
for _, v in audio.voices() do print(v.channel, v.gain.channel) end
```

## typed/builtin//modules/api/engine/audio/audio/setMasterVolume {#typed-builtin-modules-api-engine-audio-audio-setmastervolume}

```lua
audio.setMasterVolume(volume: number)
```

Set the master level of the mix, on the engine's 0..1 amplitude
scale. It scales every voice whatever channel it plays on, reaches
voices that are already playing, and comes back per voice as
`gain.master`.

**Parameters**

- `volume` `number` — Master level, 0..1.

```lua
audio.setMasterVolume(0.5)
```

## typed/builtin//modules/api/engine/audio/audio/setMuted {#typed-builtin-modules-api-engine-audio-audio-setmuted}

```lua
audio.setMuted(muted: boolean)
```

Silence or unsilence the whole mix. A muted mix sounds nothing
whatever its master and channel levels read, every voice reports
`masterSilent`, and unmuting hands the levels back untouched.

**Parameters**

- `muted` `boolean` — Whether the mix is silenced.

```lua
audio.setMuted(true)
```

## typed/builtin//modules/api/engine/audio/audio/voice {#typed-builtin-modules-api-engine-audio-audio-voice}

```lua
audio.voice(entityId: string) -> AudioVoice?
```

The voice on one entity, or nil when that entity carries no audio
source.

**Parameters**

- `entityId` `string` — The entity's stable id.

**Returns** `AudioVoice?` — An `AudioVoice`, or nil.

```lua
local v = audio.voice(e.id); print(v and v.mixerState)
```

## typed/builtin//modules/api/engine/audio/audio/voiceAccounting {#typed-builtin-modules-api-engine-audio-audio-voiceaccounting}

```lua
audio.voiceAccounting() -> AudioVoiceAccounting
```

How many voices the mixer can hold, how many are in use, how many
are free — read off the mixer's own tracks, so the free count is the one
a play call is granted or refused against. The two pools are reported
apart: `capacity` / `inUse` / `free` are the main track, which carries
the NON-spatial voices, while a spatial voice plays through its own
sub-track and is counted by `spatialInUse` instead. `sourcesHolding`
counts both pools from the sources that own them, so it equals
`inUse + spatialInUse` while every voice answers to a source.

**Returns** `AudioVoiceAccounting` — An `AudioVoiceAccounting`.

```lua
local v = audio.voiceAccounting(); print(v.inUse .. "/" .. v.capacity)
local v = audio.voiceAccounting(); print(v.sourcesHolding - (v.inUse + v.spatialInUse))
```

## typed/builtin//modules/api/engine/audio/audio/voices {#typed-builtin-modules-api-engine-audio-audio-voices}

```lua
audio.voices() -> { AudioVoice }
```

Every live audio source with the mixer's opinion of it.

**Returns** `{ AudioVoice }` — An array of `AudioVoice`.

```lua
for _, v in audio.voices() do print(v.clip, v.gain.effective) end
```

## typed/builtin//modules/api/engine/audio/audio/whySilent {#typed-builtin-modules-api-engine-audio-audio-whysilent}

```lua
audio.whySilent(entityId: string) -> (string?, string?)
```

Why the source on an entity is making no sound. Returns nil when it
IS sounding, and one of `noBackend`, `noDevice`, `notResident`, `neverStarted`,
`refused`, `paused`, `ended`, `gainZero`, `channelSilent`,
`masterSilent`, `outOfRange` when it is not — the nearest cause, so the
answer names the thing to change. A second
return carries the mixer's own words when it refused the source, and
`"no audio source on this entity"` when nothing there plays at all.

**Parameters**

- `entityId` `string` — The entity's stable id.

**Returns** `(string?, string?)` — `(reason, detail)`.

```lua
local why = audio.whySilent(e.id); if why then print(why) end
```
