Dear friendy reader,

nice to see you around here. I publish these articles for free and it would be very kind of you to support my blog. You can start by disabling your AdBlocker. That would mean a lot to me and will help to finance this blog.

Best regards, J. Wiese

Unity 3D – Cross-fade two AudioClips with two AudioSources

Unity 3D – Cross-fade two AudioClips with two AudioSources

Cross-fading is a technique to stop one song and softly bend to another song. The following script is designed to cross-fade loop-able songs. The following script is designed to fade between two loop-able songs. Afterwards the song is played until it is replaced by another song. Just call the Play(AudioClip clip) function and the script organizes the rest.

How it works

In the beginning the script creates two AudioSource-Instances, which will be later used to generate the fade effect. At _player are the references of the AudioSource-Instances stored. Then set one of them as ActivePlayer. Afterwards we initialize the AudioSources with some start values. If you want persistent audio across multiple scenes, you should call DontDestroyOnLoad (this.gameObject).

private AudioSource[] _player;
    private int ActivePlayer = 0;
    private void Awake()
    {
        //Generate the two AudioSources
        _player = new AudioSource[2]{
            gameObject.AddComponent<AudioSource>(),
            gameObject.AddComponent<AudioSource>()
        };

        //Set default values
        foreach (AudioSource s in _player)
        {
            s.loop = true;
            s.playOnAwake = false;
            s.volume = 0.0f;
        }
        DontDestroyOnLoad (this.gameObject);
    }

The next part is a coroutine (IEnumerator) to manipulate an AudioSource-Instance. The first step of it is to calculate the changes which each execution of the coroutine will make. volumeChangesPerSecond defines the resolution in which the coroutine will work with. Note: If the value of volumeChangesPerSecond is higher than the frames per second, the execution time of the coroutine will be extended. After the progress the finishedCallback will be called to notify the script, that the coroutine has finished.

public float volumeChangesPerSecond = 10;
    IEnumerator FadeAudioSource(AudioSource player, float duration, float targetVolume, Action finishedCallback)
    {
        //Calculate the steps
        int Steps = (int)(volumeChangesPerSecond * duration);
        float StepTime = duration / Steps;
        float StepSize = (targetVolume - player.volume) / Steps;

        //Fade now
        for (int i = 1; i < Steps; i++)
        {
            player.volume += StepSize;
            yield return new WaitForSeconds(StepTime);
        }
        //Make sure the targetVolume is set
        player.volume = targetVolume;

        //Callback
        if (finishedCallback != null)
        {
            finishedCallback();
        }

To cross-fade a song you need to call Play(), which will setup the next fade. At first, it checks if the current track is not the same as currently playing, because fading to the same song sounds wired. Then the function kills all active fading coroutines to prevent multiple coroutines to interfere with each other. As next step the function starts the new coroutines and audio clip. At last, it sets ActivePlayer to the next index.

public void Play(AudioClip clip)
    { 
        //Prevent fading the same clip on both players 
        if (clip == _player[ActivePlayer].clip)
        {
            return;
        }
        //Kill all playing
        foreach (IEnumerator i in fader)
        {
            if (i != null)
            {
                StopCoroutine(i);
            }
        }

        //Fade-out the active play, if it is not silent (eg: first start)
        if (_player[ActivePlayer].volume > 0)
        {
            fader[0] = FadeAudioSource(_player[ActivePlayer], fadeDuration, 0.0f, () => { fader[0] = null; });
            StartCoroutine(fader[0]);
        }

        //Fade-in the new clip
        int NextPlayer = (ActivePlayer + 1) % _player.Length;
        _player[NextPlayer].clip = clip;
        _player[NextPlayer].Play();
        fader[1] = FadeAudioSource(_player[NextPlayer], fadeDuration, volume, () => { fader[1] = null; });
        StartCoroutine(fader[1]);

        //Register new active player
        ActivePlayer = NextPlayer;
    }

The rest of the script are getter and setter for muting and changing volume.

Download

Script with example Scene:

Crossfade Unity3D AssetPack

using System.Collections;
using UnityEngine;

public class AudioSourceCrossfade : MonoBehaviour
{

    private AudioSource[] _player;
    private IEnumerator[] fader = new IEnumerator[2];
    private int ActivePlayer = 0;

    //Note: If the volumeChangesPerSecond value is higher than the fps, the duration of the fading will be extended!
    private int volumeChangesPerSecond = 15;

    public float fadeDuration = 1.0f;

    [Range(0.0f, 1.0f)]
    [SerializeField]
    private float _volume = 1.0f;
    public float volume
    {
        get
        {
            return _volume;
        }
        set
        {
            _volume = value;
        }
    }

    /// <summary>
    /// Mutes all AudioSources, but does not stop them!
    /// </summary>
    public bool mute
    {
        set
        {
            foreach (AudioSource s in _player)
            {
                s.mute = value;
            }
        }
        get
        {
            return _player[ActivePlayer].mute;
        }
    }
    /// <summary>
    /// Setup the AudioSources
    /// </summary>
    private void Awake()
    {
        //Generate the two AudioSources
        _player = new AudioSource[2]{
            gameObject.AddComponent<AudioSource>(),
            gameObject.AddComponent<AudioSource>()
        };

        //Set default values
        foreach (AudioSource s in _player)
        {
            s.loop = true;
            s.playOnAwake = false;
            s.volume = 0.0f;
        }
    }
    /// <summary>
    /// Starts the fading of the provided AudioClip and the running clip
    /// </summary>
    /// <param name="clip">AudioClip to fade-in</param>
    public void Play(AudioClip clip)
    { 
        //Prevent fading the same clip on both players 
        if (clip == _player[ActivePlayer].clip)
        {
            return;
        }
        //Kill all playing
        foreach (IEnumerator i in fader)
        {
            if (i != null)
            {
                StopCoroutine(i);
            }
        }

        //Fade-out the active play, if it is not silent (eg: first start)
        if (_player[ActivePlayer].volume > 0)
        {
            fader[0] = FadeAudioSource(_player[ActivePlayer], fadeDuration, 0.0f, () => { fader[0] = null; });
            StartCoroutine(fader[0]);
        }

        //Fade-in the new clip
        int NextPlayer = (ActivePlayer + 1) % _player.Length;
        _player[NextPlayer].clip = clip;
        _player[NextPlayer].Play();
        fader[1] = FadeAudioSource(_player[NextPlayer], fadeDuration, volume, () => { fader[1] = null; });
        StartCoroutine(fader[1]);

        //Register new active player
        ActivePlayer = NextPlayer;
    }
    /// <summary>
    /// Fades an AudioSource(player) during a given amount of time(duration) to a specific volume(targetVolume)
    /// </summary>
    /// <param name="player">AudioSource to be modified</param>
    /// <param name="duration">Duration of the fading</param>
    /// <param name="targetVolume">Target volume, the player is faded to</param>
    /// <param name="finishedCallback">Called when finshed</param>
    /// <returns></returns>
    IEnumerator FadeAudioSource(AudioSource player, float duration, float targetVolume, System.Action finishedCallback)
    {
        //Calculate the steps
        int Steps = (int)(volumeChangesPerSecond * duration);
        float StepTime = duration / Steps;
        float StepSize = (targetVolume - player.volume) / Steps;

        //Fade now
        for (int i = 1; i < Steps; i++)
        {
            player.volume += StepSize;
            yield return new WaitForSeconds(StepTime);
        }
        //Make sure the targetVolume is set
        player.volume = targetVolume;

        //Callback
        if (finishedCallback != null)
        {
            finishedCallback();
        }
    }
}

You want to comment on this post?
Send an email to: bloginput@jwiese.eu