AU Wrapper: Workaround for off-by-one error of timing information in Logic Pro X

My audio plug-in requires to know the time of the sample it’s currently processing.
To do this, I have something like the following code:

	// retrieve the current time of the playhead in samples, if possible
	std::optional<int64> playTimeOpt;
	if (auto *playHead = getPlayHead()) {
		AudioPlayHead::CurrentPositionInfo info;
		if (playHead->getCurrentPosition(info)) {
			if (info.isPlaying) {
				playTimeOpt = info.timeInSamples;
			}
		}
	}

	for (int i = 0; i < buffer.getNumSamples(); i++) {
		// calculate the absolute position of the current sample
		std::optional<int64> samplePlayTimeOpt;
		if (playTimeOpt) {
			samplePlayTimeOpt = *playTimeOpt + i;
		}

		// code using the sample time here
		// [...]
	}

This code works very well, however, there is an issue in Logic Pro X that causes an off-by-one error at the very start of the timeline with plugins that introduce latency.

In my case, it first reports a negative value for info.timeInSamples like -179 (I assume this is for latency compensation purposes), and in the next processBlock callback it reports 1868.
The problem is that the size of the first audio buffer I receive is 2048 samples, and (1868 - -179) = 2047.
Therefore, the code above determines the same sample play time twice in a row, which causes my application to reset some internal states, as it deems the input non-continuous.

This Logic bug only occurs at the very start of the timeline, these off-by-one errors don’t happen when I move the audio clip a few seconds into the timeline.

I’ve found a workaround: I modified juce_AU_Wrapper.mm to not use the sample time received by CallHostTransportState, and instead always use lastTimeStamp.mSampleTime, which has the correct value even at the start of the timeline.

I simply replaced this code

        if (CallHostTransportState (&playing,
                                    &playchanged,
                                    &outCurrentSampleInTimeLine,
                                    &looping,
                                    &outCycleStartBeat,
                                    &outCycleEndBeat) != noErr)
        {
            // If the host doesn't support this callback, then use the sample time from lastTimeStamp:
            outCurrentSampleInTimeLine = lastTimeStamp.mSampleTime;
        }

with

        CallHostTransportState (&playing,
                                    &playchanged,
                                    &outCurrentSampleInTimeLine,
                                    &looping,
                                    &outCycleStartBeat,
                                    &outCycleEndBeat);

        outCurrentSampleInTimeLine = lastTimeStamp.mSampleTime;

Logic is by far the biggest plugin host using the AU format, so I think it makes sense to incorporate this workaround into JUCE, especially since there are no downsides - the AU documentation specifically says that the timestamp provided to the AudioUnitRender function (which is the one that updates lastTimeStamp in the JUCE wrapper) must be monotonic, so it seems to be the right thing for the AudioPlayHead to return this value.

Tagging @tpoole, since he seems to do a lot of work on the AU wrapper :slight_smile:

I just realized that this workaround doesn’t actually work, since the AU only requires that timestamp provided to be continuous values, not that these values actually represent the position on the timeline. Therefore, please disregard this post.

I’ll go and file a bug report with Apple so they can fix the issue.