# `tecs.platform.audiobackend`
The command and observation contract between Tecs audio policy and a mixer.
Tecs owns voice slots, groups, keyed limits and every decision about what
should sound. A backend owns decoding, device output and sample mixing, and
learns what Tecs decided from one array of plain commands per frame. Nothing
crosses the seam except that array, the finished handles drained back, and the
two flushing queries that need an answer in the same call.
Recording and device naming sit here for the same reason. A recording stream has
the playback hazard from the other side: captured frames arrive on a thread the
Lua virtual machine never created, so they reach Nupp only by a `read` on the
frame thread emptying a buffer the backend filled. Nothing on this contract is a
function the backend calls.
A command names its voice by the packed handle Tecs already hands to game code,
so a backend stores no slot table of its own and an observation about a voice
that has since been recycled is recognized as stale by the generation inside
the handle rather than by anything the backend tracked.
```nupp
local backend = tecs.platform.audiobackend.silent()
local buffer = {tecs.platform.audiobackend.newCommand()}
buffer[1].kind = tecs.platform.audiobackend.SET_MASTER_GAIN
buffer[1].gain = 0.5
backend.submit(buffer, 1)
```
## Constructors
### `newCommand` _constructor_
```nupp
function newCommand(): Command
```
Creates one zeroed command, for a caller filling a reusable buffer.
#### Returns
| Type | Description |
| --- | --- |
| `Command` | a command whose every field is at rest |
## Types
### `Backend` _record_
```nupp
record Backend
failure: (function(): string?)?
available: boolean
frequency: integer
channels: integer
submit: function(commands: {Command}, count: integer): nil
drain: function(out: {integer}): integer
loadClip: function(path: string, clip: integer, mode: LoadMode, streamSeconds: number): (ClipInfo?, string?)
releaseClip: function(clip: integer): nil
position: function(handle: integer): number?
seek: function(handle: integer, seconds: number): boolean
decoders: function(): {string}
close: function(): nil
end
```
Mixes sound for one output.
Every field is a plain function rather than a method, so a test stub is a
record of closures and nothing has to be subclassed to observe the command
stream Tecs produces.
#### Methods
##### `submit`
```nupp
submit: function(commands: {Command}, count: integer): nil
```
Read-only. Applies the first `count` commands of `commands` in order.
The array stays the caller's, and a backend copies whatever it needs
before returning.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `commands` | `{Command}` | |
| `count` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `drain`
```nupp
drain: function(out: {integer}): integer
```
Read-only. Fills `out` with the handles of voices that have finished
since the previous drain, and returns how many it wrote. Writes at most
`#out` when the array is preallocated by the caller.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `out` | `{integer}` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `loadClip`
```nupp
loadClip: function(path: string, clip: integer, mode: LoadMode, streamSeconds: number): (ClipInfo?, string?)
```
Read-only. Reads a file and holds it under `clip`, returning what the
load settled on, or nil and a reason.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string` | |
| `clip` | `integer` | |
| `mode` | `LoadMode` | |
| `streamSeconds` | `number` | |
###### Returns
| Type | Description |
| --- | --- |
| `ClipInfo?` | |
| `string?` | |
##### `releaseClip`
```nupp
releaseClip: function(clip: integer): nil
```
Read-only. Drops whatever a clip index holds. Voices already reading it
finish on the samples they started with.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `clip` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `position`
```nupp
position: function(handle: integer): number?
```
Read-only. Reports a voice's read position in seconds, or nil when the
handle names nothing and when the input will not say. Flushes queued
commands first, because the answer has to include them.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `handle` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `number?` | |
##### `seek`
```nupp
seek: function(handle: integer, seconds: number): boolean
```
Read-only. Moves a voice's read position to `seconds` from the start of
its clip, and reports whether the input accepted it. Flushes queued
commands first.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `handle` | `integer` | |
| `seconds` | `number` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `decoders`
```nupp
decoders: function(): {string}
```
Read-only. Reports the decoder names this build linked, in the
backend's own order.
###### Returns
| Type | Description |
| --- | --- |
| `{string}` | |
##### `close`
```nupp
close: function(): nil
```
Read-only. Stops everything and closes the output. Calling it twice is
harmless.
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
#### Fields
##### `failure`
```nupp
failure: (function(): string?)?
```
Read-only. Reports terminal stream failure, or nil while healthy. Omitted
by backends without a device. The frame thread polls; no callback enters Nupp.
##### `available`
```nupp
available: boolean
```
Read-only. Reports whether a real output opened. A false here still
takes every command and answers every query; nothing is heard.
##### `frequency`
```nupp
frequency: integer
```
Read-only. Reports the output frequency in frames per second.
##### `channels`
```nupp
channels: integer
```
Read-only. Reports the number of output channels.
### `Capture` _record_
```nupp
record Capture
failure: (function(): string?)?
frequency: integer
channels: integer
availableFrames: function(): integer
overruns: function(): integer
read: function(maxFrames: integer): (string?, string?)
readInto: function(out: {number}, maxFrames: integer): integer
write: function(samples: {number}, frames: integer): integer
pause: function(): (boolean, string?)
resume: function(): (boolean, string?)
close: function(): nil
end
```
Captures sound from one recording device, pulled rather than pushed.
Nothing arrives until a read asks for it. The backend fills a bounded buffer
from its own thread and this side empties it, which is the same shape the
playback seam has and for the same reason: a device thread must never enter
Nupp.
Every field is a plain function rather than a method, so a device-free test
is a record of closures.
#### Methods
##### `availableFrames`
```nupp
availableFrames: function(): integer
```
Read-only. Reports the complete frames ready to read without waiting.
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `overruns`
```nupp
overruns: function(): integer
```
Read-only. Reports the frames dropped because nothing read them in time,
counted since the capture opened.
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `read`
```nupp
read: function(maxFrames: integer): (string?, string?)
```
Read-only. Takes up to `maxFrames` frames as interleaved native-endian
float32 bytes, and returns nil with a reason when nothing could be read.
An empty string means nothing was ready and is not a failure.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `maxFrames` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | |
| `string?` | |
##### `readInto`
```nupp
readInto: function(out: {number}, maxFrames: integer): integer
```
Read-only. Takes up to `maxFrames` frames into `out` as interleaved
samples, writing no more than `#out` holds, and returns the frames
written.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `out` | `{number}` | |
| `maxFrames` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `write`
```nupp
write: function(samples: {number}, frames: integer): integer
```
Read-only. Puts `frames` interleaved frames in where a device would, and
returns how many were accepted. Only a device-free capture has anything
to feed it, and it is how a test drives this contract.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `samples` | `{number}` | |
| `frames` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `pause`
```nupp
pause: function(): (boolean, string?)
```
Read-only. Stops the device filling the buffer, keeping what is already
in it.
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
| `string?` | |
##### `resume`
```nupp
resume: function(): (boolean, string?)
```
Read-only. Starts the device filling the buffer again.
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
| `string?` | |
##### `close`
```nupp
close: function(): nil
```
Read-only. Stops capture and closes the device. Calling it twice is
harmless, and whatever was captured and not read goes with it.
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
#### Fields
##### `failure`
```nupp
failure: (function(): string?)?
```
Read-only. Reports terminal stream failure. Buffered frames may still drain;
resume must fail until the caller closes and reopens the capture.
##### `frequency`
```nupp
frequency: integer
```
Read-only. Reports the frames per second a read answers in, which is
what the caller asked for rather than what the hardware runs at.
##### `channels`
```nupp
channels: integer
```
Read-only. Reports the interleaved channels per frame a read answers
with, which is what the caller asked for rather than what the hardware
runs at.
### `ClipInfo` _record_
```nupp
record ClipInfo
duration: number
resident: boolean
end
```
Reports what a load settled on.
#### Fields
##### `duration`
```nupp
duration: number
```
Read-only. Reports the audio duration in seconds, and zero when the
input cannot say.
##### `resident`
```nupp
resident: boolean
```
Read-only. Reports whether decoded samples stay in memory and are
shared by every voice reading the clip.
### `Command` _record_
```nupp
record Command
kind: integer
handle: integer
clip: integer
flags: integer
gain: number
pitch: number
start: number
fade: number
loopStart: number
x: number
y: number
z: number
end
```
Requests one operation from a backend.
One record covers every operation, because the array crossing the seam has
to be one contiguous run of identical elements. A field a command does not
name carries whatever the previous use of that record left behind, so a
backend reads only the fields its `kind` claims.
#### Fields
##### `kind`
```nupp
kind: integer
```
Caller-writable. Selects the operation from this module's kind
constants.
##### `handle`
```nupp
handle: integer
```
Caller-writable. Names the voice as the packed handle Tecs issued, and
zero for an operation on the whole output.
##### `clip`
```nupp
clip: integer
```
Caller-writable. Selects the clip a `PLAY` reads, by the interned index
`tecs.audio.clipId` assigned.
##### `flags`
```nupp
flags: integer
```
Caller-writable. Carries the `LOOP`, `SPATIAL` and `STEREO` bits.
##### `gain`
```nupp
gain: number
```
Caller-writable. Sets linear amplitude for `PLAY`, `SET_GAIN` and
`SET_MASTER_GAIN`.
##### `pitch`
```nupp
pitch: number
```
Caller-writable. Sets the playback rate for `PLAY` and `SET_PITCH`.
##### `start`
```nupp
start: number
```
Caller-writable. Sets where a `PLAY` begins, in seconds.
##### `fade`
```nupp
fade: number
```
Caller-writable. Sets the fade duration in seconds, running in on a
`PLAY` and out on a `STOP`.
##### `loopStart`
```nupp
loopStart: number
```
Caller-writable. Sets the position in seconds a repeat returns to.
##### `x`
```nupp
x: number
```
Caller-writable. Sets the position right of the listener, or the
left-speaker gain for `SET_STEREO`.
##### `y`
```nupp
y: number
```
Caller-writable. Sets the position above the listener, or the
right-speaker gain for `SET_STEREO`.
##### `z`
```nupp
z: number
```
Caller-writable. Sets the position behind the listener.
### `Device` _record_
```nupp
record Device
id: integer
name: string
frequency: integer
channels: integer
end
```
Names one physical audio device.
The field names are a compatibility surface because a game may persist a
chosen device. `id`, `name`, `frequency`, and `channels` therefore keep
stable spellings.
#### Fields
##### `id`
```nupp
id: integer
```
Read-only. Reports the device's position in the listing that produced
it, from one up. Zero is never assigned and means "the platform's
default" wherever a device is selected, so an id is only meaningful for
the listing it came from.
##### `name`
```nupp
name: string
```
Read-only. Reports the platform's display name for the device. This is
the only durable way to name a device, because an id moves when a device
is attached or removed.
##### `frequency`
```nupp
frequency: integer
```
Read-only. Reports the device's preferred frames per second, and zero
when it will not say.
##### `channels`
```nupp
channels: integer
```
Read-only. Reports the device's preferred channels per frame, and zero
when it will not say.
### `Devices` _record_
```nupp
record Devices
playback: function(): ({Device}, string?)
recording: function(): ({Device}, string?)
open: function(
device: integer,
deviceName: string?,
frequency: integer,
channels: integer,
bufferFrames: integer
): (Capture?, string?)
end
```
Names the devices attached now and opens one for recording.
Separate from [`Backend`](tecs.platform.audiobackend.Backend) because
listing devices must not open a mixer, and because a game may want to name
devices before it decides to make any sound at all.
#### Methods
##### `playback`
```nupp
playback: function(): ({Device}, string?)
```
Read-only. Names every playback device attached now, as a snapshot
rather than a subscription. Returns an empty listing rather than nil
when nothing could be asked, with the reason beside it.
###### Returns
| Type | Description |
| --- | --- |
| `{Device}` | |
| `string?` | |
##### `recording`
```nupp
recording: function(): ({Device}, string?)
```
Read-only. Names every recording device attached now, on the same terms
as `playback`.
###### Returns
| Type | Description |
| --- | --- |
| `{Device}` | |
| `string?` | |
##### `open`
```nupp
open: function(
device: integer,
deviceName: string?,
frequency: integer,
channels: integer,
bufferFrames: integer
): (Capture?, string?)
```
Read-only. Opens a recording device as interleaved float32 samples at
`frequency` and `channels`, holding `bufferFrames` frames. A non-empty
`deviceName` selects a device and wins over `device`; neither given
takes the platform's default. Returns nil and a reason when nothing
opened.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `device` | `integer` | |
| `deviceName` | `string?` | |
| `frequency` | `integer` | |
| `channels` | `integer` | |
| `bufferFrames` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `Capture?` | |
| `string?` | |
### `LoadMode` _type_
```nupp
type LoadMode = "auto" | "resident" | "stream"
```
Selects residency for a load.
`"auto"` measures the clip's duration against the configured streaming
threshold, which is what a game that states no preference gets.
## Functions
### `silent` _function_
```nupp
function silent(): Backend
```
Creates a backend that accepts everything and produces no sound.
What a machine with no output device gets, and what a headless test gets
when it does not care about the command stream. Every call succeeds so that
a game needs no branch for the silent case.
#### Returns
| Type | Description |
| --- | --- |
| `Backend` | a backend reporting `available` as false |
### `silentDevices` _function_
```nupp
function silentDevices(): Devices
```
Creates a provider that names no devices and opens none.
What a build with no audio library gets, and what a device-free test that
wants the "nothing is attached" answer reaches for. Both listings are empty
with a reason, and `open` refuses rather than handing back a capture that
captures nothing, because a game asking for a microphone wants to know it
did not get one. That is the one place this contract does not follow
`silent`, which does hand back a mixer: a game that cannot make a noise
carries on, and a game that cannot hear has to be told.
#### Returns
| Type | Description |
| --- | --- |
| `Devices` | a provider whose listings are empty and whose open always refuses |
## Values
### `CLEAR_PLACEMENT` _variable_
```nupp
const CLEAR_PLACEMENT: integer
```
Returns a voice to unpositioned mixing.
### `LOOP` _variable_
```nupp
const LOOP: integer
```
Marks a command that repeats forever.
### `PAUSE` _variable_
```nupp
const PAUSE: integer
```
Holds a voice where it is.
### `PLAY` _variable_
```nupp
const PLAY: integer
```
Starts a voice on the named clip.
### `RESUME` _variable_
```nupp
const RESUME: integer
```
Lets a paused voice carry on.
### `SET_GAIN` _variable_
```nupp
const SET_GAIN: integer
```
Sets the amplitude a voice reaches the mix at.
### `SET_LOOP` _variable_
```nupp
const SET_LOOP: integer
```
Sets whether a voice repeats, reading the `LOOP` flag.
### `SET_MASTER_GAIN` _variable_
```nupp
const SET_MASTER_GAIN: integer
```
Scales the whole output.
### `SET_PITCH` _variable_
```nupp
const SET_PITCH: integer
```
Sets a voice's playback rate.
### `SET_POSITION` _variable_
```nupp
const SET_POSITION: integer
```
Places a voice at `x`, `y` and `z`.
### `SET_STEREO` _variable_
```nupp
const SET_STEREO: integer
```
Pins a voice to the front pair at the gains in `x` and `y`.
### `SPATIAL` _variable_
```nupp
const SPATIAL: integer
```
Marks a command carrying a position.
### `STEREO` _variable_
```nupp
const STEREO: integer
```
Marks a command carrying explicit speaker gains.
### `STOP` _variable_
```nupp
const STOP: integer
```
Ends a voice, over `fade` seconds when that is positive.
### `STREAM` _variable_
```nupp
const STREAM: integer
```
Marks a `PLAY` that reads its input from the file rather than from held
samples.