Skip to content

Mixing & Buses

Every voice routes through a bus, a mix group with its own volume and mute. Buses are how you give players a Music slider that doesn’t touch gunfire, or duck the world under dialogue.

BusFor
MasterEverything. Feeds the output device.
MusicScore and licensed tracks.
SFXWorld and gameplay sound. The default for new sources.
UIMenu clicks, notifications, anything non-diegetic.
VoiceDialogue and VO.
AmbientBeds, room tone, weather.

The five named buses all feed Master, so Master’s volume scales the whole mix. Pick a bus per source with its Bus property, per play call with the play params’ Bus field, or move a live voice with SetBus.

A bus volume is a plain multiplier, 1.0 is unchanged and 0.0 is silent. The starting values ship in the audio settings, and anything can change them at runtime.

GAudioContext->SetBusVolume(EAudioBus::Music, 0.4f);
GAudioContext->SetBusMuted(EAudioBus::Voice, true);

Volume changes are ramped rather than applied instantly, so dragging a slider doesn’t click.

This is the whole pattern. Read the current values to populate your sliders, write them back as the player drags, and persist once when they hit Apply.

The difference between the two languages is only in the last step. C# has a single SaveMixSettings() call; in C++ you copy the live volumes into CAudioSettings and ask the config system to write it, which is exactly what that C# call does internally.

// Populate.
const float Music = GAudioContext->GetBusVolume(EAudioBus::Music);
const float Sfx = GAudioContext->GetBusVolume(EAudioBus::SFX);
// Live preview while dragging.
GAudioContext->SetBusVolume(EAudioBus::Music, MusicSlider);
GAudioContext->SetBusVolume(EAudioBus::SFX, SfxSlider);
// Apply.
CAudioSettings* Settings = GetMutableDefault<CAudioSettings>();
for (uint32 Index = 0; Index < NumAudioBuses; ++Index)
{
const EAudioBus Bus = (EAudioBus)Index;
Settings->SetBusVolume(Bus, GAudioContext->GetBusVolume(Bus));
}
GConfig->SaveSettings(CAudioSettings::StaticClass());

Either way the values land in the project’s AudioSettings.json, so they come back on the next launch.

There’s no automatic sidechain, but a duck is a few lines: drop the bus you want out of the way, restore it when the important sound finishes.

FAudioPlayParams Params;
Params.Bus = EAudioBus::Voice;
FAudioHandle Line = GAudioContext->PlayAudio(Dialogue->GetAudioData(), Params);
GAudioContext->SetBusVolume(EAudioBus::Music, 0.25f);
// When GAudioContext->IsPlaying(Line) goes false, put it back.
GAudioContext->SetBusVolume(EAudioBus::Music, 1.0f);

The engine has one shared reverb return. Buses send into it, so you can wet the world without touching UI or music.

C# takes the four reverb parameters as arguments; C++ fills in an FAudioReverbParams struct with the same four fields.

GAudioContext->SetBusReverbSend(EAudioBus::SFX, 0.35f);
GAudioContext->SetBusReverbSend(EAudioBus::Ambient, 0.5f);
FAudioReverbParams Reverb;
Reverb.RoomSize = 0.8f;
Reverb.Damping = 0.4f;
Reverb.Width = 1.0f;
Reverb.WetLevel = 0.5f;
GAudioContext->SetReverbParams(Reverb);
ParameterDoes
Room SizeDecay length. 0 is a small room, 1 is a cathedral.
DampingHigh-frequency absorption in the tail. Higher is darker.
WidthStereo spread of the tail.
Wet LevelGain of the reverb return.

Sends default to 0 and the reverb network isn’t built until something asks for it, so a project that never uses reverb pays nothing for it.

Because the parameters are live, reverb zones are just a trigger volume that sets them on enter. Starting values for the sends and the reverb shape live in the audio settings.