Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question How can I sort of loop a piece of music?

Discussion in 'Scripting' started by karderos, Apr 16, 2023.

  1. karderos

    karderos

    Joined:
    Mar 28, 2023
    Posts:
    376
    I have a song but it doesnt loop perfectly.

    To loop perfectly it has to be played like so:

    Untitled2.png
    It starts normally, I can just call to play it from an audio source

    But then I need a 2nd audio source? To start playing the song again after 1min has passed.

    And at the same time fade out the currently playing track.

    And after this process it should continue to loop forever using the same logic.

    What is the best way to code this? What is the best way to tell the current time of the song so I know when to trigger the effects?

    Is there a feature out of the box in unity for this? I think that just checkmarking "loop" doesn't work for this case?

    I know I can schedule tracks to play but then to loop infinitely I dont see how I could do that.

    What would be the best way to code this? Thanks!
     
  2. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,495
    Is it necessary to have it fade? Or do you just want to loop a specific section?

    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class AudioLooper : MonoBehaviour
    4. {
    5.     public AudioSource audioSource;
    6.     public float loopStart;
    7.     public float loopEnd;
    8.  
    9.     private void Start()
    10.     {
    11.         // Set the initial playback position to the loop start position
    12.         audioSource.time = loopStart;
    13.     }
    14.  
    15.     private void Update()
    16.     {
    17.         // Check if the audio clip has reached the end of the loop section
    18.         if (audioSource.time >= loopEnd)
    19.         {
    20.             // Set the time to the start of the loop section
    21.             audioSource.time = loopStart;
    22.         }
    23.     }
    24. }
     
    karderos likes this.
  3. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,495
    This is a lot easier if you just record the music to include the blending, but sometimes you can't do that.

    Once you can detect the current time (
    audioSource.time
    ), you can calculate the desired volume at that time. Mathf.InverseLerp interpolates but clamps the result to 0~1, so it's easy:

    audioSource.volume =
    1f - Mathf.InverseLerp(fadeStartTime, clipEndTime, audioSource.time);


    The problem is the overlapping nature of your crossfade. I think you need two AudioSource to do that, one for the "current" loop which begins to fade, and one for the "next" loop which starts to play at the beginning of the other's fade. Then you can reuse the first AudioSource as the next-next one.
     
    karderos likes this.
  4. karderos

    karderos

    Joined:
    Mar 28, 2023
    Posts:
    376
    thanks, I have the music but when i try to create a good loop in premiere somehow the sound is getting screwed on the export

    I guess Im not a sound guy

    If I was just doing this for a movie I could easily do it since I just have to duplicate the effect for the duration, but for a game I need it to be able to run infinitely.

    Anyway if I embed the loop into the music the beginning of the song is no longer a clean start


    I don't mind having many audio sources, thanks for the code for fading

    I do need to fade the end otherwise it doesnt sound clean.

    Using update to check for the time, I think gives me a clue of when to create the extra audio source thx
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,066
    karderos likes this.
  6. Deleted User

    Deleted User

    Guest

  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,066
    I use MP3 if I just need music, which I never loop seamlessly. I just let it fade, then restart.

    If it has to loop seamlessly I use WAV, such as for engine thrusters and whatnot.
     
  8. karderos

    karderos

    Joined:
    Mar 28, 2023
    Posts:
    376
    Kurt-Dekker likes this.
  9. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,495
    Oh, it occurred to me I already had this class lying around. It can use one or two AudioSources, and ping-pong them with a list of clips to be selected randomly. If using two AudioSources (such as with one on a child object of the other), it will do the crossfade just as I suggested above. If using only one AudioSource, it just plays them using PlayOneShot, so the clips need their own ramp-in.

    Code (CSharp):
    1.     public class AmbientSound: MonoBehaviour
    2.     {
    3.         public AudioClip[] clips;
    4.         public AudioSource one;
    5.         public AudioSource two;
    6.         public float overlap = 2f;
    7.         [Range(0f, 1f)] public float volume = 1f;
    8.  
    9.         private float time = 0f;
    10.         private float transition = 0f;
    11.  
    12.         // [ReadOnly]
    13.         public AudioClip playing = null;
    14.  
    15.         void Start()
    16.         {
    17.             if (one == null)
    18.                 one = GetComponentInChildren<AudioSource>();
    19.             if (two == one)
    20.                 two = null;
    21.         }
    22.  
    23.         void Update()
    24.         {
    25.             if (one == null)
    26.                 return;
    27.  
    28.             if (clips == null || clips.Length == 0)
    29.                 return;
    30.  
    31.             time += Time.deltaTime;
    32.  
    33.             // are we ready to start a clip?
    34.             if (one != null && playing == null)
    35.             {
    36.                 playing = clips[Random.Range(0, clips.Length)];
    37.                 time = 0f;
    38.                 if (two == null)
    39.                     one.PlayOneShot(playing);
    40.                 else
    41.                     { one.Stop(); one.clip = playing; one.time = 0f; one.Play(); }
    42.                 transition = Mathf.Max(0.001f, playing.length - overlap);
    43.                 //Debug.Log($"chose {playing} will transition in {transition}");
    44.             }
    45.  
    46.             // ramp in first source's volume if we can, otherwise hold
    47.             //
    48.             if (two == null)
    49.                 one.volume = volume;
    50.             else
    51.                 one.volume = volume * Mathf.InverseLerp(0f, overlap, one.time);
    52.  
    53.             // if we have two sources, ramp out second source's volume
    54.             if (two != null && two.clip != null)
    55.             {
    56.                 two.volume = volume * (1f - Mathf.InverseLerp(
    57.                     two.clip.length-overlap, two.clip.length, two.time));
    58.  
    59.                 if (two.volume <= 0f || two.time >= two.clip.length)
    60.                 {
    61.                     //Debug.Log($"stopped {two.clip}");
    62.                     two.Stop();
    63.                     two.clip = null;
    64.                 }
    65.             }
    66.  
    67.             // approaching end of clip in one, prepare for a choice
    68.             if (time >= transition)
    69.             {
    70.                 playing = null;
    71.                 //Debug.Log($"open to new choices");
    72.                 if (two != null)
    73.                 {
    74.                     Assert.IsTrue(two.clip == null);
    75.                     AudioSource swap = two;
    76.                     two = one;
    77.                     one = swap;
    78.                 }
    79.             }
    80.  
    81.         }
    82.  
     
    karderos likes this.
  10. MaxLohMusic

    MaxLohMusic

    Joined:
    Jan 17, 2022
    Posts:
    68
    It is an industry-standard practice to actually bake all the fading needed, into the audio file itself. Even if it wasn't recorded like that, it's always possible to edit it to be seamlessly loopable. You must copy/paste the "fade out" and overlay it on top of the beginning of the audio file. Then, when it loops back, it will behave exactly the same as shown in your picture.

    You will also need two separate files. 1 is the first file to play. 2 is the file to play on all subsequent loops. 1 is the normal song but stops exactly at the "fade out" tail. 2 is the same thing but the "fade out" tail is overlaid onto the beginning. (If you don't care that the beginning of the first loop will include the fade out tail, because it's very quiet and barely noticeable, then you really only need file #2).

    Then, all the game logic has to do is make sure the audio file loops completely seamlessly. There is no crossfading or special logic needed.

    Surprisingly a lot of people don't know this. They even did it totally wrong in the video game "Outward" and there was an audible glitch every time it looped. I had to make a mod to fix it.
     
  11. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,495
    Above, I agreed that it's preferable to do so. It's always possible, but not always feasible. If you're trying to emulate cross-fades between randomly selected variations, the number of pre-recorded cross-fade snippets you need to make and manage increases at an exponential rate. A-fades-into-B, A-fades-into-C, C-fades-into-B, etc.

    While a team the size of Nintendo's Super Mario Wonder may be able to stitch together all those assets and choose to do so because major parts of the gameplay is designed around adaptive music middleware, a much smaller team (or solo dev) would much rather say "here's the five different bits of audio, please pick from them randomly and cross-fade on two audio channels."