[feat](trx-rds,trx-backend-soapysdr): condition weak-signal rds updates
Co-authored-by: Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user