[feat](trx-backend-soapysdr): add adaptive wfm stereo blend

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-01 00:07:13 +01:00
parent 5060522541
commit 206696acc5
@@ -100,6 +100,12 @@ fn polyphase_resample(
.sum() .sum()
} }
#[inline]
fn smoothstep01(x: f32) -> f32 {
let x = x.clamp(0.0, 1.0);
x * x * (3.0 - 2.0 * x)
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct OnePoleLowPass { struct OnePoleLowPass {
alpha: f32, alpha: f32,
@@ -545,13 +551,13 @@ impl WfmStereoDecoder {
let pilot_abs = self.pilot_abs_lp.process(pilot_tone.abs()); let pilot_abs = self.pilot_abs_lp.process(pilot_tone.abs());
let pilot_coherence = (pilot_mag / (pilot_abs + 1e-4)).clamp(0.0, 1.0); let pilot_coherence = (pilot_mag / (pilot_abs + 1e-4)).clamp(0.0, 1.0);
let pilot_lock = ((pilot_coherence - 0.4) / 0.2).clamp(0.0, 1.0); let pilot_lock = ((pilot_coherence - 0.4) / 0.2).clamp(0.0, 1.0);
let stereo_blend = (pilot_mag * pilot_lock * 120.0).clamp(0.0, 1.0); let stereo_drive = (pilot_mag * pilot_lock * 120.0).clamp(0.0, 1.0);
let detect_coeff = if stereo_blend > self.stereo_detect_level { let detect_coeff = if stereo_drive > self.stereo_detect_level {
0.0008 0.0008
} else { } else {
0.0002 0.0002
}; };
self.stereo_detect_level += detect_coeff * (stereo_blend - self.stereo_detect_level); self.stereo_detect_level += detect_coeff * (stereo_drive - self.stereo_detect_level);
if self.stereo_detected { if self.stereo_detected {
if self.stereo_detect_level < 0.35 { if self.stereo_detect_level < 0.35 {
self.stereo_detected = false; self.stereo_detected = false;
@@ -559,6 +565,8 @@ impl WfmStereoDecoder {
} else if self.stereo_detect_level > 0.6 { } else if self.stereo_detect_level > 0.6 {
self.stereo_detected = true; self.stereo_detected = true;
} }
let stereo_blend_target =
smoothstep01((self.stereo_detect_level - 0.18) / (0.92 - 0.18));
// --- RDS --- // --- RDS ---
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);
@@ -593,7 +601,7 @@ impl WfmStereoDecoder {
let prev_phase = self.output_phase; let prev_phase = self.output_phase;
self.output_phase += self.output_phase_inc; self.output_phase += self.output_phase_inc;
if self.output_phase < 1.0 { if self.output_phase < 1.0 {
self.prev_blend = stereo_blend; self.prev_blend = stereo_blend_target;
continue; continue;
} }
self.output_phase -= 1.0; self.output_phase -= 1.0;
@@ -605,12 +613,9 @@ impl WfmStereoDecoder {
let sum_i = polyphase_resample(&self.sum_hist, &self.resample_bank, frac); let sum_i = polyphase_resample(&self.sum_hist, &self.resample_bank, frac);
let diff_i = polyphase_resample(&self.diff_hist, &self.resample_bank, frac); let diff_i = polyphase_resample(&self.diff_hist, &self.resample_bank, frac);
let diff_q = polyphase_resample(&self.diff_q_hist, &self.resample_bank, frac); let diff_q = polyphase_resample(&self.diff_q_hist, &self.resample_bank, frac);
let blend_i = if self.stereo_detected { let blend_i =
1.0 (self.prev_blend + frac * (stereo_blend_target - self.prev_blend)).clamp(0.0, 1.0);
} else { self.prev_blend = stereo_blend_target;
(self.prev_blend + frac * (stereo_blend - self.prev_blend)).clamp(0.0, 1.0)
};
self.prev_blend = stereo_blend;
let (trim_sin, trim_cos) = STEREO_SEPARATION_PHASE_TRIM.sin_cos(); let (trim_sin, trim_cos) = STEREO_SEPARATION_PHASE_TRIM.sin_cos();
let diff_i = (diff_i * trim_cos + diff_q * trim_sin) * STEREO_SEPARATION_GAIN; let diff_i = (diff_i * trim_cos + diff_q * trim_sin) * STEREO_SEPARATION_GAIN;