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}