Skip to content

Audio System

PlatanoGames edited this page Feb 26, 2026 · 3 revisions

Audio System v1.1 -- Dual-Backend Audio Management

Lyra-validated. Production-hardened. Zero audio-programmer required.


The Problem

Unreal Engine ships with two audio systems that solve overlapping problems in incompatible ways.

The legacy pipeline -- SoundMix, SoundClass, Sound Concurrency -- has been stable since UE4. Every tutorial on YouTube uses it. Every marketplace pack assumes it. It works, it is understood, and it has known limitations: no per-bus modulation, no parameter-driven mixing, no runtime-programmable attenuation curves.

The Audio Modulation pipeline -- Control Buses, Control Bus Mixes, Modulators -- arrived in UE5 as Epic's answer to modern audio middleware. It is powerful, parameter-driven, and almost entirely undocumented for game teams that are not Epic. Lyra uses it. Nobody else seems to.

Here is what happens in practice:

  1. Teams pick one pipeline and lose the other. A project that starts with Legacy cannot switch to Modulation without rewriting every mix configuration. A project that starts with Modulation discovers that half their audio middleware integration assumes SoundClass.

  2. Mix management is manual and fragile. There is no layered priority system. When a loading screen needs to mute gameplay audio, someone writes custom code. When dialogue needs to duck music, someone writes more custom code. When those two interact, someone writes a bug.

  3. There is no music state machine. The engine provides playback primitives -- play, stop, fade. But crossfading between combat music and exploration music based on game state? That is all bespoke code in every project.

  4. There is no dialogue queue. Priority-based dialogue with automatic ducking, subtitle callbacks, and interruption policies? Custom implementation every time.

  5. There is no observability. When audio behaves unexpectedly in a 200-person multiplayer session, the debugging tool is "add print statements and hope." There is no channel inspector, no pool statistics, no event history.

  6. Memory management is invisible. Sound pools grow without limits. Concurrent sound counts are uncapped or capped with no policy. The first time a team discovers their audio memory budget is a platform certification failure.

The core tension: the engine provides primitives, not a system. Every project builds the same audio management layer from scratch, makes the same mistakes, and ships with the same blind spots.


How PGX Solves It

PGX Audio wraps both engine pipelines behind a single unified interface. The project configures which backend to use -- Legacy, Modulation, or Auto-detect -- in a single Data Asset. The backend can be hot-switched at runtime without restarting. All active sounds gracefully fade out during the switch, and the new backend takes over seamlessly.

The architecture was validated against Epic's Lyra audio implementation. Where Lyra uses a World-scoped subsystem for per-level mixing, PGX does the same. Where Lyra separates global audio state from level-specific state, PGX follows the same split. The difference: PGX makes this architecture configurable and backend-agnostic rather than hardcoded to Audio Modulation.


Architecture

                          +-------------------------------------------+
                          |          PGX Audio System v1.1             |
                          +-------------------------------------------+
                          |                                           |
  Game Code / BP  ------->|  Blueprint Library  (sole BP entry point) |
                          |       |                                   |
                          |       v                                   |
                          |  [Global Subsystem]    [Per-Level Subsystem]
                          |  (persists across       (destroyed on travel,
                          |   level transitions)     recreated per world)
                          |       |                        |          |
                          |       |                   5-Layer Mix     |
                          |       |                   Ducking Rules   |
                          |       |                   Ambient Zones   |
                          |       |                   HDR/LDR Chains  |
                          |       |                   HRTF Toggle     |
                          |       |                                   |
                          |  +----+----+                              |
                          |  |         |                              |
                          |  v         v                              |
                          | [Music]  [Dialogue]   [Sound Pool]       |
                          | Manager   Manager     (memory-tracked)   |
                          |  |         |              |               |
                          +--+---------+--------------+---------------+
                             |         |              |
                             v         v              v
                       +----------+ +----------+  +----------+
                       | Backend  | | Backend  |  | Backend  |
                       | Legacy   | | Modula-  |  | (future) |
                       | SoundMix | | tion     |  |          |
                       | SoundCls | | CtrlBus  |  |          |
                       +----------+ +----------+  +----------+

Two subsystem hosts solve a fundamental lifecycle problem:

  • The Global Subsystem persists across level transitions. It owns the music manager, dialogue manager, sound pool, backend selection, and user volume preferences. When the player travels between levels, music keeps playing, dialogue is not interrupted, and volume settings are preserved.

  • The Per-Level Subsystem is destroyed and recreated with each world. It owns level-specific audio: the 5-layer mix stack, ducking rules, ambient zones, reverb configuration. When a new level loads, it reads its audio configuration from a per-level Data Asset and rebuilds the mix state from scratch. No stale state from previous levels leaks through.

The Global Subsystem detects when the Per-Level Subsystem is null (during travel) and gracefully skips ducking and level-specific features until the new world initializes.


The 5-Layer Mix Model

Audio mixing in PGX follows a strict priority stack. Higher layers override lower layers. This eliminates the "who changed my volume?" problem that plagues manual mix management.

  Priority
     ^
     |   +----------------------------+
  5  |   |  User Preferences          |  <-- Player's volume sliders (highest priority)
     |   +----------------------------+
  4  |   |  Loading Screen            |  <-- Mute/attenuate during loads
     |   +----------------------------+
  3  |   |  Ducking                   |  <-- Dynamic: dialogue ducks music, etc.
     |   +----------------------------+
  2  |   |  Level Overrides           |  <-- Per-level mix from Level Audio Config
     |   +----------------------------+
  1  |   |  Default Base              |  <-- Global defaults, always active
     |   +----------------------------+
Layer Scope Lifetime Description
Default Base Global Always active Base mix values from the global config. The foundation everything else builds on.
Level Per-level World lifetime Overrides from the level's audio configuration. Caves are quieter, arenas are louder.
Ducking Per-level Dynamic Automatic attenuation. When a dialogue line plays, music ducks by 6dB over 200ms. Rules are data-driven -- no code required.
Loading Screen Global During loads Attenuates or mutes gameplay audio while loading screens are active. Integrates with the Loading system automatically.
User Global Session lifetime The player's volume preferences. Persisted through the Save system. Always wins.

Each layer stores per-channel volume values. The final mix is computed by compositing all active layers top-down. A channel that is not modified by a higher layer inherits the value from below.


Capabilities

Sound Resolution with Tag-Based Variants

Rather than referencing audio assets directly, game code references sounds by GameplayTag. The system resolves the tag to the correct sound variant based on context.

A single sound tag might resolve to different audio assets based on equipped weapon, current biome, time of day, or any combination of gameplay tags. Variant selection supports weighted random, sequential round-robin, pure random, or first-match strategies.

  Sound Tag: "Footstep.Run"
       |
       v
  +------------------------------------+
  | Variant Resolution                 |
  |                                    |
  |  Context: [Surface.Wood] --------> Footstep_Wood_01..03 (random)
  |  Context: [Surface.Metal] -------> Footstep_Metal_01..05 (weighted)
  |  Context: [Surface.Grass] -------> Footstep_Grass_01..04 (sequential)
  |  Default (no context match) -----> Footstep_Generic_01..02
  +------------------------------------+

This is purely data-driven. Adding a new surface type means adding a row to the sound definition asset. No code changes.

Music Management

The music manager is a state machine with built-in crossfade support.

  States:  Idle ----> Playing ----> CrossFading ----> Playing (new track)
                        |                               |
                        v                               v
                      Paused                          Stopping ----> Idle

Playlists define track sequences with per-track crossfade durations, minimum play times, and interrupt policies. Music state is driven by game flow -- when the game enters combat, the music state transitions to the combat playlist. When combat ends, it crossfades back. This integration is automatic when the game flow system is present; manual API calls work without it.

Dialogue Queue

The dialogue manager implements a priority queue with automatic ducking and subtitle dispatch.

When a dialogue line is queued:

  1. If nothing is playing, it plays immediately.
  2. If a lower-priority line is playing, the new line interrupts it.
  3. If an equal-or-higher-priority line is playing, the new line waits in the queue.
  4. When a line starts playing, the ducking system automatically attenuates configured channels (typically music and ambient).
  5. A subtitle delegate fires with the text, duration, and speaker tag for UI display.

Four priority levels: Low, Normal, High, Critical. Critical lines always interrupt.

Sound Pool with Memory Tracking

The sound pool manages a pre-allocated set of sound instances to prevent per-frame allocation. When the pool is exhausted:

  1. The lowest-priority active sound is reclaimed.
  2. If all sounds have equal priority, the oldest is reclaimed.
  3. A delegate fires to notify monitoring systems of the exhaustion event.

Pool statistics are available at any time: total capacity, in-use count, available count, peak usage, miss count, and growth events. Memory estimation covers loaded sound count, approximate memory consumption, and per-consumer breakdowns.

Ducking Rules (Data-Driven)

Ducking rules define automatic volume attenuation relationships between channels.

  Rule: "Dialogue Ducks Music"
  +----------------------------------+
  | Trigger Channel: Dialogue        |
  | Target Channel:  Music           |
  | Duck Volume:     -6 dB           |
  | Attack Time:     200 ms          |
  | Release Time:    500 ms          |
  | Trigger Threshold: Any sound     |
  | Context:         (any)           |
  | Priority:        100             |
  +----------------------------------+

Rules can be context-dependent. A ducking rule might only activate during combat (using a context tag). Multiple rules can target the same channel -- they stack by priority.

HDR Audio and HRTF

  • HDR/LDR chains: Switch between high-dynamic-range and standard audio processing. HDR preserves detail in quiet sounds while allowing loud sounds to exceed the normalized range. Brief crossfade during switch.
  • HRTF (Head-Related Transfer Function): Spatial audio for VR platforms. Toggled per-platform via the Profile system's capability flags.

Configuration

All configuration is data-driven through 7 Data Asset types:

Asset Type Purpose Discovery
Global Config Backend selection (Legacy/Modulation/Auto), pool size, HDR/LDR chain references, HRTF defaults, ducking config reference, trace integration Auto-discovered
Channel Config Per-channel settings: tag identity, default volume, engine-specific bus/class references Auto-discovered
Sound Definition Sound + tag-query variant resolution. Multiple variants with context tags, weights, and volume multipliers Auto-discovered
Audio Profile Playback profile: volume/pitch multipliers, attenuation overrides, fade settings, 2D/3D mode Auto-discovered
Music Playlist Track sequence with crossfade durations, loop/shuffle settings, game-state activation tag Auto-discovered
Ducking Config Collection of ducking rules: trigger/target channels, duck volume, attack/release times, context tags Auto-discovered
Level Audio Config Per-level settings: ducking override, ambient zone definitions, reverb configuration, music state to activate on level load Auto-discovered

All 7 types are auto-discovered via Asset Registry scan at initialization. Drop the asset in your Content directory, and the system finds it.

Configuration Flow

  1. Create Global Config DA -----> Set backend, pool size, features
  2. Create Channel Configs  -----> One per audio channel (Music, SFX, Voice, etc.)
  3. Create Sound Definitions ----> One per logical sound, with variants
  4. Create Audio Profiles   -----> Reusable playback settings
  5. Create Ducking Config   -----> Define ducking relationships
  6. Create Level Audio DAs  -----> One per level with specific mix overrides
  7. (Optional) Music Playlists --> Define track sequences per game state

Zero-config default: if no Global Config DA exists, the system initializes with Legacy backend, default pool size, and no ducking. Add configuration progressively as needed.


Editor Tooling

Audio Inspector (12 Sections)

The inspector provides live monitoring of every aspect of the audio system:

Section What It Shows
System Status Current state, backend type, initialization health
Channel Mixer Live volume bars per channel with mute toggles
Active Sounds Table of all playing sounds: tag, channel, profile, age, priority, location
Music Manager Current state (Playing/Crossfading/etc.), current track, playlist progress
Dialogue Manager Current line, queue contents, subtitle state
Mix Layers Visualization of all 5 layers and their per-channel values
Ducking Rules Active rules showing trigger/target channels and current duck state
Sound Pool Capacity, usage, peak, miss count, growth events
Memory Loaded sound count, estimated memory, per-consumer breakdown
Event History Scrollable log of recent audio events (plays, stops, transitions, errors)
Profile Constraints Active platform budgets (concurrent limits, memory caps)
Backend Details Per-backend diagnostic information

Console Commands (20)

  pgx.audio.status          System state summary
  pgx.audio.channels        Channel volumes and mute states
  pgx.audio.playing         Active sound list
  pgx.audio.music           Music manager state
  pgx.audio.pool            Sound pool statistics
  pgx.audio.memory          Memory usage breakdown
  pgx.audio.history [N]     Last N audio events
  pgx.audio.backend         Backend type and details
  pgx.audio.set <ch> <vol>  Set channel volume
  pgx.audio.mute <ch|all>   Toggle mute
  pgx.audio.play <tag>      Test-play resolved sound
  pgx.audio.stop [all]      Stop sounds
  pgx.audio.switch <type>   Switch backend
  pgx.audio.debug [on|off]  Toggle debug overlay
  pgx.audio.mix             Show 5-layer mix state
  pgx.audio.ducking         Show active ducking rules
  pgx.audio.dialogue        Show dialogue queue
  pgx.audio.hdr [on|off]    Toggle HDR audio
  pgx.audio.hrtf [on|off]   Toggle HRTF
  pgx.audio.device [name]   Query/set audio device

Integration Points

With the Save System

User audio preferences are automatically persisted: per-channel volumes, mute states, global mute, backend preference, HRTF setting, HDR setting, and selected audio device. On save, the system serializes these values. On load, it restores them. No integration code required.

With the Profile System

Platform capabilities constrain audio behavior:

  • Budgets: Maximum concurrent sounds per channel, total audio memory. The system enforces the minimum of the configured value and the platform budget.
  • Features: Occlusion, HDR audio, HRTF (VR platforms). Features not supported by the current platform are automatically disabled.
  • Policies: Concurrency policy, backend preference. Platforms can override defaults.

When the active platform profile changes at runtime, the audio system receives a notification and re-evaluates all constraints.

With the Loading System

When a loading screen activates, the LoadingScreen mix layer automatically engages, attenuating or muting gameplay audio. When loading completes, the layer disengages with a configurable release time. No manual wiring needed.

With the Game Flow System

Game flow state changes drive music transitions. When game state moves from "Exploration" to "Combat," the music manager can automatically switch playlists. This is opt-in: configure the activation tag on a music playlist, and it activates when that game flow tag becomes active.

With the Level Flow System

When a level transition completes, the per-level subsystem reads the new level's audio configuration and rebuilds the mix state. Ambient zones, reverb settings, and level-specific ducking rules are all loaded from the Level Audio Config DA.


State Machines

Three state machines govern audio lifecycle:

  Audio System:
  Uninitialized --> Initializing --> Ready --> Error
                                       |
                                       v
                                    Suspended

  Music Manager:
  Idle --> Playing --> CrossFading --> Playing (new track)
              |                          |
              v                          v
           Paused                    Stopping --> Idle

  Dialogue Manager:
  Idle --> Playing --> Interrupted --> (next in queue?) --> Playing
              |                                              |
              v                                              v
           (finished) -----> Idle <--------------------------+

Tag Hierarchy

  PGX.Audio
      |
      +-- Channel       Master, Music, SFX, Voice, Ambient, UI, Dialogue
      +-- Profile       Default, UI, SFX3D, SFXAttached, Ambient3D, Voice3D, Music
      +-- Sound         {user-extensible}
      +-- Music
      |     +-- State     {user-extensible: Exploration, Combat, Menu, ...}
      |     +-- Playlist  {user-extensible}
      +-- Ducking
      |     +-- Rule      {user-extensible}
      |     +-- Context   Combat, Cinematic, Stealth, ...
      +-- Mix
      |     +-- Layer     DefaultBase, Level, Ducking, LoadingScreen, User
      |     +-- Zone      {user-extensible}
      +-- Dialogue
      |     +-- Speaker   {user-extensible}
      |     +-- Priority  Low, Normal, High, Critical
      +-- Backend        Legacy, Modulation
      +-- Event          Play, Stop, VolumeChanged, MuteChanged, ...

Event Notifications (12 Delegates)

The system broadcasts notifications for every significant audio event:

Event When It Fires
System Ready Audio system initialization complete
Sound Played A sound begins playing (includes handle and profile tag)
Sound Stopped A sound stops (includes reason: explicit, fade, concurrency, budget, pool reclaim, shutdown)
Channel Volume Changed Any channel's volume changes (includes old and new values)
Mute Changed Any channel's mute state toggles
Music State Changed Music manager transitions between states
Backend Switched Audio backend changes (Legacy to Modulation or vice versa)
Pool Exhausted Sound pool runs out of available instances
Mix Layer Changed A mix layer activates or deactivates
Ducking Changed A ducking rule engages or disengages
Dialogue Subtitle A dialogue line starts (includes text, duration, speaker tag)
Mix Subsystem Ready Per-level subsystem initialization complete

Every delegate is available in both C++ (native multicast) and Blueprint (dynamic multicast).


Why This Matters

Audio is the most under-engineered system in most game projects. It receives the least engineering attention, the most last-minute changes, and has the fewest debugging tools. The result is audio bugs that persist through ship -- because nobody can see what the audio system is doing.

PGX Audio changes the equation:

  • Backend-agnostic: Choose Legacy or Modulation without committing to an irreversible architecture decision. Switch at runtime if needed.
  • Observable: 12 inspector sections, 20 console commands, 12 delegates. Every aspect of audio state is visible and queryable.
  • Data-driven: 7 Data Asset types mean that audio designers can author behavior without touching code.
  • Layered mixing: The 5-layer model eliminates "who changed my volume?" bugs by making priority explicit.
  • Memory-aware: Pool statistics and memory estimation prevent platform certification surprises.
  • Integration-first: Automatic hooks into Save, Profile, Loading, GameFlow, and LevelFlow mean that common audio behaviors (persist preferences, duck during loading, music transitions) work out of the box.

The goal is not to replace Wwise or FMOD. It is to provide the system layer that those tools do not: state management, mix orchestration, cross-system integration, and observability. Whether the backend is engine-native or middleware, the management layer above it should not need to be rebuilt per-project.

Clone this wiki locally