Skip to content

AudioManager

The AudioManager system owns the audio mixer for your game: a bus tree with parent / child relationships, default fader levels in dB, ducking rules between buses, and a voice pool that caps simultaneous playback.

Open it from Inspector → Systems → AudioManager → Open. The editor renders the mixer visually so you can see and tune everything at once.

A bus is a named audio channel with an optional parent and a default fader level (dB, from -60 to +6).

Common shapes:

Master
├── Music
├── SFX
│ ├── SFXOneshot
│ └── SFXLoop
├── Dialog
└── UI

The Master bus is the root; everything else hangs off it. A child bus’s gain is multiplied by its parent’s, so muting Master mutes everything.

To add a bus, click + Add bus in the editor. Each bus gets:

  • A name (required, unique).
  • A parent (optional; defaults to “no parent” which makes it a root).
  • A default dB (-60 to +6).

Names go into your code (audio.playSfx('/audio/coin.wav', { bus: 'SFXOneshot' })), so pick them carefully. Renaming a bus is a hand-edit-everywhere operation.

Ducking lowers the volume on one or more buses whenever a different bus emits sound. The canonical example: when Dialog plays, Music ducks by 8 dB and SFXLoop ducks by 6 dB so the player can hear the line.

Each rule has:

  • A trigger bus (whenBus): the source that causes the duck.
  • A list of targets: each with the bus name, the amount in dB to duck, an attack time in ms, and a release time in ms.

The defaults in the bundled bundles are tuned for typical game audio:

PatternAttackReleaseWhy
Music ducking under Dialog80 ms250 msFast enough to clear the first word, soft enough not to pop
SFXLoop ducking under Dialog80 ms250 msSame shape; loops are usually ambient
SFXLoop ducking under SFXOneshot40 ms150 msQuick punch for hit / pickup feedback

The editor surfaces the rules as a visual matrix so you can see at a glance which bus ducks which.

The voicePoolSize field caps how many concurrent audio sources can play at once. The Web Audio API has practical limits on simultaneous sources; 16 is a sensible default for most games. Bump it for sound-dense games (twin-stick shooters, bullet hells), lower it for narrative games where you want to guarantee dialogue never gets crowded out.

When the pool is full, the AudioManager’s behavior depends on the source’s priority: low-priority oneshots are dropped, high-priority lines wait.

Music crossfade on level transition: call audio.crossfade('Music', '/audio/music/forest.ogg', 800). The current music fades out over 800ms while the new track fades in.

Dialogue voice-over: route to the Dialog bus. The ducking rule fires automatically; you don’t need to manually lower Music every time.

One-shot SFX (jump, coin, hit): route to SFXOneshot. Pool eviction picks the oldest oneshot when the pool fills.

Looping ambience (wind, water, machinery): route to SFXLoop. Don’t put it on SFXOneshot; the eviction strategy will kill your ambience the moment any other oneshot fires.

The AudioManager’s config in .forge/gamemanager.toml looks like:

[systems.AudioManager.config]
voicePoolSize = 16
buses = [
{ name = "Master", defaultDb = 0 },
{ name = "Music", parent = "Master", defaultDb = -6 },
{ name = "SFX", parent = "Master", defaultDb = -3 },
{ name = "Dialog", parent = "Master", defaultDb = 0 },
]
duckingRules = [
{ whenBus = "Dialog", duck = [
{ bus = "Music", amountDb = -8, attackMs = 80, releaseMs = 250 },
{ bus = "SFX", amountDb = -6, attackMs = 80, releaseMs = 250 },
] }
]

Hand-edit it and the editor will pick up your changes on next load. Validation: bus names must be unique, every parent must reference an existing bus, dB values are clamped to the -60 to +6 range.