
# tecs.audio


Clips, voices, groups, limits, and entity-owned sound.

[`Application`](/modules/Application/) exposes one
[`Audio`](/modules/audio/#tecs.audio.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`](/modules/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.

```teal
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`](/modules/audio/#tecs.audio.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`](/modules/audio/#tecs.audio.newAudio) | Builds the mixer a game plays sound through, opening the platform's default output. |

### Types

| Type | Kind | Description |
| --- | --- | --- |
| [`Audio`](/modules/audio/#tecs.audio.Audio) | <span class="tealdoc-kind-badge tealdoc-kind-record">record</span> | Represents one audio output and the voices sounding on it. |
| [`Clip`](/modules/audio/#tecs.audio.Clip) | <span class="tealdoc-kind-badge tealdoc-kind-type">type</span> | A loaded clip, or one still loading. |
| [`Config`](/modules/audio/#tecs.audio.Config) | <span class="tealdoc-kind-badge tealdoc-kind-type">type</span> | Configures newAudio. |
| [`Device`](/modules/audio/#tecs.audio.Device) | <span class="tealdoc-kind-badge tealdoc-kind-record">record</span> | Describes a physical device before the game opens it. |
| [`Limit`](/modules/audio/#tecs.audio.Limit) | <span class="tealdoc-kind-badge tealdoc-kind-type">type</span> | Defines how many voices a key allows and how soon it may repeat. |
| [`LoadOptions`](/modules/audio/#tecs.audio.LoadOptions) | <span class="tealdoc-kind-badge tealdoc-kind-type">type</span> | Configures Audio:load. |
| [`Microphone`](/modules/audio/#tecs.audio.Microphone) | <span class="tealdoc-kind-badge tealdoc-kind-record">record</span> | An open recording device, read by polling. |
| [`MicrophoneConfig`](/modules/audio/#tecs.audio.MicrophoneConfig) | <span class="tealdoc-kind-badge tealdoc-kind-record">record</span> | Configures openMicrophone. |
| [`PlayOptions`](/modules/audio/#tecs.audio.PlayOptions) | <span class="tealdoc-kind-badge tealdoc-kind-type">type</span> | Configures Audio:play. |
| [`Sound`](/modules/audio/#tecs.audio.Sound) | <span class="tealdoc-kind-badge tealdoc-kind-record">record</span> | Represents a sound attached to an entity. |
| [`VoiceInfo`](/modules/audio/#tecs.audio.VoiceInfo) | <span class="tealdoc-kind-badge tealdoc-kind-type">type</span> | Describes one voice returned by Audio:voices. |

### Functions

| Function | Kind | Description |
| --- | --- | --- |
| [`openMicrophone`](/modules/audio/#tecs.audio.openMicrophone) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Opens a microphone as interleaved native-endian 32-bit float samples. |
| [`playbackDevices`](/modules/audio/#tecs.audio.playbackDevices) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Returns the physical playback devices attached now. |
| [`recordingDevices`](/modules/audio/#tecs.audio.recordingDevices) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Returns the physical recording devices attached now. |

## Constructors

<a id="tecs.audio.newAudio"></a>
### tecs.audio.newAudio <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

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.



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

#### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `config` | [`Audio.Config`](/modules/audio/#tecs.audio.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`](/modules/audio/#tecs.audio.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

<a id="tecs.audio.Audio"></a>
### tecs.audio.Audio <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

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`](/modules/audio/#tecs.audio.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.


```teal
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
```

<a id="tecs.audio.Audio.Config"></a>
#### tecs.audio.Audio.Config <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

Configures `newAudio` through optional fields.


```teal
record tecs.audio.Audio.Config
    frequency: integer
    channels: integer
    maxVoices: integer
    streamSeconds: number
    backend: Backend
    device: number
end
```

<a id="tecs.audio.Audio.Config.frequency"></a>
##### tecs.audio.Audio.Config.frequency <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.Config.frequency: integer
```

<a id="tecs.audio.Audio.Config.channels"></a>
##### tecs.audio.Audio.Config.channels <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.Config.channels: integer
```

<a id="tecs.audio.Audio.Config.maxVoices"></a>
##### tecs.audio.Audio.Config.maxVoices <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.Config.maxVoices: integer
```

<a id="tecs.audio.Audio.Config.streamSeconds"></a>
##### tecs.audio.Audio.Config.streamSeconds <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Sets the duration in seconds at which a clip
streams instead of remaining resident.
Defaults to 10.


```teal
tecs.audio.Audio.Config.streamSeconds: number
```

<a id="tecs.audio.Audio.Config.backend"></a>
##### tecs.audio.Audio.Config.backend <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.Config.backend: Backend
```

<a id="tecs.audio.Audio.Config.device"></a>
##### tecs.audio.Audio.Config.device <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Selects a physical output device by an id from
`tecs.audio.playbackDevices`.
Omitted follows the operating-system default as it changes.


```teal
tecs.audio.Audio.Config.device: number
```

<a id="tecs.audio.Audio.Clip"></a>
#### tecs.audio.Audio.Clip <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

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.


```teal
record tecs.audio.Audio.Clip
    path: string
    id: integer
    status: string
    error: string
    duration: number
    resident: boolean
end
```

<a id="tecs.audio.Audio.Clip.path"></a>
##### tecs.audio.Audio.Clip.path <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Audio.Clip.path: string
```

<a id="tecs.audio.Audio.Clip.id"></a>
##### tecs.audio.Audio.Clip.id <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports the `path` index carried by a
[`Sound`](/modules/audio/#tecs.audio.Sound) component.


```teal
tecs.audio.Audio.Clip.id: integer
```

<a id="tecs.audio.Audio.Clip.status"></a>
##### tecs.audio.Audio.Clip.status <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports `"pending"` while the loader reads the file,
then `"ready"` or `"failed"`, and `"released"` once the owning
[`Audio`](/modules/audio/#tecs.audio.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`](/modules/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.


```teal
tecs.audio.Audio.Clip.status: string
```

<a id="tecs.audio.Audio.Clip.error"></a>
##### tecs.audio.Audio.Clip.error <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports the error when loading fails.


```teal
tecs.audio.Audio.Clip.error: string
```

<a id="tecs.audio.Audio.Clip.duration"></a>
##### tecs.audio.Audio.Clip.duration <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports the audio duration in seconds, or zero when
the file cannot provide one.


```teal
tecs.audio.Audio.Clip.duration: number
```

<a id="tecs.audio.Audio.Clip.resident"></a>
##### tecs.audio.Audio.Clip.resident <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports whether decoded audio remains in memory.
False identifies a clip each
voice reads from the file for itself.


```teal
tecs.audio.Audio.Clip.resident: boolean
```

<a id="tecs.audio.Audio.LoadOptions"></a>
#### tecs.audio.Audio.LoadOptions <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

Configures `load` through one optional field.


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

<a id="tecs.audio.Audio.LoadOptions.stream"></a>
##### tecs.audio.Audio.LoadOptions.stream <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Forces streaming when true and residency when
false. Left unset,
the clip's duration decides against `streamSeconds`.


```teal
tecs.audio.Audio.LoadOptions.stream: boolean
```

<a id="tecs.audio.Audio.PlayOptions"></a>
#### tecs.audio.Audio.PlayOptions <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

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.


```teal
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
```

<a id="tecs.audio.Audio.PlayOptions.gain"></a>
##### tecs.audio.Audio.PlayOptions.gain <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.PlayOptions.gain: number
```

<a id="tecs.audio.Audio.PlayOptions.loop"></a>
##### tecs.audio.Audio.PlayOptions.loop <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.PlayOptions.loop: boolean
```

<a id="tecs.audio.Audio.PlayOptions.loopStart"></a>
##### tecs.audio.Audio.PlayOptions.loopStart <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Audio.PlayOptions.loopStart: number
```

<a id="tecs.audio.Audio.PlayOptions.start"></a>
##### tecs.audio.Audio.PlayOptions.start <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.PlayOptions.start: number
```

<a id="tecs.audio.Audio.PlayOptions.fadeIn"></a>
##### tecs.audio.Audio.PlayOptions.fadeIn <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.PlayOptions.fadeIn: number
```

<a id="tecs.audio.Audio.PlayOptions.pitch"></a>
##### tecs.audio.Audio.PlayOptions.pitch <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.PlayOptions.pitch: number
```

<a id="tecs.audio.Audio.PlayOptions.pitchVariance"></a>
##### tecs.audio.Audio.PlayOptions.pitchVariance <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Audio.PlayOptions.pitchVariance: number
```

<a id="tecs.audio.Audio.PlayOptions.group"></a>
##### tecs.audio.Audio.PlayOptions.group <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.PlayOptions.group: string
```

<a id="tecs.audio.Audio.PlayOptions.key"></a>
##### tecs.audio.Audio.PlayOptions.key <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.PlayOptions.key: string
```

<a id="tecs.audio.Audio.PlayOptions.spatial"></a>
##### tecs.audio.Audio.PlayOptions.spatial <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Enables spatial positioning. See
[`Sound`](/modules/audio/#tecs.audio.Sound) for
the coordinate system.


```teal
tecs.audio.Audio.PlayOptions.spatial: boolean
```

<a id="tecs.audio.Audio.PlayOptions.x"></a>
##### tecs.audio.Audio.PlayOptions.x <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.PlayOptions.x: number
```

<a id="tecs.audio.Audio.PlayOptions.y"></a>
##### tecs.audio.Audio.PlayOptions.y <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Sets the position above the listener, which
uses the opposite sign from
world Y. Defaults to 0.


```teal
tecs.audio.Audio.PlayOptions.y: number
```

<a id="tecs.audio.Audio.PlayOptions.z"></a>
##### tecs.audio.Audio.PlayOptions.z <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.PlayOptions.z: number
```

<a id="tecs.audio.Audio.PlayOptions.stereo"></a>
##### tecs.audio.Audio.PlayOptions.stereo <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Audio.PlayOptions.stereo: boolean
```

<a id="tecs.audio.Audio.PlayOptions.left"></a>
##### tecs.audio.Audio.PlayOptions.left <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Audio.PlayOptions.left: number
```

<a id="tecs.audio.Audio.PlayOptions.right"></a>
##### tecs.audio.Audio.PlayOptions.right <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.PlayOptions.right: number
```

<a id="tecs.audio.Audio.Limit"></a>
#### tecs.audio.Audio.Limit <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

Defines what a key allows.


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

<a id="tecs.audio.Audio.Limit.voices"></a>
##### tecs.audio.Audio.Limit.voices <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Sets how many voices this key may hold at once.
Zero or absent removes the ceiling.


```teal
tecs.audio.Audio.Limit.voices: integer
```

<a id="tecs.audio.Audio.Limit.cooldown"></a>
##### tecs.audio.Audio.Limit.cooldown <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.Limit.cooldown: number
```

<a id="tecs.audio.Audio.VoiceInfo"></a>
#### tecs.audio.Audio.VoiceInfo <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

Describes one sounding voice for inspection.


```teal
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
```

<a id="tecs.audio.Audio.VoiceInfo.handle"></a>
##### tecs.audio.Audio.VoiceInfo.handle <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.VoiceInfo.handle: integer
```

<a id="tecs.audio.Audio.VoiceInfo.clip"></a>
##### tecs.audio.Audio.VoiceInfo.clip <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.VoiceInfo.clip: string
```

<a id="tecs.audio.Audio.VoiceInfo.gain"></a>
##### tecs.audio.Audio.VoiceInfo.gain <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.VoiceInfo.gain: number
```

<a id="tecs.audio.Audio.VoiceInfo.applied"></a>
##### tecs.audio.Audio.VoiceInfo.applied <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Audio.VoiceInfo.applied: number
```

<a id="tecs.audio.Audio.VoiceInfo.pitch"></a>
##### tecs.audio.Audio.VoiceInfo.pitch <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Audio.VoiceInfo.pitch: number
```

<a id="tecs.audio.Audio.VoiceInfo.group"></a>
##### tecs.audio.Audio.VoiceInfo.group <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.VoiceInfo.group: string
```

<a id="tecs.audio.Audio.VoiceInfo.key"></a>
##### tecs.audio.Audio.VoiceInfo.key <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.VoiceInfo.key: string
```

<a id="tecs.audio.Audio.VoiceInfo.paused"></a>
##### tecs.audio.Audio.VoiceInfo.paused <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.VoiceInfo.paused: boolean
```

<a id="tecs.audio.Audio.VoiceInfo.stopping"></a>
##### tecs.audio.Audio.VoiceInfo.stopping <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.VoiceInfo.stopping: boolean
```

<a id="tecs.audio.Audio.VoiceInfo.owned"></a>
##### tecs.audio.Audio.VoiceInfo.owned <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports whether a
[`Sound`](/modules/audio/#tecs.audio.Sound) component started the
voice instead of `play`.


```teal
tecs.audio.Audio.VoiceInfo.owned: boolean
```

<a id="tecs.audio.Audio.VoiceInfo.loop"></a>
##### tecs.audio.Audio.VoiceInfo.loop <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.VoiceInfo.loop: boolean
```

<a id="tecs.audio.Audio.VoiceInfo.spatial"></a>
##### tecs.audio.Audio.VoiceInfo.spatial <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports whether the voice has a spatial position.
This remains false when
`stereo`: the mixer holds one placement per track.


```teal
tecs.audio.Audio.VoiceInfo.spatial: boolean
```

<a id="tecs.audio.Audio.VoiceInfo.x"></a>
##### tecs.audio.Audio.VoiceInfo.x <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports the last horizontal position pushed in the
mixer's coordinate system, positive right.
Meaningless unless `spatial`.


```teal
tecs.audio.Audio.VoiceInfo.x: number
```

<a id="tecs.audio.Audio.VoiceInfo.y"></a>
##### tecs.audio.Audio.VoiceInfo.y <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports the last vertical position pushed, positive
up. It has no meaning unless `spatial`.


```teal
tecs.audio.Audio.VoiceInfo.y: number
```

<a id="tecs.audio.Audio.VoiceInfo.z"></a>
##### tecs.audio.Audio.VoiceInfo.z <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports the last depth position pushed, positive
behind. It has no meaning unless
`spatial`.


```teal
tecs.audio.Audio.VoiceInfo.z: number
```

<a id="tecs.audio.Audio.VoiceInfo.stereo"></a>
##### tecs.audio.Audio.VoiceInfo.stereo <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.VoiceInfo.stereo: boolean
```

<a id="tecs.audio.Audio.VoiceInfo.left"></a>
##### tecs.audio.Audio.VoiceInfo.left <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.VoiceInfo.left: number
```

<a id="tecs.audio.Audio.VoiceInfo.right"></a>
##### tecs.audio.Audio.VoiceInfo.right <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.VoiceInfo.right: number
```

<a id="tecs.audio.Audio.Sound"></a>
#### tecs.audio.Audio.Sound <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

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`](/modules/ecs/#tecs.ecs.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`](/modules/audio/#tecs.audio.Sound)
component for queries and writes. See
the record's own documentation for what its fields mean.


```teal
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`](/modules/ecs/#tecs.ecs.Component) |

<a id="tecs.audio.Audio.Sound.clip"></a>
##### tecs.audio.Audio.Sound.clip <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.Sound.clip: number
```

<a id="tecs.audio.Audio.Sound.playing"></a>
##### tecs.audio.Audio.Sound.playing <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Audio.Sound.playing: number
```

<a id="tecs.audio.Audio.Sound.gain"></a>
##### tecs.audio.Audio.Sound.gain <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.Sound.gain: number
```

<a id="tecs.audio.Audio.Sound.loop"></a>
##### tecs.audio.Audio.Sound.loop <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Uses nonzero to repeat. Clearing it part way through lets the voice play out
to its end rather than cutting it.


```teal
tecs.audio.Audio.Sound.loop: number
```

<a id="tecs.audio.Audio.Sound.pitch"></a>
##### tecs.audio.Audio.Sound.pitch <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Sets playback rate. One leaves it unchanged, while two
plays an octave higher in half the time.


```teal
tecs.audio.Audio.Sound.pitch: number
```

<a id="tecs.audio.Audio.Sound.spatial"></a>
##### tecs.audio.Audio.Sound.spatial <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.Sound.spatial: number
```

<a id="tecs.audio.Audio.Sound.x"></a>
##### tecs.audio.Audio.Sound.x <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.Sound.x: number
```

<a id="tecs.audio.Audio.Sound.y"></a>
##### tecs.audio.Audio.Sound.y <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Sets the position above the listener.


```teal
tecs.audio.Audio.Sound.y: number
```

<a id="tecs.audio.Audio.Sound.z"></a>
##### tecs.audio.Audio.Sound.z <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Sets the position behind the listener.


```teal
tecs.audio.Audio.Sound.z: number
```

<a id="tecs.audio.Audio.Sound.group"></a>
##### tecs.audio.Audio.Sound.group <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Audio.Sound.group: number
```

<a id="tecs.audio.Audio.Sound.voice"></a>
##### tecs.audio.Audio.Sound.voice <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Audio.Sound.voice: number
```

<a id="tecs.audio.Audio.available"></a>
#### tecs.audio.Audio.available <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports whether an output opened. False identifies a
machine with no sound, where
every call here still works and nothing is heard.


```teal
tecs.audio.Audio.available: boolean
```

<a id="tecs.audio.Audio.clipId"></a>
#### tecs.audio.Audio.clipId <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Returns the index of a clip path and assigns one on first use.



```teal
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`](/modules/audio/#tecs.audio.Audio) in the process. It belongs only to this run and means nothing in a file, which is why [`Sound`](/modules/audio/#tecs.audio.Sound) serializes the path instead. |

<a id="tecs.audio.Audio.clipPath"></a>
#### tecs.audio.Audio.clipPath <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Returns the path represented by a clip index, or nil.



```teal
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` |  |

<a id="tecs.audio.Audio.decoders"></a>
#### tecs.audio.Audio.decoders <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

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.



```teal
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. |

<a id="tecs.audio.Audio.groupId"></a>
#### tecs.audio.Audio.groupId <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Returns the index of a group name and assigns one on first use.

What a [`Sound`](/modules/audio/#tecs.audio.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.



```teal
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`](/modules/audio/#tecs.audio.Audio) in the process. |

<a id="tecs.audio.Audio.groupName"></a>
#### tecs.audio.Audio.groupName <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Returns the name represented by a group index, or nil.



```teal
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`](/modules/audio/#tecs.audio.Sound) in no group carries. |

##### Returns

| Type | Description |
| --- | --- |
| `string` |  |

<a id="tecs.audio.Audio.of"></a>
#### tecs.audio.Audio.of <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

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.



```teal
function tecs.audio.Audio.of(world: types.World): Audio
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `world` | [`types.World`](/modules/ecs/#tecs.World) |  |

##### Returns

| Type | Description |
| --- | --- |
| [`Audio`](/modules/audio/#tecs.audio.Audio) | nil before `install` and again after `destroy`, which removes the instance from every world. |

<a id="tecs.audio.Audio.clearSpatial"></a>
#### tecs.audio.Audio:clearSpatial <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.


```teal
function tecs.audio.Audio.clearSpatial(self, handle: integer)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |
| `handle` | `integer` |  |

##### Returns

None.

<a id="tecs.audio.Audio.clip"></a>
#### tecs.audio.Audio:clip <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns the clip represented by an index, or nil when this instance has not
loaded it.



```teal
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`](/modules/audio/#tecs.audio.Audio.Clip) |  |

<a id="tecs.audio.Audio.clips"></a>
#### tecs.audio.Audio:clips <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
function tecs.audio.Audio.clips(self): {Audio.Clip}
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |

##### Returns

| Type | Description |
| --- | --- |
| `{`[`Audio.Clip`](/modules/audio/#tecs.audio.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. |

<a id="tecs.audio.Audio.destroy"></a>
#### tecs.audio.Audio:destroy <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.


```teal
function tecs.audio.Audio.destroy(self)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |

##### Returns

None.

<a id="tecs.audio.Audio.groupGain"></a>
#### tecs.audio.Audio:groupGain <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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. |

<a id="tecs.audio.Audio.groupMuted"></a>
#### tecs.audio.Audio:groupMuted <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Reports whether mute applies to a group.


```teal
function tecs.audio.Audio.groupMuted(self, name: string): boolean
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |
| `name` | `string` |  |

##### Returns

| Type | Description |
| --- | --- |
| `boolean` |  |

<a id="tecs.audio.Audio.groupPaused"></a>
#### tecs.audio.Audio:groupPaused <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns whether a group holds its current and future voices.


```teal
function tecs.audio.Audio.groupPaused(self, name: string): boolean
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |
| `name` | `string` |  |

##### Returns

| Type | Description |
| --- | --- |
| `boolean` |  |

<a id="tecs.audio.Audio.groups"></a>
#### tecs.audio.Audio:groups <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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. |

<a id="tecs.audio.Audio.install"></a>
#### tecs.audio.Audio:install <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Adds the system that plays [`Sound`](/modules/audio/#tecs.audio.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.



```teal
function tecs.audio.Audio.install(self, world: types.World)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |
| `world` | [`types.World`](/modules/ecs/#tecs.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`](/modules/audio/#tecs.audio.Audio) per world. |

##### Returns

None.

<a id="tecs.audio.Audio.keyCount"></a>
#### tecs.audio.Audio:keyCount <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns how many voices a key holds now.



```teal
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. |

<a id="tecs.audio.Audio.keys"></a>
#### tecs.audio.Audio:keys <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns every key with a limit or counted voice, sorted.



```teal
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. |

<a id="tecs.audio.Audio.limit"></a>
#### tecs.audio.Audio:limit <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns the limit assigned to a key, or nil.



```teal
function tecs.audio.Audio.limit(self, key: string): Audio.Limit
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |
| `key` | `string` |  |

##### Returns

| Type | Description |
| --- | --- |
| [`Audio.Limit`](/modules/audio/#tecs.audio.Audio.Limit) | The record that was set, not a copy, so writing through it changes the limit in force. |

<a id="tecs.audio.Audio.load"></a>
#### tecs.audio.Audio:load <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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`](/modules/audio/#tecs.audio.Audio.LoadOptions) |  |

##### Returns

| Type | Description |
| --- | --- |
| [`Future`](/modules/Future/)`<`[`Audio.Clip`](/modules/audio/#tecs.audio.Audio.Clip)`>` | A caller-owned [`Future`](/modules/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`. |

<a id="tecs.audio.Audio.loading"></a>
#### tecs.audio.Audio:loading <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns the number of loads this instance is still waiting on.



```teal
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. |

<a id="tecs.audio.Audio.looping"></a>
#### tecs.audio.Audio:looping <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns whether a voice repeats at the end.



```teal
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. |

<a id="tecs.audio.Audio.masterGain"></a>
#### tecs.audio.Audio:masterGain <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns master gain, whether or not mute holds it down.


```teal
function tecs.audio.Audio.masterGain(self): number
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |

##### Returns

| Type | Description |
| --- | --- |
| `number` |  |

<a id="tecs.audio.Audio.maxVoices"></a>
#### tecs.audio.Audio:maxVoices <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns the configured maximum number of simultaneous voices.



```teal
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. |

<a id="tecs.audio.Audio.muted"></a>
#### tecs.audio.Audio:muted <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns whether master mute holds the output down.


```teal
function tecs.audio.Audio.muted(self): boolean
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |

##### Returns

| Type | Description |
| --- | --- |
| `boolean` |  |

<a id="tecs.audio.Audio.pause"></a>
#### tecs.audio.Audio:pause <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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.

<a id="tecs.audio.Audio.paused"></a>
#### tecs.audio.Audio:paused <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns whether a handle names a paused voice.



```teal
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. |

<a id="tecs.audio.Audio.pauseGroup"></a>
#### tecs.audio.Audio:pauseGroup <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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 `resume`d out of a paused group stays going, and the group's hold still applies to whatever joins next. |

##### Returns

None.

<a id="tecs.audio.Audio.play"></a>
#### tecs.audio.Audio:play <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
function tecs.audio.Audio.play(
    self, clip: Audio.Clip, options: Audio.PlayOptions
): integer
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |
| `clip` | [`Audio.Clip`](/modules/audio/#tecs.audio.Audio.Clip) | Accepts nil and returns zero, so callers may pass a `load` result directly without a guard. |
| `options` | [`Audio.PlayOptions`](/modules/audio/#tecs.audio.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. |

<a id="tecs.audio.Audio.playing"></a>
#### tecs.audio.Audio:playing <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns whether a handle still names a sounding voice. A paused or fading voice
still counts: it has not finished.



```teal
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`](/modules/audio/#tecs.audio.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. |

<a id="tecs.audio.Audio.reload"></a>
#### tecs.audio.Audio:reload <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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`](/modules/audio/#tecs.audio.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.


```teal
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` |  |

<a id="tecs.audio.Audio.resume"></a>
#### tecs.audio.Audio:resume <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Lets a paused voice carry on.



```teal
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.

<a id="tecs.audio.Audio.resumeGroup"></a>
#### tecs.audio.Audio:resumeGroup <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Lets a paused group carry on, and lets later joiners start sounding.


```teal
function tecs.audio.Audio.resumeGroup(self, name: string)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |
| `name` | `string` |  |

##### Returns

None.

<a id="tecs.audio.Audio.seek"></a>
#### tecs.audio.Audio:seek <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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. |

<a id="tecs.audio.Audio.setGain"></a>
#### tecs.audio.Audio:setGain <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Sets a voice's gain, before its group and the master.



```teal
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.

<a id="tecs.audio.Audio.setGroupGain"></a>
#### tecs.audio.Audio:setGroupGain <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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.

<a id="tecs.audio.Audio.setGroupMuted"></a>
#### tecs.audio.Audio:setGroupMuted <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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)`.


```teal
function tecs.audio.Audio.setGroupMuted(
    self, name: string, muted: boolean
)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |
| `name` | `string` |  |
| `muted` | `boolean` |  |

##### Returns

None.

<a id="tecs.audio.Audio.setLimit"></a>
#### tecs.audio.Audio:setLimit <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
function tecs.audio.Audio.setLimit(
    self, key: string, limit: Audio.Limit
)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |
| `key` | `string` |  |
| `limit` | [`Audio.Limit`](/modules/audio/#tecs.audio.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.

<a id="tecs.audio.Audio.setLoop"></a>
#### tecs.audio.Audio:setLoop <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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.

<a id="tecs.audio.Audio.setMasterGain"></a>
#### tecs.audio.Audio:setMasterGain <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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.

<a id="tecs.audio.Audio.setMuted"></a>
#### tecs.audio.Audio:setMuted <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.


```teal
function tecs.audio.Audio.setMuted(self, muted: boolean)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |
| `muted` | `boolean` |  |

##### Returns

None.

<a id="tecs.audio.Audio.setPitch"></a>
#### tecs.audio.Audio:setPitch <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Sets a voice's playback rate. A value of 1 preserves the original rate.



```teal
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.

<a id="tecs.audio.Audio.setPosition"></a>
#### tecs.audio.Audio:setPosition <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Places a voice in space. See [`Sound`](/modules/audio/#tecs.audio.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.



```teal
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.

<a id="tecs.audio.Audio.setStereo"></a>
#### tecs.audio.Audio:setStereo <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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.

<a id="tecs.audio.Audio.sounding"></a>
#### tecs.audio.Audio:sounding <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns the number of voices sounding now, including paused and fading
voices.



```teal
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`. |

<a id="tecs.audio.Audio.stop"></a>
#### tecs.audio.Audio:stop <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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.

<a id="tecs.audio.Audio.stopAll"></a>
#### tecs.audio.Audio:stopAll <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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`](/modules/audio/#tecs.audio.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.



```teal
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.

<a id="tecs.audio.Audio.stopGroup"></a>
#### tecs.audio.Audio:stopGroup <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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.

<a id="tecs.audio.Audio.tell"></a>
#### tecs.audio.Audio:tell <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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. |

<a id="tecs.audio.Audio.update"></a>
#### tecs.audio.Audio:update <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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. |

<a id="tecs.audio.Audio.voices"></a>
#### tecs.audio.Audio:voices <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
function tecs.audio.Audio.voices(self): {Audio.VoiceInfo}
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Audio` |  |

##### Returns

| Type | Description |
| --- | --- |
| `{`[`Audio.VoiceInfo`](/modules/audio/#tecs.audio.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. |

<a id="tecs.audio.Audio.waitForLoads"></a>
#### tecs.audio.Audio:waitForLoads <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.



```teal
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.

<a id="tecs.audio.Clip"></a>
### tecs.audio.Clip <span class="tealdoc-kind-badge tealdoc-kind-type">type</span>

A loaded clip, or one still loading.


```teal
type tecs.audio.Clip = Audio.Clip
```

<a id="tecs.audio.Config"></a>
### tecs.audio.Config <span class="tealdoc-kind-badge tealdoc-kind-type">type</span>

Configures `newAudio`.


```teal
type tecs.audio.Config = Audio.Config
```

<a id="tecs.audio.Device"></a>
### tecs.audio.Device <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

Describes a physical device before the game opens it.


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

<a id="tecs.audio.Device.id"></a>
#### tecs.audio.Device.id <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Device.id: number
```

<a id="tecs.audio.Device.name"></a>
#### tecs.audio.Device.name <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Device.name: string
```

<a id="tecs.audio.Device.frequency"></a>
#### tecs.audio.Device.frequency <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Device.frequency: integer
```

<a id="tecs.audio.Device.channels"></a>
#### tecs.audio.Device.channels <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Device.channels: integer
```

<a id="tecs.audio.Limit"></a>
### tecs.audio.Limit <span class="tealdoc-kind-badge tealdoc-kind-type">type</span>

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


```teal
type tecs.audio.Limit = Audio.Limit
```

<a id="tecs.audio.LoadOptions"></a>
### tecs.audio.LoadOptions <span class="tealdoc-kind-badge tealdoc-kind-type">type</span>

Configures `Audio:load`.


```teal
type tecs.audio.LoadOptions = Audio.LoadOptions
```

<a id="tecs.audio.Microphone"></a>
### tecs.audio.Microphone <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

An open recording device, read by polling.


```teal
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
```

<a id="tecs.audio.Microphone.frequency"></a>
#### tecs.audio.Microphone.frequency <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Microphone.frequency: integer
```

<a id="tecs.audio.Microphone.channels"></a>
#### tecs.audio.Microphone.channels <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Microphone.channels: integer
```

<a id="tecs.audio.Microphone.availableFrames"></a>
#### tecs.audio.Microphone:availableFrames <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Complete sample frames ready to read without blocking.



```teal
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. |

<a id="tecs.audio.Microphone.destroy"></a>
#### tecs.audio.Microphone:destroy <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

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.


```teal
function tecs.audio.Microphone.destroy(self)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Microphone` |  |

##### Returns

None.

<a id="tecs.audio.Microphone.pause"></a>
#### tecs.audio.Microphone:pause <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Stops the device filling the stream, keeping whatever is already in it.



```teal
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. |

<a id="tecs.audio.Microphone.read"></a>
#### tecs.audio.Microphone:read <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Pulls up to `maxFrames` complete sample frames.



```teal
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. |

<a id="tecs.audio.Microphone.resume"></a>
#### tecs.audio.Microphone:resume <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Starts the device filling the stream again after `pause`.



```teal
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. |

<a id="tecs.audio.MicrophoneConfig"></a>
### tecs.audio.MicrophoneConfig <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

Configures `openMicrophone`.


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

<a id="tecs.audio.MicrophoneConfig.device"></a>
#### tecs.audio.MicrophoneConfig.device <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.MicrophoneConfig.device: number
```

<a id="tecs.audio.MicrophoneConfig.frequency"></a>
#### tecs.audio.MicrophoneConfig.frequency <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.MicrophoneConfig.frequency: integer
```

<a id="tecs.audio.MicrophoneConfig.channels"></a>
#### tecs.audio.MicrophoneConfig.channels <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.MicrophoneConfig.channels: integer
```

<a id="tecs.audio.PlayOptions"></a>
### tecs.audio.PlayOptions <span class="tealdoc-kind-badge tealdoc-kind-type">type</span>

Configures `Audio:play`.


```teal
type tecs.audio.PlayOptions = Audio.PlayOptions
```

<a id="tecs.audio.Sound"></a>
### tecs.audio.Sound <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

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`](/modules/ecs/#tecs.ecs.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.


```teal
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`](/modules/ecs/#tecs.ecs.Component) |

<a id="tecs.audio.Sound.clip"></a>
#### tecs.audio.Sound.clip <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Sound.clip: number
```

<a id="tecs.audio.Sound.playing"></a>
#### tecs.audio.Sound.playing <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Sound.playing: number
```

<a id="tecs.audio.Sound.gain"></a>
#### tecs.audio.Sound.gain <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Sound.gain: number
```

<a id="tecs.audio.Sound.loop"></a>
#### tecs.audio.Sound.loop <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Uses nonzero to repeat. Clearing it part way through lets the voice play out
to its end rather than cutting it.


```teal
tecs.audio.Sound.loop: number
```

<a id="tecs.audio.Sound.pitch"></a>
#### tecs.audio.Sound.pitch <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Sets playback rate. One leaves it unchanged, while two
plays an octave higher in half the time.


```teal
tecs.audio.Sound.pitch: number
```

<a id="tecs.audio.Sound.spatial"></a>
#### tecs.audio.Sound.spatial <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Sound.spatial: number
```

<a id="tecs.audio.Sound.x"></a>
#### tecs.audio.Sound.x <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Sound.x: number
```

<a id="tecs.audio.Sound.y"></a>
#### tecs.audio.Sound.y <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Sets the position above the listener.


```teal
tecs.audio.Sound.y: number
```

<a id="tecs.audio.Sound.z"></a>
#### tecs.audio.Sound.z <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Sets the position behind the listener.


```teal
tecs.audio.Sound.z: number
```

<a id="tecs.audio.Sound.group"></a>
#### tecs.audio.Sound.group <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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


```teal
tecs.audio.Sound.group: number
```

<a id="tecs.audio.Sound.voice"></a>
#### tecs.audio.Sound.voice <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

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.


```teal
tecs.audio.Sound.voice: number
```

<a id="tecs.audio.VoiceInfo"></a>
### tecs.audio.VoiceInfo <span class="tealdoc-kind-badge tealdoc-kind-type">type</span>

Describes one voice returned by `Audio:voices`.


```teal
type tecs.audio.VoiceInfo = Audio.VoiceInfo
```

## Functions

<a id="tecs.audio.openMicrophone"></a>
### tecs.audio.openMicrophone <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

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



```teal
function tecs.audio.openMicrophone(
    config: platformaudio.MicrophoneConfig
): platformaudio.Microphone, string
```

#### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `config` | [`platformaudio.MicrophoneConfig`](/modules/audio/#tecs.audio.MicrophoneConfig) | Omitted opens the default recording device at its own frequency and channel count. |

#### Returns

| Type | Description |
| --- | --- |
| [`platformaudio.Microphone`](/modules/audio/#tecs.audio.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. |

<a id="tecs.audio.playbackDevices"></a>
### tecs.audio.playbackDevices <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Returns the physical playback devices attached now.



```teal
function tecs.audio.playbackDevices(): {platformaudio.Device}, string
```

#### Arguments

None.

#### Returns

| Type | Description |
| --- | --- |
| `{`[`platformaudio.Device`](/modules/audio/#tecs.audio.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. |

<a id="tecs.audio.recordingDevices"></a>
### tecs.audio.recordingDevices <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Returns the physical recording devices attached now.



```teal
function tecs.audio.recordingDevices(): {platformaudio.Device}, string
```

#### Arguments

None.

#### Returns

| Type | Description |
| --- | --- |
| `{`[`platformaudio.Device`](/modules/audio/#tecs.audio.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. |