diff --git a/src/render.c b/src/render.c index 7d5dad1..a60e410 100644 --- a/src/render.c +++ b/src/render.c @@ -965,7 +965,9 @@ static unsigned char AlphaForEvent(int eventIndex, float fillMultiplier) // "BULK 3/4 3/6" or "PRESENCE/SEED". Falls back to the producer note. static void FormatTxFrameLabel(const MlnlEvent* e, char* out, int cap) { - char pos[16] = { 0 }; + // Sized for the worst case " %d/%d" with two full-width ints (2 + 11 + 1 + // + 11 + NUL = 26) so -Wformat-truncation stays quiet at -O2. + char pos[28] = { 0 }; if (e->has_seqn && e->nFrames > 1) snprintf(pos, sizeof(pos), " %d/%d", e->seq + 1, e->nFrames); @@ -1036,7 +1038,9 @@ void DrawAnnotations(Rectangle bounds) Color stroke = (Color){ c.r, c.g, c.b, strokeA }; DrawRectangleRec(r, (Color){ c.r, c.g, c.b, fillA }); DrawRectangleLinesEx(r, 1.5f, stroke); - char lbl[64]; + // Sized to hold the longest base (note[96]) + rate + position without + // truncation; DrawBoxLabel scissor-clips it to the box width anyway. + char lbl[160]; FormatTxFrameLabel(e, lbl, sizeof(lbl)); DrawBoxLabel(r, lbl, stroke, /*topInside=*/true); diff --git a/src/spectrogram.c b/src/spectrogram.c index dd7947d..e034b0c 100644 --- a/src/spectrogram.c +++ b/src/spectrogram.c @@ -70,6 +70,36 @@ static bool IsUserInteracting(void) return false; } +// Idle FPS throttle. raylib re-renders the whole scene every frame regardless +// of whether anything changed, so an idle window still pins the GPU (and, on a +// software GL stack, the CPU) at 60 fps. We drop to IDLE_FPS whenever nothing +// needs animating, and snap back to ACTIVE_FPS the moment activity resumes. +#define ACTIVE_FPS 60 +#define IDLE_FPS 10 +#define IDLE_GRACE_SECONDS 0.5 // stay at full rate briefly after the last activity + +/** + * Returns true if the frame must keep redrawing at full rate: live input, a + * moving mouse (hover readouts/tooltips), playback, in-progress loading or + * background STFT, an active drag/pan/divider, or a counting-down notice. + * Everything else is a static frame we can throttle. + */ +static bool IsAppActive(void) +{ + if (IsUserInteracting()) return true; + Vector2 d = GetMouseDelta(); + if (d.x != 0.0f || d.y != 0.0f) return true; // hover / cursor readout + if (IsWindowResized()) return true; + if (app.isPlaying) return true; // playhead is moving + if (app.loaded && !app.stftComputed) return true; // STFT still loading + if (app.isBgProcessing && !app.bgFinished) return true;// background high-res fill + if (app.view.isPanning || app.isDividing) return true; + if (app.sel.isDragging || app.sel.isTimeSelecting || app.sel.isFreqSelecting) return true; + if (app.marker.dragging) return true; + if (app.exportMessageTimer > 0.0f) return true; // notification countdown + return false; +} + // Fraction of the view area used by the spectrogram. When the scope is hidden // the spectrogram fills the whole area (divider at the bottom); otherwise the // scope takes the remainder below dividerY. @@ -751,6 +781,28 @@ int main(int argc, char* argv[]) #ifdef __EMSCRIPTEN__ // Track the browser viewport (fill + reflow on resize, like desktop). SyncCanvasToWindow(); +#else + // When the window isn't focused there's nothing for the user to see, so + // skip the entire frame: pump window events (so focus regain and the + // close button still register) and idle, with zero drawing. The + // headless render uses a hidden window that never reports focus, so it + // is exempt — otherwise it would never draw its single capture frame. + if (!headless && !IsWindowFocused()) { + PollInputEvents(); + WaitTime(0.1); // ~10 Hz event poll, no rendering + continue; + } + + // Idle FPS throttle (desktop only; the browser drives RAF/vsync itself). + // Keep full rate while active or within a short grace window after the + // last activity, then fall back to a low idle rate. + { + static double lastActive = -1000.0; + static int curFps = ACTIVE_FPS; + if (IsAppActive()) lastActive = GetTime(); + int wantFps = (GetTime() - lastActive < IDLE_GRACE_SECONDS) ? ACTIVE_FPS : IDLE_FPS; + if (wantFps != curFps) { SetTargetFPS(wantFps); curFps = wantFps; } + } #endif // Drag & Drop