[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:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user