Search Unity

Audio BUG: Looping audio pops (C#)

Discussion in 'Audio & Video' started by JonKelliher, Jun 26, 2018.

  1. JonKelliher

    JonKelliher

    Joined:
    Apr 25, 2017
    Posts:
    6
    I have been trying to get this to work for a little while and I am extremely aware of the limitations of looping manually in Unity.

    The idea is that using a double buffer you would be able to transition seamlessly between audio clips, be it the same one, or others that are randomised or sequenced. For this example it will just be for the same audio clip.

    My issue lies in the fact that I've got it working to the point that each loop is about as seamless as it's going to get apart from the first one. For some reason there is still a pop. If anyone has any ideas as to why this is and how it can be solved I'm all ears.

    Note that the variables such as AudioClips and AudioSources have been hooked up manually in the inspector.

    Here is a demonstration of the result. At about 0:05 the audio starts and plays for 4 seconds. At just before 0:09 the audio loops and though it is very small you will be able to hear a small pop. However, at just before 0:13 and other loops there is no pop.




    Here is the code in C#:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MusicLoopScript : MonoBehaviour {
    6.  
    7.     public AudioSource audioSource1;
    8.     public AudioSource audioSource2;
    9.  
    10.     private bool isCurrentAudioSource = true;
    11.     public AudioSource currentAudioSource { get { if (isCurrentAudioSource) { return audioSource1; } else { return audioSource2; } } }
    12.     public AudioSource otherAudioSource { get { if (isCurrentAudioSource) { return audioSource2; } else { return audioSource1; } } }
    13.  
    14.     const float loadingTimeBeforeClipEnds = 2f;
    15.  
    16.     double dspTimeOfNextEvent;
    17.     double timeDelay;
    18.  
    19.     bool isLooping = false;
    20.  
    21.  
    22.  
    23.     public void Update()
    24.     {
    25.         if (!currentAudioSource.isPlaying)
    26.             isLooping = false;
    27.  
    28.  
    29.         if (!isLooping
    30.            && currentAudioSource.isPlaying
    31.            && currentAudioSource.time >= currentAudioSource.clip.length - loadingTimeBeforeClipEnds)
    32.         {
    33.             isLooping = true;
    34.  
    35.             StartCoroutine(Loop());
    36.             //Loop();
    37.         }
    38.         else
    39.             isLooping = false;
    40.     }
    41.  
    42.  
    43.     public IEnumerator Loop()
    44.     {
    45.         //otherAudioSource.PlayDelayed(timeDelay);  //
    46.         otherAudioSource.PlayScheduled(dspTimeOfNextEvent);
    47.  
    48.         dspTimeOfNextEvent = dspTimeOfNextEvent + otherAudioSource.clip.length;
    49.  
    50.         timeDelay = dspTimeOfNextEvent - AudioSettings.dspTime;
    51.  
    52.         isCurrentAudioSource = !isCurrentAudioSource;
    53.  
    54.         yield return new WaitForSecondsRealtime((float)timeDelay);
    55.  
    56.         //otherAudioSource.Stop();  //
    57.  
    58.         isLooping = false;
    59.     }
    60.  
    61.  
    62.  
    63.     //ui button handler
    64.     public void StartLoop()
    65.     {
    66.         timeDelay = 1f;
    67.         dspTimeOfNextEvent = AudioSettings.dspTime + timeDelay;
    68.  
    69.         currentAudioSource.PlayScheduled(dspTimeOfNextEvent);
    70.  
    71.         dspTimeOfNextEvent = dspTimeOfNextEvent + currentAudioSource.clip.length;
    72.     }
    73. }
    74.  
     
    Last edited: Jun 26, 2018
  2. JLF

    JLF

    Joined:
    Feb 25, 2013
    Posts:
    140
    In the UI Button Handler, should this not be: currentAudioSource.PlayScheduled(dspTimeOfNextEvent); which is the AudioSettings.dspTime + timeDelay that you set up in the previous line.

    By my guess timeDelay at that point in the Script would be 1f and since PlayScheduled takes a timeline value, it would just start immediately when the button is pressed. This might be where you're getting timeline variance until the system resets on the first loop.
     
  3. JonKelliher

    JonKelliher

    Joined:
    Apr 25, 2017
    Posts:
    6
    You are absolutely right in that it should be this instead:

    Code (CSharp):
    1.  //ui button handler
    2.     public void StartLoop()
    3.     {
    4.         timeDelay = 1f;
    5.         dspTimeOfNextEvent = AudioSettings.dspTime + timeDelay;
    6.         currentAudioSource.PlayScheduled(dspTimeOfNextEvent);
    7.         dspTimeOfNextEvent = dspTimeOfNextEvent + currentAudioSource.clip.length;
    8.     }
    That being said the issue still remains the same after these changes.
     
  4. JLF

    JLF

    Joined:
    Feb 25, 2013
    Posts:
    140
    Ah rats, I thought that would do it.

    So a couple of other things that could be happening here then:

    1. Stopping an audio clip part-way through will usually create a pop anyway, unless you stop it exactly in time with a signal zero crossing point, which you may have already set up using fades into whatever file you're using for this. The example sinewave in the video is incredibly susceptible to this. Just stopping the video will create a pop. So first of all, are your audio files creating the pop, not a timing issue. What should be happening? are the files getting cut off at the end for example? I'm not assuming this is the case but it's the simplest cause.

    2. The 2nd thing that could be happening is the use of clip.Length, which is a float not a double. Floats generally aren't accurate enough, even for keeping musical time let alone sample precise timings. Ive made similar systems to this in the past, using a setup quite similar to your, that have worked really well, however I've always used the following calculate a really accurate double value clip length value:

    Code (CSharp):
    1. double clipLength = (double)clip.samples / clip.frequency;
    Hope that helps!
     
  5. JonKelliher

    JonKelliher

    Joined:
    Apr 25, 2017
    Posts:
    6

    Yeah, I was bummed when it didn't work :/

    1. I see what you mean, however, the looping point is seamless - tested using unity's AudioSource Loop option. I would also agree that this was the issue if it weren't for the fact that after the first loop, the rest of the loops don't cause a pop.

    2. That could very well be an issue. I will have to try that out and see if it makes much of a difference, though I'm sure it will. But once again, it seems strange that the pop is only happening on the first loop and not the following loops. To me it seems like there is some sort of very slight delay when playing a new AudioSource or AudioClip for the first time, despite making sure it's PreLoaded when loading the scene. Some colleagues of mine have experienced the same thing in the past and mentioned that it may perhaps be a streaming issue. But it would be nice to get some affirmation that that is the case or not.

    In the mean time, my workaround is baking in an Audio Tail to the end of the loop and specifying a loop point to start the next AudioClip while the Audio Tail plays until the track ends. It's a more elegant method, however, it would still be nice to have this issue solved as an alternative.
     
  6. r618

    r618

    Joined:
    Jan 19, 2009
    Posts:
    1,305
    Do you have Best latency selected in Audio Settings ?
    it might be worth testing this in an actual build, not just editor btw (I would do IL2CPP build, on latest (4.6) runtime for this as well)
     
  7. JonKelliher

    JonKelliher

    Joined:
    Apr 25, 2017
    Posts:
    6
    Tested it with Best Latency selected in Audio Settings, but didn't fix the issue.

    Also tested it in a Build and it still happens.
     
  8. r618

    r618

    Joined:
    Jan 19, 2009
    Posts:
    1,305
    yeah.. good to know at least
     
  9. JonKelliher

    JonKelliher

    Joined:
    Apr 25, 2017
    Posts:
    6
    Indeed! Thanks for the suggestions though :)
     
  10. Docaroo

    Docaroo

    Joined:
    Nov 7, 2017
    Posts:
    82
    This is difficult from the get-go and you've said yourself you know the limitations of manual looping. The problem is that unless the samples are synced up to the EXACT sample point itself you will not get seamless looping.

    In order to loop seamlessly the audio needs to be loaded into a buffer and ready to go when the first loop stops at the exact sample point it ends... this will require you to handle all this in-code I think.

    Is there a reason you can't use Unity's build in loop parameter?? Or middleware such as FMOD to handle the looping? This is going to be tricky to do manually.

    Plus, in a fully built game with loads of other stuff using the CPU you will probably find that the loop pops more and more often unless it's correctly handled by buffer sample timings...
     
  11. JonKelliher

    JonKelliher

    Joined:
    Apr 25, 2017
    Posts:
    6
    Indeed, writing a sample-based audio system seems to be the best option for this case.
    As for the reason behind doing it this way is due to having randomly looped audio clips. And the reason for not using middleware is due to license fees. That being said I still use middleware, I'm simply doing this to create a simpler audio management system with mostly fundamental functions.

    My fix for this is currently is looping with audio tails at the end of each track - it works well, however, with regards to this bug I have also found that there is a bug to do with AudioSource.isPlaying which is true when PlayScheduled() has been triggered, but hasn't started the audio clip yet. Have submitted a bug report for that one.
     
  12. karstenv

    karstenv

    Joined:
    Jan 12, 2014
    Posts:
    81
    Since this is an old post I just like to add something for future google searches that land here.

    I had the same problem but remembered an older project that didn't have this problem and I managed to find and load this old project.

    Tested it in my new project and the solution still works.
    I converted all the looping audio clips to OggVobis format and the audio flies becomes mush more precise in their length and thereby avoid pops and clicks.

    It might not do any difference in the original post and I cannot see what format he used.
    But just wanted to share my findings.
     
    CaptN_Coding likes this.
  13. CaptN_Coding

    CaptN_Coding

    Joined:
    Nov 6, 2019
    Posts:
    4
    Thanks for this tip, worked perfectly !
     
    karstenv likes this.