[fix](trx-backend-soapysdr): keep wfm discriminator state across blocks
Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -401,6 +401,7 @@ pub struct WfmStereoDecoder {
|
|||||||
rds_decoder: RdsDecoder,
|
rds_decoder: RdsDecoder,
|
||||||
rds_bpf: BiquadBandPass,
|
rds_bpf: BiquadBandPass,
|
||||||
rds_dc: DcBlocker,
|
rds_dc: DcBlocker,
|
||||||
|
prev_iq: Option<Complex<f32>>,
|
||||||
pilot_phase: f32,
|
pilot_phase: f32,
|
||||||
pilot_freq: f32,
|
pilot_freq: f32,
|
||||||
pilot_i_lp: OnePoleLowPass,
|
pilot_i_lp: OnePoleLowPass,
|
||||||
@@ -475,6 +476,7 @@ impl WfmStereoDecoder {
|
|||||||
rds_decoder: RdsDecoder::new(composite_rate),
|
rds_decoder: RdsDecoder::new(composite_rate),
|
||||||
rds_bpf: BiquadBandPass::new(composite_rate_f, RDS_SUBCARRIER_HZ, RDS_BPF_Q),
|
rds_bpf: BiquadBandPass::new(composite_rate_f, RDS_SUBCARRIER_HZ, RDS_BPF_Q),
|
||||||
rds_dc: DcBlocker::new(0.995),
|
rds_dc: DcBlocker::new(0.995),
|
||||||
|
prev_iq: None,
|
||||||
pilot_phase: 0.0,
|
pilot_phase: 0.0,
|
||||||
pilot_freq: 2.0 * std::f32::consts::PI * PILOT_HZ / composite_rate_f,
|
pilot_freq: 2.0 * std::f32::consts::PI * PILOT_HZ / composite_rate_f,
|
||||||
pilot_i_lp: OnePoleLowPass::new(composite_rate_f, 400.0),
|
pilot_i_lp: OnePoleLowPass::new(composite_rate_f, 400.0),
|
||||||
@@ -512,7 +514,7 @@ impl WfmStereoDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_iq(&mut self, samples: &[Complex<f32>]) -> Vec<f32> {
|
pub fn process_iq(&mut self, samples: &[Complex<f32>]) -> Vec<f32> {
|
||||||
let composite = demod_fm(samples);
|
let composite = demod_fm_with_prev(samples, &mut self.prev_iq);
|
||||||
if composite.is_empty() {
|
if composite.is_empty() {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
@@ -659,6 +661,10 @@ impl WfmStereoDecoder {
|
|||||||
self.stereo_detected = false;
|
self.stereo_detected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset_demod_state(&mut self) {
|
||||||
|
self.prev_iq = None;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stereo_detected(&self) -> bool {
|
pub fn stereo_detected(&self) -> bool {
|
||||||
self.stereo_detected
|
self.stereo_detected
|
||||||
}
|
}
|
||||||
@@ -756,14 +762,24 @@ fn demod_am(samples: &[Complex<f32>]) -> Vec<f32> {
|
|||||||
|
|
||||||
/// FM quadrature discriminator: instantaneous frequency via arg(s[n] * conj(s[n-1])).
|
/// FM quadrature discriminator: instantaneous frequency via arg(s[n] * conj(s[n-1])).
|
||||||
/// Output is in radians/sample, scaled by 1/π to normalise to [-1, 1].
|
/// Output is in radians/sample, scaled by 1/π to normalise to [-1, 1].
|
||||||
fn demod_fm(samples: &[Complex<f32>]) -> Vec<f32> {
|
fn demod_fm_with_prev(
|
||||||
|
samples: &[Complex<f32>],
|
||||||
|
prev: &mut Option<Complex<f32>>,
|
||||||
|
) -> Vec<f32> {
|
||||||
if samples.is_empty() {
|
if samples.is_empty() {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let inv_pi = std::f32::consts::FRAC_1_PI;
|
let inv_pi = std::f32::consts::FRAC_1_PI;
|
||||||
let mut output = Vec::with_capacity(samples.len());
|
let mut output = Vec::with_capacity(samples.len());
|
||||||
output.push(0.0_f32);
|
|
||||||
|
if let Some(prev_sample) = prev.as_ref().copied() {
|
||||||
|
let product = samples[0] * prev_sample.conj();
|
||||||
|
let angle = product.im.atan2(product.re);
|
||||||
|
output.push(angle * inv_pi);
|
||||||
|
} else {
|
||||||
|
output.push(0.0_f32);
|
||||||
|
}
|
||||||
|
|
||||||
for i in 1..samples.len() {
|
for i in 1..samples.len() {
|
||||||
let product = samples[i] * samples[i - 1].conj();
|
let product = samples[i] * samples[i - 1].conj();
|
||||||
@@ -771,9 +787,17 @@ fn demod_fm(samples: &[Complex<f32>]) -> Vec<f32> {
|
|||||||
output.push(angle * inv_pi);
|
output.push(angle * inv_pi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*prev = samples.last().copied();
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// FM quadrature discriminator: instantaneous frequency via arg(s[n] * conj(s[n-1])).
|
||||||
|
/// Output is in radians/sample, scaled by 1/π to normalise to [-1, 1].
|
||||||
|
fn demod_fm(samples: &[Complex<f32>]) -> Vec<f32> {
|
||||||
|
let mut prev = None;
|
||||||
|
demod_fm_with_prev(samples, &mut prev)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// CW
|
// CW
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -605,6 +605,7 @@ impl ChannelDsp {
|
|||||||
if let Some(decoder) = &mut self.wfm_decoder {
|
if let Some(decoder) = &mut self.wfm_decoder {
|
||||||
decoder.reset_rds();
|
decoder.reset_rds();
|
||||||
decoder.reset_stereo_detect();
|
decoder.reset_stereo_detect();
|
||||||
|
decoder.reset_demod_state();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user