Sample accurate VST sync'
Posted: Sun Mar 03, 2013 1:41 pm
Following on from Billy's thread about accurate MIDI timing, here's something that may interest all you step-sequencer and arpeggiator fanatics out there...
Here's a prototype of a synchronised clock using Ruby and stream 'frames' to read the PPQ clock....
Very basic at the moment - there's a simple synth triggered by the Ruby MIDI output set to play a short 'blip' once every beat. Recording the output in Reaper has shown perfectly repeatable accuracy of the note at the beat divisions. Everything is set up inside one module that you can just export as a VST and start experimenting straight away.
The Ruby CPU load should be pretty low - I realised that with a bit of maths, the beat positions within the PPQ can be found without having to read the entire frame, just the first and last samples.
However, I also discovered something else rather interesting...
In my first attempts, I found that there was a latency of precisely one ASIO buffer. I investigated this further using some audio stuff, and it seems that this is inherent in using the 'frame' data type.
Because streams are designed so that we can process samples one at a time, it always takes one ASIO buffer to fill the 'frames' with sample data. Testing with a variety of buffer sizes shows that this effect is precisely predictable.
So the example schematic includes latency compensation by one buffer - predicting what the PPQ signal will be one frame in advance.
However, this has a couple of consequences...
1) If you start playback precisely on the beat, there is no output on the first downbeat - because the latency compensation is already looking beyond that point. So if you need a first downbeat, your playback cursor will need a small pre-roll.
2) Sample accuracy will be ever so slightly off during tempo changes. The maximum error is one ASIO buffer size; though for smooth tempo changes, it would normally be much less than that. As soon as the tempo becomes constant again, things immediately lock back into precise sync'.
Still plenty to do with this - things that I'm working on at the moment are...
1) If the beat position is exactly at a 'frame' boundary, it won't be detected - so it needs a little extra code to detect this case.
2) Beat sub-divisions. Should be fairly simple by scaling the PPQ ramp before the modulus maths.
3) Getting that note to play on beat one when playback is started! 'Is playing' primitive may provide a workaround for this.
4) Output for the current beat/division number so that sequencers know where they are in the beat - just needs some simple maths. But note that the output would have to be a Ruby value - converting it to green would ruin the timing. So any sequencer relying on the new clock would also have to be written in Ruby.
5) MUCH more rigorous testing!!!!
I'll keep working on those - but I'm very keen to hear the results of anyone else's tests before adding too many more features - in particular, whether it is working equally well in other VST hosts besides Reaper.
And as ever - bug reports, suggestions, code improvements will be most welcome!
Here's a prototype of a synchronised clock using Ruby and stream 'frames' to read the PPQ clock....
Very basic at the moment - there's a simple synth triggered by the Ruby MIDI output set to play a short 'blip' once every beat. Recording the output in Reaper has shown perfectly repeatable accuracy of the note at the beat divisions. Everything is set up inside one module that you can just export as a VST and start experimenting straight away.
The Ruby CPU load should be pretty low - I realised that with a bit of maths, the beat positions within the PPQ can be found without having to read the entire frame, just the first and last samples.
However, I also discovered something else rather interesting...
In my first attempts, I found that there was a latency of precisely one ASIO buffer. I investigated this further using some audio stuff, and it seems that this is inherent in using the 'frame' data type.
Because streams are designed so that we can process samples one at a time, it always takes one ASIO buffer to fill the 'frames' with sample data. Testing with a variety of buffer sizes shows that this effect is precisely predictable.
So the example schematic includes latency compensation by one buffer - predicting what the PPQ signal will be one frame in advance.
However, this has a couple of consequences...
1) If you start playback precisely on the beat, there is no output on the first downbeat - because the latency compensation is already looking beyond that point. So if you need a first downbeat, your playback cursor will need a small pre-roll.
2) Sample accuracy will be ever so slightly off during tempo changes. The maximum error is one ASIO buffer size; though for smooth tempo changes, it would normally be much less than that. As soon as the tempo becomes constant again, things immediately lock back into precise sync'.
Still plenty to do with this - things that I'm working on at the moment are...
1) If the beat position is exactly at a 'frame' boundary, it won't be detected - so it needs a little extra code to detect this case.
2) Beat sub-divisions. Should be fairly simple by scaling the PPQ ramp before the modulus maths.
3) Getting that note to play on beat one when playback is started! 'Is playing' primitive may provide a workaround for this.
4) Output for the current beat/division number so that sequencers know where they are in the beat - just needs some simple maths. But note that the output would have to be a Ruby value - converting it to green would ruin the timing. So any sequencer relying on the new clock would also have to be written in Ruby.
5) MUCH more rigorous testing!!!!
I'll keep working on those - but I'm very keen to hear the results of anyone else's tests before adding too many more features - in particular, whether it is working equally well in other VST hosts besides Reaper.
And as ever - bug reports, suggestions, code improvements will be most welcome!