[fix](trx-rds): apply RRC pulse shaping in test signal generator
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 <noreply@anthropic.com>
This commit is contained in:
@@ -1155,19 +1155,46 @@ mod tests {
|
|||||||
|
|
||||||
/// Modulate chip stream as BPSK on the 57 kHz RDS subcarrier.
|
/// Modulate chip stream as BPSK on the 57 kHz RDS subcarrier.
|
||||||
/// Returns a composite FM-baseband signal for `RdsDecoder::process_sample`.
|
/// 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<f32> {
|
fn chips_to_rds_signal(chips: &[i8], sample_rate: f32) -> Vec<f32> {
|
||||||
let samples_per_chip = sample_rate / RDS_CHIP_RATE;
|
let spc = sample_rate / RDS_CHIP_RATE;
|
||||||
let n = (chips.len() as f32 * samples_per_chip).ceil() as usize;
|
let n = (chips.len() as f32 * spc).ceil() as usize;
|
||||||
let mut sig = vec![0.0f32; n];
|
|
||||||
|
// 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() {
|
for (ci, &chip) in chips.iter().enumerate() {
|
||||||
let t0 = (ci as f32 * samples_per_chip) as usize;
|
let center = ((ci as f32 + 0.5) * spc).round() as usize;
|
||||||
let t1 = ((ci + 1) as f32 * samples_per_chip) as usize;
|
if center < n {
|
||||||
for t in t0..t1.min(n) {
|
baseband[center] = chip as f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
let phase = TAU * RDS_SUBCARRIER_HZ * t as f32 / sample_rate;
|
||||||
sig[t] = chip as f32 * phase.cos();
|
shaped[t] *= phase.cos();
|
||||||
}
|
}
|
||||||
}
|
shaped
|
||||||
sig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add AWGN at the given SNR (dB) relative to the signal's actual power.
|
/// Add AWGN at the given SNR (dB) relative to the signal's actual power.
|
||||||
|
|||||||
Reference in New Issue
Block a user