use std::f32::consts::PI;
#[derive(Clone, Copy, Debug)]
pub struct SubtractCfg {
pub sample_rate: f32,
pub tone_spacing_hz: f32,
pub samples_per_symbol: usize,
pub base_offset_s: f32,
}
fn generate_iq(tones: &[u8], freq_hz: f32, cfg: &SubtractCfg) -> (Vec<f32>, Vec<f32>) {
let n = tones.len() * cfg.samples_per_symbol;
let mut w_cos = vec![0.0f32; n];
let mut w_sin = vec![0.0f32; n];
let mut phase = 0.0f32;
for (sym, &tone) in tones.iter().enumerate() {
let freq = freq_hz + tone as f32 * cfg.tone_spacing_hz;
let dphi = 2.0 * PI * freq / cfg.sample_rate;
let base = sym * cfg.samples_per_symbol;
for j in 0..cfg.samples_per_symbol {
w_cos[base + j] = phase.cos();
w_sin[base + j] = phase.sin();
phase += dphi;
if phase > PI {
phase -= 2.0 * PI;
}
}
}
(w_cos, w_sin)
}
#[inline]
pub fn subtract_tones(
audio: &mut [i16],
tones: &[u8],
freq_hz: f32,
dt_sec: f32,
gain: f32,
cfg: &SubtractCfg,
) {
let (w_cos, w_sin) = generate_iq(tones, freq_hz, cfg);
let start = ((cfg.base_offset_s + dt_sec) * cfg.sample_rate).round() as usize;
let len = w_cos.len().min(audio.len().saturating_sub(start));
if len == 0 {
return;
}
let (num_a, num_b, den_a, den_b) =
(0..len).fold((0.0f32, 0.0f32, 0.0f32, 0.0f32), |(na, nb, da, db), i| {
let rx = audio[start + i] as f32;
(
na + rx * w_cos[i],
nb + rx * w_sin[i],
da + w_cos[i] * w_cos[i],
db + w_sin[i] * w_sin[i],
)
});
let a = if den_a > f32::EPSILON {
num_a / den_a
} else {
0.0
};
let b = if den_b > f32::EPSILON {
num_b / den_b
} else {
0.0
};
for i in 0..len {
let sub = gain * (a * w_cos[i] + b * w_sin[i]);
let new_val = audio[start + i] as f32 - sub;
audio[start + i] = new_val.clamp(-32_768.0, 32_767.0) as i16;
}
}