[feat](trx-rs): redesign SDR noise blanker with tuning profiles
The old IQ noise blanker tracked a fast running RMS and, on a threshold crossing, replaced the sample with the last clean one. That hard sample-and-hold is a step discontinuity: it splatters energy back across the wideband passband, so after the narrow channel filter it often sounded worse than the noise it removed — especially on SSB, CW and digital. It also had no look-ahead (the impulse leading edge leaked through before the fast RMS reacted), blanked only single samples, and used a fixed 1/128 time constant that did not scale with capture rate. Redesign the blanker around accepted wideband-NB practice and add profiles matched to the interference source: - Noise-floor tracker updated only from clean samples and frozen while blanking, so a burst cannot desensitise detection. - Detection on instantaneous power vs threshold² × noise floor. - Look-ahead delay line so the gate closes *before* the impulse reaches the output, removing the leading edge. - Raised-cosine tapered gate (ramp 1→0→1) instead of a hard hold, which minimises blanker splatter. - Windowed blanking that re-arms on every detected sample to cover the full width of a burst. - All timings expressed in real time and converted to samples at the capture rate, so behaviour is consistent across SDR sample rates. Profiles (`NoiseBlankerProfile`, default `spike`): spike, ignition, powerline, broadband — each selects the blank window, look-ahead, taper and floor time constant. `threshold` stays orthogonal as the sensitivity knob. Core/protocol: - New `NoiseBlankerProfile` in trx-core (serde/parse/u8/TS), re-exported at the crate root; `RigFilterState.sdr_nb_profile` for state sync. - `profile` added to `RigCommand`/`ClientCommand::SetSdrNoiseBlanker`, the trait method, and the command mapping. Config: `[rig.sdr.noise_blanker] profile = "spike"` (regenerated trx-rs.toml.example). SDR backend: `NoiseBlanker` rewritten in the channel DSP; profile wired through `SoapySdrConfig`, the runtime setter, and `filter_state()`. Frontend: an "NB profile" selector in the SDR advanced controls (POST /set_sdr_noise_blanker&profile=…), reflecting server state; the profile rides along with the enable/threshold quick toggle so it is preserved. Regenerated generated.ts and app.js. Tests: profile u8/parse round-trips (trx-core); DSP tests for impulse suppression with no leading-edge leak, strong-steady-signal pass-through, and wider-profile-blanks-longer. Docs: User-Manual NB section rewritten for the new algorithm and profiles. Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -4766,6 +4766,13 @@ function render(update) {
|
||||
sdrNbThresholdEl.value = String(Math.round(update.filter.sdr_nb_threshold));
|
||||
}
|
||||
}
|
||||
if (typeof update.filter.sdr_nb_profile === "string") {
|
||||
sdrNbProfile = update.filter.sdr_nb_profile;
|
||||
if (sdrNbProfileWrapEl) sdrNbProfileWrapEl.style.display = "";
|
||||
if (sdrNbProfileEl && document.activeElement !== sdrNbProfileEl) {
|
||||
sdrNbProfileEl.value = update.filter.sdr_nb_profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof update.filter.sdr_dig_sideband === "string") {
|
||||
sdrDigSidebandSupported = true;
|
||||
@@ -6644,7 +6651,10 @@ var sdrNbEnabledEl = document.getElementById("sdr-nb-enabled");
|
||||
var sdrNbThresholdControlsEl = document.getElementById("sdr-nb-threshold-controls");
|
||||
var sdrNbThresholdEl = document.getElementById("sdr-nb-threshold");
|
||||
var sdrNbThresholdSetBtn = document.getElementById("sdr-nb-threshold-set");
|
||||
var sdrNbProfileWrapEl = document.getElementById("sdr-nb-profile-wrap");
|
||||
var sdrNbProfileEl = document.getElementById("sdr-nb-profile");
|
||||
var sdrNbSupported = false;
|
||||
var sdrNbProfile = "spike";
|
||||
var sdrDigSidebandWrapEl = document.getElementById("sdr-dig-sideband-wrap");
|
||||
var sdrDigSidebandEl = document.getElementById("sdr-dig-sideband");
|
||||
var sdrDigSidebandSupported = false;
|
||||
@@ -7019,7 +7029,7 @@ function submitSdrNbState() {
|
||||
const threshold = sdrNbThresholdEl ? Number.parseFloat(sdrNbThresholdEl.value) : 10;
|
||||
if (!isFiniteNumber(threshold) || threshold < 1 || threshold > 100) return;
|
||||
postPath(
|
||||
`/set_sdr_noise_blanker?enabled=${enabled ? "true" : "false"}&threshold=${encodeURIComponent(threshold)}`
|
||||
`/set_sdr_noise_blanker?enabled=${enabled ? "true" : "false"}&threshold=${encodeURIComponent(threshold)}&profile=${encodeURIComponent(sdrNbProfile)}`
|
||||
).catch(() => {
|
||||
});
|
||||
}
|
||||
@@ -7028,6 +7038,16 @@ if (sdrNbEnabledEl) {
|
||||
submitSdrNbState();
|
||||
});
|
||||
}
|
||||
if (sdrNbProfileEl) {
|
||||
sdrNbProfileEl.addEventListener("change", () => {
|
||||
const profile = sdrNbProfileEl.value || "spike";
|
||||
if (profile !== "spike" && profile !== "ignition" && profile !== "powerline" && profile !== "broadband") {
|
||||
return;
|
||||
}
|
||||
sdrNbProfile = profile;
|
||||
submitSdrNbState();
|
||||
});
|
||||
}
|
||||
function submitSdrNbThreshold() {
|
||||
if (!sdrNbThresholdEl) return;
|
||||
const parsed = Number.parseFloat(sdrNbThresholdEl.value);
|
||||
|
||||
@@ -385,6 +385,15 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</label>
|
||||
<button id="sdr-nb-threshold-set" type="button" class="wfm-inline-btn">Set</button>
|
||||
</div>
|
||||
<label class="wfm-control" id="sdr-nb-profile-wrap" style="display:none;">
|
||||
<span class="wfm-control-label" title="Matches the blanker to the interference: Spike = sharp bursts, Ignition = engine/PWM, Powerline = mains buzz, Broadband = dense noise.">NB profile</span>
|
||||
<select id="sdr-nb-profile" class="status-input">
|
||||
<option value="spike">Spike</option>
|
||||
<option value="ignition">Ignition</option>
|
||||
<option value="powerline">Powerline</option>
|
||||
<option value="broadband">Broadband</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="wfm-control" id="sdr-dig-sideband-wrap" style="display:none;">
|
||||
<span class="wfm-control-label" title="Sideband used to demodulate DIG. Auto = USB ≥ 10 MHz, LSB below.">DIG sideband</span>
|
||||
<select id="sdr-dig-sideband" class="status-input">
|
||||
|
||||
Reference in New Issue
Block a user