Rendering a tick?

With the correct values passed in updateClock(), this Metronome class will by definition be perfectly sample-accurate since it places the notes at exactly the right positions within the audio output block, without using a timer.

Precisely why I was intrigued :slight_smile: That code cleared up my questions, I knew there had to be something I was missing… updating advanceClock was rather trivial but I noticed something… it seems to work fine up to a bpm of 519. Once you go above that (520+) it jumps down, and then increases again…

Any idea about that? Cheers, and much thanks!

Did you turn on note stealing? If not, then if your sample length is longer than the beat interval it will skip.

Doh, swore I had done that but I must have done one too many cmd+z’s! Works great, I tested it against some other metro’s (ableton…etc) and there’s absolutely no drift! Now I just need to figure out how to count beats in there so I can tell it to play one sound file on beat 1, and another one all other beats in the bar… Was easy when I knew i was incrementing 96 note ticks, because i could just have a tickCounter count every 96 ticks, and each time increment and wrap a beatCounter around the number of 1/4 note beats in the bar and just check that before deciding which noteOn to call…

To do that correctly you will have to add some members

// call once
void setupBar (int startingBeat, int beatsPerBar);

int m_currentBeat; // range [0, beatsPerBar)
int m_beatsPerBar;

call setupBar() once, and then inside getNextAudioBlock() increment m_currentBeat and mod it with m_beatsPerBar.

Yep, I had actually done something just like that this morning, but had some strange results. I changed it to your proposed naming convention for consistancy. Basically when the user enables the metro and presses play on the transport, I call setupBar(0, 4). Then in getNextAudioBlock(), after advanceClock and phase adjustment, I do your pos calculation, and then test m_currentBeat to choose which sound to play, and finally, increment m_currentBeat and modulo it against m_beatsPerBar

Beat “0” should be my high tick, and the rest (1-3 inclusive) should be my low tick. This is working right, although the ordering is a little strange. Printing m_currentBeat heres what its doing…

setting up metronome...
2 <--starting on the wrong beat for some reason?
1
0 from here to...
3
2
1
0
3
2
1
0
3
2
1 ...here it seems to be fine
0 then we get an extra 0 beats
0 
3
2
1
...
// Set notes in midi buffer
		for (;;beat += 1)
		{
			// Calc beat pos
			int pos = static_cast <int> (beat * samplesPerBeat);
			if (pos < numSamples)
			{
				if (m_currentBeat == 0){
					midi.addEvent(MidiMessage::noteOn (1, 60, 1.f), pos);
				}else{
					midi.addEvent(MidiMessage::noteOn (1, 61, 1.f), pos);
				}
				std::cout<<m_currentBeat << std::endl;
			}
			else
			{
				break;
			}
		}
	}
	
	samplerSynth.renderNextBlock (*bufferToFill.buffer, midi, 0, bufferToFill.numSamples);
	 	
	m_currentBeat++;
	m_currentBeat = m_currentBeat % m_beatsPerBar;

m_currentBeat has to be incremented mod beatsPerBar inside the for() loop, every time “pos < numSamples” is true.

Remember that because the size of the audio block, and tempo, can be almost any value, Metronome::getNextAudioBlock() has to handle all of 3 cases:

  • no beats start in the audio block
  • one beat starts in the audio block
  • more than one beat starts in the audio block

ah! makes sense. Working great now-- added a few more simple logic steps so it resets / runs when the transport is goes into “playing”, and only sounds if the metro buttons has been enabled. Thanks for taking the time to help!

Hey Vinn (and all), just thought I’d mention in case others have implemented your great methods here that advanceClock (which I think you called updateClock by mistake in the quote above) should actually be called at the end of the getNextAudioBlock, not the beginning as originally said so that the initial beat happens right away, as opposed to being one beat in the future…