NEWA drop-in replacement for AssetService:SearchAudioAsyncRead the docs →
← API Reference
Sound design

Audio Packs

NEW

One sound, built from layers that play together — each picking from its own pool.


On this pageWhat it is

What it is

A gunshot is not one recording. It is a crack, a mechanical clack a beat later, and a tail that rings out — three sounds fired together. Play the same three every time and it reads as a loop; swap one clip in each and the same weapon sounds alive.

An Audio Pack is that structure written down: a named sound made of layers that all fire on one trigger, where each layer picks one variation from its own pool. It is a JSON document you build in the pack editor, with gain, trim points, delays and fades on every piece.

Layers and variations

These are the two axes, and mixing them up is the one mistake worth avoiding:

  • Layers play together. Every layer in the pack sounds on every fire. Three layers means three simultaneous sounds.
  • Variations play instead of each other. A layer picks exactly one of its variations per fire. Three variations means one sound that differs run to run.

So a pack with 3 layers of 4 variations plays 3 sounds at once, chosen from 64 combinations. A pack may hold up to 24 layers, 64 variations per layer, and 128 variations in total — far more than a real weapon pack uses.

Build one

Open Audio Packs in the console. Add a layer per component, search the catalog for each variation, and audition the whole thing — the editor decodes every asset and plays the layers exactly as the format says, so what you hear is what the pack does. Export the JSON when it sounds right, or mark the pack public so other developers can start from it.

Packs also import: drop in a JSON document and it is validated against the field rules below before anything is saved.

The format

One JSON document. format is always 2 and consumers must reject a pack without it.

JSON
{
  "format": 2,
  "name": "Pistol",
  "masterVolume": 1,
  "layers": [
    {
      "name": "Shot",
      "role": "shot",
      "gain": 1,
      "variations": [
        { "id": "77455268208044", "gain": 1.31 },
        { "id": "83214550119820", "gain": 1.18 },
        { "id": "94120385572213", "gain": 1.24 }
      ]
    },
    {
      "name": "Mech",
      "role": "foley",
      "gain": 0.8,
      "delaySec": 0.02,
      "variations": [
        { "id": "71204885530012", "cut": { "start": 0.1, "end": 0.45 } }
      ]
    },
    {
      "name": "Tail",
      "role": "tail",
      "gain": 0.6,
      "delaySec": 0.05,
      "fadeOut": { "time": 0.4 },
      "variations": [{ "id": "88012774553091" }]
    }
  ]
}
FieldOnMeaning
formatpackRequired. Always 2.
namepack, layerRequired. A display label — never infer a role from it.
masterVolumepackOptional, default 1. Linear multiplier over the pack.
rolelayerOptional. What the layer is: shot, foley, punch, tail, bed, event, or any string.
gainlayer, variationOptional, default 1. Linear multiplier. Values above 1 are legal.
delaySeclayerOptional. Fires this many seconds late; humanized ±20% per fire.
looplayerOptional, ambient. Plays continuously. Mutually exclusive with every.
everylayerOptional, ambient. { min, max } seconds; each next fire is drawn uniform-random.
fadeIn / fadeOutlayerOptional. { time, curve } — curve is "exp" (default) or "linear".
idvariationRequired. A Roblox audio asset id, as a string.
cutvariationOptional. { start, end } seconds into the source asset.

There is deliberately no mute/solo (editor session state, never data), no metadata block (the catalog is the live source for asset facts — snapshots go stale), no ids or timestamps (whatever stores a pack owns its identity), and no nested groups. Layers are a flat list.

Timing and fades

delaySec is what stops a layered sound reading as one mushy hit — the mech clack lands 20ms after the crack, the tail 50ms after that. The runtime humanizes the value ±20% on every fire, so repeated shots never phase-lock. There is no separate knob for that.

fadeIn applies on fire, fadeOut on stop or retrigger — a short fadeOut doubles as a declick. The default "exp" curve is a true dB-linear ramp, which is what a fade sounds like to a listener; "linear" ramps the raw multiplier and is only right for very short declicks.

Ambient layers

A pack's mode is inferred from its data — there is no mode field. No layer carrying loop or every means triggered: one fire, all layers sound once. Otherwise the pack is ambient.

JSON
{
  "format": 2,
  "name": "Swamp at night",
  "layers": [
    {
      "name": "Bed",
      "role": "bed",
      "loop": true,
      "fadeIn": { "time": 2 },
      "variations": [{ "id": "70112884553900" }]
    },
    {
      "name": "Frogs",
      "role": "event",
      "every": { "min": 4, "max": 11 },
      "variations": [
        { "id": "83120495528817" },
        { "id": "91274003318452" }
      ]
    }
  ]
}

every is a random interval, never a fixed cadence — a frog on a 6-second timer is a metronome, a frog on { min: 4, max: 11 } is a swamp.

Gain and cuts

Gains compose multiplicatively, and all three are plain linear multipliers, so an editor slider maps to a stored value 1:1.

effective volume = masterVolume × layer.gain × variation.gain
1 (pack) × 0.8 (Mech layer) × 1.0 (variation) = 0.8

A cut is an inner slice: playback seeks to start and stops at end. It is how you use the useful 300ms of a 4-second upload. A cut counts only if end − start > 0.01 — anything narrower is hand-editing residue and gets dropped, and the variation plays whole-file.

One trap: cut.end may sit past the end of the asset. It clamps harmlessly at playback, but never read it as an asset duration.

Use it in a game

A pack is data, not a runtime. Export the JSON from the editor and drive it from your own code: on each fire, walk the layers, pick one variation per layer, apply masterVolume × layer.gain × variation.gain, honour delaySec and cut, and play them together.

The MCP server ships an audio-weapons skill that writes that wiring for you, including the emitters and buses. There is no AudioScape:playPack() in the Luau SDK today — if you want one, say so and it moves up the list.

Packs vs sound banks

Both fight repetition, from opposite ends.

  • An Audio Pack is authored. You choose every variation and shape it — gain, trim, delay, fade — and you can layer. Use it when the sound matters: a weapon, an ability, a signature impact.
  • A sound bank is found. You give one asset and the SDK expands it into a pool of neighbours at runtime, in one request. Use it when you have a hundred footsteps to de-duplicate and no time to audition any of them.

And an SFX playlist is neither: a flat list of sound effects your game pulls from, curated the same way a music playlist is.