From 3ccda5fb5a9e67cf153827fb91bfd1dc204bde9f Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 28 Feb 2026 22:34:54 +0100 Subject: [PATCH] [fix](trx-backend-soapysdr,trx-frontend-http): reset stereo state and soften wfm top end Co-authored-by: OpenAI Codex Signed-off-by: Stan Grams --- .../trx-frontend-http/assets/web/app.js | 8 ++++++++ .../trx-backend-soapysdr/src/demod.rs | 16 +++++++--------- .../trx-backend/trx-backend-soapysdr/src/dsp.rs | 14 ++++++++++---- .../trx-backend/trx-backend-soapysdr/src/lib.rs | 2 +- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index cf28697..31f059a 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -964,11 +964,19 @@ function resetRdsDisplay() { updateRdsPsOverlay(null); } +function resetWfmStereoIndicator() { + if (!wfmStFlagEl) return; + wfmStFlagEl.textContent = "MO"; + wfmStFlagEl.classList.remove("wfm-st-flag-stereo"); + wfmStFlagEl.classList.add("wfm-st-flag-mono"); +} + function applyLocalTunedFrequency(hz, forceDisplay = false) { if (!Number.isFinite(hz)) return; const freqChanged = lastFreqHz !== hz; if (freqChanged) { resetRdsDisplay(); + resetWfmStereoIndicator(); } lastFreqHz = hz; updateDocumentTitle(lastSpectrumData?.rds ?? null); 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 bb39fe0..5968ef2 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 @@ -23,8 +23,6 @@ const BW4_Q1: f32 = 0.5412; const BW4_Q2: f32 = 1.3066; /// Q for the 19 kHz pilot notch (~3.8 kHz 3 dB bandwidth). const PILOT_NOTCH_Q: f32 = 5.0; -/// Tighter post-matrix stereo notch for suppressing residual pilot leakage. -const STEREO_PILOT_NOTCH_Q: f32 = 12.0; /// Narrow 19 kHz band-pass used to derive zero-crossings for switching stereo demod. const PILOT_BPF_Q: f32 = 20.0; /// Fixed phase trim on the recovered L-R channel to compensate pilot-path delay. @@ -362,9 +360,6 @@ pub struct WfmStereoDecoder { dc_m: DcBlocker, dc_l: DcBlocker, dc_r: DcBlocker, - /// Post-matrix pilot notches for stereo outputs. - pilot_notch_l: BiquadNotch, - pilot_notch_r: BiquadNotch, deemph_m: Deemphasis, deemph_l: Deemphasis, deemph_r: Deemphasis, @@ -431,8 +426,6 @@ impl WfmStereoDecoder { dc_m: DcBlocker::new(0.9999), dc_l: DcBlocker::new(0.9999), dc_r: DcBlocker::new(0.9999), - pilot_notch_l: BiquadNotch::new(audio_rate.max(1) as f32, PILOT_HZ, STEREO_PILOT_NOTCH_Q), - pilot_notch_r: BiquadNotch::new(audio_rate.max(1) as f32, PILOT_HZ, STEREO_PILOT_NOTCH_Q), deemph_m: Deemphasis::new(audio_rate.max(1) as f32, deemphasis_us), deemph_l: Deemphasis::new(audio_rate.max(1) as f32, deemphasis_us), deemph_r: Deemphasis::new(audio_rate.max(1) as f32, deemphasis_us), @@ -553,11 +546,11 @@ impl WfmStereoDecoder { let right_corr = (sum_i - diff) * 0.5; let left = self .dc_l - .process(self.pilot_notch_l.process(self.deemph_l.process(left_corr))) + .process(self.deemph_l.process(left_corr)) .clamp(-1.0, 1.0); let right = self .dc_r - .process(self.pilot_notch_r.process(self.deemph_r.process(right_corr))) + .process(self.deemph_r.process(right_corr)) .clamp(-1.0, 1.0); output.push(left); output.push(right); @@ -591,6 +584,11 @@ impl WfmStereoDecoder { self.rds_decoder.reset(); } + pub fn reset_stereo_detect(&mut self) { + self.stereo_detect_level = 0.0; + self.stereo_detected = false; + } + pub fn stereo_detected(&self) -> bool { self.stereo_detected } diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs index f292fd9..803d975 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs @@ -568,6 +568,13 @@ impl ChannelDsp { } } + pub fn reset_wfm_state(&mut self) { + if let Some(decoder) = &mut self.wfm_decoder { + decoder.reset_rds(); + decoder.reset_stereo_detect(); + } + } + /// Process a block of raw IQ samples through the full DSP chain. /// /// 1. **Batch mixer**: compute the full LO signal for the block at once, @@ -990,7 +997,7 @@ mod tests { fn channel_dsp_processes_silence() { let (pcm_tx, _pcm_rx) = broadcast::channel::>(8); let mut dsp = - ChannelDsp::new(0.0, &RigMode::USB, 48_000, 8_000, 1, 20, 3000, 75, true, true, 31, pcm_tx); + ChannelDsp::new(0.0, &RigMode::USB, 48_000, 8_000, 1, 20, 3000, 75, true, 31, pcm_tx); let block = vec![Complex::new(0.0_f32, 0.0_f32); 4096]; dsp.process_block(&block); } @@ -999,7 +1006,7 @@ mod tests { fn channel_dsp_set_mode() { let (pcm_tx, _) = broadcast::channel::>(8); let mut dsp = - ChannelDsp::new(0.0, &RigMode::USB, 48_000, 8_000, 1, 20, 3000, 75, true, true, 31, pcm_tx); + ChannelDsp::new(0.0, &RigMode::USB, 48_000, 8_000, 1, 20, 3000, 75, true, 31, pcm_tx); assert_eq!(dsp.demodulator, Demodulator::Usb); dsp.set_mode(&RigMode::FM); assert_eq!(dsp.demodulator, Demodulator::Fm); @@ -1015,7 +1022,6 @@ mod tests { 20, 75, true, - true, &[(200_000.0, RigMode::USB, 3000, 64)], ); assert_eq!(pipeline.pcm_senders.len(), 1); @@ -1024,7 +1030,7 @@ mod tests { #[test] fn pipeline_empty_channels() { - let pipeline = SdrPipeline::start(Box::new(MockIqSource), 1_920_000, 48_000, 1, 20, 75, true, true, &[]); + let pipeline = SdrPipeline::start(Box::new(MockIqSource), 1_920_000, 48_000, 1, 20, 75, true, &[]); assert_eq!(pipeline.pcm_senders.len(), 0); assert_eq!(pipeline.channel_dsps.len(), 0); } diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs index a398a55..a1e911f 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs @@ -279,7 +279,7 @@ impl RigCat for SoapySdrRig { let mut dsp = dsp_arc.lock().unwrap(); dsp.set_channel_if_hz(channel_if_hz); if freq_changed { - dsp.reset_rds(); + dsp.reset_wfm_state(); } } Ok(())