Why does audio get out of sync with multiple loops playing?

Hi,

Can anyone explain why this code sort of works, but then gets out of sync if I keep stopping and starting loops?

I have 3 audio loop sources loaded as WAVs and set to Compressed (MPEG) and Compressed in Memory and Gapless Looping ON (though Gapless looping off also seems to be gapless?).

I am clicking/tapping on 3 objects which represent each of the loops. I want to stop (or mute) them individually and then clicking again should restart that loop at the same point in time as the other playing loops continue playing.

I’ve tried using .time but that does the same thing - I read that .timeSamples is more precise so I assumed this would work. In this code, I stop the loops if they are playing, and if not I see which one is playing and set it’s timeSamples to that one and play it. If none are playing I reset timeSamples to 0 and restart (that is the only way to get them back in sync if they get out of sync).

So with this pretty simple code, what is happening with this when they do get out of sync? Is it the Debug statements? I’ve tried this in Editor and iPad and it gets out of sync on both, but usually only if I tap fast turning them on and off quickly.

Just assume melody, bass, and drums are AudioSources and my CheckObject function fires when one of the corresponding gameobjects of these is tapped (This snippet below also uses FingerGestures and is in an OnTap method for a FingerGestureRecognizer – is FingerGestures slowing this down?)

Any help getting audio loops to start at EXACTLY where the other playing one is would be appreciated! Thanks! :slight_smile:

    if( CheckObject( source.Position, melodyObject ) )
	{
		Debug.Log ("melody clicked");
		if(melody.isPlaying)
		{
			melody.Stop();
		}
		else
		{
			if(bass.isPlaying)
			{
				melody.timeSamples = bass.timeSamples;
				melody.Play();
			}
			else
			{
				if(drums.isPlaying)
				{
					melody.timeSamples = drums.timeSamples;
					melody.Play();
				}
				else
				{
					melody.timeSamples = 0;
					bass.timeSamples = 0;
					drums.timeSamples = 0;
					melody.Play();
					bass.Play();
					drums.Play();
				}
			}
		}
		
	}
	if( CheckObject( source.Position, bassObject ) )
	{
		Debug.Log ("bass clicked");
		if(bass.isPlaying)
		{
			bass.Stop();
		}
		else
		{
			if(melody.isPlaying)
			{
				bass.timeSamples = melody.timeSamples;
				bass.Play();
			}
			else
			{
				if(drums.isPlaying)
				{
					bass.timeSamples = drums.timeSamples;
					bass.Play();
				}
				else
				{
					melody.timeSamples = 0;
					bass.timeSamples = 0;
					drums.timeSamples = 0;
					melody.Play();
					bass.Play();
					drums.Play();
				}
			}
		}			
	}
	if( CheckObject( source.Position, drumsObject ) )
	{
		Debug.Log ("drums clicked");
		if(drums.isPlaying)
		{
			drums.Stop();
		}
		else
		{
			if(bass.isPlaying)
			{
				drums.timeSamples = bass.timeSamples;
				drums.Play();
			}
			else
			{
				if(melody.isPlaying)
				{
					drums.timeSamples = melody.timeSamples;
					drums.Play();
				}
				else
				{
					melody.timeSamples = 0;
					bass.timeSamples = 0;
					drums.timeSamples = 0;
					melody.Play();
					bass.Play();
					drums.Play();
				}
			}
		}

Maybe latency variance on play? A workaround could be just to mute the audio sources that should not be heard and let them keep playing.

Thanks for responding. How can I get around latency variance on play? Is there any way to programmatically determine that so I can add time to the amount before playing and have it be device independent? The muting is a good idea changing volumes, however I need to be able to have far more than 3 audio tracks that are playable (but only 3 at a time) so that means I couldn't have 30 playing and 27 muted performance-wise so that solution wont help unfortunately.

I just tried adding this to see what the system thinks the .timeSamples are for each audio source. But what is weird is they sound out of sync when the debug prints that they are in sync? void Start() { melody.Play(); bass.Play(); drums.Play(); InvokeRepeating("PrintSamples", 2F, 2F); } void PrintSamples() { Debug.Log("melody = "+ melody.timeSamples + "\n bass = " +bass.timeSamples + "\n drums = "+ drums.timeSamples+"\n -----------"); }

As I write in my answer, try not compressing the wavs! And are you 100% sure each track is of the same, sample accurate, length?

I just noticed something else. This whole out of sync problem seems to only occur (on both PC, Mac & iPad) whenever the AudioSources are set to "Compressed (MPEG)" if I set them all to "Native (WAV)" this sync problem goes away - but that means that memory usage is higher - Anyone have any thoughts on all of this or what the best way forward is? thanks :)

1 Answer

1

If you want sample level accuracy, do not compress.

Your code should work then. If not, you could try an offset:

sourceToSync.timeSamples = (playingSource.timeSamples+offset)%playingSource.clip.samples;
sourceToSync.Play(offset);

I use this a lot to start playing an audioSource on the beat! for example, at 60 bpm, 44.1 khz, one beat is 44100 samples:

beatSamples = 44100*(60/bpm);

offset = beatSamples - playingSource.timeSamples%beatSamples;
sourceToPlayOnTheNextBeat.timeSamples = (playingSource.timeSamples+offset)%playingSource.clip.samples;
 sourceToPlayOnTheNextBeat.Play(offset);

I have 49 10s loops (mono) in my iPad app, all set to Load into Memory. No problem, even on an iPad 1... It depends how much ram you need for the rest of your game, I guess. I haven't tried stream from disc, because my app is an instrument and I need quick access to all 49 clips!

Great. Thanks for mentioneing that- it's good to know that will work even on iPad 1!

You're welcome. Check my posts here and on the forum in case you need help audio wise: I was a total noob 6 months ago, and am now a proud noob+! Unity 3.5 is great for audio if you know how to work with it. The new GetData and SetData functions, in particular, can be used effectively to solve a lot of synching problems.