Sure!
Take the following hypothetical example:
5.do { |i|
TempoClock.sched(i, { ~playNote.() });
}
Here we want to play 5 notes with a musical time difference of 1 beat each. Every loop iteration takes some time to process in the language. (Imagine that ~playNote is actually a pretty complex function.)
If we use the current real system time as our time base, the delta between schedules notes would be slightly different and we would not get consistent and reliable musical timing.
The current logical (system) time, however, remains constant until any function in the current call stack yields back to the scheduler (i.e. calls wait or yield). In our case, this means that the delta will be exactly 1 beat because every iteration uses the same time stamp.
This can be demonstrated easily with the following code:
(
// logical time -> prints 3x the same time
SystemClock.seconds.postln;
SystemClock.seconds.postln;
SystemClock.seconds.postln;
)
(
// real elapsed system time -> prints 3 different times
Main.elapsedTime.postln;
Main.elapsedTime.postln;
Main.elapsedTime.postln;
)
The algorithm for a scheduler with a logical time base goes something like this:
- wait until the priority queue has at least one item
- pop the top item and compare its scheduled time (T) with the current system time (S).
If T <= S, go to 5. - sleep until T
- when we wake up, sample the current system time again (S). If T > S, go back to 3.
- set the current logical time to T and run the item, then go back to 1.
As you can see, the system clock only needs to catch up with the scheduled items. The clock precision is not too important because we do not schedule on the current system time. Just like language jitter, clock jitter is tolerable to a certain degree, depending on the Server latency. This brings us to the last point.
To preserve the musical timing of our events on the Server, we typically schedule things in the future with some fixed delay (= Server latency). The main two reasons are:
-
language jitter
All code takes time to execute and certain operations may temporarily block the language thread. Scheduling Server commands into the future helps to accomodate such timing irregularities -
audio jitter
Audio is computed in blocks. Messages that arrive between control blocks will be quantized to block boundaries if they are not scheduled at least one block into the future. Note that the audio callback typically calculates multiple control blocks in a row. E.g. with a hardware buffer size of 256 samples, every callback calculates 4 control blocks of 64 samples each. This means that the Server latency should be at least as big as the hardware buffer size.