Skip to content

fix(spectrum): investigate FFT decode range drift on amplitude scale drag (#2384)#2387

Closed
M7HNF-Ian wants to merge 0 commit into
aethersdr:mainfrom
M7HNF-Ian:fix/spectrum-dbm-shift-on-scale-adjust
Closed

fix(spectrum): investigate FFT decode range drift on amplitude scale drag (#2384)#2387
M7HNF-Ian wants to merge 0 commit into
aethersdr:mainfrom
M7HNF-Ian:fix/spectrum-dbm-shift-on-scale-adjust

Conversation

@M7HNF-Ian

@M7HNF-Ian M7HNF-Ian commented May 5, 2026

Copy link
Copy Markdown
Contributor

Status

Investigation in progress. Original fix (lock decode range to first radio value) was incorrect and has been removed from this branch.

Problem

When the user adjusts the vertical amplitude scale, signal levels and noise floor shift by the same amount as the viewport delta — reported as a consistent 1:1 shift by LU5DX (Martin) and rnash2 (Randy Nash).

What we know so far

Root cause of original fix being wrong: FlexLib (Panadapter.cs) confirms the radio does re-encode FFT bins when min_dbm/max_dbm changes — LowDbm/HighDbm are updated immediately on the client side and the radio follows. Locking the decode range to the first value forces the decoder to use a stale range against freshly re-encoded bins, producing the exact 1:1 offset Jeremy observed on FLEX-8600 fw 4.1.5.

SpectrumWidget render path is not the cause: the render cleanly maps absolute dBm values to screen Y using m_refLevel/m_dynamicRange — no double application of the viewport range.

Likely remaining candidate: a sync issue between PanadapterStream's decode range and SpectrumWidget's display range. Both must track the radio's current encoding range in lockstep. If the decode range update (via status echo → handlePanadapterStatusPanadapterStream::setDbmRange) is delayed or arrives with different values than what AetherSDR sent, a permanent offset results.

Next step

Add diagnostic logging in decodeFFT to capture raw bin values before and after a viewport drag, confirming whether the radio's encoding changes and whether the decode range stays in sync.

Files modified

None — branch is currently clean pending the correct fix.

Reported by LU5DX (Martin) and rnash2 (Randy Nash) with reproducible steps.

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the careful root-cause analysis on the dBm shift, Ian — the report from LU5DX/rnash2, the decode-formula explanation, and the "radio doesn't re-encode on viewport change" finding are exactly what this kind of bug needs. The PanadapterStream fix itself is well scoped:

On the stated fix (PanadapterStream.{h,cpp})

  • Lock-on-first-call is the right shape, and clearing the entry in both unregisterPanStream and clearRegisteredStreams matches the existing per-stream cleanup pattern, so reconnect / pan-recreate paths are covered.
  • setDbmRange is already under m_streamMutex, so the new QSet access is correctly serialised — no extra locking concerns.
  • The inline comment is great — future readers will understand exactly why subsequent calls are a no-op.

Scope concern — please split this PR

The PR description and title only mention the two PanadapterStream files, but the diff actually changes nine files in three unrelated buckets:

  1. The advertised FFT decode-range fixPanadapterStream.cpp/h (good, ready as-is)
  2. TciServer crash-on-quit fixTciServer.h (QPointer<RadioModel>) and MainWindow.cpp (explicit delete m_tciServer in ~MainWindow). This is the exact change from your PR #2386 and is unrelated to #2384.
  3. New "Spot Lines" enable/disable featureDxClusterDialog.cpp, SpectrumWidget.cpp/h, SpotSettingsDialog.cpp/h, plus the two MainWindow.cpp hunks at lines ~5359 and ~8727 wiring setSpotShowLines. Neither the title nor the body mentions this feature, and it has no connection to issues #2384 / #2310.

It looks like the branch picked up commits from #2386 plus an in-progress Spot Lines change. Could you rebase fix/spectrum-dbm-shift-on-scale-adjust onto a clean main so this PR contains only the PanadapterStream change? The TciServer fix should land via #2386 on its own merits, and the Spot Lines toggle deserves its own PR with a description (and probably its own issue) so reviewers can evaluate the UX choice (default-on, two separate dialogs both writing IsSpotsLinesEnabled, etc.) independently.

Once split, the FFT fix here looks ready to go. Thanks again for the thorough investigation and the reproducible steps from the reporters.

@ten9876

ten9876 commented May 6, 2026

Copy link
Copy Markdown
Collaborator

Claude here on Jeremy's behalf — wanted to share hardware test results before this lands.

To get a clean test, I cherry-picked just the spectrum-fix commit (74195f25) onto current main, dropping the already-merged commits (#2349 Spot Lines toggle and #2386 TCI crash fix) and the two unrelated spot-lines naming commits — should let us evaluate the spectrum fix in isolation. Built clean (m_decodeRangeInitialized and the "decode range locked" debug strings confirmed present in the binary), Jeremy tested on his FLEX-8600 fw 4.1.5.

Result: the symptom is unchanged. Dragging the amplitude scale by 10 dB still moves the noise floor reading by 10 dB, 1:1 — same drift the original reporters saw.

The mechanical implication, since the lock is verifiably in the binary:

  • If the lock is being honored (which the debug log path says it is), then the decode range is staying at the initial radio-reported value while the radio's bin encoding is changing with the viewport.
  • That means the radio does re-encode FFT bins when min_dbm/max_dbm changes — opposite of the PR's premise. Locking the decode then makes the decoder use a stale range vs. what the radio is sending, producing an offset that matches the viewport delta exactly. Which is what Jeremy observed.

So either:

  1. The radio re-encodes on viewport change. In that case the original behavior (decode follows viewport) was correct, and the 30-40 dBm drift LU5DX and rnash2 reported has a different root cause. Maybe a math/units error in the decode formula in decodePanadapterTile, an off-by-one (yPix - 1 vs yPix), or a byte interpretation issue (signed vs unsigned, big- vs little-endian). Worth double-checking against a Wireshark capture of a known-amplitude signal.

  2. The radio doesn't re-encode (PR's premise), but our display path is doing the bin → screen mapping using viewport-relative math somewhere that we'd want to leave viewport-relative. In that case the lock is correct in concept but a parallel path is still drifting.

Either way the lock alone doesn't close out #2384/#2310 in practice — at least on the FLEX-8600 setup Jeremy's testing against. Would be useful to know what radio model the original reporters tested on; if Aurora vs. 6000 vs. 8000 series differ in their encoding behavior, that'd narrow it down.

The two spot-lines naming commits on this branch (b3d5cb84, 6b0f38be) look like fine cleanup, and address triage feedback you got on #2349. Worth opening as their own PR titled around "fix(spots): align Spot Lines naming with project conventions" — they'd merge cleanly on current main.

Holding the cherry-pick for now (reverted locally) until we have a clearer picture of which of the two cases above is in play. Happy to help dig if you want a Wireshark capture from Jeremy's test bench, or pair on the decode formula vs. FlexLib's Panadapter::ProcessFFTPacket.

73, Jeremy KK7GWY & Claude (AI dev partner)

ten9876 added a commit that referenced this pull request May 6, 2026
…2380) (#2394)

SpotHub (DX cluster / RBN) did not automatically reconnect after a Wi-Fi
drop or any other failed connection attempt. The app would try once when
the network came back, fail, and stop retrying — requiring a manual
reconnect.

Root cause: Qt's QAbstractSocket only emits disconnected() on a Connected
→ Unconnected transition. When a socket fails during ConnectingState
(host blocked, refused, or timed out), Qt emits errorOccurred but NOT
disconnected. The reconnect timer was only armed inside onDisconnected(),
so a single failed connect attempt permanently halted the backoff chain.

Two additional failure paths had the same gap: onSocketError() emitted
the UI error only and never re-armed the timer, and the 10-second connect
timeout lambda called abort() without scheduling a retry.

Fix: extracts a scheduleReconnect() helper called from all three failure
paths, with two guards — m_intentionalDisconnect (don't override
user-initiated disconnect) and m_reconnectTimer->isActive() (prevents
double-scheduling when errorOccurred and the connect timeout both fire
for the same failed attempt). A per-call epoch counter (m_connectEpoch)
captured by the timeout lambda ensures a stale timeout from attempt N
cannot abort a later attempt N+1 that has since succeeded.
connectToCluster() also now rejects calls when the socket is already in
ConnectingState to prevent overlapping attempts during rapid retries.

Backoff sequence is unchanged: 5 s → 10 s → 20 s → 40 s → 60 s (cap).

Cherry-picked from PR #2388 (M7HNF-Ian). The rest of that branch contained
already-merged commits (#2349 Spot Lines toggle, #2386 TCI crash fix), the
rejected #2387 spectrum fix, and two unrelated spot-lines naming-refinement
commits that belong in their own focused PR.

Co-authored-by: Ian M7HNF <idimmock@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@M7HNF-Ian

Copy link
Copy Markdown
Contributor Author

Thanks for the hardware test results — that's a clear outcome.

Dug into FlexLib (Panadapter.cs) to settle the re-encoding question. The design is unambiguous:

  • AddData fires raw ushort[] bins with no dBm conversion — the consumer applies LowDbm/HighDbm at render time
  • When the user sets LowDbm/HighDbm, FlexLib updates the local value immediately before sending the command — SetLowDbmReply returns early on success because the value was already applied
  • The model is fully optimistic: local range updates instantly, radio re-encodes, UI renders with the current range

The radio re-encodes on viewport change. The lock forces the decoder to use a stale range against freshly re-encoded bins, producing the exact 1:1 offset Jeremy observed. The lock needs to come out.

Also looked at SpectrumWidget's render path — it cleanly maps absolute dBm values to screen Y using m_refLevel/m_dynamicRange. No double application of the range there.

That leaves the actual cause of #2384 still open. The most likely remaining candidate is a sync issue: PanadapterStream's decode range and SpectrumWidget's display range both need to track the radio's current encoding range in lockstep. If they diverge — for example if the decode range update from the status echo is delayed or arrives with different values than what AetherSDR sent — you'd get a permanent offset. The diagnostic logging approach outlined above would pin down whether that's happening.

Will rebase the branch to drop the lock change.

@M7HNF-Ian M7HNF-Ian closed this May 6, 2026
@M7HNF-Ian M7HNF-Ian force-pushed the fix/spectrum-dbm-shift-on-scale-adjust branch from 74195f2 to 3af3df3 Compare May 6, 2026 07:20
@M7HNF-Ian M7HNF-Ian changed the title fix(spectrum): lock FFT decode range to initial radio values, not viewport (#2384) fix(spectrum): investigate FFT decode range drift on amplitude scale drag (#2384) May 6, 2026
@M7HNF-Ian M7HNF-Ian deleted the fix/spectrum-dbm-shift-on-scale-adjust branch June 7, 2026 14:58
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.

2 participants