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; }