use comnum_core::{Error, Result};
use comnum_math::{normal_pdf, qfunc};
use std::f64::consts::{PI, TAU};
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FrequencyPulse {
Rectangular,
RaisedCosine,
Gaussian {
bt: f64,
},
}
impl FrequencyPulse {
pub fn name(&self) -> &'static str {
match self {
FrequencyPulse::Rectangular => "rec",
FrequencyPulse::RaisedCosine => "rc",
FrequencyPulse::Gaussian { .. } => "gmsk",
}
}
pub fn phase_response(&self, t: f64, length: usize) -> f64 {
let l = length as f64;
if t <= 0.0 {
return 0.0;
}
if t >= l {
return 0.5;
}
match *self {
FrequencyPulse::Rectangular => t / (2.0 * l),
FrequencyPulse::RaisedCosine => t / (2.0 * l) - (TAU * t / l).sin() / (2.0 * TAU),
FrequencyPulse::Gaussian { bt } => {
let lo = gmsk_phase(-l / 2.0, bt);
let hi = gmsk_phase(l / 2.0, bt);
0.5 * (gmsk_phase(t - l / 2.0, bt) - lo) / (hi - lo)
}
}
}
pub fn from_name(name: &str, bt: f64) -> Result<FrequencyPulse> {
match name.to_ascii_lowercase().as_str() {
"rec" | "rectangular" => Ok(FrequencyPulse::Rectangular),
"rc" | "raised-cosine" => Ok(FrequencyPulse::RaisedCosine),
"gmsk" | "gaussian" => Ok(FrequencyPulse::Gaussian { bt }),
other => Err(Error::Config(format!("pulse must be rec, rc or gmsk, got {other:?}"))),
}
}
}
fn q_integral(x: f64) -> f64 {
x * qfunc(x) - normal_pdf(x)
}
pub fn gmsk_pulse(t: f64, bt: f64) -> f64 {
let kappa = TAU * bt / 2f64.ln().sqrt();
0.5 * (qfunc(kappa * (t - 0.5)) - qfunc(kappa * (t + 0.5)))
}
pub fn gmsk_phase(t: f64, bt: f64) -> f64 {
let kappa = TAU * bt / 2f64.ln().sqrt();
0.5 + (q_integral(kappa * (t - 0.5)) - q_integral(kappa * (t + 0.5))) / (2.0 * kappa)
}
pub fn msk_psd(ft: f64) -> f64 {
let d = 1.0 - 16.0 * ft * ft;
let ratio = if d.abs() < 1e-7 { PI / 4.0 } else { (TAU * ft).cos() / d };
16.0 / (PI * PI) * ratio * ratio
}