Claude Skill

roblox-audio

Build or debug Roblox music, spatial audio, effects, text-to-speech, and speech-to-text. Use for audio graphs, asset permissions, playback problems, or migration from Sound.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download nonlooped-roblox-suite-roblox-audio-2904c16.zip · 13 KB
Part of nonlooped/roblox-suite — 15 skills

Install

skills CLI npx skills add https://github.com/nonlooped/roblox-suite/tree/main/roblox-audio
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install nonlooped-roblox-suite@llmmart
Git git clone https://github.com/nonlooped/roblox-suite.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole nonlooped/roblox-suite collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

roblox-audio

Official sources:

Roblox recommends audio objects over Sound, SoundGroup, and SoundEffect. New work should use the graph; legacy code can keep using Sound where the graph offers no advantage.

Cross-reference:

The two systems

Modern audio graph (recommended for new work)

Modular instances that mirror real-world audio devices. Each object produces, consumes, modifies, or carries an audio stream. You wire them together with Wire instances (SourceInstance → TargetInstance).

Object Role Real-world analog
AudioPlayer Produces a stream from an audio asset ID A audio file player
AudioEmitter Emits a stream into the 3D environment (parent position = emission point) A speaker in the world
AudioListener Picks up streams from the environment (parent = camera or character) A microphone in the world
AudioDeviceOutput Plays a stream to the player's physical speaker/headphones The player's hardware output
AudioDeviceInput Captures audio from the player's physical microphone The player's hardware mic
AudioTextToSpeech Converts text to audio with an artificial voice A TTS engine
AudioSpeechToText Converts spoken audio to text A transcription engine
Wire Carries a stream from SourceInstance to TargetInstance An audio cable

Effects (all "modify" category): AudioEqualizer, AudioCompressor, AudioReverb, AudioChorus, AudioDistortion, AudioEcho, AudioFlanger, AudioPitchShifter, AudioTremolo, AudioFader, AudioAnalyzer. See references/audio-effects.md.

Legacy Sound system (still works, discouraged)

Sound parented to a BasePart or Attachment emits from that position with built-in Doppler and distance rolloff (RollOffMode, RollOffMaxDistance, RollOffMinDistance, EmitterSize). A "global" Sound (not parented to a part/attachment) plays at constant volume everywhere. SoundGroup controls group volume and effects; SoundEffect subclasses (EqualizerSoundEffect, ReverbSoundEffect, etc.) apply per-group effects. SoundService exposes global properties (AmbientReverb, DistanceFactor, DopplerScale, RespectFilteringEnabled) that affect Sound playback.

SoundService.AmbientReverb and the Doppler/distance properties affect only legacy Sound, not the audio graph. The graph has its own effect objects and per-emitter DistanceAttenuation curves.

Decision tree: which system?

  • New experience, greenfield audio → audio graph. It supports TTS, STT, acoustic simulation, and routing.
  • Simple 2D SFX or music with no routing/effects → Sound is acceptable and simpler. Don't rewrite working legacy code just to migrate.
  • 3D positional audio with custom attenuation curves → audio graph (AudioEmitter.DistanceAttenuation).
  • Multiple sources through one effect (e.g. all gunfire through one compressor) → audio graph (one effect, many players wired in).
  • Text-to-speech or speech-to-text → audio graph (only the graph has AudioTextToSpeech / AudioSpeechToText).
  • Voice chat / spatial voice → VoiceChatService (separate from in-experience audio; uses AudioDeviceInput under the hood when UseAudioApi is enabled).
  • Acoustic simulation (occlusion, diffraction, reverb) → audio graph with SoundService.AcousticSimulationEnabled = true and per-instance AcousticSimulationEnabled on emitters/listeners.
  • Quick prototype / one-shot UI click sound → Sound is fine.

See references/audio-graph-vs-sound.md for a side-by-side property map and migration notes.

2D audio (non-directional)

Same volume everywhere. Requires: AudioPlayer → Wire → AudioDeviceOutput, all parented under SoundService.

--!strict
local SoundService = game:GetService("SoundService")

local player = Instance.new("AudioPlayer")
player.AssetId = "rbxassetid://YOUR_AUDIO_ID"
player.Looping = true
player.Volume = 1
player.Parent = SoundService

local output = Instance.new("AudioDeviceOutput")
output.Parent = SoundService

local wire = Instance.new("Wire")
wire.SourceInstance = player
wire.TargetInstance = output
wire.Parent = SoundService

player:Play()

3D audio (positional)

Volume changes with the listener's distance to the emitter. Requires six objects: AudioPlayer → Wire → AudioEmitter (parented to the 3D part), and AudioListener → Wire → AudioDeviceOutput (under SoundService). Set SoundService.ListenerLocation to Character or Camera (the engine auto-creates the AudioDeviceOutput under SoundService at runtime when you do).

--!strict
local SoundService = game:GetService("SoundService")
SoundService.ListenerLocation = Enum.ListenerLocation.Camera -- or Character

-- On the 3D part that should emit audio:
local part = workspace:WaitForChild("NoisyPart")
local player = Instance.new("AudioPlayer")
player.AssetId = "rbxassetid://YOUR_AUDIO_ID"
player.Looping = true
player.Parent = part

local emitter = Instance.new("AudioEmitter")
-- Custom curve: DistanceAttenuation is a NumberSequence (x = distance studs, y = volume 0..1).
-- This curve is only used when DistanceAttenuationMode == Custom (the default).
emitter.DistanceAttenuation = NumberSequence.new({
    NumberSequenceKeypoint.new(0, 1),    -- full volume at 0 studs
    NumberSequenceKeypoint.new(50, 0.5), -- half volume at 50 studs
    NumberSequenceKeypoint.new(70, 0),   -- silent at 70 studs
})
-- Preset alternative (no custom curve needed):
-- emitter.DistanceAttenuationMode = Enum.DistanceAttenuationMode.Inverse
-- emitter.DistanceAttenuationBounds = NumberRange.new(4, 10000) -- [min,max] for the preset rolloff
emitter.Parent = part

local wire = Instance.new("Wire")
wire.SourceInstance = player
wire.TargetInstance = emitter
wire.Parent = part

player:Play()

The emitter's parent position determines where audio emits from. AudioEmitter ignores its own orientation; rotate the parent part/attachment to steer emission.

Distance model: AudioEmitter.DistanceAttenuationMode selects the rolloff formula (Custom by default; uses your DistanceAttenuation curve; other presets use DistanceAttenuationBounds which defaults to [4, 10000] and ignore the custom curve). GetDistanceAttenuation() always returns the custom curve even when a preset is active. Don't assume it reflects the audible rolloff unless Mode == Custom. SetDistanceAttenuation(curve) only affects playback when Mode == Custom. Angle attenuation is set via SetAngleAttenuation({[angle]=volume}) (0–180° → 0–1); use for directional sources (e.g. avatar voice projects forward).

Acoustic simulation (occlusion/diffraction/reverb): Enable SoundService.AcousticSimulationEnabled and AcousticSimulationEnabled on both the emitter and listener. The current reference no longer exposes the separate per-instance OcclusionEnabled, DiffractionEnabled, and ReverbEnabled toggles; do not generate calls to them. Use emitter:GetAudibilityFor(listener) or listener:GetAudibilityFor(emitter) for combined distance and angle attenuation (0–1).

Listener location

SoundService.ListenerLocation (a ListenerLocation enum) controls where the AudioListener is auto-created:

  • Default: camera in experiences with voice chat.
  • None: no auto-listener; create one via script.
  • Character: parented to the local player's character (Humanoid.RootPart).
  • Camera: parented to workspace.CurrentCamera.

When set to Character or Camera, the engine auto-creates an AudioDeviceOutput under SoundService at runtime. The AudioListener picks up audio from AudioEmitters based on distance and the emitter's DistanceAttenuation curve.

Triggering audio from scripts

local audio = script.Parent :: AudioPlayer
someEvent:Connect(function()
    audio:Play()
end)

AudioPlayer:Play(atTime?), :Stop(atTime?). When atTime is supplied the action is scheduled against SoundService:GetMixerTime() for sample-accurate, framerate-independent timing (rhythm games / beat sync). :Cancel(actionId) cancels a future-scheduled Play/Stop (returns true if cancelled). :Pause(), :SeekTime(...), :GetWaveform(timeRange) (samples volume without playing; useful for waveform visualization vs live AudioAnalyzer). AudioPlayer.TimeVolume is tweenable. See references/audio-effects.md for tweening volume and effect parameters.

Preloading audio

Preload prominent audio assets before they're needed (loading screen, round start) to avoid first-play hitches on lower-end devices:

--!strict
local ContentProvider = game:GetService("ContentProvider")
local audioPlayer = workspace:WaitForChild("MusicPlayer") :: AudioPlayer
ContentProvider:PreloadAsync({ audioPlayer })

For the legacy Sound system, preload the Sound instance the same way.

Performance limits

  • Concurrent voices (simultaneously playing audio streams) are capped; the cap varies by device. Mobile is significantly lower than desktop.
  • Effects cost CPU per active stream routed through them. Reverb and acoustic simulation are the heaviest.
  • Acoustic simulation (SoundService.AcousticSimulationEnabled) adds per-emitter occlusion/diffraction/reverb cost; disable on low-end clients or when not needed.
  • Mobile throttling: the engine may drop or degrade audio effects on low-memory clients (e.g. flipbooks were dropped on older phones; the same applies to some audio effects). Test at low quality levels.
  • AudioAnalyzer is for inspection only; don't chain it into audible paths unnecessarily.

Profile audio with the MicroProfiler (audio appears under worker threads) and the Developer Console Memory tab.

Script context (client vs server)

  • Playback of AudioPlayer, Sound, and effects is client-side. Each client plays its own audio. The server does not mix audio for clients.
  • Replication: AudioPlayer state (playing/paused/stopped) replicates from server to clients if the instance is in a replicated location, but per-client volume/effects are local. For one-shot SFX, prefer client-authoritative emission: server signals "this event happened" via RemoteEvent, each affected client plays the sound locally. This avoids replicating per-burst timing and respects each client's quality settings (same pattern as VFX; see roblox-vfx skill).
  • Music/ambience that should be synchronized across all clients can be server-driven (the AudioPlayer lives in ReplicatedStorage or SoundService and the server calls :Play()), but be aware each client still renders locally and may drift.
  • AudioDeviceInput (microphone) is client-only. It captures the local player's mic. Pair with VoiceChatService for spatial voice. Access control: SetUserIdAccessList(userIds) + AccessType (Allow = only listed users hear the input, Deny = all except listed; default Deny), GetUserIdAccessList() returns the allow/deny list.
  • Never trust client audio state for gameplay. A client claiming "I played the reload sound" tells you nothing authoritative; validate gameplay effects on the server (see roblox-networking).

Audio asset permissions

  • Audio assets uploaded before the 2022 audio privacy changes may be private or have restricted use. Assets you upload to your own experience are usable by that experience.
  • The Creator Store has a library of free-to-use audio assets. These are safe to reference by asset ID in any experience.
  • Audio uploaded by other creators may be unusable in your experience unless they've marked it for public use. If you reference a third-party audio asset ID and it doesn't play, permissions are the usual cause.
  • For new audio, upload through the Creator Dashboard's asset manager or the Open Cloud Assets API (see roblox-open-cloud skill for programmatic upload).

Text-to-speech (TTS)

AudioTextToSpeech converts text (≤300 chars per request) to audio with an artificial voice. The available VoiceId values are 1–11 (English variants), 101–102 (Spanish), 201–202 (German), 301–302 (Italian), 401–402 (French), 501–502 (Mandarin Chinese), 601–602 (Hindi), 701–702 (Japanese), 801–802 (Arabic), 901–902 (Korean), and 1001–1002 (Portuguese); odd IDs are male and even IDs are female for the locale-specific pairs. Wire it like an AudioPlayer: for 2D, AudioTextToSpeech → Wire → AudioDeviceOutput; for 3D, AudioTextToSpeech → Wire → AudioEmitter (plus the listener→output wire). Set Text, VoiceId, Volume on the AudioTextToSpeech. :WaitForSpeechReady() yields until AssetFetchStatus is Success (or Failure). All text must comply with Roblox Community Standards and Terms of Use.

Audio analysis: AudioAnalyzer window is controlled by AudioWindowSize. Small (lowest latency, low frequency resolution), Medium (balanced), Large (highest resolution, more latency).

Use cases: accessibility (reading UI text aloud), NPC voiceover without recorded audio, dynamic announcements.

Speech-to-text (STT)

AudioSpeechToText converts speech captured by AudioDeviceInput into text. Requires VoiceChatService.UseAudioApi = Enabled. Wire: AudioDeviceInput → Wire → AudioSpeechToText. Set audioDeviceInput.Player = Players.LocalPlayer at runtime to target the local player's mic. Roblox auto-detects the spoken language (17 supported: Arabic, Chinese Simplified/Traditional, English, French, German, Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Spanish, Russian, Turkish, Thai, Vietnamese).

To use STT without broadcasting voice to other players, disable VoiceChatService.EnableDefaultVoice. All audio for AudioSpeechToText must comply with Community Standards and Terms of Use.

Best practices

  • Preload prominent audio (music, common SFX) during loading. One-shot UI sounds can lazy-load.
  • Loop ambient/music with Looping = true (graph) or Looped = true (legacy). For finite loops N times, use the DidLoop event (legacy Sound) or count plays on the graph.
  • Fade in/out by tweening AudioPlayer.Volume (graph) or Sound.Volume (legacy). AudioFader is the graph-native way to control multiple streams' volume at once.
  • Music vs SFX: route music and SFX through separate AudioFader or SoundGroup so you can mute music independently and apply ducking (SFX ducks music via AudioCompressor sidechain).
  • 3D vs 2D: if the sound has a world position, use 3D (AudioEmitter); if it's UI/global, use 2D (AudioDeviceOutput direct).
  • Accessibility: offer TTS for important text, provide subtitle/caption options for VO, and never make audio the only cue for gameplay-critical info.
  • Mobile: test at low quality. The engine may drop effects; design so the experience still works without them.
  • Concurrency: pool AudioPlayer/Sound instances for frequent one-shots rather than creating/destroying per shot.

Common mistakes

  • Using Sound/SoundGroup/SoundEffect for new work when the graph is the recommended path.
  • Parenting an AudioEmitter to SoundService (it must be parented to a 3D part/attachment for 3D audio).
  • Forgetting the Wire (audio won't flow from player to output/emitter).
  • Expecting SoundService.AmbientReverb to affect the audio graph (it only affects legacy Sound).
  • Setting DistanceAttenuation as a single number instead of a NumberSequence (it's a curve, not a scalar).
  • Trusting client audio state for gameplay (reload sounds, hit sounds) instead of server validation.
  • Not preloading, causing first-play hitches on mobile.
  • Hardcoding asset IDs for third-party audio that may be permission-restricted.

Scripts

  • scripts/AudioBus.lua: a small music/SFX bus helper using AudioFader to control group volume and duck music when SFX play. Adapt for your mix.

How to proceed

  1. Pick the system: graph for new/complex work, Sound for simple/legacy.
  2. Lay out your buses (music, SFX, VO) and route through AudioFader or SoundGroup.
  3. Place 3D emitters on the parts that should make sound; set DistanceAttenuation per emitter.
  4. Add effects where they add value (reverb in caves, muffling underwater, ducking).
  5. Preload, then trigger from events (animation markers, UI, gameplay).
  6. Profile concurrent voices and effect cost on the lowest target device.
  7. Verify asset permissions for any third-party audio.

Reference index

Acoustic API references: https://create.roblox.com/docs/reference/engine/classes/AudioEmitter and https://create.roblox.com/docs/reference/engine/classes/AudioListener.

Files (roblox-suite)
  • references
    • audio-effects.md 6.9 KB
      ---
      read_when: "Route effects, mix audio buses, duck music, or tune attenuation"
      last_reviewed: 2026-08-18
      ---
      
      # Audio effects reference
      
      **Official source:** https://create.roblox.com/docs/en-us/audio/effects
      
      Audio effects are modular `Audio*` instances that non-destructively modify a stream *in transit*. You insert them between a source (`AudioPlayer`, `AudioTextToSpeech`, etc.) and a target (`AudioDeviceOutput`, `AudioEmitter`) by wiring `Wire.SourceInstance` → effect, then effect → `Wire.TargetInstance`. Multiple effects can chain, and **order matters** (chorus→distortion sounds different from distortion→chorus).
      
      ## Effect catalog
      
      | Effect | Purpose | Typical use |
      | --- | --- | --- |
      | `AudioEqualizer` | Volume control per frequency range | Muffle underwater, cut harsh highs |
      | `AudioCompressor` | Reduce dynamic range (lower peaks, raise floor) | Consistent VO volume; **ducking** (sidechain music under SFX) |
      | `AudioReverb` | Simulate sound bouncing off surfaces | Cave, hall, stadium ambience |
      | `AudioChorus` | Overlay detuned copies for thickness | Robotic/futuristic vocals, ensemble feel |
      | `AudioDistortion` | Overdriven clipping "fuzziness" | Guitar intensity, grungy character |
      | `AudioEcho` | Delayed repeats with decaying volume | Canyons, hard-surface reflections |
      | `AudioFlanger` | Sweeping/whooshing modulation | Sci-fi engines, otherworldly SFX |
      | `AudioPitchShifter` | Raise/lower pitch without changing speed | Scale small sounds up or large sounds down |
      | `AudioTremolo` | Volume variation (trembling) | Wavy dreamlike instruments, weather swells |
      | `AudioFader` | Volume control for one or more streams | Music/SFX bus master, group fades |
      | `AudioAnalyzer` | Inspect volume and frequency content | Visualization, debugging; not for audible path |
      
      ## Wiring an effect
      
      2D audio with one effect (e.g. muffling all audio):
      
      ```
      AudioPlayer → Wire → AudioEqualizer → Wire → AudioDeviceOutput
      ```
      
      ```lua
      --!strict
      local SoundService = game:GetService("SoundService")
      
      local player = Instance.new("AudioPlayer")
      player.AssetId = "rbxassetid://YOUR_ID"
      player.Parent = SoundService
      
      local eq = Instance.new("AudioEqualizer")
      -- Muffle: cut high and low, keep mid. See AudioEqualizer property reference for exact ranges.
      eq.Parent = SoundService
      
      local output = Instance.new("AudioDeviceOutput")
      output.Parent = SoundService
      
      local w1 = Instance.new("Wire")
      w1.SourceInstance = player
      w1.TargetInstance = eq
      w1.Parent = SoundService
      
      local w2 = Instance.new("Wire")
      w2.SourceInstance = eq
      w2.TargetInstance = output
      w2.Parent = SoundService
      
      player:Play()
      ```
      
      ## Routing multiple sources through one effect
      
      The graph lets many `AudioPlayer`s feed one effect. You don't need a per-player effect with identical settings. Wire each player's `Wire.TargetInstance` to the shared effect, then one wire from the effect to the output.
      
      ```
      AudioPlayer A ─┐
      AudioPlayer B ─┼→ Wire → AudioCompressor → Wire → AudioDeviceOutput
      AudioPlayer C ─┘
      ```
      
      ## Chaining effects
      
      Insert effects in series. Order is significant:
      
      ```
      AudioPlayer → Wire → AudioChorus → Wire → AudioDistortion → Wire → AudioDeviceOutput
      ```
      
      vs.
      
      ```
      AudioPlayer → Wire → AudioDistortion → Wire → AudioChorus → Wire → AudioDeviceOutput
      ```
      
      These sound different. There's no "correct" order. Choose by ear and by intent (e.g. distortion before reverb so the reverb trails the distorted signal, not the other way around).
      
      ## Ducking (music under SFX) with AudioCompressor
      
      `AudioCompressor` can duck one stream when another is active (sidechain-style). The pattern: route music through a compressor whose threshold is driven by SFX volume. When SFX play, the compressor reduces music gain, then releases. This keeps SFX audible without a hard music cut.
      
      For a simpler approach, use `AudioFader` to tween music volume down when SFX fire and back up after. See `scripts/AudioBus.lua`.
      
      ## Tweening volume and effect parameters
      
      Several `AudioPlayer` and effect properties (e.g. `AudioPlayer.TimeVolume`, effect wet/dry mixes) are plain `number` properties you can tween with `TweenService` for smooth fades and parameter sweeps:
      
      ```lua
      --!strict
      local TweenService = game:GetService("TweenService")
      local audioPlayer = workspace.MusicPlayer.AudioPlayer :: AudioPlayer
      
      -- Fade out over 2 seconds
      local tween = TweenService:Create(audioPlayer.TimeVolume, TweenInfo.new(2), { Value = 0 })
      tween:Play()
      tween.Completed:Connect(function()
          audioPlayer:Stop()
      end)
      ```
      
      ## Reverb: graph vs legacy
      
      - **Graph:** `AudioReverb` instance in the signal path. Apply per-bus or per-emitter.
      - **Legacy:** `SoundService.AmbientReverb` (an `Enum.ReverbType` preset) applies globally to all `Sound` instances. **It does not affect the audio graph.**
      - **Acoustic simulation:** Enable `SoundService.AcousticSimulationEnabled` and `AcousticSimulationEnabled` on both `AudioEmitter` and `AudioListener` for geometry-based occlusion, diffraction, and reverberation. The current reference removed the individual effect toggles. Profile the result on low-end devices.
      
      ## Distance and angle attenuation (updated)
      
      - **Custom curve:** `AudioEmitter.DistanceAttenuation` / `AudioListener.DistanceAttenuation` (`NumberSequence`, keys 0..∞ → 0..1) is used only when `DistanceAttenuationMode == Custom`.
      - **Preset modes:** `AudioEmitter.DistanceAttenuationMode` (`Enum.DistanceAttenuationMode`) + `DistanceAttenuationBounds` (`NumberRange`, default `[4, 10000]`) define a preset rolloff (Inverse, Linear, etc.). When a preset is active, `SetDistanceAttenuation`/`GetDistanceAttenuation` still read/write the custom curve but it is ignored for playback.
      - **Angle attenuation:** `SetAngleAttenuation({[0]=1, [90]=0.5, [180]=0})`, `GetAngleAttenuation()`: maps 0–180° to 0–1 volume. Useful for directional sources.
      - **Audibility:** `emitter:GetAudibility(listener)` / `listener:GetAudibility(emitter)` returns 0..1 after combined distance+angle attenuation.
      
      ## Performance notes
      
      - Effects cost CPU per active stream passing through them. Reverb and acoustic simulation are the heaviest.
      - `AudioAnalyzer` is inspection-only. Don't leave it in audible paths.
      - On low-memory mobile clients the engine may drop effects; design so the experience is still intelligible without them.
      - Don't chain more effects than you can hear; each adds CPU and potential phase artifacts.
      
      ## Sources
      
      - https://create.roblox.com/docs/en-us/audio/effects
      - https://create.roblox.com/docs/en-us/reference/engine/classes/AudioEqualizer
      - https://create.roblox.com/docs/en-us/reference/engine/classes/AudioCompressor
      - https://create.roblox.com/docs/en-us/reference/engine/classes/AudioReverb
      - https://create.roblox.com/docs/en-us/reference/engine/classes/AudioFader
      - https://create.roblox.com/docs/en-us/reference/engine/classes/SoundService (`AcousticSimulationEnabled`, `AmbientReverb`)
      
      Acoustic API references: https://create.roblox.com/docs/reference/engine/classes/AudioEmitter and https://create.roblox.com/docs/reference/engine/classes/AudioListener.
      
    • audio-graph-vs-sound.md 5.2 KB
      ---
      read_when: "Choose an audio system or migrate legacy Sound code"
      last_reviewed: 2026-08-18
      ---
      
      # Audio graph vs legacy Sound
      
      Roblox recommends audio objects over `Sound`, `SoundGroup`, and `SoundEffect`. New work should use the graph; legacy `Sound` code that works can stay.
      
      ## Capability comparison
      
      | Capability | Legacy `Sound` | Audio graph |
      | --- | --- | --- |
      | 2D (global) audio | `Sound` not parented to a part | `AudioPlayer` → `Wire` → `AudioDeviceOutput` |
      | 3D positional audio | `Sound` parented to `BasePart`/`Attachment` | `AudioPlayer` → `Wire` → `AudioEmitter` (parented to part) |
      | Distance attenuation | `RollOffMode`, `RollOffMaxDistance`, `RollOffMinDistance`, `EmitterSize` | `AudioEmitter.DistanceAttenuation` (`NumberSequence` when `DistanceAttenuationMode == Custom`) or preset `DistanceAttenuationMode` + `DistanceAttenuationBounds` (`[4,10000]`) |
      | Doppler | Automatic (affected by `SoundService.DopplerScale`, `DistanceFactor`) | Automatic for `AudioEmitter`/`AudioListener` (affected by `SoundService.AcousticSimulationEnabled`) |
      | Group volume / mixing | `SoundGroup` | `AudioFader` (one fader controls many streams) |
      | Per-source effects | `SoundEffect` subclasses on `SoundGroup` | `AudioEqualizer` etc. in the signal path |
      | Global reverb | `SoundService.AmbientReverb` (preset enum) | `AudioReverb` instance; or `SoundService.AcousticSimulationEnabled` for automatic |
      | Text-to-speech | Not available | `AudioTextToSpeech` |
      | Speech-to-text | Not available | `AudioSpeechToText` |
      | Microphone capture | Not directly | `AudioDeviceInput` |
      | Acoustic occlusion/diffraction | Not available | `SoundService.AcousticSimulationEnabled` plus `AcousticSimulationEnabled` on both emitter and listener |
      | Routing multiple sources through one effect | Hard (per-group effects only) | Native (many wires → one effect → one output) |
      | Tweenable parameter | `Sound.Volume` (number) | `AudioPlayer.Volume` and other number props |
      
      ## Text-to-speech voice IDs
      
      `AudioTextToSpeech` accepts the English voices 1–11 and locale-specific pairs: Spanish 101–102, German 201–202, Italian 301–302, French 401–402, Chinese (Mandarin) 501–502, Hindi 601–602, Japanese 701–702, Arabic 801–802, Korean 901–902, and Portuguese 1001–1002. The odd ID is male and the even ID is female in every locale-specific pair.
      
      ## When to keep using `Sound`
      
      - Simple 2D SFX or music with no routing, no effects, no TTS/STT.
      - Existing working code: don't rewrite just to migrate.
      - Quick prototypes and one-shot UI clicks.
      - When you need `SoundService.AmbientReverb`'s preset behavior (it doesn't apply to the graph).
      
      ## When to use the graph
      
      - 3D audio with custom attenuation curves.
      - Multiple sources through a shared effect (all gunfire → one compressor).
      - Text-to-speech or speech-to-text.
      - Acoustic simulation (occlusion through walls, diffraction around corners).
      - Any new, non-trivial audio system where routing and mixing matter.
      - Voice-driven features (capture via `AudioDeviceInput`, STT via `AudioSpeechToText`).
      
      ## Migration notes
      
      The graph and legacy systems can coexist in one experience. `SoundService` properties split: `AmbientReverb`, `DopplerScale`, `DistanceFactor`, `RespectFilteringEnabled` affect **only legacy `Sound`**; `AcousticSimulationEnabled` affects **only the graph**. `ListenerLocation` and `DefaultListenerLocation` affect the graph's `AudioListener`.
      
      There is no automatic migration. To migrate:
      1. Replace each `Sound` parented to a part with `AudioPlayer` + `Wire` + `AudioEmitter` on the same part.
      2. Replace `SoundGroup` volume control with an `AudioFader` that all relevant players wire through.
      3. Replace `EqualizerSoundEffect` etc. with the corresponding `Audio*` effect in the signal path.
      4. For global reverb, either add an `AudioReverb` to the bus or enable `SoundService.AcousticSimulationEnabled`.
      5. Re-tune attenuation: `RollOffMode`/`RollOffMaxDistance` don't map 1:1 to `DistanceAttenuation` (a curve). Redesign the curve by ear.
      
      ## Legacy `Sound` quick reference (still useful)
      
      | Property | Purpose |
      | --- | --- |
      | `SoundId` | `rbxassetid://...` |
      | `Volume` | 0–4 (amplitude; >1 can clip) |
      | `Looped` | boolean |
      | `PlaybackSpeed` | 1 = normal; pitch and speed together |
      | `EmitterSize` | Larger = louder at distance before rolloff begins |
      | `RollOffMode` | Inverse, InverseTangent, Linear, LinearSquare, Cubic, CubicTangent |
      | `RollOffMinDistance` / `RollOffMaxDistance` | Rolloff range |
      | `IsPlaying`, `IsPaused`, `TimePosition` | Runtime state |
      | `:Play()`, `:Pause()`, `:Stop()`, `:Resume()` | Control |
      | `Loaded` event, `IsLoaded` | Asset readiness |
      | `DidLoop` event | Loop counter (for finite loops) |
      
      `SoundGroup.Volume` scales all member `Sound`s; `SoundGroup` can hold `SoundEffect` children.
      
      ## Sources
      
      - https://create.roblox.com/docs/en-us/audio/objects
      - https://create.roblox.com/docs/en-us/reference/engine/classes/Sound
      - https://create.roblox.com/docs/en-us/reference/engine/classes/SoundGroup
      - https://create.roblox.com/docs/en-us/reference/engine/classes/SoundService
      
      Acoustic API references: https://create.roblox.com/docs/reference/engine/classes/AudioEmitter and https://create.roblox.com/docs/reference/engine/classes/AudioListener.
      
  • scripts
    • AudioBus.lua 3.6 KB · in bundle
  • SKILL.md 18.1 KB
    ---
    name: roblox-audio
    description: "Build or debug Roblox music, spatial audio, effects, text-to-speech, and speech-to-text. Use for audio graphs, asset permissions, playback problems, or migration from Sound."
    last_reviewed: 2026-08-18
    ---
    
    # roblox-audio
    
    **Official sources:**
    - https://create.roblox.com/docs/en-us/audio/objects (audio graph overview)
    - https://create.roblox.com/docs/en-us/audio/effects (audio effects)
    - https://create.roblox.com/docs/en-us/reference/engine/classes/Sound (legacy `Sound`)
    - https://create.roblox.com/docs/en-us/reference/engine/classes/SoundService
    - Engine classes: `AudioPlayer`, `AudioEmitter`, `AudioListener`, `AudioDeviceOutput`, `AudioDeviceInput`, `AudioTextToSpeech`, `AudioSpeechToText`, `Wire`, `AudioEqualizer`, `AudioCompressor`, `AudioReverb`, `AudioChorus`, `AudioDistortion`, `AudioEcho`, `AudioFlanger`, `AudioPitchShifter`, `AudioTremolo`, `AudioFader`, `AudioAnalyzer`
    - Full reference: https://create.roblox.com/docs/en-us/reference/engine
    
    Roblox recommends audio objects over `Sound`, `SoundGroup`, and `SoundEffect`. New work should use the graph; legacy code can keep using `Sound` where the graph offers no advantage.
    
    Cross-reference:
    - [roblox-core/SKILL.md](../roblox-core/SKILL.md) for services and script locations.
    - [roblox-animation/SKILL.md](../roblox-animation/SKILL.md) for driving audio from animation markers (footsteps, impact sounds).
    - [roblox-user-interfaces/SKILL.md](../roblox-user-interfaces/SKILL.md) for UI-triggered sound feedback.
    - [roblox-networking/SKILL.md](../roblox-networking/SKILL.md) for client-authoritative cosmetic audio vs server-authoritative gameplay audio.
    
    ## The two systems
    
    ### Modern audio graph (recommended for new work)
    
    Modular instances that mirror real-world audio devices. Each object **produces**, **consumes**, **modifies**, or **carries** an audio stream. You wire them together with `Wire` instances (`SourceInstance` → `TargetInstance`).
    
    | Object | Role | Real-world analog |
    | --- | --- | --- |
    | `AudioPlayer` | Produces a stream from an audio asset ID | A audio file player |
    | `AudioEmitter` | Emits a stream into the 3D environment (parent position = emission point) | A speaker in the world |
    | `AudioListener` | Picks up streams from the environment (parent = camera or character) | A microphone in the world |
    | `AudioDeviceOutput` | Plays a stream to the player's physical speaker/headphones | The player's hardware output |
    | `AudioDeviceInput` | Captures audio from the player's physical microphone | The player's hardware mic |
    | `AudioTextToSpeech` | Converts text to audio with an artificial voice | A TTS engine |
    | `AudioSpeechToText` | Converts spoken audio to text | A transcription engine |
    | `Wire` | Carries a stream from `SourceInstance` to `TargetInstance` | An audio cable |
    
    Effects (all "modify" category): `AudioEqualizer`, `AudioCompressor`, `AudioReverb`, `AudioChorus`, `AudioDistortion`, `AudioEcho`, `AudioFlanger`, `AudioPitchShifter`, `AudioTremolo`, `AudioFader`, `AudioAnalyzer`. See [references/audio-effects.md](references/audio-effects.md).
    
    ### Legacy `Sound` system (still works, discouraged)
    
    `Sound` parented to a `BasePart` or `Attachment` emits from that position with built-in Doppler and distance rolloff (`RollOffMode`, `RollOffMaxDistance`, `RollOffMinDistance`, `EmitterSize`). A "global" `Sound` (not parented to a part/attachment) plays at constant volume everywhere. `SoundGroup` controls group volume and effects; `SoundEffect` subclasses (`EqualizerSoundEffect`, `ReverbSoundEffect`, etc.) apply per-group effects. `SoundService` exposes global properties (`AmbientReverb`, `DistanceFactor`, `DopplerScale`, `RespectFilteringEnabled`) that affect `Sound` playback.
    
    `SoundService.AmbientReverb` and the Doppler/distance properties affect **only legacy `Sound`**, not the audio graph. The graph has its own effect objects and per-emitter `DistanceAttenuation` curves.
    
    ## Decision tree: which system?
    
    - **New experience, greenfield audio** → audio graph. It supports TTS, STT, acoustic simulation, and routing.
    - **Simple 2D SFX or music with no routing/effects** → `Sound` is acceptable and simpler. Don't rewrite working legacy code just to migrate.
    - **3D positional audio with custom attenuation curves** → audio graph (`AudioEmitter.DistanceAttenuation`).
    - **Multiple sources through one effect (e.g. all gunfire through one compressor)** → audio graph (one effect, many players wired in).
    - **Text-to-speech or speech-to-text** → audio graph (only the graph has `AudioTextToSpeech` / `AudioSpeechToText`).
    - **Voice chat / spatial voice** → `VoiceChatService` (separate from in-experience audio; uses `AudioDeviceInput` under the hood when `UseAudioApi` is enabled).
    - **Acoustic simulation (occlusion, diffraction, reverb)** → audio graph with `SoundService.AcousticSimulationEnabled = true` and per-instance `AcousticSimulationEnabled` on emitters/listeners.
    - **Quick prototype / one-shot UI click sound** → `Sound` is fine.
    
    See [references/audio-graph-vs-sound.md](references/audio-graph-vs-sound.md) for a side-by-side property map and migration notes.
    
    ## 2D audio (non-directional)
    
    Same volume everywhere. Requires: `AudioPlayer` → `Wire` → `AudioDeviceOutput`, all parented under `SoundService`.
    
    ```lua
    --!strict
    local SoundService = game:GetService("SoundService")
    
    local player = Instance.new("AudioPlayer")
    player.AssetId = "rbxassetid://YOUR_AUDIO_ID"
    player.Looping = true
    player.Volume = 1
    player.Parent = SoundService
    
    local output = Instance.new("AudioDeviceOutput")
    output.Parent = SoundService
    
    local wire = Instance.new("Wire")
    wire.SourceInstance = player
    wire.TargetInstance = output
    wire.Parent = SoundService
    
    player:Play()
    ```
    
    ## 3D audio (positional)
    
    Volume changes with the listener's distance to the emitter. Requires six objects: `AudioPlayer` → `Wire` → `AudioEmitter` (parented to the 3D part), and `AudioListener` → `Wire` → `AudioDeviceOutput` (under `SoundService`). Set `SoundService.ListenerLocation` to `Character` or `Camera` (the engine auto-creates the `AudioDeviceOutput` under `SoundService` at runtime when you do).
    
    ```lua
    --!strict
    local SoundService = game:GetService("SoundService")
    SoundService.ListenerLocation = Enum.ListenerLocation.Camera -- or Character
    
    -- On the 3D part that should emit audio:
    local part = workspace:WaitForChild("NoisyPart")
    local player = Instance.new("AudioPlayer")
    player.AssetId = "rbxassetid://YOUR_AUDIO_ID"
    player.Looping = true
    player.Parent = part
    
    local emitter = Instance.new("AudioEmitter")
    -- Custom curve: DistanceAttenuation is a NumberSequence (x = distance studs, y = volume 0..1).
    -- This curve is only used when DistanceAttenuationMode == Custom (the default).
    emitter.DistanceAttenuation = NumberSequence.new({
        NumberSequenceKeypoint.new(0, 1),    -- full volume at 0 studs
        NumberSequenceKeypoint.new(50, 0.5), -- half volume at 50 studs
        NumberSequenceKeypoint.new(70, 0),   -- silent at 70 studs
    })
    -- Preset alternative (no custom curve needed):
    -- emitter.DistanceAttenuationMode = Enum.DistanceAttenuationMode.Inverse
    -- emitter.DistanceAttenuationBounds = NumberRange.new(4, 10000) -- [min,max] for the preset rolloff
    emitter.Parent = part
    
    local wire = Instance.new("Wire")
    wire.SourceInstance = player
    wire.TargetInstance = emitter
    wire.Parent = part
    
    player:Play()
    ```
    
    The emitter's **parent position** determines where audio emits from. `AudioEmitter` ignores its own orientation; rotate the parent part/attachment to steer emission.
    
    **Distance model:** `AudioEmitter.DistanceAttenuationMode` selects the rolloff formula (`Custom` by default; uses your `DistanceAttenuation` curve; other presets use `DistanceAttenuationBounds` which defaults to `[4, 10000]` and ignore the custom curve). `GetDistanceAttenuation()` always returns the custom curve even when a preset is active. Don't assume it reflects the audible rolloff unless `Mode == Custom`. `SetDistanceAttenuation(curve)` only affects playback when `Mode == Custom`. Angle attenuation is set via `SetAngleAttenuation({[angle]=volume})` (0–180° → 0–1); use for directional sources (e.g. avatar voice projects forward).
    
    **Acoustic simulation (occlusion/diffraction/reverb):** Enable `SoundService.AcousticSimulationEnabled` and `AcousticSimulationEnabled` on both the emitter and listener. The current reference no longer exposes the separate per-instance `OcclusionEnabled`, `DiffractionEnabled`, and `ReverbEnabled` toggles; do not generate calls to them. Use `emitter:GetAudibilityFor(listener)` or `listener:GetAudibilityFor(emitter)` for combined distance and angle attenuation (0–1).
    
    ## Listener location
    
    `SoundService.ListenerLocation` (a `ListenerLocation` enum) controls where the `AudioListener` is auto-created:
    - **Default**: camera in experiences with voice chat.
    - **None**: no auto-listener; create one via script.
    - **Character**: parented to the local player's character (`Humanoid.RootPart`).
    - **Camera**: parented to `workspace.CurrentCamera`.
    
    When set to `Character` or `Camera`, the engine auto-creates an `AudioDeviceOutput` under `SoundService` at runtime. The `AudioListener` picks up audio from `AudioEmitter`s based on distance and the emitter's `DistanceAttenuation` curve.
    
    ## Triggering audio from scripts
    
    ```lua
    local audio = script.Parent :: AudioPlayer
    someEvent:Connect(function()
        audio:Play()
    end)
    ```
    
    `AudioPlayer:Play(atTime?)`, `:Stop(atTime?)`. When `atTime` is supplied the action is scheduled against `SoundService:GetMixerTime()` for sample-accurate, framerate-independent timing (rhythm games / beat sync). `:Cancel(actionId)` cancels a future-scheduled Play/Stop (returns true if cancelled). `:Pause()`, `:SeekTime(...)`, `:GetWaveform(timeRange)` (samples volume without playing; useful for waveform visualization vs live `AudioAnalyzer`). `AudioPlayer.TimeVolume` is tweenable. See [references/audio-effects.md](references/audio-effects.md) for tweening volume and effect parameters.
    
    ## Preloading audio
    
    Preload prominent audio assets before they're needed (loading screen, round start) to avoid first-play hitches on lower-end devices:
    
    ```lua
    --!strict
    local ContentProvider = game:GetService("ContentProvider")
    local audioPlayer = workspace:WaitForChild("MusicPlayer") :: AudioPlayer
    ContentProvider:PreloadAsync({ audioPlayer })
    ```
    
    For the legacy `Sound` system, preload the `Sound` instance the same way.
    
    ## Performance limits
    
    - **Concurrent voices** (simultaneously playing audio streams) are capped; the cap varies by device. Mobile is significantly lower than desktop.
    - **Effects cost CPU** per active stream routed through them. Reverb and acoustic simulation are the heaviest.
    - **Acoustic simulation** (`SoundService.AcousticSimulationEnabled`) adds per-emitter occlusion/diffraction/reverb cost; disable on low-end clients or when not needed.
    - **Mobile throttling:** the engine may drop or degrade audio effects on low-memory clients (e.g. flipbooks were dropped on older phones; the same applies to some audio effects). Test at low quality levels.
    - **`AudioAnalyzer`** is for inspection only; don't chain it into audible paths unnecessarily.
    
    Profile audio with the MicroProfiler (audio appears under worker threads) and the Developer Console Memory tab.
    
    ## Script context (client vs server)
    
    - **Playback** of `AudioPlayer`, `Sound`, and effects is **client-side**. Each client plays its own audio. The server does not mix audio for clients.
    - **Replication:** `AudioPlayer` state (playing/paused/stopped) replicates from server to clients if the instance is in a replicated location, but per-client volume/effects are local. For one-shot SFX, prefer **client-authoritative emission**: server signals "this event happened" via RemoteEvent, each affected client plays the sound locally. This avoids replicating per-burst timing and respects each client's quality settings (same pattern as VFX; see roblox-vfx skill).
    - **Music/ambience** that should be synchronized across all clients can be server-driven (the `AudioPlayer` lives in `ReplicatedStorage` or `SoundService` and the server calls `:Play()`), but be aware each client still renders locally and may drift.
    - **`AudioDeviceInput`** (microphone) is client-only. It captures the local player's mic. Pair with `VoiceChatService` for spatial voice. Access control: `SetUserIdAccessList(userIds)` + `AccessType` (`Allow` = only listed users hear the input, `Deny` = all except listed; default `Deny`), `GetUserIdAccessList()` returns the allow/deny list.
    - **Never trust client audio state for gameplay.** A client claiming "I played the reload sound" tells you nothing authoritative; validate gameplay effects on the server (see roblox-networking).
    
    ## Audio asset permissions
    
    - Audio assets uploaded before the **2022 audio privacy changes** may be private or have restricted use. Assets you upload to your own experience are usable by that experience.
    - The **Creator Store** has a library of free-to-use audio assets. These are safe to reference by asset ID in any experience.
    - Audio uploaded by other creators may be unusable in your experience unless they've marked it for public use. If you reference a third-party audio asset ID and it doesn't play, permissions are the usual cause.
    - For new audio, upload through the Creator Dashboard's asset manager or the Open Cloud Assets API (see roblox-open-cloud skill for programmatic upload).
    
    ## Text-to-speech (TTS)
    
    `AudioTextToSpeech` converts text (≤300 chars per request) to audio with an artificial voice. The available `VoiceId` values are 1–11 (English variants), 101–102 (Spanish), 201–202 (German), 301–302 (Italian), 401–402 (French), 501–502 (Mandarin Chinese), 601–602 (Hindi), 701–702 (Japanese), 801–802 (Arabic), 901–902 (Korean), and 1001–1002 (Portuguese); odd IDs are male and even IDs are female for the locale-specific pairs. Wire it like an `AudioPlayer`: for 2D, `AudioTextToSpeech` → `Wire` → `AudioDeviceOutput`; for 3D, `AudioTextToSpeech` → `Wire` → `AudioEmitter` (plus the listener→output wire). Set `Text`, `VoiceId`, `Volume` on the `AudioTextToSpeech`. `:WaitForSpeechReady()` yields until `AssetFetchStatus` is `Success` (or `Failure`). All text must comply with Roblox Community Standards and Terms of Use.
    
    **Audio analysis:** `AudioAnalyzer` window is controlled by `AudioWindowSize`. `Small` (lowest latency, low frequency resolution), `Medium` (balanced), `Large` (highest resolution, more latency).
    
    Use cases: accessibility (reading UI text aloud), NPC voiceover without recorded audio, dynamic announcements.
    
    ## Speech-to-text (STT)
    
    `AudioSpeechToText` converts speech captured by `AudioDeviceInput` into text. Requires `VoiceChatService.UseAudioApi = Enabled`. Wire: `AudioDeviceInput` → `Wire` → `AudioSpeechToText`. Set `audioDeviceInput.Player = Players.LocalPlayer` at runtime to target the local player's mic. Roblox auto-detects the spoken language (17 supported: Arabic, Chinese Simplified/Traditional, English, French, German, Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Spanish, Russian, Turkish, Thai, Vietnamese).
    
    To use STT without broadcasting voice to other players, disable `VoiceChatService.EnableDefaultVoice`. All audio for `AudioSpeechToText` must comply with Community Standards and Terms of Use.
    
    ## Best practices
    
    - **Preload** prominent audio (music, common SFX) during loading. One-shot UI sounds can lazy-load.
    - **Loop** ambient/music with `Looping = true` (graph) or `Looped = true` (legacy). For finite loops N times, use the `DidLoop` event (legacy `Sound`) or count plays on the graph.
    - **Fade in/out** by tweening `AudioPlayer.Volume` (graph) or `Sound.Volume` (legacy). `AudioFader` is the graph-native way to control multiple streams' volume at once.
    - **Music vs SFX:** route music and SFX through separate `AudioFader` or `SoundGroup` so you can mute music independently and apply ducking (SFX ducks music via `AudioCompressor` sidechain).
    - **3D vs 2D:** if the sound has a world position, use 3D (`AudioEmitter`); if it's UI/global, use 2D (`AudioDeviceOutput` direct).
    - **Accessibility:** offer TTS for important text, provide subtitle/caption options for VO, and never make audio the only cue for gameplay-critical info.
    - **Mobile:** test at low quality. The engine may drop effects; design so the experience still works without them.
    - **Concurrency:** pool `AudioPlayer`/`Sound` instances for frequent one-shots rather than creating/destroying per shot.
    
    ## Common mistakes
    
    - Using `Sound`/`SoundGroup`/`SoundEffect` for new work when the graph is the recommended path.
    - Parenting an `AudioEmitter` to `SoundService` (it must be parented to a 3D part/attachment for 3D audio).
    - Forgetting the `Wire` (audio won't flow from player to output/emitter).
    - Expecting `SoundService.AmbientReverb` to affect the audio graph (it only affects legacy `Sound`).
    - Setting `DistanceAttenuation` as a single number instead of a `NumberSequence` (it's a curve, not a scalar).
    - Trusting client audio state for gameplay (reload sounds, hit sounds) instead of server validation.
    - Not preloading, causing first-play hitches on mobile.
    - Hardcoding asset IDs for third-party audio that may be permission-restricted.
    
    ## Scripts
    
    - `scripts/AudioBus.lua`: a small music/SFX bus helper using `AudioFader` to control group volume and duck music when SFX play. Adapt for your mix.
    
    ## How to proceed
    
    1. Pick the system: graph for new/complex work, `Sound` for simple/legacy.
    2. Lay out your buses (music, SFX, VO) and route through `AudioFader` or `SoundGroup`.
    3. Place 3D emitters on the parts that should make sound; set `DistanceAttenuation` per emitter.
    4. Add effects where they add value (reverb in caves, muffling underwater, ducking).
    5. Preload, then trigger from events (animation markers, UI, gameplay).
    6. Profile concurrent voices and effect cost on the lowest target device.
    7. Verify asset permissions for any third-party audio.
    
    <!-- catalog:references:start -->
    ## Reference index
    
    - [audio-effects.md](references/audio-effects.md): Route effects, mix audio buses, duck music, or tune attenuation.
    - [audio-graph-vs-sound.md](references/audio-graph-vs-sound.md): Choose an audio system or migrate legacy Sound code.
    <!-- catalog:references:end -->
    
    
    Acoustic API references: https://create.roblox.com/docs/reference/engine/classes/AudioEmitter and https://create.roblox.com/docs/reference/engine/classes/AudioListener.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related