use crate::error::WasmError;
use wasm_bindgen::prelude::*;
fn parse_interleaved_complex(
data: &[f64],
) -> Result<Vec<scirs2_core::numeric::Complex64>, WasmError> {
if !data.len().is_multiple_of(2) {
return Err(WasmError::InvalidParameter(
"Interleaved complex data must have an even number of elements (real/imag pairs)"
.to_string(),
));
}
let complex_vec: Vec<scirs2_core::numeric::Complex64> = data
.chunks_exact(2)
.map(|pair| scirs2_core::numeric::Complex64::new(pair[0], pair[1]))
.collect();
Ok(complex_vec)
}
fn complex_to_interleaved(data: &[scirs2_core::numeric::Complex64]) -> Vec<f64> {
let mut result = Vec::with_capacity(data.len() * 2);
for c in data {
result.push(c.re);
result.push(c.im);
}
result
}
#[wasm_bindgen]
pub fn fft(data: &[f64]) -> Result<Vec<f64>, JsValue> {
let complex_input = parse_interleaved_complex(data)?;
let result = scirs2_fft::fft(&complex_input, None)
.map_err(|e| WasmError::ComputationError(format!("FFT failed: {}", e)))?;
Ok(complex_to_interleaved(&result))
}
#[wasm_bindgen]
pub fn ifft(data: &[f64]) -> Result<Vec<f64>, JsValue> {
let complex_input = parse_interleaved_complex(data)?;
let result = scirs2_fft::ifft(&complex_input, None)
.map_err(|e| WasmError::ComputationError(format!("IFFT failed: {}", e)))?;
Ok(complex_to_interleaved(&result))
}
#[wasm_bindgen]
pub fn rfft(data: &[f64]) -> Result<Vec<f64>, JsValue> {
if data.is_empty() {
return Err(WasmError::InvalidParameter("Input data cannot be empty".to_string()).into());
}
let result = scirs2_fft::rfft(data, None)
.map_err(|e| WasmError::ComputationError(format!("RFFT failed: {}", e)))?;
Ok(complex_to_interleaved(&result))
}
#[wasm_bindgen]
pub fn irfft(data: &[f64], n: usize) -> Result<Vec<f64>, JsValue> {
let complex_input = parse_interleaved_complex(data)?;
if complex_input.is_empty() {
return Err(
WasmError::InvalidParameter("Input spectrum cannot be empty".to_string()).into(),
);
}
let output_len = if n == 0 {
2 * (complex_input.len() - 1)
} else {
n
};
let result = scirs2_fft::irfft(&complex_input, Some(output_len))
.map_err(|e| WasmError::ComputationError(format!("IRFFT failed: {}", e)))?;
Ok(result)
}
#[wasm_bindgen]
pub fn fftfreq(n: usize, d: f64) -> Result<Vec<f64>, JsValue> {
if n == 0 {
return Err(WasmError::InvalidParameter(
"Number of samples (n) must be positive".to_string(),
)
.into());
}
if d <= 0.0 {
return Err(
WasmError::InvalidParameter("Sample spacing (d) must be positive".to_string()).into(),
);
}
scirs2_fft::fftfreq(n, d)
.map_err(|e| WasmError::ComputationError(format!("fftfreq failed: {}", e)).into())
}
#[wasm_bindgen]
pub fn power_spectrum(data: &[f64]) -> Result<Vec<f64>, JsValue> {
if data.is_empty() {
return Err(WasmError::InvalidParameter("Input data cannot be empty".to_string()).into());
}
let spectrum = scirs2_fft::rfft(data, None)
.map_err(|e| WasmError::ComputationError(format!("Power spectrum FFT failed: {}", e)))?;
let power: Vec<f64> = spectrum.iter().map(|c| c.norm_sqr()).collect();
Ok(power)
}
#[wasm_bindgen]
pub fn rfftfreq(n: usize, d: f64) -> Result<Vec<f64>, JsValue> {
if n == 0 {
return Err(WasmError::InvalidParameter(
"Number of samples (n) must be positive".to_string(),
)
.into());
}
if d <= 0.0 {
return Err(
WasmError::InvalidParameter("Sample spacing (d) must be positive".to_string()).into(),
);
}
scirs2_fft::rfftfreq(n, d)
.map_err(|e| WasmError::ComputationError(format!("rfftfreq failed: {}", e)).into())
}
#[wasm_bindgen]
pub fn fftshift(data: &[f64]) -> Result<Vec<f64>, JsValue> {
if data.is_empty() {
return Err(WasmError::InvalidParameter("Input data cannot be empty".to_string()).into());
}
let n = data.len();
let mid = n.div_ceil(2);
let mut result = Vec::with_capacity(n);
result.extend_from_slice(&data[mid..]);
result.extend_from_slice(&data[..mid]);
Ok(result)
}
#[wasm_bindgen]
pub fn ifftshift(data: &[f64]) -> Result<Vec<f64>, JsValue> {
if data.is_empty() {
return Err(WasmError::InvalidParameter("Input data cannot be empty".to_string()).into());
}
let n = data.len();
let mid = n / 2;
let mut result = Vec::with_capacity(n);
result.extend_from_slice(&data[mid..]);
result.extend_from_slice(&data[..mid]);
Ok(result)
}
#[wasm_bindgen]
pub fn fft_magnitude(data: &[f64]) -> Result<Vec<f64>, JsValue> {
let complex_data = parse_interleaved_complex(data)?;
let magnitudes: Vec<f64> = complex_data
.iter()
.map(|c| (c.re * c.re + c.im * c.im).sqrt())
.collect();
Ok(magnitudes)
}
#[wasm_bindgen]
pub fn fft_phase(data: &[f64]) -> Result<Vec<f64>, JsValue> {
let complex_data = parse_interleaved_complex(data)?;
let phases: Vec<f64> = complex_data.iter().map(|c| c.im.atan2(c.re)).collect();
Ok(phases)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_interleaved_complex() {
let data = [1.0, 2.0, 3.0, 4.0];
let result = parse_interleaved_complex(&data);
assert!(result.is_ok());
let complex = result.expect("should parse successfully");
assert_eq!(complex.len(), 2);
assert!((complex[0].re - 1.0).abs() < 1e-10);
assert!((complex[0].im - 2.0).abs() < 1e-10);
assert!((complex[1].re - 3.0).abs() < 1e-10);
assert!((complex[1].im - 4.0).abs() < 1e-10);
}
#[test]
fn test_parse_interleaved_complex_odd_length() {
let data = [1.0, 2.0, 3.0];
let result = parse_interleaved_complex(&data);
assert!(result.is_err());
}
#[test]
fn test_complex_to_interleaved() {
use scirs2_core::numeric::Complex64;
let complex = vec![Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)];
let result = complex_to_interleaved(&complex);
assert_eq!(result.len(), 4);
assert!((result[0] - 1.0).abs() < 1e-10);
assert!((result[1] - 2.0).abs() < 1e-10);
assert!((result[2] - 3.0).abs() < 1e-10);
assert!((result[3] - 4.0).abs() < 1e-10);
}
#[test]
fn test_fftfreq_basic() {
let result = fftfreq(8, 1.0);
assert!(result.is_ok());
let freqs = result.expect("should compute fftfreq");
assert_eq!(freqs.len(), 8);
assert!((freqs[0] - 0.0).abs() < 1e-10);
}
#[test]
fn test_fftfreq_zero_n() {
let result = fftfreq(0, 1.0);
assert!(result.is_err());
}
#[test]
fn test_fftfreq_negative_d() {
let result = fftfreq(8, -1.0);
assert!(result.is_err());
}
#[test]
fn test_rfftfreq_basic() {
let result = rfftfreq(8, 1.0);
assert!(result.is_ok());
let freqs = result.expect("should compute rfftfreq");
assert_eq!(freqs.len(), 5);
assert!((freqs[0] - 0.0).abs() < 1e-10);
}
#[test]
fn test_fftshift_even() {
let data = [0.0, 1.0, 2.0, 3.0, -4.0, -3.0, -2.0, -1.0];
let result = fftshift(&data);
assert!(result.is_ok());
let shifted = result.expect("should shift");
assert_eq!(shifted, vec![-4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]);
}
#[test]
fn test_fftshift_odd() {
let data = [0.0, 1.0, 2.0, -2.0, -1.0];
let result = fftshift(&data);
assert!(result.is_ok());
let shifted = result.expect("should shift");
assert_eq!(shifted, vec![-2.0, -1.0, 0.0, 1.0, 2.0]);
}
#[test]
fn test_ifftshift_even() {
let data = [-4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0];
let result = ifftshift(&data);
assert!(result.is_ok());
let unshifted = result.expect("should unshift");
assert_eq!(unshifted, vec![0.0, 1.0, 2.0, 3.0, -4.0, -3.0, -2.0, -1.0]);
}
#[test]
fn test_fft_magnitude() {
let data = [3.0, 4.0, 0.0, 1.0];
let result = fft_magnitude(&data);
assert!(result.is_ok());
let mags = result.expect("should compute magnitude");
assert_eq!(mags.len(), 2);
assert!((mags[0] - 5.0).abs() < 1e-10);
assert!((mags[1] - 1.0).abs() < 1e-10);
}
#[test]
fn test_fft_phase() {
let data = [1.0, 0.0, 0.0, 1.0];
let result = fft_phase(&data);
assert!(result.is_ok());
let phases = result.expect("should compute phase");
assert_eq!(phases.len(), 2);
assert!((phases[0] - 0.0).abs() < 1e-10);
assert!((phases[1] - std::f64::consts::FRAC_PI_2).abs() < 1e-10);
}
#[test]
fn test_power_spectrum_empty() {
let data: [f64; 0] = [];
let result = power_spectrum(&data);
assert!(result.is_err());
}
}