use egui::ahash::HashMap;
#[cfg(feature = "fft")]
use rustfft::{num_complex::Complex, FftPlanner};
use std::collections::VecDeque;
use crate::data::traces::{TraceData, TraceRef};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FFTWindow {
Rect,
Hann,
Hamming,
Blackman,
}
impl Default for FFTWindow {
fn default() -> Self {
FFTWindow::Hann
}
}
impl FFTWindow {
pub const ALL: &'static [FFTWindow] = &[
FFTWindow::Rect,
FFTWindow::Hann,
FFTWindow::Hamming,
FFTWindow::Blackman,
];
pub fn label(&self) -> &'static str {
match self {
FFTWindow::Rect => "Rect",
FFTWindow::Hann => "Hann",
FFTWindow::Hamming => "Hamming",
FFTWindow::Blackman => "Blackman",
}
}
pub fn weight(&self, n: usize, len: usize) -> f64 {
match self {
FFTWindow::Rect => 1.0,
FFTWindow::Hann => {
0.5 - 0.5 * (2.0 * std::f64::consts::PI * n as f64 / (len as f64)).cos()
}
FFTWindow::Hamming => {
0.54 - 0.46 * (2.0 * std::f64::consts::PI * n as f64 / (len as f64)).cos()
}
FFTWindow::Blackman => {
0.42 - 0.5 * (2.0 * std::f64::consts::PI * n as f64 / (len as f64)).cos()
+ 0.08 * (4.0 * std::f64::consts::PI * n as f64 / (len as f64)).cos()
}
}
}
}
pub struct FftData {
pub fft_size: usize,
pub fft_window: FFTWindow,
pub fft_traces: HashMap<TraceRef, TraceData>,
}
impl Default for FftData {
fn default() -> Self {
Self {
fft_size: 1024,
fft_window: FFTWindow::Hann,
fft_traces: HashMap::default(),
}
}
}
impl FftData {
pub fn compute_fft(
buf: &VecDeque<[f64; 2]>,
paused: bool,
buffer_snapshot: &Option<VecDeque<[f64; 2]>>,
fft_size: usize,
fft_window: FFTWindow,
) -> Option<Vec<[f64; 2]>> {
let buf = if paused {
buffer_snapshot.as_ref()?
} else {
buf
};
if buf.len() < fft_size {
return None;
}
let len = buf.len();
let slice: Vec<[f64; 2]> = buf.iter().skip(len - fft_size).cloned().collect();
if slice.len() != fft_size {
return None;
}
let t0 = slice.first()?[0];
let t1 = slice.last()?[0];
if !(t1 > t0) {
return None;
}
let dt_est = (t1 - t0) / (fft_size as f64 - 1.0);
if dt_est <= 0.0 {
return None;
}
let sample_rate = 1.0 / dt_est;
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(fft_size);
let mut data: Vec<Complex<f64>> = slice
.iter()
.enumerate()
.map(|(i, arr)| {
let w = fft_window.weight(i, fft_size);
Complex {
re: arr[1] * w,
im: 0.0,
}
})
.collect();
fft.process(&mut data);
let half = fft_size / 2;
let scale = 2.0 / fft_size as f64; let mut out: Vec<[f64; 2]> = Vec::with_capacity(half);
for (k, c) in data.iter().take(half).enumerate() {
let freq = k as f64 * sample_rate / fft_size as f64;
let mag = (c.re * c.re + c.im * c.im).sqrt() * scale;
out.push([freq, mag]);
}
Some(out)
}
}