- Home /
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).
Answer by gregzo · Jan 13, 2012 at 09:49 AM
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!
There is no documentation on AudioVelocityUpdate$$anonymous$$ode.Fixed - I tried using it yesterday but cannot figure out how to use it. Advice?
Answer by gregzo · Feb 21, 2012 at 04:22 PM
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();
}
}
}
Answer by memetic-arts · Feb 17, 2012 at 06:46 AM
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!
Answer by memetic-arts · Feb 22, 2012 at 03:28 PM
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
Hi! One problem I see, unrelated to audio, is your getComponent call. If you know you will need the component, you should cache a reference to it ins$$anonymous$$d of getting it everytime it is required! Second, I notice you are adjusting the timeSamples of your loop after playing. Better to know first at which sample you want to start playing, and when(using play(offset in samples)).
re: caching ref to getComponent -- sounds like a good idea, but I have no idea how to do that . . . please enlighten me! ;-)
re: setting the start point in the loop before invoking Play() -- d'oh!! you're absolutely right!
thanks again (and again!)!!
Answer by gregzo · Feb 22, 2012 at 10:34 PM
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!
gregzo, thanks for the tips, really appreciate that. But since, as you implied, #pragma strict now causes my script to fail, I'm not sure how to pass a reference to the newly-triggered AudioSource to the metronome script, e.g. how to properly compare timeSamples. Any thoughts on that?
Thanks again . . .
and just to clarify, in the syncAudio function, the argument "loop" is intended to be a reference to the Audio Source, which I thought I was passing from onTriggerEnter as this.audio. Obviously that's not working, so will continue to experiment.
Ha, figured it out, I think. $$anonymous$$ight not be the most efficient in terms of performance, ultimately, but works for now. Ins$$anonymous$$d of trying to pass a reference to the AudioSource component itself, I'm passing the name of the GameObject to which it is attached. Then, in the controller script, am just using Find to access the Audio Source through the Game Object.
So, from the trigger script:
ctrl.syncAudio(this.gameObject.name);
And, in the control script, where objName is the argument/value passed from the function call in the trigger script:
var zObj : GameObject;
zObj = GameObject.Find(objName);
zObj.audio.timeSamples = audio.timeSamples;
$$anonymous$$ake sense?
Your answer
Follow this Question
Related Questions
How to send audio to a specific channel? 1 Answer
How to make two audio tracks play in sync? 2 Answers
Screenshot Movie with Audio Capture 1 Answer
Music Visualiser Troubles 0 Answers
Synchronising Audio 1 Answer