PPQ Position and Actual transport start point

Hi,

I could really use some pointers on getting the ppq position of where the host transport really starts from.

When I set the Cubase playhead to being from the second 16th note at 1.1.2.0 and press play the ppq position given by positionInfo->getPpqPosition() starts with negative values, incrementing many times as the ProcessBlock is called before it reaches the actual ppq position that the Cubase playhead starts from (1.125).

I check to make sure that positionInfo->getIsPlaying() is true before calling positionInfo->getPpqPosition() so it’s not that I’m taking the values whilst the playhead is stopped.

So how are we supposed to calculate the position of the DAWs playhead if getPpqPosition() cannot be trusted? :thinking:

1 Like

i think the reason for that is that cubase plays a bit of empty audio before hitting the point where the user sets the playhead in order to make sure all the plugins already started warming up their processes if needed so that you can hear the point of interest with the same punch it would have while running freely. if starting from the first bar that means some ms of negative ppq. from experience i can tell that some plugins can’t deal with that well which is why i personally got into the habit of not starting a project from bar 1, but you can also just make sure that it works well in your plugins for those who like to live dangerously

2 Likes

Yeah, I think you’re right - Cubase does seem to add some kind of delay/pre-roll of it’s own but, at least from my perspective, that renders the ppqPosition values pretty much useless!

At least it can’t be trusted to give you what it’s name and description claims :smiley :smiley:

Anyway, I figure the samples in the buffer should be accurate - so counting them and calculating the real ppq position from those should be more reliable than the other way round like I was doing. I think it should actually be less expensive too calculate it too.

Uggh! I’m pulling my hair out here - how do people get heir plugins to sync properly and consistently to Cubase!?

I thought I would be able to use positionInfo.timeInSamples rather than positionInfo.ppqPosition since the ppqPosition Cubase reports is not correct at start-up; but no - even the reported time in samples is wrong!

I move the Cubase transport forwards to 1.1.2.0 (one 16th note from zero) and in a buffer where positionInfo.isPlaying is true I get these values


positionInfo.timeInSeconds: 0.000000
positionInfo.ppqPosition: -0.000000
positionInfo.timeInSamples: 0

All of these should be non-zero. This makes it impossible to reliably be in sync and trigger notes correctly. Either I would have to accept that notes are scheduled at positions before the playhead begins, or I have to accept that I can’t sync until the first bar has elapsed. But I know that plugins exist that do sync correctly so what am I missing? Please help! :confounded_face: :sweat_smile:

@eyalamir @daniel SOS

A. PPQ got limited resolution to begin with.
B. What’s the exact value you’re seeing with the negative ppq?
C. Most reliable position from my experience is seconds. Even samples ends up sometimes being rounded the wrong way so you get a sample offset.
D. Keep in mind that ppq is limited with changing tempo and/or time signatures. You’ll need extra information to calculate position for those. (Unless you store your notes in PPQ)

Yeah, PPQ is not as fine grained as samples but I didn’t expect it to be completely off and so unreliable to the point that it reports negative values when the playhead is ahead of the zero start position.

An example of the negative value when starting the playhead a 16th note ahead of zero:

PPQ Position: -0.123900

No pre-roll or anything like that enabled. It’s simply wrong.

So, I was planning to ignore the PPQ reported and calculate it myself based upon the bpm, samplerate, and either timeInSample or timeInSeconds but since both of these are also wrongly reported there doesn’t seem to be any reliable way to know ‘where’ the transport started from.

Right now, the only option I can think of is to delay processing until I see that the reported values have stabilized across a buffer or two. The irritating thing is that I know that plugins exist that are able to sync correctly from the playposition; it just eludes me how they are doing it.

Essentially sample counting can keep you at correct speed, but it cannot tell you the correct musical phase if the host gives bad initial info.

A negative PPQ is fine, the DAW is totally allowed to start playing from before the 0 position. Cubase has a bunch of features that could make that happen (bar offset, ASIO guard, preroll
)

My plugins sync fine with Cubase without doing anything special (except handling negative PPQ) so it’s hard to tell exactly what the problem is.

If you want to create some minimal example showing the problem, it might be more useful to take a look.

1 Like

Hi @eyalamir :slight_smile:

I’ll try to explain. I am triggering notes at note divisions; let’s say every quarter-note.

If I move the Cubase transport ahead so that it begins at 1.1.2.0 (the second 16th note) and start playing from there, then I don’t want a note to be scheduled at 1.1.1.0 before the playhead start position.

This does happen because Cubase is not reporting the real start position and instead reports that the PPQ position is some place before 1.1.2.0.

So it’s not just negative PPQ that is the problem but PPQ’s before the transport start point.

So how can my plugin figure out ‘where’ the playhead started from?

I’m not seeing those issues here, my sequencer plays in exact timing starting from anywhere in the Cubase transport, even if the user starts to play from an un-quantized position like the middle of the first bar, etc.

I think you’ll need to create some minimal reproducible example - I suspect the problem might be in how you interpret the PPQ values, and not the actual PPQ values sent by Cubase.

Hi @eyalamir , is there any way for me to send you some code?

You can make some minimal reproducible example and put it on GitHub for everyone to see, Ideally do that as something ‘clean’ as possible and with as less project-specific complications as you can.

For example if you can just build something simple that generates quarter notes MIDI notes or some white noise, and shows the problem in a clear way.

Otherwise if you want me to really work on your project you can pay my hourly fee. :slight_smile:

Thanks @eyalamir :slight_smile:

Here’s an example that just produces a note every beat.

To reproduce just start the Cubase transport at 1.1.2.0 and press record - you will see the note get placed at 1.1.1.0 - not every single time but often.

Oh and you’ll need a midi track that receives the input from the plug - but of course you would know that!

Much appreciated for the help and yes, if I need more help then I would be happy to pay your no doubt extortionate hourly fee! :face_savoring_food:

It all works fine here. I think the problem you’re experiencing is that Cubase has a feature called “Retrospective MIDI Recording” which means it actually calls the plugin for a little while before starting the record. So if you hit record from 1.1.2 it might actually start from 0.

1 Like

hmm, I’m pretty sure I found that feature - I was checking all the menus to see if there was anything that could make a difference. I turned that off and it still happens.

Are you sure you can’t reproduce it?

Start the transport from 1.1.2.0. Hit record and you don’t see a note captured at 1.1.1.0?

:thinking:

Do I need to restart Cubase after changing that feature? Maybe that could be it as I don’t think I did that

I think you misunderstand - I can see it creating the clip retroactively. It’s a ‘feature’ not a bug - there used to be a way to turn it off but as of Cubase 12/Mac I can’t find a way to turn it off.

What I see Cubase does is that it creates a clip retroactively and actually starts the record from 1.1.1.0 even if your transport was after that. It’s meant to prevent you from missing notes.

Ok, so that means that Cubase is sending positional info that starts before the actual start point in the UI - hence there is no way a plugin can really account for that.

Yes, it calls the plugin from an earlier position. In the past you used to be able to go to preferences->Record->MIDI and turn it off, but that checkbox seems to be gone. Maybe there’s a hidden way to turn it off.

Having that said from the plugin’s perspective this is all correct - Cubase actually calls you to start from an earlier position, and you can see in the resulting clip that it positioned the notes correctly at the start of the bar.

Alright then! If you say that it’s correct behaviour then that’s good enough for me and I won’t spend more hours trying to fix it.

Thank you so much for taking a look - really appreciated! <3

Well I’m really struggling again here! :sweat_smile:

So I am trying my hardest to handle looping.

I have written my code to detect when a loop happens and we wrap back around. Once I know that my start PPQ postion in the buffer is less or equal to the loop start PPQ and that the previous start position of the last buffer is more or equal to the loop end PPQ I can schedule a note.

What is actually happening is that sometimes two notes end up in the same bar! It’s as though Cubase is misplacing one of the notes to end up in the previous bar.

Here are my logs showing the 5 notes. The result in Cubase is 4 takes, the first 3 are perfect and the last has 2 notes - one ok and one super long (no note off)


NOTE-ON: Buffer #1508
Global sample counter #96448
Loop wrapped at sample offset: 0 | ppqAtSample: 0.002177 | previousPPQPosition: 4.002133 | std::floor(ppqAtSample): 0.000000 | std::ceil(*previousPPQPosition): 5.000000
samplesFromLastNoteOnUntilBufferEnds: 64

Note-off triggered after 22080 samples, offset: 30

NOTE-ON: Buffer #2886
Global sample counter #184640
Loop wrapped at sample offset: 0 | ppqAtSample: 0.001815 | previousPPQPosition: 4.001768 | std::floor(ppqAtSample): 0.000000 | std::ceil(*previousPPQPosition): 5.000000
samplesFromLastNoteOnUntilBufferEnds: 64

Note-off triggered after 22080 samples, offset: 30

NOTE-ON: Buffer #4264
Global sample counter #272832
Loop wrapped at sample offset: 0 | ppqAtSample: 0.001450 | previousPPQPosition: 4.001405 | std::floor(ppqAtSample): 0.000000 | std::ceil(*previousPPQPosition): 5.000000
samplesFromLastNoteOnUntilBufferEnds: 64

Note-off triggered after 22080 samples, offset: 30

NOTE-ON: Buffer #5642
Global sample counter #361024
Loop wrapped at sample offset: 0 | ppqAtSample: 0.001088 | previousPPQPosition: 4.001043 | std::floor(ppqAtSample): 0.000000 | std::ceil(*previousPPQPosition): 5.000000
samplesFromLastNoteOnUntilBufferEnds: 64

Note-off triggered after 22080 samples, offset: 30

NOTE-ON: Buffer #7020
Global sample counter #449216
Loop wrapped at sample offset: 0 | ppqAtSample: 0.000725 | previousPPQPosition: 4.000680 | std::floor(ppqAtSample): 0.000000 | std::ceil(*previousPPQPosition): 5.000000
samplesFromLastNoteOnUntilBufferEnds: 64

Note-off triggered after 22080 samples, offset: 30

The difference between the buffer count is very regular at 1,378 so I know it’s not the triggering logic being triggered twice in the same cycle. I have no idea what is happening. @eyalamir @jules Any tips?

Unfortunately, this to me has way too much product-specific info for me to solve in a forum like that. It just sounds to me like your logic/math is somehow not correct, I don’t think Cubase is doing anything wrong when looping.