diff --git a/README.md b/README.md index 5829b4d..ed01551 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,21 @@ The API is designed to be as similar as possible to RustFFT. Using this library instead of RustFFT directly avoids the need of converting real-valued data to complex before performing a FFT. If the length is even, it also enables faster computations by using a complex FFT of half the length. -It then packs a 2N long real vector into an N long complex vector, which is transformed using a standard FFT. -The FFT result is then post-processed to give only the first half of the complex spectrum, as an N+1 long complex vector. +It then packs a 2N long real signal into an N long complex signal, which is transformed using a standard FFT. +The FFT result is then post-processed to give only the first half of the complex spectrum, as an N+1 long complex signal. The iFFT goes through the same steps backwards, to transform an N+1 long complex spectrum to a 2N long real result. -The speed increase compared to just converting the input to a 2N long complex vector +The speed increase compared to just converting the input to a 2N long complex signal and using a 2N long FFT depends on the length of the input data. The largest improvements are for long FFTs and for lengths over around 1000 elements there is an improvement of about a factor 2. The difference shrinks for shorter lengths, and around 30 elements there is no longer any difference. ### Why use real-to-complex FFT? #### Using a complex-to-complex FFT -A simple way to get the FFT of a real valued vector is to convert it to complex, and use a complex-to-complex FFT. +A simple way to get the FFT of a real valued signal is to convert it to complex, and use a complex-to-complex FFT. -Let's assume `x` is a 6 element long real vector: +Let's assume `x` is a 6 element long real signal: ``` x = [x0r, x1r, x2r, x3r, x4r, x5r] ``` @@ -87,7 +87,7 @@ The results are printed while running, and are compiled into an html report cont To view, open `target/criterion/report/index.html` in a browser. ### Example -Transform a vector, and then inverse transform the result. +Transform a signal, and then inverse transform the result. ```rust use realfft::RealFftPlanner; use rustfft::num_complex::Complex; @@ -100,7 +100,7 @@ let mut real_planner = RealFftPlanner::::new(); // create a FFT let r2c = real_planner.plan_fft_forward(length); -// make input and output vectors +// make input and output signals let mut indata = r2c.make_input_vec(); let mut spectrum = r2c.make_output_vec(); @@ -111,7 +111,7 @@ assert_eq!(spectrum.len(), length/2+1); // Forward transform the input data r2c.process(&mut indata, &mut spectrum).unwrap(); -// create an iFFT and an output vector +// create an iFFT and an output signal let c2r = real_planner.plan_fft_inverse(length); let mut outdata = c2r.make_output_vec(); assert_eq!(outdata.len(), length); diff --git a/examples/concurrency.rs b/examples/concurrency.rs index ccc2997..38e438b 100644 --- a/examples/concurrency.rs +++ b/examples/concurrency.rs @@ -13,8 +13,8 @@ fn main() { .map(|_| { let fft_copy = Arc::clone(&fft); thread::spawn(move || { - let mut data = fft_copy.make_input_vec(); - let mut output = fft_copy.make_output_vec(); + let mut data = fft_copy.make_input_buffer(); + let mut output = fft_copy.make_output_buffer(); fft_copy.process(&mut data, &mut output).unwrap(); }) }) diff --git a/src/lib.rs b/src/lib.rs index e25bd5f..b0fccbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,21 +5,21 @@ //! //! Using this library instead of RustFFT directly avoids the need of converting real-valued data to complex before performing a FFT. //! If the length is even, it also enables faster computations by using a complex FFT of half the length. -//! It then packs a 2N long real vector into an N long complex vector, which is transformed using a standard FFT. -//! The FFT result is then post-processed to give only the first half of the complex spectrum, as an N+1 long complex vector. +//! It then packs a 2N long real signal into an N long complex signal, which is transformed using a standard FFT. +//! The FFT result is then post-processed to give only the first half of the complex spectrum, as an N+1 long complex signal. //! //! The iFFT goes through the same steps backwards, to transform an N+1 long complex spectrum to a 2N long real result. //! -//! The speed increase compared to just converting the input to a 2N long complex vector +//! The speed increase compared to just converting the input to a 2N long complex signal //! and using a 2N long FFT depends on the length of the input data. //! The largest improvements are for long FFTs and for lengths over around 1000 elements there is an improvement of about a factor 2. //! The difference shrinks for shorter lengths, and around 30 elements there is no longer any difference. //! //! ## Why use real-to-complex FFT? //! ### Using a complex-to-complex FFT -//! A simple way to get the FFT of a real valued vector is to convert it to complex, and using a complex-to-complex FFT. +//! A simple way to get the FFT of a real valued signal is to convert it to complex, and using a complex-to-complex FFT. //! -//! Let's assume `x` is a 6 element long real vector: +//! Let's assume `x` is a 6 element long real signal: //! ```text //! x = [x0r, x1r, x2r, x3r, x4r, x5r] //! ``` @@ -87,7 +87,7 @@ //! To view, open `target/criterion/report/index.html` in a browser. //! //! ## Example -//! Transform a vector, and then inverse transform the result. +//! Transform a signal, and then inverse transform the result. //! ``` //! use realfft::RealFftPlanner; //! use rustfft::num_complex::Complex; @@ -100,9 +100,9 @@ //! //! // create a FFT //! let r2c = real_planner.plan_fft_forward(length); -//! // make input and output vectors -//! let mut indata = r2c.make_input_vec(); -//! let mut spectrum = r2c.make_output_vec(); +//! // make input and output buffers +//! let mut indata = r2c.make_input_buffer(); +//! let mut spectrum = r2c.make_output_buffer(); //! //! // Are they the length we expect? //! assert_eq!(indata.len(), length); @@ -111,9 +111,9 @@ //! // Forward transform the input data //! r2c.process(&mut indata, &mut spectrum).unwrap(); //! -//! // create an iFFT and an output vector +//! // create an iFFT and an output buffer //! let c2r = real_planner.plan_fft_inverse(length); -//! let mut outdata = c2r.make_output_vec(); +//! let mut outdata = c2r.make_output_buffer(); //! assert_eq!(outdata.len(), length); //! //! c2r.process(&mut spectrum, &mut outdata).unwrap(); @@ -247,19 +247,19 @@ pub struct ComplexToRealEven { scratch_len: usize, } -/// An FFT that takes a real-valued input vector of length 2*N and transforms it to a complex +/// An FFT that takes a real-valued input signal of length 2*N and transforms it to a complex /// spectrum of length N+1. #[allow(clippy::len_without_is_empty)] pub trait RealToComplex: Sync + Send { - /// Transform a vector of N real-valued samples, storing the result in the N/2+1 (with N/2 rounded down) element long complex output vector. + /// Transform a signal of N real-valued samples, storing the result in the N/2+1 (with N/2 rounded down) element long complex output signal. /// The input buffer is used as scratch space, so the contents of input should be considered garbage after calling. /// It also allocates additional scratch space as needed. /// An error is returned if any of the given slices has the wrong length. fn process(&self, input: &mut [T], output: &mut [Complex]) -> Res<()>; - /// Transform a vector of N real-valued samples, storing the result in the N/2+1 (with N/2 rounded down) element long complex output vector. + /// Transform a signal of N real-valued samples, storing the result in the N/2+1 (with N/2 rounded down) element long complex output signal. /// The input buffer is used as scratch space, so the contents of input should be considered garbage after calling. - /// It also uses the provided scratch vector instead of allocating, which will be faster if it is called more than once. + /// It also uses the provided scratch buffer instead of allocating, which will be faster if it is called more than once. /// An error is returned if any of the given slices has the wrong length. fn process_with_scratch( &self, @@ -274,17 +274,17 @@ pub trait RealToComplex: Sync + Send { /// Get the number of points that this FFT can process. fn len(&self) -> usize; - /// Convenience method to make an input vector of the right type and length. - fn make_input_vec(&self) -> Vec; + /// Convenience method to make an input buffer of the right type and length. + fn make_input_buffer(&self) -> Box<[T]>; - /// Convenience method to make an output vector of the right type and length. - fn make_output_vec(&self) -> Vec>; + /// Convenience method to make an output buffer of the right type and length. + fn make_output_buffer(&self) -> Box<[Complex]>; - /// Convenience method to make a scratch vector of the right type and length. - fn make_scratch_vec(&self) -> Vec>; + /// Convenience method to make a scratch buffer of the right type and length. + fn make_scratch_buffer(&self) -> Box<[Complex]>; } -/// An FFT that takes a complex-valued input vector of length N+1 and transforms it to a complex +/// An FFT that takes a complex-valued input signal of length N+1 and transforms it to a complex /// spectrum of length 2*N. #[allow(clippy::len_without_is_empty)] pub trait ComplexToReal: Sync + Send { @@ -299,7 +299,7 @@ pub trait ComplexToReal: Sync + Send { /// Transform a complex spectrum of N/2+1 (with N/2 rounded down) values and store the real result in the 2*N long output. /// The input buffer is used as scratch space, so the contents of input should be considered garbage after calling. - /// It also uses the provided scratch vector instead of allocating, which will be faster if it is called more than once. + /// It also uses the provided scratch buffer instead of allocating, which will be faster if it is called more than once. /// An error is returned if any of the given slices has the wrong length. /// If the input data is invalid, meaning that one of the positions that should contain a zero holds a different value, /// the transform is still performed. The function then returns an `FftError::InputValues` error to tell that the @@ -317,14 +317,14 @@ pub trait ComplexToReal: Sync + Send { /// Get the number of points that this FFT can process. fn len(&self) -> usize; - /// Convenience method to make an input vector of the right type and length. - fn make_input_vec(&self) -> Vec>; + /// Convenience method to make an input buffer of the right type and length. + fn make_input_buffer(&self) -> Box<[Complex]>; - /// Convenience method to make an output vector of the right type and length. - fn make_output_vec(&self) -> Vec; + /// Convenience method to make an output buffer of the right type and length. + fn make_output_buffer(&self) -> Box<[T]>; - /// Convenience method to make a scratch vector of the right type and length. - fn make_scratch_vec(&self) -> Vec>; + /// Convenience method to make a scratch buffer of the right type and length. + fn make_scratch_buffer(&self) -> Box<[Complex]>; } fn zip3(a: A, b: B, c: C) -> impl Iterator @@ -416,18 +416,18 @@ impl RealToComplexOdd { } impl RealToComplex for RealToComplexOdd { - /// Transform a vector of N real-valued samples, storing the result in the N/2+1 (with N/2 rounded down) element long complex output vector. + /// Transform a signal of N real-valued samples, storing the result in the N/2+1 (with N/2 rounded down) element long complex output signal. /// The input buffer is used as scratch space, so the contents of input should be considered garbage after calling. /// It also allocates additional scratch space as needed. /// An error is returned if any of the given slices has the wrong length. fn process(&self, input: &mut [T], output: &mut [Complex]) -> Res<()> { - let mut scratch = self.make_scratch_vec(); + let mut scratch = self.make_scratch_buffer(); self.process_with_scratch(input, output, &mut scratch) } - /// Transform a vector of N real-valued samples, storing the result in the N/2+1 (with N/2 rounded down) element long complex output vector. + /// Transform a signal of N real-valued samples, storing the result in the N/2+1 (with N/2 rounded down) element long complex output signal. /// The input buffer is used as scratch space, so the contents of input should be considered garbage after calling. - /// It also uses the provided scratch vector instead of allocating, which will be faster if it is called more than once. + /// It also uses the provided scratch buffer instead of allocating, which will be faster if it is called more than once. /// An error is returned if any of the given slices has the wrong length. fn process_with_scratch( &self, @@ -467,16 +467,16 @@ impl RealToComplex for RealToComplexOdd { self.length } - fn make_input_vec(&self) -> Vec { - vec![T::zero(); self.len()] + fn make_input_buffer(&self) -> Box<[T]> { + vec![T::zero(); self.len()].into_boxed_slice() } - fn make_output_vec(&self) -> Vec> { - vec![Complex::zero(); self.len() / 2 + 1] + fn make_output_buffer(&self) -> Box<[Complex]> { + vec![Complex::zero(); self.len() / 2 + 1].into_boxed_slice() } - fn make_scratch_vec(&self) -> Vec> { - vec![Complex::zero(); self.get_scratch_len()] + fn make_scratch_buffer(&self) -> Box<[Complex]> { + vec![Complex::zero(); self.get_scratch_len()].into_boxed_slice() } } @@ -508,18 +508,18 @@ impl RealToComplexEven { } impl RealToComplex for RealToComplexEven { - /// Transform a vector of N real-valued samples, storing the result in the N/2+1 element long complex output vector. + /// Transform a signal of N real-valued samples, storing the result in the N/2+1 element long complex output signal. /// The input buffer is used as scratch space, so the contents of input should be considered garbage after calling. /// It also allocates additional scratch space as needed. /// An error is returned if any of the given slices has the wrong length. fn process(&self, input: &mut [T], output: &mut [Complex]) -> Res<()> { - let mut scratch = self.make_scratch_vec(); + let mut scratch = self.make_scratch_buffer(); self.process_with_scratch(input, output, &mut scratch) } - /// Transform a vector of N real-valued samples, storing the result in the N/2+1 element long complex output vector. + /// Transform a signal of N real-valued samples, storing the result in the N/2+1 element long complex output signal. /// The input buffer is used as scratch space, so the contents of input should be considered garbage after calling. - /// It also uses the provided scratch vector instead of allocating, which will be faster if it is called more than once. + /// It also uses the provided scratch buffer instead of allocating, which will be faster if it is called more than once. /// An error is returned if any of the given slices has the wrong length. fn process_with_scratch( &self, @@ -627,16 +627,16 @@ impl RealToComplex for RealToComplexEven { self.length } - fn make_input_vec(&self) -> Vec { - vec![T::zero(); self.len()] + fn make_input_buffer(&self) -> Box<[T]> { + vec![T::zero(); self.len()].into_boxed_slice() } - fn make_output_vec(&self) -> Vec> { - vec![Complex::zero(); self.len() / 2 + 1] + fn make_output_buffer(&self) -> Box<[Complex]> { + vec![Complex::zero(); self.len() / 2 + 1].into_boxed_slice() } - fn make_scratch_vec(&self) -> Vec> { - vec![Complex::zero(); self.get_scratch_len()] + fn make_scratch_buffer(&self) -> Box<[Complex]> { + vec![Complex::zero(); self.get_scratch_len()].into_boxed_slice() } } @@ -667,13 +667,13 @@ impl ComplexToReal for ComplexToRealOdd { /// these non-zero values are ignored and the transform is still performed. /// The function then returns an `FftError::InputValues` error to tell that the result may not be correct. fn process(&self, input: &mut [Complex], output: &mut [T]) -> Res<()> { - let mut scratch = self.make_scratch_vec(); + let mut scratch = self.make_scratch_buffer(); self.process_with_scratch(input, output, &mut scratch) } /// Transform a complex spectrum of N/2+1 (with N/2 rounded down) values and store the real result in the N long output. /// The input buffer is used as scratch space, so the contents of input should be considered garbage after calling. - /// It also uses the provided scratch vector instead of allocating, which will be faster if it is called more than once. + /// It also uses the provided scratch buffer instead of allocating, which will be faster if it is called more than once. /// An error is returned if any of the given slices has the wrong length. /// If the input data is invalid, meaning that one of the positions that should contain a zero holds a different value, /// these non-zero values are ignored and the transform is still performed. @@ -735,16 +735,16 @@ impl ComplexToReal for ComplexToRealOdd { self.length } - fn make_input_vec(&self) -> Vec> { - vec![Complex::zero(); self.len() / 2 + 1] + fn make_input_buffer(&self) -> Box<[Complex]> { + vec![Complex::zero(); self.len() / 2 + 1].into_boxed_slice() } - fn make_output_vec(&self) -> Vec { - vec![T::zero(); self.len()] + fn make_output_buffer(&self) -> Box<[T]> { + vec![T::zero(); self.len()].into_boxed_slice() } - fn make_scratch_vec(&self) -> Vec> { - vec![Complex::zero(); self.get_scratch_len()] + fn make_scratch_buffer(&self) -> Box<[Complex]> { + vec![Complex::zero(); self.get_scratch_len()].into_boxed_slice() } } @@ -783,13 +783,13 @@ impl ComplexToReal for ComplexToRealEven { /// these non-zero values are ignored and the transform is still performed. /// The function then returns an `FftError::InputValues` error to tell that the result may not be correct. fn process(&self, input: &mut [Complex], output: &mut [T]) -> Res<()> { - let mut scratch = self.make_scratch_vec(); + let mut scratch = self.make_scratch_buffer(); self.process_with_scratch(input, output, &mut scratch) } /// Transform a complex spectrum of N/2+1 values and store the real result in the N long output. /// The input buffer is used as scratch space, so the contents of input should be considered garbage after calling. - /// It also uses the provided scratch vector instead of allocating, which will be faster if it is called more than once. + /// It also uses the provided scratch buffer instead of allocating, which will be faster if it is called more than once. /// An error is returned if any of the given slices has the wrong length. /// If the input data is invalid, meaning that one of the positions that should contain a zero holds a different value, /// these non-zero values are ignored and the transform is still performed. @@ -911,16 +911,16 @@ impl ComplexToReal for ComplexToRealEven { self.length } - fn make_input_vec(&self) -> Vec> { - vec![Complex::zero(); self.len() / 2 + 1] + fn make_input_buffer(&self) -> Box<[Complex]> { + vec![Complex::zero(); self.len() / 2 + 1].into_boxed_slice() } - fn make_output_vec(&self) -> Vec { - vec![T::zero(); self.len()] + fn make_output_buffer(&self) -> Box<[T]> { + vec![T::zero(); self.len()].into_boxed_slice() } - fn make_scratch_vec(&self) -> Vec> { - vec![Complex::zero(); self.get_scratch_len()] + fn make_scratch_buffer(&self) -> Box<[Complex]> { + vec![Complex::zero(); self.get_scratch_len()].into_boxed_slice() } } @@ -964,8 +964,8 @@ mod tests { for length in 1..1000 { let mut real_planner = RealFftPlanner::::new(); let c2r = real_planner.plan_fft_inverse(length); - let mut out_a = c2r.make_output_vec(); - let mut indata = c2r.make_input_vec(); + let mut out_a = c2r.make_output_buffer(); + let mut indata = c2r.make_input_buffer(); let mut rustfft_check: Vec> = vec![Complex::zero(); length]; let mut rng = rand::thread_rng(); for val in indata.iter_mut() { @@ -1013,8 +1013,8 @@ mod tests { let length = 100; let mut real_planner = RealFftPlanner::::new(); let c2r = real_planner.plan_fft_inverse(length); - let mut out_a = c2r.make_output_vec(); - let mut indata = c2r.make_input_vec(); + let mut out_a = c2r.make_output_buffer(); + let mut indata = c2r.make_input_buffer(); let mut rng = rand::thread_rng(); // Make some valid data @@ -1051,8 +1051,8 @@ mod tests { let length = 101; let mut real_planner = RealFftPlanner::::new(); let c2r = real_planner.plan_fft_inverse(length); - let mut out_a = c2r.make_output_vec(); - let mut indata = c2r.make_input_vec(); + let mut out_a = c2r.make_output_buffer(); + let mut indata = c2r.make_input_buffer(); let mut rng = rand::thread_rng(); // Make some valid data @@ -1078,8 +1078,8 @@ mod tests { for length in 1..1000 { let mut real_planner = RealFftPlanner::::new(); let r2c = real_planner.plan_fft_forward(length); - let mut out_a = r2c.make_output_vec(); - let mut indata = r2c.make_input_vec(); + let mut out_a = r2c.make_output_buffer(); + let mut indata = r2c.make_input_buffer(); let mut rng = rand::thread_rng(); for val in indata.iter_mut() { *val = rng.gen::(); @@ -1108,8 +1108,8 @@ mod tests { fn test_error() -> Result<(), Box> { let mut real_planner = RealFftPlanner::::new(); let r2c = real_planner.plan_fft_forward(100); - let mut out_a = r2c.make_output_vec(); - let mut indata = r2c.make_input_vec(); + let mut out_a = r2c.make_output_buffer(); + let mut indata = r2c.make_input_buffer(); r2c.process(&mut indata, &mut out_a)?; Ok(()) }