From 06796342e7ffdcf9c63b17a0940ce06ac187d9a2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 27 Mar 2026 00:51:51 +0000 Subject: [PATCH] [fix](trx-rds): apply RRC pulse shaping in test signal generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `chips_to_rds_signal` test helper was generating rectangular chip pulses, but the receiver expects RRC-shaped transmit pulses so that RRC(tx) × RRC(rx) = raised cosine with zero ISI. The rectangular pulses caused ISI that drifted the symbol clock sampling point, consistently skipping PS segment 2 in the end-to-end test. Replace rectangular pulses with an impulse train convolved with the same RRC taps used by the receiver. All 15 tests now pass including `end_to_end_clean_signal_decodes_ps`. https://claude.ai/code/session_01N2UcGaLDzYiM3gNrZ6kFBj Signed-off-by: Claude --- src/decoders/trx-rds/src/lib.rs | 45 ++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/decoders/trx-rds/src/lib.rs b/src/decoders/trx-rds/src/lib.rs index 8164fc3..f8e06a1 100644 --- a/src/decoders/trx-rds/src/lib.rs +++ b/src/decoders/trx-rds/src/lib.rs @@ -1155,19 +1155,46 @@ mod tests { /// Modulate chip stream as BPSK on the 57 kHz RDS subcarrier. /// Returns a composite FM-baseband signal for `RdsDecoder::process_sample`. + /// + /// Each chip is RRC pulse-shaped so that RRC(tx) × RRC(rx) = raised cosine, + /// giving zero ISI at the receiver's optimal sampling instants. fn chips_to_rds_signal(chips: &[i8], sample_rate: f32) -> Vec { - let samples_per_chip = sample_rate / RDS_CHIP_RATE; - let n = (chips.len() as f32 * samples_per_chip).ceil() as usize; - let mut sig = vec![0.0f32; n]; + let spc = sample_rate / RDS_CHIP_RATE; + let n = (chips.len() as f32 * spc).ceil() as usize; + + // Build the transmit RRC pulse shape (same taps as the receiver). + let taps = build_rrc_taps(sample_rate, RDS_CHIP_RATE); + + // Create baseband impulse train and convolve with RRC taps. + let mut baseband = vec![0.0f32; n]; for (ci, &chip) in chips.iter().enumerate() { - let t0 = (ci as f32 * samples_per_chip) as usize; - let t1 = ((ci + 1) as f32 * samples_per_chip) as usize; - for t in t0..t1.min(n) { - let phase = TAU * RDS_SUBCARRIER_HZ * t as f32 / sample_rate; - sig[t] = chip as f32 * phase.cos(); + let center = ((ci as f32 + 0.5) * spc).round() as usize; + if center < n { + baseband[center] = chip as f32; } } - sig + + // Convolve baseband impulse train with RRC taps (direct FIR). + let half = taps.len() / 2; + let mut shaped = vec![0.0f32; n]; + for (i, &impulse) in baseband.iter().enumerate() { + if impulse == 0.0 { + continue; + } + for (j, &tap) in taps.iter().enumerate() { + let idx = i + j; + if idx >= half && idx - half < n { + shaped[idx - half] += impulse * tap; + } + } + } + + // BPSK modulate onto the 57 kHz subcarrier. + for t in 0..n { + let phase = TAU * RDS_SUBCARRIER_HZ * t as f32 / sample_rate; + shaped[t] *= phase.cos(); + } + shaped } /// Add AWGN at the given SNR (dB) relative to the signal's actual power.