-
Notifications
You must be signed in to change notification settings - Fork 8
Architecture
UdonVoiceUtils has two independent audio systems running side by side:
- PlayerAudioController — manages player voice audio (VRCPlayerApi-based). Runs independently.
- WorldAudioController — manages AudioSource emitters in the scene (importance-based). Reads config from PlayerAudioController.
┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐
│ PlayerAudioController │ │ WorldAudioController │
│ (Player Voice) │ │ (AudioSource Emitters) │
│ │ │ │
│ MVC Pattern: │ │ Importance-Based: │
│ View ←→ Controller ←→ Model │ │ Registry → Tree → Partitions │
│ │ │ │
│ • VRCPlayerApi-based │ │ • AudioSource-based │
│ • Time-sliced player updates │ │ • Time-sliced emitter updates │
│ • Overrides, occlusion, channels │ │ • Culling, reverb, channels │
│ • Runs independently │ │ • Reads config from PAC │
└─────────────────────────────────────┘ └─────────────────────────────────────┘
▲ │
│ │
└──── reads config + override ───────┘
Uses an MVC (Model-View-Controller) pattern from TLP.UdonUtils.Runtime.DesignPatterns.MVC.
MVC separates concerns into three roles:
| Role | Responsibility | UVU Component |
|---|---|---|
| Model | Holds data, notifies listeners on change | PlayerAudioConfigurationModel |
| View | Displays data, handles user input | PlayerAudioView |
| Controller | Mediates between Model and View, applies business logic | PlayerAudioController |
PlayerAudioView (UI) ←→ PlayerAudioController (Logic) ←→ PlayerAudioConfigurationModel (Data)
▲ ▲
│ │
Sliders/Toggles Processes overrides
Displays current Applies settings
Sends user input Manages occlusion/channels
-
Model (
PlayerAudioConfigurationModel) — holds all audio settings (voice range, gain, occlusion, etc.). Notifies the Controller when settings change. Can be synchronized across players. -
View (
PlayerAudioView) — UI elements (sliders, toggles) that display current settings and accept user input. Sends changes to the Controller. -
Controller (
PlayerAudioController) — processes player updates, applies overrides based on priority, handles occlusion and channels. Reads from Model, updates View.
- Separation of concerns — config data (Model) is independent from UI (View). The Controller can run without the View (headless worlds, no menu).
- Testability — Model and Controller can be tested independently.
- Reusability — the same Model can feed multiple Views (settings menu, HUD, etc.).
- Sync-friendly — Model can be synchronized across players while View stays local.
The microphone subsystem uses the same MVC pattern:
| Role | Component |
|---|---|
| Model |
MicModel — holds UserId and IsOn state |
| Controller |
MicController — activation logic (pickup/drop handled by PickupMicrophone) |
| View |
MicView — mic UI display |
See PickupMicrophone for microphone architecture details.
The central component. Manages all player audio overrides.
- Processes player updates (time-sliced, configurable rate)
- Applies overrides based on priority
- Handles occlusion, directionality, reverb, and channels
- File:
Packages/tlp.udonvoiceutils/Runtime/Core/PlayerAudio/PlayerAudioController.cs
Per-zone or per-item audio settings. Defines how audio should behave when a player is in a specific area or holding a specific item.
- Voice range, gain, occlusion, reverb, privacy channels
- Priority-based — higher priority overrides lower
- File:
Packages/tlp.udonvoiceutils/Runtime/Core/PlayerAudio/PlayerAudioOverride.cs
Sorted list of active overrides per player. The controller reads this list each frame to determine which override applies.
- Overrides are sorted by priority
- Add/remove overrides dynamically (e.g., when entering/leaving zones)
- File:
Packages/tlp.udonvoiceutils/Runtime/Core/PlayerAudio/PlayerAudioOverrideList.cs
Data model holding all audio settings (voice range, gain, etc.). Shared between controller and view.
- File:
Packages/tlp.udonvoiceutils/Runtime/Core/PlayerAudio/PlayerAudioConfigurationModel.cs
UI view for displaying and modifying audio settings. Part of the MVC triad.
- File:
Packages/tlp.udonvoiceutils/Runtime/Core/PlayerAudio/PlayerAudioView.cs
Importance-based management of AudioSource emitters in the scene. See WorldAudio-Subsystem for full details.
Core controller driving the emitter update loop. Recalculates importance and updates a subset of emitters per frame.
- File:
Packages/tlp.udonvoiceutils/Runtime/Core/WorldAudio/WorldAudioController.cs
Per-emitter lifecycle controller. Manages importance, reverb, culling, and privacy gating.
- File:
Packages/tlp.udonvoiceutils/Runtime/Core/WorldAudio/OccludedEmitter.cs
| Component | Purpose |
|---|---|
| EmitterRegistry | Tracks active emitters |
| PartitionState | Partition configuration |
| PartitionCursor | Round-robin iteration |
| ReverbController | Reverb/lowpass per emitter |
| AudioSourceStateController | Play/pause/cull state machine |
Muffles audio behind walls/obstacles.
-
PlayerOcclusionStrategy— abstract base class for occlusion behavior -
DefaultPlayerOcclusion— raycast-based, uses AudioObstacle clarity -
NullPlayerOcclusion— no occlusion (passthrough) - See Occlusion for details
Head rotation affects voice range — facing toward/away from a speaker changes volume.
Groups of players who can hear each other but not others. Used for private rooms or competitive games.
- Channel IDs stored per-emitter
-
ChannelNoPrivacy = -1means audible to all
1. Player enters zone → AddOverride() called on PlayerAudioOverrideList
2. Each frame → PlayerAudioController processes time-sliced player updates
3. For each player → find highest-priority override from list
4. Apply override settings (voice range, gain, occlusion, etc.)
5. Handle occlusion via PlayerOcclusionStrategy
6. Player leaves zone → RemoveOverride() called
1. Emitter starts → OccludedEmitter registers with EmitterRegistry
2. Each frame → WorldAudioController rebuilds registry if dirty
3. Recalculate importance for subset of emitters ( AVL tree sort )
4. Update subset of emitters (partition-based time-slicing):
a. Privacy channel check
b. Occlusion clarity (raycast)
c. Culling (if beyond MaxActiveSources)
d. Apply reverb and lowpass filters
5. Emitter destroyed → unregisters from EmitterRegistry
TLP_PlayerAudioController (prefab)
├── PlayerAudioController (script)
├── Configurations/
│ ├── LocalConfiguration (active settings)
│ └── DefaultConfiguration (reset defaults)
└── ...
TLP_Essentials (prefab, from tlp.udonutils dependency)
├── TLP_Logger
└── ...
| Prefab | Purpose | See Also |
|---|---|---|
| VoiceOverrideTriggerZone | Zone-based override (trigger enter/exit) | Tutorial #1 |
| VoiceOverrideRoom | Room with door/button enter/exit | Private Voice Channels |
| VoiceOverrideDoor | Door-based room enter/exit | VoiceOverrideRoom |
| VoiceOverrideRoomEnterButton | Button-based room enter | VoiceOverrideRoom |
| VoiceOverrideRoomExitButton | Button-based room exit | VoiceOverrideRoom |
| PickupMicrophone | Microphone pickup item (MVC: MicModel/MicController/MicView) |
- 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