Metronome with groove / offset

Hi all,

I’m currently working on a step sequencer audio application based on a metronome tempo.
Some audio samplers are listening the metronome callbacks (triggered at each beat) and the listener classes will play a sample sound at each call.

I developed the metronome (sample accurate) and it work well. I would like to add some groove / offset option on it in order to offset the trigger of the sound a little before / after the beat.

Having the same offset on all the beat is straightforward but how could I handle different offsets in a sequence of 8 beats?

I’m totally lost on how to architecture it and I didn’t find any exemple of it.

Let say the following step sequence:

[1_2_3_4_5_6_7_8]

and with some offsets:

[0.5_2_3.1_4_4.8_6_7_8.9]

Any advice on how to handle it and the best practice to use?

Hmm… Kind of hard to say without knowing how you implemented the regular beat in the first place.

Yes sorry I thought saying “sample accurate” was enough, my bad.

I calculate the sample per beat using the formula: samplerPerBeat = ((60.0f / bpm) * sampleRate) / beatPerBar

Let’s say I’m working with 4 beat per bar.

Also the Metronome class inherits from AudioIODeviceCallback and on each call of audioDeviceIOCallback I calculate the the current beat like this:

void MyMetronomeClass::audioDeviceIOCallback(const float** inputChannelData,
                                             int totalNumInputChannels,
                                             float** outputChannelData,
                                             int totalNumOutputChannels,
                                             int numSamples) {
    sampleCount += bufferSize;
    remainingSamples = sampleCount % samplesPerBeat;

    if ((remainingSamples + numSamples) < samplesPerBeat) { 
      return;
    }

    currentBeat++;
    if (currentBeat == beatsPerBar) {
        currentBeat = 0;
    }

    triggerListnerCall(); 
}

Nothing fancy but it works well.
I’m wondering how I could add offbeat or swing on it.
Maybe should I store an array of 4 keeping the offset for each beat and use this value on the line

const auto offset = offset[currentBeat]; // Could be positive or negative
if ((remainingSamples + numSamples + offset) < samplesPerBeat) { 
      return;
}

I just thinking of it right now :thinking:

It looks like you are already separating your metronome logic from your sample-triggering logic, yes?

What I would suggest is keeping the metronome always consistent - only the sampler needs to know about its offset.

I would make the metronome always store the sample index in the buffer of its “next click”, that way you can do this:

for offsets that are just behind a beat:

sampleIndexOfSamplerStart = metronome.nextBeat() + sampleOffset;

or for offsets that are just ahead of a beat:

sampleIndexOfSamplerStart = metronome.nextBeat() - sampleOffset;

Thanks @benvining , yes the metronome logic is totally separated from the the other class.
The others classes, that used the metronome, are just listeners and received a notification when triggerListnerCall is called.
I’m sorry but I didn’t understand what was the metronome.nextBeat(). What is supposed to return this method?
Also the sampleIndexOfSamplerStart = metronome.nextBeat() + sampleOffset; code is inside the class that listens to the metronome?

There are two options for what metronome.nextBeat() returns:

  • the sample # within the current process block where the next beat/metronome click occurs
  • the # of samples left until the next beat/tick

depending on how you’d rather keep track of things.

But for your current setup, don’t you need to implement some sort of mechanic like this, so that the listeners know where in the processBlock buffer the beats fall?

Oh I see what you mean and understand your proposition. In fact (I will edit it in the first message) the triggerListnerCall listener play a sample sound. It’s an audio application (not a plugin). I’ve several audioPlayer and they will play a sample when they got a call from the listeners. The only part of the code where I use samples calculation is inside the metronome.

So I guess your formula should be used inside 1) the metronome 2) (better option) a middleware class listening the metronome and adding the offset and will trigger some call that will be listened by the final class to launch the sample sounds.