Here is the logic/calculation I am doing…
// === Loop Wrap and Note-On Detection ===
if (posInfo.isLooping && !lastNoteWasNoteOn)
{
const double loopLengthPPQ = loopEndPPQ - loopStartPPQ;
//set the previousPPQ to the start of the loop
double previousPPQ = ppqStart;
//iterate through the samples in the buffer
for (int sample = 0; sample < numSamples; ++sample)
{
// Calculate the current PPQ position based on the sample time
const double currentPPQ = ppqStart + (sample * ppqPerSample);
// Calculate the wrapped PPQ positions
const double previousWrappedPPQ = fmod(previousPPQ - loopStartPPQ + loopLengthPPQ, loopLengthPPQ);
const double currentWrappedPPQ = fmod(currentPPQ - loopStartPPQ + loopLengthPPQ, loopLengthPPQ);
// Check if the current PPQ is less than the previous PPQ - we have wrapped
const bool wrapped = currentWrappedPPQ < previousWrappedPPQ;
if (wrapped && !lastNoteWasNoteOn)
{
const int sampleOffset = juce::jlimit(0, numSamples - 1, sample);
midiMessages.addEvent(juce::MidiMessage::noteOn(1, 60, (juce::uint8)127), sampleOffset);
lastNoteWasNoteOn = true;
samplesFromLastNoteOnUntilBufferEnds = numSamples - sampleOffset;
break; // Only trigger once per buffer
}
//update the previous PPQ for the next iteration
previousPPQ = currentPPQ;
}
}
And the result is that sometimes the note is placed perfectly at the loop start after looping but other times the note is placed precisely at the end of the bar after looping - actually outside of the loop! This is what is most baffling. If it was scheduled slightly late after the loop point then I would expect the note to be slightly late and a little bit more than 0.0 - but to get placed in the right take but all the way to the end and outside of the loop points - that is baffling for me. There is no product specific info here @eyalamir right?


