#fft #dft #discrete #fourier #const-generics

easyfft

A library crate providing an easy FFT API for arrays and slices

11 releases

0.4.1 Feb 7, 2024
0.4.0 Aug 29, 2023
0.3.5 Jan 12, 2023
0.3.2 Dec 26, 2022
0.1.0 Sep 4, 2022

#89 in Math

Download history 121/week @ 2023-10-24 128/week @ 2023-10-31 89/week @ 2023-11-07 116/week @ 2023-11-14 143/week @ 2023-11-21 282/week @ 2023-11-28 228/week @ 2023-12-05 352/week @ 2023-12-12 141/week @ 2023-12-19 142/week @ 2023-12-26 100/week @ 2024-01-02 146/week @ 2024-01-09 136/week @ 2024-01-16 72/week @ 2024-01-23 111/week @ 2024-01-30 103/week @ 2024-02-06

456 downloads per month
Used in 2 crates

MIT/Apache

55KB
818 lines

easyfft

A Rust library crate providing an FFT API for arrays and slices. This crate wraps the rustfft and realfft crates that does the heavy lifting behind the scenes.

What does easyfft actually do?

  • Manages the FftPlanner and scratch buffers for you in thread local storage.
  • Exposes extension traits on arrays and slices for terse and ergonomic expression of the underlying logic.
  • Implementation on arrays has the signal sizes verified at compile time using the type checker.
  • Size calculations for real-valued signals are done for you so you can't mess it up.
  • Wrapper scructs (RealDft and DynRealDft), that forces you to call the correct version of real_ifft.

Example

The nightly dependent features are commented out.

// NOTE: Only required for real arrays
// #![allow(incomplete_features)]
// #![feature(generic_const_exprs)]

use easyfft::prelude::*;
use easyfft::num_complex::Complex;

// Complex arrays
let complex_array = [Complex::new(1.0, 0.0); 100];
let complex_array_dft = complex_array.fft();
let _complex_array_dft_idft = complex_array_dft.ifft();

// Real to complex arrays
let real_array = [1.0; 100];
let _real_array_dft = real_array.fft();

// // Real arrays
// let real_array = [1.0; 100];
// let real_array_dft = real_array.real_fft();
// let _real_array_dft_idft = real_array_dft.real_ifft();

// Complex slices
let complex_slice: &[_] = &[Complex::new(1.0, 0.0); 100];
let complex_slice_dft = complex_slice.fft();
let _complex_slice_dft_idft = complex_slice_dft.ifft();

// Real to complex slices
let real_slice: &[_] = &[1.0; 100];
let _real_slice_dft = real_slice.fft();

// Real slices
let real_slice: &[_] = &[1.0; 100];
let real_slice_dft = real_slice.real_fft();
let _real_slice_dft_idft = real_slice_dft.real_ifft();

// In-place mutation on complex -> complex transforms
let mut complex_slice = [Complex::new(1.0, 0.0); 100];
complex_slice.fft_mut();
complex_slice.ifft_mut();
let mut complex_array = [Complex::new(1.0, 0.0); 100];
complex_array.fft_mut();
complex_array.ifft_mut();

Current limitations

  • The const-realfft feature requires the nightly compiler because it depends on the generic_const_exprs feature.
  • There are no methods for in-place mutation for complex -> real or real -> complex transforms.
  • easyfft stores the FftPlanner and scratch buffer in thread local storage. This means IT'S NOT APPROPRIATE FOR APPLICATIONS THAT SPIN UP A NEW THREAD FOR EACH FFT OPERATION. It will work, it'll just reduce performance.

⚠️ SCALING ⚠️

Like rustfft and realfft, easyfft does NOT rescale output! This is intentional and done for performance reasons. Many applications do an fft, manipulate the data, and do an ifft before actually analysing the data. The two normalisation steps (1/len().sqrt()) can be combined into a single step 1/len(), halving the number of expensive division instructions.

The fallible feature

The DynRealDft struct has some associated operations which can panic. This is because the rust language does not have the ability to encode properties of the length of slices in the type system. This might become possible in the future if the rust team manages to extend const generics to fully fledged dependent types. For now, we're limited to using arrays where we can ensure these properties. If safety is your primary concern I recommend you take a step back and consider if you REALLY need to work with slices instead of arrays. Many applications can get away with knowing the size of their signal at compile time. You can opt out of these panic-able operations by removing the fallible feature flag, which is enabled by default.

Why a prelude-glob import?

I generally don't like glob imports because it makes code harder to reason about. Explicit imports means you can easily search for the items and see which packages they come from. I believe there is an exception: Traits. Traits don't allow you to do textual matching to figure out which trait is being used and which package it comes from. Since it's already a bit implicit and convoluted, you may aswell get the benefit of importing all of them in a single short line:

use easyfft::prelude::*;

For this reason, easyfft only exposes Traits via the prelude module.

Dependencies

~3.5–10MB
~78K SLoC