-
Notifications
You must be signed in to change notification settings - Fork 8
Audio Processing Pipeline
What happens each frame to process audio. Two independent systems run in parallel:
- PlayerAudioController — processes player voice audio
- WorldAudioController — manages AudioSource emitters
PostLateUpdate (every frame)
└─ For each remote player (time-sliced):
├─ 1. Get head positions
├─ 2. Find highest-priority override
├─ 3. Channel check (privacy)
├─ 4. Apply audio settings
│ ├─ Occlusion (raycast)
│ ├─ Directionality (dot products)
│ ├─ Voice range reduction
│ ├─ Avatar audio range reduction
│ └─ Reverb
└─ 5. Update AudioSources
- Listener: local player's camera position/rotation
- Emitter: remote player's head tracking data
- Check the player's
PlayerAudioOverrideList -
GetMaxPriority()returns the override with highest priority - If no override → use global configuration
- If emitter has a private channel and listener doesn't share it → mute
- If listener has
DisallowListeningToChanneland emitter is outsider → mute - See Private Voice Channels for details
- Raycast from listener head toward emitter head
- 0 hits → full clarity (1.0)
- 1 hit → check distance, apply obstacle/player clarity
- 2 hits → environment dominates, unless both hits are player avatars → player clarity applies
- See Occlusion for details
- Two dot products: listener's forward vs direction-to-speaker, speaker's backward vs direction-to-listener
- Listener facing away → reduced range
- Speaker facing away → reduced range
- Both multiplied for final multiplier
- See Voice Directionality for details
relativeAudioRange = occlusionClarity × directionalityMultiplier
voiceDistanceFar × heightMultiplier × relativeAudioRange
voiceDistanceNear × heightMultiplier × relativeAudioRange
Applied via VRCPlayerApi.SetVoiceDistanceFar() and SetVoiceDistanceNear().
Same calculation for avatar audio sources (footsteps, animations, etc.).
-
SetVoiceGain(),SetVoiceVolumetricRadius() - Avatar audio:
SetAvatarAudioGain(),SetAvatarAudioFarRadius(), etc. - Reverb filter applied if configured
Not all players are updated every frame. PlayerUpdateRate controls how many:
| Rate | 80 players, 20fps | Full update time |
|---|---|---|
| 0 | All every frame | 0.05s |
| 1 | 1 per frame | 4s |
| 4 | 4 per frame | 1s |
| 80 | All per frame | 0.05s |
Lower rate = better performance, less responsive. Higher rate = more responsive, more CPU.
Manages AudioSource emitters (music, ambient, sound effects) via importance-based prioritization.
See WorldAudio-Subsystem for full details.
PostLateUpdate (every frame)
├─ 1. Rebuild registry if emitters changed
├─ 2. Recalculate importance (time-sliced)
│ └─ For each emitter in batch:
│ └─ RefreshInTree → AVL tree re-sort
└─ 3. Update emitters (partition-based time-sliced)
└─ For each emitter in batch:
├─ a. Privacy channel check
├─ b. Occlusion clarity (raycast)
├─ c. Culling (if beyond MaxActiveSources)
└─ d. Apply reverb + lowpass filters
When EmitterRegistry.NeedsRebuild is true (emitter added/removed):
- Rebuild
Emitters[]array from dictionary - Reinitialize partitions and update scheduler
Each frame, recalculate importance for ImportanceUpdatesPerFrame emitters:
- Calculate importance based on distance, volume, priority, rolloff mode
- Remove from AVL tree, re-add with updated score
- Privacy-culled emitters are pushed behind the
MaxActiveSourcesboundary
Each frame, update UpdatesPerFrame emitters via partition cursor:
| Step | Description |
|---|---|
| Privacy check | Is this emitter audible to the local player? |
| Occlusion | Raycast from listener to emitter, calculate clarity |
| Culling | If emitter index ≥ MaxActiveSources, suspend it |
| Apply effects | Update reverb, lowpass filter, spatial blend |
Emitters are divided into partitions with exponential bucket sizes:
Partition 0: [1 emitter] ← highest priority, updates every frame
Partition 1: [4 emitters] ← updates frequently
Partition 2: [16 emitters] ← updates less often
Configure via PartitionState:
-
Partitions(1–10) — number of buckets -
PartitionGrowthFactor(default: 4) — size multiplier between partitions (recommended range: 2–6)
- Architecture — component overview
- WorldAudio-Subsystem — importance-based emitter management
- PlayerAudioController — player voice controller
- WorldAudioController — emitter update controller
- Architecture
- Occlusion
- Voice Directionality
- Height Scaling
- Audio Processing Pipeline
- Private Voice Channels
- WorldAudio Subsystem
- WorldAudio Limitations
- Set Up Occlusion
- Set Up Voice Channels
- Set Up Reverb
- Set Up Height Scaling
- Set Up Voice Directionality
- Set Up Microphone
- Set Up WorldAudio Emitters
- Enable Debug Mode
- Tutorial #1: Custom Voice Override Zone
- Tutorial #2: Private Voice Channels
- Tutorial #3: Setting Up World Audio
- PlayerAudioController
- PlayerAudioOverride
- PlayerAudioOverrideList
- PlayerAudioView
- PlayerAudioConfigurationModel
- SyncedPlayerAudioConfigurationModel
- VoiceUtils
- IgnoredPlayers
- AudioObstacle
- DefaultPlayerOcclusion
- NullPlayerOcclusion
- PlayerOcclusionStrategy
- Privacy Channel Reference
- AdjustableGain
- DynamicPrivacy
- PickupMicrophone — MVC microphone system with MicModel/MicController/MicView
- VoiceOverrideTriggerZone
- VoiceOverrideRoom