Musical beat not in perfect time with tempo. WaitForSeconds not accurate.

My app plays musical chords progressions. Each chord is played for a certain number of beats and behind the chords is a simple metronome beat.

Each of my play events, i.e. a beat audiosource play or a chord audiosource play, is played in order and “yield return new WaitForSeconds()” is used to wait until the next event. I do it like this:

    public IEnumerator play()
    {
        beat1.Stop();
        otherBeats.Stop();
        float timeBetweenBeats = (60f / (float)bpm);

        List<playEvent> eventList = new List<playEvent>();
        float totalChordTime = 0f;
        for (int i =0; i <allSlots.Count;i++)
        {    
            float timeForThisChord = numberOfBeats(allSlots_.getNoteLength()) * timeBetweenBeats;_

playEvent chordEvent = new playEvent();
chordEvent.beatNo = -1;
chordEvent.chordIndex = i;
chordEvent.chordOrBeat = true;
chordEvent.timeToPlay = Time.time + totalChordTime;
totalChordTime += timeForThisChord;

eventList.Add(chordEvent);

}

float beatTime = 0;
int b = 0;
while (beatTime<totalChordTime)
{
playEvent beat = new playEvent();
beat.beatNo = b;
b++;
beat.chordIndex = -1;
beat.chordOrBeat = false;
beat.timeToPlay = Time.time + beatTime;
beatTime +=timeBetweenBeats;
eventList.Add(beat);
}

eventList.Sort();

float timeToStop = Time.time + totalChordTime;
for (int i = 0; i< eventList.Count;i++)
{
if (Time.time < eventList*.timeToPlay)*
{
yield return new WaitForSeconds(eventList*.timeToPlay-Time.time);*
}

if (!eventList*.chordOrBeat)*
{
if (eventList*.beatNo%beatsInBar ==0)*
{
beat1.Play();
}
else
{
otherBeats.Play();
}
}
else
{
stopAllChords();
allSlots[eventList*.chordIndex].playChord();*
audioSliders[eventList.chordIndex].GetComponent().moveSlider(Time.time, Time.time + numberOfBeats(allSlots[eventList_.chordIndex].getNoteLength()) * timeBetweenBeats);
}_

if (i== eventList.Count-1) {
yield return new WaitForSeconds(numberOfBeats(allSlots[allSlots.Count - 1].getNoteLength()) * timeBetweenBeats);
}

}

stopAllChords();
resetPlaySliders();
}
However, after debugging and printing lots of parameters, I have found that it is the “WaitForSeconds” function that does not wait for the exact amount of time required. I have a screenshot of some of my logs to the terminal:
[123142-time-logs.jpg|123142]

For example the logs for the first beat show what time the function is aiming for (14.46284) which occurs 0.6s from the point the log was made, the time before the WaitForSeconds occurs and the time after it. The difference between the two times is 0.614 seconds, which may not sounds like a lot but is definitely noticeable. The music sounds a little bit all over the place and out of time.
What can do i do so that the WaitForSeconds is accurate at least to 2 or 3 decimal places?
Thanks

Umm, this is what happens when people don’t understand the tools they are using.
I’m not going to go into too technical explanations for the why, but basically what happens is :

  • The WaitForSecond DOES NOT wait for precisely a second.
  • The method sends a signal to the system, which takes another one or multiple tasks to do while the waiting method is queued up.
  • Once a MINIMUM of a second is up, then it goes back to the queued method.
  • This means that it will alsmost never be exactly a second.
  • What you are doing is the wrong approach many beginners try to use.
  • What you SHOULD be doing is find a way to use event based checking to stop/play the beats.

Using event based methods will allow you to react to whichever event you need to coordinate your beats. For example :

  • Playing a beat after another beat? Set an event on beat ending playing

Event based methods react to other events, so you can chain them infinitely which, for a musical game/application is a much better solution than unity coroutines which add a lot of overhead.

Edit :
I wasn’t sure if i should add this but here it is :

  • A coroutine does work in between frames
  • Yields returns control to Unity so it goes on the next frame
  • You are basically waiting waiting for n time AND the next frame
  • This makes your code heavily dependend on the frame rate

just as @Madks13 wrote, WaitForSeconds is not designed to be accurate, it effectively waits for Time+Random.Range(0, FrameTime).

What you can try is to use WaitForFixedUpdate. FixedUpdate ticks independently of the framerate (except for some weird cases where fps drops too low, that is lesser than 4 fps, than FixedUpdates may be discarded).

If you don’t use physics, you can even set Fixed update rate equal to your metronome rate.