[codex] Smooth FFT trace and clamp display scale#3847
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves SpectrumWidget’s spectrum rendering and dBm scale interaction by reducing per-frame allocations/copies in the FFT display trace path and clamping UI-generated dBm ranges before they’re applied/emitted, preventing invalid scale requests during drag interactions.
Changes:
- Reworks
buildFftDisplayTrace()to return aconst QVector<float>&backed by reusable scratch buffers, avoiding per-frameQVectorcopies in the display-only smoothing/resampling path. - Adds centralized dBm-range clamping helpers and applies them during dBm scale press/drag/release and immediate range application to prevent out-of-bounds/invalid ranges.
- Skips GPU FFT-line VBO updates (and line drawing) when the FFT line width is disabled.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/gui/SpectrumWidget.h | Updates buildFftDisplayTrace() signature and adds scratch buffers for display smoothing/resampling. |
| src/gui/SpectrumWidget.cpp | Implements scratch-backed FFT trace generation, clamps dBm scale interactions, and avoids unnecessary GPU VBO updates when line width is off. |
There was a problem hiding this comment.
Thanks @rfoust — this is a tidy, well-scoped change to SpectrumWidget. I traced both halves against the surrounding code and it holds up. CI is green across all six checks (build, macOS, Windows, CodeQL, accessibility).
What I verified
Scratch-buffer reuse (buildFftDisplayTrace) — Correct. The two scratch buffers don't alias: m_fftDisplaySmoothScratch (smoothing) is read and m_fftDisplayTraceScratch (resample) is written in the trace path, and the input bins (displaySpectrumBins() → m_smoothed/m_kiwiSdrFftTrace) is never one of the scratch vectors. Both call sites (renderGpuFrame, drawSpectrum) are compile-gated by AETHER_GPU_SPECTRUM so only one is live, and each invokes the function once per frame and consumes the result before anything else touches the buffers — so the reference return is safe today.
Line-VBO skip — Safe. The new if (m_fftLineWidth > 0.0f) guard on updateDynamicBuffer(m_fftLineVbo, …) mirrors the existing draw-side guard at the line pass (if (m_fftLineWidth > 0.0f) before cb->draw(...) on m_fftLineVbo), so the buffer is never drawn stale when the update is skipped.
dBm clamp helpers — The clamp math is robust: clampDbmRange handles non-finite inputs, inverted min/max ordering, and degenerate equal-value cases, always converging to a valid [10, 180] dB span within [-180, 80] dBm. The arrow-click / drag / release paths now route through these consistently, and the release path correctly bails (resets drag flags, no emit) on non-finite state rather than emitting a garbage range. This matches the stated goal of not sending invalid operator scale requests.
One minor note (non-blocking)
buildFftDisplayTrace changed from returning by value to returning const QVector<float>& into shared mutable scratch. It's safe for all current callers, but it's now a footgun if a future caller ever holds two results live (const auto& a = build(...); const auto& b = build(...); — a would be clobbered). A one-line comment on the declaration noting "result is valid only until the next call" would save a future maintainer the surprise. Your call — not a blocker.
Nice cleanup overall. 👍
🤖 aethersdr-agent · cost: $2.9860 · model: claude-opus-4-8
The trace builder now returns a reference into shared mutable scratch buffers (m_fftDisplaySmoothScratch / m_fftDisplayTraceScratch), so the result is only valid until the next call. Document that on the declaration so a future caller doesn't hold two results live and silently clobber the first. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ten9876
left a comment
There was a problem hiding this comment.
Approved. Verified both halves independently against the diff and call sites:
- Scratch-buffer reuse — the two scratch buffers don't alias each other or the input (
displaySpectrumBins()), and both call sites (renderGpuFrame,drawSpectrum) consume the result in place with no two-results-live, so the transient-reference return is safe. (Documented that contract on the declaration in a follow-up commit.) - dBm clamp — robust across the edge cases (inverted, equal, span >180 / <10, non-finite); all interaction paths (arrow, control-drag, pan-drag, move, release) route through the helpers consistently, and the release path correctly bails without emitting a garbage range.
Display-only; Principle II honored (clamps UI-generated requests before sending, never overrides radio-authoritative state). CI green. Thanks @rfoust — clean, well-scoped work.
Summary
Improves the zoomed FFT trace rendering and tightens dBm scale handling in
SpectrumWidget. The FFT display trace now uses reusable scratch buffers for the display-only smoothing path, avoiding per-frame vector copies, and skips FFT-line VBO updates when the line width is disabled. The dBm scale interaction now clamps generated ranges before applying or emitting them, preventing invalid spectrum scale requests such as overly high max dBm values during manual scale drags.Constitution principle honored
Principle II - radio status remains authoritative; this change only validates local UI-generated dBm ranges before sending operator-requested scale changes.
Test plan
cmake --build build --target AetherSDR -j8)ctest --test-dir build --output-on-failure -R "vita_tile_frequency_test|profile_load_command_test|kiwi_sdr_trace_math_test"git diff --check HEAD~1 HEADChecklist
docs/COMMIT-SIGNING.md)AppSettingscalls - use nested-JSON-under-one-key (Principle V)MeterSmoother(AGENTS.md convention): not applicable