A busy scene can ask for more sounds than the hardware can play: a dozen cars, rain, gunfire and the UI at once. Pepper.SFX plays everything from a fixed pool of voices, ranks each sound by how much it matters, and keeps the important ones audible without clicks or engines losing a layer.
How it works
- A fixed pool. The Audio Manager builds its AudioSources once, at start: Max Real Voices + Max Virtual Voices + Steal Headroom (32 + 64 + 4 by default).
- Importance is how loud a sound is where it is heard, times its event’s Priority weight: Low 1/8, Medium 1/4, High 1/2, Critical 1. A loud Low sound beside you can outrank a faint Medium one far away.
- Virtual voices. Over Max Real Voices, the least important sounds go virtual: silent, using no real voice, but still counting their place in the clip at their pitch. When there is room they come back exactly where they would have been, faded in over about 25 ms. A whole sound goes virtual at once, so an engine’s layers stay together.
- Stealing ends whole sounds. When the pool is full, a new sound makes room by ending whole sounds, silent virtual ones first, then the least important: every layer of a Blend together, never one layer. If there still isn’t room, the new sound is skipped with a warning.
- Persistent events are never stolen. Tick Persistent on an engine’s event. It can still go virtual. Critical is the strongest weight, not an exemption.
- Steals fade. A stolen sound fades out over Steal Fade Seconds (0.025 s, at least three frames) instead of clicking.
- Per-bus caps. A bus’s Max Real Voices limits the real voices its sounds use, so other players’ cars can’t crowd out yours. Over the cap, sounds go virtual; it never ends one.
- Dedicated servers. With
AudioManager.Inert = trueset at boot, every post is silent and free: no voices, no allocation, no logging.
Example
using AudioSystem;
using UnityEngine;
public class VoiceBudgetReadout : MonoBehaviour
{
[SerializeField] private AudioBus otherCarsBus; // Max Real Voices set on the bus asset
private void OnGUI()
{
if (AudioManager.Instance == null) return;
var stats = AudioManager.Instance.GetStatistics();
GUILayout.Label($"Real voices: {stats.activeVoices} / {stats.realVoiceBudget}");
GUILayout.Label($"Free in the pool: {stats.availableVoices} of {stats.poolSize}");
GUILayout.Label($"Other cars: {otherCarsBus.RealVoiceCount} / {otherCarsBus.MaxRealVoices}");
}
#if UNITY_SERVER
// A dedicated server build plays nothing and builds no voices.
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void SilenceServer() => AudioManager.Inert = true;
#endif
}
The readout shows real voices against the budget, free voices in the pool and a capped bus’s live count. In a dedicated server build, Pepper goes inert before the first scene loads.
Setting it up
- Select the Audio Manager and set Max Real Voices and Max Virtual Voices. The guide suggests 32–64 real voices on desktop, 16–24 on mobile.
- On each Audio Event, set Priority, and tick Persistent on events that must never drop out.
- On a bus that groups many similar sounds, set Max Real Voices in its Voices section (0 = no cap).
- In Play mode, open Window > Audio System > Voice Pool Debug for the real and virtual voices, or Window > Audio System > Live View to see why a voice is virtual.
Good to know
- If a very large number of sounds start in the same frame, some can be skipped when the pool can’t free voices fast enough. Raise Steal Headroom if it happens often.
- If a full pool holds only persistent sounds, a new sound is skipped. Raise Max Virtual Voices: 8 cars of 14 layers need 112 voices.
- A virtual voice can’t follow Unity’s own Doppler. On engine layers that must come back in step, set Doppler to Pepper or Doppler Level to 0.
- Pepper warns at startup if its voice counts exceed Unity’s (Project Settings > Audio).
- Pepper doesn’t detect a server by itself, and
Inertcan’t change while an Audio Manager runs: your boot code decides.