Skip to main content

dsp_process/
basic.rs

1use crate::{Inplace, Process, SplitInplace, SplitProcess};
2
3//////////// ELEMENTARY PROCESSORS ////////////
4
5/// Summation
6///
7/// Fan-in addition over tuples or arrays.
8///
9/// This is the standard reducer used after [`Parallel`](crate::Parallel) or
10/// [`Pair`](crate::Pair) style branching.
11#[derive(Debug, Copy, Clone, Default)]
12pub struct Add;
13impl<X: Copy, Y: core::iter::Sum<X>, const N: usize> Process<[X; N], Y> for Add {
14    fn process(&mut self, x: [X; N]) -> Y {
15        x.into_iter().sum()
16    }
17}
18impl<X0: Copy + core::ops::Add<X1, Output = Y>, X1: Copy, Y> Process<(X0, X1), Y> for Add {
19    fn process(&mut self, x: (X0, X1)) -> Y {
20        x.0 + x.1
21    }
22}
23impl<X: Copy> Inplace<X> for Add where Self: Process<X> {}
24
25/// Product
26///
27/// Fan-in multiplication over tuples or arrays.
28#[derive(Debug, Copy, Clone, Default)]
29pub struct Mul;
30impl<X: Copy, Y: core::iter::Product<X>, const N: usize> Process<[X; N], Y> for Mul {
31    fn process(&mut self, x: [X; N]) -> Y {
32        x.into_iter().product()
33    }
34}
35impl<X0: Copy + core::ops::Mul<X1, Output = Y>, X1: Copy, Y> Process<(X0, X1), Y> for Mul {
36    fn process(&mut self, x: (X0, X1)) -> Y {
37        x.0 * x.1
38    }
39}
40impl<X: Copy> Inplace<X> for Mul where Self: Process<X> {}
41
42/// Difference
43///
44/// Fan-in subtraction for pairs.
45#[derive(Debug, Copy, Clone, Default)]
46pub struct Sub;
47impl<X: Copy + core::ops::Sub<Output = Y>, Y> Process<[X; 2], Y> for Sub {
48    fn process(&mut self, x: [X; 2]) -> Y {
49        x[0] - x[1]
50    }
51}
52impl<X0: Copy + core::ops::Sub<X1, Output = Y>, X1: Copy, Y> Process<(X0, X1), Y> for Sub {
53    fn process(&mut self, x: (X0, X1)) -> Y {
54        x.0 - x.1
55    }
56}
57impl<X: Copy> Inplace<X> for Sub where Self: Process<X> {}
58
59/// Sum and difference of a two-element input.
60///
61/// This is the classic butterfly primitive used in lattice, complementary, and
62/// polyphase-style constructions. It is also the unnormalized 2-point
63/// Hadamard/Haar transform; apply any desired normalization outside the
64/// primitive.
65///
66/// # Examples
67///
68/// ```rust
69/// use dsp_process::{Butterfly, Process};
70///
71/// assert_eq!(Butterfly.process([4, 1]), [5, 3]);
72/// ```
73#[derive(Debug, Copy, Clone, Default)]
74pub struct Butterfly;
75impl<X: Copy + core::ops::Add<Output = Y> + core::ops::Sub<Output = Y>, Y> Process<[X; 2], [Y; 2]>
76    for Butterfly
77{
78    fn process(&mut self, x: [X; 2]) -> [Y; 2] {
79        [x[0] + x[1], x[0] - x[1]]
80    }
81}
82
83impl<X: Copy> Inplace<X> for Butterfly where Self: Process<X> {}
84
85/// Identity stage and simple fan-out/fan-in adapter.
86///
87/// Besides the usual `T -> T` identity, this type also provides several useful
88/// shape conversions such as scalar fan-out to arrays or tuples.
89#[derive(Debug, Copy, Clone, Default)]
90pub struct Identity;
91impl<T: Copy> Process<T> for Identity {
92    fn process(&mut self, x: T) -> T {
93        x
94    }
95
96    fn block(&mut self, x: &[T], y: &mut [T]) {
97        y.copy_from_slice(x);
98    }
99}
100
101/// NOP
102impl<T: Copy> Inplace<T> for Identity {
103    fn inplace(&mut self, _xy: &mut [T]) {}
104}
105
106/// Fan out
107impl<X: Copy> Process<X, (X, X)> for Identity {
108    fn process(&mut self, x: X) -> (X, X) {
109        (x, x)
110    }
111}
112
113/// Fan out
114impl<X: Copy, const N: usize> Process<X, [X; N]> for Identity {
115    fn process(&mut self, x: X) -> [X; N] {
116        core::array::repeat(x)
117    }
118}
119
120/// Flatten
121impl<X: Copy> Process<[X; 1], X> for Identity {
122    fn process(&mut self, x: [X; 1]) -> X {
123        x[0]
124    }
125}
126
127/// Inversion using `Neg`.
128#[derive(Debug, Copy, Clone, Default)]
129pub struct Neg;
130impl<T: Copy + core::ops::Neg<Output = T>> Process<T> for Neg {
131    fn process(&mut self, x: T) -> T {
132        x.neg()
133    }
134}
135
136impl<T: Copy> Inplace<T> for Neg where Self: Process<T> {}
137
138/// Addition of a constant in split form.
139///
140/// See also [`Gain`] and [`Clamp`].
141#[derive(Debug, Clone, Copy, Default)]
142#[repr(transparent)]
143pub struct Offset<T>(pub T);
144
145/// Offset using `Add`
146impl<X: Copy + core::ops::Add<T, Output = Y>, Y, T: Copy> SplitProcess<X, Y> for Offset<T> {
147    fn process(&self, _state: &mut (), x: X) -> Y {
148        x + self.0
149    }
150}
151
152impl<X: Copy, T> SplitInplace<X> for Offset<T> where Self: SplitProcess<X> {}
153
154/// Multiplication by a constant in split form.
155#[derive(Debug, Clone, Copy, Default)]
156#[repr(transparent)]
157pub struct Gain<T>(pub T);
158
159/// Gain using `Mul`
160impl<X: Copy + core::ops::Mul<T, Output = Y>, Y, T: Copy> SplitProcess<X, Y> for Gain<T> {
161    fn process(&self, _state: &mut (), x: X) -> Y {
162        x * self.0
163    }
164}
165
166impl<X: Copy, T> SplitInplace<X> for Gain<T> where Self: SplitProcess<X> {}
167
168/// Clamp between min and max using `Ord`
169///
170/// This is a split-state combinator because the bounds are immutable
171/// configuration.
172#[derive(Debug, Copy, Clone, Default)]
173pub struct Clamp<T> {
174    /// Lowest output value
175    pub min: T,
176    /// Highest output value
177    pub max: T,
178}
179
180impl<T: Copy + Ord> SplitProcess<T> for Clamp<T> {
181    fn process(&self, _state: &mut (), x: T) -> T {
182        x.clamp(self.min, self.max)
183    }
184}
185
186impl<T: Copy> SplitInplace<T> for Clamp<T> where Self: SplitProcess<T> {}
187
188/// Select or place one sample in a fixed-size rate-conversion slot.
189///
190/// `Rate<I>` keeps or places the sample in slot `I`.
191///
192/// As `[X; N] -> X`, it returns `x[I]`. As `X -> [X; N]`, it emits the sample
193/// in slot `I` and fills the rest with `Default::default()`.
194///
195/// This is a stateless shape-conversion primitive. It does not track stream
196/// phase over time, so it is not a substitute for [`crate::Downsample`] or
197/// [`crate::Decimator`].
198#[derive(Debug, Copy, Clone, Default)]
199pub struct Rate<const I: usize>;
200impl<X: Copy, const I: usize, const N: usize> Process<[X; N], X> for Rate<I> {
201    fn process(&mut self, x: [X; N]) -> X {
202        const { assert!(I < N) }
203        x[I]
204    }
205}
206
207impl<X: Copy + Default, const I: usize, const N: usize> Process<X, [X; N]> for Rate<I> {
208    fn process(&mut self, x: X) -> [X; N] {
209        const { assert!(I < N) }
210        let mut y = [X::default(); N];
211        y[I] = x;
212        y
213    }
214}
215impl<X: Copy, const I: usize> Inplace<X> for Rate<I> where Self: Process<X> {}
216
217/// Fixed-size sample buffer used as a delay line or chunk accumulator.
218///
219/// The exact behavior depends on the chosen `Process` implementation:
220///
221/// * `X -> X`: delay line
222/// * `X -> Option<[X; N]>`: buffer into chunks
223/// * `Option<[X; N]> -> X`: stream samples out of a chunk buffer
224#[derive(Debug, Copy, Clone, Default)]
225pub struct Buffer<B> {
226    buffer: B,
227    idx: usize,
228}
229
230impl<X, const N: usize> Buffer<[X; N]> {
231    /// Whether the chunk buffer is currently empty.
232    ///
233    /// For delay-line use this only reports whether the write index is at zero;
234    /// it does not indicate whether previous samples are all defaults.
235    #[must_use]
236    pub fn is_empty(&self) -> bool {
237        self.idx == 0
238    }
239}
240
241/// Delay line
242///
243/// This is the simplest stateful FIFO in the crate.
244impl<X: Copy, const N: usize> Process<X> for Buffer<[X; N]> {
245    fn process(&mut self, x: X) -> X {
246        const { assert!(N > 0) }
247        let y = core::mem::replace(&mut self.buffer[self.idx], x);
248        self.idx = (self.idx + 1) % N;
249        y
250    }
251
252    fn block(&mut self, x: &[X], y: &mut [X]) {
253        const { assert!(N > 0) }
254        debug_assert_eq!(x.len(), y.len());
255        let mut x = x;
256        let mut y = y;
257
258        if self.idx != 0 {
259            let n = x.len().min(N - self.idx);
260            let (xh, xr) = x.split_at(n);
261            let (yh, yr) = y.split_at_mut(n);
262            yh.copy_from_slice(&self.buffer[self.idx..self.idx + n]);
263            self.buffer[self.idx..self.idx + n].copy_from_slice(xh);
264            self.idx = (self.idx + n) % N;
265            if self.idx != 0 {
266                return;
267            }
268            x = xr;
269            y = yr;
270        }
271
272        let (xc, xt) = x.as_chunks::<N>();
273        let (yc, yt) = y.as_chunks_mut::<N>();
274        for (xc, yc) in xc.iter().zip(yc) {
275            *yc = self.buffer;
276            self.buffer = *xc;
277        }
278
279        yt.copy_from_slice(&self.buffer[..xt.len()]);
280        self.buffer[..xt.len()].copy_from_slice(xt);
281        self.idx = xt.len();
282    }
283}
284
285impl<X: Copy, const N: usize> Inplace<X> for Buffer<[X; N]> {
286    fn inplace(&mut self, xy: &mut [X]) {
287        const { assert!(N > 0) }
288        let mut xy = xy;
289
290        if self.idx != 0 {
291            let n = xy.len().min(N - self.idx);
292            let (head, rest) = xy.split_at_mut(n);
293            for (xy, buf) in head
294                .iter_mut()
295                .zip(self.buffer[self.idx..self.idx + n].iter_mut())
296            {
297                core::mem::swap(xy, buf);
298            }
299            self.idx = (self.idx + n) % N;
300            if self.idx != 0 {
301                return;
302            }
303            xy = rest;
304        }
305
306        let (chunks, tail) = xy.as_chunks_mut::<N>();
307        for chunk in chunks {
308            core::mem::swap(chunk, &mut self.buffer);
309        }
310
311        let n = tail.len();
312        for (xy, buf) in tail.iter_mut().zip(self.buffer[..n].iter_mut()) {
313            core::mem::swap(xy, buf);
314        }
315        self.idx = n;
316    }
317}
318
319impl<X: Copy, const N: usize, const M: usize> Process<[X; M]> for Buffer<[X; N]> {
320    fn process(&mut self, x: [X; M]) -> [X; M] {
321        const { assert!(N > 0) }
322        let mut y = x;
323        <Self as Process<X>>::block(self, &x, &mut y);
324        y
325    }
326}
327
328/// Buffer into chunks
329///
330/// Returns `Some(chunk)` every `N` samples and `None` otherwise.
331impl<X: Copy, const N: usize> Process<X, Option<[X; N]>> for Buffer<[X; N]> {
332    fn process(&mut self, x: X) -> Option<[X; N]> {
333        const { assert!(N > 0) }
334        self.buffer[self.idx] = x;
335        self.idx += 1;
336        (self.idx == N).then(|| {
337            self.idx = 0;
338            self.buffer
339        })
340    }
341
342    fn block(&mut self, x: &[X], y: &mut [Option<[X; N]>]) {
343        const { assert!(N > 0) }
344        debug_assert_eq!(x.len(), y.len());
345        let mut x = x;
346        let mut y = y;
347
348        if self.idx != 0 {
349            let n = x.len().min(N - self.idx);
350            let (xh, xr) = x.split_at(n);
351            let (yh, yr) = y.split_at_mut(n);
352            self.buffer[self.idx..self.idx + n].copy_from_slice(xh);
353            yh.fill(None);
354            self.idx += n;
355            if self.idx == N {
356                self.idx = 0;
357                yh[n - 1] = Some(self.buffer);
358            } else {
359                return;
360            }
361            x = xr;
362            y = yr;
363        }
364
365        let (xc, xt) = x.as_chunks::<N>();
366        let (yc, yt) = y.as_chunks_mut::<N>();
367        for (xc, yc) in xc.iter().zip(yc) {
368            let (yl, yr) = yc.split_last_mut().unwrap();
369            yr.fill(None);
370            *yl = Some(*xc);
371        }
372
373        self.buffer[..xt.len()].copy_from_slice(xt);
374        yt.fill(None);
375        self.idx = xt.len();
376    }
377}
378
379impl<X: Copy, const N: usize> Process<Option<[X; N]>, X> for Buffer<[X; N]> {
380    fn process(&mut self, x: Option<[X; N]>) -> X {
381        const { assert!(N > 0) }
382        if let Some(x) = x {
383            self.buffer = x;
384            self.idx = 0;
385        } else {
386            self.idx += 1;
387        }
388        self.buffer[self.idx]
389    }
390
391    fn block(&mut self, x: &[Option<[X; N]>], y: &mut [X]) {
392        const { assert!(N > 0) }
393        debug_assert_eq!(x.len(), y.len());
394        let mut i = 0;
395        while i < x.len() {
396            if let Some(buf) = x[i] {
397                self.buffer = buf;
398                self.idx = 0;
399                y[i] = self.buffer[0];
400                i += 1;
401                continue;
402            }
403
404            let run = x[i..]
405                .iter()
406                .position(Option::is_some)
407                .unwrap_or(x.len() - i);
408            y[i..i + run].copy_from_slice(&self.buffer[self.idx + 1..self.idx + 1 + run]);
409            self.idx += run;
410            i += run;
411        }
412    }
413}
414
415/// Nyquist zero with gain 2
416///
417/// This is inefficient for large differential delays
418///
419/// See also [`Comb`] for the corresponding difference operator.
420#[derive(Debug, Copy, Clone, Default)]
421pub struct Nyquist<X>(
422    /// Previous input
423    pub X,
424);
425impl<X: Copy + core::ops::Add<X, Output = Y>, Y, const N: usize> Process<X, Y> for Nyquist<[X; N]> {
426    fn process(&mut self, x: X) -> Y {
427        const { assert!(N > 0) }
428        let y = x + self.0[N - 1];
429        self.0.copy_within(..N - 1, 1);
430        self.0[0] = x;
431        y
432    }
433
434    fn block(&mut self, x: &[X], y: &mut [Y]) {
435        const { assert!(N > 0) }
436        debug_assert_eq!(x.len(), y.len());
437        let n = x.len().min(N);
438        let (xh, xt) = x.split_at(n);
439        let (yh, yt) = y.split_at_mut(n);
440
441        for ((xi, yi), s) in xh
442            .iter()
443            .zip(yh.iter_mut())
444            .zip(self.0[N - n..].iter().rev())
445        {
446            *yi = *xi + *s;
447        }
448        for ((xi, yi), xp) in xt.iter().zip(yt.iter_mut()).zip(x.iter()) {
449            *yi = *xi + *xp;
450        }
451
452        if x.len() >= N {
453            for (dst, src) in self.0.iter_mut().zip(x[x.len() - N..].iter().rev()) {
454                *dst = *src;
455            }
456        } else {
457            self.0.copy_within(..N - x.len(), x.len());
458            for (dst, src) in self.0.iter_mut().zip(x.iter().rev()) {
459                *dst = *src;
460            }
461        }
462    }
463
464    // TODO: inplace()
465}
466impl<X: Copy, const N: usize> Inplace<X> for Nyquist<[X; N]> where Self: Process<X> {}
467
468/// Running sum / discrete-time integrator.
469#[derive(Debug, Copy, Clone, Default)]
470pub struct Integrator<Y>(
471    /// Current integrator value
472    pub Y,
473);
474impl<X: Copy, Y: core::ops::AddAssign<X> + Copy> Process<X, Y> for Integrator<Y> {
475    fn process(&mut self, x: X) -> Y {
476        self.0 += x;
477        self.0
478    }
479}
480impl<X: Copy> Inplace<X> for Integrator<X> where Self: Process<X> {}
481
482/// Comb (derivative)
483///
484/// Bad for large delays
485///
486/// See also [`Nyquist`] for the sum form.
487#[derive(Debug, Copy, Clone, Default)]
488pub struct Comb<X>(
489    /// Delay line
490    pub X,
491);
492impl<X: Copy + core::ops::Sub<X, Output = Y>, Y, const N: usize> Process<X, Y> for Comb<[X; N]> {
493    fn process(&mut self, x: X) -> Y {
494        const { assert!(N > 0) }
495        let y = x - self.0[N - 1];
496        self.0.copy_within(..N - 1, 1);
497        self.0[0] = x;
498        y
499    }
500
501    fn block(&mut self, x: &[X], y: &mut [Y]) {
502        const { assert!(N > 0) }
503        debug_assert_eq!(x.len(), y.len());
504        let n = x.len().min(N);
505        let (xh, xt) = x.split_at(n);
506        let (yh, yt) = y.split_at_mut(n);
507
508        for ((xi, yi), s) in xh
509            .iter()
510            .zip(yh.iter_mut())
511            .zip(self.0[N - n..].iter().rev())
512        {
513            *yi = *xi - *s;
514        }
515        for ((xi, yi), xp) in xt.iter().zip(yt.iter_mut()).zip(x.iter()) {
516            *yi = *xi - *xp;
517        }
518
519        if x.len() >= N {
520            for (dst, src) in self.0.iter_mut().zip(x[x.len() - N..].iter().rev()) {
521                *dst = *src;
522            }
523        } else {
524            self.0.copy_within(..N - x.len(), x.len());
525            for (dst, src) in self.0.iter_mut().zip(x.iter().rev()) {
526                *dst = *src;
527            }
528        }
529    }
530
531    // TODO: inplace()
532}
533impl<X: Copy, const N: usize> Inplace<X> for Comb<[X; N]> where Self: Process<X> {}