Skip to content
audio-system logo

Roblox Audio Systems

audio-system

Implements audio systems including sound management, music systems, positional audio, and audio effects. Use when adding sound effects, music, ambient audio, or any audio features to a game.

SKILL.md

Full skill instructions

Roblox Audio Systems

Quick Reference Links

Official Documentation:

Wiki References:


When implementing audio, follow these patterns for immersive and performant sound design.

Sound Management

Sound Pooling

local SoundPool = {}
SoundPool.pools = {}

function SoundPool.create(soundId, poolSize)
    poolSize = poolSize or 5

    local pool = {
        sounds = {},
        currentIndex = 1
    }

    for i = 1, poolSize do
        local sound = Instance.new("Sound")
        sound.SoundId = soundId
        sound.Parent = SoundService
        table.insert(pool.sounds, sound)
    end

    SoundPool.pools[soundId] = pool
    return pool
end

function SoundPool.play(soundId, properties)
    local pool = SoundPool.pools[soundId]
    if not pool then
        pool = SoundPool.create(soundId)
    end

    local sound = pool.sounds[pool.currentIndex]
    pool.currentIndex = pool.currentIndex % #pool.sounds + 1

    -- Apply properties
    if properties then
        for key, value in pairs(properties) do
            sound[key] = value
        end
    end

    sound:Play()
    return sound
end

-- Usage
SoundPool.create("rbxassetid://123456789", 10)  -- Pre-create pool
SoundPool.play("rbxassetid://123456789", {Volume = 0.5, PlaybackSpeed = 1.2})

Sound Priority System

local SoundManager = {}
SoundManager.activeSounds = {}
SoundManager.maxSounds = 32  -- Roblox limit is higher, but good for performance

local SoundPriority = {
    UI = 100,
    PlayerAction = 80,
    Combat = 70,
    Environment = 50,
    Ambient = 30
}

function SoundManager.play(soundId, priority, properties)
    priority = priority or SoundPriority.Environment

    -- Check if we need to stop lower priority sounds
    if #SoundManager.activeSounds >= SoundManager.maxSounds then
        -- Find lowest priority sound
        local lowestPriority = priority
        local lowestIndex = nil

        for i, soundData in ipairs(SoundManager.activeSounds) do
            if soundData.priority < lowestPriority then
                lowestPriority = soundData.priority
                lowestIndex = i
            end
        end

        if lowestIndex then
            local removed = table.remove(SoundManager.activeSounds, lowestIndex)
            removed.sound:Stop()
        else
            return nil  -- Can't play, all sounds are higher priority
        end
    end

    local sound = Instance.new("Sound")
    sound.SoundId = soundId

    if properties then
        for key, value in pairs(properties) do
            sound[key] = value
        end
    end

    sound.Parent = SoundService
    sound:Play()

    local soundData = {
        sound = sound,
        priority = priority
    }

    table.insert(SoundManager.activeSounds, soundData)

    sound.Ended:Connect(function()
        local index = table.find(SoundManager.activeSounds, soundData)
        if index then
            table.remove(SoundManager.activeSounds, index)
        end
        sound:Destroy()
    end)

    return sound
end

Volume Categories

local VolumeManager = {}
VolumeManager.categories = {
    Master = 1,
    Music = 0.7,
    SFX = 0.8,
    Voice = 1,
    Ambient = 0.5
}

VolumeManager.soundGroups = {}

function VolumeManager.setup()
    -- Create SoundGroups for each category
    for category, defaultVolume in pairs(VolumeManager.categories) do
        local group = Instance.new("SoundGroup")
        group.Name = category
        group.Volume = defaultVolume
        group.Parent = SoundService
        VolumeManager.soundGroups[category] = group
    end

    -- Set Master as parent of others
    for category, group in pairs(VolumeManager.soundGroups) do
        if category ~= "Master" then
            group.Parent = VolumeManager.soundGroups.Master
        end
    end
end

function VolumeManager.setVolume(category, volume)
    VolumeManager.categories[category] = volume
    if VolumeManager.soundGroups[category] then
        VolumeManager.soundGroups[category].Volume = volume
    end
end

function VolumeManager.playInCategory(soundId, category, properties)
    local sound = Instance.new("Sound")
    sound.SoundId = soundId
    sound.SoundGroup = VolumeManager.soundGroups[category]

    if properties then
        for key, value in pairs(properties) do
            sound[key] = value
        end
    end

    sound.Parent = SoundService
    sound:Play()

    return sound
end

Music Systems

Music Playlist

local MusicPlayer = {}
MusicPlayer.playlist = {}
MusicPlayer.currentIndex = 0
MusicPlayer.currentSound = nil
MusicPlayer.isPlaying = false
MusicPlayer.shuffle = false

function MusicPlayer.addTrack(soundId, name)
    table.insert(MusicPlayer.playlist, {
        id = soundId,
        name = name
    })
end

function MusicPlayer.play()
    if #MusicPlayer.playlist == 0 then return end

    MusicPlayer.isPlaying = true

    if MusicPlayer.currentIndex == 0 then
        MusicPlayer.next()
    elseif MusicPlayer.currentSound then
        MusicPlayer.currentSound:Resume()
    end
end

function MusicPlayer.pause()
    if MusicPlayer.currentSound then
        MusicPlayer.currentSound:Pause()
    end
end

function MusicPlayer.next()
    if MusicPlayer.currentSound then
        MusicPlayer.currentSound:Stop()
        MusicPlayer.currentSound:Destroy()
    end

    if MusicPlayer.shuffle then
        MusicPlayer.currentIndex = math.random(1, #MusicPlayer.playlist)
    else
        MusicPlayer.currentIndex = MusicPlayer.currentIndex % #MusicPlayer.playlist + 1
    end

    local track = MusicPlayer.playlist[MusicPlayer.currentIndex]

    MusicPlayer.currentSound = Instance.new("Sound")
    MusicPlayer.currentSound.SoundId = track.id
    MusicPlayer.currentSound.Volume = 0.5
    MusicPlayer.currentSound.Looped = false
    MusicPlayer.currentSound.SoundGroup = VolumeManager.soundGroups.Music
    MusicPlayer.currentSound.Parent = SoundService

    MusicPlayer.currentSound.Ended:Connect(function()
        if MusicPlayer.isPlaying then
            MusicPlayer.next()
        end
    end)

    if MusicPlayer.isPlaying then
        MusicPlayer.currentSound:Play()
    end
end

function MusicPlayer.previous()
    MusicPlayer.currentIndex = MusicPlayer.currentIndex - 2
    if MusicPlayer.currentIndex < 0 then
        MusicPlayer.currentIndex = #MusicPlayer.playlist - 1
    end
    MusicPlayer.next()
end

Crossfade Transition

local function crossfade(fromSound, toSound, duration)
    duration = duration or 2

    toSound.Volume = 0
    toSound:Play()

    local startTime = os.clock()
    local fromVolume = fromSound.Volume

    local conn
    conn = RunService.Heartbeat:Connect(function()
        local elapsed = os.clock() - startTime
        local t = math.min(elapsed / duration, 1)

        fromSound.Volume = fromVolume * (1 - t)
        toSound.Volume = fromVolume * t

        if t >= 1 then
            fromSound:Stop()
            conn:Disconnect()
        end
    end)
end

Contextual Music

local MusicContext = {}
MusicContext.contexts = {
    peaceful = "rbxassetid://peaceful_music",
    combat = "rbxassetid://combat_music",
    boss = "rbxassetid://boss_music",
    victory = "rbxassetid://victory_music"
}
MusicContext.currentContext = nil
MusicContext.currentSound = nil

function MusicContext.setContext(contextName)
    if contextName == MusicContext.currentContext then return end

    local newSoundId = MusicContext.contexts[contextName]
    if not newSoundId then return end

    local newSound = Instance.new("Sound")
    newSound.SoundId = newSoundId
    newSound.Looped = true
    newSound.Parent = SoundService

    if MusicContext.currentSound then
        crossfade(MusicContext.currentSound, newSound, 2)
        task.delay(2, function()
            MusicContext.currentSound:Destroy()
            MusicContext.currentSound = newSound
        end)
    else
        newSound.Volume = 0.5
        newSound:Play()
        MusicContext.currentSound = newSound
    end

    MusicContext.currentContext = contextName
end

-- Usage
MusicContext.setContext("peaceful")
-- Later, when combat starts:
MusicContext.setContext("combat")

Positional Audio

3D Sound Setup

local function play3DSound(soundId, position, properties)
    local part = Instance.new("Part")
    part.Anchored = true
    part.CanCollide = false
    part.Transparency = 1
    part.Size = Vector3.new(0.1, 0.1, 0.1)
    part.Position = position
    part.Parent = workspace

    local sound = Instance.new("Sound")
    sound.SoundId = soundId
    sound.RollOffMode = Enum.RollOffMode.Linear
    sound.RollOffMinDistance = properties.minDistance or 10
    sound.RollOffMaxDistance = properties.maxDistance or 100
    sound.Volume = properties.volume or 1
    sound.Parent = part

    sound:Play()

    sound.Ended:Connect(function()
        part:Destroy()
    end)

    return sound, part
end

Sound Falloff Modes

-- Linear falloff (most predictable)
sound.RollOffMode = Enum.RollOffMode.Linear
sound.RollOffMinDistance = 10  -- Full volume within this distance
sound.RollOffMaxDistance = 100 -- Silent beyond this distance

-- Inverse (more realistic)
sound.RollOffMode = Enum.RollOffMode.Inverse
-- Volume = 1 / (1 + (distance - minDistance) / (maxDistance - minDistance))

-- InverseTapered (smoother falloff)
sound.RollOffMode = Enum.RollOffMode.InverseTapered
-- Combines linear and inverse

-- Custom falloff with emitter size
sound.EmitterSize = 5  -- Sound appears to come from area, not point

Footstep Sounds

local FootstepSounds = {
    [Enum.Material.Grass] = "rbxassetid://grass_step",
    [Enum.Material.Concrete] = "rbxassetid://concrete_step",
    [Enum.Material.Wood] = "rbxassetid://wood_step",
    [Enum.Material.Metal] = "rbxassetid://metal_step",
    [Enum.Material.Sand] = "rbxassetid://sand_step",
    [Enum.Material.Water] = "rbxassetid://water_splash"
}

local function setupFootsteps(character)
    local humanoid = character:WaitForChild("Humanoid")
    local rootPart = character:WaitForChild("HumanoidRootPart")

    local lastStep = 0
    local stepInterval = 0.4  -- Seconds between steps

    humanoid.Running:Connect(function(speed)
        if speed > 1 then
            local now = os.clock()
            if now - lastStep >= stepInterval / (speed / 16) then
                lastStep = now

                -- Get ground material
                local ray = workspace:Raycast(
                    rootPart.Position,
                    Vector3.new(0, -3, 0)
                )

                local material = ray and ray.Material or Enum.Material.Concrete
                local soundId = FootstepSounds[material] or FootstepSounds[Enum.Material.Concrete]

                local sound = Instance.new("Sound")
                sound.SoundId = soundId
                sound.Volume = 0.5
                sound.PlaybackSpeed = 0.9 + math.random() * 0.2  -- Slight variation
                sound.Parent = rootPart
                sound:Play()

                Debris:AddItem(sound, 1)
            end
        end
    end)
end

Audio Effects

Sound Groups & Effects

-- Create reverb effect
local reverbGroup = Instance.new("SoundGroup")
reverbGroup.Name = "Reverb"
reverbGroup.Parent = SoundService

local reverb = Instance.new("ReverbSoundEffect")
reverb.DecayTime = 2
reverb.Density = 0.8
reverb.Diffusion = 0.9
reverb.DryLevel = 0
reverb.WetLevel = -6
reverb.Parent = reverbGroup

-- Create low-pass filter (muffled)
local muffledGroup = Instance.new("SoundGroup")
muffledGroup.Name = "Muffled"
muffledGroup.Parent = SoundService

local lowPass = Instance.new("EqualizerSoundEffect")
lowPass.HighGain = -20  -- Reduce high frequencies
lowPass.MidGain = -5
lowPass.LowGain = 0
lowPass.Parent = muffledGroup

-- Assign sounds to groups
caveSound.SoundGroup = reverbGroup
underwaterSound.SoundGroup = muffledGroup

Dynamic Audio Processing

local function applyUnderwaterEffect(enable)
    local muffleEffect = camera:FindFirstChild("UnderwaterMuffle")

    if enable then
        if not muffleEffect then
            muffleEffect = Instance.new("EqualizerSoundEffect")
            muffleEffect.Name = "UnderwaterMuffle"
            muffleEffect.HighGain = -30
            muffleEffect.MidGain = -10
            muffleEffect.LowGain = 5
            muffleEffect.Parent = SoundService
        end
    else
        if muffleEffect then
            muffleEffect:Destroy()
        end
    end
end

Doppler Effect (Moving Sources)

-- Roblox doesn't have built-in Doppler, but we can simulate it
local function simulateDoppler(sound, sourceVelocity, listenerVelocity)
    local speedOfSound = 343  -- m/​s

    local relativeVelocity = sourceVelocity - listenerVelocity
    local directionToListener = (camera.CFrame.Position - sound.Parent.Position).Unit
    local approachSpeed = relativeVelocity:Dot(directionToListener)

    -- Doppler shift formula
    local dopplerShift = speedOfSound / (speedOfSound + approachSpeed)
    sound.PlaybackSpeed = dopplerShift

    -- Also adjust volume slightly
    if approachSpeed > 0 then
        sound.Volume = sound.Volume * 1.1  -- Approaching, slightly louder
    else
        sound.Volume = sound.Volume * 0.9  -- Receding, slightly quieter
    end
end

Ambient Audio

Ambient Soundscape

local AmbientManager = {}
AmbientManager.activeSounds = {}

function AmbientManager.setup(sounds)
    for _, soundData in ipairs(sounds) do
        local sound = Instance.new("Sound")
        sound.SoundId = soundData.id
        sound.Volume = soundData.volume or 0.3
        sound.Looped = true
        sound.Parent = SoundService

        AmbientManager.activeSounds[soundData.name] = {
            sound = sound,
            baseVolume = soundData.volume or 0.3
        }
    end
end

function AmbientManager.setIntensity(name, intensity)
    local data = AmbientManager.activeSounds[name]
    if data then
        data.sound.Volume = data.baseVolume * intensity
    end
end

function AmbientManager.start()
    for _, data in pairs(AmbientManager.activeSounds) do
        data.sound:Play()
    end
end

-- Usage
AmbientManager.setup({
    {name = "wind", id = "rbxassetid://wind", volume = 0.2},
    {name = "birds", id = "rbxassetid://birds", volume = 0.3},
    {name = "water", id = "rbxassetid://river", volume = 0.4}
})
AmbientManager.start()

-- Based on location
if isNearWater then
    AmbientManager.setIntensity("water", 1)
else
    AmbientManager.setIntensity("water", 0.2)
end

Region-Based Audio

local AudioRegions = {}

local function checkAudioRegions()
    local character = Players.LocalPlayer.Character
    if not character then return end

    local position = character.PrimaryPart.Position

    for _, region in ipairs(AudioRegions) do
        local inRegion = position.X >= region.min.X and position.X <= region.max.X
                     and position.Y >= region.min.Y and position.Y <= region.max.Y
                     and position.Z >= region.min.Z and position.Z <= region.max.Z

        if inRegion and not region.active then
            region.active = true
            region.sound:Play()
            if region.onEnter then region.onEnter() end
        elseif not inRegion and region.active then
            region.active = false
            region.sound:Stop()
            if region.onExit then region.onExit() end
        end
    end
end

RunService.Heartbeat:Connect(checkAudioRegions)

More Other skills

Delegate independent tasks to specialized agents working concurrently with isolated context.

295.4K 206.3K
View

Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.

179.7K 100.1K
View

Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.

179.7K 89.5K
View

A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).

179.7K 76.1K
View

Fundamental design principles for writing maintainable, testable Python code.

40.2K 21.9K
View

Modern responsive layouts using container queries, fluid typography, CSS Grid, and mobile-first strategies.

40.2K 20K
View

Generate Playwright tests from scenarios using interactive browser exploration and validation.

39.7K 18.2K
View

Modern React state management with Redux Toolkit, Zustand, Jotai, and React Query for every state category.

40.2K 14.7K
View

GDPR-compliant data handling with consent management, data subject rights, and privacy controls.

40.2K 14.3K
View

Python error handling patterns including input validation, exception hierarchies, and partial failure handling. Use when implementing validation logic, designing exception strategies, handling batch processing failures, or building robust APIs.

40.2K 13.5K
View

Detailed multi-file refactor planning with safety checks and rollback guidance.

39.7K 13K
View