From 262e78e72bdc9656d9bf630d500ac07e7c5bb585 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sun, 15 Mar 2026 09:28:59 +0100 Subject: [PATCH] [fix](trx-backend-soapysdr): fix AM demodulation DC blocker, AGC, and add IQ AGC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three issues caused audible distortion on AM reception: 1. DC blocker shared r=0.9999 (τ≈1.25 s at 8 kHz) across all modes. For AM the envelope detector outputs A_c+m(t) — always positive — so the blocker needs to track the carrier bias quickly. AM now uses r=0.999 (τ≈125 ms), 10× faster, while keeping the highpass cutoff below 2 Hz so speech is unaffected. 2. Audio AGC time constants were inverted relative to good AM AGC design: attack=200 ms (should be fast to prevent overload) and release=3500 ms (unreasonably sluggish). Changed to attack=5 ms / release=200 ms, target=0.5, max_gain=36 dB. 3. No IQ AGC before envelope detection meant carrier amplitude variation went directly into the audio chain, forcing the slow audio AGC to handle both RF level and audio level simultaneously. Added an AM IQ AGC (attack=0.5 ms, release=50 ms, target=0.7, max=30 dB) that normalizes carrier power before demod_am, so the DC blocker always sees the same steady-state bias regardless of signal strength. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Stan Grams --- .../trx-backend-soapysdr/src/dsp/channel.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs index 7c16ccc..2d5a3b3 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs @@ -114,7 +114,7 @@ fn agc_for_mode(mode: &RigMode, audio_sample_rate: u32) -> SoftAgc { let sr = audio_sample_rate.max(1) as f32; match mode { RigMode::CW | RigMode::CWR => SoftAgc::new(sr, 1.0, 50.0, 0.5, 30.0), - RigMode::AM => SoftAgc::new(sr, 200.0, 3_500.0, 0.95, 46.0), + RigMode::AM => SoftAgc::new(sr, 5.0, 200.0, 0.5, 36.0), _ => SoftAgc::new(sr, 5.0, 500.0, 0.5, 30.0), } } @@ -124,6 +124,11 @@ fn iq_agc_for_mode(mode: &RigMode, sample_rate: u32) -> Option { match mode { RigMode::FM | RigMode::PKT => Some(SoftAgc::new(sr, 0.5, 150.0, 0.8, 12.0)), RigMode::AIS => Some(SoftAgc::new(sr, 0.5, 150.0, 0.8, 12.0)), + // AM: normalize carrier amplitude before envelope detection so the + // DC blocker always sees the same steady-state bias (~0.7) regardless + // of RF signal strength. Fast attack (0.5 ms) catches sudden carrier + // appearance; 50 ms release tracks slow fading without distorting audio. + RigMode::AM => Some(SoftAgc::new(sr, 0.5, 50.0, 0.7, 30.0)), RigMode::WFM => None, _ => None, } @@ -132,6 +137,11 @@ fn iq_agc_for_mode(mode: &RigMode, sample_rate: u32) -> Option { fn dc_for_mode(mode: &RigMode) -> Option { match mode { RigMode::WFM => None, + // AM: the envelope detector output has a large carrier-amplitude DC + // bias (A_c). r=0.999 gives τ≈125 ms at 8 kHz, tracking carrier + // level ~10× faster than r=0.9999 while still passing all audio + // (highpass cutoff <2 Hz, well below 100 Hz speech floor). + RigMode::AM => Some(DcBlocker::new(0.999)), _ => Some(DcBlocker::new(0.9999)), } }