Log inGet started

microphone

The microphone namespace — the engine's Luau API reference for microphone.

The microphone namespace — 10 functions.

globals/microphone/awaitRunning

microphone.awaitRunning(timeout: number?) -> (MicState, string?)

Wait until the capture settles out of starting and permissionPending, and report where it landed. Returns as soon as the state settles, or when timeout seconds have passed, whichever comes first — a browser permission prompt nobody answers never settles, so the wait is always bounded.

Parameters

  • timeout number (optional) — Seconds to wait at most. Defaults to 10.

Returns (MicState, string?) — The state reached, and its reason where it has one.

microphone.start(); local state, why = microphone.awaitRunning()

globals/microphone/devices

microphone.devices() -> { MicDevice }

Every input device the platform offers. id is what microphone.start takes to select one and is stable across reboots where the platform provides a stable identifier; name is the label a person recognises.

An empty list is a legitimate answer, not a failure: a machine with no input hardware offers none, and a browser names none until microphone access has been granted at least once — the labels are part of what the permission protects.

Returns { MicDevice } — Array of { id, name, default }.

for _, d in ipairs(microphone.devices()) do print(d.name, d.default) end

globals/microphone/frequencies

microphone.frequencies() -> { number }

The frequency each spectrum bin is centred on, in Hz, as an array parallel to microphone.spectrum(). Derived from the capture's rate and transform size, so it changes only when a capture is started with different ones. Empty while no capture is running.

Returns { number } — Array of centre frequencies, one per bin.

local hz = microphone.frequencies(); print(hz[#hz]) -- the Nyquist frequency

globals/microphone/level

microphone.level() -> number

Loudness of the most recent analysis window, as an RMS amplitude in 0..1. A full-scale sine reads about 0.707 and silence reads 0.

Measured over only the samples that have arrived, so a capture that has just started reports the loudness of what it holds rather than a level diluted by a window it has not filled yet. 0 while no capture is running.

Returns number — RMS amplitude, 0..1.

if microphone.level() > 0.05 then print("someone is talking") end

globals/microphone/peak

microphone.peak() -> { [string]: number }?

The bin carrying the most energy and what it says: the frequency it is centred on, its amplitude, and the loudness of the whole window. A capture reading silence answers with amplitude 0 at bin 1.

Returns { [string]: number }?{ bin, hz, amplitude, level }, or nil while no capture is running.

local p = microphone.peak(); if p and p.amplitude > 0.05 then print(p.hz) end

globals/microphone/samples

microphone.samples(max: number?) -> buffer?

Captured mono PCM no caller has taken yet, oldest sample first, as a buffer of little-endian f32 read with buffer.readf32. The samples are removed, so successive calls walk forward through the capture and a caller doing its own analysis sees every frame once.

nil while no capture is running, and a zero-length buffer when the capture is running and nothing new has arrived. Samples nobody takes are discarded once the queue fills, and status().overruns counts every one.

Parameters

  • max number (optional) — How many samples to take at most. Omitted, everything held comes back.

Returns buffer? — Buffer of f32 samples, or nil when no capture is running.

local pcm = microphone.samples(); if pcm then print(buffer.len(pcm) // 4) end

globals/microphone/spectrum

microphone.spectrum() -> { number }

Amplitude per frequency bin over the most recent analysis window: fftSize / 2 + 1 numbers, DC at index 1 through the Nyquist frequency at the last. Bin i covers (i - 1) * status().binHz Hz.

Each value is an amplitude estimate rather than a raw transform magnitude, so a full-scale tone sitting on a bin centre reads about 1.0 and the numbers stay comparable across transform sizes.

The window is multiplied by a Hann taper before the transform. An untapered window ends abruptly at both edges and the transform reads that as energy spread across every bin, smearing one tone into a skirt that buries quieter tones beside it. Hann trades a slightly wider main lobe — a tone occupies about three bins rather than one — for sidelobes that fall away steeply, which is what lets neighbouring tones be told apart. Read a peak as "a tone near here", not "a tone exactly here".

Reading this takes no samples away from microphone.samples(). Empty while no capture is running.

Returns { number } — Array of amplitudes, one per bin.

local bins = microphone.spectrum(); print(#bins, bins[1])

globals/microphone/start

microphone.start(opts: MicOpts?) -> (MicState?, string?)

Open an input device and begin capturing. Returns the state the capture reached — "running" once a device is delivering, or "permissionPending" where the platform must ask for access first, which is the browser's normal path. Poll microphone.status() from there, or use microphone.awaitRunning().

A request that cannot be made at all returns nil and the reason: an fftSize that is not a whole power of two between 64 and 16384, a device no machine here offers, a rate the device does not capture at, or a capture that is already running.

Omitting device opens the platform default. Omitting sampleRate takes the device's own rate, which is what avoids a resample. fftSize is how many samples one analysis window covers and defaults to 1024 — at 48 kHz that spans ~21 ms and resolves ~47 Hz per bin.

Parameters

  • opts MicOpts (optional){ device, sampleRate, fftSize }.

Returns (MicState?, string?) — The state reached, or nil and the reason the request was refused.

local state, why = microphone.start({ fftSize = 2048 })

globals/microphone/status

microphone.status() -> MicStatus

Where the capture stands.

reason carries the platform's own message: the refusal for denied, the device's message for failed, what is being waited on for permissionPending. binHz is the width of one spectrum bin and bins how many microphone.spectrum() returns.

framesCaptured counts every mono frame the device delivered whether or not anything drained it, so a silent room reads differently from a stalled device. overruns counts samples discarded because a consumer did not keep up — it standing still is what says the readings are continuous, and it climbing is why a caller sees gaps.

Returns MicStatus{ state, reason, device, sampleRate, fftSize, binHz, bins, framesCaptured, overruns }.

local s = microphone.status(); print(s.state, s.framesCaptured, s.overruns)

globals/microphone/stop

microphone.stop() -> boolean

Stop the capture and release the device. True when a capture was open or being opened at call time. The device is let go before this returns, so a stop followed by a start opens it again rather than finding it held.

Returns boolean — Whether a capture was active.

microphone.stop()
  • api
  • reference