From c1ed936c2e682a9a54d5081353bd42929e10d1a7 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Tue, 3 Mar 2026 01:58:11 +0100 Subject: [PATCH] [fix](trx-backend-soapysdr): restore simple AM demodulation Revert the recent AM coherent detector experiment and restore the original envelope detector path. Co-authored-by: Stan Grams Signed-off-by: Stan Grams --- .../trx-backend-soapysdr/src/demod.rs | 2 +- .../trx-backend-soapysdr/src/demod/am.rs | 57 ++----------------- 2 files changed, 6 insertions(+), 53 deletions(-) diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod.rs index 3a60401..d6796fd 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod.rs @@ -135,7 +135,7 @@ pub enum Demodulator { Usb, /// Lower sideband SSB: negate imaginary part before taking real part. Lsb, - /// AM coherent detector using a limiter-derived carrier reference. + /// AM envelope detector: magnitude of IQ, DC-removed. Am, /// Narrow-band FM: instantaneous frequency via quadrature. Fm, diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod/am.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod/am.rs index 10293f0..4320954 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod/am.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod/am.rs @@ -4,42 +4,12 @@ use num_complex::Complex; -/// AM demodulator using a limiter-derived coherent reference. -/// -/// This is a practical DSP analogue of the patent's split/limit/mix approach: -/// derive a fixed-amplitude carrier reference from the incoming IQ, smooth that -/// reference to follow carrier phase/frequency, then mix the original branch by -/// the conjugate reference and take the in-phase projection. +/// AM envelope detector: magnitude of IQ. pub(super) fn demod_am(samples: &[Complex]) -> Vec { - const EPSILON: f32 = 1.0e-12; - const REF_BLEND: f32 = 0.02; - - let mut out = Vec::with_capacity(samples.len()); - let mut carrier_ref = Complex::new(1.0_f32, 0.0); - - for &sample in samples { - let mag_sq = sample.re * sample.re + sample.im * sample.im; - if mag_sq <= EPSILON { - out.push(0.0); - continue; - } - - let mag = mag_sq.sqrt(); - let limited = sample / mag; - let blended = carrier_ref * (1.0 - REF_BLEND) + limited * REF_BLEND; - let blended_mag_sq = blended.re * blended.re + blended.im * blended.im; - if blended_mag_sq > EPSILON { - carrier_ref = blended / blended_mag_sq.sqrt(); - } else { - carrier_ref = limited; - } - - // Project the original signal onto the limiter-derived carrier phase. - let mixed = sample * carrier_ref.conj(); - out.push(mixed.re); - } - - out + samples + .iter() + .map(|sample| (sample.re * sample.re + sample.im * sample.im).sqrt()) + .collect() } #[cfg(test)] @@ -83,21 +53,4 @@ mod tests { assert_approx_eq(got, exp, 1e-6, &format!("AM sample {idx}")); } } - - #[test] - fn test_am_tracks_rotating_carrier_reference() { - let input: Vec> = (0..16) - .map(|idx| { - let angle = idx as f32 * 0.05; - Complex::new(angle.cos(), angle.sin()) - }) - .collect(); - let out = demod_am(&input); - assert_eq!(out.len(), input.len()); - let avg = out.iter().skip(1).copied().sum::() / (out.len().saturating_sub(1) as f32); - assert!( - avg > 0.95, - "AM rotating carrier: expected strong average coherent output, got {avg}" - ); - } }