use crate::core::{FrameLayout, ModulationParams, Protocol, ProtocolId, SyncMode};
use crate::fec::ConvFano;
use crate::msg::Wspr50Message;
pub mod decode;
pub mod rx;
pub mod search;
pub mod spectrogram;
pub mod sync_vector;
pub mod tx;
pub use decode::{WsprDecode, decode_at};
pub use rx::demodulate_aligned;
pub use search::{SearchParams, SyncCandidate, coarse_search};
pub use sync_vector::WSPR_SYNC_VECTOR;
pub use tx::{synthesize_audio, synthesize_type1};
#[derive(Copy, Clone, Debug, Default)]
pub struct Wspr;
impl ModulationParams for Wspr {
const NTONES: u32 = 4;
const BITS_PER_SYMBOL: u32 = 2;
const NSPS: u32 = 8192;
const SYMBOL_DT: f32 = 8192.0 / 12_000.0;
const TONE_SPACING_HZ: f32 = 12_000.0 / 8192.0; const GRAY_MAP: &'static [u8] = &[0, 1, 2, 3];
const GFSK_BT: f32 = 1.0;
const GFSK_HMOD: f32 = 1.0;
const NFFT_PER_SYMBOL_FACTOR: u32 = 1; const NSTEP_PER_SYMBOL: u32 = 16; const NDOWN: u32 = 32; }
impl FrameLayout for Wspr {
const N_DATA: u32 = 162; const N_SYNC: u32 = 0;
const N_SYMBOLS: u32 = 162;
const N_RAMP: u32 = 0;
const SYNC_MODE: SyncMode = SyncMode::Interleaved {
sync_bit_pos: 0, vector: &WSPR_SYNC_VECTOR,
};
const T_SLOT_S: f32 = 120.0;
const TX_START_OFFSET_S: f32 = 1.0;
}
impl Protocol for Wspr {
type Fec = ConvFano;
type Msg = Wspr50Message;
const ID: ProtocolId = ProtocolId::Wspr;
}
#[inline]
fn bit_reverse_8(i: u8) -> u8 {
let i64 = i as u64;
(((i64 * 0x8020_0802u64) & 0x0884_4221_10u64).wrapping_mul(0x0101_0101_01u64) >> 32) as u8
}
pub fn interleave(bits: &mut [u8; 162]) {
let mut tmp = [0u8; 162];
let mut p = 0u8;
let mut i = 0u8;
while p < 162 {
let j = bit_reverse_8(i) as usize;
if j < 162 {
tmp[j] = bits[p as usize];
p += 1;
}
i = i.wrapping_add(1);
}
bits.copy_from_slice(&tmp);
}
pub fn deinterleave(bits: &mut [u8; 162]) {
let mut tmp = [0u8; 162];
let mut p = 0u8;
let mut i = 0u8;
while p < 162 {
let j = bit_reverse_8(i) as usize;
if j < 162 {
tmp[p as usize] = bits[j];
p += 1;
}
i = i.wrapping_add(1);
}
bits.copy_from_slice(&tmp);
}
pub fn encode_channel_symbols(info_bits: &[u8; 50]) -> [u8; 162] {
use crate::core::FecCodec;
let codec = ConvFano;
let mut cw = vec![0u8; ConvFano::N];
codec.encode(info_bits, &mut cw);
let mut channel_bits = [0u8; 162];
channel_bits.copy_from_slice(&cw);
interleave(&mut channel_bits);
let mut symbols = [0u8; 162];
for i in 0..162 {
symbols[i] = 2 * channel_bits[i] + WSPR_SYNC_VECTOR[i];
}
symbols
}
pub fn decode_from_deinterleaved_llrs(data_llrs: &[f32; 162]) -> Option<crate::msg::WsprMessage> {
use crate::core::{FecCodec, FecOpts, MessageCodec};
let codec = ConvFano;
let fec = codec.decode_soft(data_llrs, &FecOpts::default())?;
let msg = Wspr50Message;
let mut info_bits = [0u8; 50];
info_bits.copy_from_slice(&fec.info);
msg.unpack(&info_bits, &crate::core::DecodeContext::default())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::FecCodec;
#[test]
fn wspr_trait_surface() {
assert_eq!(<Wspr as ModulationParams>::NTONES, 4);
assert_eq!(<Wspr as ModulationParams>::NSPS, 8192);
assert_eq!(<Wspr as FrameLayout>::N_SYMBOLS, 162);
assert_eq!(<Wspr as FrameLayout>::T_SLOT_S, 120.0);
match <Wspr as FrameLayout>::SYNC_MODE {
SyncMode::Interleaved {
sync_bit_pos,
vector,
} => {
assert_eq!(sync_bit_pos, 0);
assert_eq!(vector.len(), 162);
}
SyncMode::Block(_) => panic!("WSPR must use interleaved sync"),
}
assert_eq!(<<Wspr as Protocol>::Fec as FecCodec>::N, 162);
assert_eq!(<<Wspr as Protocol>::Fec as FecCodec>::K, 50);
}
#[test]
fn interleave_is_involution() {
let mut bits = [0u8; 162];
for i in 0..162 {
bits[i] = ((i * 7 + 13) & 1) as u8;
}
let original = bits;
interleave(&mut bits);
assert_ne!(bits, original, "interleave must permute");
let once = bits;
deinterleave(&mut bits);
assert_eq!(bits, original);
let mut bits2 = once;
interleave(&mut bits2);
let _ = bits2;
}
#[test]
fn roundtrip_k1abc_fn42_37() {
use crate::msg::{WsprMessage, wspr::pack_type1};
let info_bits = pack_type1("K1ABC", "FN42", 37).expect("pack");
let symbols = encode_channel_symbols(&info_bits);
for i in 0..162 {
assert_eq!(
symbols[i] & 1,
WSPR_SYNC_VECTOR[i],
"sync LSB mismatch at {}",
i
);
assert!(symbols[i] < 4);
}
let mut data_bits = [0u8; 162];
for i in 0..162 {
data_bits[i] = (symbols[i] >> 1) & 1;
}
deinterleave(&mut data_bits);
let mut llrs = [0f32; 162];
for i in 0..162 {
llrs[i] = if data_bits[i] == 0 { 8.0 } else { -8.0 };
}
let msg = decode_from_deinterleaved_llrs(&llrs).expect("decode");
assert_eq!(
msg,
WsprMessage::Type1 {
callsign: "K1ABC".into(),
grid: "FN42".into(),
power_dbm: 37,
}
);
}
}