#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use crate::api::{Direction, Flags, Plan};
use crate::kernel::{Complex, Float};
pub mod chirp_tables;
#[cfg(test)]
mod tests;
use chirp_tables::{build_chirp_filter, build_chirp_post, build_chirp_y};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum CztError {
InvalidSize(usize),
InvalidParameter,
PlanFailed,
MismatchedLength {
expected: usize,
actual: usize,
},
}
impl core::fmt::Display for CztError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidSize(n) => write!(f, "invalid CZT size: {n}"),
Self::InvalidParameter => write!(f, "CZT parameter A or W causes overflow"),
Self::PlanFailed => write!(f, "internal FFT plan creation failed"),
Self::MismatchedLength { expected, actual } => {
write!(f, "length mismatch: expected {expected}, got {actual}")
}
}
}
}
pub type CztResult<T> = Result<T, CztError>;
pub struct CztPlan<T: Float> {
n: usize,
m: usize,
l: usize,
chirp_y: Vec<Complex<T>>,
h_fft: Vec<Complex<T>>,
chirp_post: Vec<Complex<T>>,
fft_plan: Plan<T>,
ifft_plan: Plan<T>,
}
impl<T: Float> CztPlan<T> {
pub fn new(n: usize, m: usize, a: Complex<T>, w: Complex<T>) -> CztResult<Self> {
if n == 0 {
return Err(CztError::InvalidSize(n));
}
if m == 0 {
return Err(CztError::InvalidSize(m));
}
let w_abs = w.norm();
if w_abs > T::ONE {
let n_f = T::from_usize(n);
let max_exp = n_f * n_f / T::TWO;
let log_factor = max_exp * num_traits::Float::ln(w_abs);
if log_factor > T::from_f64(700.0) {
return Err(CztError::InvalidParameter);
}
}
let l = (n + m - 1).next_power_of_two();
let chirp_y = build_chirp_y(n, a, w);
let h_circ = build_chirp_filter(n, m, l, w);
let fft_plan =
Plan::<T>::dft_1d(l, Direction::Forward, Flags::MEASURE).ok_or(CztError::PlanFailed)?;
let ifft_plan = Plan::<T>::dft_1d(l, Direction::Backward, Flags::MEASURE)
.ok_or(CztError::PlanFailed)?;
let mut h_fft = vec![Complex::<T>::zero(); l];
fft_plan.execute(&h_circ, &mut h_fft);
let chirp_post = build_chirp_post(m, w);
Ok(Self {
n,
m,
l,
chirp_y,
h_fft,
chirp_post,
fft_plan,
ifft_plan,
})
}
pub fn execute(&self, input: &[Complex<T>], output: &mut [Complex<T>]) -> CztResult<()> {
if input.len() != self.n {
return Err(CztError::MismatchedLength {
expected: self.n,
actual: input.len(),
});
}
if output.len() != self.m {
return Err(CztError::MismatchedLength {
expected: self.m,
actual: output.len(),
});
}
let l = self.l;
let mut y_padded = vec![Complex::<T>::zero(); l];
for i in 0..self.n {
y_padded[i] = input[i] * self.chirp_y[i];
}
let mut y_fft = vec![Complex::<T>::zero(); l];
self.fft_plan.execute(&y_padded, &mut y_fft);
let mut r_fft = vec![Complex::<T>::zero(); l];
for i in 0..l {
r_fft[i] = y_fft[i] * self.h_fft[i];
}
let mut r = vec![Complex::<T>::zero(); l];
self.ifft_plan.execute(&r_fft, &mut r);
let inv_l = T::ONE / T::from_usize(l);
for k in 0..self.m {
let r_k = Complex::<T>::new(r[k].re * inv_l, r[k].im * inv_l);
output[k] = r_k * self.chirp_post[k];
}
Ok(())
}
pub fn zoom_fft(n: usize, m: usize, f_start: T, f_stop: T, fs: T) -> CztResult<Self> {
if f_start >= f_stop {
return Err(CztError::InvalidParameter);
}
let two_pi = T::TWO_PI;
let m_f = T::from_usize(m);
let a = Complex::cis(two_pi * f_start / fs);
let bw = f_stop - f_start;
let w = Complex::cis(-two_pi * bw / (m_f * fs));
Self::new(n, m, a, w)
}
#[must_use]
pub fn n(&self) -> usize {
self.n
}
#[must_use]
pub fn m(&self) -> usize {
self.m
}
#[must_use]
pub fn l(&self) -> usize {
self.l
}
}