Voice deprecated

Deprecated as of 1.2.8. Voice is the legacy capture/playback API. New code should use SoundByte.VoiceChannel — same mic capture, but it participates in the SoundByte graph (link a voice channel through modifiers to an OutputNode or ByteSink). Voice continues to import and work for back-compat, including under the same voice Cargo feature flag.

Microphone capture + per-peer playback. Opus-encoded 20 ms frames carry over your transport (Steam P2P, the Net library, anything). Ruzit handles the audio plumbing, you decide how packets travel.

local Voice = import("Voice")

local mic = Voice.StartCapture()
mic.OnPacket:Connect(function(packet)
    send_to_peers(packet)
end)

local chan = Voice.CreateChannel()
chan:Play()
on_peer_packet:Connect(function(packet) chan:Push(packet) end)

Voice API

Voice.StartCapture() -> VoiceCapture

Open the default microphone. Encodes 20 ms Opus frames; fires OnPacket for each one. Stop with :Stop() when done.

Voice.CreateChannel() -> VoiceChannel

Per-peer playback channel. Each remote speaker gets one. Push received Opus packets in; Ruzit decodes and plays them.

Voice.PlayPacket(packet, opts?) -> VoiceChannel

Fire-and-forget: spin up a transient channel, decode + play the packet, optionally pinned to a world position. Best for one-off received messages. opts: { x?, y?, z?, falloff?, min_falloff? } (falloff is MaxFalloff). For ongoing peer voice prefer CreateChannel.

Voice.NewRecorder() -> VoiceRecorder

Empty packet collector. Hook to mic.OnPacket OR to packets you receive from a peer, the recorder doesn't care where they came from.

Voice.LoadRecording(serialized) -> VoiceRecording

Inverse of recorder:Serialize().

Voice.Record(duration) -> Signal<VoiceRecording, string>

One-shot mic recording. Opens the default input, captures for duration seconds (clamped to 1 hour), stops automatically, and fires the returned Signal once with (recording, serialized) — the recording handle for local playback (recording:PlayInto(channel) or Voice.PlayRecording(recording)) and the serialized string for sending over the network / saving to disk / reloading via Voice.LoadRecording.

local Voice = import("Voice")

Voice.Record(3):Connect(function(recording, serialized)
    -- Local playback:
    Voice.PlayRecording(recording)
    -- Or send to peers and have them Voice.LoadRecording(s):PlayInto(ch):
    lobby:Broadcast(serialized)
end)
Voice.PlayRecording(data, opts?) -> VoiceChannel

Fire-and-forget recording playback. data is either a VoiceRecording handle or a serialized string (the same form Voice.LoadRecording takes). Spins up a transient channel, plays the whole recording, and returns the channel so you can apply shaders, watch IsPlaying, or stop it early. opts: { loop?: boolean, speed?: number } (speed clamps to 0.25..4).

Shader factories

Build a VoiceShader userdata you can stack on a channel via chan:ApplyShader(shader). For per-channel modifiers you only need one of (Volume / Speed / Position), prefer the properties on VoiceChannel below.

Voice.Volume(factor)Linear gain. 1.0 unchanged, 0 silence, >1 boost.
Voice.Speed(factor)Time + pitch. Clamps to 0.25..4.
Voice.Spatial(x, y, z, max_falloff?)Anchor to a world point with distance falloff. Listener = active Renderable.Camera.
Voice.ListenerPosition(x, y, z)Override the listener position globally. Pass (0, 0, 0) to revert to the camera.

VoiceCapture

Returned by Voice.StartCapture().

.OnPacketSignal<string>read-only

Fires with each Opus-encoded packet (~20 ms = 50/sec). Pass packet to peers via Steam P2P / Net / whatever transport.

cap:Stop()method

Stop and release the mic. After Stop, every other method is a no-op.

cap:IsActive()-> booleanmethod

True between StartCapture and Stop.

cap:Pause()method

Push-to-talk: stop emitting packets without closing the mic. Toggling is cheap (one atomic).

cap:Resume()method
cap:SetActive(on)method

Alias for Pause/Resume that reads naturally with a state variable.

cap:IsPaused()-> booleanmethod
cap:SetThreshold(amplitude)method

Voice activation: peak amplitude (0..1). 0 = always send (default). Typical: 0.02–0.05 just above ambient noise. Frames whose peak amplitude is below the threshold are dropped before they hit the Opus encoder.

cap:GetThreshold()-> numbermethod

VoiceChannel

Returned by Voice.CreateChannel() / Voice.PlayPacket(...). One per remote speaker.

.IsPlayingbooleanread-only

True while at least one packet is queued for playback.

chan:Push(packet)method

Feed an Opus packet (received from a peer). Decoded and queued for playback. Auto-clamps backlog to ~1 second to prevent runaway latency.

chan:Play()method

Begin the audio mixer for this channel.

chan:Stop()method

After Stop, new packets queue silently until you Play again.

chan:ApplyShader(shader)method

Stack a VoiceShader built via the factories above.

chan:ClearShaders()method
chan:SetPosition(x?, y?, z?)method

Convenience setter, equivalent to assigning chan.Position. Pass nil (or no args) to clear.

chan:SetSpatial(x?, y?, z?, max_falloff?)method

Same as SetPosition but also installs / replaces the Spatial shader with this distance falloff in one call.

Modifier properties

Live, settable properties on the channel. Same idea as SFX Sound, but the Voice mixer only has Volume / Speed / Spatial under the hood, so the property list is the portable subset. Assigning replaces the prior value (no stacking), reading returns the current value.

chan.VolumenumberLinear gain. 1.0 unchanged, 0 silence, >1 boost.
chan.SpeednumberTime + pitch. Clamps to 0.25..4.
chan.PitchnumberAlias for Speed.
chan.PositionVector?World-space anchor. Set to nil to revert to non-positional.
chan.MinFalloffnumberInner radius. Distances ≤ this play at full volume. Default 0.
chan.MaxFalloffnumberOuter radius. Distances ≥ this are silent. Default 8.
-- A peer joins, their channel; squeeze them through a radio.
local chan = Voice.CreateChannel()
chan.Volume = 0.8
chan.Position = Vector(12, 0, 3)
chan.MinFalloff = 2
chan.MaxFalloff = 25
chan:Play()

Tweening

A VoiceChannel is a valid target for TweenService.new. Tweenable properties: Volume, Pitch / Speed, Position, MinFalloff, MaxFalloff.

local Tween = import("TweenService")

-- Smooth volume duck while a system alert plays.
Tween.new(chan, 0.25, "Sine", { Volume = 0.2 }):Play()

VoiceRecorder

Captures Opus packets into an in-memory buffer. Packets are opaque , the recorder doesn't care if they came from your own mic or a peer.

rec:Push(packet)method

Append one Opus packet.

rec:Clear()method

Drop everything captured so far.

rec:Length()-> numbermethod

Total packet count.

rec:Duration()-> numbermethod

Total recorded duration in seconds.

rec:Serialize()-> stringmethod

Length-prefixed binary serialization. Save with IO.write or to Steam Cloud, send over the network, etc. Reload with Voice.LoadRecording.

rec:ToRecording()-> VoiceRecordingmethod

Snapshot the current packet list as an immutable VoiceRecording.

VoiceRecording

Immutable snapshot. Built from recorder:ToRecording() or loaded via Voice.LoadRecording(bytes).

rec:PacketCount()-> numbermethod
rec:Duration()-> numbermethod
rec:PlayInto(channel, opts?) method

Stream packets into channel at the original ~50 packets/second pacing. Returns immediately, playback is dripped by the heart pump. opts: { loop?: boolean, speed?: 0.25..4.0 }.

VoiceShader

Returned by every Voice.Volume / Speed / Spatial / ... factory. Opaque handle, pass it to chan:ApplyShader(shader). Multiple channels can share the same shader instance, and one channel can stack many shaders.