A typical game mix: music, the spatial world and UI sounds, each with their own volume, and nothing that clips.
SpatialAudioRenderer2D or SpatialAudioRenderer3D with normal Channels.Limiter, and a limiter on the device's master channel.Compressor on the music.Music clip -> Channel "Music" [Compressor] --+
UI clips -> Channel "UI" ------------------+--> device master [Limiter] --> speakers
^
World sources -> SpatialAudioRenderer -> renderer.master [Limiter] ------------+
By default every spatial renderer has its own master channel, created with audioDevice.createMasterChannel(). It connects to the speakers next to the device's default master channel. A renderer can also send to a bus channel instead, with the output option; see Example 12 for a mix with a bus per category.
import { AudioClip, Compressor, Limiter, SpatialAudioRenderer2D } from "@fluex/fluexgl-dsp";
// Assumes `audioDevice` from Example 01, and loaded `musicData` and `clickData`.
const deviceMaster = audioDevice.getMasterChannel();
// --- Music: compressed, not spatial ---
const music = audioDevice.createChannel("Music");
music.send(deviceMaster);
music.addEffect(new Compressor({ threshold: -18, ratio: 3, makeupGain: 2 }));
music.volume(0.5);
const musicClip = new AudioClip(musicData);
musicClip.send(music);
musicClip.setLoop(true);
musicClip.play();
// --- UI: not spatial ---
const ui = audioDevice.createChannel("UI");
ui.send(deviceMaster);
const clickClip = new AudioClip(clickData);
clickClip.send(ui);
// --- Safety limiter on the device master (music + UI) ---
deviceMaster.attachEffect(new Limiter({ ceiling: -1 }));
// --- World: spatial, with its own master and limiter ---
const world = new SpatialAudioRenderer2D(audioDevice, {
limiter: { ceiling: -1, release: 0.15 }
});
// World volume, for example from a settings menu (0..1).
function setWorldVolume(volume: number) {
world.master.gainNode?.gain.setTargetAtTime(volume, audioDevice.context.currentTime, 0.05);
}
// Duck the world while a dialog is open.
function setDialogOpen(open: boolean) {
setWorldVolume(open ? 0.3 : 1);
}
setInterval(() => {
console.log("world limiter reduction:", world.limiter?.reduction.toFixed(1), "dB");
}, 250);
If the limiter is reducing by more than a few dB most of the time, the mix is too loud: lower source volumes or world.master instead of relying on the limiter.
master is a normal Master. Its gainNode sits after the limiter, so lowering it never causes clipping.world.master after construction come after the built-in limiter. If you need effects before the limiter, create the renderer with limiter: false, attach your effects, then attach your own Limiter last.channel.volume(0).AudioDevice.