audio
The audio namespace — 61 functions.
globals/audio/decode
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
zaudbuffer | string— A ZAUD payload — a buffer or a binary string.
Returns (string?, number?, number?) — (pcm, sampleRate, channels), or (nil, err).
globals/audio/device
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.
local d = audio.device(); print(d.state, d.device, d.reopens)
globals/audio/encode
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
sourceBytesbuffer | 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).
local zaud = audio.encode(oggBytes, { bitrateKbps = 96 })
globals/audio/encodePcm
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
pcmany(optional) — Interleaved f32 samples — a buffer or a binary string of little-endian f32, the shapemicrophone.samplesandaudio.decodehand 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.sampleRatenumber— Source sample rate in Hz.channelsnumber— 1 or 2, and a divisor of the sample count.opts{ [string]: any }(optional) — Same shape asaudio.encode.
Returns (string?, string?) — The ZAUD bytes, or (nil, err).
local s = microphone.status(); local zaud = audio.encodePcm(microphone.samples(), s.sampleRate, 1)
globals/audio/info
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
zaudbuffer | string— A ZAUD payload — a buffer or a binary string.
Returns (AudioInfo?, string?) — An AudioInfo table, or (nil, err).
local info = audio.info(zaud); print(info.durationMs)
globals/audio/levels
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.
local l = audio.levels(); print(l.peak, l.rms, l.windowMs)
globals/audio/listener
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.
local l = audio.listener(); print(l.present, l.count, l.entity)
globals/audio/loopSeam
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
zaudbuffer | string— A ZAUD payload — a buffer or a binary string.
Returns (AudioLoopSeam?, string?) — An AudioLoopSeam table, or (nil, err).
local seam = audio.loopSeam(clipRef:getBytes()); print(seam.ratio, seam.seamless)
globals/audio/mixer
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.
local m = audio.mixer(); print(m.master, m.muted, m.channels.music)
globals/audio/observe
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.
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
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
windownumber— Awindowscount taken fromaudio.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.
local mark = audio.levels().windows
local peak = audio.peakSince(mark)
globals/audio/profile
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.
audio.resetProfile(); task.wait(1); local p = audio.profile()
print("per frame:", (p.pump.totalMs + p.observe.totalMs) / p.frames)
globals/audio/resetProfile
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.
audio.resetProfile()
globals/audio/setChannelVolume
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
channelstring— The channel name, matchingAudio.channel.volumenumber— Channel level, 0..1.
audio.setChannelVolume("music", 0.3)
for _, v in audio.voices() do print(v.channel, v.gain.channel) end
globals/audio/setMasterVolume
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
volumenumber— Master level, 0..1.
audio.setMasterVolume(0.5)
globals/audio/setMuted
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
mutedboolean— Whether the mix is silenced.
audio.setMuted(true)
globals/audio/voice
audio.voice(entityId: string) -> AudioVoice?
The voice on one entity, or nil when that entity carries no audio source.
Parameters
entityIdstring— The entity's stable id.
Returns AudioVoice? — An AudioVoice, or nil.
local v = audio.voice(e.id); print(v and v.mixerState)
globals/audio/voiceAccounting
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.
local v = audio.voiceAccounting(); print(v.inUse .. "/" .. v.capacity)
local v = audio.voiceAccounting(); print(v.sourcesHolding - (v.inUse + v.spatialInUse))
globals/audio/voices
audio.voices() -> { AudioVoice }
Every live audio source with the mixer's opinion of it.
Returns { AudioVoice } — An array of AudioVoice.
for _, v in audio.voices() do print(v.clip, v.gain.effective) end
globals/audio/whySilent
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
entityIdstring— The entity's stable id.
Returns (string?, string?) — (reason, detail).
local why = audio.whySilent(e.id); if why then print(why) end
modules/audio/README
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
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
zaudbuffer | string— A ZAUD payload — a buffer or a binary string.
modules/audio/device
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.
local d = audio.device(); print(d.state, d.device, d.reopens)
modules/audio/encode
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
sourceBytesbuffer | 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? }
local zaud = audio.encode(oggBytes, { bitrateKbps = 96 })
modules/audio/encodePcm
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
pcmany(optional) — Interleaved f32 samples — a buffer or a binary string of little-endian f32, the shapemicrophone.samplesandaudio.decodehand 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.sampleRatenumber— Source sample rate in Hz.channelsnumber— 1 or 2, and a divisor of the sample count.opts{ [string]: any }?(optional) — Same shape asaudio.encode.
local s = microphone.status(); local zaud = audio.encodePcm(microphone.samples(), s.sampleRate, 1)
modules/audio/info
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
zaudbuffer | string— A ZAUD payload — a buffer or a binary string.
local info = audio.info(zaud); print(info.durationMs)
modules/audio/levels
levels(): AudioLevels
The master mix's peak and RMS over the meter's most recent closed window, measured without recording anything.
local l = audio.levels(); print(l.peak, l.rms, l.windowMs)
modules/audio/listener
listener(): AudioListenerState
Where the scene is heard from, how many active listeners exist, and which entity's listener drives the ears.
local l = audio.listener(); print(l.present, l.count, l.entity)
modules/audio/loopSeam
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
zaudbuffer | string— A ZAUD payload — a buffer or a binary string.
local seam = audio.loopSeam(clipRef:getBytes()); print(seam.ratio, seam.seamless)
modules/audio/mixer
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.
local m = audio.mixer(); print(m.master, m.muted, m.channels.music)
modules/audio/observe
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.
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
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
windownumber— Awindowscount taken fromaudio.levels()earlier.
local mark = audio.levels().windows
local peak = audio.peakSince(mark)
modules/audio/profile
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.
audio.resetProfile(); task.wait(1); local p = audio.profile()
print("per frame:", (p.pump.totalMs + p.observe.totalMs) / p.frames)
modules/audio/resetProfile
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.
audio.resetProfile()
modules/audio/setChannelVolume
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
channelstring— The channel name, matchingAudio.channel.volumenumber— Channel level, 0..1.
audio.setChannelVolume("music", 0.3)
for _, v in audio.voices() do print(v.channel, v.gain.channel) end
modules/audio/setMasterVolume
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
volumenumber— Master level, 0..1.
audio.setMasterVolume(0.5)
modules/audio/setMuted
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
mutedboolean— Whether the mix is silenced.
audio.setMuted(true)
modules/audio/voice
voice(entityId: string): AudioVoice?
The voice on one entity, or nil when that entity carries no audio source.
Parameters
entityIdstring— The entity's stable id.
local v = audio.voice(e.id); print(v and v.mixerState)
modules/audio/voiceAccounting
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.
local v = audio.voiceAccounting(); print(v.inUse .. "/" .. v.capacity)
local v = audio.voiceAccounting(); print(v.sourcesHolding - (v.inUse + v.spatialInUse))
modules/audio/voices
voices(): { AudioVoice }
Every live audio source with the mixer's opinion of it.
for _, v in audio.voices() do print(v.clip, v.gain.effective) end
modules/audio/whySilent
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
entityIdstring— The entity's stable id.
local why = audio.whySilent(e.id); if why then print(why) end
typed/builtin//modules/api/engine/audio/audio/decode
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
zaudbuffer | 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
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.
local d = audio.device(); print(d.state, d.device, d.reopens)
typed/builtin//modules/api/engine/audio/audio/encode
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
sourceBytesbuffer | 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).
local zaud = audio.encode(oggBytes, { bitrateKbps = 96 })
typed/builtin//modules/api/engine/audio/audio/encodePcm
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
pcmany(optional) — Interleaved f32 samples — a buffer or a binary string of little-endian f32, the shapemicrophone.samplesandaudio.decodehand 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.sampleRatenumber— Source sample rate in Hz.channelsnumber— 1 or 2, and a divisor of the sample count.opts{ [string]: any }(optional) — Same shape asaudio.encode.
Returns (string?, string?) — The ZAUD bytes, or (nil, err).
local s = microphone.status(); local zaud = audio.encodePcm(microphone.samples(), s.sampleRate, 1)
typed/builtin//modules/api/engine/audio/audio/info
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
zaudbuffer | string— A ZAUD payload — a buffer or a binary string.
Returns (AudioInfo?, string?) — An AudioInfo table, or (nil, err).
local info = audio.info(zaud); print(info.durationMs)
typed/builtin//modules/api/engine/audio/audio/levels
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.
local l = audio.levels(); print(l.peak, l.rms, l.windowMs)
typed/builtin//modules/api/engine/audio/audio/listener
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.
local l = audio.listener(); print(l.present, l.count, l.entity)
typed/builtin//modules/api/engine/audio/audio/loopSeam
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
zaudbuffer | string— A ZAUD payload — a buffer or a binary string.
Returns (AudioLoopSeam?, string?) — An AudioLoopSeam table, or (nil, err).
local seam = audio.loopSeam(clipRef:getBytes()); print(seam.ratio, seam.seamless)
typed/builtin//modules/api/engine/audio/audio/mixer
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.
local m = audio.mixer(); print(m.master, m.muted, m.channels.music)
typed/builtin//modules/api/engine/audio/audio/observe
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.
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
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
windownumber— Awindowscount taken fromaudio.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.
local mark = audio.levels().windows
local peak = audio.peakSince(mark)
typed/builtin//modules/api/engine/audio/audio/profile
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.
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
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.
audio.resetProfile()
typed/builtin//modules/api/engine/audio/audio/setChannelVolume
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
channelstring— The channel name, matchingAudio.channel.volumenumber— Channel level, 0..1.
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
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
volumenumber— Master level, 0..1.
audio.setMasterVolume(0.5)
typed/builtin//modules/api/engine/audio/audio/setMuted
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
mutedboolean— Whether the mix is silenced.
audio.setMuted(true)
typed/builtin//modules/api/engine/audio/audio/voice
audio.voice(entityId: string) -> AudioVoice?
The voice on one entity, or nil when that entity carries no audio source.
Parameters
entityIdstring— The entity's stable id.
Returns AudioVoice? — An AudioVoice, or nil.
local v = audio.voice(e.id); print(v and v.mixerState)
typed/builtin//modules/api/engine/audio/audio/voiceAccounting
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.
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
audio.voices() -> { AudioVoice }
Every live audio source with the mixer's opinion of it.
Returns { AudioVoice } — An array of AudioVoice.
for _, v in audio.voices() do print(v.clip, v.gain.effective) end
typed/builtin//modules/api/engine/audio/audio/whySilent
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
entityIdstring— The entity's stable id.
Returns (string?, string?) — (reason, detail).
local why = audio.whySilent(e.id); if why then print(why) end