I finally managed to reach a solution.
For me, at least, it works fine for now. I don’t know if there was an easier solution, but for me, being able to sync that grid when I modify the tempo (for the internal clock), this change worked. I’ll leave below the function I modified in the source code.
MasterClock::GridInfo MasterClock::processAndCheckGrid(int numSamples,
const AudioPlayHead::CurrentPositionInfo& externalInfo)
{
GridInfo gi;
auto shouldUseExternalBpm = !linkBpmToSync || !shouldPreferInternal();
if (bpm != externalInfo.bpm && shouldUseExternalBpm)
setBpm(externalInfo.bpm);
if (currentSyncMode == SyncModes::Inactive)
return gi;
// .. ADDED .. //
// Store last tempo for detection
static double lastBpm = -1.0;
static double bpmSmooth = bpm; // Smoothed BPM
static double lastPpqPosition = 0.0; // Store last PPQ position
bool tempoChanged = (bpm != lastBpm); // Detect tempo change
if (tempoChanged)
lastBpm = bpm;
// Interpolate BPM smoothly over time
double smoothingFactor = 0.1; // Lower = smoother, Higher = faster response
bpmSmooth += (bpm - bpmSmooth) * smoothingFactor;
// Convert BPM to quarter notes in samples (using smoothed BPM)
const auto quarterInSamples = (double)TempoSyncer::getTempoInSamples(bpmSmooth, sampleRate, 1.0f);
// Instead of relying on uptime, calculate PPQ dynamically (like in updateFromExternalPlayHead)
double ppqTime = lastPpqPosition + ((double)numSamples / quarterInSamples);
lastPpqPosition = ppqTime;
// Calculate grid alignment based on PPQ (like in updateFromExternalPlayHead)
double multiplier = (double)TempoSyncer::getTempoFactor(clockGrid);
double nextGridPPQ = std::ceil(ppqTime / multiplier) * multiplier;
double nextGridSamples = nextGridPPQ * quarterInSamples;
samplesToNextGrid = nextGridSamples - (ppqTime * quarterInSamples);
if (currentSyncMode == SyncModes::SyncInternal && externalInfo.isPlaying)
{
uptime = ppqTime * quarterInSamples;
samplesToNextGrid = gridDelta - (uptime % gridDelta);
}
// ... added up to here
if (currentState != nextState)
{
currentState = nextState;
uptime = numSamples - nextTimestamp;
currentGridIndex = 0;
if (currentState != State::Idle && gridEnabled)
{
gi.change = true;
gi.timestamp = nextTimestamp;
gi.gridIndex = currentGridIndex;
gi.firstGridInPlayback = true;
samplesToNextGrid = gridDelta - nextTimestamp;
}
nextTimestamp = 0;
}
else
{
if (currentState == State::Idle)
{
uptime = 0;
lastPpqPosition = 0.0; // Added ... to reset PpqPosition
}
else
{
jassert(nextTimestamp == 0);
uptime += numSamples;
samplesToNextGrid -= numSamples;
if (samplesToNextGrid < 0 && gridEnabled)
{
currentGridIndex++;
gi.change = true;
gi.firstGridInPlayback = waitForFirstGrid;
waitForFirstGrid = false;
gi.gridIndex = currentGridIndex;
gi.timestamp = numSamples + samplesToNextGrid;
samplesToNextGrid += gridDelta;
}
}
}
return gi;
}