use nalgebra::DVector;
use num_complex::Complex;
pub const FSK_MARK_HZ: f32 = 2083.3;
pub const FSK_SPACE_HZ: f32 = 1562.5;
pub const BAUD_HZ: f32 = 520.83;
pub const PREAMBLE: u8 = 0xab;
pub const PREAMBLE_SYNC_WORD: u32 = u32::from_be_bytes([PREAMBLE, PREAMBLE, PREAMBLE, PREAMBLE]);
pub fn samples_per_symbol(fs: u32) -> f32 {
fs as f32 / BAUD_HZ as f32
}
pub fn matched_filter(fs: u32) -> (DVector<Complex<f32>>, DVector<Complex<f32>>) {
let ntaps = f32::floor(samples_per_symbol(fs)) as usize;
let mark = cisoid_matched_filter(ntaps, FSK_MARK_HZ / fs as f32);
let space = cisoid_matched_filter(ntaps, FSK_SPACE_HZ / fs as f32);
(mark, space)
}
fn cisoid_matched_filter(points: usize, freq_fs: f32) -> DVector<Complex<f32>> {
let mut out = DVector::from_element(points, Complex::new(0.0, 0.0));
for (iter, o) in out.iter_mut().enumerate() {
*o = Complex::new(
0.0,
2.0 * std::f32::consts::PI * freq_fs as f32 * ((points - 1 - iter) as f32),
);
*o = 2.0f32 * o.exp().conj() / points as f32;
}
out
}
#[cfg(test)]
pub fn modulate_afsk(syms: &[f32], fs: u32) -> (DVector<f32>, usize) {
const TWOPI: f32 = 2.0f32 * std::f32::consts::PI;
let mark_rad_per_sa = TWOPI * FSK_MARK_HZ / (fs as f32);
let space_rad_per_sa = TWOPI * FSK_SPACE_HZ / (fs as f32);
let symlen = {
let symlen = f32::floor(samples_per_symbol(fs)) as usize;
if symlen % 2 == 0 {
symlen
} else {
symlen + 1
}
};
let mut out = DVector::from_element(syms.len() * symlen, 0.0f32);
let mut phase = 0.0f32;
for (itr, sa) in out.iter_mut().enumerate() {
let sym = syms[itr / symlen] >= 0.0;
if sym {
phase += mark_rad_per_sa;
} else {
phase += space_rad_per_sa;
}
if phase > TWOPI {
phase = -TWOPI + phase;
}
*sa = phase.cos();
}
(out, symlen)
}
#[cfg(test)]
pub fn bytes_to_symbols(bytes: &[u8]) -> Vec<f32> {
let mut v = Vec::with_capacity(bytes.len() * 8);
for byte in bytes {
let mut word = *byte;
for _i in 0..8 {
let bit = word & 0x01;
if bit == 1 {
v.push(1.0f32);
} else {
v.push(-1.0f32);
}
word = word >> 1;
}
}
v
}
#[cfg(test)]
pub fn bytes_to_samples(bytes: &[u8], nsps: usize) -> Vec<f32> {
let nsps = usize::max(1, nsps);
let mut v = Vec::with_capacity(bytes.len() * 8 * 2);
for byte in bytes {
let mut word = *byte;
for _i in 0..8 {
let bit = word & 0x01;
v.extend(std::iter::repeat(0.0f32).take(nsps - 1));
if bit == 1 {
v.push(1.0f32);
} else {
v.push(-1.0f32);
}
word = word >> 1;
}
}
v
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cisoid_matched_filter() {
const FREQ_FS: f32 = 0.0944807256f32;
const EXPECT_REAL: &[f32] = &[-0.719973f32, -0.208581, 0.374184, 0.828910, 1.000000];
const EXPECT_IMAG: &[f32] = &[-0.694002f32, -0.978005, -0.927355, -0.559382, -0.000000];
let gain = 2.0f32 / EXPECT_REAL.len() as f32;
let out = cisoid_matched_filter(EXPECT_REAL.len(), FREQ_FS);
for (i, item) in out.iter().enumerate() {
let d = (item - gain * Complex::new(EXPECT_REAL[i], EXPECT_IMAG[i])).norm();
assert!(d < 1e-4);
}
}
#[test]
fn test_bytes_to_symbols() {
const EXPECT_SYMS: &[f32] = &[
1.0f32, 1.0f32, -1.0f32, 1.0f32, -1.0f32, 1.0f32, -1.0f32, 1.0f32, 1.0f32, -1.0f32,
-1.0f32, -1.0f32, -1.0f32, 1.0f32, -1.0f32, -1.0f32,
];
const BYTES: &[u8] = &[0xAB, 0x21];
let syms = bytes_to_symbols(BYTES);
assert_eq!(EXPECT_SYMS, syms.as_slice());
}
}