Guidance / API reference
Loading
Namespaces: AudioSystem.Loading (always available) · AudioSystem.Loading.Addressables (gated by com.unity.addressables)
The clip-loading layer: import policy, warming, and the optional Addressables backend. See the User Guide → Audio Loading & Memory for the concepts and setup.
Always available (SFXSystem.Runtime)
// The seam every container resolves clips through. Default = DirectClipSource.
interface IAudioClipSource {
bool TryGetClip(in AudioClipRef clipRef, out AudioClip clip); // hot path; false = cold → drop
bool SupportsAddressables { get; } // false on Direct — tells a real misconfig from a cold-drop
void Pin(string address); // hold an addressable clip resident (persistent holders); no-op on Direct
void Unpin(string address);
}
// Optional companion an addressable source also implements (Direct does not).
interface IVoiceLifecycleListener {
void OnVoiceBound(AudioVoice voice); // source.clip just assigned → voice refcount++
void OnVoiceReleased(AudioVoice voice); // voice returning to pool → voice refcount--
}
readonly struct AudioClipRef {
AudioClip DirectClip; string Address; // an entry is EITHER a direct clip OR an address
// Address = the Addressables address (what a bank label resolves to),
// not the asset GUID unless the address IS the GUID
bool HasAddress; bool IsEmpty;
AudioClipRef(AudioClip clip);
AudioClipRef(AudioClip clip, string address);
}
static class AudioClipSources {
static IAudioClipSource Active { get; set; } // default DirectClipSource; assigning null reverts to it
static IVoiceLifecycleListener VoiceListener { get; } // cached; the active source's listener, if any
// Resolve + diagnose: warns once-per-address when an address can't resolve for lack of a backend
// (vs an expected cold-drop). Containers / AmbientSource use this instead of Active.TryGetClip.
static bool TryResolve(in AudioClipRef clipRef, Object context, out AudioClip clip);
}
// Pre-load clip audio data ahead of play (coroutines). No Addressables dependency.
static class AudioClipWarmer {
static bool IsWarm(AudioClip);
static IEnumerator Warm(AudioClip);
static IEnumerator WarmAll(IEnumerable<AudioClip>); // parallel
static bool Unload(AudioClip); // blunt — no live-voice check (Addressables handles that)
}
// AudioManager convenience hosts (it's the persistent coroutine runner). Return the coroutine.
Coroutine AudioManager.WarmClip(AudioClip);
Coroutine AudioManager.WarmClips(IEnumerable<AudioClip>);
Coroutine AudioManager.WarmContainer(AudioContainer); // uses EnumerateClips()
// Every clip a container could play (cycle-safe; recurses Switch/Blend children):
IEnumerable<AudioClip> AudioContainer.EnumerateClips();
// Optional addressable key per entry (empty for direct entries):
class WeightedAudioClip { AudioClip clip; float weight; float volumeMultiplier; string address; } // Random
class SequenceEntry { AudioClip clip; …; string address; } // Sequence
class RoutingEntry { AudioClip clip; string address; } // Routing
// AmbientSource: addressable loop clip — pins the clip on the active source for its active life
// (and moves the pin if the active source changes, e.g. the Addressables backend starting later).
string AmbientSource.LoopingClipAddress; // serialized field; Inspector: "Looping Clip Address"
AudioClip AmbientSource.PlayableClip; // resolved clip; null while an addressable clip is loading
// Raised on genuine listener zone-membership change (the addressable bank binder subscribes).
// Always paired one-to-one: an Exit is also raised when ResyncListenerAfterTeleport leaves a zone,
// when a zone the listener is in is disabled/destroyed/unloaded, and when the manager itself is destroyed.
static event Action<AudioZone> PropagationManager.ListenerEnteredZone;
static event Action<AudioZone> PropagationManager.ListenerExitedZone;
Gated by com.unity.addressables
Namespace AudioSystem.Loading.Addressables; assembly SFXSystem.Addressables (compiles only when the package is present).
// Refcounted addressable clip source. Installed as AudioClipSources.Active when AddressableAudioRuntime starts the backend.
sealed class AddressableClipSource : IAudioClipSource, IVoiceLifecycleListener {
float LingerSeconds; // keep-warm after refcount 0 (default 5)
void BankAcquire(string address); // called by AudioBank
void BankRelease(string address);
void Tick(float unscaledNow); // linger sweep (driven by AddressableAudioRuntime)
void ReleaseAll(); // teardown hard-reset (truth is zero)
void GetDebugEntries(List<DebugEntry>); // overlay readout
}
// A bank = an Addressables label. Preload loads/refcounts every clip under it; Release drops them.
sealed class AudioBank {
AudioBank(string label, AddressableClipSource source);
string Label; bool IsLoaded;
void Preload(); // idempotent (refcounted per bank; survives re-entered zones)
void Release();
}
// MonoBehaviours (Add Component ▸ Audio ▸ Loading):
ZoneAudioBank // on an AudioZone GameObject; holds the bank label (BankLabel)
AddressableAudioRuntime // starts the backend: installs the source, binds zone events, ticks linger.
// The backend persists across scene loads (own hidden DontDestroyOnLoad object);
// one runtime is enough — later ones (other scenes) log and do nothing.
Refcount model: resident(address) = (banks holding it) + (live voices bound to it) + (pins) > 0. Bank Release, OnVoiceReleased, and Unpin each decrement; the real unload fires at 0, after LingerSeconds. A clip a bank or pin loads also has its audio data loaded, so its first play doesn’t hitch. Teardown (ReleaseAll) force-releases everything regardless of count; it runs only when the backend is destroyed (application quit / leaving Play Mode), not on scene loads.
Cold-post policy (v1): a not-yet-resident addressable clip makes TryGetClip return false → the play is dropped (like a null clip, but without the missing-clip warning). A Sequence container stays on a cold entry, so its next play tries that entry again; an Auto Advance run ends there. Bank preload is the warm path; there is no defer-and-play-late in v1.
Editor tooling: Window ▸ Audio System ▸ Addressable Audio Authoring (Convert/Revert containers; gated assembly SFXSystem.Addressables.Editor) and Window ▸ Audio System ▸ Loading Validator (always-on; runs via reflection even without the package).
From the Pepper.SFX 3.1 guides (API_REFERENCE.md). Something wrong or unclear? Tell me.