Play(offsetInSamples) acuracy - still cannot get it to work perfectly

Hi to all!

Hi to all! Tried posting this on the forum, and not getting any love… . I still have the following issue: I am trying to play an audioclip exactly at sample 0 of another looping one. This works fine in an empty scene, but not in my application. To try to narrow down the problem, I made the following test, which reveals (on my machine at least) that Play(offsetInSamples) will be inaccurate if there is as much as a Print in the same function… Details of my test are below, any help would be greatly appreciated!

The scene contains a camera, and 3 GameObjects with 1 AudioSource each : metronome, met2 and met3. They all have the same AudioClip, which is a .25s click (precisely 11025 samples at 44100 hz). All sources set to loop, metronome audio source to PlayOnAwake.

Metronome has the following script attached to it:

var met2 : GameObject;
var met3 : GameObject;

private var offset : int;

function Update(){
     if(Input.GetKeyDown("2")){
          offset = audio.clip.samples - audio.timeSamples;
          if(met2.audio.isPlaying == false){
               met2.audio.Play(offset);
               print("anything"); //The Culprit
          }
          else{
               met2.audio.Stop();
          }
     }

     if(Input.GetKeyDown("3")){
          offset = audio.clip.samples-audio.timeSamples;
          if(met3.audio.isPlaying == false){
               met3.audio.Play(offset);
          }
          else{
               met3.audio.Stop();
          }
     }
}

When I hit 3, sync is perfect, but if I hit 2, the metronomes are out of sync at least 10% of the times, and once in a while seriously out of sync (50ms) a few times in a row. It seems printing a string is enough to trigger this inaccuracy…

I’m using Unity 3.5, Windows XP, audio settings to best latency, decompress on load, file is a wav (2d sound, but the same applies to 3d sounds).

Solved. To get perfect sync, all the audio components need to be set to AudioVelocityUpdateMode.Fixed. Then, the before playing the 2nd clip(the one to synchronize), yield WaitForFixedUpdate and calculate the offset in samples. I spent so much time going crazy with this, I’ll gladly post detailed steps if asked. I’m now firing different sequencers (tested up to 8 simultaneously), in sync or out of sync, at different bpm’s, of varying lengths. Relief!

Hi and sorry for the wait!

As before, the following script is attached to a gameObject containing an AudioSource which loops a click.

The scene also contains met2, another similar object but without this script attached.

Pressing space will start met2’s audio precisely when the click is heard.

yield WaitForEndOfFrame ensures nothing else is done at the same time, which isn’t at all what it was meant for but works.

AudioVelocityUpdateMode might have an impact, I’m not 100% sure. It is by default set to Dynamic on objects without rigidbodies, and since I don’t use rigidbodies much these days… Try Dynamic if you’re having trouble. And do let me know of your precise use case if you’re still struggling!

var met2 : GameObject;

private var offset : int;

function Update(){
     if(Input.GetKeyDown("space")){
         
          if(met2.audio.isPlaying == false){
               yield WaitForEndOfFrame;
               offset = audio.clip.samples - audio.timeSamples;
               met2.audio.Play(offset);
          }
          else{
               met2.audio.Stop();
          }
     }
}

This sounds very promising. I’m still a bit green in coding for U3D though . . .would you mind posting your final code? I’m just not sure where or how you are setting AudioVelocityUpdateMode.Fixed, or where yield WaitForFixedUpdate is being called.

Thanks in advance for the help!

Thanks gregzo, it was worth the wait because it worked! I’ve only tried about five separate clips so far, but they all seem to be synced and holding steady, after five minutes of continuous playback.

I actually only used the yield WaitForEndOfFrame statement . . . still wasn’t sure what to do with AudioVelocityUpdateMode, or where to put it. If you could expound, that would be awesome.

I guess it’s only fair for me to post my code. My ‘usecase’ is to set up an environment consisting of any number of zones/objects that trigger individual tracks – that is discrete musical segments, e.g. bass, drums, etc. So obviously, those must all be in sync with each other.

In looking for a somewhat “elegant” and reusable solution, I’ve arrived at an approach in which there is a single “guide” track (a metronome, effectively), which is global, and is attached to my FPC. It starts running when the first zone/object is triggered. Each zone/object has its own audio source, and when triggered, polls the metronome for its time position in samples, and if they’re not the same (which they usually wouldn’t be), sets itself to the metronomes position value.

And that’s it. I don’t know if this is the “standard” way to do it or not – I haven’t seen any definitive method posted anywhere – but it seems to work. Here’s the code:

audioControl.js (attached to the FPC)

var started = false;
var guide : AudioClip;
audio.clip = guide;

function syncAudio(loop){
	loop.timeSamples = audio.timeSamples;
    // for some reason, the first object to call this function never wants
    // to fall into sync using the above statment, so we must reiterate below.
    // don't ask me why!
	if(loop.timeSamples!=audio.timeSamples){
		yield WaitForEndOfFrame;
		loop.timeSamples = audio.timeSamples;
	}
}

=============================

TriggerScript.js (attached to every object that has a triggerable audio source. Note that it references variables/functions in the audioControl script)

var target : Collider;
var mySound : AudioClip;

function OnTriggerEnter(cubeTrigger : Collider){
     if (cubeTrigger == target){
          audio.clip = mySound;
          var fpc : GameObject;
          // POV is the name of my FPC object
          fpc = GameObject.Find("POV");
          var ctrl =  fpc.GetComponent(audioControl);
          if (!audio.isPlaying){			
               audio.Play();
               if(ctrl.started==false){
                    fpc.audio.Play();
                    ctrl.started = true;
               }
               else{
                   ctrl.syncAudio(this.audio);
	       }
          }     
     }
}

====================

Thanks again for the help, gregzo, much appreciated it. If you see any improvements that could/should be made on the above, I’d gladly welcome your input!

Cheers,

==rr

Be careful with your use of timeSamples. It is a member variable of an AudioSource, not of a clip. You should check audio.timeSamples… And use #pragma strict at the top of all your scripts, it will forbid this kind of misuse. Also, yield WaitForEndOfFrame somehow doesn’t always give the best results for me (I’m still using Unity 3.5 RC1). It’s always worth trying both with and without…

About AudioVelocityUpdateMode, I can’t tell you much more since I don’t use it at all in my current projects. No time to do some precise testing, unfortunately… I’m not a pro at all, just tried everything I could think of and stuck to the most functional solution!

Good luck and don’t hesitate to ask more. And do practise strict typing, I assure you it will help a lot!