use crate::codec::varicode::{varicode_encode, VaricodeEncoder, VaricodeDecoder};
use crate::codec::psk31_conv::{conv_encode, viterbi_decode_hard, StreamingViterbi};
use crate::modulate::psk31::{psk31_sps, PSK31_BAUD, PSK31_SPS_8000, PSK31_SPS_12000};
#[test]
fn varicode_encode_space() {
let (cw, len) = varicode_encode(b' ');
assert_eq!(len, 1);
assert_eq!(cw, 1);
}
#[test]
fn varicode_decode_roundtrip() {
use crate::codec::varicode::varicode_encode as enc;
use crate::codec::varicode::varicode_decode as dec;
for b in 0u8..128u8 {
let (cw, len) = enc(b);
let decoded = dec(cw, len);
assert!(decoded.is_some(), "no decode for byte {} (cw=0b{:b}, len={})", b, cw, len);
assert_eq!(decoded.unwrap(), b,
"roundtrip mismatch: byte {} encoded to (0b{:b},{}) but decoded as {}",
b, cw, len, decoded.unwrap());
}
}
#[test]
fn varicode_table_no_collisions() {
use crate::codec::varicode::varicode_encode as enc;
use std::collections::HashMap;
let mut seen: HashMap<(u16, u8), u8> = HashMap::new();
for b in 0u8..128u8 {
let (cw, len) = enc(b);
if let Some(&prev) = seen.get(&(cw, len)) {
panic!("collision: byte {} and byte {} both encode to (0b{:b}, {})",
prev, b, cw, len);
}
seen.insert((cw, len), b);
}
}
#[test]
fn varicode_no_internal_zero_pairs() {
use crate::codec::varicode::varicode_encode as enc;
for b in 0u8..128u8 {
let (cw, len) = enc(b);
for i in 1..len {
let b0 = (cw >> (i - 1)) & 1;
let b1 = (cw >> i) & 1;
if b0 == 0 && b1 == 0 {
panic!("byte {} (0b{:b}, len={}) has internal 00 at bit positions {}-{}",
b, cw, len, i - 1, i);
}
}
}
}
#[test]
fn varicode_stream_roundtrip_all_printable() {
let mut enc = VaricodeEncoder::new();
for b in 32u8..127u8 {
enc.push_byte(b);
}
let bits = enc.drain_bits();
let mut dec = VaricodeDecoder::new();
for &bit in &bits {
dec.push_bit(bit);
}
dec.push_bit(0);
dec.push_bit(0);
let mut decoded = Vec::new();
while let Some(ch) = dec.pop_char() {
decoded.push(ch);
}
let expected: Vec<u8> = (32u8..127u8).collect();
assert_eq!(decoded, expected,
"stream roundtrip failed: expected {} chars, got {}.\n\
First mismatch at index {}",
expected.len(), decoded.len(),
decoded.iter().zip(expected.iter())
.position(|(a, b)| a != b)
.unwrap_or(decoded.len().min(expected.len())));
}
#[test]
fn varicode_encoder_stream_cq() {
let mut enc = VaricodeEncoder::new();
enc.push_preamble(4);
enc.push_byte(b'C');
enc.push_byte(b'Q');
let bits = enc.drain_bits();
assert_eq!(&bits[..4], &[0, 0, 0, 0]);
let (c_cw, c_len) = varicode_encode(b'C');
let c_bits: Vec<u8> = (0..c_len).rev().map(|i| ((c_cw >> i) & 1) as u8).collect();
assert_eq!(&bits[4..4 + c_len as usize], c_bits.as_slice());
let gap_start = 4 + c_len as usize;
assert_eq!(bits[gap_start], 0);
assert_eq!(bits[gap_start + 1], 0);
let (q_cw, q_len) = varicode_encode(b'Q');
let q_bits: Vec<u8> = (0..q_len).rev().map(|i| ((q_cw >> i) & 1) as u8).collect();
assert_eq!(&bits[gap_start + 2..gap_start + 2 + q_len as usize], q_bits.as_slice());
}
#[test]
fn varicode_decoder_boundary() {
let (cw, len) = varicode_encode(b'C');
let mut dec = VaricodeDecoder::new();
for i in (0..len).rev() {
dec.push_bit(((cw >> i) & 1) as u8);
}
assert_eq!(dec.pop_char(), None); dec.push_bit(0);
dec.push_bit(0); assert_eq!(dec.pop_char(), Some(b'C'));
}
#[test]
fn psk31_sps_8000() {
assert_eq!(psk31_sps(8000.0), PSK31_SPS_8000);
}
#[test]
fn psk31_sps_12000() {
assert_eq!(psk31_sps(12000.0), PSK31_SPS_12000);
}
#[test]
fn psk31_hann_endpoints() {
let sps = psk31_sps(8000.0);
assert_eq!(sps, 256);
let denom = (sps - 1) as f32;
let h0 = 0.5 - 0.5 * (std::f32::consts::PI * 0.0 / denom).cos();
let hn = 0.5 - 0.5 * (std::f32::consts::PI * denom / denom).cos();
assert!(h0.abs() < 1e-4, "hann[0] = {}", h0);
assert!((hn - 1.0).abs() < 1e-4, "hann[sps-1] = {}", hn);
}
#[test]
fn bpsk31_decider_sign() {
use crate::demodulate::psk31::Bpsk31Decider;
use crate::core::Block;
let soft = [1.0f32, -1.0, 2.5, -0.5];
let mut out = [0u8; 4];
Bpsk31Decider::new().process(&soft, &mut out);
assert_eq!(out, [1, 0, 1, 0]);
}
#[test]
fn conv_encode_known() {
let input = vec![1u8, 0u8];
let encoded = conv_encode(&input);
assert_eq!(encoded.len(), 4);
assert_eq!(encoded[0], 1); assert_eq!(encoded[1], 1); assert_eq!(encoded[2], 0); assert_eq!(encoded[3], 0); }
#[test]
fn viterbi_decode_noiseless() {
let input: Vec<u8> = (0..32).map(|i| (i & 1) as u8).collect();
let encoded = conv_encode(&input);
let recovered = viterbi_decode_hard(&encoded);
assert_eq!(recovered.len(), input.len());
for i in 0..input.len() {
assert_eq!(recovered[i], input[i], "bit {} mismatch", i);
}
}
#[test]
fn qpsk31_modulate_preamble() {
use crate::modulate::psk31::Qpsk31Mod;
let mut mod_ = Qpsk31Mod::new(8000.0, 0.0, 1.0);
let iq = mod_.modulate_bits(&[0u8; 16]);
let sps = psk31_sps(8000.0);
assert_eq!(iq.len(), 16 * sps);
let power: f32 = iq.iter().map(|s| s.re * s.re + s.im * s.im).sum::<f32>() / iq.len() as f32;
assert!(power > 0.01, "expected non-zero power, got {}", power);
}
#[test]
fn psk31_baud_constant() {
assert!((PSK31_BAUD - 31.25).abs() < 1e-6);
}
#[test]
fn qpsk31_hard_decide_dqpsk_four_quadrants() {
use crate::demodulate::psk31::hard_decide_dqpsk;
assert_eq!(hard_decide_dqpsk( 0.8, 0.2), ( 1.0, 0.0)); assert_eq!(hard_decide_dqpsk(-0.8, 0.2), (-1.0, 0.0)); assert_eq!(hard_decide_dqpsk( 0.2, 0.8), ( 0.0, 1.0)); assert_eq!(hard_decide_dqpsk( 0.2, -0.8), ( 0.0, -1.0)); assert_eq!(hard_decide_dqpsk( 0.707, 0.707), (1.0, 0.0));
}
#[test]
fn streaming_viterbi_matches_batch() {
use crate::codec::psk31_conv::{viterbi_decode, DQPSK_EXP};
let bits_in: Vec<u8> = (0..200).map(|i| ((i * 7 + 3) & 1) as u8).collect();
let coded = conv_encode(&bits_in);
let n_syms = coded.len() / 2;
let mut soft = Vec::with_capacity(n_syms * 2);
for i in 0..n_syms {
let c0 = coded[i * 2] & 1;
let c1 = coded[i * 2 + 1] & 1;
let dibit = c0 * 2 + c1;
let (re, im) = DQPSK_EXP[dibit as usize];
soft.push(re);
soft.push(im);
}
let batch = viterbi_decode(&soft);
let phase_steps: [(f32, f32); 4] = DQPSK_EXP;
let mut sv = StreamingViterbi::new(&phase_steps);
let mut streaming = Vec::new();
for i in 0..n_syms {
if let Some(b) = sv.feed_symbol(soft[i * 2], soft[i * 2 + 1]) {
streaming.push(b);
}
}
streaming.extend(sv.flush());
let compare_len = batch.len().min(streaming.len());
assert!(compare_len > 100, "too few bits to compare: batch={} streaming={}",
batch.len(), streaming.len());
let errors: usize = (0..compare_len)
.filter(|&i| batch[i] != streaming[i])
.count();
println!("streaming vs batch: {} bits compared, {} errors", compare_len, errors);
assert_eq!(errors, 0, "streaming Viterbi diverges from batch: {errors} errors in {compare_len} bits");
}
#[test]
fn streaming_viterbi_text_roundtrip() {
use crate::codec::psk31_conv::DQPSK_EXP;
let text = b"CQ CQ CQ DE N0GNR";
let mut enc = VaricodeEncoder::new();
enc.push_preamble(64);
for &b in text { enc.push_byte(b); }
enc.push_postamble(32);
let var_bits = enc.drain_bits();
let coded = conv_encode(&var_bits);
let n_syms = coded.len() / 2;
let phase_steps: [(f32, f32); 4] = DQPSK_EXP;
let mut sv = StreamingViterbi::new(&phase_steps);
let mut vdec = VaricodeDecoder::new();
let mut decoded = Vec::new();
for i in 0..n_syms {
let c0 = coded[i * 2] & 1;
let c1 = coded[i * 2 + 1] & 1;
let dibit = c0 * 2 + c1;
let (re, im) = DQPSK_EXP[dibit as usize];
if let Some(b) = sv.feed_symbol(re, im) {
vdec.push_bit(b);
while let Some(ch) = vdec.pop_char() {
decoded.push(ch);
}
}
}
for b in sv.flush() {
vdec.push_bit(b);
while let Some(ch) = vdec.pop_char() {
decoded.push(ch);
}
}
vdec.push_bit(0);
vdec.push_bit(0);
while let Some(ch) = vdec.pop_char() {
decoded.push(ch);
}
let decoded_str: String = decoded.iter()
.filter(|&&c| c >= 0x20 && c < 0x7f)
.map(|&c| c as char)
.collect();
println!("streaming Viterbi text: {:?}", decoded_str);
assert!(decoded_str.contains("CQ CQ CQ DE N0GNR"),
"expected message not found in {:?}", decoded_str);
}