[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 <sjg@haxx.space>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-03 01:58:11 +01:00
parent e2e4d34f30
commit c1ed936c2e
2 changed files with 6 additions and 53 deletions
@@ -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,
@@ -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<f32>]) -> Vec<f32> {
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<Complex<f32>> = (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::<f32>() / (out.len().saturating_sub(1) as f32);
assert!(
avg > 0.95,
"AM rotating carrier: expected strong average coherent output, got {avg}"
);
}
}