Source file unity-csharp/audio/MusicStemLayers.cs from the GDnD code examples. Download raw file

// GDnD wiki example (2 of 2: AdaptiveMusicPlayer.cs, MusicStemLayers.cs)
// Demonstrates: vertical layering (vertical remixing) in plain Unity, with no FMOD or Wwise.
//   Every stem of one piece (pad, percussion, bass, brass...) is exported at the same length
//   and tempo, given its own looping AudioSource, and started on the SAME dspTime with
//   AudioSource.PlayScheduled so the stems stay locked together. Intensity then just fades
//   stems in and out; nothing is ever stopped or restarted, so the layers never drift.
//   SetIntensity(0) = only stem 0 audible, SetIntensity(2) = stems 0, 1 and 2, and so on.
// Related pages: [[unity-adaptive-music-crossfade]], [[interactive-music-techniques]],
//   [[unity-audiosource]]
// Unity 6 (6000.x). Built-in audio only (UnityEngine.AudioModule).
 
using UnityEngine;
 
namespace GDnD.Audio
{
    public class MusicStemLayers : MonoBehaviour
    {
        [Tooltip("One AudioSource per stem, quietest/most basic first. Loop on, Play On Awake off.")]
        [SerializeField] private AudioSource[] stems;
        [SerializeField, Range(0f, 1f)] private float stemVolume = 0.8f;
        [Tooltip("Seconds for a stem to fade fully in or out.")]
        [SerializeField] private float fadeSeconds = 1.5f;
        [SerializeField] private int startIntensity = 0;
 
        private float[] targets;
 
        private void Start()
        {
            targets = new float[stems.Length];
            double startDsp = AudioSettings.dspTime + 0.2; // shared start time for every stem
 
            foreach (AudioSource stem in stems)
            {
                stem.loop = true;
                stem.volume = 0f;
                stem.PlayScheduled(startDsp);
            }
 
            SetIntensity(startIntensity);
            for (int i = 0; i < stems.Length; i++) stems[i].volume = targets[i]; // no fade at start
        }
 
        /// <summary>Call from game code, e.g. when enemies notice the player.</summary>
        public void SetIntensity(int level)
        {
            for (int i = 0; i < stems.Length; i++)
                targets[i] = i <= level ? stemVolume : 0f;
        }
 
        private void Update()
        {
            float step = stemVolume / Mathf.Max(fadeSeconds, 0.01f) * Time.unscaledDeltaTime;
            for (int i = 0; i < stems.Length; i++)
                stems[i].volume = Mathf.MoveTowards(stems[i].volume, targets[i], step);
        }
    }
}