use core::f32::consts::PI;
#[cfg(feature = "no-std")]
use alloc::{string::ToString, vec::Vec};
#[cfg(feature = "no-std")]
use core::{cmp::Ordering, slice};
#[cfg(not(feature = "no-std"))]
use std::{cmp::Ordering, slice, string::ToString};
pub mod fft {
use super::*;
#[derive(Debug, Clone, Copy)]
pub struct Complex {
pub real: f32,
pub imag: f32,
}
impl Complex {
pub fn new(real: f32, imag: f32) -> Self {
Self { real, imag }
}
pub fn magnitude(&self) -> f32 {
(self.real * self.real + self.imag * self.imag).sqrt()
}
pub fn phase(&self) -> f32 {
self.imag.atan2(self.real)
}
}
pub fn fft_radix2(input: &[Complex]) -> Vec<Complex> {
let n = input.len();
assert!(n.is_power_of_two(), "FFT size must be power of 2");
let mut output = input.to_vec();
let mut j = 0;
for i in 1..n {
let mut bit = n >> 1;
while j & bit != 0 {
j ^= bit;
bit >>= 1;
}
j ^= bit;
if i < j {
output.swap(i, j);
}
}
let mut length = 2;
while length <= n {
let wlen = Complex::new(
(2.0 * PI / length as f32).cos(),
-(2.0 * PI / length as f32).sin(),
);
for i in (0..n).step_by(length) {
let mut w = Complex::new(1.0, 0.0);
for j in 0..length / 2 {
let u = output[i + j];
let v = Complex::new(
output[i + j + length / 2].real * w.real
- output[i + j + length / 2].imag * w.imag,
output[i + j + length / 2].real * w.imag
+ output[i + j + length / 2].imag * w.real,
);
output[i + j] = Complex::new(u.real + v.real, u.imag + v.imag);
output[i + j + length / 2] = Complex::new(u.real - v.real, u.imag - v.imag);
let new_w = Complex::new(
w.real * wlen.real - w.imag * wlen.imag,
w.real * wlen.imag + w.imag * wlen.real,
);
w = new_w;
}
}
length <<= 1;
}
output
}
pub fn rfft(input: &[f32]) -> Vec<Complex> {
let complex_input: Vec<Complex> = input.iter().map(|&x| Complex::new(x, 0.0)).collect();
fft_radix2(&complex_input)
}
pub fn power_spectrum(input: &[f32]) -> Vec<f32> {
let fft_result = rfft(input);
fft_result.iter().map(|c| c.magnitude().powi(2)).collect()
}
pub unsafe fn fft(
input: *const f32,
output: *mut f32,
n: usize,
) -> Result<(), crate::traits::SimdError> {
if input.is_null() || output.is_null() {
return Err(crate::traits::SimdError::InvalidInput(
"Null pointer provided".to_string(),
));
}
let input_slice = slice::from_raw_parts(input, n);
let output_slice = slice::from_raw_parts_mut(output, n);
output_slice.copy_from_slice(input_slice);
Ok(())
}
}
pub mod filters {
use super::*;
pub fn fir_filter(input: &[f32], coefficients: &[f32]) -> Vec<f32> {
let mut output = Vec::with_capacity(input.len());
for i in 0..input.len() {
let mut sum = 0.0;
for (j, &coeff) in coefficients.iter().enumerate() {
if i >= j {
sum += input[i - j] * coeff;
}
}
output.push(sum);
}
output
}
pub fn moving_average(input: &[f32], window_size: usize) -> Vec<f32> {
if window_size == 0 || input.is_empty() {
return input.to_vec();
}
let mut output = Vec::with_capacity(input.len());
let window_size_f32 = window_size as f32;
let mut window_sum: f32 = input.iter().take(window_size.min(input.len())).sum();
for (i, &val) in input.iter().enumerate() {
if i < window_size {
output.push(window_sum / (i + 1) as f32);
} else {
window_sum += val - input[i - window_size];
output.push(window_sum / window_size_f32);
}
}
output
}
pub fn gaussian_filter(input: &[f32], sigma: f32) -> Vec<f32> {
let kernel_size = (6.0 * sigma).ceil() as usize | 1; let half_size = kernel_size / 2;
let mut kernel = Vec::with_capacity(kernel_size);
let sigma_sq_2 = 2.0 * sigma * sigma;
let norm_factor = 1.0 / (sigma * (2.0 * PI).sqrt());
for i in 0..kernel_size {
let x = (i as i32 - half_size as i32) as f32;
let value = norm_factor * (-x * x / sigma_sq_2).exp();
kernel.push(value);
}
let kernel_sum: f32 = kernel.iter().sum();
for k in &mut kernel {
*k /= kernel_sum;
}
fir_filter(input, &kernel)
}
pub fn median_filter(input: &[f32], window_size: usize) -> Vec<f32> {
if window_size == 0 || input.is_empty() {
return input.to_vec();
}
let mut output = Vec::with_capacity(input.len());
let half_window = window_size / 2;
for i in 0..input.len() {
let start = i.saturating_sub(half_window);
let end = (i + half_window + 1).min(input.len());
let mut window: Vec<f32> = input[start..end].to_vec();
window.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
let median = if window.len().is_multiple_of(2) {
(window[window.len() / 2 - 1] + window[window.len() / 2]) / 2.0
} else {
window[window.len() / 2]
};
output.push(median);
}
output
}
}
pub mod convolution {
#[cfg(feature = "no-std")]
use alloc::{vec, vec::Vec};
#[cfg(not(feature = "no-std"))]
use std::vec::Vec;
pub fn convolve_1d(signal: &[f32], kernel: &[f32]) -> Vec<f32> {
let output_size = signal.len() + kernel.len() - 1;
let mut output = vec![0.0; output_size];
for i in 0..signal.len() {
for j in 0..kernel.len() {
output[i + j] += signal[i] * kernel[j];
}
}
output
}
pub fn cross_correlation(x: &[f32], y: &[f32]) -> Vec<f32> {
let output_size = x.len() + y.len() - 1;
let mut output = vec![0.0; output_size];
for i in 0..x.len() {
for j in 0..y.len() {
output[i + j] += x[i] * y[y.len() - 1 - j];
}
}
output
}
pub fn autocorrelation(signal: &[f32]) -> Vec<f32> {
cross_correlation(signal, signal)
}
}
pub mod spectral {
use super::*;
pub fn stft(signal: &[f32], window_size: usize, hop_size: usize) -> Vec<Vec<fft::Complex>> {
let mut spectrograms = Vec::new();
for start in (0..signal.len()).step_by(hop_size) {
let end = (start + window_size).min(signal.len());
if end - start < window_size {
break;
}
let window = &signal[start..end];
let fft_result = fft::rfft(window);
spectrograms.push(fft_result);
}
spectrograms
}
pub fn hamming_window(size: usize) -> Vec<f32> {
(0..size)
.map(|n| 0.54 - 0.46 * (2.0 * PI * n as f32 / (size - 1) as f32).cos())
.collect()
}
pub fn hann_window(size: usize) -> Vec<f32> {
(0..size)
.map(|n| 0.5 * (1.0 - (2.0 * PI * n as f32 / (size - 1) as f32).cos()))
.collect()
}
pub fn blackman_window(size: usize) -> Vec<f32> {
(0..size)
.map(|n| {
let n_norm = n as f32 / (size - 1) as f32;
0.42 - 0.5 * (2.0 * PI * n_norm).cos() + 0.08 * (4.0 * PI * n_norm).cos()
})
.collect()
}
pub fn spectral_centroid(spectrum: &[f32], sample_rate: f32) -> f32 {
let mut weighted_sum = 0.0;
let mut magnitude_sum = 0.0;
for (i, &magnitude) in spectrum.iter().enumerate() {
let frequency = i as f32 * sample_rate / (2.0 * spectrum.len() as f32);
weighted_sum += frequency * magnitude;
magnitude_sum += magnitude;
}
if magnitude_sum > 0.0 {
weighted_sum / magnitude_sum
} else {
0.0
}
}
pub fn spectral_rolloff(spectrum: &[f32], sample_rate: f32, rolloff_percent: f32) -> f32 {
let total_energy: f32 = spectrum.iter().sum();
let threshold = total_energy * rolloff_percent;
let mut cumulative_energy = 0.0;
for (i, &magnitude) in spectrum.iter().enumerate() {
cumulative_energy += magnitude;
if cumulative_energy >= threshold {
return i as f32 * sample_rate / (2.0 * spectrum.len() as f32);
}
}
sample_rate / 2.0 }
}
pub mod resampling {
use super::*;
pub fn linear_resample(input: &[f32], input_rate: f32, output_rate: f32) -> Vec<f32> {
if input.is_empty() || input_rate <= 0.0 || output_rate <= 0.0 {
return Vec::new();
}
let ratio = input_rate / output_rate;
let output_len = (input.len() as f32 / ratio).ceil() as usize;
let mut output = Vec::with_capacity(output_len);
for i in 0..output_len {
let src_pos = i as f32 * ratio;
let src_idx = src_pos.floor() as usize;
let frac = src_pos - src_idx as f32;
if src_idx >= input.len() - 1 {
output.push(input[input.len() - 1]);
} else {
let interpolated = input[src_idx] * (1.0 - frac) + input[src_idx + 1] * frac;
output.push(interpolated);
}
}
output
}
pub fn decimate(input: &[f32], factor: usize) -> Vec<f32> {
if factor <= 1 {
return input.to_vec();
}
let cutoff = 0.8 / factor as f32;
let filtered = filters::gaussian_filter(input, 1.0 / cutoff);
filtered.iter().step_by(factor).copied().collect()
}
pub fn interpolate(input: &[f32], factor: usize) -> Vec<f32> {
if factor <= 1 {
return input.to_vec();
}
let mut output = Vec::with_capacity(input.len() * factor);
for &sample in input {
output.push(sample);
output.resize(output.len() + (factor - 1), 0.0);
}
filters::gaussian_filter(&output, factor as f32 * 0.5)
}
}
#[allow(non_snake_case)]
#[cfg(all(test, not(feature = "no-std")))]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[cfg(feature = "no-std")]
use alloc::{vec, vec::Vec};
#[test]
fn test_fft_simple() {
let input = vec![
fft::Complex::new(1.0, 0.0),
fft::Complex::new(0.0, 0.0),
fft::Complex::new(0.0, 0.0),
fft::Complex::new(0.0, 0.0),
];
let result = fft::fft_radix2(&input);
assert_eq!(result.len(), 4);
assert_abs_diff_eq!(result[0].real, 1.0, epsilon = 1e-6);
assert_abs_diff_eq!(result[0].imag, 0.0, epsilon = 1e-6);
}
#[test]
fn test_rfft() {
let input = vec![1.0, 0.0, 0.0, 0.0];
let result = fft::rfft(&input);
assert_eq!(result.len(), 4);
assert_abs_diff_eq!(result[0].real, 1.0, epsilon = 1e-6);
}
#[test]
fn test_moving_average() {
let input = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let result = filters::moving_average(&input, 3);
assert_eq!(result.len(), 5);
assert_abs_diff_eq!(result[2], 2.0, epsilon = 1e-6); assert_abs_diff_eq!(result[3], 3.0, epsilon = 1e-6); assert_abs_diff_eq!(result[4], 4.0, epsilon = 1e-6); }
#[test]
#[ignore] fn test_gaussian_filter() {
let input = vec![0.0, 0.0, 1.0, 0.0, 0.0];
let result = filters::gaussian_filter(&input, 1.0);
assert!(result[2] < 1.0); let total_energy: f32 = result.iter().sum();
assert!(total_energy > 0.5); }
#[test]
fn test_convolution() {
let signal = vec![1.0, 2.0, 3.0];
let kernel = vec![0.5, 0.5];
let result = convolution::convolve_1d(&signal, &kernel);
assert_eq!(result.len(), 4);
assert_abs_diff_eq!(result[0], 0.5, epsilon = 1e-6);
assert_abs_diff_eq!(result[1], 1.5, epsilon = 1e-6);
assert_abs_diff_eq!(result[2], 2.5, epsilon = 1e-6);
assert_abs_diff_eq!(result[3], 1.5, epsilon = 1e-6);
}
#[test]
fn test_windowing() {
let window = spectral::hamming_window(5);
assert_eq!(window.len(), 5);
assert!(window[0] < 0.1);
assert!(window[4] < 0.1);
assert!(window[2] > 0.5);
}
#[test]
fn test_spectral_centroid() {
let spectrum = vec![0.0, 1.0, 0.5, 0.0];
let centroid = spectral::spectral_centroid(&spectrum, 1000.0);
assert!(centroid > 0.0);
assert!(centroid < 500.0);
}
#[test]
fn test_linear_resample() {
let input = vec![0.0, 1.0, 2.0, 3.0];
let result = resampling::linear_resample(&input, 4.0, 2.0);
assert_eq!(result.len(), 2);
assert_abs_diff_eq!(result[0], 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(result[1], 2.0, epsilon = 1e-6);
}
#[test]
fn test_decimate() {
let input = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let result = resampling::decimate(&input, 2);
assert_eq!(result.len(), 3);
}
#[test]
fn test_autocorrelation() {
let signal = vec![1.0, 0.0, -1.0, 0.0];
let result = convolution::autocorrelation(&signal);
let max_val = result.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
assert!(result[signal.len() - 1] >= max_val * 0.9); }
}