On this page
tecs.audio
Clips, voices, groups, limits, and entity-owned sound.
Application exposes one Audio as app.audio. A platform without an output device still provides the object, but available reports false and playback returns handle zero.
Playback
load returns a Future for one shared clip per path. Starting the load never blocks the frame. Reading a pending future's value does, so that dereference is for startup, tools, and tests rather than a frame.
local step <const> = app.audio:load("assets/sfx/step.ogg").value
app.audio:setLimit(
"footstep",
{
voices = 3,
cooldown = 0.05,
}
)
local voice <const> = app.audio:play(
step,
{
gain = 0.7,
group = "sfx",
key = "footstep",
pitchVariance = 0.1,
}
)
if voice ~= 0 then
app.audio:stop(voice, 0.2)
endClips shorter than streamSeconds stay resident and share decoded samples. Longer clips stream separately for each voice. LoadOptions.stream overrides that choice. Use Audio.decoders() when a game needs to report the formats available in the current build.
A stale voice handle never controls a later voice that reuses its slot. Commands ignore stale handles and queries return false or nil.
Groups and limits
A group controls gain, mute, pause, resume, and stop. Its settings also apply to voices that join later. A key controls admission through a concurrent voice limit and cooldown. One voice may use both.
Snapshots retain master and group settings. Pitch variance uses the world's snapshotted tecs.audio random stream.
Placement
setPosition uses right-handed coordinates with a listener fixed at the origin. The game chooses the listener, subtracts its position, and converts world units to an audio scale. setStereo assigns explicit left and right gains. The latest placement call replaces the earlier mode.
Entity-owned sound
An entity can carry Sound instead of keeping a voice handle. The audio system starts it, follows writable playback fields, and stops it when the component or entity disappears. Write through world:getMut. A direct FFI write needs world:markComponentDirty, and batchSpawn must initialize every field.
Module contents
Constructors
| Constructor | Description |
|---|---|
newAudio |
Builds the mixer a game plays sound through, opening the platform's default output. |
Types
| Type | Kind | Description |
|---|---|---|
Audio |
record | Represents one audio output and the voices sounding on it. |
Clip |
type | A loaded clip, or one still loading. |
Config |
type | Configures newAudio. |
Device |
record | Describes a physical device before the game opens it. |
Limit |
type | Defines how many voices a key allows and how soon it may repeat. |
LoadOptions |
type | Configures Audio:load. |
Microphone |
record | An open recording device, read by polling. |
MicrophoneConfig |
record | Configures openMicrophone. |
PlayOptions |
type | Configures Audio:play. |
Sound |
record | Represents a sound attached to an entity. |
VoiceInfo |
type | Describes one voice returned by Audio:voices. |
Functions
| Function | Kind | Description |
|---|---|---|
openMicrophone |
Static | Opens a microphone as interleaved native-endian 32-bit float samples. |
playbackDevices |
Static | Returns the physical playback devices attached now. |
recordingDevices |
Static | Returns the physical recording devices attached now. |
Constructors
tecs.audio.newAudio Static
Builds the mixer a game plays sound through, opening the platform's default output.
Never raises for want of hardware. A machine with no sound card gets an object whose calls all succeed and produce nothing, because few games require an audio device while many test machines lack one.
Arguments
| Name | Type | Description |
|---|---|---|
config |
Audio.Config |
Omitted takes the defaults. maxVoices outside 1 to 65535 raises, which is the one field here that does; every other one out of range is the platform's to refuse. |
Returns
| Type | Description |
|---|---|
Audio |
The mixer, which the caller has to destroy. Check available for whether an output actually opened; it is false on a machine with no sound, and every call still succeeds and is silent. |
Types
tecs.audio.Audio record
Represents one audio output and the voices sounding on it.
Units, in one place, because none of them is in a signature:
- Time is seconds everywhere on this surface: a fade, a seek, a loop point, a cooldown, a clip's duration. Milliseconds appear only below the platform seam. Every fade is a duration to run for, never a moment to finish at, so calling one twice restarts it rather than moving a deadline.
- Gain is linear amplitude, not decibels. 0 is silence and 1 is the sound as recorded. Above 1 is louder and may clip. The level a voice reaches the hardware at is its own gain times its group's gain times the master gain, and a mute at either level composes as a zero without discarding the gain underneath it.
- Pitch is a playback rate, not an interval. 1 preserves the original rate, 2 is an octave up and half as long, 0.5 an octave down and twice as long. It resamples, so it changes duration; there is no time-stretch here.
- Position is the mixer's frame, not the world's. The mixer fixes the listener at the origin. x is positive to the right, y is positive up, and z is positive behind. World Y runs down, so a caller feeding world coordinates in has to flip Y as well as subtract whatever it decided is listening. See
Sound.
A voice handle is a generation and a slot packed into one integer. It stays meaningful for exactly as long as the voice sounds. When a voice ends naturally or either stop or update ends it, every call taking the handle becomes a no-op, and it never starts naming the next voice to take that slot. Nothing requires release, and holding a stale handle is not an error.
Nothing here is thread safe, and nothing here calls back. Everything runs on the thread that calls it.
record tecs.audio.Audio
record Config
frequency: integer
channels: integer
maxVoices: integer
streamSeconds: number
backend: Backend
device: number
end
record Clip
path: string
id: integer
status: string
error: string
duration: number
resident: boolean
end
record LoadOptions
stream: boolean
end
record PlayOptions
gain: number
loop: boolean
loopStart: number
start: number
fadeIn: number
pitch: number
pitchVariance: number
group: string
key: string
spatial: boolean
x: number
y: number
z: number
stereo: boolean
left: number
right: number
end
record Limit
voices: integer
cooldown: number
end
record VoiceInfo
handle: integer
clip: string
gain: number
applied: number
pitch: number
group: string
key: string
paused: boolean
stopping: boolean
owned: boolean
loop: boolean
spatial: boolean
x: number
y: number
z: number
stereo: boolean
left: number
right: number
end
record Sound is Component
clip: number
playing: number
gain: number
loop: number
pitch: number
spatial: number
x: number
y: number
z: number
group: number
voice: number
end
available: boolean
clipId: function(path: string): integer
clipPath: function(id: integer): string
decoders: function(): {string}
groupId: function(name: string): integer
groupName: function(id: integer): string
of: function(world: types.World): Audio
clearSpatial: function(self, handle: integer)
clip: function(self, id: integer): Audio.Clip
clips: function(self): {Audio.Clip}
destroy: function(self)
groupGain: function(self, name: string): number
groupMuted: function(self, name: string): boolean
groupPaused: function(self, name: string): boolean
groups: function(self): {string}
install: function(self, world: types.World)
keyCount: function(self, key: string): integer
keys: function(self): {string}
limit: function(self, key: string): Audio.Limit
load: function(
self, path: string, options: Audio.LoadOptions
): Future<Audio.Clip>
loading: function(self): integer
looping: function(self, handle: integer): boolean
masterGain: function(self): number
maxVoices: function(self): integer
muted: function(self): boolean
pause: function(self, handle: integer)
paused: function(self, handle: integer): boolean
pauseGroup: function(self, name: string)
play: function(
self, clip: Audio.Clip, options: Audio.PlayOptions
): integer
playing: function(self, handle: integer): boolean
reload: function(self, path: string): boolean, string
resume: function(self, handle: integer)
resumeGroup: function(self, name: string)
seek: function(self, handle: integer, seconds: number): boolean
setGain: function(self, handle: integer, gain: number)
setGroupGain: function(self, name: string, gain: number)
setGroupMuted: function(self, name: string, muted: boolean)
setLimit: function(self, key: string, limit: Audio.Limit)
setLoop: function(self, handle: integer, loop: boolean)
setMasterGain: function(self, gain: number)
setMuted: function(self, muted: boolean)
setPitch: function(self, handle: integer, ratio: number)
setPosition: function(
self, handle: integer, x: number, y: number, z: number
)
setStereo: function(
self, handle: integer, left: number, right: number
)
sounding: function(self): integer
stop: function(self, handle: integer, fadeOut: number)
stopAll: function(self, fadeOut: number)
stopGroup: function(self, name: string, fadeOut: number)
tell: function(self, handle: integer): number
update: function(self, dt: number): integer
voices: function(self): {Audio.VoiceInfo}
waitForLoads: function(self, timeoutMs: number)
endtecs.audio.Audio.Config record
Configures newAudio through optional fields.
record tecs.audio.Audio.Config
frequency: integer
channels: integer
maxVoices: integer
streamSeconds: number
backend: Backend
device: number
endtecs.audio.Audio.Config.frequency field
Caller-writable. Sets the sample frequency in frames per second and defaults to 48000.
tecs.audio.Audio.Config.channels field
Caller-writable. Sets the number of output channels and defaults to two.
tecs.audio.Audio.Config.maxVoices field
Caller-writable. Sets how many voices may sound at once and defaults to 32.
tecs.audio.Audio.Config.streamSeconds field
Caller-writable. Sets the duration in seconds at which a clip streams instead of remaining resident. Defaults to 10.
tecs.audio.Audio.Config.streamSeconds: numbertecs.audio.Audio.Config.backend field
Caller-writable. Selects the output backend and defaults to the installed platform backend.
tecs.audio.Audio.Config.device field
Caller-writable. Selects a physical output device by an id from tecs.audio.playbackDevices. Omitted follows the operating-system default as it changes.
tecs.audio.Audio.Clip record
Represents one shared playable sound after its load.
load settles a future with this only after reading succeeds. clip also exposes the cached instance while its load is pending or after it fails, which is why the status and error remain visible. Every load for the same path shares one instance.
record tecs.audio.Audio.Clip
path: string
id: integer
status: string
error: string
duration: number
resident: boolean
endtecs.audio.Audio.Clip.path field
Read-only. Reports the path used to load the clip, exactly as given. This is a clip's identity, so two spellings of one file are two clips.
tecs.audio.Audio.Clip.id field
Read-only. Reports the path index carried by a Sound component.
tecs.audio.Audio.Clip.status field
Read-only. Reports "pending" while the loader reads the file, then "ready" or "failed", and "released" once the owning Audio is destroyed.
These four words reach an agent over JSON-RPC, through the audio debug tool's clip list, so they are a compatibility surface rather than identifiers this tree renames in one commit.
The first three are Future's, because a clip's first three states are its load's. "released" has no counterpart there: a released clip is a load that succeeded and then had its samples given back, and a future has no word for that because a future ends at the answer. "canceled" never appears here, because the only thing that gives up a clip's load is destroy, which has written "released" by then.
tecs.audio.Audio.Clip.error field
Read-only. Reports the error when loading fails.
tecs.audio.Audio.Clip.duration field
Read-only. Reports the audio duration in seconds, or zero when the file cannot provide one.
tecs.audio.Audio.Clip.resident field
Read-only. Reports whether decoded audio remains in memory. False identifies a clip each voice reads from the file for itself.
tecs.audio.Audio.LoadOptions record
Configures load through one optional field.
record tecs.audio.Audio.LoadOptions
stream: boolean
endtecs.audio.Audio.LoadOptions.stream field
Caller-writable. Forces streaming when true and residency when false. Left unset, the clip's duration decides against streamSeconds.
tecs.audio.Audio.LoadOptions.stream: booleantecs.audio.Audio.PlayOptions record
Configures play through optional fields. The whole table may be omitted.
Read once, when the voice starts. Changing the table afterwards reaches nothing: use setGain, setPitch, setLoop, setPosition and setStereo on the handle instead. The table is not retained, so one a caller may fill and reuse one table for every play.
record tecs.audio.Audio.PlayOptions
gain: number
loop: boolean
loopStart: number
start: number
fadeIn: number
pitch: number
pitchVariance: number
group: string
key: string
spatial: boolean
x: number
y: number
z: number
stereo: boolean
left: number
right: number
endtecs.audio.Audio.PlayOptions.gain field
Caller-writable. Sets linear gain from zero to one before group and master gains. Defaults to one.
tecs.audio.Audio.PlayOptions.gain: numbertecs.audio.Audio.PlayOptions.loop field
Caller-writable. Repeats playback until stopped and defaults to false.
tecs.audio.Audio.PlayOptions.loop: booleantecs.audio.Audio.PlayOptions.loopStart field
Caller-writable. Sets the position in seconds to which a repeat returns, so an intro can play once and the rest of it loop. Defaults to 0.
tecs.audio.Audio.PlayOptions.loopStart: numbertecs.audio.Audio.PlayOptions.start field
Caller-writable. Sets where the first pass begins in seconds and defaults to zero.
tecs.audio.Audio.PlayOptions.start: numbertecs.audio.Audio.PlayOptions.fadeIn field
Caller-writable. Sets the fade-in duration in seconds and defaults to zero.
tecs.audio.Audio.PlayOptions.fadeIn: numbertecs.audio.Audio.PlayOptions.pitch field
Caller-writable. Sets playback rate and defaults to one.
tecs.audio.Audio.PlayOptions.pitch: numbertecs.audio.Audio.PlayOptions.pitchVariance field
Caller-writable. Sets the fraction of pitch varied for each new voice. A value of 0.1 spreads voices over plus or minus a tenth. Defaults to 0.
tecs.audio.Audio.PlayOptions.pitchVariance: numbertecs.audio.Audio.PlayOptions.group field
Caller-writable. Selects the group this voice joins and defaults to none.
tecs.audio.Audio.PlayOptions.group: stringtecs.audio.Audio.PlayOptions.key field
Caller-writable. Selects the limit bucket counted by this voice and defaults to none.
tecs.audio.Audio.PlayOptions.key: stringtecs.audio.Audio.PlayOptions.spatial field
Caller-writable. Enables spatial positioning. See Sound for the coordinate system.
tecs.audio.Audio.PlayOptions.spatial: booleantecs.audio.Audio.PlayOptions.x field
Caller-writable. Sets the position right of the listener and defaults to zero.
tecs.audio.Audio.PlayOptions.x: numbertecs.audio.Audio.PlayOptions.y field
Caller-writable. Sets the position above the listener, which uses the opposite sign from world Y. Defaults to 0.
tecs.audio.Audio.PlayOptions.y: numbertecs.audio.Audio.PlayOptions.z field
Caller-writable. Sets the position behind the listener and defaults to zero.
tecs.audio.Audio.PlayOptions.z: numbertecs.audio.Audio.PlayOptions.stereo field
Caller-writable. Pins the voice to the front pair of speakers at left and right, which is a pan rather than a position. Ignored when spatial is set, because the mixer holds one placement per track. Both gains default to 1.
tecs.audio.Audio.PlayOptions.stereo: booleantecs.audio.Audio.PlayOptions.left field
Caller-writable. Sets left-speaker gain on the same linear scale as gain. Negative reads as silence and above 1 is louder. Defaults to 1.
tecs.audio.Audio.PlayOptions.left: numbertecs.audio.Audio.PlayOptions.right field
Caller-writable. Sets right-speaker gain on the same terms as left.
tecs.audio.Audio.PlayOptions.right: numbertecs.audio.Audio.Limit record
Defines what a key allows.
tecs.audio.Audio.Limit.voices field
Caller-writable. Sets how many voices this key may hold at once. Zero or absent removes the ceiling.
tecs.audio.Audio.Limit.cooldown field
Caller-writable. Sets the cooldown in seconds after a voice starts. Zero disables the cooldown.
tecs.audio.Audio.VoiceInfo record
Describes one sounding voice for inspection.
record tecs.audio.Audio.VoiceInfo
handle: integer
clip: string
gain: number
applied: number
pitch: number
group: string
key: string
paused: boolean
stopping: boolean
owned: boolean
loop: boolean
spatial: boolean
x: number
y: number
z: number
stereo: boolean
left: number
right: number
endtecs.audio.Audio.VoiceInfo.handle field
Read-only. Reports the handle accepted by playing and stop.
tecs.audio.Audio.VoiceInfo.clip field
Read-only. Reports the clip path, or nil after release.
tecs.audio.Audio.VoiceInfo.gain field
Read-only. Reports the gain requested by the voice before group and master gain.
tecs.audio.Audio.VoiceInfo.applied field
Read-only. Reports the gain applied to the track, including group gain and mute. The master gain is not in it: that is the mixer's own number and is not multiplied per voice.
tecs.audio.Audio.VoiceInfo.pitch field
Read-only. Reports the playback rate carried by the track, so the pitch variance a value already includes any variance that play drew rather than only the requested rate.
tecs.audio.Audio.VoiceInfo.group field
Read-only. Reports the group tag, or nil for no group.
tecs.audio.Audio.VoiceInfo.key field
Read-only. Reports the limit bucket, or nil for no bucket.
tecs.audio.Audio.VoiceInfo.paused field
Read-only. Reports whether its own pause or its group holds it.
tecs.audio.Audio.VoiceInfo.stopping field
Read-only. Reports whether a fade-out is stopping the voice.
tecs.audio.Audio.VoiceInfo.owned field
Read-only. Reports whether a Sound component started the voice instead of play.
tecs.audio.Audio.VoiceInfo.loop field
Read-only. Reports whether playback repeats at the end.
tecs.audio.Audio.VoiceInfo.spatial field
Read-only. Reports whether the voice has a spatial position. This remains false when stereo: the mixer holds one placement per track.
tecs.audio.Audio.VoiceInfo.x field
Read-only. Reports the last horizontal position pushed in the mixer's coordinate system, positive right. Meaningless unless spatial.
tecs.audio.Audio.VoiceInfo.y field
Read-only. Reports the last vertical position pushed, positive up. It has no meaning unless spatial.
tecs.audio.Audio.VoiceInfo.z field
Read-only. Reports the last depth position pushed, positive behind. It has no meaning unless spatial.
tecs.audio.Audio.VoiceInfo.stereo field
Read-only. Reports whether the voice remains pinned to the front speaker pair.
tecs.audio.Audio.VoiceInfo.left field
Read-only. Reports the last left-speaker gain pushed. It has no meaning unless stereo.
tecs.audio.Audio.VoiceInfo.right field
Read-only. Reports the last right-speaker gain pushed. It has no meaning unless stereo.
tecs.audio.Audio.Sound record
Represents a sound attached to an entity.
Presence is the instruction: an entity carrying this with a loaded clip starts sounding on the next audio pass, and stops when the component or the entity goes away. That is what makes sound an entity rather than a handle a game has to remember to release, and it is why despawning something mid-sound does the obvious thing.
The audio pass sends position to the mixer and does nothing else with it. spatial switches it on, and the mixer reads x, y and z in a right-handed system. The mixer fixes its listener at the origin, with x positive to the right, y positive up and z positive behind. So a caller with a camera and a world has three jobs this component does not do for it, and doing any of them here would fix answers that belong to a game:
- Subtract the listener. There is no listener component and no rule that the camera is one, so whatever a game decides is listening is what it subtracts before writing these fields.
- Choose a scale. The mixer attenuates with distance on its own, and how loud a sound a hundred world units away should be is a question about a game's units, not about audio.
- Decide what a screen-space sound means. A sound on a layer that does not move with the camera has no world position to convert, and the answer is to leave
spatialat zero.
A name identifies a group, just as a path identifies a clip. An FFI component holds numbers, so what group carries is an interned index from Audio.groupId rather than the mixer's tag itself, and Audio.groupName reads it back. The index is this run's and no other, which is why serialize writes the name: an integer in a save file would name a different group the next time a build interned its names in a different order.
The audio pass follows playing, gain, loop, pitch and the position for as long as the voice sounds, so writing any of them is enough and nothing has to restart the voice to apply a change. Each of those costs one read and compare per sounding row per frame, which is the price of the component being live and is why clip and group are not on the list: both take an untag, a retag and a fresh input to change, neither is something a game writes often, and following them would put that cost on every row that never does. Moving a sound into another group is setting group and clearing voice, which is the same thing changing its clip takes.
Disabling an entity silences its sound and re-enabling starts it again. Queries exclude Disabled unless they name it, so a disabled row is not followed and its voice is taken back; the audio pass clears the handle at the same moment, which is what lets the sound return with the entity rather than reading as a one-shot that had finished. Read-only. Exposes the Sound component for queries and writes. See the record's own documentation for what its fields mean.
record tecs.audio.Audio.Sound is Component
clip: number
playing: number
gain: number
loop: number
pitch: number
spatial: number
x: number
y: number
z: number
group: number
voice: number
endInterfaces
| Interface |
|---|
Component |
tecs.audio.Audio.Sound.clip field
Caller-writable. Selects a clip by its Audio.clipId index. Zero plays nothing.
tecs.audio.Audio.Sound.playing field
Caller-writable. Uses nonzero to request playback and zero to stop. This field gives an instruction rather than a report: clearing it stops the voice and setting it again starts one, including on a one-shot that has already run out.
tecs.audio.Audio.Sound.gain field
Caller-writable. Sets linear gain from zero to one before group and master gain.
tecs.audio.Audio.Sound.loop field
Caller-writable. Uses nonzero to repeat. Clearing it part way through lets the voice play out to its end rather than cutting it.
tecs.audio.Audio.Sound.pitch field
Caller-writable. Sets playback rate. One leaves it unchanged, while two plays an octave higher in half the time.
tecs.audio.Audio.Sound.spatial field
Caller-writable. Uses nonzero to read x, y, and z; zero mixes without a position.
tecs.audio.Audio.Sound.x field
Caller-writable. Sets the position right of the listener.
tecs.audio.Audio.Sound.y field
Caller-writable. Sets the position above the listener.
tecs.audio.Audio.Sound.z field
Caller-writable. Sets the position behind the listener.
tecs.audio.Audio.Sound.group field
Caller-writable. Selects a group by its Audio.groupId index. Zero joins no group.
tecs.audio.Audio.Sound.voice field
Engine-owned. Reports the voice assigned by the audio pass: zero before it starts and negative once a one-shot has finished. Written by the engine; setting it back to zero is how a game asks for the sound again.
tecs.audio.Audio.available field
Read-only. Reports whether an output opened. False identifies a machine with no sound, where every call here still works and nothing is heard.
tecs.audio.Audio.clipId Static
Returns the index of a clip path and assigns one on first use.
function tecs.audio.Audio.clipId(path: string): integerArguments
| Name | Type | Description |
|---|---|---|
path |
string |
Must be non-empty; an empty one raises. Not checked against the filesystem, so this hands out an index for a path that does not exist. |
Returns
| Type | Description |
|---|---|
integer |
An index from 1 up, shared by every Audio in the process. It belongs only to this run and means nothing in a file, which is why Sound serializes the path instead. |
tecs.audio.Audio.clipPath Static
Returns the path represented by a clip index, or nil.
function tecs.audio.Audio.clipPath(id: integer): stringArguments
| Name | Type | Description |
|---|---|---|
id |
integer |
Zero is the index of no clip and answers nil, as does any index never handed out. |
Returns
| Type | Description |
|---|---|
string |
tecs.audio.Audio.decoders Static
Returns the decoders linked into this build in mixer order.
What a build asked for and what it got are different questions: a decoder The mixer silently omits a decoder whose dependency the build could not find. This function answers the second question.
function tecs.audio.Audio.decoders(): {string}Arguments
None.
Returns
| Type | Description |
|---|---|
{string} |
Decoder names such as "WAV" and "OGG", in the mixer's own order rather than sorted, and empty when the mixer will not start at all. A fresh table each call. |
tecs.audio.Audio.groupId Static
Returns the index of a group name and assigns one on first use.
What a Sound carries in group. The index means nothing outside the run that handed it out, so it belongs in a component and never in a file.
function tecs.audio.Audio.groupId(name: string): integerArguments
| Name | Type | Description |
|---|---|---|
name |
string |
Must be non-empty; an empty one raises. A group needs no declaring: naming one here is all it takes to exist. |
Returns
| Type | Description |
|---|---|
integer |
An index from 1 up, shared by every Audio in the process. |
tecs.audio.Audio.groupName Static
Returns the name represented by a group index, or nil.
function tecs.audio.Audio.groupName(id: integer): stringArguments
| Name | Type | Description |
|---|---|---|
id |
integer |
Zero is the index of no group and answers nil, which is what a Sound in no group carries. |
Returns
| Type | Description |
|---|---|
string |
tecs.audio.Audio.of Static
Returns the audio installed into a world, or nil.
What lets something holding only the world reach the mixer, which is what the debug tools have and what a game writing its own systems often has too.
Arguments
| Name | Type | Description |
|---|---|---|
world |
types.World |
Returns
| Type | Description |
|---|---|
Audio |
nil before install and again after destroy, which removes the instance from every world. |
tecs.audio.Audio:clearSpatial Instance
Returns a voice to unpositioned mixing, out of either placement.
One call answers for both, because clearing either mode in the mixer clears the other. A no-op for a handle that names nothing and for a voice that was never placed.
function tecs.audio.Audio.clearSpatial(self, handle: integer)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
Returns
None.
tecs.audio.Audio:clip Instance
Returns the clip represented by an index, or nil when this instance has not loaded it.
Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
id |
integer |
From Audio.clipId, or from a Sound.clip field. Clip indices are every instance shares clip indices but not clips, so an index another instance loaded answers nil here. |
Returns
| Type | Description |
|---|---|
Audio.Clip |
tecs.audio.Audio:clips Instance
Returns every loaded clip in request order.
For introspection: it builds a list per call, so nothing on a frame's path should read it.
Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
Returns
| Type | Description |
|---|---|
{Audio.Clip} |
A fresh list each call, holding the live clip records rather than copies, so their status moves under a caller that keeps one. |
tecs.audio.Audio:destroy Instance
Stops everything and closes the output.
Blocks until this instance's loads have settled, since a clip arriving afterwards would hold a pointer into a library that has released everything it made. A load the wait ran out of time on is given up instead. This method stops every voice without a fade, moves every clip to "released", and removes this mixer from every world that installed it. There is no reopening: make a new instance instead.
function tecs.audio.Audio.destroy(self)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
Returns
None.
tecs.audio.Audio:groupGain Instance
Returns a group's gain, or one when none has been set.
The level, not what is audible: a muted group still answers with the gain an unmute would put back.
function tecs.audio.Audio.groupGain(self, name: string): numberArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
name |
string |
Returns
| Type | Description |
|---|---|
number |
1 for a group nothing has set, which is indistinguishable from one explicitly set to 1. |
tecs.audio.Audio:groupMuted Instance
Reports whether mute applies to a group.
function tecs.audio.Audio.groupMuted(self, name: string): booleanArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
name |
string |
Returns
| Type | Description |
|---|---|
boolean |
tecs.audio.Audio:groupPaused Instance
Returns whether a group holds its current and future voices.
function tecs.audio.Audio.groupPaused(self, name: string): booleanArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
name |
string |
Returns
| Type | Description |
|---|---|
boolean |
tecs.audio.Audio:groups Instance
Returns every group known to this instance, including any whose gain, mute, or pause has been set, and one a sounding voice is in. Sorted, so a caller reading it twice reads it the same way.
function tecs.audio.Audio.groups(self): {string}Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
Returns
| Type | Description |
|---|---|
{string} |
A fresh table each call, which the caller owns. A group whose only voice has ended and which nothing was set on drops out of it, so this is not a stable list of a game's groups. |
tecs.audio.Audio:install Instance
Adds the system that plays Sound components, and the snapshot handler that carries the mixer.
update is not added here. Reaping voices is not world work: it has to continue during a world pause, and an application drives it from the iteration instead.
Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
world |
types.World |
Repoints this instance's pitch variance at the world's tecs.audio random stream, so installing into a second world moves the variance to that one's. This interface expects one Audio per world. |
Returns
None.
tecs.audio.Audio:keyCount Instance
Returns how many voices a key holds now.
function tecs.audio.Audio.keyCount(self, key: string): integerArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
key |
string |
Returns
| Type | Description |
|---|---|
integer |
Voices counted against the key, paused and fading ones included, since a slot is not given back until the voice ends. 0 for a key that has never held one. |
tecs.audio.Audio:keys Instance
Returns every key with a limit or counted voice, sorted.
function tecs.audio.Audio.keys(self): {string}Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
Returns
| Type | Description |
|---|---|
{string} |
A fresh table each call, which the caller owns. A key keeps its bucket once one exists, so a key that has played and stopped is still listed. |
tecs.audio.Audio:limit Instance
Returns the limit assigned to a key, or nil.
Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
key |
string |
Returns
| Type | Description |
|---|---|
Audio.Limit |
The record that was set, not a copy, so writing through it changes the limit in force. |
tecs.audio.Audio:load Instance
Queues a sound for loading and returns a future for its clip.
Loading the same path twice returns distinct futures over one load and one eventual clip; a clip is the file, and playing it twice over is two voices reading one clip. Canceling one caller's future does not cancel another or the cached load. options.stream overrides the duration threshold that otherwise decides whether the clip is held in memory.
function tecs.audio.Audio.load(
self, path: string, options: Audio.LoadOptions
): Future<Audio.Clip>Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
path |
string |
Must be non-empty. Not checked here: a path that does not exist returns a failed future a frame or two later, so this never raises for a missing file. |
options |
Audio.LoadOptions |
Returns
| Type | Description |
|---|---|
Future<Audio.Clip> |
A caller-owned Future that settles with the cached clip after the load succeeds. Concurrent calls return distinct futures whose ready values are the same table. A later call returns an already-settled future. Only the first call reads options. |
tecs.audio.Audio:loading Instance
Returns the number of loads this instance is still waiting on.
function tecs.audio.Audio.loading(self): integerArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
Returns
| Type | Description |
|---|---|
integer |
Loads not yet settled, which counts loads rather than clips. A failed load counts as settled, so zero means every load has an answer and not that every answer was a success. Zero after waitForLoads is what tells a finished wait from a timed-out one. |
tecs.audio.Audio:looping Instance
Returns whether a voice repeats at the end.
function tecs.audio.Audio.looping(self, handle: integer): booleanArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
Returns
| Type | Description |
|---|---|
boolean |
What this layer last told the mixer, not a reading from it. false for a handle that names nothing. |
tecs.audio.Audio:masterGain Instance
Returns master gain, whether or not mute holds it down.
function tecs.audio.Audio.masterGain(self): numberArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
Returns
| Type | Description |
|---|---|
number |
tecs.audio.Audio:maxVoices Instance
Returns the configured maximum number of simultaneous voices.
function tecs.audio.Audio.maxVoices(self): integerArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
Returns
| Type | Description |
|---|---|
integer |
The ceiling play refuses at, fixed for the instance's life. Not a hardware limit: it is what newAudio was given, defaulting to 32. |
tecs.audio.Audio:muted Instance
Returns whether master mute holds the output down.
function tecs.audio.Audio.muted(self): booleanArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
Returns
| Type | Description |
|---|---|
boolean |
tecs.audio.Audio:pause Instance
Holds a voice where it is. It keeps its slot until something resumes or stops it, because a paused voice has not finished.
Neither this nor resume takes a fade, and that is a decision rather than an omission. The mixer ramps on a play and on a stop and nowhere else, so a faded pause has to be a ramp run from here: a list of voices in flight, a setGain per voice per frame, and the pause itself issued only once the ramp reaches zero, which makes this a command that takes effect later and needs its own answer for what a stop, a group gain or a second pause during the ramp means. Each step also lands on a frame boundary, so a quarter-second fade at 60 frames a second is fifteen steps on a stream running at 48000, and its length follows the frame rate rather than the clock. Drift and cost of exactly that kind are what handing fades to the mixer avoids, and one voice held for a menu is not worth giving it up.
The mixer does offer the pieces to fade out and take a sound back where it left off: tell reads the position, stop fades out, and play returns with start and fadeIn. That re-reads the input rather than holding it, so it is a different thing from a pause, and which of the two a moment wants is a game's answer rather than this layer's.
function tecs.audio.Audio.pause(self, handle: integer)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
A no-op for a handle that names nothing, one already paused, and one fading out, since a paused fade would never finish. Not holder counted: one resume undoes any number of pause calls. |
Returns
None.
tecs.audio.Audio:paused Instance
Returns whether a handle names a paused voice.
function tecs.audio.Audio.paused(self, handle: integer): booleanArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
Returns
| Type | Description |
|---|---|
boolean |
true whether the hold came from pause on this handle or from pauseGroup on its group; the two are not told apart. false for a handle that names nothing. |
tecs.audio.Audio:pauseGroup Instance
Holds every voice in a group where it is, and every voice that joins it later.
The audio object records the pause as well as sending it, because the mixer's tag pause reaches only voices sounding at that moment. Without the record, a sound started into a paused group would be the one thing still heard.
function tecs.audio.Audio.pauseGroup(self, name: string)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
name |
string |
Not holder counted: one resumeGroup undoes any number of these. A voice resumed out of a paused group stays going, and the group's hold still applies to whatever joins next. |
Returns
None.
tecs.audio.Audio:play Instance
Plays clip, returning a handle, or zero when it did not start.
Zero means the clip is not loaded, loading failed, a key's limit or cooldown declined it, every voice is busy, or the machine has no output. None of those is worth raising over: a sound that does not play is not a reason for a frame to stop.
A key's limit drops the new voice; it never steals an older one. A sound that stops halfway through because something else started is harder to explain than one that never started, and the same reasoning is why a full voice pool declines rather than stealing.
function tecs.audio.Audio.play(
self, clip: Audio.Clip, options: Audio.PlayOptions
): integerArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
clip |
Audio.Clip |
Accepts nil and returns zero, so callers may pass a load result directly without a guard. |
options |
Audio.PlayOptions |
The method reads this once and follows no later changes. |
Returns
| Type | Description |
|---|---|
integer |
A handle for stop, playing, setGain and the rest, or zero when nothing started. It stays valid while the voice sounds and goes inert for good once it does not; it is never reissued for a later voice, and requires no release. |
tecs.audio.Audio:playing Instance
Returns whether a handle still names a sounding voice. A paused or fading voice still counts: it has not finished.
function tecs.audio.Audio.playing(self, handle: integer): booleanArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
Returns
| Type | Description |
|---|---|
boolean |
false for a handle whose voice has ended, for zero, and for one from another Audio. A voice that ended on its own reads true until the next update reaps it, since nothing here is told the instant it finished. |
tecs.audio.Audio:reload Instance
Re-reads a clip's file over the clip already loaded from it.
The counterpart to renderer.sprites:replaceImage, and it keeps identity on the same terms. A clip's index is its path's, so an edited file comes back under the index every Sound row already carries and nothing in the world is touched.
A streamed clip holds nothing to replace. Each voice opens the file for itself, so the next one to start reads what is on disk now and this has only to say so. Voices already sounding read the stream they opened, which is what happens to a file edited under a running voice with or without a reload.
The loader decodes a resident clip again and swaps it in. It stays resident whatever the new file's length would have chosen: rows already pointing at it were started against held samples, and turning it into a stream under them would change what a voice is, not what it sounds like. The clip it replaces is destroyed here, which is safe with voices still on it: the mixer counts a reference per track and frees at the last one, so a sound playing across the swap finishes on the samples it started with.
Blocking, like every other reload: it is a debug operation, and answering before the file has been read would report a success that had not happened yet.
function tecs.audio.Audio.reload(self, path: string): boolean, stringArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
path |
string |
The path originally used to load the clip. |
Returns
| Type | Description |
|---|---|
boolean |
Whether the clip was re-read, and a reason when it was not. |
string |
tecs.audio.Audio:resume Instance
Lets a paused voice carry on.
function tecs.audio.Audio.resume(self, handle: integer)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
A no-op for a handle that names nothing and for one that is not paused. Resumes a voice its group paused, and the group's hold does not put it back; a voice starting into that group afterwards is still held. |
Returns
None.
tecs.audio.Audio:resumeGroup Instance
Lets a paused group carry on, and lets later joiners start sounding.
function tecs.audio.Audio.resumeGroup(self, name: string)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
name |
string |
Returns
None.
tecs.audio.Audio:seek Instance
Moves a voice's read position, in seconds from the start of its clip.
False when the handle names nothing, or when the input cannot seek: a the decoder does not support seeking, or can only reach a time rather than an exact sample.
function tecs.audio.Audio.seek(
self, handle: integer, seconds: number
): booleanArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
|
seconds |
number |
From the start of the clip, not from where the voice is now, and not from the loop point. Converted through the track's own rate, so it lands on a sample frame rather than on a mixer tick. |
Returns
| Type | Description |
|---|---|
boolean |
false when the handle names nothing or the input refused the seek. A true does not promise the exact sample: some decoders reach only the nearest point they can. |
tecs.audio.Audio:setGain Instance
Sets a voice's gain, before its group and the master.
function tecs.audio.Audio.setGain(self, handle: integer, gain: number)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
A no-op for one that names nothing. |
gain |
number |
Linear amplitude, not decibels: 0 is silence, 1 is the clip as recorded, above 1 is louder and may clip. Takes effect on the next buffer the mixer fills rather than ramping, so a large jump on a sounding voice can be audible as a step. |
Returns
None.
tecs.audio.Audio:setGroupGain Instance
Scales every voice in a group, and every voice that joins it later.
Composed here rather than through the mixer's per-tag gain, which writes each tagged track's own gain and would overwrite what the voice asked for.
function tecs.audio.Audio.setGroupGain(self, name: string, gain: number)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
name |
string |
Needs no declaring, and setting a gain on a name nothing is in is how a game configures a group before anything joins it. Recorded against the name, so it outlives every voice that was in it. |
gain |
number |
Linear amplitude, multiplied with each voice's own gain. There is no removing it: set 1 to return the group to unscaled. |
Returns
None.
tecs.audio.Audio:setGroupMuted Instance
Silences a group without discarding the level it was set to.
Bookkeeping over the gains that already exist rather than anything the mixer receives: every voice in the group contributes zero while this holds, and an unmute puts each one back at its own gain times groupGain(name).
function tecs.audio.Audio.setGroupMuted(
self, name: string, muted: boolean
)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
name |
string |
|
muted |
boolean |
Returns
None.
tecs.audio.Audio:setLimit Instance
Caps how many voices a key may hold and how often it may start one.
A key is not a group. A group says where a sound's gain comes from and what a pause reaches; a key says how many of one sound the mix will carry. The two are set independently on play, so "at most three footsteps at once, all of them in the effects group" is the ordinary case and neither name has to know about the other.
Passing nil removes the limit. Voices already sounding are left alone.
When a key reaches its limit, play drops the new voice: it returns zero and leaves every sounding voice alone.
Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
key |
string |
|
limit |
Audio.Limit |
nil removes the limit. Lowering voices below what the key already holds does not stop any of them; it only refuses the next until enough have ended. The audio object measures cooldown from when a voice last started, against the time passed to update, so a game that never calls update never advances a cooldown. |
Returns
None.
tecs.audio.Audio:setLoop Instance
Changes whether a voice repeats, part way through.
The mixer replaces its starting repeat count, so clearing this on a looping piece of music lets it play out to its end rather than cutting it, and setting it on a one-shot keeps it going. It reaches a sounding voice only: a stopped one takes its count from the next play.
function tecs.audio.Audio.setLoop(self, handle: integer, loop: boolean)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
|
loop |
boolean |
true repeats forever; there is no finite repeat count here. Setting it does not move the loop point. The voice keeps the loopStart from play. |
Returns
None.
tecs.audio.Audio:setMasterGain Instance
Scales everything. One number on the mixer, so this costs the same whether one voice is sounding or every voice is.
Setting this while muted changes the level a later unmute returns to and nothing that is audible now, which is what a volume slider moved with the sound off should do.
function tecs.audio.Audio.setMasterGain(self, gain: number)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
gain |
number |
Linear amplitude, not decibels, on the same scale as a voice's: 0 silences, 1 leaves the mix as mixed, above 1 is louder and may clip. |
Returns
None.
tecs.audio.Audio:setMuted Instance
Silences everything without discarding the master gain.
The mixer's own number again, so it costs one call however many voices are sounding. It does not fan out to the groups: groupMuted answers "is this group silenced", and writing every group's bit here would overwrite the answers an unmute has to put back. That is the same loss as setting a gain to zero, which is what a mute exists instead of.
function tecs.audio.Audio.setMuted(self, muted: boolean)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
muted |
boolean |
Returns
None.
tecs.audio.Audio:setPitch Instance
Sets a voice's playback rate. A value of 1 preserves the original rate.
function tecs.audio.Audio.setPitch(self, handle: integer, ratio: number)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
|
ratio |
number |
A resampling rate, not an interval: 2 is an octave up and half as long, 0.5 an octave down and twice as long. It changes how long the clip takes, so a looped voice's period moves with it. |
Returns
None.
tecs.audio.Audio:setPosition Instance
Places a voice in space. See Sound for what the numbers mean.
Replaces a pan set by setStereo rather than combining with it.
Puts the voice in the mixer's 3D mode, which folds its input down to mono before placing it, so a stereo clip loses its stereo image when positioned.
function tecs.audio.Audio.setPosition(
self, handle: integer, x: number, y: number, z: number
)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
|
x |
number |
Positive to the right of the listener. The mixer fixes the listener at the origin. |
y |
number |
Positive above the listener. World Y runs down, so world coordinates need their Y negated on the way in. |
z |
number |
Positive behind the listener. |
Returns
None.
tecs.audio.Audio:setStereo Instance
Pins a voice to the front pair of speakers at explicit gains.
A pan rather than a position, and usually what a game laid out on a plane wants: there is no listener to subtract and no distance model to argue with, only "how much of this comes out of each side". left at 0.8 and right at 0.2 is a sound over to the left, whatever the speakers are. Negative reads as silence and above 1 is louder, on the same terms as a gain.
Replaces a position set by setPosition rather than combining with it: the mixer holds one placement per track, and each call writes it.
function tecs.audio.Audio.setStereo(
self, handle: integer, left: number, right: number
)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
|
left |
number |
Gain out of the left speaker, on the same linear scale as setGain. It multiplies with the voice, group and master gains rather than replacing any of them, so a pan of 1 and 1 is not silence. |
right |
number |
Gain out of the right speaker. There is no normalization between the two: 1 and 1 is the sound at full out of both sides, not half out of each. |
Returns
None.
tecs.audio.Audio:sounding Instance
Returns the number of voices sounding now, including paused and fading voices.
function tecs.audio.Audio.sounding(self): integerArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
Returns
| Type | Description |
|---|---|
integer |
Slots in use, so it drops only when update reaps a voice, not the instant one ends. play declines once it reaches maxVoices. |
tecs.audio.Audio:stop Instance
Stops a voice. A handle to one that already ended does nothing.
fadeOut seconds keeps the voice sounding while it fades, so playing stays true until update sees the mixer finish it.
function tecs.audio.Audio.stop(
self, handle: integer, fadeOut: number
)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
A stale one is a no-op, as is one already fading out: a second stop does not shorten a fade in progress. |
fadeOut |
number |
Seconds to ramp down over, a duration and not a moment to finish at. Omitted or zero stops at once and frees the slot within this call. The ramp is the mixer's, so it follows the audio clock rather than the frame rate. |
Returns
None.
tecs.audio.Audio:stopAll Instance
Stops everything, over fadeOut seconds if that is given.
A faded stop leaves each voice sounding until the mixer finishes it, so update reaps them instead of this call. The mixer owns the ramp, on the same terms as stop and stopGroup.
Reaches every voice, whichever group it is in and whether a Sound component started it or play did. A row still asking to sound starts a fresh voice on the next audio pass, so this silences a world rather than keeping it silent.
function tecs.audio.Audio.stopAll(self, fadeOut: number)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
fadeOut |
number |
Seconds, a duration. Omitted or zero frees every slot within this call. A positive one unpauses the voices it fades, because a paused voice does not advance and its fade would never finish. |
Returns
None.
tecs.audio.Audio:stopGroup Instance
Ends every voice in a group, over fadeOut seconds if that is given.
A faded stop leaves the voices sounding until they finish, so they are reaped by update rather than here.
Stops the voices, not the group: a gain, mute or pause set on the name survives, and anything that joins afterwards starts under them.
function tecs.audio.Audio.stopGroup(self, name: string, fadeOut: number)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
name |
string |
|
fadeOut |
number |
Seconds, a duration. Omitted or zero frees the slots within this call. A positive one unpauses the voices it fades, since a paused voice's fade would never finish. |
Returns
None.
tecs.audio.Audio:tell Instance
Returns the voice position in seconds, or nil.
Nil when the handle names nothing or the mixer cannot say. A paused voice reports where it stopped, which with seek and fadeIn is what taking a sound back where it left off is built from.
function tecs.audio.Audio.tell(self, handle: integer): numberArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
handle |
integer |
Returns
| Type | Description |
|---|---|
number |
Seconds from the start of the clip, so a looped voice's reading falls back at each repeat rather than accumulating. nil when the handle names nothing and nil when the mixer will not say, which are not told apart here. |
tecs.audio.Audio:update Instance
Takes finished loads and reaps the voices the mixer has finished with. Call once per frame, with the frame's step.
The step advances cooldowns. Nothing else here needs time: a fade is the mixer's to run, and a voice is over when the mixer says so rather than when a clock here says it should be.
Not a world system, and install deliberately does not add one: reaping has to continue during a world pause, so an application drives this from the iteration instead.
function tecs.audio.Audio.update(self, dt: number): integerArguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
dt |
number |
Seconds since the last call. The audio pass adds it to the clock that governs cooldowns. measured against. Omitting it advances nothing, which is what a test stepping voices without time wants. |
Returns
| Type | Description |
|---|---|
integer |
Voices still sounding after the reap, the same number sounding answers. |
tecs.audio.Audio:voices Instance
Returns every sounding voice, including paused and fading voices.
For introspection, on the same terms as clips. The handles it reports are the ones playing and stop take, so callers can act on these results.
Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
Returns
| Type | Description |
|---|---|
{Audio.VoiceInfo} |
A fresh list of fresh records each call, ordered by voice slot, which is not the order the voices started in. It is a snapshot: nothing in it follows the voice afterwards. |
tecs.audio.Audio:waitForLoads Instance
Blocks until every load this instance has queued has settled.
For startup and tests. A frame should let update resolve them instead. Every load settles, whatever it settled as, so one missing file does not leave the rest unwaited for.
Waiting drains the loader, so loads another subsystem queued settle here as well and a slow one elsewhere lengthens this wait. It cannot shorten it, and an instance with nothing in flight drains nothing.
function tecs.audio.Audio.waitForLoads(self, timeoutMs: number)Arguments
| Name | Type | Description |
|---|---|---|
self |
Audio |
|
timeoutMs |
number |
Milliseconds, unlike the seconds everything else on this surface takes, because it is a wait on the asset worker rather than an audio duration. Omitting it does not wait forever: it takes a 5000 ms default. Running out is not reported, so read loading afterwards to tell a finished wait from a timed-out one. |
Returns
None.
tecs.audio.Clip type
A loaded clip, or one still loading.
tecs.audio.Config type
Configures newAudio.
tecs.audio.Device record
Describes a physical device before the game opens it.
tecs.audio.Device.id field
Read-only. Reports the platform's numeric device identifier.
tecs.audio.Device.name field
Read-only. Reports the platform's display name for the device.
tecs.audio.Device.frequency field
Read-only. Reports the device's preferred samples per second.
tecs.audio.Device.channels field
Read-only. Reports the device's preferred channels per frame.
tecs.audio.Limit type
Defines how many voices a key allows and how soon it may repeat.
tecs.audio.LoadOptions type
Configures Audio:load.
type tecs.audio.LoadOptions = Audio.LoadOptionstecs.audio.Microphone record
An open recording device, read by polling.
record tecs.audio.Microphone
frequency: integer
channels: integer
availableFrames: function(self): integer
destroy: function(self)
pause: function(self): boolean, string
read: function(self, maxFrames: integer): string, string
resume: function(self): boolean, string
endtecs.audio.Microphone.frequency field
Read-only. Reports samples per second after SDL converts from whatever the device actually runs at. This is the requested value, not the device's own.
tecs.audio.Microphone.frequency: integertecs.audio.Microphone.channels field
Read-only. Reports interleaved channels per frame after conversion.
tecs.audio.Microphone.channels: integertecs.audio.Microphone:availableFrames Instance
Complete sample frames ready to read without blocking.
function tecs.audio.Microphone.availableFrames(self): integerArguments
| Name | Type | Description |
|---|---|---|
self |
Microphone |
Returns
| Type | Description |
|---|---|
integer |
Whole frames only, so a partly arrived frame is not counted and reads as zero until the rest of it lands. Zero once destroyed, rather than an error. |
tecs.audio.Microphone:destroy Instance
Stops capture and closes the recording device. Safe more than once.
Whatever was captured and not yet read is discarded with the stream, so a last read belongs before this rather than after.
function tecs.audio.Microphone.destroy(self)Arguments
| Name | Type | Description |
|---|---|---|
self |
Microphone |
Returns
None.
tecs.audio.Microphone:pause Instance
Stops the device filling the stream, keeping whatever is already in it.
function tecs.audio.Microphone.pause(self): boolean, stringArguments
| Name | Type | Description |
|---|---|---|
self |
Microphone |
Returns
| Type | Description |
|---|---|
boolean |
False with a reason on failure, including for a microphone already destroyed. Pausing one already paused succeeds. |
string |
The reason, when the first return is false. |
tecs.audio.Microphone:read Instance
Pulls up to maxFrames complete sample frames.
function tecs.audio.Microphone.read(
self, maxFrames: integer
): string, stringArguments
| Name | Type | Description |
|---|---|---|
self |
Microphone |
|
maxFrames |
integer |
Omit to take everything ready now. Zero is allowed and takes nothing. A limit above what is ready takes what is ready rather than waiting for the rest. |
Returns
| Type | Description |
|---|---|
string |
Interleaved native-endian float32 samples, as bytes in a string, channels * 4 per frame. An empty string means nothing was ready and is not a failure. Nil on one, with the reason beside it: a destroyed microphone, a maxFrames that is not a non-negative integer, or SDL's own error. |
string |
The reason, when the first return is nil. |
tecs.audio.Microphone:resume Instance
Starts the device filling the stream again after pause.
function tecs.audio.Microphone.resume(self): boolean, stringArguments
| Name | Type | Description |
|---|---|---|
self |
Microphone |
Returns
| Type | Description |
|---|---|
boolean |
False with a reason on failure, including for a microphone already destroyed. |
string |
The reason, when the first return is false. |
tecs.audio.MicrophoneConfig record
Configures openMicrophone.
tecs.audio.MicrophoneConfig.device field
Caller-writable. Selects a physical device id from recordingDevices. Omitted uses the current system default.
tecs.audio.MicrophoneConfig.device: numbertecs.audio.MicrophoneConfig.frequency field
Caller-writable. Sets samples per second after SDL converts the device input. Defaults to 48000.
tecs.audio.MicrophoneConfig.frequency: integertecs.audio.MicrophoneConfig.channels field
Caller-writable. Sets interleaved channels. Defaults to one.
tecs.audio.MicrophoneConfig.channels: integertecs.audio.PlayOptions type
Configures Audio:play.
type tecs.audio.PlayOptions = Audio.PlayOptionstecs.audio.Sound record
Represents a sound attached to an entity.
Presence is the instruction: an entity carrying this with a loaded clip starts sounding on the next audio pass, and stops when the component or the entity goes away. That is what makes sound an entity rather than a handle a game has to remember to release, and it is why despawning something mid-sound does the obvious thing.
The audio pass sends position to the mixer and does nothing else with it. spatial switches it on, and the mixer reads x, y and z in a right-handed system. The mixer fixes its listener at the origin, with x positive to the right, y positive up and z positive behind. So a caller with a camera and a world has three jobs this component does not do for it, and doing any of them here would fix answers that belong to a game:
- Subtract the listener. There is no listener component and no rule that the camera is one, so whatever a game decides is listening is what it subtracts before writing these fields.
- Choose a scale. The mixer attenuates with distance on its own, and how loud a sound a hundred world units away should be is a question about a game's units, not about audio.
- Decide what a screen-space sound means. A sound on a layer that does not move with the camera has no world position to convert, and the answer is to leave
spatialat zero.
A name identifies a group, just as a path identifies a clip. An FFI component holds numbers, so what group carries is an interned index from Audio.groupId rather than the mixer's tag itself, and Audio.groupName reads it back. The index is this run's and no other, which is why serialize writes the name: an integer in a save file would name a different group the next time a build interned its names in a different order.
The audio pass follows playing, gain, loop, pitch and the position for as long as the voice sounds, so writing any of them is enough and nothing has to restart the voice to apply a change. Each of those costs one read and compare per sounding row per frame, which is the price of the component being live and is why clip and group are not on the list: both take an untag, a retag and a fresh input to change, neither is something a game writes often, and following them would put that cost on every row that never does. Moving a sound into another group is setting group and clearing voice, which is the same thing changing its clip takes.
Disabling an entity silences its sound and re-enabling starts it again. Queries exclude Disabled unless they name it, so a disabled row is not followed and its voice is taken back; the audio pass clears the handle at the same moment, which is what lets the sound return with the entity rather than reading as a one-shot that had finished. Read-only. Exposes the Sound component that plays a clip from an entity.
record tecs.audio.Sound is Component
clip: number
playing: number
gain: number
loop: number
pitch: number
spatial: number
x: number
y: number
z: number
group: number
voice: number
endInterfaces
| Interface |
|---|
Component |
tecs.audio.Sound.clip field
Caller-writable. Selects a clip by its Audio.clipId index. Zero plays nothing.
tecs.audio.Sound.playing field
Caller-writable. Uses nonzero to request playback and zero to stop. This field gives an instruction rather than a report: clearing it stops the voice and setting it again starts one, including on a one-shot that has already run out.
tecs.audio.Sound.gain field
Caller-writable. Sets linear gain from zero to one before group and master gain.
tecs.audio.Sound.loop field
Caller-writable. Uses nonzero to repeat. Clearing it part way through lets the voice play out to its end rather than cutting it.
tecs.audio.Sound.pitch field
Caller-writable. Sets playback rate. One leaves it unchanged, while two plays an octave higher in half the time.
tecs.audio.Sound.spatial field
Caller-writable. Uses nonzero to read x, y, and z; zero mixes without a position.
tecs.audio.Sound.x field
Caller-writable. Sets the position right of the listener.
tecs.audio.Sound.y field
Caller-writable. Sets the position above the listener.
tecs.audio.Sound.z field
Caller-writable. Sets the position behind the listener.
tecs.audio.Sound.group field
Caller-writable. Selects a group by its Audio.groupId index. Zero joins no group.
tecs.audio.Sound.voice field
Engine-owned. Reports the voice assigned by the audio pass: zero before it starts and negative once a one-shot has finished. Written by the engine; setting it back to zero is how a game asks for the sound again.
tecs.audio.VoiceInfo type
Describes one voice returned by Audio:voices.
Functions
tecs.audio.openMicrophone Static
Opens a microphone as interleaved native-endian 32-bit float samples.
The API installs no callback. The audio thread fills its own stream and the game pulls completed bytes from the main thread with read.
function tecs.audio.openMicrophone(
config: platformaudio.MicrophoneConfig
): platformaudio.Microphone, stringArguments
| Name | Type | Description |
|---|---|---|
config |
platformaudio.MicrophoneConfig |
Omitted opens the default recording device at its own frequency and channel count. |
Returns
| Type | Description |
|---|---|
platformaudio.Microphone |
The open microphone, whose closing is the caller's through destroy. Nil on failure, and nothing is left open in that case. |
string |
The reason, when the first return is nil. |
tecs.audio.playbackDevices Static
Returns the physical playback devices attached now.
function tecs.audio.playbackDevices(): {platformaudio.Device}, stringArguments
None.
Returns
| Type | Description |
|---|---|
{platformaudio.Device} |
The devices, listed afresh each call and the caller's to keep. Empty rather than nil when the subsystem cannot start. |
string |
SDL's reason, when something went wrong. |
tecs.audio.recordingDevices Static
Returns the physical recording devices attached now.
function tecs.audio.recordingDevices(): {platformaudio.Device}, stringArguments
None.
Returns
| Type | Description |
|---|---|
{platformaudio.Device} |
The devices, listed afresh each call and the caller's to keep. Empty rather than nil when the subsystem cannot start. |
string |
SDL's reason, when something went wrong. |