Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

AudioSource "Cross-fade" component

Discussion in 'Audio & Video' started by IgorAherne, Nov 28, 2016.

  1. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    Double-Audio source.
    Allows to cross-fade between two tracks by merely calling CrossFade().
    Automatically manages Audio Sources in the background, just attach it to your gameObject and you are good to go

    [obsolete, scroll down to get newer version]
     
    Last edited: Jul 19, 2017
  2. Gorzoni

    Gorzoni

    Joined:
    Oct 26, 2016
    Posts:
    1
    Nice! I am thinking of using DoTween like you use it.
     
    IgorAherne likes this.
  3. ozgesamanci

    ozgesamanci

    Joined:
    Jan 20, 2017
    Posts:
    6
    Can you explain exactly how to use CrossFade()? I'm a beginner at Unity and I'm having trouble getting it working. For debugging purposes, I'm trying to cross-fade two audio files after pressing a key. So my set up is like this:

    Code (CSharp):
    1. public AudioClip sound1;
    2.  
    3. if (Input.GetKey(KeyCode.M))
    4. {
    5.      CrossFade(sound1, 0.90f, 6.0f);
    6. }
    I have the audio clip attached to the sound1 variable, but I'm confused exactly how it cross-fades this clip with the other clip. When I start my project, both sound clips play at the same time. Then, when I press "M," only the sound1 clip plays, it doesn't do any cross-fading. Basically, I'm confused how to set up the other sound clip. A beginner-level explanation would be much appreciated!
     
  4. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    Here, I made it work without requiring 'using DG.Tweening' library.
    Should work out of the box now.

    the idea is
    1) attach this component onto gameObject.
    2) use GetComponent() to get reference to this DoubleAudioSource,
    3) tell it which AudioClip (.mp3, .wav etc) to play.

    No need to attach any clips to the _source0 or _source1, Just call CrossFade and it will smoothly transition to the clip from the currently played one.
    _source0 and _source1 are for the component's use - you don't have to worry about them.


    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Linq;
    4. using System.Collections.Generic;
    5.  
    6. //Credit Igor Aherne. Feel free to use as you wish, but mention me in credits :)
    7. //www.facebook.com/igor.aherne
    8.  
    9. //audio source which holds a reference to Two audio sources, allowing to transition
    10. //between incoming sound and the previously played one.
    11. [ExecuteInEditMode]
    12. public class DoubleAudioSource : MonoBehaviour {
    13.  
    14.     AudioSource _source0;
    15.     AudioSource _source1;
    16.  
    17.  
    18.     #region internal vars
    19.         bool _isFirst = true; //is _source0 currently the active AudioSource (plays some sound right now)
    20.  
    21.         Coroutine _zerothSourceFadeRoutine = null;
    22.         Coroutine _firstSourceFadeRoutine = null;
    23.     #endregion
    24.  
    25.  
    26. #region internal functionality
    27.     void Reset() {
    28.         Update();
    29.     }
    30.  
    31.  
    32.     void Awake() {
    33.         Update();
    34.     }
    35.  
    36.  
    37.  
    38.     void Update() {
    39.         //constantly check if our game object doesn't contain audio sources which we are referencing.
    40.  
    41.         //if the _source0 or _source1 contain obsolete references (most likely 'null'), then
    42.         //we will re-init them:
    43.         if (_source0 == null || _source1 == null) {
    44.  
    45.             //re-connect _soruce0 and _source1 to the ones in attachedSources[]
    46.             Component[] attachedSources = gameObject.GetComponents(typeof(AudioSource));
    47.             //For some reason, unity doesn't accept "as AudioSource[]" casting. We would get
    48.             //'null' array instead if we would attempt. Need to re-create a new array:
    49.             AudioSource[] sources = attachedSources.Select(c => c as AudioSource).ToArray();
    50.  
    51.             InitSources(sources);
    52.  
    53.             return;
    54.         }
    55.  
    56.     }
    57.  
    58.  
    59.     //re-establishes references to audio sources on this game object:
    60.     void InitSources(AudioSource[] audioSources) {
    61.  
    62.         if (ReferenceEquals(audioSources, null) || audioSources.Length == 0) {
    63.             _source0 = gameObject.AddComponent(typeof(AudioSource)) as AudioSource;
    64.             _source1 = gameObject.AddComponent(typeof(AudioSource)) as AudioSource;
    65.             //DefaultTheSource(_source0);
    66.             // DefaultTheSource(_source1);  //remove? we do this in editor only
    67.             return;
    68.         }
    69.  
    70.         switch (audioSources.Length) {
    71.             case 1: {
    72.                     _source0 = audioSources[0];
    73.                     _source1 = gameObject.AddComponent(typeof(AudioSource)) as AudioSource;
    74.                     //DefaultTheSource(_source1);  //TODO remove?  we do this in editor only
    75.                 }
    76.                 break;
    77.             default: { //2 and more
    78.                     _source0 = audioSources[0];
    79.                     _source1 = audioSources[1];
    80.                 }
    81.                 break;
    82.         }//end switch
    83.     }
    84.  
    85.     #endregion
    86.  
    87.  
    88.  
    89.  
    90.     //gradually shifts the sound comming from our audio sources to the this clip:
    91.     // maxVolume should be in 0-to-1 range
    92.     public void CrossFade( AudioClip playMe,
    93.                            float maxVolume,
    94.                            float fadingTime,
    95.                            float delay_before_crossFade = 0) {
    96.  
    97.         var fadeRoutine = StartCoroutine(     Fade( playMe,
    98.                                                     maxVolume,
    99.                                                     fadingTime,
    100.                                                     delay_before_crossFade)  );
    101.     }//end CrossFade()
    102.  
    103.  
    104.  
    105.     IEnumerator Fade( AudioClip playMe,
    106.                       float maxVolume,
    107.                       float fadingTime,
    108.                       float delay_before_crossFade = 0) {
    109.  
    110.  
    111.         if (delay_before_crossFade > 0) {
    112.             yield return new WaitForSeconds(delay_before_crossFade);
    113.         }
    114.  
    115.         if (_isFirst) { // _source0 is currently playing the most recent AudioClip
    116.             //so launch on source1
    117.             _source1.clip = playMe;
    118.             _source1.Play();
    119.             _source1.volume = 0;
    120.  
    121.                 if(_firstSourceFadeRoutine != null) {
    122.                     StopCoroutine(_firstSourceFadeRoutine);
    123.                 }
    124.                 _firstSourceFadeRoutine = StartCoroutine(fadeSource(_source1,
    125.                                                                     _source1.volume,
    126.                                                                     maxVolume,
    127.                                                                     fadingTime));
    128.                 if (_zerothSourceFadeRoutine != null) {
    129.                     StopCoroutine(_zerothSourceFadeRoutine);
    130.                 }
    131.                 _zerothSourceFadeRoutine = StartCoroutine(fadeSource(_source0,
    132.                                                                      _source0.volume,
    133.                                                                      0,
    134.                                                                      fadingTime));
    135.             _isFirst = false;
    136.  
    137.             yield break;
    138.         }
    139.  
    140.         //otherwise, _source1 is currently active, so play on _source0
    141.         _source0.clip = playMe;
    142.         _source0.Play();
    143.         _source0.volume = 0;
    144.  
    145.             if (_zerothSourceFadeRoutine != null) {
    146.                 StopCoroutine(_zerothSourceFadeRoutine);
    147.             }
    148.             _zerothSourceFadeRoutine = StartCoroutine(fadeSource(_source0,
    149.                                                                 _source0.volume,
    150.                                                                 maxVolume,
    151.                                                                 fadingTime));
    152.  
    153.             if (_firstSourceFadeRoutine != null) {
    154.                 StopCoroutine(_firstSourceFadeRoutine);
    155.             }
    156.             _firstSourceFadeRoutine = StartCoroutine(fadeSource(_source1,
    157.                                                                 _source1.volume,
    158.                                                                 0,
    159.                                                                 fadingTime));
    160.         _isFirst = true;
    161.     }
    162.  
    163.  
    164.  
    165.     IEnumerator fadeSource(AudioSource sourceToFade, float startVolume, float endVolume, float duration) {
    166.             float startTime = Time.time;
    167.  
    168.             while(true) {
    169.                
    170.                if(duration == 0) {
    171.                    sourceToFade.volume = endVolume;
    172.                    break;//break, to prevent division by  zero
    173.                }
    174.                 float elapsed = Time.time - startTime;
    175.            
    176.                 sourceToFade.volume = Mathf.Clamp01(Mathf.Lerp( startVolume,
    177.                                                                 endVolume,
    178.                                                                 elapsed/duration ));
    179.  
    180.                 if(sourceToFade.volume == endVolume) {
    181.                     break;
    182.                 }
    183.                 yield return null;
    184.             }//end while
    185.    }
    186.  
    187.  
    188.     //returns false if BOTH sources are not playing and there are no sounds are staged to be played.
    189.     //also returns false if one of the sources is not yet initialized
    190.     public bool isPlaying {
    191.         get {
    192.             if (_source0 == null || _source1 == null) {
    193.                 return false;
    194.             }
    195.  
    196.             //otherwise, both sources are initialized. See if any is playing:
    197.             if (_source0.isPlaying || _source1.isPlaying) {
    198.                 return true;
    199.             }
    200.  
    201.             //none is playing:
    202.             return false;
    203.         }//end get
    204.     }
    205.  
    206. }
     
    Last edited: Dec 17, 2017
  5. ozgesamanci

    ozgesamanci

    Joined:
    Jan 20, 2017
    Posts:
    6
    Would you be able to include an example of this working? I'm using your new implementation and I'm still having trouble. Instead of transitioning between sounds 1 and 2, when I call CrossFade(), it mutes sound 1 immediately and then slowly transitions sound 2 from no volume to full volume. So basically it's half-working; sound 2 transitions to full volume nicely, but sound 1 doesn't transition from full to no volume smoothly.
     
  6. Heroesflorian

    Heroesflorian

    Joined:
    Jul 17, 2015
    Posts:
    3
    Works for me. Thanks a lot!

    #Edit: Some minor point for improvement:
    1. //re-connect _soruce0 and _source1 to the ones in attachedSources[]
    2. Component[] attachedSources = gameObject.GetComponents(typeof(AudioSource));
    3. //For some reason, unity doesn't accept "as AudioSource[]" casting. We would get
    4. //'null' array instead if we would attempt. Need to re-create a new array:
    5. AudioSource[] sources = attachedSources.Select(c => c as AudioSource).ToArray();
    I would just use generics there:
    1. AudioSource[] sources = gameObject.GetComponents<AudioSource>();
     
    IgorAherne likes this.
  7. Heroesflorian

    Heroesflorian

    Joined:
    Jul 17, 2015
    Posts:
    3
    I made some slight changes (external functionality identical) to the script above by @IgorAherne
    The edited version looks like this:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. //Credit Igor Aherne. Feel free to use as you wish, but mention me in credits :)
    5. //www.facebook.com/igor.aherne
    6.  
    7. //audio source which holds a reference to Two audio sources, allowing to transition
    8. //between incoming sound and the previously played one.
    9.  
    10. /*the idea is
    11. 1) attach this component onto gameObject.
    12. 2) use GetComponent() to get reference to this DoubleAudioSource,
    13. 3) tell it which AudioClip (.mp3, .wav etc) to play.
    14.  
    15. No need to attach any clips to the _source0 or _source1, Just call CrossFade and it will smoothly transition to the clip from the currently played one.
    16. _source0 and _source1 are for the component's use - you don't have to worry about them.*/
    17.  
    18. [ExecuteInEditMode]
    19. public class DoubleAudioSource : MonoBehaviour
    20. {
    21.     AudioSource _source0;
    22.     AudioSource _source1;
    23.  
    24.     #region internal vars
    25.     bool cur_is_source0 = true; //is _source0 currently the active AudioSource (plays some sound right now)
    26.  
    27.     Coroutine _curSourceFadeRoutine = null;
    28.     Coroutine _newSourceFadeRoutine = null;
    29.     #endregion
    30.  
    31.  
    32.     #region internal functionality
    33.     void Reset()
    34.     {
    35.         Update();
    36.     }
    37.  
    38.  
    39.     void Awake()
    40.     {
    41.         Update();
    42.     }
    43.  
    44.  
    45.     void Update()
    46.     {
    47.         //constantly check if our game object doesn't contain audio sources which we are referencing.
    48.  
    49.         //if the _source0 or _source1 contain obsolete references (most likely 'null'), then
    50.         //we will re-init them:
    51.         if (_source0 == null || _source1 == null)
    52.         {
    53.             InitAudioSources();
    54.         }
    55.  
    56.     }
    57.  
    58.  
    59.     //re-establishes references to audio sources on this game object:
    60.     void InitAudioSources()
    61.     {
    62.         //re-connect _source0 and _source1 to the ones in attachedSources[]
    63.         AudioSource[] audioSources = gameObject.GetComponents<AudioSource>();
    64.  
    65.         if (ReferenceEquals(audioSources, null) || audioSources.Length == 0)
    66.         {
    67.             _source0 = gameObject.AddComponent<AudioSource>();
    68.             _source1 = gameObject.AddComponent<AudioSource>();
    69.             //DefaultTheSource(_source0);
    70.             // DefaultTheSource(_source1);  //remove? we do this in editor only
    71.             return;
    72.         }
    73.  
    74.         switch (audioSources.Length)
    75.         {
    76.             case 1:
    77.                 {
    78.                     _source0 = audioSources[0];
    79.                     _source1 = gameObject.AddComponent<AudioSource>();
    80.                     //DefaultTheSource(_source1);  //TODO remove?  we do this in editor only
    81.                 }
    82.                 break;
    83.             default:
    84.                 { //2 and more
    85.                     _source0 = audioSources[0];
    86.                     _source1 = audioSources[1];
    87.                 }
    88.                 break;
    89.         }//end switch
    90.     }
    91.     #endregion
    92.  
    93.  
    94.     //gradually shifts the sound comming from our audio sources to the this clip:
    95.     // maxVolume should be in 0-to-1 range
    96.     public void CrossFade(AudioClip clipToPlay, float maxVolume, float fadingTime, float delay_before_crossFade = 0)
    97.     {
    98.         //var fadeRoutine = StartCoroutine(Fade(clipToPlay, maxVolume, fadingTime, delay_before_crossFade));
    99.         StartCoroutine(Fade(clipToPlay, maxVolume, fadingTime, delay_before_crossFade));
    100.  
    101.     }//end CrossFade()
    102.  
    103.  
    104.     IEnumerator Fade(AudioClip playMe, float maxVolume, float fadingTime, float delay_before_crossFade = 0)
    105.     {
    106.         if (delay_before_crossFade > 0)
    107.         {
    108.             yield return new WaitForSeconds(delay_before_crossFade);
    109.         }
    110.  
    111.         AudioSource curActiveSource, newActiveSource;
    112.         if (cur_is_source0)
    113.         {
    114.             //_source0 is currently playing the most recent AudioClip
    115.             curActiveSource = _source0;
    116.             //so launch on _source1
    117.             newActiveSource = _source1;
    118.         }
    119.         else
    120.         {
    121.             //otherwise, _source1 is currently active
    122.             curActiveSource = _source1;
    123.             //so play on _source0
    124.             newActiveSource = _source0;
    125.         }
    126.  
    127.         //perform the switching
    128.         newActiveSource.clip = playMe;
    129.         newActiveSource.Play();
    130.         newActiveSource.volume = 0;
    131.  
    132.         if (_curSourceFadeRoutine != null)
    133.         {
    134.             StopCoroutine(_curSourceFadeRoutine);
    135.         }
    136.  
    137.         if (_newSourceFadeRoutine != null)
    138.         {
    139.             StopCoroutine(_newSourceFadeRoutine);
    140.         }
    141.  
    142.         _curSourceFadeRoutine = StartCoroutine(fadeSource(curActiveSource, curActiveSource.volume, 0, fadingTime));
    143.         _newSourceFadeRoutine = StartCoroutine(fadeSource(newActiveSource, newActiveSource.volume, maxVolume, fadingTime));
    144.  
    145.         cur_is_source0 = !cur_is_source0;
    146.  
    147.         yield break;
    148.     }
    149.  
    150.  
    151.     IEnumerator fadeSource(AudioSource sourceToFade, float startVolume, float endVolume, float duration)
    152.     {
    153.         float startTime = Time.time;
    154.  
    155.         while (true)
    156.         {
    157.             float elapsed = Time.time - startTime;
    158.  
    159.             sourceToFade.volume = Mathf.Clamp01(Mathf.Lerp(startVolume, endVolume, elapsed / duration));
    160.  
    161.             if (sourceToFade.volume == endVolume)
    162.             {
    163.                 break;
    164.             }
    165.  
    166.             yield return null;
    167.         }//end while
    168.     }
    169.  
    170.  
    171.     //returns false if BOTH sources are not playing and there are no sounds are staged to be played.
    172.     //also returns false if one of the sources is not yet initialized
    173.     public bool isPlaying
    174.     {
    175.         get
    176.         {
    177.             if (_source0 == null || _source1 == null)
    178.             {
    179.                 return false;
    180.             }
    181.  
    182.             //otherwise, both sources are initialized. See if any is playing:
    183.             return _source0.isPlaying || _source1.isPlaying;
    184.         }//end get
    185.     }
    186.  
    187. }
     
  8. Flavosky

    Flavosky

    Joined:
    Apr 6, 2020
    Posts:
    4
    Hello @Heroesflorian and @IgorAherne !
    I am trying to make a crossfade using the script above but I don' understand how to use it...
    Could you explain with a little example ?

    To explain in a little more detail my project, I am using Audio Importer to allow players to add their own audio loops in the game (I'm working on an update of my audio game "Forgotten Soundscape") and I would like the sound loop automatically with crossfade.

    Here is the importer script I use :

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using System.Globalization;
    6.  
    7. public class ImporterExampleLoop01 : MonoBehaviour
    8. {
    9.     public GameObject yourGameObject; //assign your gameobject from the inspector here
    10.     MeshFilter yourMesh;
    11.     public Browser browser;
    12.     public BassImporter importer;
    13.     public AudioSource audioSource;
    14.     public float WaitMinBetweenPlays = 0f;
    15.     public float WaitMaxBetweenPlays = 0f;
    16.     public float waitTimeCountdown = -0.5f;
    17.     public float AudioPitch, AudioVol, volumeMax;
    18.     public Slider slAudioPitch;
    19.     public Slider slAudioVol;
    20.     private float slAudioPitchRounded;
    21.     private float slAudioVolRounded;
    22.     public Text TextValueAudioPitch;
    23.     public Text TextValueAudioVol;
    24.     [SerializeField]
    25.     private string soundPathLoaded;
    26.     [SerializeField]
    27.     private string soundPath;  
    28.     public AudioClip audioClip;  
    29.     public Button saveButton;
    30.    
    31.        
    32.     void Start()
    33.     {
    34.         audioSource = GetComponent<AudioSource>();
    35.                
    36.         Debug.Log("The sound path is : " + soundPath);
    37.        
    38.         //Chargement du path audio sauvegardé
    39.         soundPathLoaded = PlayerPrefs.GetString("soundPathPPL1");
    40.         StartCoroutine(Import(soundPathLoaded));
    41.        
    42.         //Chargement des options de random - pitch/volume
    43.         slAudioPitch.value = PlayerPrefs.GetFloat("slPitchPPL1", 1f);
    44.         slAudioVol.value = PlayerPrefs.GetFloat("slVolPPL1", 1f);
    45.        
    46.         Button btn = saveButton.GetComponent<Button>();
    47.         btn.onClick.AddListener(TaskOnClick);              
    48.     }
    49.    
    50.     void TaskOnClick()
    51.     {
    52.         PlayerPrefs.SetFloat("slPitchPPL1", slAudioPitch.value);
    53.         PlayerPrefs.SetFloat("slVolPPL1", slAudioVol.value);
    54.        
    55.         if (soundPath.Trim().Length == 0)
    56.         {
    57.             Debug.Log("The sound path is empty");
    58.         }
    59.        
    60.         else
    61.         {  
    62.             PlayerPrefs.SetString("soundPathPPL1", soundPath);
    63.             Debug.Log("The saved path is : " + soundPath);  
    64.         }
    65.     }
    66.  
    67.     void Awake()
    68.     {
    69.         browser.FileSelected += OnFileSelected;      
    70.     }
    71.        
    72.     private void OnFileSelected(string path)
    73.     {
    74.         Destroy(audioSource.clip);
    75.         StartCoroutine(Import(path));
    76.         Debug.Log("The initial path is : " + path);
    77.        
    78.         soundPath = path;
    79.     }
    80.  
    81.     IEnumerator Import(string path)
    82.     {
    83.         importer.Import(path);
    84.  
    85.         while (!importer.isInitialized && !importer.isError)
    86.             yield return null;
    87.  
    88.         if (importer.isError)
    89.             Debug.LogError(importer.error);
    90.  
    91.         audioSource.clip = importer.audioClip;
    92.                
    93.         //Mesh (dans /Resources) qui s'applique lors de la sélection du fichier audio :
    94.         yourMesh = yourGameObject.GetComponent<MeshFilter>();
    95.         yourMesh.sharedMesh = Resources.Load<Mesh>("pizza-frozen");                
    96.     }
    97.    
    98.     void Update()
    99.     {      
    100.         slAudioPitchRounded = Mathf.Round(slAudioPitch.value * 10f) * 0.1f;
    101.         AudioPitch = slAudioPitch.value;
    102.         slAudioVolRounded = Mathf.Round(slAudioVol.value * 100f) * 0.01f;
    103.         AudioVol = slAudioVol.value;
    104.        
    105.         TextValueAudioPitch.text = slAudioPitch.value.ToString("0.0" + " x");
    106.         TextValueAudioVol.text = slAudioVolRounded.ToString("0" + " %");
    107.                
    108.         audioSource.clip = importer.audioClip;      
    109.         audioSource.pitch = AudioPitch;
    110.         audioSource.volume = AudioVol;
    111.        
    112.         if (!audioSource.isPlaying)
    113.         {
    114.             if (waitTimeCountdown < 0f)
    115.             {              
    116.                 audioSource.Play();
    117.                 waitTimeCountdown = Random.Range(WaitMinBetweenPlays, WaitMaxBetweenPlays);
    118.             }
    119.             else
    120.             {
    121.                 waitTimeCountdown -= Time.deltaTime;
    122.             }
    123.         }      
    124.     }  
    125. }
    I am not developer but Sound Designer so This is difficult to me to understand how to link your Crossfade Script to my Importer audio Script.

    Thanks in advance for your time and explanations !