use crate::sync::{ft8_sync, ft4_sync};
use crate::codec::ft8::Ft8Codec;
use crate::codec::ft4::Ft4Codec;
use crate::codec::ft8::Ft8Bits;
use crate::codec::ft4::Ft4Bits;
use crate::modulate::{Ft8Mod, Ft4Mod};
use crate::modulate::ft8::{FT8_FRAME_LEN};
use crate::modulate::ft4::{FT4_FRAME_LEN};
use super::super::super::roundtrip::add_awgn;
const FS: f32 = 12_000.0;
const REF_BW_HZ: f32 = 2_500.0;
const SIG_POWER: f32 = 0.5;
const TRIALS: usize = 50;
fn snr_to_noise_power(snr_db: f32) -> f32 {
SIG_POWER * FS / (REF_BW_HZ * 10.0_f32.powf(snr_db / 10.0))
}
const SNR_LEVELS: &[f32] = &[
-26.0, -25.0, -24.0, -23.0, -22.0, -21.0, -20.0, -19.0, -18.0,
-17.0, -16.0, -15.0, -14.0, -13.0, -12.0, -11.0, -10.0,
];
fn make_ft8_frame_iq(base_hz: f32) -> (Vec<num_complex::Complex32>, Ft8Bits) {
let payload: Ft8Bits = [0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0x01, 0x23, 0x45, 0x60];
let frame = Ft8Codec::encode(&payload);
let iq = Ft8Mod::new(FS, base_hz, 0.0, 1.0).modulate(&frame);
(iq, payload)
}
fn make_ft4_frame_iq(base_hz: f32) -> (Vec<num_complex::Complex32>, Ft4Bits) {
let payload: Ft4Bits = [0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0x01, 0x23, 0x45, 0x60];
let frame = Ft4Codec::encode(&payload);
let iq = Ft4Mod::new(FS, base_hz, 0.0, 1.0).modulate(&frame);
(iq, payload)
}
fn try_ft8(
clean_iq: &[num_complex::Complex32],
payload: &Ft8Bits,
base_hz: f32,
noise_power: f32,
seed: u64,
) -> bool {
let total = FT8_FRAME_LEN + FT8_FRAME_LEN / 4;
let mut buf = vec![num_complex::Complex32::new(0.0, 0.0); total];
buf[..FT8_FRAME_LEN].copy_from_slice(clean_iq);
add_awgn(&mut buf, noise_power, seed);
let results = ft8_sync(
&buf, FS,
base_hz - 6.25, base_hz + 50.0 + 6.25,
0, 0, 5,
);
if results.is_empty() { return false; }
Ft8Codec::decode_soft(&results[0].llr).as_ref() == Some(payload)
}
fn try_ft4(
clean_iq: &[num_complex::Complex32],
payload: &Ft4Bits,
base_hz: f32,
noise_power: f32,
seed: u64,
) -> bool {
let total = FT4_FRAME_LEN + FT4_FRAME_LEN / 4;
let mut buf = vec![num_complex::Complex32::new(0.0, 0.0); total];
buf[..FT4_FRAME_LEN].copy_from_slice(clean_iq);
add_awgn(&mut buf, noise_power, seed);
let results = ft4_sync(
&buf, FS,
base_hz - 20.833, base_hz + 100.0 + 20.833,
0, 0, 5,
);
if results.is_empty() { return false; }
Ft4Codec::decode_soft(&results[0].llr).as_ref() == Some(payload)
}
#[test]
fn snr_sweep_ft8() {
let base_hz = 1_000.0_f32;
let (clean_iq, payload) = make_ft8_frame_iq(base_hz);
println!("\n[FT8 SNR sweep, ref BW = {:.0} Hz, {} trials/point]",
REF_BW_HZ, TRIALS);
println!("{:>10} {:>8} {:>8} {:>9}", "SNR(dB)", "trials", "decoded", "success%");
println!("{}", "-".repeat(40));
for &snr_db in SNR_LEVELS {
let noise_power = snr_to_noise_power(snr_db);
let decoded: usize = (0..TRIALS)
.filter(|&i| {
let seed = 0x1234_0000_0000_0000_u64
.wrapping_add(i as u64)
.wrapping_add((snr_db * 100.0) as u64);
try_ft8(&clean_iq, &payload, base_hz, noise_power, seed)
})
.count();
let pct = 100.0 * decoded as f32 / TRIALS as f32;
println!("{:>10.1} {:>8} {:>8} {:>8.1}%", snr_db, TRIALS, decoded, pct);
}
println!();
}
#[test]
fn snr_sweep_ft4() {
let base_hz = 1_000.0_f32;
let (clean_iq, payload) = make_ft4_frame_iq(base_hz);
println!("\n[FT4 SNR sweep, ref BW = {:.0} Hz, {} trials/point]",
REF_BW_HZ, TRIALS);
println!("{:>10} {:>8} {:>8} {:>9}", "SNR(dB)", "trials", "decoded", "success%");
println!("{}", "-".repeat(40));
for &snr_db in SNR_LEVELS {
let noise_power = snr_to_noise_power(snr_db);
let decoded: usize = (0..TRIALS)
.filter(|&i| {
let seed = 0xABCD_0000_0000_0000_u64
.wrapping_add(i as u64)
.wrapping_add((snr_db * 100.0) as u64);
try_ft4(&clean_iq, &payload, base_hz, noise_power, seed)
})
.count();
let pct = 100.0 * decoded as f32 / TRIALS as f32;
println!("{:>10.1} {:>8} {:>8} {:>8.1}%", snr_db, TRIALS, decoded, pct);
}
println!();
}