Skip to main content

dsp_process/
split.rs

1use core::array::{from_fn, repeat};
2
3use crate::{
4    ByLane, Chunk, Decimator, Inplace, Interpolator, Lanes, Major, Map, Minor, Parallel, PerFrame,
5    Process, SplitInplace, SplitProcess, TryDecimator,
6};
7
8//////////// SPLIT ////////////
9
10/// A stateful processor assembled from split configuration and state.
11///
12/// This binds one [`SplitProcess`] configuration to one state and exposes the
13/// pair as [`Process`]. [`lanes()`](Self::lanes) instead shares the configuration
14/// across independent states.
15///
16/// # Examples
17///
18/// ```rust
19/// use dsp_process::{Offset, Process, Split};
20///
21/// let mut p = Split::stateless(Offset(3));
22/// assert_eq!(p.process(5), 8);
23/// ```
24#[derive(Debug, Copy, Clone, Default)]
25pub struct Split<C, S> {
26    /// Processor configuration
27    pub config: C,
28    /// Processor state
29    pub state: S,
30}
31
32impl<X: Copy, Y, S, C: SplitProcess<X, Y, S>> Process<X, Y> for Split<C, S> {
33    fn process(&mut self, x: X) -> Y {
34        self.config.process(&mut self.state, x)
35    }
36
37    fn block(&mut self, x: &[X], y: &mut [Y]) {
38        self.config.block(&mut self.state, x, y)
39    }
40}
41
42impl<X: Copy, S, C: SplitInplace<X, S>> Inplace<X> for Split<C, S> {
43    fn inplace(&mut self, xy: &mut [X]) {
44        self.config.inplace(&mut self.state, xy);
45    }
46}
47
48impl<C, S> Split<C, S> {
49    /// Create a new [`Split`] from explicit configuration and state values.
50    #[must_use]
51    pub const fn new(config: C, state: S) -> Self {
52        Self { config, state }
53    }
54
55    /// Statically assert that this implements Process<X, Y>
56    pub const fn assert_process<X: Copy, Y>(&self)
57    where
58        Self: Process<X, Y>,
59    {
60    }
61}
62
63/// Marker for values that should live in the opposite half of a [`Split`].
64///
65/// To be used in `Split<Unsplit<P>, ()>` and `Split<(), Unsplit<P>>`
66/// to mark processors requiring no state and no configuration respectively.
67///
68/// Most users will not construct this directly and should prefer
69/// [`Split::stateless`] and [`Split::stateful`].
70#[derive(Debug, Copy, Clone, Default)]
71#[repr(transparent)]
72pub struct Unsplit<P>(pub P);
73
74impl<C> Split<C, ()> {
75    /// Create a [`Split`] with configuration only and unit state.
76    #[must_use]
77    pub fn stateless(config: C) -> Self {
78        Self::new(config, ())
79    }
80}
81
82impl<S> Split<(), Unsplit<S>> {
83    /// Create a [`Split`] with state only and unit configuration.
84    #[must_use]
85    pub fn stateful(state: S) -> Self {
86        Self::new((), Unsplit(state))
87    }
88}
89
90/// Unzip two splits into one
91impl<C0, C1, S0, S1> core::ops::Mul<Split<C1, S1>> for Split<C0, S0> {
92    type Output = Split<(C0, C1), (S0, S1)>;
93
94    fn mul(self, rhs: Split<C1, S1>) -> Self::Output {
95        Split::from((self, rhs))
96    }
97}
98
99/// Unzip two splits into one parallel
100impl<C0, C1, S0, S1> core::ops::Add<Split<C1, S1>> for Split<C0, S0> {
101    type Output = Split<Parallel<(C0, C1)>, (S0, S1)>;
102
103    fn add(self, rhs: Split<C1, S1>) -> Self::Output {
104        Split::from((self, rhs)).parallel()
105    }
106}
107
108/// Unzip two splits
109impl<C0, C1, S0, S1> From<(Split<C0, S0>, Split<C1, S1>)> for Split<(C0, C1), (S0, S1)> {
110    fn from(value: (Split<C0, S0>, Split<C1, S1>)) -> Self {
111        Split::new(
112            (value.0.config, value.1.config),
113            (value.0.state, value.1.state),
114        )
115    }
116}
117
118/// Unzip multiple splits
119impl<C, S, const N: usize> From<[Split<C, S>; N]> for Split<[C; N], [S; N]> {
120    fn from(splits: [Split<C, S>; N]) -> Self {
121        // Not efficient or nice, but this is usually not a hot path
122        let mut splits = splits.map(|s| (Some(s.config), Some(s.state)));
123        Self::new(
124            from_fn(|i| splits[i].0.take().unwrap()),
125            from_fn(|i| splits[i].1.take().unwrap()),
126        )
127    }
128}
129
130impl<C, S> Split<C, S> {
131    /// Convert to [`Minor`] composition.
132    ///
133    /// This keeps the same logical processor but requests sample-by-sample
134    /// `block()`/`inplace()` execution of the wrapped serial composition.
135    ///
136    /// Use this for small fine-grained stages, or when tuple composition must
137    /// cross an intermediate type and the downstream stage is not
138    /// [`SplitInplace`] for that intermediate. Avoid it when preserving
139    /// stage-major slice processing is important for cache behavior or SIMD.
140    #[must_use]
141    pub fn minor<U>(self) -> Split<Minor<C, U>, S> {
142        Split::new(Minor::new(self.config), self.state)
143    }
144
145    /// Convert to [`Major`] composition with an explicit intermediate buffer.
146    ///
147    /// Use this when preserving stage-major slice processing is more important
148    /// than avoiding an intermediate scratch buffer, especially for larger
149    /// stages or stages with meaningful `block()` specializations.
150    #[must_use]
151    pub fn major<U>(self) -> Split<Major<C, U>, S> {
152        Split::new(Major::new(self.config), self.state)
153    }
154
155    /// Convert to [`Parallel`] composition.
156    ///
157    /// This expresses structural branching: each input lane is routed to the
158    /// matching branch and outputs stay separate unless reduced explicitly.
159    #[must_use]
160    pub fn parallel(self) -> Split<Parallel<C>, S> {
161        Split::new(Parallel::new(self.config), self.state)
162    }
163
164    /// Map `Option` and `Result` around this processor.
165    ///
166    /// This lifts the processor through outer `Option`/`Result` control flow
167    /// while preserving the current state unchanged.
168    #[must_use]
169    pub fn map(self) -> Split<Map<C>, S> {
170        Split::new(Map(self.config), self.state)
171    }
172
173    /// Convert to elementwise fixed-size chunk processing.
174    ///
175    /// This is the basic array-lifting adapter. Use the more specific chunk or
176    /// rate adapters when samples must be regrouped rather than processed
177    /// elementwise.
178    #[must_use]
179    pub fn chunk(self) -> Split<Chunk<C>, S> {
180        Split::new(Chunk(self.config), self.state)
181    }
182
183    /// Convert a scalar optional-input stage into chunk output mode.
184    ///
185    /// This preserves stream phase across one input sample expanded into one
186    /// output chunk. Prefer this over structural chunk regrouping when the
187    /// inner stage is naturally `Option<X> -> Y`.
188    #[must_use]
189    pub fn interpolate(self) -> Split<Interpolator<C>, S> {
190        Split::new(Interpolator(self.config), self.state)
191    }
192
193    /// Convert a scalar optional-output stage into unchecked chunk input mode.
194    ///
195    /// This preserves stream phase across one input chunk collapsed into one
196    /// output sample. Prefer this over structural chunk regrouping when the
197    /// inner stage is naturally `X -> Option<Y>`.
198    #[must_use]
199    pub fn decimate(self) -> Split<Decimator<C>, S> {
200        Split::new(Decimator(self.config), self.state)
201    }
202
203    /// Convert a scalar optional-output stage into checked chunk input mode.
204    ///
205    /// This is the checked form of [`decimate()`](Self::decimate), returning an
206    /// error when the inner stage does not tick exactly once per input chunk.
207    #[must_use]
208    pub fn try_decimate(self) -> Split<TryDecimator<C>, S> {
209        Split::new(TryDecimator(self.config), self.state)
210    }
211
212    /// Treat each frame of a frame-major view as one chunk sample.
213    ///
214    /// This bridges chunk-style processors such as [`crate::Chunk`],
215    /// [`crate::ChunkIn`], [`crate::ChunkOut`], and [`crate::ChunkInOut`] into
216    /// the typed view API without changing the backing layout.
217    ///
218    /// ```rust
219    /// use dsp_process::{ChunkInOut, FnSplitProcess, Split, View, ViewMut};
220    ///
221    /// let mut p = Split::stateless(ChunkInOut::<_, 2, 1>(FnSplitProcess(
222    ///     |_: &mut (), [x0, x1]: [i32; 2]| [x0 + x1],
223    /// )))
224    /// .per_frame();
225    /// let x = View::from_frames(&[[1, 2], [3, 4]]);
226    /// let mut y = [[0; 1]; 2];
227    /// let yv = ViewMut::from_frames(&mut y);
228    /// p.process_frames(x, yv);
229    /// assert_eq!(y, [[3], [7]]);
230    /// ```
231    #[must_use]
232    pub fn per_frame(self) -> Split<PerFrame<C>, S> {
233        Split::new(PerFrame(self.config), self.state)
234    }
235
236    /// Duplicate the processor by cloning both configuration and current state.
237    ///
238    /// The current state is copied as-is. Use this only when duplicating the
239    /// existing state is intentional, for example when seeding several identical
240    /// branches from a known starting point.
241    #[must_use]
242    pub fn repeat<const N: usize>(self) -> Split<[C; N], [S; N]>
243    where
244        C: Clone,
245        S: Clone,
246    {
247        Split::new(repeat(self.config), repeat(self.state))
248    }
249
250    /// Share one configuration across multiple cloned states via [`Lanes`].
251    ///
252    /// This is usually preferable to [`repeat()`](Self::repeat) when the
253    /// configuration should be shared but each lane needs its own mutable
254    /// runtime state. For lane-major view processing, pair this with
255    /// [`crate::View`] using [`crate::LaneMajor`].
256    ///
257    /// ```rust
258    /// use dsp_process::{LaneMajor, Offset, Split, View, ViewMut, ViewProcess};
259    ///
260    /// let mut p = Split::stateless(Offset(3)).lanes::<2>();
261    /// let x = View::<_, LaneMajor, 2>::from_flat(&[1, 2, 3, 10, 20, 30], 3);
262    /// let mut y = [0; 6];
263    /// let yv = ViewMut::<_, LaneMajor, 2>::from_flat(&mut y, 3);
264    /// ViewProcess::process_view(&mut p, x, yv);
265    /// assert_eq!(y, [4, 5, 6, 13, 23, 33]);
266    /// ```
267    #[must_use]
268    pub fn lanes<const N: usize>(self) -> Split<Lanes<C>, [S; N]>
269    where
270        S: Clone,
271    {
272        Split::new(Lanes::new(self.config), repeat(self.state))
273    }
274
275    /// Convert to [`ByLane`] view semantics.
276    ///
277    /// Scalar `process()` is unchanged. Use this when parallel branches should
278    /// process lane-major views as long contiguous per-lane slices.
279    #[must_use]
280    pub fn by_lane(self) -> Split<ByLane<C>, S> {
281        Split::new(ByLane::new(self.config), self.state)
282    }
283}
284
285impl<C, S, U> Split<Minor<C, U>, S> {
286    /// Strip minor
287    #[must_use]
288    pub fn inter(self) -> Split<C, S> {
289        Split::new(self.config.into_inner(), self.state)
290    }
291}
292
293impl<C, S> Split<Parallel<C>, S> {
294    /// Convert to serial
295    #[must_use]
296    pub fn inter(self) -> Split<C, S> {
297        Split::new(self.config.into_inner(), self.state)
298    }
299}
300
301impl<C, S> Split<PerFrame<C>, S> {
302    /// Remove per-frame view adaptation.
303    #[must_use]
304    pub fn inter(self) -> Split<C, S> {
305        Split::new(self.config.0, self.state)
306    }
307}
308
309impl<C, S> Split<ByLane<C>, S> {
310    /// Convert to ordinary view semantics.
311    #[must_use]
312    pub fn inter(self) -> Split<C, S> {
313        Split::new(self.config.into_inner(), self.state)
314    }
315}
316
317impl<C, S, B> Split<Major<C, B>, S> {
318    /// Remove major intermediate buffering
319    #[must_use]
320    pub fn inter(self) -> Split<C, S> {
321        Split::new(self.config.into_inner(), self.state)
322    }
323}
324
325impl<C0, C1, S0, S1> Split<(C0, C1), (S0, S1)> {
326    /// Zip up a split
327    #[must_use]
328    pub fn zip(self) -> (Split<C0, S0>, Split<C1, S1>) {
329        (
330            Split::new(self.config.0, self.state.0),
331            Split::new(self.config.1, self.state.1),
332        )
333    }
334}
335
336impl<C, S, const N: usize> Split<[C; N], [S; N]> {
337    /// Zip up a split
338    #[must_use]
339    pub fn zip(self) -> [Split<C, S>; N] {
340        let mut it = self.config.into_iter().zip(self.state);
341        from_fn(|_| {
342            let (c, s) = it.next().unwrap();
343            Split::new(c, s)
344        })
345    }
346}
347
348/// Configuration-less filters
349impl<X: Copy, Y, P: Process<X, Y>> SplitProcess<X, Y, Unsplit<P>> for () {
350    fn process(&self, state: &mut Unsplit<P>, x: X) -> Y {
351        state.0.process(x)
352    }
353
354    fn block(&self, state: &mut Unsplit<P>, x: &[X], y: &mut [Y]) {
355        state.0.block(x, y)
356    }
357}
358
359impl<X: Copy, P: Inplace<X>> SplitInplace<X, Unsplit<P>> for () {
360    fn inplace(&self, state: &mut Unsplit<P>, xy: &mut [X]) {
361        state.0.inplace(xy)
362    }
363}