Easy Learn C#

Unity Audio Systems

Introduction to Unity Audio

Audio is a crucial element of game development that greatly enhances player immersion and experience. Unity provides a powerful audio system that allows developers to easily incorporate sounds, music, and environmental effects into their games.

In this guide, you'll learn:

  • Unity's audio components and architecture
  • Playing and controlling sounds from C# scripts
  • Creating dynamic audio experiences
  • Setting up 3D spatial audio
  • Working with audio mixers and effects
  • Optimizing audio performance

Unity Audio Architecture

Unity's audio system consists of several primary components that work together:

Core Audio Components:

  • AudioClip: The actual audio data (sound file)
  • AudioSource: Component that plays AudioClips
  • AudioListener: Component that receives sounds (usually attached to the camera)
  • Audio Mixer: Asset for routing, mixing, and applying effects to audio
  • Audio Effects: Processors that modify audio (reverb, echo, filters, etc.)

Basic audio workflow:

  1. Import audio files into your project
  2. Configure import settings based on the audio type
  3. Add AudioSource components to GameObjects
  4. Assign AudioClips to AudioSources
  5. Ensure there's an AudioListener in the scene (usually on the main camera)
  6. Play sounds through direct component manipulation or script control

Audio Import Settings

Configure these settings based on your audio type:

  • Compression Format: PCM (uncompressed), ADPCM (compressed), Vorbis (variable quality)
  • Force To Mono: Convert stereo to mono (good for 3D positioned sounds)
  • Load Type: Decompress on Load (low CPU, high memory), Compressed in Memory (balanced), Streaming (low memory, higher CPU)
  • Preload Audio Data: Whether to load the audio when the scene loads
  • Quality: Vorbis compression quality setting (for Vorbis format)

Basic Audio Playback

Playing sounds in Unity can be done through the Inspector or via scripting.

Basic Audio Controller:


using UnityEngine;

public class AudioController : MonoBehaviour
{
    // Audio clip references
    public AudioClip musicTrack;
    public AudioClip jumpSound;
    public AudioClip pickupSound;
    public AudioClip impactSound;
    
    // Audio source references
    private AudioSource musicSource;
    private AudioSource sfxSource;
    
    void Start()
    {
        // Create and configure audio sources
        SetupAudioSources();
        
        // Start playing background music
        PlayMusic(musicTrack, true);
    }
    
    void SetupAudioSources()
    {
        // Create two audio sources - one for music, one for SFX
        
        // Music audio source
        musicSource = gameObject.AddComponent();
        musicSource.loop = true;        // Music usually loops
        musicSource.volume = 0.5f;      // Lower volume for background music
        musicSource.priority = 0;       // Highest priority
        
        // SFX audio source
        sfxSource = gameObject.AddComponent();
        sfxSource.loop = false;         // Sound effects don't usually loop
        sfxSource.volume = 1.0f;        // Full volume for sound effects
        sfxSource.priority = 128;       // Normal priority
    }
    
    // Play a music track
    public void PlayMusic(AudioClip clip, bool loop = true)
    {
        if (clip == null) return;
        
        // Stop any currently playing music
        musicSource.Stop();
        
        // Set the new music clip
        musicSource.clip = clip;
        musicSource.loop = loop;
        
        // Start playing
        musicSource.Play();
    }
    
    // Play a sound effect once
    public void PlaySFX(AudioClip clip)
    {
        if (clip == null) return;
        
        // Play the sound effect
        sfxSource.PlayOneShot(clip);
    }
    
    // Play a sound effect with volume control
    public void PlaySFX(AudioClip clip, float volume)
    {
        if (clip == null) return;
        
        sfxSource.PlayOneShot(clip, volume);
    }
    
    // Example methods for specific game events
    public void PlayJumpSound()
    {
        PlaySFX(jumpSound);
    }
    
    public void PlayPickupSound()
    {
        PlaySFX(pickupSound, 0.7f);
    }
    
    public void PlayImpactSound(float intensity)
    {
        // Scale volume based on impact intensity
        float volume = Mathf.Clamp(intensity / 10f, 0.1f, 1.0f);
        PlaySFX(impactSound, volume);
    }
    
    // Control music volume
    public void SetMusicVolume(float volume)
    {
        musicSource.volume = Mathf.Clamp01(volume);
    }
    
    // Control SFX volume
    public void SetSFXVolume(float volume)
    {
        sfxSource.volume = Mathf.Clamp01(volume);
    }
    
    // Fade music in
    public void FadeMusicIn(float duration = 1.0f)
    {
        StartCoroutine(FadeMusicVolume(0, musicSource.volume, duration));
    }
    
    // Fade music out
    public void FadeMusicOut(float duration = 1.0f)
    {
        StartCoroutine(FadeMusicVolume(musicSource.volume, 0, duration));
    }
    
    // Coroutine to fade music volume
    private System.Collections.IEnumerator FadeMusicVolume(float startVolume, float targetVolume, float duration)
    {
        float startTime = Time.time;
        float elapsedTime = 0;
        
        musicSource.volume = startVolume;
        
        while (elapsedTime < duration)
        {
            elapsedTime = Time.time - startTime;
            float normalizedTime = elapsedTime / duration;
            musicSource.volume = Mathf.Lerp(startVolume, targetVolume, normalizedTime);
            yield return null;
        }
        
        musicSource.volume = targetVolume;
    }
}
                            

Key audio playback features:

  • AudioSource.Play(): Start playing the assigned clip
  • AudioSource.PlayOneShot(): Play a clip without replacing the assigned clip
  • AudioSource.Stop(): Stop playback
  • AudioSource.Pause()/UnPause(): Pause and resume playback
  • AudioSource properties: volume, pitch, loop, priority, spatialBlend

3D Spatial Audio

3D spatial audio creates immersive sound experiences by simulating how sounds behave in physical space.

Setting Up 3D Audio:


using UnityEngine;

public class SpatialAudioExample : MonoBehaviour
{
    public AudioClip engineSound;
    public AudioClip hornSound;
    public float maxDistance = 20f;
    public AnimationCurve volumeRolloff = AnimationCurve.EaseInOut(0, 1, 20, 0);
    
    private AudioSource engineSource;
    private AudioSource hornSource;
    
    void Start()
    {
        // Create engine audio source with 3D settings
        engineSource = gameObject.AddComponent();
        engineSource.clip = engineSound;
        engineSource.loop = true;
        
        // Configure 3D sound settings
        ConfigureSpatialAudio(engineSource);
        
        // Set up a second source for the horn
        hornSource = gameObject.AddComponent();
        ConfigureSpatialAudio(hornSource);
        
        // Start the engine sound
        engineSource.Play();
    }
    
    void ConfigureSpatialAudio(AudioSource source)
    {
        // Set to full 3D spatial audio
        source.spatialBlend = 1.0f;  // 0 = 2D, 1 = 3D
        
        // Set the minimum and maximum distances
        source.minDistance = 1.0f;
        source.maxDistance = maxDistance;
        
        // Use custom rolloff for more control
        source.rolloffMode = AudioRolloffMode.Custom;
        source.SetCustomCurve(AudioSourceCurveType.CustomRolloff, volumeRolloff);
        
        // Optional: configure doppler effect (pitch change when passing by)
        source.dopplerLevel = 0.5f;
        
        // Optional: set spread angle for directional audio
        source.spread = 60f;
    }
    
    // Called by input or game events
    public void HonkHorn()
    {
        hornSource.PlayOneShot(hornSound);
    }
    
    // Adjust engine sound based on speed
    public void UpdateEngineSound(float speed)
    {
        // Adjust pitch based on speed
        float normalizedSpeed = Mathf.Clamp01(speed / 100f);
        engineSource.pitch = Mathf.Lerp(0.8f, 1.2f, normalizedSpeed);
        
        // Adjust volume based on speed
        engineSource.volume = Mathf.Lerp(0.3f, 1.0f, normalizedSpeed);
    }
}
                            

3D audio key concepts:

  • Spatial Blend: How much 3D positioning affects the sound (0-1)
  • Min/Max Distance: Range where volume changes with distance
  • Volume Rolloff: How volume decreases with distance (Linear, Logarithmic, Custom)
  • Doppler Effect: Pitch shift when sound sources move relative to listener
  • Spread/Stereopan: How directional or omnidirectional the sound is

Working with Audio Mixers

Audio Mixers allow you to group, control, and apply effects to multiple audio sources.

Using Audio Mixers in Scripts:


using UnityEngine;
using UnityEngine.Audio;

public class AudioMixerController : MonoBehaviour
{
    // References to Audio Mixer and groups
    public AudioMixer mainMixer;
    
    // Exposed parameter names in the mixer
    private const string MASTER_VOLUME = "MasterVolume";
    private const string MUSIC_VOLUME = "MusicVolume";
    private const string SFX_VOLUME = "SFXVolume";
    private const string REVERB_AMOUNT = "ReverbAmount";
    private const string LOWPASS_CUTOFF = "LowpassCutoff";
    
    // Convert linear volume (0-1) to mixer value (decibels)
    private float ConvertToDecibel(float volume)
    {
        // Avoid log(0) which is -infinity
        if (volume <= 0)
            return -80f; // Min volume (-80dB is practically silent)
            
        // Convert to decibels (logarithmic scale)
        return Mathf.Log10(volume) * 20f;
    }
    
    // Set master volume (0-1 range)
    public void SetMasterVolume(float volume)
    {
        mainMixer.SetFloat(MASTER_VOLUME, ConvertToDecibel(volume));
    }
    
    // Set music volume (0-1 range)
    public void SetMusicVolume(float volume)
    {
        mainMixer.SetFloat(MUSIC_VOLUME, ConvertToDecibel(volume));
    }
    
    // Set SFX volume (0-1 range)
    public void SetSFXVolume(float volume)
    {
        mainMixer.SetFloat(SFX_VOLUME, ConvertToDecibel(volume));
    }
    
    // Apply low pass filter (for underwater, muffled effects)
    public void ApplyLowPassFilter(bool enabled, float cutoffFrequency = 1000f)
    {
        if (enabled)
        {
            mainMixer.SetFloat(LOWPASS_CUTOFF, cutoffFrequency);
        }
        else
        {
            // Set to maximum frequency to effectively disable the filter
            mainMixer.SetFloat(LOWPASS_CUTOFF, 22000f);
        }
    }
    
    // Adjust reverb for environment simulation
    public void SetReverbAmount(float amount)
    {
        mainMixer.SetFloat(REVERB_AMOUNT, Mathf.Lerp(0f, 1f, amount));
    }
    
    // Apply a preset effect (for example, changing environments)
    public void ApplyPreset(string presetName)
    {
        switch (presetName)
        {
            case "cave":
                SetReverbAmount(0.8f);
                ApplyLowPassFilter(true, 5000f);
                break;
                
            case "underwater":
                SetReverbAmount(0.4f);
                ApplyLowPassFilter(true, 1000f);
                break;
                
            case "outdoor":
                SetReverbAmount(0.2f);
                ApplyLowPassFilter(false);
                break;
                
            case "indoor":
                SetReverbAmount(0.4f);
                ApplyLowPassFilter(false);
                break;
                
            default:
                // Reset to default
                SetReverbAmount(0f);
                ApplyLowPassFilter(false);
                break;
        }
    }
    
    // Save audio settings to PlayerPrefs
    public void SaveAudioSettings(float master, float music, float sfx)
    {
        PlayerPrefs.SetFloat("MasterVolume", master);
        PlayerPrefs.SetFloat("MusicVolume", music);
        PlayerPrefs.SetFloat("SFXVolume", sfx);
        PlayerPrefs.Save();
    }
    
    // Load audio settings from PlayerPrefs
    public void LoadAudioSettings()
    {
        float master = PlayerPrefs.GetFloat("MasterVolume", 1.0f);
        float music = PlayerPrefs.GetFloat("MusicVolume", 1.0f);
        float sfx = PlayerPrefs.GetFloat("SFXVolume", 1.0f);
        
        SetMasterVolume(master);
        SetMusicVolume(music);
        SetSFXVolume(sfx);
    }
}
                            

Audio Mixer features:

  • Mixer Groups: Organize audio sources into categories (Music, SFX, Ambience)
  • Effects: Apply audio processing (reverb, EQ, compression, etc.)
  • Snapshots: Store and recall mixer states for different game situations
  • Exposed Parameters: Properties that can be controlled via scripts
  • Send Effects: Route audio to shared effect processors

Setting Up an Audio Mixer

  1. Create an Audio Mixer asset (right-click in Project window → Create → Audio Mixer)
  2. Add mixer groups for categories of sounds (Music, SFX, UI, Ambience, etc.)
  3. Add effects to groups (EQ, reverb, compression, etc.)
  4. Expose parameters you want to control via script
  5. Create snapshots for different game states or environments
  6. Assign AudioSources to the appropriate mixer groups

Dynamic Audio System

Create an adaptive audio system that responds to game events and player actions.

Advanced Audio Manager:


using UnityEngine;
using UnityEngine.Audio;
using System.Collections;
using System.Collections.Generic;

public class AudioManager : MonoBehaviour
{
    // Singleton instance
    public static AudioManager Instance { get; private set; }
    
    // Audio mixer
    public AudioMixer audioMixer;
    
    // Sound categories
    [System.Serializable]
    public class Sound
    {
        public string name;
        public AudioClip clip;
        public AudioMixerGroup mixerGroup;
        [Range(0f, 1f)]
        public float volume = 1f;
        [Range(0.1f, 3f)]
        public float pitch = 1f;
        [Range(0f, 1f)]
        public float spatialBlend = 0f;
        public bool loop = false;
        
        [HideInInspector]
        public AudioSource source;
    }
    
    // List of music tracks
    public Sound[] music;
    
    // List of sound effects
    public Sound[] soundEffects;
    
    // Ambient sound layers
    public Sound[] ambientSounds;
    
    // Currently playing music
    private Sound currentMusic;
    
    // Currently active ambient layers
    private List activeAmbientLayers = new List();
    
    // For crossfading music
    private Coroutine musicFadeCoroutine;
    
    void Awake()
    {
        // Singleton pattern
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
            return;
        }
        
        InitializeSounds();
    }
    
    // Initialize all audio sources
    void InitializeSounds()
    {
        // Initialize music tracks
        foreach (Sound s in music)
        {
            CreateAudioSource(s);
        }
        
        // Initialize sound effects
        foreach (Sound s in soundEffects)
        {
            CreateAudioSource(s);
        }
        
        // Initialize ambient sounds
        foreach (Sound s in ambientSounds)
        {
            CreateAudioSource(s);
        }
    }
    
    // Create an audio source for a sound
    void CreateAudioSource(Sound sound)
    {
        // Create a child object for the sound
        GameObject soundObject = new GameObject("Sound_" + sound.name);
        soundObject.transform.parent = transform;
        
        // Add audio source component
        sound.source = soundObject.AddComponent();
        sound.source.clip = sound.clip;
        sound.source.outputAudioMixerGroup = sound.mixerGroup;
        sound.source.volume = sound.volume;
        sound.source.pitch = sound.pitch;
        sound.source.spatialBlend = sound.spatialBlend;
        sound.source.loop = sound.loop;
        sound.source.playOnAwake = false;
    }
    
    // Play a music track with optional crossfade
    public void PlayMusic(string name, float fadeTime = 1.0f)
    {
        // Find the music track
        Sound s = System.Array.Find(music, sound => sound.name == name);
        if (s == null)
        {
            Debug.LogWarning("Music: " + name + " not found!");
            return;
        }
        
        // If already playing this track, do nothing
        if (currentMusic == s && s.source.isPlaying)
            return;
        
        // Stop any current fade
        if (musicFadeCoroutine != null)
            StopCoroutine(musicFadeCoroutine);
        
        // Start a new fade
        if (fadeTime > 0 && currentMusic != null)
        {
            musicFadeCoroutine = StartCoroutine(CrossfadeMusic(currentMusic, s, fadeTime));
        }
        else
        {
            // Stop current music
            if (currentMusic != null)
                currentMusic.source.Stop();
            
            // Play new music
            s.source.Play();
        }
        
        currentMusic = s;
    }
    
    // Crossfade between two music tracks
    IEnumerator CrossfadeMusic(Sound oldMusic, Sound newMusic, float fadeTime)
    {
        // Start playing new track at zero volume
        newMusic.source.volume = 0;
        newMusic.source.Play();
        
        float startVolume = oldMusic.source.volume;
        float targetVolume = newMusic.volume;
        
        // Fade out old music, fade in new music
        float timeElapsed = 0;
        
        while (timeElapsed < fadeTime)
        {
            timeElapsed += Time.deltaTime;
            float t = timeElapsed / fadeTime;
            
            oldMusic.source.volume = Mathf.Lerp(startVolume, 0, t);
            newMusic.source.volume = Mathf.Lerp(0, targetVolume, t);
            
            yield return null;
        }
        
        // Ensure final volumes are set correctly
        oldMusic.source.volume = 0;
        newMusic.source.volume = targetVolume;
        
        // Stop the old track
        oldMusic.source.Stop();
        
        // Reset old music volume for future use
        oldMusic.source.volume = oldMusic.volume;
        
        musicFadeCoroutine = null;
    }
    
    // Play a sound effect
    public void PlaySFX(string name)
    {
        Sound s = System.Array.Find(soundEffects, sound => sound.name == name);
        if (s == null)
        {
            Debug.LogWarning("SFX: " + name + " not found!");
            return;
        }
        
        // Random pitch variation for more natural sound
        s.source.pitch = s.pitch * Random.Range(0.9f, 1.1f);
        s.source.Play();
    }
    
    // Play a sound effect at a specific position
    public void PlaySFXAtPosition(string name, Vector3 position)
    {
        Sound s = System.Array.Find(soundEffects, sound => sound.name == name);
        if (s == null)
        {
            Debug.LogWarning("SFX: " + name + " not found!");
            return;
        }
        
        // Create a temporary object at the position
        AudioSource.PlayClipAtPoint(s.clip, position, s.volume);
    }
    
    // Start an ambient sound layer
    public void StartAmbientLayer(string name)
    {
        Sound s = System.Array.Find(ambientSounds, sound => sound.name == name);
        if (s == null)
        {
            Debug.LogWarning("Ambient sound: " + name + " not found!");
            return;
        }
        
        // If not already playing
        if (!s.source.isPlaying)
        {
            s.source.Play();
            activeAmbientLayers.Add(s);
        }
    }
    
    // Stop an ambient sound layer
    public void StopAmbientLayer(string name)
    {
        Sound s = System.Array.Find(ambientSounds, sound => sound.name == name);
        if (s == null)
        {
            Debug.LogWarning("Ambient sound: " + name + " not found!");
            return;
        }
        
        if (s.source.isPlaying)
        {
            s.source.Stop();
            activeAmbientLayers.Remove(s);
        }
    }
    
    // Set environmental audio state (uses Audio Mixer snapshots)
    public void SetEnvironment(string environmentType)
    {
        switch (environmentType.ToLower())
        {
            case "cave":
                // Start cave ambient sounds
                StartAmbientLayer("CaveAmbience");
                StopAmbientLayer("ForestAmbience");
                break;
                
            case "forest":
                // Start forest ambient sounds
                StartAmbientLayer("ForestAmbience");
                StopAmbientLayer("CaveAmbience");
                break;
                
            // Add more environment types as needed
        }
    }
    
    // Pause/resume all audio
    public void SetPaused(bool isPaused)
    {
        if (isPaused)
        {
            // Pause all active sounds
            if (currentMusic != null)
                currentMusic.source.Pause();
                
            foreach (Sound s in activeAmbientLayers)
            {
                s.source.Pause();
            }
        }
        else
        {
            // Resume all sounds
            if (currentMusic != null)
                currentMusic.source.UnPause();
                
            foreach (Sound s in activeAmbientLayers)
            {
                s.source.UnPause();
            }
        }
    }
}
                            

Audio Optimization

Audio can impact game performance. Here are techniques to optimize audio usage:

  1. Compression settings: Use appropriate compression for different audio types
  2. Streaming: Stream large audio files instead of loading them entirely into memory
  3. Audio pooling: Reuse AudioSources for frequently played sounds
  4. Distance-based culling: Disable distant audio sources
  5. Sample rate reduction: Lower sample rates for less critical sounds
  6. Mono conversion: Use mono for 3D positioned sounds
  7. Sound prioritization: Set priorities to ensure important sounds are played
  8. Voice limiting: Limit the maximum number of simultaneous sounds

Audio Pooling System:


using UnityEngine;
using System.Collections.Generic;

public class AudioPool : MonoBehaviour
{
    // Singleton instance
    public static AudioPool Instance { get; private set; }
    
    // Pool settings
    public int initialPoolSize = 10;
    public int maxPoolSize = 20;
    
    // The prefab with AudioSource to clone
    public AudioSource audioSourcePrefab;
    
    // The pool of available sources
    private Queue availableSources = new Queue();
    
    // Currently active sources
    private List activeSources = new List();
    
    // Reference to audio mixer group for all pooled sources
    public UnityEngine.Audio.AudioMixerGroup mixerGroup;
    
    void Awake()
    {
        // Singleton pattern
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializePool();
        }
        else
        {
            Destroy(gameObject);
        }
    }
    
    void InitializePool()
    {
        // Create initial pool of audio sources
        for (int i = 0; i < initialPoolSize; i++)
        {
            CreatePooledSource();
        }
    }
    
    // Create a new audio source for the pool
    AudioSource CreatePooledSource()
    {
        AudioSource newSource = Instantiate(audioSourcePrefab, transform);
        newSource.outputAudioMixerGroup = mixerGroup;
        newSource.playOnAwake = false;
        newSource.gameObject.name = "PooledAudioSource_" + availableSources.Count;
        newSource.gameObject.SetActive(false);
        availableSources.Enqueue(newSource);
        return newSource;
    }
    
    // Get an available audio source from the pool
    public AudioSource GetSource()
    {
        AudioSource source;
        
        // If no sources available, create new one if possible
        if (availableSources.Count == 0)
        {
            if (activeSources.Count < maxPoolSize)
            {
                source = CreatePooledSource();
            }
            else
            {
                // Find the oldest active source
                source = activeSources[0];
                activeSources.RemoveAt(0);
                
                // Stop and reset it
                source.Stop();
                source.transform.position = transform.position;
            }
        }
        else
        {
            // Get from pool
            source = availableSources.Dequeue();
        }
        
        // Activate the source
        source.gameObject.SetActive(true);
        activeSources.Add(source);
        
        // Reset source properties
        source.loop = false;
        source.spatialBlend = 0f;
        source.volume = 1f;
        source.pitch = 1f;
        
        return source;
    }
    
    // Release an audio source back to the pool
    public void ReleaseSource(AudioSource source)
    {
        if (source != null && activeSources.Contains(source))
        {
            activeSources.Remove(source);
            source.Stop();
            source.clip = null;
            source.gameObject.SetActive(false);
            availableSources.Enqueue(source);
        }
    }
    
    // Play a sound and automatically return to pool when finished
    public AudioSource PlaySound(AudioClip clip, Vector3 position, float volume = 1f, float pitch = 1f, bool spatial = false)
    {
        if (clip == null)
            return null;
            
        AudioSource source = GetSource();
        source.clip = clip;
        source.transform.position = position;
        source.volume = volume;
        source.pitch = pitch;
        source.spatialBlend = spatial ? 1f : 0f;
        source.Play();
        
        // Return to pool after playing
        StartCoroutine(ReturnToPoolWhenFinished(source, clip.length));
        
        return source;
    }
    
    // Release the source after it finishes playing
    private System.Collections.IEnumerator ReturnToPoolWhenFinished(AudioSource source, float delay)
    {
        yield return new WaitForSeconds(delay);
        
        if (source != null && source.gameObject.activeInHierarchy)
        {
            ReleaseSource(source);
        }
    }
    
    // Clean up any unused sources (call periodically or during scene transitions)
    public void CleanupUnusedSources()
    {
        // Return any non-playing active sources to the pool
        for (int i = activeSources.Count - 1; i >= 0; i--)
        {
            if (!activeSources[i].isPlaying)
            {
                ReleaseSource(activeSources[i]);
            }
        }
        
        // Trim excess pool size if needed
        while (availableSources.Count > initialPoolSize)
        {
            AudioSource excess = availableSources.Dequeue();
            Destroy(excess.gameObject);
        }
    }
}
                            

Best Practices for Unity Audio

  1. Use appropriate import settings for different audio types
  2. Organize sounds into categories with mixer groups
  3. Create a centralized audio manager for easier control
  4. Add subtle variations to repeated sounds (pitch, volume)
  5. Use 3D sound positioning for immersive environments
  6. Implement audio fallbacks for when resources are limited
  7. Provide volume controls for different audio categories
  8. Test on target platforms to identify performance issues
  9. Consider accessibility (subtitles, visual feedback for audio cues)
  10. Keep reference levels consistent across all audio assets