Is it possible to subscribe to sub-beat callbacks?

Hello,

We are working on an adaptive audio game project that relies on callbacks from an FMOD event, in particular FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT and FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER. All in all it works well for our purposes!

However, I’ve realized that while it’s possible to get callbacks for beats in the fashion of 21.1, 21.2, 21.3, etc., there is no evidence that we can subscribe to 21.1.1, 21.1.2, 21.1.3 and so forth. Essentially we may want to support certain game events triggering of eighth notes and sixteenth notes inside a measure.

What would be the best way to go about implementing something like that? Do we have to build something custom or is there a more elegant solution?

Any help is appreciated - big thanks!

Hi,

Currently we don’t support getting callbacks on sub-beats, but I have suggested this feature to the dev team. In the mean time something custom might be your best choice.

I see, thank you for the swift response.

Do you have any recommendations on how to go about this? My first thought is to keep track of the last time beat callback occurred in Unity and then check how much time has passed since that moment to see if we need to trigger anything else. FMOD is playing on a different thread though so it might be imprecise…

One of my teammates said we could potentially use different tempo markers to account for the sub-beats for a particular section of the song, which seems to be the only reasonable workaround.

What do you think?

No worries!

Tempo markers are your best bet. I tested checking time against the last beat and you are right it is rather imprecise.

I am working on a similar project that would require certain actions being assigned to eighth and/or fourth notes. You mentioned using tempo-markers and I was wondering on how they could be used to achieve this as I find it hard to grasp.

Thanks.

Edit: After randomly playing with FMOD, I did manage to get a similar effect by multiplying bpm by 2 and making the 4/4 an 8/8. Don’t know if this is appropriate but I was wondering if this approach is similar to what you discussed?

Hi,

If you are having an issue creating the callbacks we do have a scripting example here: Unity Integration | Scripting Examples - Timeline Calbacks

However, if your method is working could you elaborate on the issue you are experiencing?

Thanks for the response.
I have a similar script to the one you linked and it works as expected. I tried altering the signature and managed to get the quarter notes, but I might need things like triplets to also be assigned to events, which seems hard to do.

        accurateTime = dspclock / 48800;
        halfTime = dspclock / 48800 * 0.5f;
        tripletTime = dspclock / 48800 * GetIntervalLength(_bpm, 0.3f);
        quarterTime = dspclock / 48800 * 0.25f;
      
        public float GetIntervalLength(float bpm, float noteLength)
        {
            return 60f / (bpm * noteLength);
        }

This is what I’m trying as of now, but for some reason it keeps being triggered with every frame in a condition like this.

        if (lastTripletTime != Mathf.FloorToInt(tripletTime))
        {
            lastTripletTime = Mathf.FloorToInt(tripletTime);
            //TripletEventUpdated();
            Debug.Log("Triplet");
        }

Any help would be much appreciated!

Hi,

Would it be possible to get the full script DM’d to me to test on my side?

1 Like

I’m not sure if there is a limit to tempo or time signature when it comes to the API, but quickly experimenting in Studio I was able to take a 4/4 track and play it back with a time signature of 24/8 using triple the original tempo.

This gives me 24 beats to the bar, which should allow you to send whole-beat callbacks that align with crotchets, quavers, triplet crotchets, and triplet quavers in the original 4/4. If you wanted semiquavers or semiquaver triplets, it seems to allow me to set the time signature to 48/4 at 6 times the original tempo to achieve that.

2 Likes

This could work, however after seeing another post about how the beat tracking became less precise as the game progressed, I opted in for this approach using sample time and eventually thought I’d convert the normal FMOD beat tracking I’m using to that as well, but I can’t just ditch FMOD as I still need it for other audio stuff. But if I can’t get the subbeat callbacks o work this way I’ll give this approach a shot as well and see if it’s accurate enough.

Thank you for your responses.

1 Like

Hi,

Thank you for sharing your code. Having a look it may be that the if() statement may be backward.

 if (lastTripletTime != Mathf.FloorToInt(tripletTime))
// Change to 
if (Mathf.FloorToInt(tripletTime) != lastTripletTime)

Hope this helps!

1 Like

Tried it, still i gets triggered nearly every frame. I think the condition might still be doing something however since not every frame triggers the Debug.Log.

Apologies for the delayed response:

The code you shared with Connor is mostly sound - I suspect the main issue is the math being used to calculate the triplet timing.

For example, the code contains the following:

public void SampleTime(){
    //...
    tripletTime = dspclock / 48800 * GetIntervalLength(_bpm, 0.3f); 
    //...
}


public float GetIntervalLength(float bpm, float noteLength)
{
    return 60f / (bpm * noteLength);
}

The fundamental issue is that the order and type of operations in the code is incorrect - the calculation should be the following:

  1. time elapsed = total samples / sample rate
  2. beat length in seconds = 60 / BPM
  3. triplet subdivision length in seconds = beat length in seconds * 3
  4. triplet subdivisions elapsed = time elapsed / triplet subdivision length in seconds

In code, this would be:

tripletTime = (dspclock / sampleRate) / (60f / _bpm) * 3f;

The sample rate needs to match the rate at which samples are being processed. You can get this for the FMOD System with System::getSoftwareFormat - by default this should be 48000khz, not 48800khz. Additionally, the noteLength for a triplet should be 0.333… recurring, not a flat 0.3.

Thank you so much for the respone, can you please provide the usage of System::getSoftwareFormat in a C# script, I’ve been looking but still have yet to figure it out.

To return info, System::getSoftwareFormat uses the out keyword to pass variables by references. In C#, you can either create variables when calling the method:

RuntimeManager.CoreSystem.getSoftwareFormat(out int samplerate, out FMOD.SPEAKERMODE speakermode, out int numrawspeaker);

Or pass existing variables:

int samplerate, numrawspeaker;
FMOD.SPEAKERMODE speakermode;
RuntimeManager.CoreSystem.getSoftwareFormat(out samplerate, out speakermode, out numrawspeaker);