use std::f32::consts::PI;
#[derive(Clone, Copy, Debug)]
pub struct GfskCfg {
pub sample_rate: f32,
pub samples_per_symbol: usize,
pub bt: f32,
pub hmod: f32,
pub ramp_samples: usize,
}
#[inline]
fn gfsk_pulse(bt: f32, t: f32) -> f32 {
let c = PI * (2.0_f32 / 2.0_f32.ln()).sqrt();
0.5 * (erf(c * bt * (t + 0.5)) - erf(c * bt * (t - 0.5)))
}
#[inline]
fn erf(x: f32) -> f32 {
let sign = if x >= 0.0 { 1.0 } else { -1.0 };
let x = x.abs();
let t = 1.0 / (1.0 + 0.3275911 * x);
let poly = t
* (0.254_829_6
+ t * (-0.284_496_72 + t * (1.421_413_8 + t * (-1.453_152_1 + t * 1.061_405_4))));
sign * (1.0 - poly * (-x * x).exp())
}
pub fn synth_f32(tones: &[u8], f0_hz: f32, amplitude: f32, cfg: &GfskCfg) -> Vec<f32> {
let nsps = cfg.samples_per_symbol;
let nsym = tones.len();
let twopi = 2.0 * PI;
let dt = 1.0 / cfg.sample_rate;
let pulse_len = 3 * nsps;
let pulse: Vec<f32> = (0..pulse_len)
.map(|i| {
let tt = (i as f32 - 1.5 * nsps as f32) / nsps as f32;
gfsk_pulse(cfg.bt, tt)
})
.collect();
let total = (nsym + 2) * nsps;
let mut dphi = vec![0.0f32; total];
let dphi_peak = twopi * cfg.hmod / nsps as f32;
for (j, &tone) in tones.iter().enumerate() {
let ib = j * nsps;
for i in 0..pulse_len {
if ib + i < total {
dphi[ib + i] += dphi_peak * pulse[i] * tone as f32;
}
}
}
for i in 0..(2 * nsps).min(total) {
dphi[i] += dphi_peak * tones[0] as f32 * pulse[nsps + i];
}
let ofs = nsym * nsps;
for i in 0..(2 * nsps) {
if ofs + i < total {
dphi[ofs + i] += dphi_peak * tones[nsym - 1] as f32 * pulse[i];
}
}
for d in dphi.iter_mut() {
*d += twopi * f0_hz * dt;
}
let nwave = nsym * nsps;
let mut wave = vec![0.0f32; nwave];
let mut phi = 0.0f32;
for k in 0..nwave {
wave[k] = amplitude * phi.sin();
phi += dphi[nsps + k];
if phi > twopi {
phi -= twopi;
}
}
let nramp = cfg.ramp_samples.min(nwave / 2);
if nramp > 0 {
for i in 0..nramp {
let env = (1.0 - (twopi * i as f32 / (2.0 * nramp as f32)).cos()) / 2.0;
wave[i] *= env;
}
let k1 = nwave - nramp;
for i in 0..nramp {
let env = (1.0 + (twopi * i as f32 / (2.0 * nramp as f32)).cos()) / 2.0;
wave[k1 + i] *= env;
}
}
wave
}
#[inline]
pub fn synth_i16(tones: &[u8], f0_hz: f32, amplitude_i16: i16, cfg: &GfskCfg) -> Vec<i16> {
synth_f32(tones, f0_hz, 1.0, cfg)
.iter()
.map(|&s| (s * amplitude_i16 as f32) as i16)
.collect()
}