Pepper.SFX
All Guidance pages

Guidance / Manual

System Architecture

2.1 Design Philosophy

Pepper.SFX follows three core principles:

Principle 1: Data-Driven Design

Traditional Approach:

// Hard-coded, tightly coupled
AudioSource.PlayClipAtPoint(explosionClip, transform.position);

Pepper.SFX Approach:

// Data-driven, decoupled
explosionEvent.Post(gameObject, transform.position);

Why It Matters:

  • Audio designers work independently in Unity Editor
  • No code changes to modify sounds
  • ScriptableObjects enable version control and team collaboration
  • Iteration is instant (no recompile)

Principle 2: Event-Based Triggering

What: Code triggers events, not direct audio playback.

Benefits:

  • Decoupling: Game logic doesn’t know about audio implementation
  • Flexibility: Same event can trigger different sounds based on game state
  • Organization: Events are named, searchable assets
  • Power: Events can execute multiple actions (play, stop, set RTPC, etc.)

Example:

// One event, multiple outcomes based on state
footstepEvent.Post();
// → Plays grass sound if on grass
// → Plays metal sound if on metal
// → Controlled by switch system

Principle 3: Hierarchical Organization

Buses: Tree structure for mixing States: Grouped for mutual exclusivity Containers: Composable playback behaviors

Example Hierarchy:

Master Bus (-3dB)
├── SFX Bus (0dB)
│   ├── Player (0dB)
│   ├── Enemies (-3dB)
│   └── Ambience (-6dB)
├── Music Bus (-6dB)
└── Dialogue Bus (0dB)

2.2 The Audio Pipeline

Understanding the complete flow from code to speaker:

1. Game Code
   ↓ myEvent.Post(gameObject, position)

2. AudioEvent (ScriptableObject)
   ↓ Execute Actions (Play, SetRTPC, SetState, etc.)

3. Container Selection
   ↓ Via Switch/State/Direct reference

4. AudioContainer (ScriptableObject)
   ↓ container.Play(position, parent)

5. AudioManager
   ↓ GetVoice() from pool

6. AudioVoiceEnhanced
   ↓ Configure AudioSource, apply GainStack

7. AudioBus
   ↓ Route through bus hierarchy
   ↓ Apply volume, ducking, effects

8. Unity AudioSource
   ↓ Play AudioClip

9. AudioMixer (optional)
   ↓ Effects, routing

10. Speaker
    ↓ Final audio output

Key Insight: No direct AudioSource manipulation. The system manages the entire pipeline automatically.

2.3 Component Hierarchy

ScriptableObject Assets (Design-Time):

  • AudioEvent
  • AudioContainer (5 types)
  • AudioBus
  • AudioState

Runtime Components:

  • AudioManager (MonoBehaviour singleton)
  • AudioVoiceEnhanced (pooled instances)
  • AudioHandle / AudioMultiHandle (playback control)

Static Utilities:

  • AudioExtensions (helper methods)
  • ListenerUtil (the listener’s position: the registered listening point, or a scene AudioListener)

2.4 Memory & Lifecycle

Startup (Scene Load):

  1. AudioManager.Awake()
    • Singleton registration (a second manager removes itself: its whole GameObject with Persist Across Scenes on, only its component with it off)
    • DontDestroyOnLoad, with Persist Across Scenes on (the default; the manager must then be a root object)
    • Voice pool creation: Max Real + Max Virtual + Steal Headroom AudioSources (32 + 64 + 4 by default), under “AudioVoices”, a root object of Pepper’s own
    • Bus initialization
    • Load events and states from the Audio Event Registry, else from Resources/Audio/Events and Resources/Audio/States
    • Start coroutines (voice management, occlusion), on “AudioVoices”

The manager’s own GameObject switched off (or a parent of it): nothing changes. The voices and every coroutine (fades, delays, ducks, the voice pass) live on “AudioVoices”, so they carry on; Unity would otherwise end the coroutines and stop the voices for good. To silence the game, use PauseAll, pause buses (§6.7) or StopAllSounds.

A dedicated server: set AudioManager.Inert = true at boot, before any AudioManager starts. Then every post returns one shared, already-finished handle without allocating or logging, with an AudioManager or without one, and an AudioManager that starts builds no voices, coroutines or per-frame step. See §17.11.

Runtime (Playing):

  1. Event.Post() → Allocate voice from pool
  2. Configure voice (clip, bus, position, gain stack)
  3. Play AudioSource
  4. Monitor (voice management coroutine)
  5. Return to pool when finished

Cleanup:

  • Voices return to pool automatically
  • No runtime allocation/deallocation
  • Predictable garbage collection

From the Pepper.SFX 3.1 guides (USER_GUIDE.md). Something wrong or unclear? Tell me.