Repository navigation
feat(dsp): run WDSP's second impulse blanker from the NB button. Principle IV. - #5966
Conversation
|
Thanks for this — the CI red here is one static gate, not your C++. Everything else in the run is fine, and the three build jobs ( What failed
That step runs Why your diff trips itIt's the third metric, and it isn't about colours at all. auto* lbl = new QLabel("NB2");
lbl->setStyleSheet(kLabelStyle); // <-- +1 call sitein Two ways to fix itPreferred — route through ThemeManager. One honest caveat if you go this route: Smallest change that clears the gate. Add a file-local helper in static QLabel* makeDspCaptionLabel(const QString& text)
{
auto* lbl = new QLabel(text);
lbl->setStyleSheet(kLabelStyle);
return lbl;
}There are 13 other Reading the CI output yourselfThe step prints the delta per metric before it fails: So you can confirm which of the three moved without guessing. You can also reproduce it locally: git worktree add /tmp/colour-base $(git merge-base HEAD origin/main)
python tools/audit_colours.py --src src --compare-src /tmp/colour-base/src --summary-only --strictReviewer commentsNo Copilot or human review comments on the PR yet, so there's nothing flagged that relates to this failure. Nothing else needs touching — the rest of the change ( References: 🤖 aethersdr-agent · cost: $5.3267 · model: claude-opus-5 |
There was a problem hiding this comment.
1. Issue fit
No fixes/closes #N in the body, and no linked issue — this is a feature PR reviewed against its own stated intent. GOVERNANCE.md wants an RFC for architectural changes; this one does change a seam verb's signature (setSliceNoiseBlanker(id, on, level) → (id, kind, level, fill)) and adds a bridge sub-verb, so whether it needed an RFC or a ruling first is a maintainer call — I'm naming it, not calling it a violation, because the shape (host-side WDSP stage behind an existing capability flag) is the same shape #5824 already landed under.
Against its own intent it delivers: both WDSP stages are created for the channel's life, at most one runs, the fill reaches SetEXTNOBMode, the transmit hold covers both, the seam verb carries the whole state in one call, and the bridge can drive and read it. The test work is unusually good — see "what I tried to break".
2. Scope
| File / group | What it changes | Claimed? | Verdict |
|---|---|---|---|
WdspChannel.{h,cpp} |
NoiseBlanker/NoiseBlankerFill enums, NOB created + run-flagged, fill pushed, both stages held |
yes | in scope |
third_party/wdsp/include/aether_wdsp.h |
declares the 12 *NOB* EXT entry points (header only; nobII.c already compiled by the glob) |
yes | in scope |
NoiseBlankerKind.h, WdspNoiseBlanker.h (new) |
seam vocabulary + the one enum-conversion point, static_assert-pinned |
yes | in scope |
IRadioBackend.h, SliceDelta.h |
verb signature + nbKind/nbFill deltas |
yes | new seam surface — maintainer decision |
anan/*, hl2/*, icom/* |
all three overrides updated; ANAN packs the applied triple into one atomic int | yes | in scope |
SliceModel.{h,cpp}, RadioModel.cpp |
nbKind/nbFill, kind-wins applyChanges, troubleshootingSnapshot gains kind/fill |
yes | in scope |
AutomationServer.cpp |
slice dsp nb2, nbKind/nbFill in the slice snapshot |
yes | new public bridge surface — maintainer decision |
VfoWidget.{h,cpp} |
NB button cycles Off→NB→NB2→Off, relabels, NB2 fill combo row | yes | in scope (see nit 2) |
docs/automation-bridge.md, docs/architecture/aetherd-hl2-backend-design.md |
document the above; gen_bridge_docs.py --check still passes |
yes | in scope |
tests/* (6 modified, 1 new), tests/tests.cmake |
mechanical signature updates + three genuinely new assertions | yes | in scope |
Nothing in the diff is unexplained by the stated change. No CHANGELOG.md entry (correct). No unrelated files, no removed guards, no formatting churn. No socket tests added, modified or removed — I read the added/modified test sources directly in the head checkout, not the working tree.
3. Blockers
None. The three items below are real but none of them breaks a user on the GUI path or fails on current main.
4. Findings (non-blocking, ranked)
-
slice dsp nb2is not capability-gated, while its read counterpart is.get hostnbrefuses on a radio withouthasHostNoiseBlanker(AutomationServer.cpp:5245), with a comment explaining exactly why an empty success is worse than a refusal. The write path six thousand lines down has no such check, soslice dsp nb2 onon a Flex or an Icom putsSliceModelintoAdvanced. Consequences I can trace in source: the VFO button relabels itself "NB2" on a radio with no NB2 (refreshNbControlskeys the label onkindalone, only the fill row is capability-gated);IcomCivBackend::setSliceNoiseBlankerfolds it to plain "on"; and it is sticky, becauseapplyChangeswas deliberately written so the radio'snb=1echo cannot pull it back down toImpulse. This contradicts the invariant the PR asserts in three separate comments ("Advanced is only reachable wherehasHostNoiseBlankeris set"). Inline comment with a suggested guard. -
The NB2 fill row outlives the NB button in FM.
m_nbModeOk = !isFmhidesm_nbBtn(VfoWidget.cpp:3455), but neither mode-recompute site (:4809,:5393) callsrefreshNbControls(), andrefreshNbControlsdoesn't consultm_nbModeOk. On an HL2/ANAN slice running NB2, switching mode to FM should leave an orphaned "NB2 [Interpolate]" row with no button above it. Honest caveat: the shared DSP level row looks like it behaves the same way — I see nopopDspLevelTarget(LvlNB)on the FM path either — so this is the new row inheriting an existing quirk rather than a new class of bug. Reasoned from code; I cannot drive the GUI headless. -
setNb(true)downgrades NB2→NB from every CAT path. The PR reasons carefully that a radio's bool echo must not downgrade a host kind, andapplyChangesimplements exactly that rule (onlyOff↔on transitions are taken from a barenb). The bool setter has no matching rule:setNb(true)unconditionally selectsImpulse. Callers are rigctlset_func NB 1(RigctlProtocol.cpp:1945), SmartCatNB1;(SmartCatProtocol.cpp:1063), TCIrx_nb_enable(TciProtocol.cpp:1437) and the MIDI bindingsetNb(v > 0.5f)(MainWindow_Controllers.cpp:1954). Any client that re-asserts its current state — and TCI clients mirroring a broadcast back is a pattern this tree has hit before — silently knocks NB2 down to NB. MirroringapplyChanges' rule in the setter is one line. That said, "the bool door means the first blanker" is also a defensible reading and it is documented, so I'd call this a maintainer call rather than a defect.
5. Nits
m_nbKindandm_nbActiveare bothmemory_order_relaxedand stored kind-then-gate.processIq's comment says "a kind seen here is a stage that is running", which is stronger than relaxed ordering actually guarantees — a reader can observe the new gate with the old kind for one block. The consequence is benign (the unchosen stage's WDSP run flag is 0, soxanbEXT/xnobEXTcopies in place and one block goes unblanked), but the comment overstates the guarantee.noise_blanker_kind_model_testis registered intests.cmakeand joinsAETHER_SETTINGS_CONSUMERScorrectly, but it is not in the frozen.github/ci-test-gate.txt, so the per-PR lane builds it and never runs it — it first executes onfull-suite.ymlafter merge. Normal for this repo; flagging so nobody reads the green badge as "the new test passed".- The NB2 discrimination checks compare doubles with
!=. That's the right direction here (bit-identity is the bug signature), but it means the check can only ever be falsely green, never falsely red — worth the one-line note it doesn't have.
6. What I tried to break, and could not
- The vendored WDSP side.
create_nobEXT's parameter list inaether_wdsp.hmatchesupstream/nobII.c:608exactly, including the argument order;nobII.cis already picked up by thefile(GLOB upstream/*.c)so no build wiring was missed. The "neitherxanb()norxnob()allocates" claim guarding the real-time path holds: everymalloc0/_aligned_freeinnobII.cis insidecreate_nob/destroy_nob. Thea->legacybuffer is a fixed 2048 complex samples and would overflow on a larger block — but it is touched only byxnobEXTF, and this code callsxnobEXTwith its own staging buffer, so there is no block-size cap on the path actually used.MAX_SAMPLERATEis 1536000 in bothnob.candnobII.c, sodline/awave/hwaveare not undersized at any rate this client runs. - ANAN's single-int packing (
AnanRxDsp.h):level + 128·kind + 1024·fill. I checked the decode against the whole domain — level 0..100, kind 0..2, fill 0..4 — max state 4452, every field extracts exactly, and no valid state collides with the-1"no installed channel" sentinel. - Exclusivity. Both run flags are written unconditionally outside the per-kind branches in
setNoiseBlanker, so I could not construct a call sequence leaving ANB and NOB both running; the newly-selected stage is flushed and the stopped one deliberately is not. - Sibling call sites. Every override of the changed verb is updated (hl2, anan, icom); FlexBackend doesn't override it. The seam probe table generated from
IRadioBackend.hcovers signals only, so the method-signature change can't stale it — confirmed by runninggen_seam_probe_table.py --check(52 signals, up to date), plusgen_bridge_docs.py --check,gen_touchpoint_manifest.py --check,gen_theme_seed.py --check,check_engine_boundary/check_command_plane/check_capability_records/check_test_registration --strictandcheck_ci_test_gate.py— all clean on the head checkout. - The VFO cycle's re-entrancy.
setNbKind(Advanced)emitsnbChangedsynchronously from inside thetoggled(false)handler, soconnectLeveledDsp's slot re-checks the button mid-emission. It's wrapped inQSignalBlocker, so no recursion; andpushDspLevelTargetdoesremoveAll-then-append, so the resulting doubled push cannot leak a stale stack entry. The cycle Off→NB→NB2→Off traces correctly, and collapses to Off→NB→Off whenm_hasHostNoiseBlankeris false. - Band-stack restore (
MainWindow.cpp:5476) looked like an obvious NB2-downgrade path, but it writes rawslice set N nb=wire text rather than callingsetNb(), so it can't reach a host blanker at all. Withdrawn. - The tests themselves. The two discrimination checks in
hl2_noise_blanker_testwould fail against the plausible fake implementation (mappingAdvancedonto the ANB, or never pushingSetEXTNOBMode) — they are not assertions of the implementation back to itself.anan_noise_blanker_readback_testasserts the applied kind while a deferred request holds a different one, which is the only arrangement that can tell a readback from an echo.wdsp_nb_hold_invariant_test's newrunFlagsUnconditionalcheck ships with its own broken miniature as a positive control. And the body's record of two candidate checks that were written and removed — one unfalsifiable, one that failed on correct code — is the kind of negative result most PRs quietly drop. - CI on
e67ebb5: build, check-windows, check-macos, Static checks and Sanitizer-configures all green.check-windowspassing rules out the usual MSVC trap on new DSP code.
Not verified, and unverifiable from here: I have no build and no run, so every statement above is reasoned from source, not reproduced. The measured peak/RMS figures in the body (0.6265 / 0.2705 / 0.2717 / 0.2573) I could not reproduce. The claim that the fill row was clipped on the bench before the relayoutToCurrentContent() call, and that the call fixes it, is untestable headless.
7. Recommendation
Needs maintainer decision, then approve with nits. The engine work is careful and well-evidenced and I could not break it. Two decisions belong to a maintainer rather than to me: the new seam-verb signature and the slice dsp nb2 bridge surface arriving in a feature PR with no linked issue or RFC; and whether setNb(true) selecting Impulse from a CAT client is the intended rule (finding 3). Concrete next step for the contributor regardless of that ruling: add the hasHostNoiseBlanker guard to the nb2 branch (finding 1) so the write path matches the read path in the same file.
🤖 aethersdr-agent · cost: $14.9302 · model: claude-opus-5
|
Finding 1 was right. I reproduced all three from source before Finding 1 —
|
| mutation | killed on |
|---|---|
| guard deleted | nb2 refuses on a radio with no host-side blanker |
widened to nb as well |
plain nb still works on the same radio |
| polarity inverted | nb2 is accepted where the capability is published |
| moved below slice resolution | with no slice, nb2 still refuses for the capability rather than the slice |
558/558 ctest (six environment skips), nine ratchets clean.
check_command_plane holds AutomationServer.cpp at its frozen six raw-command
sites, and the colour delta is +0 on all three counts.
Finding 2 — the fill row outliving the NB button in FM: confirmed, not fixed
Confirmed from source, and slightly worse than described. Neither mode-recompute
site calls refreshNbControls(); both call applyRadioSideDspVisibility(),
which hides m_nbBtn (VfoWidget.cpp:3455) and touches no fill row. And on the
syncFromModel path the single refreshNbControls() at :5350 runs before
m_nbModeOk = !isFm at :5393, so the row is evaluated against the previous
mode and never re-evaluated after. refreshNbControls itself computes
showFill = advanced && m_hasHostNoiseBlanker and never consults m_nbModeOk.
Your caveat is the reason I have not fixed it: the shared DSP level row behaves
the same way on the same paths, so the honest fix is one that makes both rows
follow the button, not an m_nbModeOk term bolted into refreshNbControls that
leaves the older row still orphaned. That is a change to shared DSP-panel
behaviour rather than to NB2, and it belongs to whoever owns that panel. Happy
to do it either way — as part of this PR if you want it here, or as its own with
the level row included.
Finding 3 — setNb(true) downgrading NB2: confirmed, and it is your call
All four call sites confirmed: rigctl set_func NB 1, SmartCat NB1;, TCI
rx_nb_enable, and the MIDI binding. SliceModel::setNb(bool) is two lines and
selects Impulse unconditionally, so any client re-asserting its current state
knocks NB2 down to NB.
For what it's worth I would mirror applyChanges' rule in the setter, and I
think the documented reading is the weaker one. None of those four callers can
express "the first blanker specifically" — set_func NB 1, NB1;,
rx_nb_enable and a MIDI button are all state assertions, so reading them as
a choice of blanker attributes an intent the protocol cannot carry. Against
that: "the bool door means the first blanker" is defensible and it is written
down, so if you prefer it I will leave it.
It is one line either way and I will take whichever you rule. I have not
pre-emptively changed it.
The two decisions you referred upward
Both still open and both yours: the new seam-verb signature
setSliceNoiseBlanker(id, kind, level, fill), and the slice dsp nb2 bridge
surface arriving in a feature PR with no linked issue or RFC. I opened this one
the way I did because the button and the seam verb are the same change and
splitting them would have produced a verb with no caller; if you would rather
see an RFC first, say so and I will file one against this branch.
The bench limits in the body still stand unchanged: G2 verified the controls,
but on-air blanking, audible fills, level bite and the TX hold are untested —
no noise source here and no G2 TX.
37fff3e to
219b4a9
Compare
ten9876
left a comment
There was a problem hiding this comment.
Reviewed head 484edefb (merge base 732b99a9). I built it on Arch Linux, ran the PR's tests, ran mutations against WdspChannel.cpp, and drove the build over the automation bridge against the demo simulator.
Issue fit
There is no linked issue, so I reviewed this against its own stated intent: add WDSP's second impulse blanker (NOB, nobII.c) behind the existing NB control on host-DSP radios. Against that intent the engine work does what it says. I reproduced the body's measurements on the head: NB2: off peak=0.6266 | nb peak=0.2704 | nb2/zero peak=0.2716 | nb2/interp peak=0.2573, which match the body's figures to rounding. The open question is the merge, not the feature. #5919 landed on main today, and the routing this PR is built on is gone (Blocker 1).
Whether a seam-verb change plus a new bridge surface needed an RFC first is still the maintainer's call. The 2026-09-26 review raised it, and nobody has ruled on it yet.
Scope
| File / group | What it changes | Claimed? | Verdict |
|---|---|---|---|
src/core/dsp/WdspChannel.{h,cpp}, third_party/wdsp/include/aether_wdsp.h |
NOB stage created/destroyed with the channel, kind+fill, NOB EXT declarations | yes | in scope |
src/core/backends/NoiseBlankerKind.h, WdspNoiseBlanker.h (new), SliceDelta.h |
seam vocabulary, the one conversion, nbKind/nbFill deltas |
yes | in scope |
IRadioBackend.h + hl2/*, anan/*, icom/* |
setSliceNoiseBlanker(id, kind, level, fill) |
yes | seam surface: maintainer decision, and now superseded by #5919 (Blocker 1) |
src/models/SliceModel.{h,cpp}, RadioModel.cpp |
nbKind/nbFill, kind-wins applyChanges, routing via noiseBlankerCommandIssued |
yes | in scope, conflicts with #5919 |
src/core/AutomationServer.cpp, docs/automation-bridge.md |
slice dsp nb2, nbKind/nbFill in get slice, kind/fill in get hostnb |
yes | new public bridge surface: maintainer decision |
src/gui/VfoWidget.{h,cpp} |
NB button cycles Off→NB→NB2 on host radios; NB2 fill row | yes (body "rule differently" item 2) | changes an existing control's interaction: maintainer decision, as the body itself asks |
.github/workflows/static-checks.yml, tools/check_nb_exclusivity.py, docs/agents/backends.md |
new CI gate and a new canon entry | yes (rebase note) | Blocker 2. This gates something that cannot fail, and it misses the failure that can |
tests/* (2 new, 10 ported), tests/tests.cmake |
signature ports + new coverage | yes | in scope |
docs/architecture/aetherd-hl2-backend-design.md |
seam table row | yes | in scope |
Commit dates: 4d3d5383 (authored 09-29) predates 649a3cfc (10-02) only because the feature commit was rewritten during the rebases. 4d3d5383 is the response to the previous review's finding 1, and 484edefb is a forced signature port of two tests that landed on main. Nothing in the diff is unrelated to the stated change. There is no CHANGELOG entry, which is correct. The step-0 socket preflight is clean: automation_nb2_capability_test constructs an AutomationServer but never calls start()/listen(), and it calls handleLine() directly.
Blockers
1. The PR no longer merges, and #5919 has removed the routing it is built on
GitHub reports CONFLICTING/DIRTY. git merge-tree against current main conflicts in eight files: AnanBackend.{h,cpp}, Hl2Backend.h, WdspChannel.h, RadioModel.cpp, SliceModel.{h,cpp} and anan_noise_blanker_readback_test.cpp. The cause is not churn. #5919 (f4e463c2a, merged 2026-10-04 09:02Z) deleted SliceModel::noiseBlankerCommandIssued: git grep noiseBlankerCommandIssued origin/main -- src tests returns nothing. On main, NB now travels as SliceModel::setNb → notifyReceiveDspIntent(Feature::Nb, Field::Enabled) → RadioModel::dispatchSliceDsp → IRadioBackend::requestSliceDsp. Hl2Backend::requestSliceDsp and AnanBackend::requestSliceDsp then call setSliceNoiseBlanker(id, request.enabled, request.level).
This PR routes through exactly that deleted signal (src/models/RadioModel.cpp:1577, src/models/SliceModel.cpp:378-408). The body says "whichever lands second moves the routing". This PR is now second, so the move is its job. It is not a mechanical rebase, because SliceDspRequest (src/core/backends/ReceiveCommand.h on main) carries only bool enabled; int level;, and valid() admits only those fields. Kind and fill need to get onto that path somehow: a widened request, a Field::Kind/Field::Fill, or a separate typed request. The body explicitly deferred that design to #5919, and #5919 did not make it. That design choice needs a maintainer ruling before the rebase, so the author doesn't redo the seam twice.
The green CI badge doesn't help here. The only runs on this head were created 2026-10-03T14:57Z against 732b99a9. main is now at 2026-10-04T18:26Z and 37 commits ahead, including #5919.
2. check_nb_exclusivity.py guards an inert property, misses the real failure, and adds a false statement to canon
.github/workflows/static-checks.yml:317, tools/check_nb_exclusivity.py, docs/agents/backends.md:159
The ratchet's premise, repeated in the canon entry and in the body, is that both run flags must be written outside the kind branches or "both blank the same window", and that "no runtime test can observe which stages are running". The code refutes both halves. WdspChannel::processIq dispatches to exactly one stage per block (src/core/dsp/WdspChannel.cpp:433-437: xnobEXT if m_nbKind == Advanced, else xanbEXT). A run flag left at 1 on the unchosen stage therefore blanks nothing, because nothing feeds that stage. I checked this with three mutations, then restored the file (sha256 matches git show HEAD:) and rebuilt:
| Mutation | check_nb_exclusivity.py --strict |
hl2_noise_blanker_test |
|---|---|---|
M1: SetEXTANBRun(m_channelId, kind != NoiseBlanker::Off ? 1 : 0), so the ANB run flag stays on under NB2 |
OK | passes; NB2 output bit-identical to unmutated (nb2/zero peak=0.2716 rms=0.17834) |
M2: processIq calls xanbEXT then xnobEXT under Advanced |
OK | passes; identical output (ANB's run flag is 0, so it copies through) |
| M1 + M2: both stages genuinely blank the window | OK | FAILS: nb2/zero peak=0.2704 rms=0.17845 | nb2/interp peak=0.2704 rms=0.17845, so DISCRIMINATION 2 (tests/hl2_noise_blanker_test.cpp:376-380) fires |
So the static gate passes on the one change that actually double-blanks. The existing runtime test is what catches that change, and it is the test the body says cannot exist. The body's own note supports this: "with the run flags deliberately broken so BOTH stages ran, the measured peak and RMS moved by less than the printout's four decimals". That happened because processing dispatch, not the run flags, decides what runs.
Fix: drop the workflow step, tools/check_nb_exclusivity.py and the docs/agents/backends.md entry. Point tests/hl2_noise_blanker_test.cpp's closing comment at the discrimination checks as the exclusivity pin, since M1+M2 shows they are one. If you want a cheaper guard, the invariant worth pinning is the single if/else dispatch in processIq, not the run-flag shape. A new CI gate is also a maintainer-ruled change in its own right.
Needs maintainer decision (flagged for @ten9876)
- Seam shape after #5919 (Blocker 1): how kind and fill ride
requestSliceDsp. - Bridge surface:
slice dsp nb2 on <level> <fill>,nbKind/nbFillinget slice,kind/fill/requested*inget hostnb. setNb(true)selectsImpulse(src/models/SliceModel.cpp:378), so rigctlset_func NB 1, SmartCatNB1;, TCIrx_nb_enableand the MIDI binding all knock a running NB2 down to NB. The author offered both readings on 2026-09-30 and is waiting for a ruling. Onmainthe bool setter is now the intent entry point, so the ruling also decides what that path carries.- FM leaves the NB2 fill row orphaned (previous review, finding 2). The author confirmed it and is waiting for a ruling on whether to fix both rows here or separately.
- The cycling NB button on HL2/ANAN is a new interaction on an existing control (body item 2). It is a defensible choice, since deskHPSDR, pihpsdr and Thetis all expose both blankers, but it is a UI ruling.
Nits (non-blocking)
src/core/AutomationServer.cpp:7759-7790: a 26-line comment with narrative ("enforced nowhere; this is where it is enforced") and already-stale line cites.IRadioBackend.h:764isfinishTxAudioat this head, and theSliceModel.hinvariant is at:300, not:317. AGENTS.md §"Comments" asks for ≤5 lines and no history. Drop the line numbers either way.tests/hl2_noise_blanker_test.cpp:399says exclusivity is pinned bywdsp_nb_hold_invariant_test, which #6075 retired and which does not exist on this branch.src/core/backends/WdspNoiseBlanker.h:9-14says thestatic_asserts stop "a third algorithm added to one enum and not the other". They only catch renumbering of existing values. A value added to one enum alone still compiles.- Comment volume generally runs well past the ≤5-line guidance (
IRadioBackend.hverb comment,NoiseBlankerKind.h,VfoWidget.cpprefresh block). Worth trimming in the rebase.
Verified vs. read
- Built the head (RelWithDebInfo). Ran all 12 touched/new targets, 12/12 passing:
hl2_noise_blanker_test,noise_blanker_kind_model_test,automation_nb2_capability_test,anan_noise_blanker_readback_test,wdsp_nb_hold_test,reroute_dead_controls_test, the fourhl2_rxdsp_*ports,anan_rxdsp_handedness_test,anan_backend_test. - Mutations: the three above. M1+M2 confirms the discrimination check is real coverage, not an assertion of the implementation back to itself.
- Bridge, demo simulator only (
DEMO-0001,get radio→"model": "AetherSDR Demo", isolated settings,AETHER_AUTOMATION_NO_TX=1), checking the "no behaviour change elsewhere" claim on a radio without a host blanker:slice dsp nb2 on 60 4→{"error": "slice dsp nb2: this radio does not run a host-side noise blanker (use 'nb' for its own — see get hostnb)", "ok": false}slice dsp nb on→{'nb': True, 'nbKind': 1, 'nbFill': 0, 'nbLevel': 50}, button textNB, fill combo hiddenslice dsp nb on 50 4→"only 'nb2' takes a fill argument";slice dsp nb off→nbKind: 0
- Could not drive: the VFO button cycle and the fill row. The demo has neither
hasRadioSideDspnorhasHostNoiseBlanker, sodspNBBtnis hidden (refused: 'dspNBBtn' is not visible), and NB2 is only reachable on an HL2/ANAN. The TX hold over NB2 is still untested on hardware, as the body says.
What I tried to break that held
- The enum metatype across the queued
invokeMethod(AnanBackend.cpp:852,Hl2Backend::pushNoiseBlanker):anan_noise_blanker_readback_testdrives the queued path and passes. - The rebuild snapshot:
startReceiverDspBuildandapplyPanBandwidthboth carry kind+fill intobuildChannel, andinstallChannelre-applies a request that changed mid-build. - Icom
scrubDrivemaps the CI-V bool toImpulse/Offwith the default fill. The capability guard sits ahead of slice resolution, as the author argued.
🤖 Generated with Claude Code
|
Maintainer rulings on #5966 (following the review above)
|
…ciple IV. WDSP ships two impulse blankers. aethersdr#5824 landed the first (ANB, nob.c); this adds the second (NOB, nobII.c), which every reference client calls NB2. It reconstructs the blanked window instead of zeroing it, which is better when the impulse lands on a wanted signal and worse when noise is dense -- so no client replaces one with the other, and neither does this. WdspChannel opens both stages for the life of a receive channel and runs at most one: they share a detector and a window, so a second stage would reconstruct what the first zeroed. Both run flags are written on every call, outside the per-kind branches, which is what makes the unchosen stage explicitly stopped. The transmit hold covers both for the reason it covered one -- the trigger is relative to a running average, and a transmit period of zeros would gate the first real receive samples. The seam verb becomes setSliceNoiseBlanker(id, kind, level, fill): one call carries the whole state, so no ordering between "which blanker" and "how hard" can exist. A radio whose blanker is its own firmware treats anything but Off as on. SliceModel gains nbKind()/nbFill() while nbOn() and setNb(bool) keep their meaning, so rigctl, SmartCat, TCI, the MIDI mapping, the keyboard shortcut and `get slice` needed no changes. A radio's bool echo can no longer downgrade a host NB2 to NB. The NB button cycles Off -> NB -> NB2 -> Off where this host runs the blanker, labelled with the running kind, and an NB2 fill combo appears under the DSP grid only while NB2 runs. The fill row asks the panel to refit when it appears: the DSP tab's height is pinned to its page's size hint, so a row that appears inside an already-sized panel is clipped, which is what happened on the bench before this. `slice dsp nb2 on <level> <fill>` drives it from the bridge and `get hostnb` reports the applied kind and fill. Measured against real WDSP in hl2_noise_blanker_test: on a linear-phase chain, an unblanked impulse peaks at 0.6265, NB at 0.2705, NB2 zero-fill at 0.2717 and NB2 interpolating at 0.2573 -- the lowest peak and the highest surviving tone RMS of the three. Every new assertion has a mutation run behind it. Two candidate checks were written and removed instead of kept: one could not fail, and one failed on correct code while being unable to distinguish the bug it was written for. The exclusivity rule is enforced as a Static checks ratchet, tools/check_nb_exclusivity.py: each run flag called exactly once per call, and neither one inside a kind branch. A text check because it cannot be anything else -- WDSP's EXT API cannot read a run flag back, so no runtime test can observe which stages are running. It self-tests on every scan and all four mutations fail it, including the kind branch renamed away, which is how a text check stops measuring while still reporting OK. This branch first carried the rule as additions to tests/wdsp_nb_hold_invariant_test.cpp; aethersdr#6075 retired that file as a source-text test, and its behavioural successor wdsp_nb_hold_test is ported to the new signature here. Clean-room: WDSP's NOB is in-tree. deskHPSDR was read for which knobs exist and what its defaults are; no code was copied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…Principle VII.
The read path already refused. `get hostnb` declines without
RadioCapabilities::hasHostNoiseBlanker, arguing that an empty success is a test
passing against a radio where the question is meaningless. The write path six
thousand lines down had no equivalent check, so `slice dsp nb2 on` put a Flex or
an Icom slice into NoiseBlankerKind::Advanced — a kind no backend on those
families can map onto anything.
Not a harmless no-op, for three reasons that compound:
* It sticks. SliceModel::applyChanges is deliberately written so a radio's
bool `nb` echo cannot pull a host kind back down, so nothing the radio says
afterwards clears it. Only a full NB off does.
* The button lies. VfoWidget::refreshNbControls keys the label on the kind
alone — only the fill row is capability-gated — so the NB button reads
"NB2" on a radio that has no NB2.
* IcomCivBackend::setSliceNoiseBlanker folds anything non-Off to plain "on",
which is faithful to the wire and means nothing upstream ever notices the
disagreement.
The invariant is stated in IRadioBackend.h:764 and SliceModel.h:317, and until
now was enforced nowhere. The GUI was never the hole: the NB2 leg of the button
cycle is already gated on m_hasHostNoiseBlanker (VfoWidget.cpp:2674). The bridge
was the only unguarded door to Advanced.
Placed with the control-name validation rather than inside the nb/nb2 branch,
because on such a radio `nb2` is not a control given bad arguments — it is not a
control at all. That also puts the refusal ahead of slice resolution, so a radio
with no slice yet is told about the capability instead of "no slice available",
which would send a caller looking for a slice on a radio that can never have the
control.
Reads the capability rather than RadioModel::hasHostNoiseBlanker(), which is
additionally gated on isConnected(): that gate exists to keep a BUTTON off an
unconfirmed radio, and no other branch of `slice dsp` refuses for being
disconnected. Same read as `get hostnb`.
New target automation_nb2_capability_test drives the real dispatcher with a stub
backend and an injected slice. Four mutations, all killed: the guard deleted,
the guard widened to `nb` as well, the polarity inverted, and the guard moved
below slice resolution — the last proving the placement and not just the
presence.
Rebased onto b714cd3. aethersdr#5982's hl2_rxdsp_squelch_test landed between this
branch's old base and the rebase and builds a channel to prove the squelch
survives a rebuild swap, so its buildChannel() call is adapted to the kind/fill
signature; the blanker is incidental there and stays off. 599/599 ctest with one
deliberate skip, twelve ratchets clean, check_command_plane exactly at its 406
raw-command baseline across 32 files, and the colour delta +0 on all three
counts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ures. Neither file existed at this branch's previous base, so neither conflicted and neither appeared in the rebase: hl2_rxdsp_adaptive_taps_test arrived with aethersdr#5981 and reroute_dead_controls_test with aethersdr#6049, and both call signatures this branch widens. hl2_rxdsp_adaptive_taps_test: four buildChannel(cfg, false, 0) calls become (cfg, NoiseBlanker::Off, 0, NoiseBlankerFill::Zero). These cases assert filter tap lengths across a rebuild and never engage a blanker, so off with a zero fill is what they already had. reroute_dead_controls_test: the fake backend's setSliceNoiseBlanker override takes the kind and fill, and records kind != Off as the bool its rows already assert — the same reading a radio with one blanker gives the request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… Principle VIII. Answers the 10-04 review of aethersdr#5966 and the maintainer's rulings on it. The exclusivity ratchet guarded the wrong thing. WdspChannel::processIq feeds exactly one blanker stage per block, so a run flag left on the unchosen stage blanks nothing. The review's mutation that does double-blank (both stages fed, the ANB run flag left on under NB2) passed check_nb_exclusivity.py and failed DISCRIMINATION 2 in hl2_noise_blanker_test. The checker, its Static checks step and its docs/agents/backends.md entry are removed. The test's closing comment and the run-flag comment in WdspChannel::setNoiseBlanker now name the dispatch as the guarantee and the discrimination checks as its pin. The NB2 fill row now follows m_nbModeOk, so it leaves with the NB button in FM (ruling 4). refreshNbControls() runs at both mode-recompute sites: the modeChanged handler, and syncFromSlice(), whose earlier refresh ran before m_nbModeOk was computed. A new fm_filter_controls_test case covers a live switch to FM and attaching a slice already in FM. The shared DSP level row is unchanged. Review nits, comments only: the slice dsp nb2 guard comment loses its stale line cites and fits five lines; WdspNoiseBlanker.h no longer claims its static_asserts catch a value added to one enum; the long comments in IRadioBackend.h, NoiseBlankerKind.h and refreshNbControls() are trimmed. On 85f2a12: build clean, ctest 655 of 656 (pgxl_panel_test fails; none of its sources differ from main), and every Static checks step passes with --strict. Five mutations, all killed: the fill row ignoring the FM gate, each of the two mode sites not refreshing, the Flex fill refusal removed, and a kind change sent as a Level request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the rulings. All of them are addressed at
Locally, ctest passes 655 of 656. The one failure is |
jensenpat
left a comment
There was a problem hiding this comment.
Reviewed 3ef4cd360f62047abd7534b2d5d1f36eb2e9d1d2.
Issue fit: no linked issue. The change matches the PR body and the maintainer rulings on 2026-10-04: kind and fill ride a widened SliceDspRequest, the NB button cycles Off → NB → NB2 on a host blanker, setNb(true) selects the first blanker, the bridge surface is the accepted slice dsp nb2 plus kind/fill readback, the FM fill row is hidden with the NB button, and the run-flag ratchet is gone.
Scope. Host-side WDSP only. HL2 and ANAN run at most one of ANB (nob.c) or NOB (nobII.c). Flex still sends nb=0|1 and returns Unsupported for Field::Fill with no wire text. Icom maps anything but Off to its one blanker and ignores fill. Sim still echoes enabled and still rejects a non-Enabled field, which is how a demo fill request stays off the wire. The capability hasHostNoiseBlanker is what shows the cycle and what admits slice dsp nb2.
| Group | What changed |
|---|---|
| Engine | WdspChannel opens both stages, processIq feeds exactly one, TX hold still skips that call |
| Seam | SliceDspRequest carries kind and fill; Field::Fill is Nb-only; requestedBlanker() maps a kind-less enabled request to Impulse |
| HL2 / ANAN | one verb applies kind, level, and fill together, including across a rebuild |
| Flex / Icom | bool blanker unchanged; fill is refused or ignored |
| Model / VFO | nbOn() derived from kind; radio bool echo cannot downgrade NB2; fill row follows NB2 and m_nbModeOk |
| Bridge | slice dsp nb2 gated like get hostnb; get slice adds nbKind / nbFill beside nb |
| Tests | model, capability gate, ANAN readback, Flex contract, FM row, measured NB vs NB2 |
Findings: none that block. The three open threads are closed by this head or by the ruling: the nb2 capability gate is in AutomationServer::doSlice ahead of slice resolution; the FM fill row follows m_nbModeOk on both the mode change and slice attach; setNb(true) dropping NB2 to NB is the accepted bool door.
Nit (non-blocking): processIq comments that storing the kind before m_nbActive makes a relaxed load ordered. Those stores are memory_order_relaxed, so that ordering is not a C++ guarantee. A torn block during a switch would run the other stage once; the run flags and the single if/else still decide the steady state, and hl2_noise_blanker_test discriminates the two stages.
Verified on this SHA: Qt 6.12.0, RelWithDebInfo, headless. backend_receive_contract_test, anan_noise_blanker_readback_test, noise_blanker_kind_model_test, automation_nb2_capability_test, hl2_noise_blanker_test, fm_filter_controls_test — 6/6 passed. CI on this SHA is green for build, check-macos, check-windows, Static checks, and Sanitizer option configures (compile and static gates; the required CI jobs do not execute this suite). git merge-tree onto current main (25de97c3) is clean; the 15 commits since the PR base do not touch the blanker path.
create_nobEXT matches third_party/wdsp/upstream/wdsp.h, and nobII.c's switch (a->mode) is 0 zero, 1 sample-hold, 2 mean-hold, 3 hold-sample, 4 interpolate.
Non-blocking follow-ups (not merge prerequisites):
wdsp_nb_hold_teststill measures the TX-edge hold on Impulse only. The hold is the same skip ahead of the kind branch, so NB2 is covered structurally and not by that measurement.- The Off → NB → NB2 click cycle is not driven by a widget test.
fm_filter_controls_testcovers the fill row leaving in FM, not the re-check that keeps the button down on the NB2 leg.
Addressed on 3ef4cd3: SliceDspRequest carries kind and fill, the run-flag ratchet is removed, and the FM fill row hides with the NB button. Approving review 5443410128 is on that SHA.
Keep both the ANAN link-telemetry registration and the NB2 tests in tests.cmake.
jensenpat
left a comment
There was a problem hiding this comment.
Reviewed 118255f7f51a4c7c1dc39d5876b607fd3be99e2e, the merge of origin/main into 3ef4cd36.
The only conflict was tests/tests.cmake: main added anan_link_telemetry_test and this branch added noise_blanker_kind_model_test and automation_nb2_capability_test at the same spot. Both registrations are kept. No blanker source hunk changed in that resolution.
Auto-merged ANAN/HL2/Icom/Flex/RadioModel files still carry kind, level, and fill on setSliceNoiseBlanker, and the new link-telemetry path on AnanBackend is intact. Rebuilt and reran, headless, on this merge: anan_backend_test, anan_noise_blanker_readback_test, automation_nb2_capability_test, noise_blanker_kind_model_test, backend_receive_contract_test, hl2_noise_blanker_test — 6/6 passed.
The approval on 3ef4cd36 still stands for the feature itself.
… Principle VIII. An audit of every page against the 106 PRs in v26.10.2, each fact checked against the source at the release commit: - Menu paths: AetherMap, AetherModem, Callsign Lookup and FreeDV Reporter are on View, and Waveforms is on Tools. - New coverage: Windows Firewall rules and the Network Diagnostics page (#6289), live CW contacts (#6268), NB2 on the HL2 and ANAN-G2 (#5966), ANAN-G2 AGC-T and per-radio memory (#6282, #6129), HL2 state restore and its level changes (#6259, #6254, #5975), DVK import (#6247, #6262, #6251), Copy Assist from unprocessed audio (#6216), keyboard-operable status-bar indicators (#6267), the KPA1500 (#5905), CTR2 extensions and the AetherKnob (#6338), live PPM (#6082), and Icom COMP and the squelch scale (#6174, #6184). - Known issues whose GitHub issues are closed are removed. - The style guide now asks for US spelling, and every page follows it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… and re-take the Stable snapshot. Principle VIII. (#6396) ## Summary This brings the user guide (docs.aethersdr.com) up to date with everything in v26.10.2 and re-takes the Stable snapshot so the default site shows it. Docs only, apart from the TCI page prose in `tools/docs/gen_reference.py`; no application source, test or CI changes. Every page was audited against the 106 PRs in the release. Every fact that was added or kept was checked against the source at `f753dbfc4`, the v26.10.2 release commit, not taken from the PR bodies alone. ## What changed - **Menu paths.** - AetherModem, Callsign Lookup, AetherMap and FreeDV Reporter are on **Tools**. They are created on View and then moved to Tools (`MainWindow_Menus.cpp:1519-1520`, `:1569-1575`); the style guide's example says **Tools → AetherMap...**. - **Show live CW contacts** is on **View**. - Waveforms is on **Tools**, not File. - The VUDU applet button was called TXDSP. - Labels now match the source's `...` vs `…`. - **Gaps from this cycle.** - **Windows and Network Diagnostics:** Windows Firewall rules and the Network Diagnostics Windows Firewall page (#6289); the `aether.network` and `aether.nvafx` log categories (#6289, #6308). - **Remote access:** Tailscale shim revocation semantics (#6325). - **Sleep, App Nap and power throttling (#6195):** per-OS notes. - **Hermes-Lite 2:** state restore, RX audio at unity, CW at TUNE level, multi-interface discovery and a discovery troubleshooting entry (#6259, #6254, #5975, #6009, #6293). - **ANAN-G2:** AGC-T range, a "What is remembered" section, and supply voltage and link status (#6282, #6129, #6137, #6211). - **NB2** on the HL2 and ANAN-G2 (#5966). - **DVK:** WAV import requirements, the slot menu without Delete, status text and licensing (#6247, #6262, #6251). - **Copy Assist** from unprocessed audio (#6216). - **Live CW contacts** replace the decoder's contact card (#6268). - **Status-bar indicators** work from the keyboard (#6267). - **Volume:** controllers, hotkeys and TCI `volume` follow the active output (#6129, #6260). - **Peripherals:** the KPA1500 (#5905), CTR2 link extensions and the AetherKnob (#6338), TMate 2 udev (#6250), MIDI RADE Modem and FDV modes (#6031, #6038). - **RTL-SDR:** live PPM (#6082). - **Icom:** COMP and the MK2 squelch scale (#6174, #6184). - **Known issues.** Entries whose GitHub issues are closed are removed (each checked with `gh issue view`). As decided, this includes three closed today without a demonstrated fix: #5107, #4824 and #5665. - **US spelling.** `docs-style-guide.md` asked for British spelling; it now asks for US, by the maintainer's decision, and every page is converted. The generated TCI pages are converted at their source, the prose strings in `tools/docs/gen_reference.py`. Code spans, links and URLs were left alone. UI labels were checked against the source strings, and none of them are British. Two in-page anchors and two Log Analyzer rule anchors (`#bnr-is-grayed-out-…`) follow the renamed headings. - **Stable snapshot.** `snapshot_stable.py 26.10.2` was re-run on the release tree with these corrections, before `main` was merged in, so Stable carries no post-release docs. `docs/` also has `main`'s post-release pages, in US spelling. Stable alone keeps the IC-9700 waterfall-rate entry (#5996): #6402 fixed it after the 26.10.2 tag, so 26.10.2 still has it. The PDF manual already attached to the v26.10.2 release keeps the tag's docs. ## Validation - `npm ci && npm run build` in `docs/user`: success. This is the broken-link and broken-anchor check. - `node tools/docs/test_log_rules.mjs`: 707 checks passed for 45 rules. - `python3 tools/docs/gen_reference.py --check`: up to date (8 pages). - `git diff --check`: clean. - Menu paths on a build of this branch (offscreen, isolated settings, no radio), `menu list`: `Tools → AetherModem...`, `Tools → Callsign Lookup...`, `Tools → AetherMap...`, `Tools → FreeDV Reporter...`, `Tools → Waveforms...`, `View → Show live CW contacts`. ## Screenshots now out of date (not recaptured) These need a recapture pass on the radio: - **Old panadapter title strip (#6386):** `panadapter`, `main-window`, `first-session-*`, `multi-slice-two-slices`, `split-pair`, `tnf-markers`, `offscreen-vfo-indicator`, `pan-3d-stacked-trace`, `default-light-theme`. - **Pre-canon windows (#6334, #6339, #6341, #6372):** `aetherrx*`, `aethertx*`, `aethermodem-aprs`, `bnr-panel`, `connect-dialog`, `connect-by-ip-card`, `connect-icom-rows`, `connect-smartlink-card`, `rx-agc-calibration`. - **Other:** - `title-bar` (lowercase tab states, #6281); - `menu-help` (#6294); - `display-panel` (#6371); - `support-diagnostics-dialog`; - `waveforms-dialog` and `tailnet-remote-access` (show shim 0.4.0); - `about-window` (v26.10.1); - `radio-setup-qrz`. ## Found during the audit, not fixed here - **App string bug:** `TailnetShimDialog.cpp:628` tells the user to "Connect remotely with Connect → Manual"; the real path is **File → Connect to Radio... → Connect by IP**. - **ANAN AGC-T tooltips:** the RX applet and VFO tooltips show the slider position as dB, which is wrong on ANAN. #6282 lists this as a follow-up. - **MIDI Master Volume on non-Flex radios:** it is refused while PC Audio is off, whereas hotkeys, FlexControl and HID drive the line out. The docs describe the code as it is. - **RTL mode names:** `rtl-sdr.md` writes "FM-N" and "CW-R", but the source strings are `FMN` and `CWR`. Left as is until someone confirms what the mode menu displays. - **IC-705 COMP readback:** inferred from the MK2 (#6174) and not verified on an IC-705. - **Not yet documented:** the #6230 Radio Health row "Forward responses skipped", and the Store/MSIX package. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Summary
WDSP ships two impulse blankers. #5824 landed the first (ANB,
nob.c); thisadds the second (NOB,
nobII.c), which deskHPSDR, pihpsdr and Thetis all callNB2. Instead of gating the blanked window to zero it reconstructs it from the
samples either side: better when the impulse lands on a wanted signal, worse
when the noise is dense enough that those samples are not clean. Every
reference client offers both and replaces neither, and so does this.
Host-side DSP on our own IQ throughout. No wire protocol, no radio firmware,
and no behaviour change for a Flex or an Icom, whose blankers are the radio's.
WdspChannelopens both stages for the life of a receive channel andruns at most one:
processIqfeeds exactly one stage per block, chosen bythe kind. Both are created even when no blanker is on, because
create_nobEXTallocates and a lazily built stage would allocate on thereal-time path the first time someone clicked NB2.
trigger is relative to a running average, so a transmit period of zeros
would decay that average and gate the first real receive samples.
SliceDspRequestgains
kindandfillmembers, carried on everyFeature::Nbrequest,and one new field,
Field::Fill. A change of blanker is an ordinaryField::Enabledrequest:enabledsays whether one runs,kindsayswhich, so a radio with one blanker (Flex, Sim) reads
enabledexactly asbefore.
SliceDspRequest::requestedBlanker()derives the three-state answerin one place, and a request built without a kind still means the first
blanker. There is deliberately no
Field::Kind: the NB button callssetNbKind()on every radio, so a new field would have reached Flex andSim, which do not handle it.
IRadioBackend::setSliceNoiseBlanker(id, kind, level, fill), which theHL2, ANAN and Icom
requestSliceDspcall. Kind, level and fill arrivetogether, so no ordering between them exists. Icom treats anything but
Offas on. Flex refusesField::FillasUnsupportedrather thanencoding it; the fill is only reachable where
hasHostNoiseBlankeris set(HL2, ANAN), so no operator sees that refusal.
core/backends/NoiseBlankerKind.his theseam's vocabulary;
WdspChannelkeeps its own, becausecore/dspis aleaf.
core/backends/WdspNoiseBlanker.hholds the conversion, withstatic_asserts that fail the build if an existing value is renumbered.SliceModelgainsnbKind()/nbFill(), andnbOn()is now derived fromthe kind, so there is one source of truth.
setNb(bool)keeps itsmeaning:
trueselects the first blanker (ruling 2). rigctl'sNB,SmartCat's
NB, TCI'srx_nb_enable,get slice, the MIDI mapping, thekeyboard shortcut and the band-stack entry are untouched. A radio's bool
echo cannot downgrade a host NB2 to NB.
blanker, and stays a plain toggle elsewhere (ruling 2). The label follows
the running kind; the
objectNamedoes not, since the bridge addresses thebutton by it. An NB2 fill combo appears under the DSP grid while NB2
runs, and leaves with the NB button in FM (ruling 4).
slice dsp nb2 on <level> <fill>, refused on a radiowithout
hasHostNoiseBlanker, andget slice/get hostnbreport thekind and fill beside the existing
on.aether_wdsp.hgained the NOB declarations.nobII.cwas alreadycompiled; only the curated header was missing it.
Review and rulings
85f2a124and moved ontoSliceDspRequestin340d00b8SliceDspRequestwith kind and fill340d00b8setNb(true)dropping NB2 to NB3ef4cd363ef4cd36static_assertclaim, comment length3ef4cd36Blocker 2. The review's mutations showed the ratchet guarded an inert
property. A run flag left on the unchosen stage blanks nothing, because
processIqnever feeds that stage. The one mutation that does double-blank(both stages fed, the ANB run flag left on under NB2) passed the ratchet and
failed DISCRIMINATION 2 in
hl2_noise_blanker_test.tools/check_nb_exclusivity.py, itsStatic checksstep and itsdocs/agents/backends.mdentry are gone. The test's closing comment now namesthe discrimination checks as the exclusivity pin, and the run-flag comment in
WdspChannel::setNoiseBlankernames the dispatch as the guarantee.Ruling 4. The fill row follows
m_nbModeOk, andrefreshNbControls()nowruns at both places that recompute it: the
modeChangedhandler, andsyncFromSlice(), whose earlier refresh ran beforem_nbModeOkwas set. Theshared DSP level row is unchanged.
Rebase onto
85f2a124Main was 38 commits ahead and the branch was
dirtyin eight files. The moveonto #5919 is a port, not a marker resolution:
SliceModelno longer hasnoiseBlankerCommandIssued.setNb,setNbKindandsetNbFillpublish through main'snotifyReceiveDspIntent,and
currentDspRequest()fillskindandfillinto every Nb request. Theper-field intent revisions grow from two fields to three.
RadioModel: the block of direct receive-DSPconnect()s this branchcarried is dropped;
wireSliceReceiveIntentsToBackend()owns that wiring now.WdspChannel.h: main'sFmReceiveand this branch's two enums landed atthe same spot; both kept.
backend_receive_contract_test(from aetherd: consolidate slice receive-control routing #5919)asserted
Config::noiseBlankerEnabled, which this branch replaces with thenoiseBlankerkind, so it now assertsImpulse.anan_noise_blanker_readback_testkeeps main's typed dispatch. New rows inthe contract test pin that a Flex reads a host kind as
nb=1and refuses thefill with no wire text, and
noise_blanker_kind_model_testasserts on theSliceDspRequestthe model emits.Main has since taken two Icom commits (to
12df6943). They touchIcomCivBackend.{h,cpp}, which this PR also changes. The merge is clean, andneither side uses a signature the other changed. CI on
3ef4cd36runs on themerge with
12df6943.Earlier rebases, onto
b714cd3dand732b99a9, resolved comment-proseconflicts from #6072, accepted #6075's retirement of
wdsp_nb_hold_invariant_test.cpp(its behavioural successorwdsp_nb_hold_testis ported), kept #5982's squelch API, and ported two testsmain had added since the earlier bases:
hl2_rxdsp_adaptive_taps_test.cppandreroute_dead_controls_test.cpp.Constitution principle honored
Principle IV: clean-room. WDSP's NOB is in-tree. deskHPSDR was read for which
knobs exist and what its defaults are (the 3-way choice, the shared threshold,
the zero-fill default), and no code was copied.
Principle VIII: the blanking, the fill reaching WDSP, single-stage dispatch,
the model's echo handling, the request shape, the fill row's FM rule and the
applied-state readback are each pinned by a test with a mutation run behind it,
listed below.
Test plan
85f2a124(cmake --build build).cteston85f2a124: 656 tests, 655 passed, 1 failed,one deliberate skip (
app_settings_safety_explicit-profile-path-isolation).The failure is
pgxl_panel_test: two pixel-exact geometry assertions atpgxl_panel_test.cpp:553-554, added by fix(pgxl): fit the docked panel's controls and readings. Principle XI. #6036. It fails here when runalone too, and passes in
main's Full Suite on85f2a124. It links onlyQt, and none of the 44 files in its include closure is changed by this
PR, so a
mainbuild here has the same inputs: the difference is thismachine's environment, not this PR.
Static checksstep run locally with--strict: the twelvetools/check_*.py, the touchpoint and seam-probe manifests, the fourchecker self-tests, and the hardcoded-colour ratchet (+0 on all three
counts).
check_command_planesits at its 369-command baseline across32 files. The accessibility step, run as CI runs it on the two changed
src/gui/files, reports 5 warnings, the same 5 asmain's copies(
m_playBtn,m_sqlSlider); none is from this PR.3ef4cd36(build,check-macos,check-windows,Static checks,Sanitizer option configures), run onthe merge with
12df6943.hl2_noise_blanker_test, linear-phasechain, 24 impulses/second on a steady tone). Impulse peak in the
demodulated audio: 0.6265 with no blanker, 0.2705 with NB,
0.2717 with NB2 zero-filling, 0.2573 with NB2 interpolating,
which also keeps the highest surviving tone RMS of the three. The engine
is unchanged since; the figures are from
732b99a9, the 10-04 reviewreproduced them on
484edefb, and the test passes on this head.fill row ignoring the FM gate; each of the two mode sites not
refreshing; the Flex fill refusal removed; a kind change sent as a
Levelrequest.Advancedonto the firstblanker makes NB2's output bit-identical to NB's (DISCRIMINATION 1);
dropping
SetEXTNOBModemakes the two fills identical(DISCRIMINATION 2); applying
SliceDelta::nbnaively turns NB2 into NB;echoing the request instead of reading the installed channel fails the
applied-kind readback; and four mutations of the
slice dsp nb2capability guard. The review's M1+M2 shows DISCRIMINATION 2 also
catches double-blanking.
SliceDspRequest::requestedBlanker(). It lives inReceiveCommand.h, under most ofaethercore, so each mutation coststwo near-full rebuilds.
noise_blanker_kind_model_testasserts itsanswer for NB2.
pre-aetherd: consolidate slice receive-control routing #5919 head, not this one: the button cycle and its label, the fill
row appearing only on NB2, no tick on switching, NB2 and its fill
surviving a sample-rate change. The routing beneath that changed in the
rebase, and the FM fill-row fix is unit-tested only, so the bench
check has not been repeated on this head.
whether the five fills differ audibly, the level slider biting, and the
transmit hold. No impulse noise source here, and no TX on the G2 yet.
The transmit hold is tested against WDSP for the first blanker only
(
wdsp_nb_hold_test); nothing exercises it with NB2, which is thethinnest part of this change.
Checklist
AppSettingscalls. None added.MeterSmoother. No meter UI touched.docs/automation-bridge.md(thenb2control andthe kind/fill readback) and the HL2 backend design's seam table. The
docs/agents/backends.mdentry an earlier head added is removed again.🤖 Generated with Claude Code