Skip to content

Architecture

Guribo edited this page Jul 11, 2026 · 7 revisions

Architecture

Overview

UdonVoiceUtils has two independent audio systems running side by side:

  1. PlayerAudioController — manages player voice audio (VRCPlayerApi-based). Runs independently.
  2. 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 ───────┘

PlayerAudioController (MVC)

Uses an MVC (Model-View-Controller) pattern from TLP.UdonUtils.Runtime.DesignPatterns.MVC.

What Is 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

How It Maps to UVU

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.

Why MVC?

  • 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.

Microphone System

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.

Core Components

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

PlayerAudioConfigurationModel

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

WorldAudio Subsystem

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

Supporting Components

Component Purpose
EmitterRegistry Tracks active emitters
PartitionState Partition configuration
PartitionCursor Round-robin iteration
ReverbController Reverb/lowpass per emitter
AudioSourceStateController Play/pause/cull state machine

Subsystems

Occlusion

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

Voice Directionality

Head rotation affects voice range — facing toward/away from a speaker changes volume.

Privacy Channels

Groups of players who can hear each other but not others. Used for private rooms or competitive games.

  • Channel IDs stored per-emitter
  • ChannelNoPrivacy = -1 means audible to all

Data Flow

Player Voice (PlayerAudioController)

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

World Audio (WorldAudioController)

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

Prefab Structure

TLP_PlayerAudioController (prefab)
├── PlayerAudioController (script)
├── Configurations/
│   ├── LocalConfiguration (active settings)
│   └── DefaultConfiguration (reset defaults)
└── ...

TLP_Essentials (prefab, from tlp.udonutils dependency)
├── TLP_Logger
└── ...

Example Prefabs

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)

Clone this wiki locally