Skip to content

HL2: panadapter SQL line and Auto SQL use the Flex −160+level scale; the HL2 amsq gate is −140+0.7·level dBFS #6092

Closed

Description

@ten9876

Refs #5678, #5982.

Problem

#5982 gives the HL2 a receive squelch. On SSB and AM it uses WDSP's level squelch (amsq), whose gate opens at −140 + 0.7·level dBFS. on8st measured that map on a real HL2, and manual SQL is verified on the radio.

The shared GUI assumes a different scale. Both the panadapter SQL line and Auto SQL treat a level as −160 + level, one dB per step:

  • src/gui/SpectrumWidget.cpp:4568 draws the line at kSqlMinDbm + level, where kSqlMinDbm = -160.
  • SpectrumWidget::updateAutoSquelchFromBins (around line 4654) picks level = floor + margin + 160.
  • Auto SQL is not gated by any capability, so it runs on the HL2 (MainWindow::autoSquelchShouldRunOnSpectrum, src/gui/MainWindow_Wiring.cpp:1356).
  • HL2 panadapter bins are dBFS shifted by the LNA reference. Hl2Backend.cpp:1736 already says the absolute levels "must not be compared, spotted or thresholded", and Auto SQL does exactly that.

Where the line is drawn versus where the gate actually is:

level line drawn at amsq gate
20 −140 −126 dBFS
50 −110 −105 dBFS
~67 the two cross
100 −60 −70 dBFS

Auto SQL, worked out from the code (not measured):

  • Quiet band, floor −116 with a 10 dB margin: it sends level 54, and the gate lands at −102, 14 dB above the floor.
  • Busy 40 m band, floor −80 with a 10 dB margin: it sends level 90, and the gate lands at −77, only 3 dB above the floor.

So on the HL2 the line is misplaced and Auto SQL does not hold the margin the operator chose. A matching slope would probably still not make the line exact: the bins are per FFT bin and LNA-referenced, while amsq measures the whole passband. That last point is reasoning, not a measurement.

This is not HL2-only. Icom maps its 0..100 slider onto CI-V 0..255 (IcomControls.cpp:169). Kiwi already has its own branch in updateAutoSquelchFromBins because its scale is server-relative. The −160+level assumption is Flex's.

Fix shapes (maintainer call)

  • B: move the HL2 amsq map onto the GUI scale (-160 + level dBFS). Tested as a mutation on feat(hl2): receive squelch per mode family, with an HL2-measured threshold map. Principle II. #5982: the audio-gate test still passes at level 50, and only the exact-value checks change. But it overrides a hardware-measured calibration without re-measuring, and the line still only lines up when the LNA offset is 0.
  • D: hide the line and Auto SQL when panAmplitude.calibratedDbm is false. No new capability and no family branch, but the HL2 loses both features.
  • Backend-published level→dB mapping, as a maintainer-reviewed capability record that SpectrumWidget uses for the line and for Auto SQL. This would also cover Icom and Kiwi, but it is architecture work.

The maintainer landed #5982 with on8st's measured map unchanged and this as the follow-up.

Activity

  1. added
    claude-activeAetherClaude is actively working on this issue
    GUIUser interface
    spectrumPanadapter and waterfall
    maintainer-reviewRequires maintainer review before any action is taken
    on Oct 2, 2026
  2. aethersdr-agent commented on Oct 2, 2026

    @aethersdr-agent
    Contributor

    Triage: maintainer-review. Every code claim in the report checks out against main (7975baf). Two corrections to the framing, both of which change the handoff rather than the conclusion.

    Verified in the tree

    • src/gui/SpectrumWidget.cpp:4534 constexpr float kSqlMinDbm = -160.0f, and :4568 draws the line at squelchDbm = kSqlMinDbm + m_flexSquelchLevel. The member name says Flex, but it is the non-Kiwi path for every family.
    • updateAutoSquelchFromBins (:4610) has exactly one family branch — Kiwi, at :4616-4626, returning m_autoSqlMarginDb raw because the server scale is median-RSSI-relative. Everything else falls through to :4654-4659: level = clamp(int(floor + margin + 160 + 0.5), 1, 100). The arithmetic in the issue's two worked examples is right.
    • No capability gates either feature. MainWindow::autoSquelchShouldRunOnSpectrum (src/gui/MainWindow_Wiring.cpp:1356-1384) gates on Kiwi assignment and RxApplet::SqlMode::Auto only.
    • Hl2Backend.cpp:1734-1742 sets amplitude.calibratedDbm = m_dbRef.isCalibrated() (false — nothing in src/ calls setFullScaleDbm()) and states the absolute levels "must not be compared, spotted or thresholded." Auto SQL thresholds them.

    Correction 1: #5982 has not landed

    The body says "The maintainer landed #5982 with on8st's measured map unchanged." On main today #5982 is open, and the map is not in the tree: grep -ri amsq src/ returns nothing (only third_party/wdsp/upstream/amsq.h), git log --all --grep 5982 is empty, and Hl2Backend.cpp:1762 still describes the no-squelch state. The HL2 has no RX squelch at all right now.

    That does not invalidate the issue — the −160 assumption is a real defect on main — but it splits the work across two bases, and authorizing it as one change against main would produce a PR that can't test its own premise:

    Correction 2: shape D does not cover Icom

    RadioCapabilities::dbmAxisIsCalibrated() is !panAmplitude || panAmplitude->calibratedDbm (RadioCapabilities.h:235-238) — absent record means calibrated, deliberately, to avoid restating a claim about unread backends. The header notes the Icom scope's record is absent, so the accessor returns true for Icom. Hiding on calibratedDbm == false therefore covers HL2, ANAN (AnanBackend.cpp:381) and RTL (RtlSdrBackend.cpp:111) and leaves the Flex-scaled line in place on Icom, whose squelch is 0..255 percent with no published dB mapping (src/core/backends/icom/IcomControls.cpp:169, and cf. the sibling rf.gain comment at :162-168). D is a valid interim, but it does not earn the issue's "this is not HL2-only" claim without also giving Icom a PanAmplitudeModel record — which is a separate maintainer call about a backend nobody has measured.

    Recommended shape

    The backend-published level→dB mapping is the right fix, and the seam already exists with a template to copy:

    • PanAmplitudeModel gains an optional level→dB mapping (offset + slope, or an explicit "unmapped"). An std::optional<...> record is not a bool, so this is not a capability-ratchet event.
    • SpectrumWidget mirrors it the way it already mirrors binsAbsolute — setPanBinsAbsolute / m_panBinsAbsolute at SpectrumWidget.h:358-363, with the comment there explaining why the widget takes a pushed value instead of reading caps.
    • Pushed from the existing per-connect caps block at src/gui/MainWindow.cpp:7563-7570, which already fans radioOwnsDbmScale and panBinsAbsolute() out over findChildren<SpectrumWidget*>() with a !connected || reset.
    • :4568 and :4654 use the mapping; absent mapping hides the line and suppresses Auto SQL (shape D's behaviour, as the fallback rather than the whole fix). Flex declares −160 + 1·level and keeps today's behaviour; HL2 declares −140 + 0.7·level; Icom and Kiwi declare unmapped until someone measures them.

    Not B. Rewriting the HL2 amsq map to −160 + level overwrites a hardware-measured calibration to satisfy a GUI constant, and by the issue's own table it would have put level 50 at −110 dBFS instead of the measured-good −105. The manual SQL that on8st verified on the radio is the part that currently works; it should not be the part that moves.

    The issue's closing caveat is worth keeping in the eventual PR description: even a correct slope will not make the line exact, because the bins are per-FFT-bin and LNA-referenced while amsq integrates the whole passband. The goal is a line that is right to within the operator's margin, not one that is exact.

    🤖 Generated with Claude Code


    🤖 aethersdr-agent · cost: $2.6475 · model: claude-opus-5


    📍 Citation health check (Foundry Constitution Principle I): 4 resolved, 6 unresolved against /Users/aetherclaude/workspace/AetherSDR at origin/main.

    Unresolved citations (6)
    Citation Reason
    AnanBackend.cpp:381 file not found in workspace
    Hl2Backend.cpp:1734-1742 file not found in workspace
    Hl2Backend.cpp:1762 file not found in workspace
    RadioCapabilities.h:235-238 file not found in workspace
    RtlSdrBackend.cpp:111 file not found in workspace
    SpectrumWidget.h:358-363 file not found in workspace
  3. ten9876 commented on Oct 3, 2026

    @ten9876
    CollaboratorAuthor

    @on8st, one question before we fix this. It's about the squelch map you measured on the HL2 for #5982.

    What we found: the error is larger than this issue says. HL2 pan bins are binDbfs + offsetDb() (Hl2Backend.cpp:850), where offsetDb = fullScaleDbm − LNA, i.e. −17 dB at the default 20 dB LNA. The amsq gate (−140 + 0.7·level dBFS, WdspChannel.cpp:649) doesn't carry that offset.

    • On the displayed axis the line sits −23 + 0.3L + G dB off the gate: about +12 dB at level 50 with G = 20.
    • Because the gate is fixed in raw dBFS, changing RF gain moves the antenna-referred point where squelch opens.

    The plan (my decision):

    1. A backend-published squelch-scale record, so the panadapter line is drawn on the HL2's own scale.
    2. Refer the HL2 amsq map to the LNA, the way the AGC ceiling already is: gate = −140 + 0.7L − lnaOffsetDb(). That reproduces your map exactly at the LNA gain you measured at, and holds it when RF gain moves.
    3. Auto SQL stays hidden on the HL2 until it measures the floor at the detector instead of from pan bins. Pan noise per bin sits about 9 dB below what amsq sees at 48 kHz.

    The question: what RF/LNA gain (dB) was the HL2 at when you measured the map, i.e. level 50 opening at −105 dBFS and the broadcast-station check? We'll anchor the referral at that gain so your calibration is preserved exactly. If it was the default (+20), say so and we'll go ahead.

    73, Jeremy KK7GWY

  4. on8st commented on Oct 3, 2026

    @on8st
    Contributor

    @ten9876 Bad news first: we did not record the gain, so I cannot give you one to anchor on, and #5982 understated the problem.

    • The sweep (2026-09-27, real HL2, receive only) logged audio level per squelch step and nothing else, no LNA gain or auto RF gain state. The app ran on a copy of my profile, so gain came from per-band memory. Backups either side of that day disagree: 22 Sep has 40 m at +20 and auto RF gain off; 28 Sep has 40 m and 80 m at −12 and auto on. 9.420 and 11.000 MHz had no band entry in either, so they would have used the default +20, but that is the code path, not a log.
    • feat(hl2): receive squelch per mode family, with an HL2-measured threshold map. Principle II. #5982 said the thresholds "shift a few dB with RF gain". That is wrong. The gate is fixed in raw dBFS, so it moves dB for dB with LNA gain, as you found.
    • −105 dBFS at level 50 is the fitted map, not a reading. What was measured is three brackets: no signal −120..−112, the 9.420 carrier −96..−88, busy 40 m −80..−72 dBFS.

    Referring the gate to the LNA is the right shape. Rather than anchor on a guess, I will re-run the sweep receive-only with LNA pinned at +20, auto off, gain logged per row, plus one leg at a second gain to check the 1:1 shift, and post the brackets here.

    73, ON8ST

  5. on8st commented on Oct 3, 2026

    @on8st
    Contributor

    @ten9876 Bad news first: the map was not measured at +20, and at +20 it does not work.

    Re-run today: real HL2, receive only, main 732b99a, EFHW, AM 8 kHz, auto RF gain off, LNA read back in every row. Gate opens at, raw dBFS:

    LNA 11.000 MHz, no signal 9.420 MHz carrier 7.150 MHz, busy 40 m
    +20 −80.5..−73.5 above −70 (open at level 100) above −70
    +8 −95.9..−93.8 −75.6..−71.4 not run
    −4 −103.6..−100.8 −84.7..−82.6 not run
    −12 −113.4..−109.9 −93.1..−91.0 −88.2..−86.8
    • The 27 Sep brackets for no signal and for the carrier (−120..−112, −96..−88) fit the −12 row, not +20. Its busy-40 m bracket (−80..−72) does not: today that leg is 4.5 dB lower, and it depends on who is transmitting. So −12 is supported, not established. My "+20" was wrong either way: at +20 all three are 25 dB or more away.
    • At +20 level 50 (−105) is at least 24 dB below the floor: open below about level 85 and never closes on the carrier.
    • Carrier shift: 10.9 dB for 12, 19.3 for 20. Fading between repeats was 2.1 dB, so dB for dB is supported, not proven.

    So −140 + 0.7L − lnaOffsetDb(), zero at +20, keeps the broken +20 case. Anchored at −12: −108 + 0.7L − lnaOffsetDb().

    One point on the code side, about plan item 3. The gap between pan bins and what amsq sees is not a fixed figure; it depends on the filter width.

    • amsq does not measure the 48 kHz stream. xamsqcap (third_party/wdsp/upstream/RXA.c:650) captures midbuff after xnbp(nbp0) (:647), which is the passband filter, and before xwcpagc (:665). So it reads passband-limited power ahead of the AGC, which also confirms that the gate sits in raw dBFS.
    • xamsq compares an averaged magnitude against 10^(dB/20) (amsq.c:125-126, :221-224). For noise, the reading therefore grows 10·log10 with the passband width: 2.7 kHz against 1.8 kHz is about +1.8 dB. Per-bin pan noise depends instead on the bin width, which follows span and FFT size.

    A single offset between the two holds for one filter and one span only. If Auto SQL comes back on the HL2, the detector-side floor in item 3 is the right source. A pan-derived floor would need both widths applied.

    73, ON8ST

  6. self-assigned this
    on Oct 3, 2026
  7. added a commit that references this issue on Oct 4, 2026
    a8cfd99
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GUIUser interfacebugSomething isn't workingmaintainer-reviewRequires maintainer review before any action is takenspectrumPanadapter and waterfall

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions