diff --git a/src/decoders/trx-rds/src/lib.rs b/src/decoders/trx-rds/src/lib.rs index 614cbad..2c4ca81 100644 --- a/src/decoders/trx-rds/src/lib.rs +++ b/src/decoders/trx-rds/src/lib.rs @@ -14,6 +14,7 @@ const SEARCH_REG_MASK: u32 = (1 << 26) - 1; const PHASE_CANDIDATES: usize = 8; const BIPHASE_CLOCK_WINDOW: usize = 128; const RDS_BASEBAND_LP_HZ: f32 = 3_000.0; +const MIN_PUBLISH_QUALITY: f32 = 0.45; const OFFSET_A: u16 = 0x0FC; const OFFSET_B: u16 = 0x198; @@ -328,7 +329,7 @@ impl RdsDecoder { } pub fn process_sample(&mut self, sample: f32, quality: f32) -> Option<&RdsData> { - let _ = quality; + let publish_quality = quality.clamp(0.0, 1.0); let (sin_p, cos_p) = self.carrier_phase.sin_cos(); self.carrier_phase = (self.carrier_phase + self.carrier_inc).rem_euclid(TAU); let mixed_i = self.i_lp.process(sample * cos_p * 2.0); @@ -338,7 +339,10 @@ impl RdsDecoder { if let Some(update) = candidate.process_sample(mixed_i, mixed_q) { if candidate.score >= self.best_score { self.best_score = candidate.score; - self.best_state = Some(update); + let same_pi = self.best_state.as_ref().and_then(|state| state.pi) == update.pi; + if publish_quality >= MIN_PUBLISH_QUALITY || same_pi || self.best_state.is_none() { + self.best_state = Some(update); + } } } } 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 d4914dc..1f6ba98 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 @@ -7,7 +7,7 @@ use trx_core::rig::state::{RdsData, RigMode}; use trx_rds::RdsDecoder; const RDS_SUBCARRIER_HZ: f32 = 57_000.0; -const RDS_BPF_Q: f32 = 8.0; +const RDS_BPF_Q: f32 = 10.0; #[derive(Debug, Clone)] struct OnePoleLowPass { @@ -28,6 +28,30 @@ struct BiquadBandPass { y2: f32, } +#[derive(Debug, Clone)] +struct DcBlocker { + r: f32, + x1: f32, + y1: f32, +} + +impl DcBlocker { + fn new(r: f32) -> Self { + Self { + r: r.clamp(0.9, 0.9999), + x1: 0.0, + y1: 0.0, + } + } + + fn process(&mut self, x: f32) -> f32 { + let y = x - self.x1 + self.r * self.y1; + self.x1 = x; + self.y1 = y; + y + } +} + impl BiquadBandPass { fn new(sample_rate: f32, center_hz: f32, q: f32) -> Self { let sr = sample_rate.max(1.0); @@ -111,6 +135,7 @@ pub struct WfmStereoDecoder { output_channels: usize, rds_decoder: RdsDecoder, rds_bpf: BiquadBandPass, + rds_dc: DcBlocker, pilot_phase: f32, pilot_freq: f32, pilot_freq_err: f32, @@ -144,6 +169,7 @@ impl WfmStereoDecoder { output_channels: output_channels.max(1), rds_decoder: RdsDecoder::new(composite_rate), rds_bpf: BiquadBandPass::new(composite_rate_f, RDS_SUBCARRIER_HZ, RDS_BPF_Q), + rds_dc: DcBlocker::new(0.995), pilot_phase: 0.0, pilot_freq: 2.0 * std::f32::consts::PI * 19_000.0 / composite_rate_f, pilot_freq_err: 0.0, @@ -183,7 +209,8 @@ impl WfmStereoDecoder { let stereo_blend = (pilot_mag * 40.0).clamp(0.0, 1.0); let rds_quality = (0.35 + pilot_mag * 20.0).clamp(0.35, 1.0); let rds_band = self.rds_bpf.process(x); - let _ = self.rds_decoder.process_sample(rds_band, rds_quality); + let rds_clean = self.rds_dc.process(rds_band); + let _ = self.rds_decoder.process_sample(rds_clean, rds_quality); let sum = self.sum_lp.process(x); let stereo_carrier = (2.0 * self.pilot_phase).cos() * 2.0;