Skip to content

fix: clamp panadapter center to prevent spectrum left edge below 0 Hz (#783)#2867

Open
NF0T wants to merge 1 commit into
aethersdr:mainfrom
NF0T:fix/pan-center-below-zero-hz
Open

fix: clamp panadapter center to prevent spectrum left edge below 0 Hz (#783)#2867
NF0T wants to merge 1 commit into
aethersdr:mainfrom
NF0T:fix/pan-center-below-zero-hz

Conversation

@NF0T
Copy link
Copy Markdown
Collaborator

@NF0T NF0T commented May 19, 2026

Summary

Fixes #783 — at MW/LW frequencies the spectrum waterfall could display a left edge below 0 Hz when dragging, zooming, or keyboard-navigating the panadapter.

Root cause: No lower-bound clamp existed on the panadapter center frequency. The left edge is center − bandwidth/2; when center drops below bandwidth/2, the left edge goes negative, displaying sub-zero spectrum that doesn't physically exist.

Fix: Apply center = std::max(center, bandwidth / 2.0) at every code path that writes a new center value — both to SpectrumWidget's local state and to the radio command.

Seven fix sites

src/gui/SpectrumWidget.cpp

  1. emitZoom lambda — zoom-in/out buttons, clamp newCenter after VFO-centering branch
  2. BW drag mouseMoveEvent — clamp zoomCenter (anchor-anchored center) after computing from drag position
  3. Pan drag mouseMoveEvent — clamp newCenter before m_centerMhz = and emit centerChangeRequested
  4. NativeGesture pinch zoom — clamp newCenter after anchor calculation

src/gui/MainWindow.cpp
5. applyPanRangeRequest — clamp centerMhz (passed by value) immediately after the null-guard; this is the central choke point for all frequencyRangeChangeRequested paths and keyboard zoom
6. zoomActivePanadapter lambda — clamp newCenter before sw->setFrequencyRange() to avoid a visual desync before the radio round-trip; applyPanRangeRequest (site 5) also clamps, providing defense-in-depth
7. centerChangeRequested handler — pan drag bypasses applyPanRangeRequest and sends directly to the radio; fetch bandwidth from the pan model and clamp before the sendCommand

No upper-bound clamp is applied — the radio enforces its own band limits.

Out-of-scope observation

During testing on a FLEX-8400, the waterfall displays solid red (out-of-band indicator) below approximately 200 kHz even after this fix. This appears to be model-specific hardware behavior — other Flex models may allow the receiver to tune lower. The red waterfall in that region is the radio correctly reporting no valid spectrum data, not a client-side rendering bug. This is left for a future investigation scoped to that model's receive floor.

Test plan

  • Set slice to 1.0 MHz AM (MW), narrow panadapter to 3 MHz bandwidth → zoom out: left edge stops at 0 Hz
  • Pan drag left past 0 Hz at 0.5 MHz → spectrum stops scrolling at the 0 Hz boundary
  • Keyboard [ zoom-out at 160m (1.8 MHz) until left edge would go below 0 Hz → clamps cleanly
  • Trackpad/NativeGesture pinch-zoom out at LW → same
  • Normal HF operation (14 MHz, 20 MHz BW) → no regression in zoom or pan behavior
  • Multi-panadapter: each pan is independently clamped (bandwidth fetched per-pan from model)

🤖 Generated with Claude Code

…splay

Seven sites across SpectrumWidget (zoom buttons, BW drag, pan drag,
NativeGesture pinch) and MainWindow (applyPanRangeRequest, keyboard
zoom, centerChangeRequested handler) now enforce std::max(center, bw/2)
so the left edge of the spectrum can never go below 0 Hz at MW/LW
frequencies. Fixes aethersdr#783.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@NF0T NF0T requested review from jensenpat and ten9876 as code owners May 19, 2026 00:00
Copy link
Copy Markdown
Contributor

@aethersdr-agent aethersdr-agent Bot left a comment

Choose a reason for hiding this comment

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

Thanks @NF0T — clean, well-scoped fix with an unusually thorough write-up. Verified all seven clamp sites against the current code:

  • MainWindow.cpp:9749 (applyPanRangeRequest) — clamp lands after the bandwidthMhz <= 0.0 guard, so division by zero is impossible. The applyPanStatus + sendCommand both see the clamped value. Correct.
  • MainWindow.cpp:10510 (centerChangeRequested handler) — properly null-guards the m_radioModel.panadapter() lookup. If pan is unexpectedly missing the unclamped value goes through, matching prior behavior; not a regression.
  • MainWindow.cpp:12572 (keyboard zoom-out) — defense-in-depth with site 5 is justified by the brief visual desync before the radio echo; agree the redundancy is worth it.
  • SpectrumWidget.cpp:408 (emitZoom), :3446 (BW drag), :3496 (pan drag), :3966 (pinch) — all four clamps applied at the natural choke point right after computing newCenter, before reproject/emit. Consistent.

The PR description's explanation that the BW-drag anchor will drift when the clamp activates (not preserved as anchor) is the right tradeoff — you can't both pin an anchor at low frequency AND keep the left edge ≥ 0 when bandwidth exceeds 2×anchor. The clamp winning is correct UX.

One small observation, no change requested: site 3 emits a center already clamped against m_bandwidthMhz / 2.0, and site 7 re-clamps against pan->bandwidthMhz() / 2.0. During a pure pan drag these are equal, so site 7 is effectively a no-op in that path — but it correctly covers other emitters (NativeGesture, BW drag) which can mutate bandwidth concurrently. Good defense-in-depth.

The FLEX-8400 sub-200 kHz red-waterfall observation is a sensible separate concern — definitely out of scope here.

LGTM.

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.

Spectrum / waterfall left edge goes below 0 hz, cursor not synched to actual frequency when zoomed in

1 participant