[feat](trx-rs): add CCI/ACI bars to WFM panel with RDS mitigation

Estimate Co-Channel Interference (CCI) from pilot tone quadrature
leakage and coherence degradation.  Estimate Adjacent Channel
Interference (ACI) from CMA equalizer tap deviation from identity.
Both metrics (0-100 scale) are surfaced through RigFilterState and
displayed as colour-coded bars in the WFM control panel.

The RDS decoder quality parameter is now adaptively penalised when
CCI/ACI levels are elevated, reducing block-error rate under
interference conditions.

https://claude.ai/code/session_016EKzep42RCvE4GxvvRaCwu
Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-03-27 08:17:34 +00:00
committed by Stan Grams
parent 6b33550116
commit 4da4d8ec66
8 changed files with 180 additions and 9 deletions
@@ -1701,6 +1701,7 @@ function resetDecoderStateOnRigSwitch() {
vchanRdsById = new Map();
resetRdsDisplay();
resetWfmStereoIndicator();
resetIntfBars();
// Spectrum — clear stale data from previous rig's SDR
lastSpectrumData = null;
@@ -1728,6 +1729,23 @@ function resetWfmStereoIndicator() {
wfmStFlagEl.classList.add("wfm-st-flag-mono");
}
function updateIntfBar(fillEl, valEl, level) {
if (!fillEl || !valEl) return;
const v = Math.round(Math.min(Math.max(level, 0), 100));
valEl.textContent = String(v);
fillEl.style.width = v + "%";
fillEl.classList.toggle("wfm-intf-warn", v >= 35 && v < 65);
fillEl.classList.toggle("wfm-intf-high", v >= 65);
if (v < 35) {
fillEl.classList.remove("wfm-intf-warn", "wfm-intf-high");
}
}
function resetIntfBars() {
updateIntfBar(wfmCciFillEl, wfmCciValEl, 0);
updateIntfBar(wfmAciFillEl, wfmAciValEl, 0);
}
// ── Fast CSS-based frequency/BW marker positioning ──────────────────────────
// These lightweight DOM elements reposition via `transform: translateX()`
// which is GPU-composited — zero layout/paint cost. The full WebGL overlay
@@ -1800,6 +1818,7 @@ function applyLocalTunedFrequency(hz, forceDisplay = false) {
primaryRds = null;
resetRdsDisplay();
resetWfmStereoIndicator();
resetIntfBars();
}
lastFreqHz = hz;
window.lastFreqHz = lastFreqHz;
@@ -2955,6 +2974,8 @@ function render(update) {
wfmStFlagEl.classList.toggle("wfm-st-flag-stereo", detected);
wfmStFlagEl.classList.toggle("wfm-st-flag-mono", !detected);
}
if (typeof update.filter.wfm_cci === "number") updateIntfBar(wfmCciFillEl, wfmCciValEl, update.filter.wfm_cci);
if (typeof update.filter.wfm_aci === "number") updateIntfBar(wfmAciFillEl, wfmAciValEl, update.filter.wfm_aci);
if (samStereoWidthEl && typeof update.filter.sam_stereo_width === "number") {
samStereoWidthEl.value = String(Math.round(update.filter.sam_stereo_width * 100));
}
@@ -7494,6 +7515,10 @@ const sdrLnaGainEl = document.getElementById("sdr-lna-gain-db");
const sdrLnaGainSetBtn = document.getElementById("sdr-lna-gain-set");
const sdrAgcEl = document.getElementById("sdr-agc-enabled");
const wfmStFlagEl = document.getElementById("wfm-st-flag");
const wfmCciFillEl = document.getElementById("wfm-cci-fill");
const wfmCciValEl = document.getElementById("wfm-cci-val");
const wfmAciFillEl = document.getElementById("wfm-aci-fill");
const wfmAciValEl = document.getElementById("wfm-aci-val");
const samControlsCol = document.getElementById("sam-controls-col");
const samStereoWidthEl = document.getElementById("sam-stereo-width");
const samCarrierSyncEl = document.getElementById("sam-carrier-sync");