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            x = xr;
266            y = yr;
267        }
268
269        let (xc, xt) = x.as_chunks::<N>();
270        let (yc, yt) = y.as_chunks_mut::<N>();
271        for (xc, yc) in xc.iter().zip(yc) {
272            *yc = self.buffer;
273            self.buffer = *xc;
274        }
275
276        yt.copy_from_slice(&self.buffer[..xt.len()]);
277        self.buffer[..xt.len()].copy_from_slice(xt);
278        self.idx = xt.len();
279    }
280}
281
282impl<X: Copy, const N: usize> Inplace<X> for Buffer<[X; N]> {
283    fn inplace(&mut self, xy: &mut [X]) {
284        const { assert!(N > 0) }
285        let mut xy = xy;
286
287        if self.idx != 0 {
288            let n = xy.len().min(N - self.idx);
289            let (head, rest) = xy.split_at_mut(n);
290            for (xy, buf) in head
291                .iter_mut()
292                .zip(self.buffer[self.idx..self.idx + n].iter_mut())
293            {
294                core::mem::swap(xy, buf);
295            }
296            self.idx = (self.idx + n) % N;
297            xy = rest;
298        }
299
300        let (chunks, tail) = xy.as_chunks_mut::<N>();
301        for chunk in chunks {
302            core::mem::swap(chunk, &mut self.buffer);
303        }
304
305        let n = tail.len();
306        for (xy, buf) in tail.iter_mut().zip(self.buffer[..n].iter_mut()) {
307            core::mem::swap(xy, buf);
308        }
309        self.idx = n;
310    }
311}
312
313impl<X: Copy, const N: usize, const M: usize> Process<[X; M]> for Buffer<[X; N]> {
314    fn process(&mut self, x: [X; M]) -> [X; M] {
315        const { assert!(N > 0) }
316        let mut y = x;
317        <Self as Process<X>>::block(self, &x, &mut y);
318        y
319    }
320}
321
322/// Buffer into chunks
323///
324/// Returns `Some(chunk)` every `N` samples and `None` otherwise.
325impl<X: Copy, const N: usize> Process<X, Option<[X; N]>> for Buffer<[X; N]> {
326    fn process(&mut self, x: X) -> Option<[X; N]> {
327        const { assert!(N > 0) }
328        self.buffer[self.idx] = x;
329        self.idx += 1;
330        (self.idx == N).then(|| {
331            self.idx = 0;
332            self.buffer
333        })
334    }
335
336    fn block(&mut self, x: &[X], y: &mut [Option<[X; N]>]) {
337        const { assert!(N > 0) }
338        debug_assert_eq!(x.len(), y.len());
339        let mut x = x;
340        let mut y = y;
341
342        if self.idx != 0 {
343            let n = x.len().min(N - self.idx);
344            let (xh, xr) = x.split_at(n);
345            let (yh, yr) = y.split_at_mut(n);
346            self.buffer[self.idx..self.idx + n].copy_from_slice(xh);
347            yh.fill(None);
348            self.idx += n;
349            if self.idx == N {
350                self.idx = 0;
351                yh[n - 1] = Some(self.buffer);
352            }
353            x = xr;
354            y = yr;
355        }
356
357        let (xc, xt) = x.as_chunks::<N>();
358        let (yc, yt) = y.as_chunks_mut::<N>();
359        for (xc, yc) in xc.iter().zip(yc) {
360            let (yl, yr) = yc.split_last_mut().unwrap();
361            yr.fill(None);
362            *yl = Some(*xc);
363        }
364
365        self.buffer[..xt.len()].copy_from_slice(xt);
366        yt.fill(None);
367        self.idx = xt.len();
368    }
369}
370
371impl<X: Copy, const N: usize> Process<Option<[X; N]>, X> for Buffer<[X; N]> {
372    fn process(&mut self, x: Option<[X; N]>) -> X {
373        const { assert!(N > 0) }
374        if let Some(x) = x {
375            self.buffer = x;
376            self.idx = 0;
377        } else {
378            self.idx += 1;
379        }
380        self.buffer[self.idx]
381    }
382
383    fn block(&mut self, x: &[Option<[X; N]>], y: &mut [X]) {
384        const { assert!(N > 0) }
385        debug_assert_eq!(x.len(), y.len());
386        let mut i = 0;
387        while i < x.len() {
388            if let Some(buf) = x[i] {
389                self.buffer = buf;
390                self.idx = 0;
391                y[i] = self.buffer[0];
392                i += 1;
393                continue;
394            }
395
396            let run = x[i..]
397                .iter()
398                .position(Option::is_some)
399                .unwrap_or(x.len() - i);
400            y[i..i + run].copy_from_slice(&self.buffer[self.idx + 1..self.idx + 1 + run]);
401            self.idx += run;
402            i += run;
403        }
404    }
405}
406
407/// Nyquist zero with gain 2
408///
409/// This is inefficient for large differential delays
410///
411/// See also [`Comb`] for the corresponding difference operator.
412#[derive(Debug, Copy, Clone, Default)]
413pub struct Nyquist<X>(
414    /// Previous input
415    pub X,
416);
417impl<X: Copy + core::ops::Add<X, Output = Y>, Y, const N: usize> Process<X, Y> for Nyquist<[X; N]> {
418    fn process(&mut self, x: X) -> Y {
419        const { assert!(N > 0) }
420        let y = x + self.0[N - 1];
421        self.0.copy_within(..N - 1, 1);
422        self.0[0] = x;
423        y
424    }
425
426    fn block(&mut self, x: &[X], y: &mut [Y]) {
427        debug_assert_eq!(x.len(), y.len());
428        let n = x.len().min(N);
429        let (xh, xt) = x.split_at(n);
430        let (yh, yt) = y.split_at_mut(n);
431
432        for ((xi, yi), s) in xh.iter().zip(yh.iter_mut()).zip(self.0[..n].iter().rev()) {
433            *yi = *xi + *s;
434        }
435        for ((xi, yi), xp) in xt.iter().zip(yt.iter_mut()).zip(x.iter()) {
436            *yi = *xi + *xp;
437        }
438
439        if x.len() >= N {
440            for (dst, src) in self.0.iter_mut().zip(x[x.len() - N..].iter().rev()) {
441                *dst = *src;
442            }
443        } else {
444            self.0.copy_within(..N - x.len(), x.len());
445            for (dst, src) in self.0.iter_mut().zip(x.iter().rev()) {
446                *dst = *src;
447            }
448        }
449    }
450
451    // TODO: inplace()
452}
453impl<X: Copy, const N: usize> Inplace<X> for Nyquist<[X; N]> where Self: Process<X> {}
454
455/// Running sum / discrete-time integrator.
456#[derive(Debug, Copy, Clone, Default)]
457pub struct Integrator<Y>(
458    /// Current integrator value
459    pub Y,
460);
461impl<X: Copy, Y: core::ops::AddAssign<X> + Copy> Process<X, Y> for Integrator<Y> {
462    fn process(&mut self, x: X) -> Y {
463        self.0 += x;
464        self.0
465    }
466}
467impl<X: Copy> Inplace<X> for Integrator<X> where Self: Process<X> {}
468
469/// Comb (derivative)
470///
471/// Bad for large delays
472///
473/// See also [`Nyquist`] for the sum form.
474#[derive(Debug, Copy, Clone, Default)]
475pub struct Comb<X>(
476    /// Delay line
477    pub X,
478);
479impl<X: Copy + core::ops::Sub<X, Output = Y>, Y, const N: usize> Process<X, Y> for Comb<[X; N]> {
480    fn process(&mut self, x: X) -> Y {
481        const { assert!(N > 0) }
482        let y = x - self.0[N - 1];
483        self.0.copy_within(..N - 1, 1);
484        self.0[0] = x;
485        y
486    }
487
488    fn block(&mut self, x: &[X], y: &mut [Y]) {
489        debug_assert_eq!(x.len(), y.len());
490        let n = x.len().min(N);
491        let (xh, xt) = x.split_at(n);
492        let (yh, yt) = y.split_at_mut(n);
493
494        for ((xi, yi), s) in xh.iter().zip(yh.iter_mut()).zip(self.0[..n].iter().rev()) {
495            *yi = *xi - *s;
496        }
497        for ((xi, yi), xp) in xt.iter().zip(yt.iter_mut()).zip(x.iter()) {
498            *yi = *xi - *xp;
499        }
500
501        if x.len() >= N {
502            for (dst, src) in self.0.iter_mut().zip(x[x.len() - N..].iter().rev()) {
503                *dst = *src;
504            }
505        } else {
506            self.0.copy_within(..N - x.len(), x.len());
507            for (dst, src) in self.0.iter_mut().zip(x.iter().rev()) {
508                *dst = *src;
509            }
510        }
511    }
512
513    // TODO: inplace()
514}
515impl<X: Copy, const N: usize> Inplace<X> for Comb<[X; N]> where Self: Process<X> {}