[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:
@@ -135,7 +135,7 @@ pub enum Demodulator {
|
|||||||
Usb,
|
Usb,
|
||||||
/// Lower sideband SSB: negate imaginary part before taking real part.
|
/// Lower sideband SSB: negate imaginary part before taking real part.
|
||||||
Lsb,
|
Lsb,
|
||||||
/// AM coherent detector using a limiter-derived carrier reference.
|
/// AM envelope detector: magnitude of IQ, DC-removed.
|
||||||
Am,
|
Am,
|
||||||
/// Narrow-band FM: instantaneous frequency via quadrature.
|
/// Narrow-band FM: instantaneous frequency via quadrature.
|
||||||
Fm,
|
Fm,
|
||||||
|
|||||||
@@ -4,42 +4,12 @@
|
|||||||
|
|
||||||
use num_complex::Complex;
|
use num_complex::Complex;
|
||||||
|
|
||||||
/// AM demodulator using a limiter-derived coherent reference.
|
/// AM envelope detector: magnitude of IQ.
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
pub(super) fn demod_am(samples: &[Complex<f32>]) -> Vec<f32> {
|
pub(super) fn demod_am(samples: &[Complex<f32>]) -> Vec<f32> {
|
||||||
const EPSILON: f32 = 1.0e-12;
|
samples
|
||||||
const REF_BLEND: f32 = 0.02;
|
.iter()
|
||||||
|
.map(|sample| (sample.re * sample.re + sample.im * sample.im).sqrt())
|
||||||
let mut out = Vec::with_capacity(samples.len());
|
.collect()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -83,21 +53,4 @@ mod tests {
|
|||||||
assert_approx_eq(got, exp, 1e-6, &format!("AM sample {idx}"));
|
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}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user