use dsp_process::{Split, SplitProcess, View, ViewMut};
use idsp::Complex;
use std::f32::consts::TAU;
const M: usize = 4; const TAPS: usize = 8;
type Iq = [f32; 2];
type Frame = [Iq; M];
fn sinc(x: f32) -> f32 {
if x == 0.0 { 1.0 } else { x.sin() / x }
}
fn prototype() -> [f32; M * TAPS] {
let fc = 0.5 / M as f32 * 0.9;
let mid = (M * TAPS - 1) as f32 * 0.5;
let mut h = core::array::from_fn(|i| {
let n = i as f32 - mid;
let w = 0.54 - 0.46 * (TAU * i as f32 / (M * TAPS - 1) as f32).cos();
2.0 * fc * sinc(TAU * fc * n) * w
});
let sum = h.iter().sum::<f32>();
h.iter_mut().for_each(|h| *h /= sum);
h
}
#[derive(Clone, Copy, Debug, Default)]
struct BankState {
hist: [Frame; TAPS],
head: usize,
}
#[derive(Clone, Copy, Debug)]
struct PolyphaseBank {
coeff: [[f32; M]; TAPS],
}
impl SplitProcess<Frame, Frame, BankState> for PolyphaseBank {
fn process(&self, state: &mut BankState, x: Frame) -> Frame {
state.head = (state.head + TAPS - 1) % TAPS;
state.hist[state.head] = x;
let mut y = Frame::default();
for tap in 0..TAPS {
for ((y, c), h) in y
.iter_mut()
.zip(&self.coeff[tap])
.zip(&state.hist[(state.head + tap) % TAPS])
{
y[0] += h[0] * c;
y[1] += h[1] * c;
}
}
y
}
}
#[derive(Clone, Copy, Debug, Default)]
struct Dft4;
impl SplitProcess<Frame, Frame> for Dft4 {
fn process(&self, _: &mut (), x: Frame) -> Frame {
[
[
x[0][0] + x[1][0] + x[2][0] + x[3][0],
x[0][1] + x[1][1] + x[2][1] + x[3][1],
],
[
x[0][0] + x[1][1] - x[2][0] - x[3][1],
x[0][1] - x[1][0] - x[2][1] + x[3][0],
],
[
x[0][0] - x[1][0] + x[2][0] - x[3][0],
x[0][1] - x[1][1] + x[2][1] - x[3][1],
],
[
x[0][0] - x[1][1] - x[2][0] + x[3][1],
x[0][1] + x[1][0] - x[2][1] - x[3][0],
],
]
}
}
fn run_graph(x: &[Iq]) -> Vec<Frame> {
let coeff = bytemuck::cast(prototype());
let mut bank = (Split::new(PolyphaseBank { coeff }, BankState::default())
* Split::stateless(Dft4))
.minor()
.per_frame();
let (frames, []) = x.as_chunks() else {
unreachable!()
};
let mut y = vec![Frame::default(); frames.len()];
bank.process_frames(View::from_frames(frames), ViewMut::from_frames(&mut y));
y
}
fn tone(freq: f32, n: usize) -> Vec<Iq> {
(0..n)
.map(|i| Complex::<f32>::from_angle(TAU * freq * i as f32).0)
.collect()
}
fn channel_powers(freq: f32) -> [f32; M] {
let y = run_graph(&tone(freq, 4096));
let mut p = [0.0; M];
let mut used = 0.0;
for frame in &y[128..] {
for (p, y) in p.iter_mut().zip(*frame) {
*p += y[0] * y[0] + y[1] * y[1];
}
used += 1.0;
}
p.map(|p| p / used)
}
fn main() {
for freq in [0.0, 0.25, 0.5, 0.75] {
let p = channel_powers(freq);
println!(
"freq={freq:.2} powers=[{:.3}, {:.3}, {:.3}, {:.3}]",
p[0], p[1], p[2], p[3]
);
}
}
#[cfg(test)]
mod test {
use super::*;
fn argmax(x: &[f32]) -> usize {
x.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
.unwrap()
.0
}
#[test]
fn routes_center_tones_to_expected_bins() {
for (freq, want) in [(0.0, 0), (0.25, 1), (0.5, 2), (0.75, 3)] {
let p = channel_powers(freq);
assert_eq!(argmax(&p), want);
let strongest_other = p
.iter()
.enumerate()
.filter(|(i, _)| *i != want)
.map(|(_, p)| *p)
.fold(0.0, f32::max);
assert!(p[want] > 10.0 * strongest_other);
}
}
}