I’m trying to accomplish something that I imagine is pretty simple, but I’m still lacking in some of the fundamental concepts, and am struggling as a result.
Im building a piano app which uses a high resolution timer to trigger some randomised piano melodies in key. Ive accomplished this and it works as I want it to. I have some settings to change number of bars, ‘beats’ or ‘divisions’ per bar and BPM, which I use to calculate the timer intervals.
What I’m now trying to do is create a second lot of controls to be able to generate parts for the bass register that the left hand would play. To do this I need a separate high resolution timer, given the individual timer intervals necessary.
Is there any way to have 2 high resolution timers running concurrently the way you can with the standard ones?
I don’t think there is and so assume the solution would be to have a separate class inherit from the high resolution timer, and trigger the other notes from there. The problem I see with this approach is that I only want one one MidiKeyboardComponent, and therefore one MidiKeyboardState and SynthAudioSource, given that the keyboard component will display the notes as they’re played (from both timers).
How can I call note on function on a single object from 2 separate classes? I’m wondering if a static function might help.
To keep things in sync, you might want to use the audio callback as the source of time. Ie. You know the passage of time by how many samples come through the callback at a given sample rate.
cpr is right. you should actually rewrite what the first timer was doing to just be stuff on the audio callback (processBlock). for example you could have a member variable called “index” and increment it by 1 for each sample like this:
int idx = 0;
// in processBlock
for(auto s = 0; s < numSamples; ++s)
++idx;
now idx gets increased each block. what does it mean when idx reaches sampleRate? it means 1sec has passed. let’s say that your event occurs every 2 secounds. that would look like this:
// in prepareToPlay
length = int(sampleRate * 2.);
// in processBlock
for(auto s = 0; s < numSamples; ++s)
{
++idx;
if(idx >= length)
{
// code that triggers the event.
idx = 0;
}
}
that is how you work with time in the audio callback. now you can just let as many index values run in parallel as you want, with whatever lengths you want. it’s more cpu efficient than high resolution timer and even more precise. there is literally no downside to this
yeah, or splitting the block into steps of 32 or 64 samples to get a good compromise between being accurate and being efficient. making it directly depend on numSamples can make inconsistent behaviour on different blocksizes
To process something with a different block size (which is related to running a timer using the sample count) I normally check how many samples are needed up to the next trigger (which would be a full block or the need for a timer callback). This could be something like this:
Be aware that this isn’t very pretty code which shouldn’t be used as is but I hope it clarifies the approach. It allows to work as block-wise as possible without missing any triggers if the period is shorter than numSamples. One useful addition left out for simplicity would be to also pass the sample offset in triggerCallback.