Skip to main content

dsp_process/
process.rs

1//! Core traits for synchronous sample and block processing.
2
3/// Processing block
4///
5/// Single-input processing with state held in `self`.
6///
7/// This is the simplest trait in the crate: one new sample goes in, one output
8/// value comes out. Override [`block()`](Self::block) when a specialized loop can
9/// reuse scratch storage, reduce bounds checks, or better match the desired data
10/// layout.
11///
12/// [`SplitProcess`] is the corresponding trait when immutable configuration and
13/// mutable runtime state should be separated.
14///
15/// # Examples
16///
17/// ```rust
18/// use dsp_process::Process;
19///
20/// #[derive(Default)]
21/// struct Acc(i32);
22///
23/// impl Process<i32> for Acc {
24///     fn process(&mut self, x: i32) -> i32 {
25///         self.0 += x;
26///         self.0
27///     }
28/// }
29///
30/// let mut acc = Acc::default();
31/// assert_eq!(acc.process(2), 2);
32/// assert_eq!(acc.process(3), 5);
33/// ```
34pub trait Process<X: Copy, Y = X> {
35    /// Update the state with a new input and obtain an output
36    fn process(&mut self, x: X) -> Y;
37
38    /// Process a block of inputs into a block of outputs
39    ///
40    /// Input and output must be of the same size.
41    ///
42    /// For hot-path use this is treated as a caller precondition; the default
43    /// implementation only checks it in debug builds.
44    fn block(&mut self, x: &[X], y: &mut [Y]) {
45        debug_assert_eq!(x.len(), y.len());
46        for (x, y) in x.iter().zip(y) {
47            *y = self.process(*x);
48        }
49    }
50}
51
52/// Inplace processing
53///
54/// This is a convenience trait for processors where input and output element
55/// types are identical and the computation can be expressed as overwriting a
56/// mutable slice.
57///
58/// See also [`SplitInplace`] for the split configuration/state form.
59pub trait Inplace<X: Copy>: Process<X> {
60    /// Process an input block into the same data as output
61    fn inplace(&mut self, xy: &mut [X]) {
62        for xy in xy.iter_mut() {
63            *xy = self.process(*xy);
64        }
65    }
66}
67
68/// Processing with split state
69///
70/// Immutable configuration operating on explicit mutable state.
71///
72/// One configuration may drive many states; several configurations may also
73/// implement distinct phases over the same `S`. [`crate::Split<C, S>`] binds
74/// one pair into an ordinary [`Process`].
75pub trait SplitProcess<X: Copy, Y = X, S: ?Sized = ()> {
76    /// Process an input into an output
77    ///
78    /// See also [`Process::process`]
79    fn process(&self, state: &mut S, x: X) -> Y;
80
81    /// Process a block of inputs
82    ///
83    /// See also [`Process::block`]
84    ///
85    /// Length matching is a caller precondition in release builds.
86    fn block(&self, state: &mut S, x: &[X], y: &mut [Y]) {
87        debug_assert_eq!(x.len(), y.len());
88        for (x, y) in x.iter().zip(y) {
89            *y = self.process(state, *x);
90        }
91    }
92}
93
94/// Inplace processing with a split state
95///
96/// This is the split-state companion to [`Inplace`]. Implement it when a
97/// `SplitProcess<X, X, S>` can update a buffer in place more efficiently than
98/// routing through a separate output slice.
99pub trait SplitInplace<X: Copy, S: ?Sized = ()>: SplitProcess<X, X, S> {
100    /// See also [`Inplace::inplace`]
101    fn inplace(&self, state: &mut S, xy: &mut [X]) {
102        for xy in xy.iter_mut() {
103            *xy = self.process(state, *xy);
104        }
105    }
106}
107
108//////////// BLANKET ////////////
109
110impl<X: Copy, Y, T: Process<X, Y>> Process<X, Y> for &mut T {
111    fn process(&mut self, x: X) -> Y {
112        T::process(self, x)
113    }
114
115    fn block(&mut self, x: &[X], y: &mut [Y]) {
116        T::block(self, x, y)
117    }
118}
119
120impl<X: Copy, T: Inplace<X>> Inplace<X> for &mut T {
121    fn inplace(&mut self, xy: &mut [X]) {
122        T::inplace(self, xy)
123    }
124}
125
126impl<X: Copy, Y, S: ?Sized, T: SplitProcess<X, Y, S>> SplitProcess<X, Y, S> for &T {
127    fn process(&self, state: &mut S, x: X) -> Y {
128        T::process(self, state, x)
129    }
130
131    fn block(&self, state: &mut S, x: &[X], y: &mut [Y]) {
132        T::block(self, state, x, y)
133    }
134}
135
136impl<X: Copy, S: ?Sized, T: SplitInplace<X, S>> SplitInplace<X, S> for &T {
137    fn inplace(&self, state: &mut S, xy: &mut [X]) {
138        T::inplace(self, state, xy)
139    }
140}
141
142impl<X: Copy, Y, S: ?Sized, T: SplitProcess<X, Y, S>> SplitProcess<X, Y, S> for &mut T {
143    fn process(&self, state: &mut S, x: X) -> Y {
144        T::process(self, state, x)
145    }
146
147    fn block(&self, state: &mut S, x: &[X], y: &mut [Y]) {
148        T::block(self, state, x, y)
149    }
150}
151
152impl<X: Copy, S: ?Sized, T: SplitInplace<X, S>> SplitInplace<X, S> for &mut T {
153    fn inplace(&self, state: &mut S, xy: &mut [X]) {
154        T::inplace(self, state, xy)
155    }
156}
157
158/// Wrap a `FnMut` into a `Process`/`Inplace`
159///
160/// This is useful for quick experiments, benchmarks, or adapters at the edge of
161/// a pipeline. For reusable DSP stages, prefer a named type once the closure
162/// starts carrying real semantics.
163///
164/// # Examples
165///
166/// ```rust
167/// use dsp_process::{FnProcess, Process};
168///
169/// let mut square = FnProcess(|x: i32| x * x);
170/// assert_eq!(square.process(7), 49);
171/// ```
172pub struct FnProcess<F>(pub F);
173
174impl<F: FnMut(X) -> Y, X: Copy, Y> Process<X, Y> for FnProcess<F> {
175    fn process(&mut self, x: X) -> Y {
176        (self.0)(x)
177    }
178}
179
180impl<F, X: Copy> Inplace<X> for FnProcess<F> where Self: Process<X> {}
181
182/// Wrap a `Fn` into a `SplitProcess`/`SplitInplace`
183///
184/// The closure receives both the mutable split state and the new input sample.
185/// This is a compact way to prototype split-state processors before promoting
186/// them to named types.
187///
188/// # Examples
189///
190/// ```rust
191/// use dsp_process::{FnSplitProcess, SplitProcess};
192///
193/// let proc = FnSplitProcess(|state: &mut i32, x: i32| {
194///     *state += x;
195///     *state
196/// });
197///
198/// let mut state = 0;
199/// assert_eq!(proc.process(&mut state, 2), 2);
200/// assert_eq!(proc.process(&mut state, 3), 5);
201/// ```
202pub struct FnSplitProcess<F>(pub F);
203
204impl<F: Fn(&mut S, X) -> Y, X: Copy, Y, S> SplitProcess<X, Y, S> for FnSplitProcess<F> {
205    fn process(&self, state: &mut S, x: X) -> Y {
206        (self.0)(state, x)
207    }
208}
209
210impl<F, X: Copy, S> SplitInplace<X, S> for FnSplitProcess<F> where Self: SplitProcess<X, X, S> {}