Skip to content

Keep FFT scale aligned during dBm drag#2593

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
rfoust:codex/fix-fft-scale-alignment
May 12, 2026
Merged

Keep FFT scale aligned during dBm drag#2593
ten9876 merged 1 commit into
aethersdr:mainfrom
rfoust:codex/fix-fft-scale-alignment

Conversation

@rfoust

@rfoust rfoust commented May 11, 2026

Copy link
Copy Markdown
Collaborator

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_dbm values. 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

  • Keep dBm drag movement local while the mouse is down so fast dragging does not spam display pan set commands.
  • Send the final min_dbm/max_dbm command only when the drag finishes.
  • Update PanadapterStream with pending dBm ranges and ignore stale nonmatching echo-backs while waiting for the requested final range.
  • Route per-pan levelChanged updates through guarded lambdas so stale or cross-pan updates do not overwrite an in-flight user drag.
  • Reset FFT smoothing when the dBm range changes so range transitions do not blend old and new display mappings.
  • Add a bounded post-release render correction for FFT frames that are still shifted by the drag delta.
  • Clamp the lower dBm display limit to -180 dBm for direct range changes, drag, arrow adjustment, and noise-floor auto-adjust.

Testing

  • cmake --build build

Copilot AI review requested due to automatic review settings May 11, 2026 19:31
@rfoust rfoust requested review from jensenpat and ten9876 as code owners May 11, 2026 19:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 PanadapterStream dBm range on each SpectrumWidget::dbmRangeChangeRequested before sending the display pan set ... min_dbm/max_dbm command.
  • Updates RadioModel::handlePanadapterStatus() to only push min/max into PanadapterStream when 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.

@rfoust rfoust marked this pull request as draft May 11, 2026 20:27
@rfoust rfoust force-pushed the codex/fix-fft-scale-alignment branch from 12235d9 to 3886d34 Compare May 11, 2026 23:13
@rfoust rfoust marked this pull request as ready for review May 12, 2026 00:03

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. No spamming during drag — the emit dbmRangeChangeRequested inside mouseMoveEvent is removed. Only dbmRangeDragFinished fires on release. Avoids hammering display pan set over TCP at mouse-move rate.

  2. setDbmRange(..., waitForEcho=true) on PanadapterStream — backwards-compat-default new parameter. When set, stashes the range as "pending" and ignores subsequent stale setDbmRange(false) calls until an echo within 0.01 dB matches. Map cleanup in unregisterPanStream/clearRegisteredStreams is correct.

  3. 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.

  4. pendingDbm shared between MainWindow connect handlers via std::shared_ptr<PendingDbmRange> captured in 3 lambdas. Coordinates pending state across levelChanged (radio echo arrives), dbmRangeChangeRequested (arrow/keyboard change), and dbmRangeDragFinished (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."

  5. Post-release render correctionm_holdFftUpdatesAfterDbmRelease = 10 frames during which incoming bins are adjusted by m_dbmReleasePreviewOffset if 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_resetFftSmoothingOnNextFrame flag clears the EMA accumulator on range change so old/new mappings don't blend through the smoothing buffer.
  • kMinDisplayDbm = -180.0f clamp applied consistently at all 4 entry points (direct, drag, arrow, noise-floor auto-adjust). Matches SmartSDR floor.
  • RadioModel::handlePanadapterStatus now short-circuits redundant setDbmRange calls when the value didn't actually change (captures previous via pan->minDbm() before pan->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 mouseMoveEvent emit of dbmRangeChangeRequested removed cleanly; replaced with new dbmRangeDragFinished in mouseReleaseEvent
  • m_pendingDbmRanges cleaned up on unregister/clearRegistered paths
  • kMinDisplayDbm applied consistently (4 sites)
  • setDbmRange parameter addition is backwards-compatible (waitForEcho = false default)
  • ✓ 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 = 10 frames — 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.01f threshold 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 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — well-architected multi-layer fix for the dBm drag race.

@ten9876 ten9876 merged commit 5b07f21 into aethersdr:main May 12, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants