use num_complex::Complex32 as C32;
pub struct Waterfall {
pub mag: Vec<f32>,
pub num_syms: usize,
pub num_tones: usize,
}
impl Waterfall {
pub fn new(num_syms: usize, num_tones: usize) -> Self {
Self {
mag: vec![0.0f32; num_syms * num_tones],
num_syms,
num_tones,
}
}
#[inline]
pub fn get(&self, sym: usize, tone: usize) -> f32 {
self.mag[sym * self.num_tones + tone]
}
#[inline]
pub fn set(&mut self, sym: usize, tone: usize, val: f32) {
self.mag[sym * self.num_tones + tone] = val;
}
}
pub fn compute_waterfall(
iq: &[C32],
fs: f32,
base_hz: f32,
tone_spacing_hz: f32,
samples_per_sym: usize,
num_syms: usize,
num_tones: usize,
time_offset: usize,
) -> Waterfall {
let steps: Vec<C32> = (0..num_tones)
.map(|k| {
let f = base_hz + (k as f32) * tone_spacing_hz;
let phi = -core::f32::consts::TAU * f / fs;
let (s, c) = phi.sin_cos();
C32::new(c, s)
})
.collect();
let mut wf = Waterfall::new(num_syms, num_tones);
for sym in 0..num_syms {
let start = time_offset + sym * samples_per_sym;
let end = start + samples_per_sym;
if start >= iq.len() {
continue;
}
let slice = if end <= iq.len() {
&iq[start..end]
} else {
&iq[start..]
};
for (k, &w) in steps.iter().enumerate() {
let e = goertzel_energy(slice, w);
wf.set(sym, k, (e + 1e-12).ln());
}
}
wf
}
#[inline]
fn goertzel_energy(slice: &[C32], w: C32) -> f32 {
let n = slice.len();
let mut acc = C32::new(0.0, 0.0);
let mut phasor = C32::new(1.0, 0.0);
let mut i = 0;
let nn = n & !3;
while i < nn {
acc += slice[i] * phasor;
let p1 = mul(phasor, w);
acc += slice[i + 1] * p1;
let p2 = mul(p1, w);
acc += slice[i + 2] * p2;
let p3 = mul(p2, w);
acc += slice[i + 3] * p3;
phasor = mul(p3, w);
i += 4;
}
while i < n {
acc += slice[i] * phasor;
phasor = mul(phasor, w);
i += 1;
}
acc.norm_sqr()
}
#[inline(always)]
fn mul(a: C32, b: C32) -> C32 {
C32::new(
a.re.mul_add(b.re, -a.im * b.im),
a.im.mul_add(b.re, a.re * b.im),
)
}