Guidance / Manual
Audio Loading & Memory
This chapter covers how clip audio gets into memory and onto a voice — the RAM footprint of your audio and when the cost of loading it is paid. It’s entirely opt-in and tiered. The API surface lives in API_REFERENCE.md → Loading.
16.1 Do I even need this?
| Your situation | What to use | Extra package? |
|---|---|---|
| Small / medium game | Nothing — clips are direct references and load with the project (the default). Skip this chapter. | — |
| Lots of audio, occasional hitches on first play | The Warmer (§16.4) — front-load clip data on your schedule. | No |
| Open-world / live-service, RAM is the constraint | The Addressables backend (§16.5) — load/unload a region’s audio by zone. | com.unity.addressables |
Nothing here changes behavior for a project that doesn’t opt in. The default path — direct AudioClip references resolved synchronously — is unchanged and zero-overhead.
16.2 The three layers
Cheapest first. Each is independent; you can use one without the others.
1. Import policy — per-category Load Type / compression. Sets each clip's RAM footprint. (automatic)
2. Warming — pre-load a clip's audio DATA ahead of first play, to kill the hitch. (no package)
3. Addressables — load/unload the clip OBJECTS themselves by region, so only the current (gated package)
zone's audio is resident.
The distinction between layers 2 and 3 matters: warming loads the audio data of a clip you already hard-reference (the object is already in memory); Addressables controls whether the clip object is in memory at all. Warming reduces hitches; Addressables reduces peak RAM.
16.3 Import policy (automatic)
The editor import pipeline (Editor/AudioImport/) stamps each clip’s import settings from its category preset (CategoryPreset.GetDefault). You don’t configure it per clip — you pick the category:
| Category | Load Type | Compression | Preload Audio Data | Why |
|---|---|---|---|---|
| UI | Decompress On Load | PCM | yes | tiny, fired often — cheapest to play |
| SFX | Decompress On Load | ADPCM | yes | short, fired often/overlapping |
| Music | Streaming | Vorbis | no | one instance, long — don’t hold PCM in RAM |
| Ambience | Streaming | Vorbis | no | few instances, long |
| Dialogue | Compressed In Memory | Vorbis | no | many lines, decode on demand |
Your project’s settings. The presets live in an Audio Import Settings asset in your project (Create ▸ Audio System ▸ Import Settings, anywhere under Assets). Until you make one, the code defaults above apply. The package ships none: an installed package is read-only, so one there couldn’t be saved. With more than one, the first by path is used and the Console says which.
Folder rules. In the settings asset’s Folder Rules, each rule is a name (its own category: “Engine loops”, “Tyres”), a folder, whether it covers the folders below, and import settings. A clip imported into that folder for the first time gets the rule’s settings, so engine loops dropped into Assets/Audio/Cars/Engines come in mono and Decompress On Load without a trip to the Import Manager. The most specific folder wins. Clips already in the project keep their settings, even when they are reimported, so a hand tweak is never undone; Apply to Existing Clips on a rule re-applies it. With no rules, imports are Unity’s as before.
The one authoring rule that bites people: Streaming is per-instance expensive — a stream buffer plus disk reads for every playing voice. Right for music and long ambience (one or a few instances); wrong for a footstep or weapon pool firing many overlapping instances (many disk-streaming voices seeking at once → hitch). The presets already encode the right choice; the mistake is manually setting a short, frequently-fired SFX to Streaming.
| Load Type | RAM | Per-instance cost | Use for |
|---|---|---|---|
| Decompress On Load | high (full PCM) | cheapest | short SFX fired often — footsteps, gunshots |
| Compressed In Memory | medium | decode per play | many medium-length variants |
| Streaming | ~none (reads disk while playing) | stream buffer + disk seek per voice | music, long ambience — one/few instances only |
16.4 Warming (no extra package)
Categories with Preload Audio Data = false (Music/Ambience = Streaming, Dialogue = Compressed-In-Memory) load their audio data on first play, which hitches. The warmer front-loads that cost on your schedule — e.g. on zone-enter or level-load — operating on the clips you already hard-reference, with no Addressables dependency.
// On zone-enter / level-load, before the sound is needed:
AudioManager.Instance.WarmContainer(zoneAmbienceContainer); // warms all its clips (recurses Switch/Blend)
AudioManager.Instance.WarmClip(clip); // a single clip
AudioManager.Instance.WarmClips(clipList); // a set, loaded in parallel
AmbientSourcewarms its loop clip automatically on register, so an ambient bed that fades in is typically warm by the time it’s audible.- Biggest win for Dialogue (Compressed-In-Memory — decoding a line otherwise hitches) and the Ambience first-play. For pure Streaming clips the gain is smaller — they stream at play regardless.
- Finer control than the all-or-nothing
Preload Audio Dataimport flag: you warm exactly the clips you’re about to need, when you choose.
Addressables blind spot: the warmer operates on direct, hard-referenced clips only. An addressable-authored entry (null clip + address) is invisible to it. Its loading is owned by bank preload (§16.5), which loads both the clip and its audio data, so it doesn’t need the warmer. Don’t expect
WarmContainerto warm converted (addressable) content.
AudioClipWarmer.Unload exists but is blunt — it frees the data with no check for live voices. Only unload data you know nothing is playing. Safe, refcounted residency (that never unloads a clip under a playing voice) is what the Addressables backend adds.
16.5 Addressables backend
Requires com.unity.addressables. This loads and unloads the clip objects themselves by region, so only the current zone’s audio is resident. The assembly (SFXSystem.Addressables) compiles automatically when the package is present; without it, everything here is dormant and the project runs on direct references — exactly like the netcode adapters (§17).
Setup:
- Install
com.unity.addressables(Package Manager). TheSFXSystem.Addressablesassembly compiles automatically when present. - Mark the region’s clips Addressable and give them a shared label (e.g.
zone_crypt) — easiest with the Authoring window (§16.6), which puts them in the label’s own group,Audio zone_crypt, with one bundle per clip. By hand in the Addressables Groups window, don’t put every zone’s clips in one group with the default Bundle Mode (Pack Together): Unity unloads a bundle only as a whole, so leaving a zone would free nothing while you stand in another. Give each zone its own group, or set the group’s Bundle Mode to Pack Separately (one bundle per clip). Pack Separately also frees the rest of a zone’s clips while one of them, say a loop, plays on across the border. Measured in a test build with three zones: with Pack Separately, 7–11 MB of audio was loaded in any one zone against 26 MB for all three; with one shared group, every zone walked through stayed loaded, as much as loading all three up front. - Author the container entries as addressable. Easiest is the Addressable Audio Authoring window (§16.6): select the containers, pick the label + address style, hit Convert. By hand: set each entry’s address to the clip’s Addressable address, exactly as the Addressables Groups window shows it, and its clip to None. Use the address, not the asset GUID (unless the address is the GUID): a zone bank loads its clips by address, and an entry keyed any other way never loads and stays silent. The Loading Validator flags such keys as unregistered. Per entry it’s either a direct clip or an address — a populated clip would eager-load and defeat the point.
- Add a
Zone Audio Bankcomponent to the AudioZone’s GameObject (Add Component ▸ Audio ▸ Loading ▸ Zone Audio Bank) and set its Bank Label to the label from step 2. - Put one
Addressable Audio Runtimecomponent in your first scene (Add Component ▸ Audio ▸ Loading ▸ Addressable Audio Runtime); a bootstrap scene is ideal. It starts the backend, which installs the addressable clip source and preloads/releases each zone’s bank as the listener enters/exits. The backend persists across scene loads, so you need only one. A runtime in a later scene finds it already running, logs that, and does nothing (the first runtime’s settings apply).
Now a zone’s clips load on enter and unload (after a short linger) on exit. A clip still playing when you leave a zone stays resident until the voice finishes — no mid-play unload, including across a scene load. When a scene unloads, its zones count as exited, so their banks release normally. After teleporting the listener, call PropagationManager.ResyncListenerAfterTeleport(): the zones you left release their banks, and the ones you land in load theirs.
Voice-safe residency (the important part). A zone’s bank releasing on exit does not immediately unload a clip a voice is still playing — a looping ambience, or a one-shot tail ringing out as you cross the border. Residency is refcounted across three holders:
clip resident while (banks holding it) + (live voices bound to it) + (explicit pins) > 0
The actual unload fires only when that total hits 0, and then only after a linger window (default 5 s) to absorb zone-border dithering (step out / step back in). A stuck-high refcount is only wasted RAM, never a click — the design deliberately errs toward holding the ref.
Debug overlay. Tick Show Debug Overlay on Addressable Audio Runtime to see each address’s bank / voice / pin / total refcounts and warm/cold state live during Play. On zone-exit, bank drops to 0 but a playing voice keeps voice ≥ 1, so the clip stays warm until the voice ends plus linger. Off by default; zero cost when off.
Addressable ambiences. AmbientSource can be addressable too: set its Looping Clip Address and leave Looping Clip empty. The source pins the clip loaded for its active life (the pin refcount), so it’s ready even when the ambience is heard through a portal before you enter the zone. Its resolved clip (null while an addressable clip is still loading) is exposed as PlayableClip; propagation reads that and defers until it’s ready.
16.6 Authoring window — convert clips in one click
Window ▸ Audio System ▸ Addressable Audio Authoring does the whole “mark addressable + set address + assign label + null the direct clip” in one click, across Random / Sequence / Routing containers, and keeps the groups tidy afterwards — no Groups-window round-trip. It has two tabs.
Convert Containers
- Select one or more AudioContainer assets in the Project.
- Set options (all in-window):
- Bank Label — dropdown of existing labels, plus New label…. This is the bank a
ZoneAudioBankloads. Optional. - Group — Bank’s own group (the default) puts the clips in
Audio <label>, made on first use with one bundle per clip, so a zone’s clips unload when the listener leaves (§16.5 step 2). You can also pick an existing group or New group… (also one bundle per clip). If the group you pick packs everything into one bundle and already holds another bank’s clips, the window warns you. It also warns when the group isn’t in builds (a blank group made in the Addressables Groups window, or one with Include in Build off): its clips would play in the editor and be missing from a build. - Address Style — how the key reads: Label-scoped (default)
<label>/<clip>e.g.Zone_Crypt/Add-S_1(readable + organized); Plain nameAdd-S_1; Asset pathAssets/Audio/Add-S_1.wav; GUID (rename-proof, unreadable). Collisions auto-resolve with a numeric suffix (…_2).
- Bank Label — dropdown of existing labels, plus New label…. This is the bank a
- Convert to Addressable — marks each clip addressable (into the group, labeled), writes the readable address onto the container entry, nulls the direct clip. Undo-able for the containers; the Addressables entries stay.
- Revert to Direct — relinks the direct clips from their addresses and clears the addresses. Assets stay marked Addressable by default; tick Revert: also remove from Addressables (with a confirmation) to fully un-addressable them — only safe when nothing else uses those addresses.
Clips added later. Add the clip to the container as a normal clip, select the container, and Convert again: only the new entries are converted. When the selected containers already have addressable clips, the tab shows their group and label, with a Use button, so the new clip joins them.
Convert skips entries that are already addressable (clip already null). To re-address with a different style, Revert first, then Convert. The window logs what it touched.
Groups
Pick a group to see its audio clips, each marked used ×N or unused. Used means a container asset, or an AmbientSource in an open scene, points at the clip’s address. Scripts, prefabs and closed scenes aren’t searched, so check before removing an “unused” clip one of those plays.
- Add Selected Clips — puts the AudioClips selected in the Project into the group, with the chosen Bank Label (it starts as the label the group’s clips share) and the Address Style from the other tab. Use it for a clip that isn’t in a container, such as an ambience’s loop: paste its address (the Copy button) into the AmbientSource’s Looping Clip Address.
- Remove (per clip) and Remove Unused — take clips out of Addressables to clear out old ones. The clip assets stay in the project. A removed clip that something still points at goes silent; the confirmation names who. Ctrl+Z doesn’t undo it: add the clip again instead.
- Pack Each Clip Separately — shown when the group packs several banks into one bundle (for example after converting every zone into the Default Group). It switches the group to one bundle per clip. Rebuild the Addressables content afterwards.
- Include in Builds — shown when Addressables leaves the group out of builds: it has no Content Packing & Loading schema (a blank group made in the Addressables Groups window), or its Include in Build is off. Its clips play in the editor but play nothing in a build, and Addressables only mentions it while building. The button gives the group the Default Group’s build settings it lacks (one bundle per clip) and switches Include in Build on. Leave it if you keep the group out of builds on purpose.
A clip asset you delete from the project leaves its Addressables group by itself, so there’s nothing to clean up for those.
16.7 Diagnostics
- Precise miss warnings. An address that can’t resolve — no backend active, a typo, or an unregistered key — logs a precise, clickable, once-per-address warning instead of dropping silently. (An expected cold-drop — backend active, clip simply not preloaded yet — is not warned.)
- Loading Validator. Window ▸ Audio System ▸ Loading Validator → Validate scans every container asset and the open scene’s
AmbientSources and reports: an address but no backend; an unregistered / typo’d address; an entry with both a clip and an address; addressable content but noAddressableAudioRuntimein the scene. Each finding has a Select button. The validator uses reflection, so it runs even without the Addressables package installed.
16.8 How it works (architecture)
Same dependency-inversion pattern as the netcode layer: a small seam in the core, with the package-specific weight quarantined in a gated assembly.
SFXSystem.Runtime
IAudioClipSource (seam) ──default──► DirectClipSource (returns the serialized clip, sync, zero overhead)
▲
SFXSystem.Addressables (gated by com.unity.addressables)
AddressableClipSource ──► refcounted Addressables load/unload by region
AudioBank.Preload()/Release() (a bank = an Addressables label)
- Containers never read
.clipdirectly. They resolve throughAudioClipSources.Active(anIAudioClipSource). The defaultDirectClipSourcereturns the serialized clip synchronously. The Addressables backend drops in as an alternative source without re-touching a singlePlay()method. - An entry holds EITHER a direct
AudioClipOR an Addressables address string — never both. The address is the key a bank’s label resolves to, as shown in the Addressables Groups window (the GUID only if the address is the GUID). The core stays free of any Addressables type. - The play path stays synchronous on the warm path.
TryGetClipreturns a resident clip instantly; only a cold clip returns false and the play is dropped (drop-on-cold, v1). Bank preload keeps zones warm, so ~every play is warm. - Voice-level refcount is taken at the two chokepoints already in the core:
OnVoiceBound(whensource.clipis assigned) andOnVoiceReleased(when the voice returns to the pool). That’s what makes a bank release safe against a playing voice. - Zone binding is decoupled.
PropagationManagerraisesListenerEnteredZone/ExitedZoneevents; the Addressables assembly subscribes. Propagation never depends on Addressables, and vice-versa — the dependency is one-way (Addressables → core).
16.9 Status & limitations
- Import policy & Warming: stable; behavior-neutral for existing projects.
- Addressables backend: covers Random / Sequence / Routing per-entry addresses, the
AmbientSourcepin path, diagnostics + validator, and the authoring window. Automated tests cover a playing voice keeping its clip loaded after the zone’s bank releases, one clip shared by two zones, linger at zone borders, cold entries, teardown, the backend persisting across scene loads, and zone enter/exit staying paired through teleports and unloaded zones. - Cold entries in a Sequence: a cold entry drops that play, and the sequence stays on it, so the next play tries it again. With Auto Advance the run stops at that entry. Keep the zone’s bank loaded before the sequence reaches it.
- Tested in a player build (Windows, 6000.0.48f1): three zones, walked in and out. Only the zone you stand in stays loaded, a loop started in one zone keeps playing (and keeps only its own clip loaded) after you walk into the next, and nothing stays loaded outside the zones.
- Test unloading and memory in a build. With the default Play Mode Script (“Use Asset Database”), Addressables in the editor loads straight from the AssetDatabase, and releasing a clip never actually unloads it. To see real unloads in the editor, build the content (Addressables Groups window ▸ Build ▸ New Build ▸ Default Build Script) and set Play Mode Script to “Use Existing Build”; rebuild the content after changing it. The memory you save only shows properly in a player build: make a Development Build and watch memory with Unity’s Memory Profiler as you move between zones.
- Out of scope (v1): drop-on-cold (not defer-and-play-late) — keep zones preloaded; only the clip leaf is lazy (events/containers stay eager); zone-driven banks are one Addressables label per
AudioZone.
16.10 Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
| Addressable sound never plays | The clip is cold (its zone bank wasn’t preloaded) → drop-on-cold. Confirm a ZoneAudioBank with the right label is on the zone and an AddressableAudioRuntime is in this scene or an earlier one. Check the entry’s key is the clip’s Addressables address, not its GUID (run the Loading Validator). Watch the Debug Overlay. |
| Addressable sound plays in the editor but not in a build | Its group isn’t in builds: no Content Packing & Loading schema (a blank group from the Addressables Groups window) or Include in Build off. Addressables only says so while building (“does not have any associated AddressableAssetGroupSchemas”). The Authoring window’s Groups tab flags the group and Include in Builds fixes it (§16.6); then rebuild the Addressables content. |
references addressable key '…', but no Addressables backend is active |
No backend installed/active: install com.unity.addressables and add an AddressableAudioRuntime, or assign a direct AudioClip to that entry. |
| An entry has both a clip and an address | A contradiction — the populated clip eager-loads. The Loading Validator flags it; null the direct clip (or Revert the entry). |
| Clip unloads mid-play / click on zone exit | Should not happen — residency is voice-refcounted. Check you didn’t call AudioClipWarmer.Unload (blunt) or a raw Addressables.Release outside the refcounted source. |
| Load/unload churn at a zone border | Increase Linger Seconds on AddressableAudioRuntime. |
| Memory doesn’t drop when you leave a zone (in a build) | The zone’s clips share a bundle with clips still in use: one group for every zone, or a zone’s loop still playing in a Pack Together group. The Authoring window’s Groups tab flags such a group and can switch it to Pack Each Clip Separately (§16.6); or give each zone its own group. The Debug Overlay’s counts going to 0 only tells you the system let go; the bundle frees when nothing else in it is held. |
WarmContainer doesn’t warm my converted content |
Warming operates on direct clips only; addressable entries are owned by bank preload (§16.4). |
| Footsteps/weapons hitch under load | A short, frequently-fired SFX is set to Streaming — a per-voice disk stream. Set it to Decompress On Load (§16.3). |
| Addressable ambience silent until I enter the zone | Set the AmbientSource’s Looping Clip Address (leave Looping Clip empty) so it pins the clip. |
From the Pepper.SFX 3.1 guides (USER_GUIDE.md). Something wrong or unclear? Tell me.