Keep FFT scale aligned during dBm drag#2593
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes a visual misalignment during right-side dBm scale dragging by ensuring the FFT stream decoder’s dBm mapping updates immediately (optimistically) during the drag, rather than waiting for the radio’s min/max echo-back. It also avoids reapplying unchanged min/max status updates back into the stream decoder, preventing stale/no-op status cycles from “rewinding” the decoder’s range mid-drag.
Changes:
- Optimistically updates
PanadapterStreamdBm range on eachSpectrumWidget::dbmRangeChangeRequestedbefore sending thedisplay pan set ... min_dbm/max_dbmcommand. - Updates
RadioModel::handlePanadapterStatus()to only push min/max intoPanadapterStreamwhen the pan’s confirmed min/max actually changed.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/models/RadioModel.cpp | Adds change detection around confirmed min/max status so PanadapterStream only updates its cached dBm range when the pan model’s range changes. |
| src/gui/MainWindow.cpp | Applies an optimistic PanadapterStream::setDbmRange() during user drag events so FFT decoding stays aligned with the locally-updated scale. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
12235d9 to
3886d34
Compare
ten9876
left a comment
There was a problem hiding this comment.
Claude here, reviewing on Jeremy's behalf.
Verdict
Real fix for a real visible bug, with mechanism that's appropriately distributed across the layers involved. Recommend merge after Jeremy verifies the drag feel on the FLEX-8600, since this is the kind of fix where "looks right" matters more than any test can say.
The bug, restated
User drags the right-side dBm scale → SpectrumWidget updates m_refLevel/m_dynamicRange immediately (scale numbers slide visibly), but the FFT bin → screen-Y mapping in PanadapterStream::scaleBin() is still using the previous dBm range until the radio echoes back the new min_dbm/max_dbm via panadapter status. During that ~one round-trip window, the scale numbers and the FFT line are out of sync. Fast drags + mouse release amplify the race: stale radio frames may render with the old range and overshoot.
The fix is genuinely multi-layered
Five coordinated mechanisms working together:
-
No spamming during drag — the
emit dbmRangeChangeRequestedinsidemouseMoveEventis removed. OnlydbmRangeDragFinishedfires on release. Avoids hammeringdisplay pan setover TCP at mouse-move rate. -
setDbmRange(..., waitForEcho=true)onPanadapterStream— backwards-compat-default new parameter. When set, stashes the range as "pending" and ignores subsequent stalesetDbmRange(false)calls until an echo within 0.01 dB matches. Map cleanup inunregisterPanStream/clearRegisteredStreamsis correct. -
Symmetric pending-echo guard in
SpectrumWidget::setDbmRange— same idea on the GUI side. If a request is pending, only the matching echo unfreezes display updates. -
pendingDbmshared between MainWindow connect handlers viastd::shared_ptr<PendingDbmRange>captured in 3 lambdas. Coordinates pending state acrosslevelChanged(radio echo arrives),dbmRangeChangeRequested(arrow/keyboard change), anddbmRangeDragFinished(mouse release). If a non-matching echo arrives, the handler re-sends the request — this is the recovery path for "radio rejected our range, snapped to its own value." -
Post-release render correction —
m_holdFftUpdatesAfterDbmRelease = 10frames during which incoming bins are adjusted bym_dbmReleasePreviewOffsetif they match the expected shift (within tolerance). Uses median-of-deltas to detect when the radio's frame finally reflects the new range. Bounds the correction window to prevent runaway adjustment.
Plus the cosmetic-but-important polish:
m_resetFftSmoothingOnNextFrameflag clears the EMA accumulator on range change so old/new mappings don't blend through the smoothing buffer.kMinDisplayDbm = -180.0fclamp applied consistently at all 4 entry points (direct, drag, arrow, noise-floor auto-adjust). Matches SmartSDR floor.RadioModel::handlePanadapterStatusnow short-circuits redundantsetDbmRangecalls when the value didn't actually change (captures previous viapan->minDbm()beforepan->applyPanStatus(kvs), compares to post-apply — correct ordering).
Stale-code audit
- ✓ Old
connect(pan, levelChanged, sw, &SpectrumWidget::setDbmRange)replaced with guarded lambdas in both wiring sites - ✓ Old
mouseMoveEventemit ofdbmRangeChangeRequestedremoved cleanly; replaced with newdbmRangeDragFinishedinmouseReleaseEvent - ✓
m_pendingDbmRangescleaned up on unregister/clearRegistered paths - ✓
kMinDisplayDbmapplied consistently (4 sites) - ✓
setDbmRangeparameter addition is backwards-compatible (waitForEcho = falsedefault) - ✓ New signals/members declared in header, used in cpp
- ✓ Built locally — clean except for pre-existing unrelated warnings
- ✓ All 5 CI checks green
Concerns worth flagging (none blocking)
1. Heuristic numbers chosen by feel
m_holdFftUpdatesAfterDbmRelease = 10frames — at ~25 fps that's ~400 ms, reasonable for an Ethernet radio echo but might be tight over SmartLink with high RTT- Tolerance
qMax(1.5f, std::abs(offset) * 0.35f)for delta-match detection - Sampling step
qMax(1, binsDbm.size() / 256)for the delta vector 0.01fthreshold for "dBm values match"
These work for typical setups. Edge cases (very fast drag, very busy band where signal peaks dominate the median, high-latency SmartLink) could misclassify the post-release frames. Not a blocker — the fallback (correction times out after 10 frames, then the radio's actual values take over) is graceful.
2. State complexity
6 new members in SpectrumWidget for this one feature:
m_resetFftSmoothingOnNextFrame
m_pendingDbmRangeEcho
m_holdFftUpdatesAfterDbmRelease
m_dbmReleasePreviewOffset
m_pendingMinDbm
m_pendingMaxDbm
Plus the std::shared_ptr<PendingDbmRange> shared between 3 lambdas in wirePanadapter, plus the m_pendingDbmRanges map in PanadapterStream. Future contributors touching dBm range code need to understand all 4-5 coordinated mechanisms.
A DbmRangeCoordinator helper class encapsulating the pending-echo + post-release-correction state could simplify the SpectrumWidget surface. Not blocking — but worth a future cleanup if more "drag immediately, radio echoes async" patterns show up.
3. No tests
Hard to test in isolation without mocking the radio echo. Worth manually verifying:
- Slow drag → scale and FFT stay aligned throughout
- Fast drag-and-release → no visible jump at release (the 10-frame correction window catches this)
- Drag past -180 dBm → clamps correctly
- Drag on a busy band with strong signals → median heuristic doesn't misbehave
- Arrow-key dBm adjust (separate code path, also clamped to -180) — still works
- Multi-pan: drag pan A's scale, confirm pan B's scale doesn't twitch
- SmartLink (high RTT): the 10-frame correction window is short enough that the radio echo might arrive after the correction expires. Worth checking what the visual is in that case.
Recommendation
Merge after Jeremy validates on the FLEX-8600 — specifically the visual smoothness of fast-drag-and-release, since that's the case the post-release correction is built for. The mechanism is correct in principle; whether the heuristic numbers feel right is the kind of thing only live testing can confirm.
Thanks @rfoust — diagnosing the radio-echo race correctly and building a layered fix (no-spam during drag + pending-echo guards on both client and stream + smoothing reset + bounded post-release correction) is exactly the right shape for this class of distributed-state coordination bug. The complexity is in proportion to the underlying difficulty.
73, Jeremy KK7GWY & Claude (AI dev partner)
ten9876
left a comment
There was a problem hiding this comment.
Approved — well-architected multi-layer fix for the dBm drag race.
Summary
This updates the spectrum dBm-scale drag path so the on-screen scale, FFT line rendering, and FFT stream dBm conversion stay coordinated while the user drags the right-side dBm scale. It also clamps the lower display limit to
-180 dBm, matching the floor observed in SmartSDR-style behavior.Bug
Dragging the right-side dBm scale updated the widget's displayed dBm range immediately, but FFT frames were still decoded through the panadapter stream's previous dBm range until the radio echoed the new
min_dbm/max_dbmvalues. That created visible mismatch between the right-side scale and the FFT line. Fast drags and mouse release made the race more obvious: stale radio echoes or FFT frames could briefly render with an old range, causing the line to jump or overshoot.Fix
display pan setcommands.min_dbm/max_dbmcommand only when the drag finishes.PanadapterStreamwith pending dBm ranges and ignore stale nonmatching echo-backs while waiting for the requested final range.levelChangedupdates through guarded lambdas so stale or cross-pan updates do not overwrite an in-flight user drag.-180 dBmfor direct range changes, drag, arrow adjustment, and noise-floor auto-adjust.Testing
cmake --build build