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}