diff --git a/src/spectrogram.c b/src/spectrogram.c index e2b73bd..3ee89e8 100644 --- a/src/spectrogram.c +++ b/src/spectrogram.c @@ -1049,72 +1049,69 @@ int main(int argc, char* argv[]) } if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) app.view.isPanning = false; - // Foreground high-res: when user zooms in, compute missing - // segments in the visible range immediately (responsive). - // Background task handles the rest when idle. - if (app.skipFactor > 1 && app.stft.numSegments > 0 && !app.bgFinished) { - float viewRange = app.view.end - app.view.start; + // ---- Progressive full-resolution fill ---- + // After the strided overview loads, the missing segments are filled + // in at full resolution: the visible range first (foreground, so a + // zoom-in sharpens immediately), then a monotonic sweep of the whole + // file whenever the user is idle. The sweep RESUMES on its own after + // an interaction — we only gate the per-frame compute, we never latch + // the task off. (Previously an interaction set isBgProcessing=false + // permanently, so the fill died on the first pan/zoom and left + // black/low-res stripes wherever the foreground pass hadn't reached.) + bool fillingDirty = false; - if (viewRange <= 0.25f) { - // Clamp to valid segment range - int viewStartSeg = (int)(app.view.start * app.stft.numSegments); - int viewEndSeg = (int)(app.view.end * app.stft.numSegments); - if (viewStartSeg < 0) viewStartSeg = 0; - if (viewStartSeg >= app.stft.numSegments) viewStartSeg = app.stft.numSegments - 1; - if (viewEndSeg >= app.stft.numSegments) viewEndSeg = app.stft.numSegments - 1; - - // Find first missing segment in the visible range and compute it - for (int seg = viewStartSeg; seg <= viewEndSeg && seg < app.stft.numSegments; seg++) { - if (app.stft.segments[seg].spectrum == NULL) { - int startSeg = seg; - int endSeg = seg + 50; - if (endSeg > viewEndSeg + 1) endSeg = viewEndSeg + 1; - app.bgHighResSeg = ComputeNextHighResChunk(&app.signal, &app.stft, app.fftSize, startSeg, endSeg); - app.visibleTextureValid = false; - TraceLog(LOG_INFO, "Foreground high-res (%d to %d)", startSeg, endSeg - 1); - break; - } + // Foreground: fill missing segments in the visible range right away. + // Must NOT move the background sweep cursor (app.bgHighResSeg), or the + // sweep would skip everything before the current view and strand it. + if (app.stftComputed && !app.bgFinished && app.stft.numSegments > 0 && + app.view.end - app.view.start <= 0.25f) { + int viewStartSeg = (int)(app.view.start * app.stft.numSegments); + int viewEndSeg = (int)(app.view.end * app.stft.numSegments); + if (viewStartSeg < 0) viewStartSeg = 0; + if (viewEndSeg >= app.stft.numSegments) viewEndSeg = app.stft.numSegments - 1; + for (int seg = viewStartSeg; seg <= viewEndSeg; seg++) { + if (app.stft.segments[seg].spectrum == NULL) { + int endSeg = seg + 50; + if (endSeg > viewEndSeg + 1) endSeg = viewEndSeg + 1; + ComputeNextHighResChunk(&app.signal, &app.stft, app.fftSize, seg, endSeg); + fillingDirty = true; + break; } } } - // Background high-res: when user is idle, fill in remaining - // segments at full resolution. Pauses on any interaction. - // Also kicks in when zoomed out (no foreground trigger) to fill - // segments outside the view range. - bool isZoomedIn = (app.skipFactor > 1 && app.view.end - app.view.start <= 0.25f); - if (app.isBgProcessing && !app.bgFinished && !IsUserInteracting()) { - int endSeg = app.bgHighResSeg + 50; // chunks of 50 segments + // Background: when idle, sweep the whole file from the cursor and + // compute any still-missing segment (already-computed ones are skipped + // cheaply). Done once the cursor passes the last segment. + if (app.stftComputed && !app.bgFinished && app.stft.numSegments > 0 && + !IsUserInteracting()) { + int endSeg = app.bgHighResSeg + 50; // chunks of 50 segments if (endSeg > app.stft.numSegments) endSeg = app.stft.numSegments; app.bgHighResSeg = ComputeNextHighResChunk(&app.signal, &app.stft, app.fftSize, app.bgHighResSeg, endSeg); + fillingDirty = true; if (app.bgHighResSeg >= app.stft.numSegments) { - // All done — generate full-res texture and mark complete - AutoScaleAmplitude(&app.stft); - GenerateSpectrogramTexture(&app.stft, &app.spectrogramImage, &app.spectrogramTexture); - app.visibleTextureValid = false; app.bgFinished = true; - app.isBgProcessing = false; - TraceLog(LOG_INFO, "Background high-res complete (%d segments)", app.stft.numSegments); - // Save the full-res result to cache (overwrites the overview-only entry) - SaveToCache(); + TraceLog(LOG_INFO, "Full-res fill complete (%d segments)", app.stft.numSegments); + SaveToCache(); // overwrite the overview-only cache entry } } - if (app.isBgProcessing && IsUserInteracting()) { - // Pause background processing — user is interacting - app.isBgProcessing = false; - } - // If not zoomed in, scan for missing segments to kick off processing - if (!isZoomedIn && app.isBgProcessing && !app.bgFinished && app.bgHighResSeg < app.stft.numSegments) { - bool hasMissing = false; - for (int i = app.bgHighResSeg; i < app.stft.numSegments; i++) { - if (app.stft.segments[i].spectrum == NULL) { hasMissing = true; break; } - } - if (!hasMissing) { - // No more missing segments — mark complete - app.bgFinished = true; - app.isBgProcessing = false; - SaveToCache(); + // Stay awake while work remains so idle frames keep the sweep moving + // (IsAppActive() checks this); let the loop sleep once it's done. + app.isBgProcessing = !app.bgFinished; + + // Reveal freshly-filled segments. Reassigning the whole file is too + // costly to do every frame, so throttle during the sweep and force a + // final pass at completion. Skipped while interacting to avoid a hitch + // mid-gesture — the next idle frame repaints. + if (fillingDirty && (app.bgFinished || !IsUserInteracting())) { + static double lastFillColorize = 0.0; + double now = GetTime(); + if (app.bgFinished || now - lastFillColorize > 0.5) { + if (app.bgFinished) AutoScaleAmplitude(&app.stft); + GenerateSpectrogramTexture(&app.stft, &app.spectrogramImage, &app.spectrogramTexture); + app.visibleTextureValid = false; + lastFillColorize = now; } } @@ -1421,7 +1418,12 @@ int main(int argc, char* argv[]) app.stftComputed = true; app.loadingPhase = 0; // Reset — background processing runs outside this block app.loadingProgress = 0.0f; - app.isBgProcessing = true; // Kick off background high-res next frame + // Arm the progressive full-res fill from the start of the file. + // A full-resolution overview (skipFactor 1) has nothing missing, + // so mark it finished and skip the sweep entirely. + app.bgHighResSeg = 0; + app.bgFinished = (app.skipFactor <= 1); + app.isBgProcessing = !app.bgFinished; TraceLog(LOG_INFO, "STFT overview computed (%d segments, skipFactor=%d)", app.stft.numSegments, app.skipFactor); // Save the overview result to cache (will be overwritten when full-res completes)