Skip to content

fix(waterfall): remove 1 GHz ceiling in VITA-49 tile frequency decode#3457

Merged
ten9876 merged 2 commits into
aethersdr:mainfrom
jensenpat:transverter-waterfall-1ghz
Jun 8, 2026
Merged

fix(waterfall): remove 1 GHz ceiling in VITA-49 tile frequency decode#3457
ten9876 merged 2 commits into
aethersdr:mainfrom
jensenpat:transverter-waterfall-1ghz

Conversation

@jensenpat

Copy link
Copy Markdown
Collaborator

Summary

The native waterfall goes all-black for any transverter operating above 1 GHz (1296 MHz, 2.3/2.4 GHz) while the FFT trace above it stays correct. This fixes the long-standing root cause behind #3449, #2835, #1928, and #1843 — none of which the previous attempts (#1845, #2709, #2853) could reach, because they were all editing the wrong layer.

Root cause

PanadapterStream::decodeWaterfallTile decoded the VITA-49 tile's FrameLowFreq / BinBandwidth by assuming VitaFrequency (Hz × 2²⁰), dividing, then rejecting any result > 1000.0 MHz and re-reading it as plain Hz:

double lowFreqMhz = static_cast<double>(frameLowRaw) / (1048576.0 * 1e6);
if (lowFreqMhz < 0.001 || lowFreqMhz > 1000.0) {   // ← hard 1 GHz ceiling
    lowFreqMhz = static_cast<double>(frameLowRaw) / 1e6;   // wrong scale → ×2²⁰ error

A real ~1296 MHz VitaFrequency value (raw ≈ 1.359e15) trips that ceiling, gets divided by 1e6 instead of 2²⁰·1e6, and is inflated by exactly 2²⁰ to ~1.359e9 MHz. Every waterfall bin then maps outside the panadapter range, so SpectrumWidget::updateWaterfallRow leaves the scanline qRgb(0,0,0) → entirely black. The FFT path has no frequency awareness, so it is unaffected. This ceiling has existed since the first native-waterfall commit (406dc100) and predates all of the XVTR work.

Why the previous fixes never worked

PRs #1845, #2709, and #2853 all edited the IF→RF remap layer (src/models/XvtrPolicy.cpp), which runs after the decode — it was merely shifting an already-corrupted value. A support bundle on #2835 (FLEX-6400, 1296 MHz, XVTA) shows it directly:

WaterfallXVTR: ... pan_center_mhz=1296.065804 tile_mhz=1358902484.271104..1359144312.111104

i.e. 1296 × 2²⁰. The bundle also shows the XVTR entry as valid=true rf=1296 if=28, so the isValid debate (#2709#2853) was a red herring — the value was destroyed six orders of magnitude upstream of any remap.

Changes

  • src/core/VitaTileFrequency.h (new) — extracts the VitaFrequency-vs-plain-Hz disambiguation into a pure, unit-testable helper that decides the encoding from the raw integer magnitude (>= 1e11 → VitaFrequency, else plain Hz). There is a clean gap between the two encodings across the entire usable spectrum (~135 kHz to ~100 GHz), so this imposes no upper frequency limit while still supporting radios that send plain Hz.
  • src/core/PanadapterStream.cpp — decoder now calls the helper.
  • tests/vita_tile_frequency_test.cpp + CMake target — regression coverage: HF, IF-domain, the 1000.015/1000.016 boundary from Waterfall breaks down on frequency higher than 1GHz #3449, 1296 / 2.3G / 2.4G, 2200 m, and plain-Hz radios.

Validation

  • ./build-ninja/vita_tile_frequency_test — all checks pass
  • ./build-ninja/xvtr_policy_test — still passes (remap layer untouched)
  • cmake --build build-ninja --target AetherSDR --parallel 22 — clean
  • Verified on hardware (FLEX-6400, synthetic 1296 MHz XVTR profile, XVTA antenna): logs now show sane tile_mhz (~1296, zero garbage tiles), and the waterfall renders the noise floor (dark blue) instead of going black. Reversing the exact corrupted raw value from the In/out XVTA Transverter No signal in waterfall #2835 bundle through the fixed decoder recovers 1295.95 MHz.

Known follow-up (out of scope)

A separate, pre-existing gap remains for transverters whose slice antenna is not named XVT* and whose offset doesn't match a configured XVTR profile: such a tile is left in its own domain and would still not align with the pan. That is independent of this decode bug (which affected every >1 GHz tile regardless of antenna) and is better handled in the remap layer in a follow-up.

Fixes #3449
Fixes #2835
Fixes #1928
Fixes #1843

💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat

…hersdr#3449)

The native waterfall went all-black for any transverter operating above
1 GHz (1296 MHz, 2.3/2.4 GHz) while the FFT trace stayed correct.

Root cause: PanadapterStream::decodeWaterfallTile decoded FrameLowFreq /
BinBandwidth by assuming VitaFrequency (Hz x 2^20), dividing, then
rejecting any result > 1000.0 MHz and re-reading it as plain Hz. A real
~1296 MHz VitaFrequency value (raw ~1.359e15) tripped that ceiling, got
divided by 1e6 instead of 2^20*1e6, and inflated by exactly 2^20 to
~1.359e9 MHz. Every waterfall bin then mapped outside the panadapter
range, so SpectrumWidget::updateWaterfallRow left the scanline
qRgb(0,0,0) -> entirely black. The ceiling has existed since the first
native-waterfall commit (406dc10) and predates all the XVTR work.

This is why the prior fixes never worked: PRs aethersdr#1845, aethersdr#2709 and aethersdr#2853 all
edited the IF->RF remap layer (XvtrPolicy.cpp), which runs *after* the
decode and was merely shifting an already-corrupted value. A support
bundle on aethersdr#2835 (FLEX-6400, 1296 MHz, XVTA) shows it plainly:
  tile_mhz=1358902484..1359144312  pan_center_mhz=1296.065804
i.e. 1296 x 2^20. The XVTR was already valid=true, so the isValid debate
was a red herring.

Fix: extract the VitaFrequency-vs-plain-Hz disambiguation into a pure,
unit-testable helper (src/core/VitaTileFrequency.h) that decides the
encoding from the raw integer magnitude (>= 1e11 -> VitaFrequency, else
plain Hz). There is a clean gap between the two encodings across the
entire usable spectrum (~135 kHz to ~100 GHz), so this imposes no upper
frequency limit while still supporting radios that send plain Hz.

Verified on hardware (FLEX-6400, synthetic 1296 XVTR profile, XVTA):
logs now show sane tile_mhz (~1296, zero garbage tiles) and the
waterfall renders the noise floor instead of going black.

Fixes aethersdr#3449
Fixes aethersdr#2835
Fixes aethersdr#1928
Fixes aethersdr#1843

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Co-authored-by: Codex <noreply@openai.com>
@jensenpat jensenpat marked this pull request as ready for review June 8, 2026 04:41
@jensenpat jensenpat requested review from a team as code owners June 8, 2026 04:41
std::llabs requires <cstdlib>; with only <cmath> included it compiled on
clang/libc++ (macOS) but failed on GCC/libstdc++ (Linux CI):
  error: 'llabs' is not a member of 'std'
Switch to Qt's qAbs (already available via <QtGlobal>), which also drops
the explicit cast, and remove the now-unused <cmath> include.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Co-authored-by: Codex <noreply@openai.com>
@ten9876 ten9876 self-assigned this Jun 8, 2026
@ten9876 ten9876 merged commit 789540b into aethersdr:main Jun 8, 2026
6 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

2 participants