use super::M17Error;
use crate::types::SampleRate;
#[must_use]
pub const fn dibit_to_symbol(dibit: u8) -> i8 {
match dibit & 0b11 {
0b01 => 3,
0b00 => 1,
0b10 => -1,
_ => -3,
}
}
#[must_use]
pub const fn symbol_to_dibit(symbol: i8) -> u8 {
match symbol {
3 => 0b01,
1 => 0b00,
-1 => 0b10,
_ => 0b11,
}
}
pub const FRAME_SYMBOLS: usize = 192;
#[must_use]
pub fn sync_symbols(sync: u16) -> [i8; 8] {
let mut out = [0i8; 8];
for (i, s) in out.iter_mut().enumerate() {
*s = dibit_to_symbol(((sync >> (14 - 2 * i)) & 0b11) as u8);
}
out
}
pub const SYMBOL_RATE: u32 = 4_800;
pub const RRC_ALPHA_NUM: u32 = 1;
pub const RRC_ALPHA_DEN: u32 = 2;
pub const RRC_SPAN_SYMBOLS: usize = 8;
pub const MAX_SPS: usize = 10;
pub(super) const MAX_TAPS: usize = RRC_SPAN_SYMBOLS * MAX_SPS + 1;
fn sin_taylor(x: f64) -> f64 {
const PI: f64 = core::f64::consts::PI;
let mut r = x % (2.0 * PI);
if r < 0.0 {
r += 2.0 * PI;
}
let r = if r <= 0.5 * PI {
r
} else if r <= 1.5 * PI {
PI - r
} else {
r - 2.0 * PI
};
let r2 = r * r;
r * (1.0
+ r2 * (-1.0 / 6.0
+ r2 * (1.0 / 120.0
+ r2 * (-1.0 / 5_040.0 + r2 * (1.0 / 362_880.0 + r2 * (-1.0 / 39_916_800.0))))))
}
fn cos_taylor(x: f64) -> f64 {
sin_taylor(x + 0.5 * core::f64::consts::PI)
}
fn sqrt_newton(x: f64) -> f64 {
if x <= 0.0 {
return 0.0;
}
let mut g = x;
for _ in 0..40 {
g = 0.5 * (g + x / g);
}
g
}
fn rrc(t: f64) -> f64 {
const PI: f64 = core::f64::consts::PI;
const ALPHA: f64 = 0.5;
let at = 4.0 * ALPHA * t;
if t.abs() < 1e-9 {
return 1.0 - ALPHA + 4.0 * ALPHA / PI;
}
if (at.abs() - 1.0).abs() < 1e-9 {
let s = sin_taylor(PI / (4.0 * ALPHA));
let c = cos_taylor(PI / (4.0 * ALPHA));
return ALPHA / sqrt_newton(2.0) * ((1.0 + 2.0 / PI) * s + (1.0 - 2.0 / PI) * c);
}
(sin_taylor(PI * t * (1.0 - ALPHA)) + at * cos_taylor(PI * t * (1.0 + ALPHA)))
/ (PI * t * (1.0 - at * at))
}
pub(super) fn design_rrc(sps: usize, taps: &mut [f64; MAX_TAPS]) -> usize {
let n = RRC_SPAN_SYMBOLS * sps + 1;
let mid = (n - 1) / 2;
let mut energy = 0.0;
for (i, t) in taps.iter_mut().enumerate().take(n) {
let x = (i as f64 - mid as f64) / sps as f64;
*t = rrc(x);
energy += *t * *t;
}
let norm = sqrt_newton(energy);
for t in taps.iter_mut().take(n) {
*t /= norm;
}
n
}
pub(super) fn checked_sps(sample_rate: SampleRate) -> Result<usize, M17Error> {
let hz = sample_rate.hz();
if !hz.is_multiple_of(SYMBOL_RATE) {
return Err(M17Error::SampleRateInexact { got: hz });
}
Ok((hz / SYMBOL_RATE) as usize)
}
#[derive(Debug, Clone)]
pub struct M17Modulator {
taps: [i32; MAX_TAPS],
ntaps: usize,
sps: usize,
history: [i8; RRC_SPAN_SYMBOLS + 1],
remaining: usize,
}
impl M17Modulator {
pub fn new(sample_rate: SampleRate) -> Result<Self, M17Error> {
let sps = checked_sps(sample_rate)?;
let mut f = [0.0f64; MAX_TAPS];
let ntaps = design_rrc(sps, &mut f);
let mut worst = 0.0f64;
for p in 0..sps {
let mut acc = 0.0;
let mut idx = p;
while idx < ntaps {
acc += f[idx].abs();
idx += sps;
}
if acc > worst {
worst = acc;
}
}
let scale = 30_000.0 / (3.0 * worst);
let mut taps = [0i32; MAX_TAPS];
for (q, &h) in taps.iter_mut().zip(f.iter()).take(ntaps) {
*q = (h * scale + if h >= 0.0 { 0.5 } else { -0.5 }) as i32;
}
Ok(Self {
taps,
ntaps,
sps,
history: [0; RRC_SPAN_SYMBOLS + 1],
remaining: 0,
})
}
#[must_use]
pub const fn samples_per_symbol(&self) -> usize {
self.sps
}
pub fn feed(&mut self, symbol: i8) {
for i in (1..self.history.len()).rev() {
self.history[i] = self.history[i - 1];
}
self.history[0] = symbol;
self.remaining = self.sps;
}
pub fn next_i16(&mut self) -> Option<i16> {
if self.remaining == 0 {
return None;
}
let phase = self.sps - self.remaining;
self.remaining -= 1;
let mut acc: i64 = 0;
for (j, &sym) in self.history.iter().enumerate() {
let idx = phase + j * self.sps;
if idx < self.ntaps && sym != 0 {
acc += i64::from(sym) * i64::from(self.taps[idx]);
}
}
Some(acc.clamp(-32_768, 32_767) as i16)
}
}