Skip to content

Mixer

Mixer groups your audio into buses — like Music, SFX, and Ambience — so you can control whole categories of sound at once. Route any Source or SoundFontInstrument to a group, then set that group’s volume, mute, or solo. Each sound plays at its own volume times its group’s effective gain (the group’s gain times its ancestors’, gated by mute and solo), applied every frame. Groups nest, so a Master group scales everything. Introduced by engine PLM-147 (ADR 0046).

Mixer is a static facade; MixerGroup is a handle to one group.

Returns: the MixerGroup for the implicit Master bus (the root every group descends from).

Get or create a group of that name directly under Master.

Returns: a MixerGroup.

Get or create a group named name nested under an existing parent MixerGroup.

Returns: a MixerGroup.

import "engine" for Mixer
var music = Mixer.group("Music")
var sfx = Mixer.group("SFX")
var footsteps = Mixer.groupUnder("Footsteps", sfx) // SFX/Footsteps

Route a Source (from Audio.newSource / Audio.newStreamingSource) to this group.

Route a SoundFontInstrument to this group.

var shot = Audio.newSource("sfx/shot.wav")
sfx.addSource(shot)
var piano = SoundFontInstrument.new("soundfonts/piano.sf2", 48000, 2)
music.addInstrument(piano)

A sound that is never routed plays under Master (unity gain), so adding the mixer to a project changes nothing until you route sounds and adjust groups.

Returns: String — the group’s name.

The group’s linear gain (1 = unity). Setting it scales every sound routed to the group (and its child groups). Clamped to >= 0.

Mute or unmute the group. A muted group (and everything under it) is silent, regardless of volume.

Solo the group. While any group is soloed, only soloed groups and their subtrees are audible — everything else is silenced. (Mute still wins inside a soloed subtree.)

music.volume = 0.6 // duck the music bus
sfx.muted = true // silence all SFX
music.soloed = true // hear only Music (and its children)

A group can duck — automatically drop in level — while another group is making sound. The classic use is music ducking under dialogue or SFX. Introduced by PLM-148 (ADR 0047).

group.duckUnder(triggerGroup, amountDb, attack, release)

Section titled “group.duckUnder(triggerGroup, amountDb, attack, release)”

Make this group duck while triggerGroup’s subtree has a playing source.

Parameters:

  • triggerGroup (MixerGroup) — when any source routed to this group (or its children) is playing, the duck fires.
  • amountDb (Num) — the reduction at full duck, in decibels (e.g. -12; 0 or positive = no duck).
  • attack (Num) — seconds to ramp down to the ducked level.
  • release (Num) — seconds to ramp back to full when the trigger goes quiet.

Remove the duck and release the group back to full level.

Returns: Num — the live duck envelope (1 = not ducked, down to the target while ducking). Useful for a meter.

var music = Mixer.group("Music")
var voice = Mixer.group("Voice")
// Music drops 12 dB whenever anything on the Voice bus is playing.
music.duckUnder(voice, -12, 0.05, 0.4) // 50 ms attack, 400 ms release

Ducking is activity-driven: it fires whenever a source in the trigger’s subtree is playing (not keyed to its loudness — a level-metered sidechain compressor is a later, per-group-DSP capability). The duck composes with the group’s own volume, and mute/solo still win.

A group can host audio effects via OpenAL EFX — put a whole bus in a reverb space, or run its sound through an echo, chorus, distortion, and more. Everything routed to the group takes on the effect, and a group can hold several effects in order (a serial chain). Introduced by PLM-153 (reverb, ADR 0050); the full suite by PLM-154 (ADR 0051). Effects need a device with ALC_EXT_EFX; without it these calls are safe no-ops and audio stays dry.

Add an effect by type name. Returns a MixerEffect handle, or null for an unknown type. Call it more than once on a group to build an ordered chain.

Types (case-insensitive): reverb, chorus, echo, flanger, distortion, equalizer, frequencyshifter, vocalmorpher, pitchshifter, ringmodulator, autowah, compressor. Each starts with OpenAL’s sensible defaults, so addEffect("echo") is already a working echo — tune it with setParam.

Shortcut for addEffect("reverb") with a named EAX preset applied. Returns a MixerEffect handle.

Presets: Generic, Room, Bathroom, LivingRoom, StoneRoom, Auditorium, ConcertHall, Cave, Arena, Hangar, Alley, Forest, City, Mountains, Plain, ParkingLot, Underwater, Drugged, Dizzy (case-insensitive). Presets are reverb-only; other effect types are tuned purely with setParam.

Remove all effects on the group (back to dry). This invalidates any MixerEffect handles into the group.

Returns: Num — how many effects the group has.

Set the group’s wet/dry mix — how much the effect is heard. An effect is a parallel wet mixed on top of the dry signal, and OpenAL caps the wet at unity, so on a full-volume bus an added effect can be too quiet to notice. setEffectMix fixes that by ducking the dry so the wet stands out:

  • mix = 0 (default) — dry unchanged; a subtle parallel wet (the original behavior).
  • mix → 1 — dry fully ducked; you hear mostly the effect (insert-style, e.g. for distortion).
  • e.g. 0.5 — dry −6 dB, so a reverb or echo is clearly audible.

Raising the mix lowers the group’s overall level (the usual wet/dry trade) — turn the group’s volume up to make it up. Only bites when the group has effects.

var music = Mixer.group("Music")
music.addReverbPreset("Cave")
music.setEffectMix(0.5) // now the cave is clearly audible, not a whisper under the mix
var music = Mixer.group("Music")
music.addReverbPreset("Cave") // a reverb space
var guitar = Mixer.group("Guitar")
var dist = guitar.addEffect("distortion") // a chain: distortion -> echo
dist.setParam("edge", 0.3)
var echo = guitar.addEffect("echo")
echo.setParam("delay", 0.2)
echo.setParam("feedback", 0.5)

Separate from the effect chain, a group can carry one direct-path filter — a low-pass (muffle), high-pass (thin out), or band-pass — that shapes the dry signal every source on the bus produces (the OpenAL AL_DIRECT_FILTER). It composes with the effects: a group can be both low-passed and sent through a reverb. Introduced by PLM-157 (ADR 0052). Also needs ALC_EXT_EFX.

Gains are linear 0..1 (1 = pass unchanged, 0 = fully cut). OpenAL’s filters are shelf gains, not a swept cutoff with resonance — you attenuate a band, you don’t sweep a resonant frequency.

Low-pass the group: gain scales the whole signal, gainHF how much of the highs pass (lower = more muffled).

High-pass the group: gainLF how much of the lows pass (lower = thinner).

Band-pass: attenuate both the lows (gainLF) and the highs (gainHF).

Remove the group’s filter (back to full-range dry).

var voice = Mixer.group("Voice")
voice.setLowPass(1.0, 0.1) // muffled, like through a wall
// voice.setHighPass(1.0, 0.1) // thin, like a telephone
// voice.clearFilter()

A handle to one effect on a group, returned by addEffect / addReverbPreset.

Set a reverb preset (see the list above). Only meaningful for a reverb effect.

Set a named parameter to a number. Names are case-insensitive; a name that doesn’t belong to this effect type is ignored. Int-valued params (waveforms, phase, tunings, on/off, shift direction) take a whole number.

Per-effect parameter names:

EffectParameters
reverbdensity, diffusion, gain, gainhf, gainlf, decaytime, decayhfratio, decaylfratio, reflectionsgain, reflectionsdelay, latereverbgain, latereverbdelay, echotime, echodepth, modulationtime, modulationdepth, airabsorptiongainhf, hfreference, lfreference, roomrollofffactor
choruswaveform, phase, rate, depth, feedback, delay
flangerwaveform, phase, rate, depth, feedback, delay
echodelay, lrdelay, damping, feedback, spread
distortionedge, gain, lowpasscutoff, eqcenter, eqbandwidth
equalizerlowgain, lowcutoff, mid1gain, mid1center, mid1width, mid2gain, mid2center, mid2width, highgain, highcutoff
frequencyshifterfrequency, leftdirection, rightdirection
vocalmorpherphonemea, phonemeacoarsetuning, phonemeb, phonemebcoarsetuning, waveform, rate
pitchshiftercoarsetune, finetune
ringmodulatorfrequency, highpasscutoff, waveform
autowahattacktime, releasetime, resonance, peakgain
compressoronoff

OpenAL’s Compressor is on/off only, and its Equalizer is a fixed 4-band — a parameterized compressor (threshold/ratio) and an arbitrary parametric EQ are a later software-mix feature, not part of the EFX set.

var music = Mixer.group("Music")
var verb = music.addReverbPreset("Cave")
verb.setParam("decaytime", 3.5) // lengthen the tail
// music.clearEffects() // back to dry

Set default group volumes in game.toml’s [Mixer] section; the engine applies them at startup (pre-creating the groups). Persist a player’s changes with:

Returns: Bool — writes the current group volumes and mute to settings.toml in the app directory, so the player’s mix survives relaunch. Call it when the player changes a level.

Returns: Bool — re-applies settings.toml now. The engine already auto-loads it at startup (after the game.toml defaults, so player overrides win).

music.volume = 0.4
Mixer.saveSettings() // remember the player's choice
  • Applied every frame. The engine reads each group’s effective gain each frame and applies it to the live sources routed to it, on top of each source’s own setVolume. Your setVolume value is preserved — the group multiplier does not leak back into it.
  • Nesting. A group’s effective gain includes all its ancestors’, so a Master volume of 0.5 halves everything, and a SFX volume scales SFX/Footsteps too.
  • get-or-create. Mixer.group(name) returns the same group each time, so you can fetch a bus by name from anywhere without tracking the handle.