fix: progressive full-res STFT fill never resumed after interaction
The overview-then-idle high-res fill had two defects that left permanent black/low-res vertical stripes: - On any interaction the driver set isBgProcessing=false to pause, but nothing ever re-armed it, so the background sweep died on the first pan/zoom and the strided overview was never completed. Now isBgProcessing simply tracks !bgFinished and the per-frame compute is gated on !IsUserInteracting(), so the sweep resumes on its own once the user stops interacting. - The foreground (visible-range) fill overwrote the background sweep cursor bgHighResSeg with the current view position, so the sweep skipped everything before the view and stranded it uncomputed. The foreground pass no longer touches the cursor; the sweep stays monotonic from 0. Also recolor the displayed image as segments fill in (throttled during the sweep, forced at completion) instead of only at the very end, so freshly computed regions actually appear; and short files whose overview is already full-resolution (skipFactor 1) skip the sweep entirely. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+57
-55
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user