[fix](trx-backend-soapysdr): fix AM demodulation quality
Three bugs made the AM path sound wrong: 1. AGC attack too fast (5 ms). The slowest audio a broadcast AM station can transmit is ~50 Hz (20 ms period). A 5 ms attack lets the AGC track individual audio cycles, which causes severe pumping and amplitude distortion. Change to 500 ms attack / 5 s release so the AGC only responds to slow carrier-amplitude fading, not the audio modulation itself. 2. Bandwidth too narrow. The IQ filter cutoff is audio_bandwidth_hz / 2, so the previous 6 000 Hz setting gave only 3 kHz audio bandwidth. AM broadcast sidebands extend to ±4.5–5 kHz; raise the default to 12 000 (cutoff 6 kHz) to cover the full audio band. 3. DC blocker rate inconsistent. For AM the demodulated magnitude is always ≥ 0 and the DC component equals the carrier amplitude; only true DC needs removing. Unify all non-WFM modes to r = 0.9999 (corner ≈ 0.76 Hz @ 48 kHz), which strips carrier DC without touching any audible bass content. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -255,28 +255,30 @@ impl BlockFirFilter {
|
||||
|
||||
/// Build the AGC for a given mode.
|
||||
///
|
||||
/// CW uses fast attack/release to follow individual dots and dashes.
|
||||
/// AM uses a moderate release so the AGC tracks fading carriers.
|
||||
/// All other modes use a longer release suitable for voice and data signals.
|
||||
/// AM AGC must be far slower than audio modulation. With a 50 Hz bass
|
||||
/// component the modulation period is 20 ms; an attack faster than that
|
||||
/// causes the AGC to follow the audio envelope and distort (pumping).
|
||||
/// 500 ms / 5 s only reacts to slow carrier-amplitude fading, not audio.
|
||||
///
|
||||
/// CW uses a fast attack/release to follow individual dots and dashes.
|
||||
/// All other modes use 5 ms / 500 ms, suitable for SSB voice and FM.
|
||||
fn agc_for_mode(mode: &RigMode, audio_sample_rate: u32) -> SoftAgc {
|
||||
let sr = audio_sample_rate.max(1) as f32;
|
||||
match mode {
|
||||
RigMode::CW | RigMode::CWR => SoftAgc::new(sr, 1.0, 50.0, 0.5, 30.0),
|
||||
RigMode::AM => SoftAgc::new(sr, 5.0, 200.0, 0.5, 30.0),
|
||||
RigMode::AM => SoftAgc::new(sr, 500.0, 5_000.0, 0.5, 30.0),
|
||||
_ => SoftAgc::new(sr, 5.0, 500.0, 0.5, 30.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the DC blocker for a given mode, or `None` if not applicable.
|
||||
///
|
||||
/// WFM is excluded because it has its own internal DC blockers on each output
|
||||
/// channel. AM uses a slightly faster blocker (r = 0.999, corner ≈ 7.6 Hz
|
||||
/// @ 48 kHz) so it can track slow carrier-amplitude fading. All other modes
|
||||
/// (including CW, which now demodulates as USB) use r = 0.9999 (≈ 0.76 Hz).
|
||||
/// WFM is excluded because it has its own internal DC blockers per channel.
|
||||
/// All other modes use r = 0.9999 (corner ≈ 0.76 Hz @ 48 kHz), which strips
|
||||
/// only true carrier DC without affecting any audible bass content.
|
||||
fn dc_for_mode(mode: &RigMode) -> Option<DcBlocker> {
|
||||
match mode {
|
||||
RigMode::WFM => None,
|
||||
RigMode::AM => Some(DcBlocker::new(0.999)),
|
||||
_ => Some(DcBlocker::new(0.9999)),
|
||||
}
|
||||
}
|
||||
@@ -513,7 +515,7 @@ fn default_bandwidth_for_mode(mode: &RigMode) -> u32 {
|
||||
match mode {
|
||||
RigMode::LSB | RigMode::USB | RigMode::PKT | RigMode::DIG => 3_000,
|
||||
RigMode::CW | RigMode::CWR => 500,
|
||||
RigMode::AM => 6_000,
|
||||
RigMode::AM => 12_000,
|
||||
RigMode::FM => 12_500,
|
||||
RigMode::WFM => 180_000,
|
||||
RigMode::Other(_) => 3_000,
|
||||
|
||||
Reference in New Issue
Block a user