-
Notifications
You must be signed in to change notification settings - Fork 0
Audio System
Lyra-validated. Production-hardened. Zero audio-programmer required.
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:
-
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.
-
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.
-
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.
-
There is no dialogue queue. Priority-based dialogue with automatic ducking, subtitle callbacks, and interruption policies? Custom implementation every time.
-
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.
-
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.
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.
+-------------------------------------------+
| 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.
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.
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.
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.
The dialogue manager implements a priority queue with automatic ducking and subtitle dispatch.
When a dialogue line is queued:
- If nothing is playing, it plays immediately.
- If a lower-priority line is playing, the new line interrupts it.
- If an equal-or-higher-priority line is playing, the new line waits in the queue.
- When a line starts playing, the ducking system automatically attenuates configured channels (typically music and ambient).
- 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.
The sound pool manages a pre-allocated set of sound instances to prevent per-frame allocation. When the pool is exhausted:
- The lowest-priority active sound is reclaimed.
- If all sounds have equal priority, the oldest is reclaimed.
- 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 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/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.
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.
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.
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 |
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
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.
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.
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.
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.
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.
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 <--------------------------+
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, ...
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).
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.
- Development Preview
- Getting Started
- Release branch catalog
- Public Plugin Matrix
- Early Preview Plugins
- Known Issues
- Architecture Overview
- Plugin Topology
- Module Reference
- Configuration and Registry
- Data-Driven Design
- Profiles and Budgets
- Gameplay Tag Architecture
- Initialization Pipeline
- Cross-Plugin Communication
- Message System
- Event Handlers
- Logging and Trace
- Runtime Flows
- Blueprint API Design
- Editor Integration
- Editor Visual System