Skip to content

WorldAudio Subsystem

Guribo edited this page Jul 12, 2026 · 4 revisions

WorldAudio Subsystem

Importance-based audio emitter management for AudioSource components in the scene.

What Is It

The WorldAudio subsystem manages AudioSources that play in the world (music, ambient sound, sound effects) — as opposed to player voice audio, which is handled by PlayerAudioController.

It provides:

  • Importance-based prioritization — closer/louder emitters update more frequently
  • Partition-based time-slicing — distributes CPU budget across frames
  • Automatic culling — beyond a configurable limit, low-importance emitters are suspended
  • Privacy channel gating — emitters respect the same privacy channels as player voice
  • Reverb and lowpass — applies distance-based audio effects per emitter

Why It Exists

In VRChat, the measured limit is 32 active AudioSources with a single player in the world (probably ~64 virtual voices). When this limit is exceeded, Unity stops playing new AudioSources — they enter a "neither playing nor virtual" state where they simply don't produce sound, even when the player walks right up to them. There's no automatic recovery; once an AudioSource is stuck in this state, it stays silent.

The WorldAudio subsystem solves this by:

  • Active culling — instead of letting Unity randomly fail AudioSources, the system explicitly suspends low-importance emitters (distant/quiet) to keep the active count within budget. When a culled emitter becomes important (player approaches), it is reactivated and resumes playback seamlessly
  • Dynamic reverb and occlusion — applies per-emitter reverb and lowpass filters based on distance and environment, which Unity does not provide out of the box
  • Privacy channels — restricts which AudioSources a player can hear based on room/channel assignment, something Unity's audio system has no concept of

Without this system, world creators would need to manually manage AudioSource activation, implement their own distance-based culling, and build custom reverb/occlusion logic.

Two-Subsystem Architecture

UdonVoiceUtils has two independent audio systems running side by side:

┌─────────────────────────────────┐  ┌─────────────────────────────────┐
│   PlayerAudioController         │  │   WorldAudioController          │
│   (Player Voice)                │  │   (AudioSource Emitters)        │
│                                 │  │                                 │
│   • VRCPlayerApi-based          │  │   • AudioSource-based           │
│   • Processes remote players    │  │   • Processes scene emitters    │
│   • Voice range, gain, etc.     │  │   • Importance sorting          │
│   • Runs independently          │  │   • Reads config from PAC       │
│   • Occlusion, directionality   │  │   • Partition-based updates     │
│   • Privacy channels            │  │   • Privacy channel gating      │
└─────────────────────────────────┘  └─────────────────────────────────┘
              ▲                                    │
              │                                    │
              └──── reads config + override ───────┘
  • PlayerAudioController handles player voice audio via VRCPlayerApi. It runs independently — you can use it without WorldAudio.
  • WorldAudioController manages AudioSource emitters in the scene. It reads configuration from PlayerAudioController (occlusion settings, local override) but PlayerAudioController does not depend on it.

How It Works

Emitter Registration

Each OccludedEmitter component in the scene registers itself with the EmitterRegistry on startup. When an emitter is destroyed, it unregisters automatically.

Importance Scoring

Each frame, the system recalculates importance for a subset of emitters. Importance is based on:

  • Distance from the local player's head
  • Volume of the AudioSource
  • Priority setting of the AudioSource
  • Rolloff mode (logarithmic, linear, or custom)

Closer, louder emitters get lower importance scores (sorted ascending in the AVL tree). The most important emitters ( lowest scores) are at the front of the tree.

Partition-Based Time-Slicing

Emitters are divided into partitions (buckets) with exponential sizes:

Partition 0: [1 emitter]     ← highest priority, updates every frame
Partition 1: [4 emitters]    ← updates frequently
Partition 2: [16 emitters]   ← updates less often

Each frame, the cursor cycles through partitions and returns the next emitter needing an update. This distributes the update budget so not all emitters are processed simultaneously.

Configure via PartitionState:

  • Partitions (1–10) — number of buckets
  • PartitionGrowthFactor (2–6) — size multiplier between partitions

Per-Emitter Update

For each emitter selected by the partition cursor:

  1. Privacy check — is this emitter audible to the local player?
  2. Occlusion — raycast from listener to emitter, calculate clarity
  3. Culling — if emitter index ≥ MaxActiveSources, suspend it
  4. Apply effects — update reverb, lowpass filter, spatial blend

GameObject Activation

When DisableGameObjectWhenCulled is enabled (default), culled emitters have their GameObject deactivated. This saves CPU on AudioSources that Unity videoplayers control. Reactivated when the emitter becomes active again.

Configuration

WorldAudioController Settings

Field Default Description
ImportanceUpdatesPerFrame 1 How many emitters to recalculate importance for each frame. Higher = more responsive, more CPU
UpdatesPerFrame 3 How many emitters to update (apply effects) each frame. Higher = more responsive, more CPU
MaxActiveSources 32 Maximum concurrent active emitters. Beyond this, lowest-importance emitters are culled

OccludedEmitter Settings

Field Default Description
DisableGameObjectWhenCulled true Deactivate GameObject when culled. Set to false for Unity videoplayer sources — disabling breaks audio in combination with private channels
PrivacyChannelId -1 Privacy channel for this emitter. -1 = no privacy (always audible)

Known Limitations

AudioSources managed by the WorldAudio system cannot be directly controlled by world creators. All play/pause/stop control must go through AudioSourceStateController. Do not call AudioSource.Play()/Stop() directly.

Direct manipulation of managed AudioSources (Play/Pause/Stop/clip changes) conflicts with:

  • Culling and reactivation (position tracking breaks)
  • Importance sorting (volume/priority changes affect scoring)
  • Partition-based updates (state becomes inconsistent)

If you need to control an AudioSource directly, do not attach OccludedEmitter to it.

Only works with Unity-based video players. The WorldAudio system does not work with AVPro video players. AVPro manages its own audio pipeline and does not use Unity's AudioSource component in a way that is compatible with the emitter lifecycle management. Use Unity's built-in video player if you need WorldAudio to manage the audio.

Component Overview

Component Purpose
WorldAudioController Core controller — drives the update loop
OccludedEmitter Per-emitter lifecycle management
EmitterRegistry Tracks active emitters
PartitionState Partition configuration
PartitionCursor Round-robin iteration
ReverbController Reverb/lowpass per emitter
AudioSourceStateController Play/pause/cull state machine

See Also

Clone this wiki locally