Critical Rules
- Do not make changes before reporting findings to the user
- Follow steps in strict order; never jump ahead
- STOP at every
WAIT checkpoint and await the user's response before continuing
- Quality is more important than speed: measure before and after every change
- Always verify results in a device build; Editor audio stats are indicative only
0. Set up the execution path
Every C# step below runs inside a live Editor through the Unity CLI. The unity-cli skill owns getting you there — installing the CLI, confirming a connected Editor, adding the project's com.unity.pipeline package, telling a genuinely absent Editor apart from one stuck in Safe Mode, and discovering the Editor's command catalog. Follow it first; don't re-derive any of it here.
Two things it can't know for you:
- You need
eval in particular, not just a reachable Editor. Confirm it appears in the
catalog. Its presence depends on the Pipeline package version, not on the CLI, so a healthy install can still lack it — if it's missing, say so and stop.
- Do not hand-edit
.meta files to change import settings. Importer values only take effect
through SaveAndReimport() in a live Editor, so an unreachable Editor is a stop, not a cue to edit metadata directly.
Run C# with unity command eval --code '<snippet>'. Discover the parameter shape from unity command --format json rather than assuming one. unity command defaults to a 30 second timeout.
Passing C# to eval
eval compiles a statement block, not a file. Two consequences, both of which cause a compile error rather than a warning:
- No
using directives. The compiler reads using UnityEngine; as a resource-disposal
statement and rejects it (CS0210).
- Types must be fully qualified. A bare
AssetDatabase or AudioImporter does not resolve
(CS0246 / CS0103), and a bare Object is ambiguous with object (CS0104).
The recipes in [resources/audio-import-api.md](resources/audio-import-api.md) are written fully qualified so they can be passed to eval as-is.
1. Pre-Flight: Detect Audio System
Before doing anything else, establish the audio environment:
- Detect platform and sample rate: Use
eval to read EditorUserBuildSettings.activeBuildTarget and AudioSettings.outputSampleRate. The output sample rate affects whether overriding clip sample rates will actually save memory.
- Detect AudioMixer presence: Use the mixer-asset query recipe in [resources/audio-import-api.md](resources/audio-import-api.md) to see if a mixer graph exists. If none exists, note that routing and effect costs are not a concern.
- Detect AudioListener: Use the scene-component query recipe in [resources/audio-import-api.md](resources/audio-import-api.md) for
UnityEngine.AudioListener to confirm exactly one listener is present. Multiple listeners produce incorrect spatialization; zero listeners produce silence.
- Proceed only after platform and listener state are confirmed.
2. Assess Current State
Before recommending any change, gather observable data:
- Find all AudioSources: Use the scene-component query recipe in [resources/audio-import-api.md](resources/audio-import-api.md) for
UnityEngine.AudioSource. For each result, use one eval call to batch-read properties — see the batch read recipe in [resources/audio-import-api.md](resources/audio-import-api.md).
- Inspect mixer topology: If a mixer was found in Pre-Flight, use
eval to read the AudioMixer's exposed parameters and group count. A group count above ~8 or effects on the Master group are immediate flags.
- Check DSP buffer size: Use the DSP buffer recipe in [resources/audio-import-api.md](resources/audio-import-api.md) to read buffer size. See DSP Buffer Size Guidelines in [resources/platform-settings.md](resources/platform-settings.md) for recommended values.
- Report findings before making changes: Summarize ALL detected sources, the listener count, and mixer depth to the user. Flag any immediate risks (e.g., stereo clip with
spatialBlend = 1, Decompress On Load on a clip > 1 MB, reverb on the Master group).
WAIT for the user to review the assessment before proceeding.
3. Understand Request
Route to the correct section based on what the user needs:
| User Says |
Path |
| "audio memory too high" / "memory profiler shows audio" |
Section 4 — Import settings audit |
| "load times slow" / "decompression stall" |
Section 4 — Load Type review |
| "DSP spike" / "mixer CPU" / "audio CPU high" |
Section 4B — Mixer audit |
| "3D sound wrong" / "only left channel plays" / "stereo in 3D" |
Section 4A — Force To Mono + spatial settings |
| "quality artifacts" / "voice sounds bad" / "Vorbis crackling" |
Section 4C — Compression quality tuning |
| "mobile audio battery" / "mobile memory" |
Section 4D — Mobile sample rate override |
| "set import settings on all clips" / "batch audio settings" |
Section 4 — Bulk import audit |
| "streaming" / "background loading" / "Addressables audio" |
Section 4E — Streaming and async load |
If the symptom is ambiguous, ask: "Is the problem audio memory usage, DSP CPU spikes, or audio playback quality?"
4. Primary Diagnostic Workflow
Use the findings from Section 2 to determine which sub-section applies. More than one may apply simultaneously.
4A. Force To Mono and Spatial Settings
For any AudioSource where spatialBlend > 0 (3D positioned sound):
- Check clip channel count: Use
eval to read audioSource.clip.channels. If channels == 2 and spatialBlend == 1, only the left channel plays — this is a bug, not a feature.
- Recommend Force To Mono: Use the read importer recipe in [resources/audio-import-api.md](resources/audio-import-api.md) to inspect current settings, then apply Force To Mono using the force-to-mono recipe.
- Apply and reimport: Report before/after channel counts to the user.
- Verify spatial blend: Use
eval to confirm audioSource.spatialBlend is 1.0 (full 3D) and audioSource.rolloffMode is set to an appropriate curve.
4B. AudioMixer Audit
- Measure group depth: Use
eval to walk the mixer's group tree and count levels. More than 3 levels (Master → SFX / Music / Voice → sub-bus) adds routing overhead every frame, even when children are silent.
- Check effects on silent groups: Use
eval to query each group's effects list. Effects such as AudioReverbFilter run their DSP at full cost even when no AudioSource routes to that group.
- Flag SFX Reverb on parent groups: This is the most expensive built-in effect. If found on the Master or a high-level group, flag it explicitly.
- Present recommendations to the user:
- Remove or bypass effects on groups that have no active sources. - Use snapshots to switch mix states (combat / explore / pause) rather than toggling effects at runtime. - Flatten unnecessary sub-buses; redirect sources to a shallower ancestor.
WAIT for the user to approve the mixer changes before applying.
- Verify DSP buffer size: If
bufferLength from Pre-Flight is very small (< 256), recommend increasing it — see DSP Buffer Size Guidelines in [resources/platform-settings.md](resources/platform-settings.md).
4C. Compression Quality Tuning
- Read current compression format: Use the read importer recipe in [resources/audio-import-api.md](resources/audio-import-api.md) to read
compressionFormat and quality for the clips reported by the user.
- Apply the platform matrix: See the Compression Format Matrix in [resources/platform-settings.md](resources/platform-settings.md) for per-platform recommendations.
- Warn about lossy sources: Use the lossy source check recipe in [resources/audio-import-api.md](resources/audio-import-api.md). If the original file is MP3, warn the user that lossy source quality is lost permanently after Unity re-encodes. Recommend WAV or AIFF sources.
4D. Mobile Sample Rate Override
- Identify SFX clips on mobile target: Use the scene-component query recipe for
UnityEngine.AudioSource and filter for non-music, non-dialogue clips.
- Read current sample rate setting: Use the read importer recipe in [resources/audio-import-api.md](resources/audio-import-api.md) to read
sampleRateSetting and sampleRateOverride for each clip.
- Apply mobile override: Use the sample rate override recipe in [resources/audio-import-api.md](resources/audio-import-api.md). See Sample Rate Recommendations in [resources/platform-settings.md](resources/platform-settings.md) for per-use-case rates.
- Report savings: Halving the sample rate halves the PCM memory cost. Report the estimated saving for each clip changed.
4E. Load Type and Streaming
- Audit Load Type per clip: Use
eval to read clip.loadType for each clip found in Section 2.
- Apply the decision rule: See Load Type Decision Table in [resources/platform-settings.md](resources/platform-settings.md).
- Flag mismatches: See Load Type Mismatch Flags in [resources/platform-settings.md](resources/platform-settings.md). Report both types of mismatches to the user.
- Apply
Load In Background for any Streaming clip — use the Load In Background recipe in [resources/audio-import-api.md](resources/audio-import-api.md).
5. Validation
After any import setting or mixer change:
- Re-read clip stats: Use
eval to re-read clip.loadType, clip.channels, AudioSettings.outputSampleRate, and the importer's compressionFormat to confirm the change applied after reimport.
- Confirm AudioSource routing: Use the scene-component query recipe for
UnityEngine.AudioSource and verify audioSource.outputAudioMixerGroup is assigned as expected after any mixer restructure.
- Report delta: State the before and after values for each setting changed. Do not assume the change was effective without reading back the applied importer values.
- Iterate limit: Maximum 3 adjust-and-verify cycles before pausing to ask the user for feedback.
6. Troubleshooting
Stereo clip on a 3D AudioSource — only left channel audible
- Confirm
audioSource.spatialBlend == 1.
- Confirm
audioSource.clip.channels == 2.
- Enable
forceToMono in the AudioClip importer and reimport. Unity mixes both channels to mono during import, preserving level with normalize = true (keep on).
- If the user does not want to reimport: set
audioSource.panStereo = 0 as a runtime workaround, but warn this does not recover stereo information.
Decompress On Load clip causes memory spike
- Confirm
clip.loadType == AudioClipLoadType.DecompressOnLoad and clip.length is long (> 5 s).
- Switch to
Streaming if it is music or ambience, CompressedInMemory if played only occasionally.
- If the clip is short but still large: check
clip.channels (stereo wastes double the memory) and clip.frequency (high sample rate on a mobile target wastes memory). Apply Force To Mono and/or sample rate override.
AudioMixer CPU spike — DSP thread hot
- Confirm with the mixer-asset query recipe that the mixer graph exists.
- Use
eval to list all groups and their attached effects. Look for reverb, chorus, or EQ on high-level groups.
- Move expensive effects down to leaf groups that are only active when sources are playing.
- Use snapshots to bypass effect chains during gameplay states where they are not heard (e.g., bypass reverb during a menu).
- If the DSP buffer is small (64 or 128 samples), raise it — see DSP Buffer Size Guidelines in [resources/platform-settings.md](resources/platform-settings.md).
Vorbis quality artifacts on dialogue
- Confirm
defaultSampleSettings.compressionFormat == AudioCompressionFormat.Vorbis.
- Confirm
defaultSampleSettings.quality — default is 0.5, which is often audible on voice. Raise to 0.7–0.85.
- On iOS: switch to AAC instead of Vorbis (hardware decode, better quality at equivalent bitrate).
- Confirm the source file is lossless (WAV or AIFF). MP3 sources cannot recover quality lost before Unity's re-encode.
AudioListener count is not exactly one
- Zero listeners: All audio will be silent. Use
eval to add an AudioListener component to the main camera: UnityEngine.Camera.main.gameObject.AddComponent<UnityEngine.AudioListener>().
- Multiple listeners: Unity uses the last enabled one, producing unpredictable spatialization. Use the scene-component query recipe for
UnityEngine.AudioListener and disable all but the intended one.
Load In Background causes first-play silence
This is expected behavior: the clip has not finished loading when Play() is first called. Mitigate with:
- Preload the clip at scene start by calling
clip.LoadAudioData() before it is needed.
- Use
AudioSource.PlayScheduled() with a slight delay to allow async load to complete.
- For AudioSources that must play immediately: switch to
CompressedInMemory (synchronous on first play) rather than Streaming with background load.
7. Completion
After finishing the audit or optimization:
- Summarize every setting changed with before/after values.
- List any clips or groups that still need attention (e.g., clips that require on-device measurement to confirm savings).
- If the user needs runtime memory measurement, point them at the Memory Profiler package, which reports the largest AudioClips by runtime byte cost.
- If mixer CPU is still high after the audit, point them at the Unity Profiler's Audio module for DSP thread profiling.
Detailed References
- Platform settings, compression matrix, load types, sample rates: [resources/platform-settings.md](resources/platform-settings.md)
- AudioImporter API recipes and code patterns: [resources/audio-import-api.md](resources/audio-import-api.md)
See Also
- Memory Profiler package — finds the largest AudioClips by runtime byte cost.
- Unity Profiler, Audio module — DSP CPU markers and frame-time budget.
audio-setup-mixers — creating mixers and routing Audio Sources into groups.