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;
|
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) app.view.isPanning = false;
|
||||||
|
|
||||||
// Foreground high-res: when user zooms in, compute missing
|
// ---- Progressive full-resolution fill ----
|
||||||
// segments in the visible range immediately (responsive).
|
// After the strided overview loads, the missing segments are filled
|
||||||
// Background task handles the rest when idle.
|
// in at full resolution: the visible range first (foreground, so a
|
||||||
if (app.skipFactor > 1 && app.stft.numSegments > 0 && !app.bgFinished) {
|
// zoom-in sharpens immediately), then a monotonic sweep of the whole
|
||||||
float viewRange = app.view.end - app.view.start;
|
// 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) {
|
// Foreground: fill missing segments in the visible range right away.
|
||||||
// Clamp to valid segment range
|
// Must NOT move the background sweep cursor (app.bgHighResSeg), or the
|
||||||
int viewStartSeg = (int)(app.view.start * app.stft.numSegments);
|
// sweep would skip everything before the current view and strand it.
|
||||||
int viewEndSeg = (int)(app.view.end * app.stft.numSegments);
|
if (app.stftComputed && !app.bgFinished && app.stft.numSegments > 0 &&
|
||||||
if (viewStartSeg < 0) viewStartSeg = 0;
|
app.view.end - app.view.start <= 0.25f) {
|
||||||
if (viewStartSeg >= app.stft.numSegments) viewStartSeg = app.stft.numSegments - 1;
|
int viewStartSeg = (int)(app.view.start * app.stft.numSegments);
|
||||||
if (viewEndSeg >= app.stft.numSegments) viewEndSeg = app.stft.numSegments - 1;
|
int viewEndSeg = (int)(app.view.end * app.stft.numSegments);
|
||||||
|
if (viewStartSeg < 0) viewStartSeg = 0;
|
||||||
// Find first missing segment in the visible range and compute it
|
if (viewEndSeg >= app.stft.numSegments) viewEndSeg = app.stft.numSegments - 1;
|
||||||
for (int seg = viewStartSeg; seg <= viewEndSeg && seg < app.stft.numSegments; seg++) {
|
for (int seg = viewStartSeg; seg <= viewEndSeg; seg++) {
|
||||||
if (app.stft.segments[seg].spectrum == NULL) {
|
if (app.stft.segments[seg].spectrum == NULL) {
|
||||||
int startSeg = seg;
|
int endSeg = seg + 50;
|
||||||
int endSeg = seg + 50;
|
if (endSeg > viewEndSeg + 1) endSeg = viewEndSeg + 1;
|
||||||
if (endSeg > viewEndSeg + 1) endSeg = viewEndSeg + 1;
|
ComputeNextHighResChunk(&app.signal, &app.stft, app.fftSize, seg, endSeg);
|
||||||
app.bgHighResSeg = ComputeNextHighResChunk(&app.signal, &app.stft, app.fftSize, startSeg, endSeg);
|
fillingDirty = true;
|
||||||
app.visibleTextureValid = false;
|
break;
|
||||||
TraceLog(LOG_INFO, "Foreground high-res (%d to %d)", startSeg, endSeg - 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background high-res: when user is idle, fill in remaining
|
// Background: when idle, sweep the whole file from the cursor and
|
||||||
// segments at full resolution. Pauses on any interaction.
|
// compute any still-missing segment (already-computed ones are skipped
|
||||||
// Also kicks in when zoomed out (no foreground trigger) to fill
|
// cheaply). Done once the cursor passes the last segment.
|
||||||
// segments outside the view range.
|
if (app.stftComputed && !app.bgFinished && app.stft.numSegments > 0 &&
|
||||||
bool isZoomedIn = (app.skipFactor > 1 && app.view.end - app.view.start <= 0.25f);
|
!IsUserInteracting()) {
|
||||||
if (app.isBgProcessing && !app.bgFinished && !IsUserInteracting()) {
|
int endSeg = app.bgHighResSeg + 50; // chunks of 50 segments
|
||||||
int endSeg = app.bgHighResSeg + 50; // chunks of 50 segments
|
|
||||||
if (endSeg > app.stft.numSegments) endSeg = app.stft.numSegments;
|
if (endSeg > app.stft.numSegments) endSeg = app.stft.numSegments;
|
||||||
app.bgHighResSeg = ComputeNextHighResChunk(&app.signal, &app.stft, app.fftSize, app.bgHighResSeg, endSeg);
|
app.bgHighResSeg = ComputeNextHighResChunk(&app.signal, &app.stft, app.fftSize, app.bgHighResSeg, endSeg);
|
||||||
|
fillingDirty = true;
|
||||||
if (app.bgHighResSeg >= app.stft.numSegments) {
|
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.bgFinished = true;
|
||||||
app.isBgProcessing = false;
|
TraceLog(LOG_INFO, "Full-res fill complete (%d segments)", app.stft.numSegments);
|
||||||
TraceLog(LOG_INFO, "Background high-res complete (%d segments)", app.stft.numSegments);
|
SaveToCache(); // overwrite the overview-only cache entry
|
||||||
// Save the full-res result to cache (overwrites the overview-only entry)
|
|
||||||
SaveToCache();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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
|
// Stay awake while work remains so idle frames keep the sweep moving
|
||||||
if (!isZoomedIn && app.isBgProcessing && !app.bgFinished && app.bgHighResSeg < app.stft.numSegments) {
|
// (IsAppActive() checks this); let the loop sleep once it's done.
|
||||||
bool hasMissing = false;
|
app.isBgProcessing = !app.bgFinished;
|
||||||
for (int i = app.bgHighResSeg; i < app.stft.numSegments; i++) {
|
|
||||||
if (app.stft.segments[i].spectrum == NULL) { hasMissing = true; break; }
|
// Reveal freshly-filled segments. Reassigning the whole file is too
|
||||||
}
|
// costly to do every frame, so throttle during the sweep and force a
|
||||||
if (!hasMissing) {
|
// final pass at completion. Skipped while interacting to avoid a hitch
|
||||||
// No more missing segments — mark complete
|
// mid-gesture — the next idle frame repaints.
|
||||||
app.bgFinished = true;
|
if (fillingDirty && (app.bgFinished || !IsUserInteracting())) {
|
||||||
app.isBgProcessing = false;
|
static double lastFillColorize = 0.0;
|
||||||
SaveToCache();
|
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.stftComputed = true;
|
||||||
app.loadingPhase = 0; // Reset — background processing runs outside this block
|
app.loadingPhase = 0; // Reset — background processing runs outside this block
|
||||||
app.loadingProgress = 0.0f;
|
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)",
|
TraceLog(LOG_INFO, "STFT overview computed (%d segments, skipFactor=%d)",
|
||||||
app.stft.numSegments, app.skipFactor);
|
app.stft.numSegments, app.skipFactor);
|
||||||
// Save the overview result to cache (will be overwritten when full-res completes)
|
// Save the overview result to cache (will be overwritten when full-res completes)
|
||||||
|
|||||||
Reference in New Issue
Block a user