Position of the host tempo change


#1

Hello Jules !

I have a question. I would like to know how to detect correctly the tempo changes from the sequencer timeline. Using the AudioPlayHead class, and the getCurrentPosition function, I can only get the value of the tempo at the beginning of each processReplacing callbacks. But if the tempo changes for example at the 125th sample, I don’t know how to detect that. I thought that the input MidiBuffer object may contain such information, but this is not the case…

I’m developing a plug-in which generates notes from scratch, and converts them in MIDI information. Without this information, they can’t be synchronized with the host timeline…

Any advice ? Thanks in advance !


#2

AFAIK there’s no way of getting a more accurate position for things like tempo changes, I’m afraid…


#3

That’s something I thought looking for the original VST SDK code. But that’s odd isn’t it ? That means that no VST plug-in in the world is able to track accurately the tempo changes from the host ? How do plug-ins creators develop arpeggiators or any sync-ed processing?

I was thinking also that the MIDI plug-ins SDK from steinberg may have additional functions to solve this issue…


#4

Well, as far as I know (and having few synced plugins behind me) there is no other way to sync to the host. Tempo value (and also ppqPosition) available via Juce AudioPlayHead::CurrentPositionInfo (and corresponding VST/AU methods) is valid for this processBlock you are actually in. And you should assume that this tempo is not changing in the current buffer length. Besides of that you have to assume that in next blocks tempo may be completely different.

This shouldn’t be the problem for you even if tempo is automated for example. Let’s say that tempo increases from very low to high value during long period of time. During playback you will get interpolated values of these change and you can only follow them. During recording/exporting hosts (not all) will be calling processBlock more often (more precisely ‘when it’s needed’) which means even for one sample if precise interpolation requires it.

During playback your messages won’t be precisely synced, but always within one buffer (as close to the end of buffer the messages will be more out of sync). But it shouldn’t be even noticed when the buffer is low. During recording everything will be synced to sample position.

Never accumulate ppqPosition on your own out of the buffer processing scope, never expect that host will do something, or notify about something even if it should happen. And never rely that past is before now and now before future (according to values returned by some hosts). Remember to be in the current buffer only :slight_smile:

Cheers,
Przemek


#5

Hello bld, and thanks for the information.

My problem happens in a test project, with my plug-in in “sync mode”, which has to generate MIDI notes at the right tempo, and at the right time. Let’s say the tempo is 120 BPM everywhere. Then I add a tempo change somewhere at 180 BPM. Because I don’t know really when it happens, my events can not be sync-ed with the host timeline.

In Reaper, I can record the MIDI generated, and I have been able to see that the events are not on the right times… In the current implementation of my plug-in, I take all the tempo change events available, then I create a buffer with the number of samples of processReplacing. It contains the tempo for each sample, and a calculus of its time position, and the number of quarters from the sample 0. So I don’t guess anything related to the future :wink:

You said that some hosts are able to do things differently, and use different number of samples in the processReplacing function, can you tell more about that ? Which hosts for example ?


#6

…the tempo for each sample in your buffer is always the same, because no matter how often you will call getCurrentPosition within one buffer, you will always get the same data. These informations are for “positioning” you on the start of current block only.

I used MidiOut functionality only in 1 or 2 plugins but as I remember I’ve done it using Juce MidiBuffer::addEvent() with sample position to place message in the “right time” of the buffer.

Well last time I had to check it it was Cubase 6 (or 6.5) and I noticed that when tempo was automated linearly, I’ve got calls with blocks of only one sample to process during interpolation, BUT only when it was offline (the wav from project was rendered). During normal playback Cubase served normal blocks and tempo was interpolated to these periods.


#7

[quote]Well, you should assume that it is happening (the change from 120 to 180) on the beginning of the process where you get 180.
I used MidiOut functionality only in 1 or 2 plugins but as I remember I’ve done it using Juce MidiBuffer::addEvent() with sample position to place message in the “right time” of the buffer.[/quote]
This is what my plug-in currently does, without another source available of tempo changes than the one given by the VST SDK :wink: But, with this implementation, I can see in Reaper that my events start a few ms after what is expected. Everything works fine without any tempo change, I have spent a lot of time in my MidiBuffer creation algorithm.

I see :wink: I had only seen such size changes when the loop functionnality is available in the host, or with Logic which automatically changes the buffer size during record…


#8

[quote=“Wolfen”][quote]Well, you should assume that it is happening (the change from 120 to 180) on the beginning of the process where you get 180.
I used MidiOut functionality only in 1 or 2 plugins but as I remember I’ve done it using Juce MidiBuffer::addEvent() with sample position to place message in the “right time” of the buffer.[/quote]
This is what my plug-in currently does, without another source available of tempo changes than the one given by the VST SDK :wink: But, with this implementation, I can see in Reaper that my events start a few ms after what is expected. Everything works fine without any tempo change, I have spent a lot of time in my MidiBuffer creation algorithm.
[/quote]

That’s why I wrote “right time” in quotes. From plugin’s point of view if tempo is 120 in first block and 121 in next one, you can’t distinguish if the change has been made suddenly or if it has been increasing smoothly by the host internally during whole buffer. Host can deals with that more precisely but you have now way to know about that. That’s why your events can be shifted a bit in time - within one block. The question is how Reaper behaves when it renders the wav file. Do a test and check how it’s positioned.

Even if it’s a quick change from 120 to 180 without any interpolation, host may do that internally in exact position, but you will know about the change in next buffer, processing let’s say half of a buffer with tempo value outdated.

The question is: how big is the problem?
If the differences and out-syncing is very small then, maybe it’s not a problem because nobody will notice that.
If it’s synced properly during final export of a project, it can also be enough for some cases.
In the worst case, where it could be used playing live etc. and proper sync is crucial it maybe only the problem of small latency. When the latency of card/asio etc. is smaller then block size should be also smaller making the out-of-syncing less significant.

[quote]

I see :wink: I had only seen such size changes when the loop functionnality is available in the host, or with Logic which automatically changes the buffer size during record…[/quote]

Well, I’ve seen so many strange behaviors of many hosts that probably nothing can makes me wonder.
Ppq traveling in time to the past after tempo change (!), transport change notification delayed for few process blocks (!), not mention about “normal” situations when ppq is just not accumulated by host precisely :slight_smile:


#9

OK, I think I have finally understood your point :mrgreen:

I have forgotten to say something, I have a counter which records the current quarters from the start of the playing. If I had the value of the tempo for each sample, this counter may have the same value than the one from the CurrentInfo structure, which I do not use. Obviously, for the case I have presented, this is not the case…

So I should assume that the tempo does not change during the processReplacing function, and use the ppq variables inside the CurrentInfo to know where is the time origin in the function, and the events I need to put in the MidiBuffer (instead of my own counter with my own calculation, right in theory but wrong without the tempo changes information). And then, I should have “faith” ( :lol: ) in the ability of the host to choose the right buffer sizes :mrgreen:

I think this the best implementation I can do, thanks again for the answers :wink:


#10

Yes, exactly. Always rely on values given by the host. If you need to count something, do it within this very buffer you’re actually in. In next buffer don’t rely on values counted in previous buffer, get fresh CurrentInfo data instead and rely on them. And don’t be afraid, hosts deal with following buffer sizes in many ways, but it won’t be a problem for you as long as your code will do right job in every current buffer.

That’s in very short, I think.