[feat](trx-backend-soapysdr): prefilter rds subcarrier
Co-authored-by: Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -6,12 +6,70 @@ use num_complex::Complex;
|
|||||||
use trx_core::rig::state::{RdsData, RigMode};
|
use trx_core::rig::state::{RdsData, RigMode};
|
||||||
use trx_rds::RdsDecoder;
|
use trx_rds::RdsDecoder;
|
||||||
|
|
||||||
|
const RDS_SUBCARRIER_HZ: f32 = 57_000.0;
|
||||||
|
const RDS_BPF_Q: f32 = 8.0;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct OnePoleLowPass {
|
struct OnePoleLowPass {
|
||||||
alpha: f32,
|
alpha: f32,
|
||||||
y: f32,
|
y: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct BiquadBandPass {
|
||||||
|
b0: f32,
|
||||||
|
b1: f32,
|
||||||
|
b2: f32,
|
||||||
|
a1: f32,
|
||||||
|
a2: f32,
|
||||||
|
x1: f32,
|
||||||
|
x2: f32,
|
||||||
|
y1: f32,
|
||||||
|
y2: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BiquadBandPass {
|
||||||
|
fn new(sample_rate: f32, center_hz: f32, q: f32) -> Self {
|
||||||
|
let sr = sample_rate.max(1.0);
|
||||||
|
let center = center_hz.clamp(100.0, sr * 0.45);
|
||||||
|
let q = q.max(0.2);
|
||||||
|
let w0 = 2.0 * std::f32::consts::PI * center / sr;
|
||||||
|
let alpha = w0.sin() / (2.0 * q);
|
||||||
|
let cos_w0 = w0.cos();
|
||||||
|
|
||||||
|
let a0 = 1.0 + alpha;
|
||||||
|
let inv_a0 = 1.0 / a0;
|
||||||
|
|
||||||
|
// RBJ band-pass, constant skirt gain.
|
||||||
|
let b0 = alpha * inv_a0;
|
||||||
|
let b1 = 0.0;
|
||||||
|
let b2 = -alpha * inv_a0;
|
||||||
|
let a1 = (-2.0 * cos_w0) * inv_a0;
|
||||||
|
let a2 = (1.0 - alpha) * inv_a0;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
b0,
|
||||||
|
b1,
|
||||||
|
b2,
|
||||||
|
a1,
|
||||||
|
a2,
|
||||||
|
x1: 0.0,
|
||||||
|
x2: 0.0,
|
||||||
|
y1: 0.0,
|
||||||
|
y2: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(&mut self, x: f32) -> f32 {
|
||||||
|
let y = self.b0 * x + self.b1 * self.x1 + self.b2 * self.x2 - self.a1 * self.y1 - self.a2 * self.y2;
|
||||||
|
self.x2 = self.x1;
|
||||||
|
self.x1 = x;
|
||||||
|
self.y2 = self.y1;
|
||||||
|
self.y1 = y;
|
||||||
|
y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl OnePoleLowPass {
|
impl OnePoleLowPass {
|
||||||
fn new(sample_rate: f32, cutoff_hz: f32) -> Self {
|
fn new(sample_rate: f32, cutoff_hz: f32) -> Self {
|
||||||
let sr = sample_rate.max(1.0);
|
let sr = sample_rate.max(1.0);
|
||||||
@@ -52,6 +110,7 @@ impl Deemphasis {
|
|||||||
pub struct WfmStereoDecoder {
|
pub struct WfmStereoDecoder {
|
||||||
output_channels: usize,
|
output_channels: usize,
|
||||||
rds_decoder: RdsDecoder,
|
rds_decoder: RdsDecoder,
|
||||||
|
rds_bpf: BiquadBandPass,
|
||||||
pilot_phase: f32,
|
pilot_phase: f32,
|
||||||
pilot_freq: f32,
|
pilot_freq: f32,
|
||||||
pilot_freq_err: f32,
|
pilot_freq_err: f32,
|
||||||
@@ -84,6 +143,7 @@ impl WfmStereoDecoder {
|
|||||||
Self {
|
Self {
|
||||||
output_channels: output_channels.max(1),
|
output_channels: output_channels.max(1),
|
||||||
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),
|
||||||
pilot_phase: 0.0,
|
pilot_phase: 0.0,
|
||||||
pilot_freq: 2.0 * std::f32::consts::PI * 19_000.0 / composite_rate_f,
|
pilot_freq: 2.0 * std::f32::consts::PI * 19_000.0 / composite_rate_f,
|
||||||
pilot_freq_err: 0.0,
|
pilot_freq_err: 0.0,
|
||||||
@@ -122,7 +182,8 @@ impl WfmStereoDecoder {
|
|||||||
let pilot_mag = (i * i + q * q).sqrt();
|
let pilot_mag = (i * i + q * q).sqrt();
|
||||||
let stereo_blend = (pilot_mag * 40.0).clamp(0.0, 1.0);
|
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_quality = (0.35 + pilot_mag * 20.0).clamp(0.35, 1.0);
|
||||||
let _ = self.rds_decoder.process_sample(x, rds_quality);
|
let rds_band = self.rds_bpf.process(x);
|
||||||
|
let _ = self.rds_decoder.process_sample(rds_band, rds_quality);
|
||||||
|
|
||||||
let sum = self.sum_lp.process(x);
|
let sum = self.sum_lp.process(x);
|
||||||
let stereo_carrier = (2.0 * self.pilot_phase).cos() * 2.0;
|
let stereo_carrier = (2.0 * self.pilot_phase).cos() * 2.0;
|
||||||
|
|||||||
Reference in New Issue
Block a user