Syncing to getPosition() issue

I am rolling my own LFO class which needs to always be in sync with the DAW clock even during timeline jumps and BPM change. So I can’t use a simple internal sample counter for that purpose.

I was thinking that relying on getPlayHead()->getPosition() on every processBlock() call will do the trick but I got some weird clicking going on. I debugged the timestamps I’m getting from the DAW and it seems that they are not linear:

4.91471
4.92051
4.9263
4.9321
**5.025**
4.93789
4.94368
4.94948
4.95534
4.96113
4.96693
4.97272
4.97852
**5.07142**
4.98431

I am guessing that it has got something to do with the way the host is slicing and moving buffers internally or something similar. But anyways looks like I shouldn’t be calling getPosition() if I want the true timestamp I’m currently on, or am I missing something here?

2 Likes

WTF! Which DAW was doing this? I don’t think the DAW would actually be sending blocks out of order, that would be nigh on impossible to deal with for a plugin that trying to process audio in a linear fashion, unless it was really giving the correct timestamp for that block, but still it would make a lot of plugins (such as filters) that rely on the previous state of the DSP to continues processing audio correctly impossible. edit: actually the more I think about this, I don’t think beyond really simple effects like tremolo it would be at all possible to process audio blocks out of order. :man_shrugging:

My guess is that that number being reported back is incorrect. If it’s just that one DAW doing that then it makes your life really diificult to somehow work around that. If it’s more than one DAW that would suggest perhaps the problem lies in JUCE itself. Did you check against the timeInSamples timestamp too?

it looks like you are just looping between 4th and 5th bar, but with slightly jagged loop points. can you try zoom in on your loop points to find out if this is a possibility? (maybe you had snap-to-grid turned off while setting the loop accidently or so)

I also don’t think any DAW would do that, but Bitwig for example, at least all of the Version4.somethings, has a pretty major bug, where it just sends wrong transport information to all plugins constantly, unless you have changed both loop points as well as the start playhead at least once after having opened a project, which has confused me a numerous amount of times already and made me feel like my plugin actually had a problem. And this is definitely not a JUCE-issue, because it also messes with melodyne-instances in the DAW… unless Melodyne was made with JUCE… was it? Actually hard to tell for a plugin without knobs, but would be really interesting now. They might have fixed it already because I tried to help them with this multiple times via support, however my Bitwig subscription ran out of time so I have no idea what changes they made in the meantime and no user has ever updated me on this. Kinda annoys me that I’d have to pay for it after all that…

So if the DAW in question is Bitwig I’d do the things I mentioned and check if the issue persists.

Edit: ok, the DAW in question is not Bitwig, but I’d still test if it works after moving around the loop points and playhead, because it might be that Logic has similiar issues for some reason.

Is it possible that this is actually a race condition issue? i.e. the host was using different threads to call your plugin (not at the same time of course) and your print handler miss ordered them?

I am using Logic on Mac. Would be nice if anyone else can confirm this behavior.
Simply call DBG (*getPlayHead()->getPosition()->getPpqPosition()); and see what you get.
Without looping or anything, just by starting the transport I get this again:

0
0
0.0464844
0.0929036
0
0.00585938
0.0116536
0.0174479
0.0232422
0.0290365
0.0348307
0.0406901
0.0464844
0.139323
0.0522786
0.0580729
0.0638672
0.0696615
0.0755208
0.0813151
0.0871094
0.0929036
0.185807

I don’t think its a race condition or anything because if you look closely you’ll see that the problem timestamp happens every 9 times (I confirmed it on a longer test as well) so this is something which is well calculated by the host for some reason.

And actually it gets even weirder than that. I would assume that at least the error is consistent but the clicking is irregular. When I save the calculated LFO values to a file and open it for viewing I see the following picture. Not every time (as I explained, every 9 buffers) the LFO gets out of sync. It rather looks like the bad timestamp is a dummy/backup buffer which the host decides when to use or not. I don’t know. I am completely lost.

it seems the discontinuities always go in the same direction as the curve of the LFO at this given rate

This is because the bad timestamps are always exactly 2048 samples ahead (while the host is actually configured to run 128 samples buffers) which is causing the LFO to end at a position later in the future than what it should. Then the timestamp go back to where they should be - that part is consistent.

So this is generated by the host for some reason on every 9 buffers, but the times when it is affecting the processing seems to be random.

I was able to hack my way around this issue by using SmoothedValue in my LFO class where needed to get a clean output.

But still… wtf? I am 100% sure that I am not given linear and consistent timestamps from Logic during playback and this is a major issue. I’ll be spending the rest of my days looking over my shoulder until I figure out what’s going on here.

2 Likes

I’m about to dive into some new code that will ideally make use of the PPQ position, so I will be able to report back my own findings in the next few days. Just curious which version of Logic were you using, and was it Intel or ARM based machine?

2 Likes

Logic 10.7.0 for Rosetta. I’m using M1.

BTW, I also printed out the calculated index count difference (based on the PPQ data and not an internal sample counter) from consecutive processBlock calls and if I wasn’t wrong, I got ~30 samples delta while the buffer size is always 128.

Meaning the Logic is using some overlap-and-add method on top of all this crazyness.

At this point I decided that the less I know - the happier I will be.

Hello all. Just to say. I’m testing some code in Logic 10.7.8 at the moment and having similar issues. I noticed this when stopping and starting the transport. I have been consoling out the PPQ when the transport starts and it’s sometimes in the “future”. If anyone finds out more about why this is happening I would love to know, because this is really messing up the plugin I’m working on!

1 Like

So if we can’t promise a proper sync to Logic’s clock what does it mean then? Do we all need to wrap up our things here and go back to our old jobs?

1 Like

Just a couple of observations I can add (maybe my problem needs a new thread anyway…?):

  • I don’t think this is a problem of misordered timestamps - sometimes the values I’m getting for ppqPosition are up to 20 beats ahead of where the playhead is.
  • I guess it would be useful to know whether this is a JUCE problem or a Logic problem. I’ve tried drilling down into how JUCE accesses the underlying Audio Unit, but I’m neither a C++ or Audio Unit expert. I might try accessing this directly from the processBlock() and see if this changes the results.
  • Presumably if this is a Logic problem, this must be messing up a lot of plugins…?
  • I’ve only tried tracking this problem with AUv2, not AUv3 so far - not sure if this would make any difference.
  • Finally, I’ve noticed that I sometimes get the wrong ppqPosition even on the very first time I access it after the transport starts - so I can’t even get a starting position and then calculate offsets each subsequent block.

I’m on 2020 MBP using Logic 10.7.8 in Monterey.

1 Like

This confuses me. Aren’t you doing this work in processBlock already? That’s the only place where the playhead should be accessed, and the initial post says it was in processBlock.

I mean, accessing the underlying AudioUnit from processBlock() directly, to see if I can get closer to the values Logic is sending in. But I’ve spent the afternoon looking at how to do this, and it doesn’t seem trivial…

I am always accessing getPlayhead() from the processBlock().