a short class for
synchronizing events
and audio

syncing events with audio


by Charles Hinshaw
Jun 6, 2013

Pacing is critical in Static Sky, so while it isn’t a rhythm game, we’re doing a lot to synchronize events and audio. One of the tools we’re using is a super simple class called Beat, which synchronizes events using the number of samples the audio system processes. I thought it might be nice to share the code with you.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Beat : Singleton
{
    public enum Timing { Measure = 16, Half = 8, Quarter = 4, Eighth = 2, Sixteenth };
    public double bpm = 120.0;
    
    private int tickCounter;    
    private double nextTick = 0.0;
    
public override void Awake()
{
    base.Awake();
}
    
void Start()
{
    tickCounter = 16;
    double time = AudioSettings.dspTime;
    nextTick = time + (60.0f / bpm) * 4.0f; // pre-roll 4 beats
}
    
void Update()
{
    double time = AudioSettings.dspTime;
    if (time > nextTick)
    {
        nextTick = time + ((60.0 / bpm) / 4); // sixteenths
        if (++tickCounter > 16)
            tickCounter = 1;
    }
}
    
public void Sync(System.Action callback, Beat.Timing timing = Beat.Timing.Measure)
{
    StartCoroutine(YieldForSync(callback, timing));
}
	
IEnumerator YieldForSync(System.Action callback, Beat.Timing timing)
{
    int startCount = tickCounter;
    bool isStartTick = true;
    bool waiting = true;
    while (waiting)
    {
        isStartTick = (isStartTick && startCount == tickCounter);
	if (isStartTick)
	    yield return false;
	isStartTick = false;
	if (timing == Timing.Sixteenth || tickCounter % (int)timing == 1)
	    waiting = false;
	else
	    yield return false;		
    }
    callback(tickCounter);
}
}

The class uses the singleton pattern that we detailed in the Unity Singletons blog post for a static getter. Using it is simple. If, for example, you wanted DoSomething() to be called when the next quarter note triggers, you would simply call:

Beat.get.Sync(DoSomething, Beat.Timing.Quarter);

That’s all there is to it. Keep in mind that it is a class ripped out of a game that is very much in production and it hasn’t been tested thoroughly. It may not meet your needs, our needs or anybody’s needs. That said, you’re free to use it however you see fit — have fun.

Add Your Comment