[fix](trx-backend-soapysdr,trx-frontend-http): reset stereo state and soften wfm top end
Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -964,11 +964,19 @@ function resetRdsDisplay() {
|
|||||||
updateRdsPsOverlay(null);
|
updateRdsPsOverlay(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetWfmStereoIndicator() {
|
||||||
|
if (!wfmStFlagEl) return;
|
||||||
|
wfmStFlagEl.textContent = "MO";
|
||||||
|
wfmStFlagEl.classList.remove("wfm-st-flag-stereo");
|
||||||
|
wfmStFlagEl.classList.add("wfm-st-flag-mono");
|
||||||
|
}
|
||||||
|
|
||||||
function applyLocalTunedFrequency(hz, forceDisplay = false) {
|
function applyLocalTunedFrequency(hz, forceDisplay = false) {
|
||||||
if (!Number.isFinite(hz)) return;
|
if (!Number.isFinite(hz)) return;
|
||||||
const freqChanged = lastFreqHz !== hz;
|
const freqChanged = lastFreqHz !== hz;
|
||||||
if (freqChanged) {
|
if (freqChanged) {
|
||||||
resetRdsDisplay();
|
resetRdsDisplay();
|
||||||
|
resetWfmStereoIndicator();
|
||||||
}
|
}
|
||||||
lastFreqHz = hz;
|
lastFreqHz = hz;
|
||||||
updateDocumentTitle(lastSpectrumData?.rds ?? null);
|
updateDocumentTitle(lastSpectrumData?.rds ?? null);
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ const BW4_Q1: f32 = 0.5412;
|
|||||||
const BW4_Q2: f32 = 1.3066;
|
const BW4_Q2: f32 = 1.3066;
|
||||||
/// Q for the 19 kHz pilot notch (~3.8 kHz 3 dB bandwidth).
|
/// Q for the 19 kHz pilot notch (~3.8 kHz 3 dB bandwidth).
|
||||||
const PILOT_NOTCH_Q: f32 = 5.0;
|
const PILOT_NOTCH_Q: f32 = 5.0;
|
||||||
/// Tighter post-matrix stereo notch for suppressing residual pilot leakage.
|
|
||||||
const STEREO_PILOT_NOTCH_Q: f32 = 12.0;
|
|
||||||
/// Narrow 19 kHz band-pass used to derive zero-crossings for switching stereo demod.
|
/// Narrow 19 kHz band-pass used to derive zero-crossings for switching stereo demod.
|
||||||
const PILOT_BPF_Q: f32 = 20.0;
|
const PILOT_BPF_Q: f32 = 20.0;
|
||||||
/// Fixed phase trim on the recovered L-R channel to compensate pilot-path delay.
|
/// Fixed phase trim on the recovered L-R channel to compensate pilot-path delay.
|
||||||
@@ -362,9 +360,6 @@ pub struct WfmStereoDecoder {
|
|||||||
dc_m: DcBlocker,
|
dc_m: DcBlocker,
|
||||||
dc_l: DcBlocker,
|
dc_l: DcBlocker,
|
||||||
dc_r: DcBlocker,
|
dc_r: DcBlocker,
|
||||||
/// Post-matrix pilot notches for stereo outputs.
|
|
||||||
pilot_notch_l: BiquadNotch,
|
|
||||||
pilot_notch_r: BiquadNotch,
|
|
||||||
deemph_m: Deemphasis,
|
deemph_m: Deemphasis,
|
||||||
deemph_l: Deemphasis,
|
deemph_l: Deemphasis,
|
||||||
deemph_r: Deemphasis,
|
deemph_r: Deemphasis,
|
||||||
@@ -431,8 +426,6 @@ impl WfmStereoDecoder {
|
|||||||
dc_m: DcBlocker::new(0.9999),
|
dc_m: DcBlocker::new(0.9999),
|
||||||
dc_l: DcBlocker::new(0.9999),
|
dc_l: DcBlocker::new(0.9999),
|
||||||
dc_r: DcBlocker::new(0.9999),
|
dc_r: DcBlocker::new(0.9999),
|
||||||
pilot_notch_l: BiquadNotch::new(audio_rate.max(1) as f32, PILOT_HZ, STEREO_PILOT_NOTCH_Q),
|
|
||||||
pilot_notch_r: BiquadNotch::new(audio_rate.max(1) as f32, PILOT_HZ, STEREO_PILOT_NOTCH_Q),
|
|
||||||
deemph_m: Deemphasis::new(audio_rate.max(1) as f32, deemphasis_us),
|
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_l: Deemphasis::new(audio_rate.max(1) as f32, deemphasis_us),
|
||||||
deemph_r: Deemphasis::new(audio_rate.max(1) as f32, deemphasis_us),
|
deemph_r: Deemphasis::new(audio_rate.max(1) as f32, deemphasis_us),
|
||||||
@@ -553,11 +546,11 @@ impl WfmStereoDecoder {
|
|||||||
let right_corr = (sum_i - diff) * 0.5;
|
let right_corr = (sum_i - diff) * 0.5;
|
||||||
let left = self
|
let left = self
|
||||||
.dc_l
|
.dc_l
|
||||||
.process(self.pilot_notch_l.process(self.deemph_l.process(left_corr)))
|
.process(self.deemph_l.process(left_corr))
|
||||||
.clamp(-1.0, 1.0);
|
.clamp(-1.0, 1.0);
|
||||||
let right = self
|
let right = self
|
||||||
.dc_r
|
.dc_r
|
||||||
.process(self.pilot_notch_r.process(self.deemph_r.process(right_corr)))
|
.process(self.deemph_r.process(right_corr))
|
||||||
.clamp(-1.0, 1.0);
|
.clamp(-1.0, 1.0);
|
||||||
output.push(left);
|
output.push(left);
|
||||||
output.push(right);
|
output.push(right);
|
||||||
@@ -591,6 +584,11 @@ impl WfmStereoDecoder {
|
|||||||
self.rds_decoder.reset();
|
self.rds_decoder.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset_stereo_detect(&mut self) {
|
||||||
|
self.stereo_detect_level = 0.0;
|
||||||
|
self.stereo_detected = false;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stereo_detected(&self) -> bool {
|
pub fn stereo_detected(&self) -> bool {
|
||||||
self.stereo_detected
|
self.stereo_detected
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -568,6 +568,13 @@ impl ChannelDsp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset_wfm_state(&mut self) {
|
||||||
|
if let Some(decoder) = &mut self.wfm_decoder {
|
||||||
|
decoder.reset_rds();
|
||||||
|
decoder.reset_stereo_detect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Process a block of raw IQ samples through the full DSP chain.
|
/// Process a block of raw IQ samples through the full DSP chain.
|
||||||
///
|
///
|
||||||
/// 1. **Batch mixer**: compute the full LO signal for the block at once,
|
/// 1. **Batch mixer**: compute the full LO signal for the block at once,
|
||||||
@@ -990,7 +997,7 @@ mod tests {
|
|||||||
fn channel_dsp_processes_silence() {
|
fn channel_dsp_processes_silence() {
|
||||||
let (pcm_tx, _pcm_rx) = broadcast::channel::<Vec<f32>>(8);
|
let (pcm_tx, _pcm_rx) = broadcast::channel::<Vec<f32>>(8);
|
||||||
let mut dsp =
|
let mut dsp =
|
||||||
ChannelDsp::new(0.0, &RigMode::USB, 48_000, 8_000, 1, 20, 3000, 75, true, true, 31, pcm_tx);
|
ChannelDsp::new(0.0, &RigMode::USB, 48_000, 8_000, 1, 20, 3000, 75, true, 31, pcm_tx);
|
||||||
let block = vec![Complex::new(0.0_f32, 0.0_f32); 4096];
|
let block = vec![Complex::new(0.0_f32, 0.0_f32); 4096];
|
||||||
dsp.process_block(&block);
|
dsp.process_block(&block);
|
||||||
}
|
}
|
||||||
@@ -999,7 +1006,7 @@ mod tests {
|
|||||||
fn channel_dsp_set_mode() {
|
fn channel_dsp_set_mode() {
|
||||||
let (pcm_tx, _) = broadcast::channel::<Vec<f32>>(8);
|
let (pcm_tx, _) = broadcast::channel::<Vec<f32>>(8);
|
||||||
let mut dsp =
|
let mut dsp =
|
||||||
ChannelDsp::new(0.0, &RigMode::USB, 48_000, 8_000, 1, 20, 3000, 75, true, true, 31, pcm_tx);
|
ChannelDsp::new(0.0, &RigMode::USB, 48_000, 8_000, 1, 20, 3000, 75, true, 31, pcm_tx);
|
||||||
assert_eq!(dsp.demodulator, Demodulator::Usb);
|
assert_eq!(dsp.demodulator, Demodulator::Usb);
|
||||||
dsp.set_mode(&RigMode::FM);
|
dsp.set_mode(&RigMode::FM);
|
||||||
assert_eq!(dsp.demodulator, Demodulator::Fm);
|
assert_eq!(dsp.demodulator, Demodulator::Fm);
|
||||||
@@ -1015,7 +1022,6 @@ mod tests {
|
|||||||
20,
|
20,
|
||||||
75,
|
75,
|
||||||
true,
|
true,
|
||||||
true,
|
|
||||||
&[(200_000.0, RigMode::USB, 3000, 64)],
|
&[(200_000.0, RigMode::USB, 3000, 64)],
|
||||||
);
|
);
|
||||||
assert_eq!(pipeline.pcm_senders.len(), 1);
|
assert_eq!(pipeline.pcm_senders.len(), 1);
|
||||||
@@ -1024,7 +1030,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pipeline_empty_channels() {
|
fn pipeline_empty_channels() {
|
||||||
let pipeline = SdrPipeline::start(Box::new(MockIqSource), 1_920_000, 48_000, 1, 20, 75, true, true, &[]);
|
let pipeline = SdrPipeline::start(Box::new(MockIqSource), 1_920_000, 48_000, 1, 20, 75, true, &[]);
|
||||||
assert_eq!(pipeline.pcm_senders.len(), 0);
|
assert_eq!(pipeline.pcm_senders.len(), 0);
|
||||||
assert_eq!(pipeline.channel_dsps.len(), 0);
|
assert_eq!(pipeline.channel_dsps.len(), 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ impl RigCat for SoapySdrRig {
|
|||||||
let mut dsp = dsp_arc.lock().unwrap();
|
let mut dsp = dsp_arc.lock().unwrap();
|
||||||
dsp.set_channel_if_hz(channel_if_hz);
|
dsp.set_channel_if_hz(channel_if_hz);
|
||||||
if freq_changed {
|
if freq_changed {
|
||||||
dsp.reset_rds();
|
dsp.reset_wfm_state();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user