SFX deprecated

Deprecated as of 1.2.8. SFX is the legacy single-Sound audio API. New code should use SoundByte — a graph-routed audio system with sources, sinks, modifiers, link/unlink, and tween hooks on every parameter. SFX continues to import and work for back-compat, but no new modifier kinds will be added here.

Stream and shape audio. Built on rodio + cpal: cross-platform output via WASAPI / ALSA / PulseAudio / CoreAudio. Each Sound is independent, play, layer, position in 3D, attach DSP shaders.

local SFX = import("SFX")
local Asset = import("Asset")

local data = Asset.GetAsset("Sound", "sfx.explode")
local snd  = SFX.LoadSound(data)
snd.Volume = 0.6
snd.Echo = { Delay = 120, Feedback = 0.4, Mix = 0.3 }
snd:Play()

SFX API

SFX.LoadSound(data) -> Sound

Wrap a SoundData into a playable Sound. The same data can back many independent Sounds, each with its own shader chain and Play/Stop state. To free every Sound backed by a particular SoundData, call data:Free() (or Asset.Drop("Sound:path") for path-loaded sounds).

Standalone shader factories

Build a SoundShader userdata you can stack on any Sound via sound:ApplyShader(shader). Useful for shader presets you reuse across many sounds.

SFX.Volume(factor)Linear gain.
SFX.Speed(factor)Time + pitch.
SFX.Pan(amount)Stereo pan, -1 to +1.
SFX.FadeIn(seconds)0->1 envelope from start.
SFX.FadeOut(seconds)1->0 envelope at end.
SFX.LowPass(freq)Biquad lowpass at the given Hz.
SFX.HighPass(freq)Biquad highpass.
SFX.Delay(seconds)Wait before producing audio.
SFX.Repeat()Loop indefinitely.
SFX.Distortion(amount)Soft saturation.
SFX.Echo(delay_ms, feedback?, mix?)Single-tap echo.
SFX.Reverb(mix?, decay?)Diffuse reverb.
SFX.Tremolo(rate?, depth?)Periodic amplitude wobble.

Sound

Properties & signals

.StartedSignal<>read-only

Fires when :Play() actually starts feeding audio.

.StoppedSignal<>read-only

Fires on natural completion or :Stop().

.DidLoopSignal<>read-only

Fires every time a looped sound wraps back to the start. Only fires while Looped = true; the first :Play() doesn't fire it, only subsequent loop restarts. If you used the legacy SFX.Repeat shader (or snd:Loop()) instead of the Looped property, this signal stays silent, the source repeats internally with no boundary the engine can observe.

.Loopedboolean

When true, playback restarts at the beginning each time the source ends, and DidLoop fires on each wrap. Toggle live, setting to false mid-play lets the current iteration finish naturally and then stops.

.TimePositionnumber

Current playback position in seconds. Reads track real-time progress (modulo the source duration when Looped is true). Writing while playing seeks immediately; writing while stopped sets the start offset for the next :Play() call.

.Sourcestringread-only
-- Background music with seek + loop counter.
local music = SFX.LoadSound(Asset.GetAsset("Sound", "music.theme"))
music.Looped = true
music.TimePosition = 12.5   -- start at 12.5 s on next Play
music:Play()

local loops = 0
music.DidLoop:Connect(function()
    loops += 1
    print("theme has restarted " .. loops .. " times")
end)

Playback control

snd:Play()method

Begin playback. Re-applying shaders / volume before this changes how this playback sounds, settings don't carry between :Play() calls unless you re-apply them.

snd:Stop()method
snd:IsPlaying()-> booleanmethod
snd:Reset()method

Drop all fluent shaders and attachments, back to a clean state.

snd:LinkToUpdate(interval)-> Signal<number>method

Fires every interval seconds with elapsed playback time. Useful for syncing animation / particles to audio.

Modifier properties

Each built-in modifier is exposed as a single live property on the Sound. Assigning replaces the prior value (no stacking), reading returns the current value, and assigning nil / 0 / false removes the modifier. Changes apply on the next :Play(), settings are kept between plays unless you explicitly clear them.

snd.Volumenumber1.0 unchanged, 0.0 silence, >1 boosted.
snd.Speednumber2.0 = 2× faster (also pitched up an octave).
snd.PitchnumberAlias for Speed, set either, both read the same value.
snd.Pannumber-1 left / 0 center / +1 right.
snd.LowPassnumber? (Hz)Biquad lowpass at the given Hz. Set to nil to disable.
snd.HighPassnumber? (Hz)Biquad highpass. Set to nil to disable.
snd.FadeInnumber (s)Linear envelope from start. Set to 0 to disable.
snd.FadeOutnumber (s)Linear envelope at end. Set to 0 to disable.
snd.Delaynumber (s)Wait before producing audio. Set to 0 to disable.
snd.LoopbooleanLegacy in-source repeat (no DidLoop signal). Prefer snd.Looped for engine-observed loops.
snd.DistortionnumberSoft saturation. Set to 0 to disable, >1 hard-clips.
snd.Echotable? { Delay, Feedback?, Mix? }Single-tap echo. Set to nil to disable.
snd.Reverbtable? { Mix?, Decay? }Diffuse reverb. Set to nil to disable.
snd.Tremolotable? { Rate?, Depth? }Periodic amplitude wobble. Set to nil to disable.
-- Live volume duck while a dialogue line plays.
music.Volume = 0.2
dialog:Play()
dialog.Stopped:Connect(function() music.Volume = 1.0 end)

-- Underwater preset.
snd.LowPass = 800
snd.Reverb  = { Mix = 0.4, Decay = 3 }

3D positional audio

Position the sound in world space, the active Renderable.Camera is the listener. Inside MinFalloff the sound is at full volume, past MaxFalloff it is silent, and in between it rolls off smoothly. Setting Position to nil reverts to non-positional (no attenuation, no spatial pan).

.PositionVector?

World-space position. Assign a Vector to place in 3D, or nil to disable spatial audio.

.MinFalloffnumber

Inner radius. Distances ≤ this play at full volume. Defaults to 0.

.MaxFalloffnumber

Outer radius. Distances ≥ this are silent. Defaults to 20.

snd:SetPosition(x, y, z, max_falloff?)method

Convenience form, takes raw components plus an optional override for MaxFalloff. Equivalent to setting snd.Position (and optionally snd.MaxFalloff).

snd:ClearPosition()method

Same as snd.Position = nil.

Tweening

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

local Tween = import("TweenService")

-- 2 s fade-out, then stop.
local t = Tween.new(music, 2, "Sine", { Volume = 0 })
t.Completed:Connect(function() music:Stop() end)
t:Play()

DSP shaders

Same shader contract as the GPU side, but the body runs once per audio sample. See Shaders > Sound shaders.

snd:AttachShader(asset)method
snd:DetachShader(asset)method
snd:SetData(asset, name, value)method
snd:GetData(asset, name)-> number?method
snd:ApplyShader(shader)method

Stack a SoundShader built via SFX.Volume(...) / etc. Stacks (unlike fluent calls).

snd:ClearShaders()method

SoundShader

Returned by every standalone shader factory (SFX.Volume(0.6), SFX.Echo(120, 0.4, 0.3), etc.). It's an opaque handle, the only thing you do with it is pass it to sound:ApplyShader(shader). Multiple sounds can share the same shader instance, and one sound can stack many shaders.

-- Build a "underwater" preset once and apply to many sounds.
local underwater_lpf = SFX.LowPass(800)
local underwater_rev = SFX.Reverb(0.4, 3)

for _, snd in all_underwater_sounds do
    snd:ApplyShader(underwater_lpf)
    snd:ApplyShader(underwater_rev)
end