basic_dsp_vector/
lib.rs

1//! Basic digital signal processing (DSP) operations
2//!
3//! Digital signal processing based on real or complex vectors in time or frequency domain.
4//! Vectors are expected to typically have a size which is at least in the order
5//! of magnitude of a couple of thousand elements. This crate tries to balance between a clear
6//! API and performance in terms of processing speed.
7//!
8//! Take this example:
9//!
10//! ```
11//! # extern crate num_complex;
12//! # extern crate basic_dsp_vector;
13//! # use basic_dsp_vector::*;
14//! # fn main() {
15//! let mut vector1 = vec!(1.0, 2.0).to_real_time_vec();
16//! let vector2 = vec!(10.0, 11.0).to_real_time_vec();
17//! vector1.add(&vector2).expect("Ignoring error handling in examples");
18//! # }
19//!
20//! ```
21//! If `vector2` would be a complex or frequency vector then this won't compile. The type mismatch
22//! indicates that a conversation is missing and that this might be a programming mistake. This lib uses
23//! the Rust type system to catch such errors.
24//!
25//! DSP vectors are meant to integrate well with other types and so they can for example be converted from and to a Rust standard vector:
26//!
27//! ```
28//! # use std::f32;
29//! # use basic_dsp_vector::*;
30//! # use basic_dsp_vector::conv_types::*;
31//! let mut dsp_vec = vec![0.0; 1000].to_real_time_vec();
32//! let mut buffer = SingleBuffer::new();
33//! dsp_vec.interpolatei(&mut buffer, &RaisedCosineFunction::new(0.35), 2).unwrap();
34//! let vec: Vec<f64> = dsp_vec.into();
35//! assert_eq!(vec.len(), 2000);
36//! ````
37//!
38//! DSP algorithms are often executed in loops. If you work with large vectors you typically try to avoid
39//! allocating buffers in every iteration. Preallocating buffers is a common practice to safe a little time
40//! with every iteration later on, but also to avoid heap fragmentation. At the same time it's a tedious task
41//! to calculate the right buffer sizes for all operations. As an attempt to provide a more convenient solution
42//! buffer types exist which don't preallocate, but store temporary memory segments so that they can be reused in the
43//! next iteration. Here is an example:
44//!
45//! ```
46//! # use std::f32;
47//! # use basic_dsp_vector::*;
48//! let vector = vec!(1.0, 0.0, -0.5, 0.8660254, -0.5, -0.8660254).to_complex_time_vec();
49//! let mut buffer = SingleBuffer::new();
50//! let _ = vector.fft(&mut buffer);
51//! ```
52//!
53//! The vector types don't distinguish between the shapes 1xN or Nx1. This is a difference to other
54//! conventions such as in MATLAB or GNU Octave.
55//! The reason for this decision is that most operations are only defined if the shape of the
56//! vector matches. So it appears to be more practical and clearer to implement the few operations
57//! where the arguments can be of different shapes as seperate methods. The methods `mul` and `dot_product`
58//! are one example for this.
59//!
60//! The trait definitions in this lib can look complex and might be overwhelming at the beginning.
61//! There is a wide range of DSP vectors, e.g. a slice can be DSP vector, a boxed array can be a DSP vector,
62//! a standard vector can be a DSP vector and so on. This lib tries to work with all of that and tries
63//! to allow all those different DSP vector types to work together. The price for this flexibility is a more complex
64//! trait definition. As a mental model, this is what the traits are specifiying:
65//! Whenever you have a complex vector in time domain, it's binary operations will work with all other
66//! complex vectors in time domain, but not with real valued vectors or frequency domain vectors.
67//! And the type `GenDspVec` serves as wild card at compile time since it defers all checks to run time.
68
69#![cfg_attr(feature = "use_simd", feature(portable_simd))]
70#![cfg_attr(feature = "use_simd", feature(stdarch_x86_avx512))]
71
72extern crate arrayvec;
73#[cfg(feature = "use_gpu")]
74extern crate clfft;
75#[cfg(feature = "std")]
76extern crate crossbeam;
77#[cfg(feature = "std")]
78#[macro_use]
79extern crate lazy_static;
80#[cfg(feature = "std")]
81extern crate linreg;
82extern crate num_complex;
83#[cfg(feature = "std")]
84extern crate num_cpus;
85extern crate num_traits;
86#[cfg(feature = "use_gpu")]
87extern crate ocl;
88extern crate rustfft;
89#[macro_use]
90mod simd_extensions;
91pub mod conv_types;
92pub mod meta;
93mod multicore_support;
94mod vector_types;
95pub mod window_functions;
96pub use crate::multicore_support::print_calibration;
97pub use crate::multicore_support::MultiCoreSettings;
98pub use crate::vector_types::*;
99mod gpu_support;
100mod inline_vector;
101use crate::numbers::*;
102use std::ops::Range;
103
104pub mod numbers {
105    //! Traits from the `num` crate which are used inside `basic_dsp` and extensions to those traits.
106    use crate::gpu_support::{Gpu32, Gpu64, GpuFloat, GpuRegTrait};
107    use crate::simd_extensions;
108    use crate::simd_extensions::*;
109    pub use num_complex::Complex;
110    use num_traits;
111    pub use num_traits::Float;
112    pub use num_traits::Num;
113    pub use num_traits::One;
114    use rustfft;
115    use std::fmt::Debug;
116
117    /// A trait for a numeric value which at least supports a subset of the operations defined in this crate.
118    /// Can be an integer or a floating point number. In order to have support for all operations in this crate
119    /// a must implement the `RealNumber`.
120    pub trait DspNumber:
121        Num
122        + Copy
123        + Clone
124        + Send
125        + Sync
126        + ToSimd
127        + Debug
128        + num_traits::Signed
129        + num_traits::FromPrimitive
130        + rustfft::FftNum
131        + 'static
132    {
133    }
134    impl<T> DspNumber for T where
135        T: Num
136            + Copy
137            + Clone
138            + Send
139            + Sync
140            + ToSimd
141            + Debug
142            + num_traits::Signed
143            + num_traits::FromPrimitive
144            + rustfft::FftNum
145            + 'static
146    {
147    }
148
149    /// Associates a number type with a SIMD register type.
150    pub trait ToSimd: Sized + Sync + Send {
151        /// Type for the SIMD register on the CPU.
152        type RegFallback: SimdGeneric<Self>;
153        type RegSse: SimdGeneric<Self>;
154        type RegAvx: SimdGeneric<Self>;
155        type RegAvx512: SimdGeneric<Self>;
156        /// Type for the SIMD register on the GPU. Defaults to an arbitrary type if GPU support is not
157        /// compiled in.
158        type GpuReg: GpuRegTrait;
159    }
160
161    impl ToSimd for f32 {
162        type RegFallback = simd_extensions::fallback::f32x4;
163
164        #[cfg(all(feature = "use_sse2", target_feature = "sse2"))]
165        type RegSse = simd_extensions::sse::f32x4;
166        #[cfg(not(all(feature = "use_sse2", target_feature = "sse2")))]
167        type RegSse = simd_extensions::fallback::f32x4;
168
169        #[cfg(all(feature = "use_avx2"))]
170        type RegAvx = simd_extensions::avx::f32x8;
171        #[cfg(not(all(feature = "use_avx2")))]
172        type RegAvx = simd_extensions::fallback::f32x4;
173
174        #[cfg(feature = "use_avx512")]
175        type RegAvx512 = simd_extensions::avx512::f32x16;
176        #[cfg(not(feature = "use_avx512"))]
177        type RegAvx512 = simd_extensions::fallback::f32x4;
178
179        type GpuReg = Gpu32;
180    }
181
182    impl ToSimd for f64 {
183        type RegFallback = simd_extensions::fallback::f64x2;
184
185        #[cfg(all(feature = "use_sse2", target_feature = "sse2"))]
186        type RegSse = simd_extensions::sse::f64x2;
187        #[cfg(not(all(feature = "use_sse2", target_feature = "sse2")))]
188        type RegSse = simd_extensions::fallback::f64x2;
189
190        #[cfg(all(feature = "use_avx2", target_feature = "avx2"))]
191        type RegAvx = simd_extensions::avx::f64x4;
192        #[cfg(not(all(feature = "use_avx2", target_feature = "avx2")))]
193        type RegAvx = simd_extensions::fallback::f64x2;
194
195        #[cfg(feature = "use_avx512")]
196        type RegAvx512 = simd_extensions::avx512::f64x8;
197        #[cfg(not(feature = "use_avx512"))]
198        type RegAvx512 = simd_extensions::fallback::f64x2;
199
200        type GpuReg = Gpu64;
201    }
202
203    /// A real floating pointer number intended to abstract over `f32` and `f64`.
204    pub trait RealNumber: Float + DspNumber + GpuFloat + num_traits::FloatConst {}
205    impl<T> RealNumber for T where T: Float + DspNumber + GpuFloat + num_traits::FloatConst {}
206
207    /// This trait is necessary so that we can define zero for types outside this crate.
208    /// It calls the `num_traits::Zero` trait where possible.
209    pub trait Zero {
210        fn zero() -> Self;
211    }
212
213    impl<T> Zero for T
214    where
215        T: DspNumber,
216    {
217        fn zero() -> Self {
218            <Self as num_traits::Zero>::zero()
219        }
220    }
221
222    impl<T> Zero for Complex<T>
223    where
224        T: DspNumber,
225    {
226        fn zero() -> Self {
227            <Self as num_traits::Zero>::zero()
228        }
229    }
230}
231
232/// Transmutes a slice. Both S and D must be `#[repr(C)]`.
233/// The code panics if the slice has a length which doesn't allow conversion.
234fn transmute_slice<S, D>(source: &[S]) -> &[D] {
235    let len = get_target_slice_len::<S, D>(source);
236    unsafe {
237        let trans: &[D] = &*(source as *const [S] as *const [D]);
238        std::slice::from_raw_parts(trans.as_ptr(), len)
239    }
240}
241
242/// Transmutes a mutable slice. Both S and D must be `#[repr(C)]`.
243/// The code panics if the slice has a length which doesn't allow conversion.
244fn transmute_slice_mut<S, D>(source: &mut [S]) -> &mut [D] {
245    let len = get_target_slice_len::<S, D>(source);
246    unsafe {
247        let trans: &mut [D] = &mut *(source as *mut [S] as *mut [D]);
248        std::slice::from_raw_parts_mut(trans.as_mut_ptr(), len)
249    }
250}
251
252/// Helper method which finds the length of a target slice and also perform checks on the length
253fn get_target_slice_len<S, D>(source: &[S]) -> usize {
254    let to_larger_type = std::mem::size_of::<D>() >= std::mem::size_of::<S>();
255    if to_larger_type {
256        assert_eq!(std::mem::size_of::<D>() % std::mem::size_of::<S>(), 0);
257        let ratio = std::mem::size_of::<D>() / std::mem::size_of::<S>();
258        assert_eq!(source.len() % ratio, 0);
259        source.len() / ratio
260    } else {
261        assert_eq!(std::mem::size_of::<S>() % std::mem::size_of::<D>(), 0);
262        let ratio = std::mem::size_of::<S>() / std::mem::size_of::<D>();
263        source.len() * ratio
264    }
265}
266
267// Returns a complex slice from a real slice
268fn array_to_complex<T>(array: &[T]) -> &[Complex<T>] {
269    transmute_slice(array)
270}
271
272// Returns a complex slice from a real slice
273fn array_to_complex_mut<T>(array: &mut [T]) -> &mut [Complex<T>] {
274    transmute_slice_mut(array)
275}
276
277/// Copies memory inside a slice
278fn memcpy<T: Copy>(data: &mut [T], from: Range<usize>, to: usize) {
279    use std::ptr::copy;
280    assert!(from.start <= from.end);
281    assert!(from.end <= data.len());
282    assert!(to <= data.len() - (from.end - from.start));
283    unsafe {
284        let ptr = data.as_mut_ptr();
285        copy(ptr.add(from.start), ptr.add(to), from.end - from.start)
286    }
287}
288
289// Zeros a range within the slice
290fn memzero<T: Copy>(data: &mut [T], range: Range<usize>) {
291    use std::ptr::write_bytes;
292    assert!(range.start <= range.end);
293    assert!(range.end <= data.len());
294    unsafe {
295        let ptr = data.as_mut_ptr();
296        write_bytes(ptr.add(range.start), 0, range.end - range.start);
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303    use crate::simd_extensions::Simd;
304
305    #[test]
306    fn to_simd_test() {
307        // This is more a check for syntax. So if it compiles
308        // then the test already passes. The assert is then only
309        // a sanity check.
310        let reg = <f32 as ToSimd>::RegFallback::splat(1.0);
311        let sum = reg.sum_real();
312        assert!(sum > 0.0);
313    }
314}