@broc: “1ms is about the resolution of the Max event world… the limits of event timing resolution are imposed by max, not m4l / live.”
I don't think this is correct. It’s true that in Max standalone, the Max thread operates at 1 msec intervals. However, I’ve started to investigate the timing accuracy in Max for Live and in Max standalone and it seems that in M4L, the timing accuracy depends on Live’s buffer size and the 1 msec scheduler is no longer used (rather the updates happen every audio buffer). I’ll explain my thinking here.
Firstly, I’ve looked into the issue to simply receive a bang every beat and timestamp this. At 120BPM, in M4L you get jitter at +- 0.6ms, ~20-30 samples. This is related to the buffersize of Live. It doesn’t make any difference if you do a system time call each beat message or calculate via sample index, this jitter is present. With my buffer size in Live set to 64, every 64samples, the Max device will look at the Max objects and update the Max thread updates based on logical timestamps: i.e. replacing the regular 1msec callbacks on messages of the max, as opposed to MSP, world, with callbacks every 64samples, i.e. 1.4 msec.
For this metronome test, if you increase the buffersize of Live to 256, I find a regular sequence of intervals to be roughly:
499.2, 499.2, 499.2, 499.2, 499.2, 499.2, 504.6, 499.2, etc
i.e. jitter is ~ 0.8msec about 8 times, then +4.6 msec.
The reason is that the the bang will occur at the end of the audio buffer during which the event was scheduled. So when the buffer size is 256, an interval of 500msec is 86.13 buffers. The interval reported will most often 86 buffers (499.23 msec) and occasionally 87 buffers (505.03 msec).
This agrees with the idea that the message update routine is called after the audio update routine in Live. So.... definitely a problem if you want accurate synchronisation with Live's beats.
In Max standalone, the same calculation reveals a jitter of up to 50 microseconds.
Using the cpp function thisthread.getid(), I've compared the thread of the bang message from the metronome with the audio thread, and it's the same thread - so essentially, it's looking likely that for my MSP~ object, any messages will be picked up when the audio callback gets called. I've used both the std::chrono::high_resolution_timer (C++ standard library within an external) and the Max object [cputime] to check the intervals between a regular metronome running in Max for Live. With both methods, the jitter depends on buffer size, going up to 4msec with a 256 buffer, and is less for a 64 sample buffer +- 1msec.
Interestingly, if you use the timer object in Max, there is no reported jitter, even in M4L, suggesting that Max has logical timestamps that are idealised and these are used to schedule Max events. When comparing events, the logical timestamps are used, so the reported interval is 500 each bang, regardless of what the actual interval was.