[fix](trx-backend-soapysdr): squelch against the meter, not the post-AGC level
The threshold was compared against the block level measured after the IQ AGC. Holding that level at a setpoint is the AGC's entire purpose, so for every mode that has one — FM, PKT, AIS, AM, SAM, which is to say the modes anyone squelches — the comparison was against a near-constant. With FM's 12 dB of gain a weak signal reads some 12 dB hotter than it is, and the value never had the decimation correction the meter applies on top of that: around 20 dB adrift at 48k/8k, more as decimation grows. The threshold arrives in the other scale entirely. The slider maps its percentage onto -120..-30 dB and Auto takes the spectrum noise floor plus 6, both of which are what the meter and the spectrum display show. So a gate set just above the noise sat open on it. It now reads last_signal_db, which is already computed each block before the AGC and corrected for decimation — the same number the meter shows. The post-AGC measurement had no other consumer. The test feeds one signal twice and takes the threshold from the channel's own meter: 6 dB above must gate it, 6 dB below must pass it. Nothing there depends on the absolute scale, only on the two agreeing. Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -837,12 +837,6 @@ impl ChannelDsp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let signal_power = decimated
|
|
||||||
.iter()
|
|
||||||
.map(|s| s.re * s.re + s.im * s.im)
|
|
||||||
.sum::<f32>()
|
|
||||||
/ decimated.len() as f32;
|
|
||||||
let signal_db = 10.0 * signal_power.max(1e-12).log10();
|
|
||||||
const WFM_OUTPUT_GAIN: f32 = 0.50;
|
const WFM_OUTPUT_GAIN: f32 = 0.50;
|
||||||
let mut audio = if let Some(decoder) = self.wfm_decoder.as_mut() {
|
let mut audio = if let Some(decoder) = self.wfm_decoder.as_mut() {
|
||||||
let mut out = decoder.process_iq(decimated);
|
let mut out = decoder.process_iq(decimated);
|
||||||
@@ -884,7 +878,14 @@ impl ChannelDsp {
|
|||||||
raw
|
raw
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if !self.squelch.update(&self.mode, signal_db) {
|
// Against the meter reading, not the block level after the IQ AGC.
|
||||||
|
// The threshold arrives in the scale the operator sets it from — the
|
||||||
|
// S-meter and the spectrum — while the level measured here had been
|
||||||
|
// through the AGC, whose whole job is to hold it at a setpoint. For
|
||||||
|
// every mode that has one (FM, PKT, AIS, AM, SAM) the squelch was
|
||||||
|
// therefore comparing against a near-constant, and for the rest it was
|
||||||
|
// still off by the decimation correction the meter applies.
|
||||||
|
if !self.squelch.update(&self.mode, self.last_signal_db) {
|
||||||
audio.fill(0.0);
|
audio.fill(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -933,6 +934,93 @@ mod tests {
|
|||||||
dsp.process_block(&block);
|
dsp.process_block(&block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Feeds one signal twice, with the squelch threshold set from the channel's
|
||||||
|
/// own meter reading: 6 dB above it must gate the audio, 6 dB below it must
|
||||||
|
/// pass. FM runs an IQ AGC, so a squelch measured after that stage compares
|
||||||
|
/// against a level pinned near the AGC setpoint — some 20 dB adrift of the
|
||||||
|
/// scale the operator reads the threshold off, and open on plain noise.
|
||||||
|
#[test]
|
||||||
|
fn squelch_follows_the_meter_the_threshold_is_set_from() {
|
||||||
|
const AMPLITUDE: f32 = 0.0025;
|
||||||
|
|
||||||
|
let (pcm_tx, mut pcm_rx) = broadcast::channel::<Vec<f32>>(4096);
|
||||||
|
let (iq_tx, _iq_rx) = broadcast::channel::<Vec<Complex<f32>>>(8);
|
||||||
|
let mut dsp = ChannelDsp::new(
|
||||||
|
0.0,
|
||||||
|
&RigMode::FM,
|
||||||
|
48_000,
|
||||||
|
8_000,
|
||||||
|
1,
|
||||||
|
20,
|
||||||
|
12_000,
|
||||||
|
75,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
VirtualSquelchConfig::default(),
|
||||||
|
NoiseBlankerConfig::default(),
|
||||||
|
pcm_tx,
|
||||||
|
iq_tx,
|
||||||
|
);
|
||||||
|
|
||||||
|
// A 1 kHz tone on the carrier, so an open gate is audibly non-zero and
|
||||||
|
// a closed one is unambiguously silent.
|
||||||
|
let mut phase = 0.0_f32;
|
||||||
|
let mut mod_phase = 0.0_f32;
|
||||||
|
let mut feed = |dsp: &mut ChannelDsp, blocks: usize| {
|
||||||
|
for _ in 0..blocks {
|
||||||
|
let mut block = Vec::with_capacity(4096);
|
||||||
|
for _ in 0..4096 {
|
||||||
|
mod_phase += std::f32::consts::TAU * 1_000.0 / 48_000.0;
|
||||||
|
phase += std::f32::consts::TAU * (3_000.0 * mod_phase.sin()) / 48_000.0;
|
||||||
|
block.push(Complex::new(
|
||||||
|
AMPLITUDE * phase.cos(),
|
||||||
|
AMPLITUDE * phase.sin(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
dsp.process_block(&block);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let drain = |rx: &mut broadcast::Receiver<Vec<f32>>| {
|
||||||
|
let mut audio = Vec::new();
|
||||||
|
while let Ok(frame) = rx.try_recv() {
|
||||||
|
audio.extend_from_slice(&frame);
|
||||||
|
}
|
||||||
|
audio
|
||||||
|
};
|
||||||
|
let peak = |audio: &[f32]| audio.iter().fold(0.0_f32, |acc, s| acc.max(s.abs()));
|
||||||
|
|
||||||
|
// Settle the meter on this signal, then read what the operator would.
|
||||||
|
feed(&mut dsp, 24);
|
||||||
|
let meter_db = dsp.signal_db();
|
||||||
|
assert!(
|
||||||
|
meter_db > -120.0,
|
||||||
|
"the meter never moved off its floor ({meter_db} dB)"
|
||||||
|
);
|
||||||
|
|
||||||
|
dsp.set_squelch(true, meter_db + 6.0);
|
||||||
|
let _ = drain(&mut pcm_rx);
|
||||||
|
feed(&mut dsp, 24);
|
||||||
|
let gated = drain(&mut pcm_rx);
|
||||||
|
assert!(!gated.is_empty(), "no audio frames were produced at all");
|
||||||
|
// From the second half on: the first frame out still carries the audio
|
||||||
|
// that was already buffered when the threshold changed.
|
||||||
|
assert_eq!(
|
||||||
|
peak(&gated[gated.len() / 2..]),
|
||||||
|
0.0,
|
||||||
|
"squelch set 6 dB above the meter ({meter_db} dB) still passed audio"
|
||||||
|
);
|
||||||
|
|
||||||
|
dsp.set_squelch(true, meter_db - 6.0);
|
||||||
|
let _ = drain(&mut pcm_rx);
|
||||||
|
feed(&mut dsp, 24);
|
||||||
|
let passed = drain(&mut pcm_rx);
|
||||||
|
assert!(!passed.is_empty(), "no audio frames were produced at all");
|
||||||
|
assert!(
|
||||||
|
peak(&passed[passed.len() / 2..]) > 0.0,
|
||||||
|
"squelch set 6 dB below the meter ({meter_db} dB) gated the audio"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user