On this page
  1. tecs.audio
  2. Playback
  3. Groups and limits
  4. Placement
  5. Entity-owned sound
  6. Module contents
    1. Constructors
    2. Types
    3. Functions
  7. Constructors
    1. newAudio
  8. Types
    1. Audio
    2. Clip
    3. Config
    4. Device
    5. Limit
    6. LoadOptions
    7. Microphone
    8. MicrophoneConfig
    9. PlayOptions
    10. Sound
    11. VoiceInfo
  9. Functions
    1. openMicrophone
    2. playbackDevices
    3. recordingDevices

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)
end

Clips 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.

function tecs.audio.newAudio(config: Audio.Config): Audio

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)
end

tecs.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
end

tecs.audio.Audio.Config.frequency field

Caller-writable. Sets the sample frequency in frames per second and defaults to 48000.

tecs.audio.Audio.Config.frequency: integer

tecs.audio.Audio.Config.channels field

Caller-writable. Sets the number of output channels and defaults to two.

tecs.audio.Audio.Config.channels: integer

tecs.audio.Audio.Config.maxVoices field

Caller-writable. Sets how many voices may sound at once and defaults to 32.

tecs.audio.Audio.Config.maxVoices: integer

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: number

tecs.audio.Audio.Config.backend field

Caller-writable. Selects the output backend and defaults to the installed platform backend.

tecs.audio.Audio.Config.backend: 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.Config.device: number

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
end

tecs.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.path: string

tecs.audio.Audio.Clip.id field

Read-only. Reports the path index carried by a Sound component.

tecs.audio.Audio.Clip.id: integer

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.status: string

tecs.audio.Audio.Clip.error field

Read-only. Reports the error when loading fails.

tecs.audio.Audio.Clip.error: string

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.duration: number

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.Clip.resident: boolean

tecs.audio.Audio.LoadOptions record

Configures load through one optional field.

record tecs.audio.Audio.LoadOptions
    stream: boolean
end

tecs.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: boolean

tecs.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
end

tecs.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: number

tecs.audio.Audio.PlayOptions.loop field

Caller-writable. Repeats playback until stopped and defaults to false.

tecs.audio.Audio.PlayOptions.loop: boolean

tecs.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: number

tecs.audio.Audio.PlayOptions.start field

Caller-writable. Sets where the first pass begins in seconds and defaults to zero.

tecs.audio.Audio.PlayOptions.start: number

tecs.audio.Audio.PlayOptions.fadeIn field

Caller-writable. Sets the fade-in duration in seconds and defaults to zero.

tecs.audio.Audio.PlayOptions.fadeIn: number

tecs.audio.Audio.PlayOptions.pitch field

Caller-writable. Sets playback rate and defaults to one.

tecs.audio.Audio.PlayOptions.pitch: number

tecs.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: number

tecs.audio.Audio.PlayOptions.group field

Caller-writable. Selects the group this voice joins and defaults to none.

tecs.audio.Audio.PlayOptions.group: string

tecs.audio.Audio.PlayOptions.key field

Caller-writable. Selects the limit bucket counted by this voice and defaults to none.

tecs.audio.Audio.PlayOptions.key: string

tecs.audio.Audio.PlayOptions.spatial field

Caller-writable. Enables spatial positioning. See Sound for the coordinate system.

tecs.audio.Audio.PlayOptions.spatial: boolean

tecs.audio.Audio.PlayOptions.x field

Caller-writable. Sets the position right of the listener and defaults to zero.

tecs.audio.Audio.PlayOptions.x: number

tecs.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: number

tecs.audio.Audio.PlayOptions.z field

Caller-writable. Sets the position behind the listener and defaults to zero.

tecs.audio.Audio.PlayOptions.z: number

tecs.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: boolean

tecs.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: number

tecs.audio.Audio.PlayOptions.right field

Caller-writable. Sets right-speaker gain on the same terms as left.

tecs.audio.Audio.PlayOptions.right: number

tecs.audio.Audio.Limit record

Defines what a key allows.

record tecs.audio.Audio.Limit
    voices: integer
    cooldown: number
end

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.voices: integer

tecs.audio.Audio.Limit.cooldown field

Caller-writable. Sets the cooldown in seconds after a voice starts. Zero disables the cooldown.

tecs.audio.Audio.Limit.cooldown: number

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
end

tecs.audio.Audio.VoiceInfo.handle field

Read-only. Reports the handle accepted by playing and stop.

tecs.audio.Audio.VoiceInfo.handle: integer

tecs.audio.Audio.VoiceInfo.clip field

Read-only. Reports the clip path, or nil after release.

tecs.audio.Audio.VoiceInfo.clip: string

tecs.audio.Audio.VoiceInfo.gain field

Read-only. Reports the gain requested by the voice before group and master gain.

tecs.audio.Audio.VoiceInfo.gain: number

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.applied: number

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.pitch: number

tecs.audio.Audio.VoiceInfo.group field

Read-only. Reports the group tag, or nil for no group.

tecs.audio.Audio.VoiceInfo.group: string

tecs.audio.Audio.VoiceInfo.key field

Read-only. Reports the limit bucket, or nil for no bucket.

tecs.audio.Audio.VoiceInfo.key: string

tecs.audio.Audio.VoiceInfo.paused field

Read-only. Reports whether its own pause or its group holds it.

tecs.audio.Audio.VoiceInfo.paused: boolean

tecs.audio.Audio.VoiceInfo.stopping field

Read-only. Reports whether a fade-out is stopping the voice.

tecs.audio.Audio.VoiceInfo.stopping: boolean

tecs.audio.Audio.VoiceInfo.owned field

Read-only. Reports whether a Sound component started the voice instead of play.

tecs.audio.Audio.VoiceInfo.owned: boolean

tecs.audio.Audio.VoiceInfo.loop field

Read-only. Reports whether playback repeats at the end.

tecs.audio.Audio.VoiceInfo.loop: boolean

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.spatial: boolean

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.x: number

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.y: number

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.z: number

tecs.audio.Audio.VoiceInfo.stereo field

Read-only. Reports whether the voice remains pinned to the front speaker pair.

tecs.audio.Audio.VoiceInfo.stereo: boolean

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.left: number

tecs.audio.Audio.VoiceInfo.right field

Read-only. Reports the last right-speaker gain pushed. It has no meaning unless stereo.

tecs.audio.Audio.VoiceInfo.right: number

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:

  1. 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.
  2. 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.
  3. 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 spatial at 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
end
Interfaces
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.clip: number

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.playing: number

tecs.audio.Audio.Sound.gain field

Caller-writable. Sets linear gain from zero to one before group and master gain.

tecs.audio.Audio.Sound.gain: number

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.loop: number

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.pitch: number

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.spatial: number

tecs.audio.Audio.Sound.x field

Caller-writable. Sets the position right of the listener.

tecs.audio.Audio.Sound.x: number

tecs.audio.Audio.Sound.y field

Caller-writable. Sets the position above the listener.

tecs.audio.Audio.Sound.y: number

tecs.audio.Audio.Sound.z field

Caller-writable. Sets the position behind the listener.

tecs.audio.Audio.Sound.z: number

tecs.audio.Audio.Sound.group field

Caller-writable. Selects a group by its Audio.groupId index. Zero joins no group.

tecs.audio.Audio.Sound.group: number

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.Sound.voice: number

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.available: boolean

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): integer
Arguments
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): string
Arguments
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): integer
Arguments
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): string
Arguments
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.

function tecs.audio.Audio.of(world: types.World): Audio
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.

function tecs.audio.Audio.clip(self, id: integer): Audio.Clip
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.

function tecs.audio.Audio.clips(self): {Audio.Clip}
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): number
Arguments
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): boolean
Arguments
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): boolean
Arguments
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.

function tecs.audio.Audio.install(self, world: types.World)
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): integer
Arguments
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.

function tecs.audio.Audio.limit(self, key: string): Audio.Limit
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): integer
Arguments
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): boolean
Arguments
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): number
Arguments
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): integer
Arguments
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): boolean
Arguments
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): boolean
Arguments
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
): integer
Arguments
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): boolean
Arguments
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, string
Arguments
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
): boolean
Arguments
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.

function tecs.audio.Audio.setLimit(
    self, key: string, limit: Audio.Limit
)
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): integer
Arguments
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): number
Arguments
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): integer
Arguments
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.

function tecs.audio.Audio.voices(self): {Audio.VoiceInfo}
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.

type tecs.audio.Clip = Audio.Clip

tecs.audio.Config type

Configures newAudio.

type tecs.audio.Config = Audio.Config

tecs.audio.Device record

Describes a physical device before the game opens it.

record tecs.audio.Device
    id: number
    name: string
    frequency: integer
    channels: integer
end

tecs.audio.Device.id field

Read-only. Reports the platform's numeric device identifier.

tecs.audio.Device.id: number

tecs.audio.Device.name field

Read-only. Reports the platform's display name for the device.

tecs.audio.Device.name: string

tecs.audio.Device.frequency field

Read-only. Reports the device's preferred samples per second.

tecs.audio.Device.frequency: integer

tecs.audio.Device.channels field

Read-only. Reports the device's preferred channels per frame.

tecs.audio.Device.channels: integer

tecs.audio.Limit type

Defines how many voices a key allows and how soon it may repeat.

type tecs.audio.Limit = Audio.Limit

tecs.audio.LoadOptions type

Configures Audio:load.

type tecs.audio.LoadOptions = Audio.LoadOptions

tecs.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
end

tecs.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: integer

tecs.audio.Microphone.channels field

Read-only. Reports interleaved channels per frame after conversion.

tecs.audio.Microphone.channels: integer

tecs.audio.Microphone:availableFrames Instance

Complete sample frames ready to read without blocking.

function tecs.audio.Microphone.availableFrames(self): integer
Arguments
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, string
Arguments
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, string
Arguments
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, string
Arguments
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.

record tecs.audio.MicrophoneConfig
    device: number
    frequency: integer
    channels: integer
end

tecs.audio.MicrophoneConfig.device field

Caller-writable. Selects a physical device id from recordingDevices. Omitted uses the current system default.

tecs.audio.MicrophoneConfig.device: number

tecs.audio.MicrophoneConfig.frequency field

Caller-writable. Sets samples per second after SDL converts the device input. Defaults to 48000.

tecs.audio.MicrophoneConfig.frequency: integer

tecs.audio.MicrophoneConfig.channels field

Caller-writable. Sets interleaved channels. Defaults to one.

tecs.audio.MicrophoneConfig.channels: integer

tecs.audio.PlayOptions type

Configures Audio:play.

type tecs.audio.PlayOptions = Audio.PlayOptions

tecs.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:

  1. 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.
  2. 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.
  3. 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 spatial at 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
end

Interfaces

Interface
Component

tecs.audio.Sound.clip field

Caller-writable. Selects a clip by its Audio.clipId index. Zero plays nothing.

tecs.audio.Sound.clip: number

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.playing: number

tecs.audio.Sound.gain field

Caller-writable. Sets linear gain from zero to one before group and master gain.

tecs.audio.Sound.gain: number

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.loop: number

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.pitch: number

tecs.audio.Sound.spatial field

Caller-writable. Uses nonzero to read x, y, and z; zero mixes without a position.

tecs.audio.Sound.spatial: number

tecs.audio.Sound.x field

Caller-writable. Sets the position right of the listener.

tecs.audio.Sound.x: number

tecs.audio.Sound.y field

Caller-writable. Sets the position above the listener.

tecs.audio.Sound.y: number

tecs.audio.Sound.z field

Caller-writable. Sets the position behind the listener.

tecs.audio.Sound.z: number

tecs.audio.Sound.group field

Caller-writable. Selects a group by its Audio.groupId index. Zero joins no group.

tecs.audio.Sound.group: number

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.Sound.voice: number

tecs.audio.VoiceInfo type

Describes one voice returned by Audio:voices.

type tecs.audio.VoiceInfo = Audio.VoiceInfo

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, string

Arguments

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}, string

Arguments

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}, string

Arguments

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.