[fix](trx-rs): remove wfm denoise and default stereo audio

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-02-28 22:01:17 +01:00
parent d6c89bcc6b
commit df1bbf8f5b
15 changed files with 4 additions and 169 deletions
+1 -1
View File
@@ -265,7 +265,7 @@ impl Default for AudioConfig {
tx_enabled: true,
device: None,
sample_rate: 48000,
channels: 1,
channels: 2,
frame_duration_ms: 20,
bitrate_bps: 192000,
}
-10
View File
@@ -472,16 +472,6 @@ async fn process_command(
let _ = ctx.state_tx.send(ctx.state.clone());
return snapshot_from(ctx.state);
}
RigCommand::SetWfmDenoise(enabled) => {
if let Err(e) = ctx.rig.set_wfm_denoise(enabled).await {
return Err(RigError::communication(format!("set_wfm_denoise: {e}")));
}
if let Some(f) = ctx.state.filter.as_mut() {
f.wfm_denoise = enabled;
}
let _ = ctx.state_tx.send(ctx.state.clone());
return snapshot_from(ctx.state);
}
RigCommand::SetCenterFreq(freq) => {
if let Err(e) = ctx.rig.set_center_freq(freq).await {
return Err(RigError::communication(format!("set_center_freq: {e}")));
@@ -297,52 +297,6 @@ impl BiquadNotch {
}
}
/// Three-band stereo blend for WFM.
///
/// Splits the L-R diff signal into three bands at audio rate and applies
/// SNR-dependent blending per band. Weaker pilot → more aggressive noise
/// reduction at higher frequencies while low frequencies retain more stereo.
///
/// | Band | Frequency | Blend factor |
/// |-----------|-------------|--------------|
/// | Low | 0 2 kHz | `blend` |
/// | Mid | 2 8 kHz | `blend²` |
/// | High | 8 15 kHz | `blend⁴` |
#[derive(Debug, Clone)]
struct MultibandStereoBlend {
/// 2nd-order Butterworth LPF at the low/mid crossover (2 kHz).
lo_lp: BiquadLowPass,
/// 2nd-order Butterworth LPF at the mid/high crossover (8 kHz).
hi_lp: BiquadLowPass,
}
impl MultibandStereoBlend {
fn new(audio_rate: f32) -> Self {
// Q = 1/√2 ≈ 0.7071 — maximally flat (Butterworth) 2nd-order response.
let q = std::f32::consts::FRAC_1_SQRT_2;
Self {
lo_lp: BiquadLowPass::new(audio_rate, 2_000.0, q),
hi_lp: BiquadLowPass::new(audio_rate, 8_000.0, q),
}
}
/// Apply multiband blend to a single diff sample.
///
/// `blend` is the pilot SNR estimate in [0, 1] (0 = mono, 1 = full stereo).
fn process(&mut self, diff: f32, blend: f32) -> f32 {
// Band-split via complementary LPFs.
let lo = self.lo_lp.process(diff); // 0 2 kHz
let lo_hi = self.hi_lp.process(diff); // 0 8 kHz
let mid = lo_hi - lo; // 2 8 kHz
let hi = diff - lo_hi; // 8 15 kHz
let blend2 = blend * blend;
let blend4 = blend2 * blend2;
lo * blend + mid * blend2 + hi * blend4
}
}
impl OnePoleLowPass {
fn new(sample_rate: f32, cutoff_hz: f32) -> Self {
let sr = sample_rate.max(1.0);
@@ -414,10 +368,6 @@ pub struct WfmStereoDecoder {
deemph_m: Deemphasis,
deemph_l: Deemphasis,
deemph_r: Deemphasis,
/// Multiband stereo blending applied at audio rate to the L-R diff channel.
diff_denoise: MultibandStereoBlend,
/// Whether multiband stereo denoising is active.
denoise_enabled: bool,
/// Smoothed pilot-derived stereo detection strength in [0, 1].
stereo_detect_level: f32,
/// Hysteretic pilot-lock result used by the UI.
@@ -453,7 +403,6 @@ impl WfmStereoDecoder {
output_channels: usize,
stereo_enabled: bool,
deemphasis_us: u32,
denoise_enabled: bool,
) -> Self {
let composite_rate_f = composite_rate.max(1) as f32;
let output_phase_inc = audio_rate.max(1) as f64 / composite_rate.max(1) as f64;
@@ -487,8 +436,6 @@ impl WfmStereoDecoder {
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),
diff_denoise: MultibandStereoBlend::new(audio_rate.max(1) as f32),
denoise_enabled,
stereo_detect_level: 0.0,
stereo_detected: false,
fm_gain: composite_rate_f / (2.0 * 75_000.0),
@@ -601,17 +548,9 @@ impl WfmStereoDecoder {
// --- Deemphasis + DC block + output ---
if self.output_channels >= 2 && self.stereo_enabled {
// Apply multiband or single-band stereo blend at audio rate.
let diff_denoised = if self.denoise_enabled {
// Multiband: attenuates high-frequency diff more aggressively
// when the pilot is weak, preserving the low-frequency stereo image.
self.diff_denoise.process(diff_i, blend_i)
} else {
// Single-band: uniform blend across all frequencies.
diff_i * blend_i
};
let left_corr = (sum_i + diff_denoised) * 0.5;
let right_corr = (sum_i - diff_denoised) * 0.5;
let diff = diff_i * blend_i;
let left_corr = (sum_i + diff) * 0.5;
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)))
@@ -640,10 +579,6 @@ impl WfmStereoDecoder {
output
}
pub fn set_denoise_enabled(&mut self, enabled: bool) {
self.denoise_enabled = enabled;
}
pub fn set_stereo_enabled(&mut self, enabled: bool) {
self.stereo_enabled = enabled;
}
@@ -987,7 +922,6 @@ mod tests {
2, // stereo output
true, // stereo enabled
50, // 50 µs deemphasis
false, // no denoise — test raw separation
);
let output = decoder.process_iq(&iq);
@@ -1069,7 +1003,6 @@ mod tests {
2,
true,
50,
false,
);
let output = decoder.process_iq(&iq);
@@ -311,8 +311,6 @@ pub struct ChannelDsp {
wfm_deemphasis_us: u32,
/// Whether WFM stereo decoding is enabled.
wfm_stereo: bool,
/// Whether multiband stereo denoising is enabled for WFM.
wfm_denoise: bool,
/// Decimation factor: `sdr_sample_rate / audio_sample_rate`.
pub decim_factor: usize,
/// Number of PCM channels emitted in each frame.
@@ -414,7 +412,6 @@ impl ChannelDsp {
self.output_channels,
self.wfm_stereo,
self.wfm_deemphasis_us,
self.wfm_denoise,
));
}
} else {
@@ -436,7 +433,6 @@ impl ChannelDsp {
audio_bandwidth_hz: u32,
wfm_deemphasis_us: u32,
wfm_stereo: bool,
wfm_denoise: bool,
fir_taps: usize,
pcm_tx: broadcast::Sender<Vec<f32>>,
) -> Self {
@@ -482,7 +478,6 @@ impl ChannelDsp {
fir_taps: taps,
wfm_deemphasis_us,
wfm_stereo,
wfm_denoise,
decim_factor,
output_channels,
frame_buf: Vec::with_capacity(frame_size + output_channels),
@@ -504,7 +499,6 @@ impl ChannelDsp {
output_channels,
wfm_stereo,
wfm_deemphasis_us,
wfm_denoise,
))
} else {
None
@@ -557,13 +551,6 @@ impl ChannelDsp {
}
}
pub fn set_wfm_denoise(&mut self, enabled: bool) {
self.wfm_denoise = enabled;
if let Some(decoder) = &mut self.wfm_decoder {
decoder.set_denoise_enabled(enabled);
}
}
pub fn rds_data(&self) -> Option<RdsData> {
self.wfm_decoder.as_ref().and_then(WfmStereoDecoder::rds_data)
}
@@ -724,7 +711,6 @@ impl SdrPipeline {
frame_duration_ms: u16,
wfm_deemphasis_us: u32,
wfm_stereo: bool,
wfm_denoise: bool,
channels: &[(f64, RigMode, u32, usize)],
) -> Self {
const IQ_BROADCAST_CAPACITY: usize = 64;
@@ -747,7 +733,6 @@ impl SdrPipeline {
audio_bandwidth_hz,
wfm_deemphasis_us,
wfm_stereo,
wfm_denoise,
fir_taps,
pcm_tx.clone(),
);
@@ -41,8 +41,6 @@ pub struct SoapySdrRig {
wfm_deemphasis_us: u32,
/// Whether WFM stereo decode is enabled.
wfm_stereo: bool,
/// Whether multiband WFM stereo denoising is enabled.
wfm_denoise: bool,
}
impl SoapySdrRig {
@@ -120,7 +118,6 @@ impl SoapySdrRig {
frame_duration_ms,
wfm_deemphasis_us,
true, // wfm_stereo: enabled by default
true, // wfm_denoise: enabled by default
channels,
);
@@ -189,7 +186,6 @@ impl SoapySdrRig {
retune_cmd,
wfm_deemphasis_us,
wfm_stereo: true,
wfm_denoise: true,
})
}
@@ -481,19 +477,6 @@ impl RigCat for SoapySdrRig {
})
}
fn set_wfm_denoise<'a>(
&'a mut self,
enabled: bool,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
Box::pin(async move {
self.wfm_denoise = enabled;
if let Some(dsp_arc) = self.pipeline.channel_dsps.get(self.primary_channel_idx) {
dsp_arc.lock().unwrap().set_wfm_denoise(enabled);
}
Ok(())
})
}
fn filter_state(&self) -> Option<RigFilterState> {
let wfm_stereo_detected = self
.pipeline
@@ -507,7 +490,6 @@ impl RigCat for SoapySdrRig {
cw_center_hz: 700,
wfm_deemphasis_us: self.wfm_deemphasis_us,
wfm_stereo: self.wfm_stereo,
wfm_denoise: self.wfm_denoise,
wfm_stereo_detected,
})
}