1use crate::{Chunk, DspNode, StreamError};
35
36pub struct Serial<A, B> {
40 first: A,
41 second: B,
42}
43
44impl<A: DspNode, B: DspNode> DspNode for Serial<A, B> {
45 fn process(&mut self, input: Chunk) -> Result<Chunk, StreamError> {
46 let mid = self.first.process(input)?;
47 self.second.process(mid)
48 }
49
50 fn reset(&mut self) {
51 self.first.reset();
52 self.second.reset();
53 }
54}
55
56pub struct Parallel<A, B> {
63 left: A,
64 right: B,
65}
66
67impl<A: DspNode, B: DspNode> DspNode for Parallel<A, B> {
68 fn process(&mut self, input: Chunk) -> Result<Chunk, StreamError> {
69 let out_l = self.left.process(input.clone())?;
70 let out_r = self.right.process(input)?;
71
72 if out_l.len() != out_r.len() {
73 return Err(StreamError::ChannelMismatch {
74 expected: out_l.len() as u16,
75 got: out_r.len() as u16,
76 });
77 }
78
79 let sr = out_l.sample_rate();
80 let ch = out_l.channels();
81 let summed: alloc::vec::Vec<f32> = out_l
82 .into_data()
83 .iter()
84 .zip(out_r.into_data().iter())
85 .map(|(a, b)| a + b)
86 .collect();
87
88 Ok(Chunk::new(summed, sr, ch))
89 }
90
91 fn reset(&mut self) {
92 self.left.reset();
93 self.right.reset();
94 }
95}
96
97pub struct Stack<A, B> {
104 top: A,
105 bottom: B,
106}
107
108impl<A: DspNode, B: DspNode> DspNode for Stack<A, B> {
109 fn process(&mut self, input: Chunk) -> Result<Chunk, StreamError> {
110 let out_t = self.top.process(input.clone())?;
111 let out_b = self.bottom.process(input)?;
112
113 let sr = out_t.sample_rate();
114 let ch = out_t.channels();
115 let mut combined = out_t.into_data();
116 combined.extend(out_b.into_data());
117
118 Ok(Chunk::new(combined, sr, ch))
119 }
120
121 fn reset(&mut self) {
122 self.top.reset();
123 self.bottom.reset();
124 }
125}
126
127pub trait GraphExt: DspNode + Sized {
145 fn serial<B: DspNode>(self, other: B) -> Serial<Self, B> {
147 Serial {
148 first: self,
149 second: other,
150 }
151 }
152
153 fn parallel<B: DspNode>(self, other: B) -> Parallel<Self, B> {
155 Parallel {
156 left: self,
157 right: other,
158 }
159 }
160
161 fn stack<B: DspNode>(self, other: B) -> Stack<Self, B> {
163 Stack {
164 top: self,
165 bottom: other,
166 }
167 }
168}
169
170impl<T: DspNode + Sized> GraphExt for T {}
171
172pub trait NodeGraph: DspNode + Send + 'static {}
177impl<T: DspNode + Send + 'static> NodeGraph for T {}
178
179extern crate alloc;
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 fn scale(factor: f32) -> impl DspNode {
186 struct Scale(f32);
187 impl DspNode for Scale {
188 fn process(&mut self, mut input: Chunk) -> Result<Chunk, StreamError> {
189 for s in input.data_mut() {
190 *s *= self.0;
191 }
192 Ok(input)
193 }
194 fn reset(&mut self) {}
195 }
196 Scale(factor)
197 }
198
199 fn make_chunk(data: alloc::vec::Vec<f32>) -> Chunk {
200 Chunk::new(data, 44100, 1)
201 }
202
203 #[test]
204 fn serial_chains_nodes() {
205 let mut g = scale(2.0).serial(scale(3.0));
206 let out = g.process(make_chunk(alloc::vec![1.0, 2.0])).unwrap();
207 assert_eq!(out.data(), &[6.0, 12.0]);
208 }
209
210 #[test]
211 fn serial_matches_manual_pipeline() {
212 let mut g = scale(2.0).serial(scale(0.5));
213 let out = g.process(make_chunk(alloc::vec![4.0])).unwrap();
214 assert_eq!(out.data(), &[4.0]); }
216
217 #[test]
218 fn serial_associativity() {
219 let chunk_a = make_chunk(alloc::vec![1.0]);
221 let chunk_b = make_chunk(alloc::vec![1.0]);
222 let mut left = scale(2.0).serial(scale(3.0)).serial(scale(4.0));
223 let mut right = scale(2.0).serial(scale(3.0).serial(scale(4.0)));
224 assert_eq!(
225 left.process(chunk_a).unwrap().into_data(),
226 right.process(chunk_b).unwrap().into_data()
227 );
228 }
229
230 #[test]
231 fn serial_error_propagates() {
232 struct Fail;
233 impl DspNode for Fail {
234 fn process(&mut self, _: Chunk) -> Result<Chunk, StreamError> {
235 Err(StreamError::ProcessingError("fail".into()))
236 }
237 fn reset(&mut self) {}
238 }
239 let mut g = scale(2.0).serial(Fail);
240 assert!(g.process(make_chunk(alloc::vec![1.0])).is_err());
241 }
242
243 #[test]
244 fn serial_reset_propagates() {
245 let mut g = scale(1.0).serial(scale(1.0));
246 g.reset(); }
248
249 #[test]
250 fn parallel_sums_outputs() {
251 let mut g = scale(2.0).parallel(scale(3.0));
253 let out = g.process(make_chunk(alloc::vec![1.0, 1.0])).unwrap();
254 assert_eq!(out.data(), &[5.0, 5.0]);
255 }
256
257 #[test]
258 fn parallel_identity_doubles() {
259 struct Pass;
261 impl DspNode for Pass {
262 fn process(&mut self, input: Chunk) -> Result<Chunk, StreamError> {
263 Ok(input)
264 }
265 fn reset(&mut self) {}
266 }
267 let mut g = Pass.parallel(Pass);
268 let out = g.process(make_chunk(alloc::vec![1.0, -0.5])).unwrap();
269 assert_eq!(out.data(), &[2.0, -1.0]);
270 }
271
272 #[test]
273 fn parallel_reset_propagates() {
274 let mut g = scale(1.0).parallel(scale(1.0));
275 g.reset();
276 }
277
278 #[test]
279 fn stack_concatenates_outputs() {
280 let mut g = scale(2.0).stack(scale(3.0));
281 let out = g.process(make_chunk(alloc::vec![1.0])).unwrap();
282 assert_eq!(out.data(), &[2.0, 3.0]);
284 }
285
286 #[test]
287 fn stack_output_length_is_sum() {
288 let mut g = scale(1.0).stack(scale(1.0));
289 let out = g.process(make_chunk(alloc::vec![1.0; 4])).unwrap();
290 assert_eq!(out.len(), 8);
291 }
292
293 #[test]
294 fn stack_reset_propagates() {
295 let mut g = scale(1.0).stack(scale(1.0));
296 g.reset();
297 }
298
299 #[test]
300 fn graph_ext_serial_method() {
301 let mut g = scale(4.0).serial(scale(0.25));
302 let out = g.process(make_chunk(alloc::vec![2.0])).unwrap();
303 assert_eq!(out.data(), &[2.0]);
304 }
305
306 #[test]
307 fn graph_ext_parallel_method() {
308 let mut g = scale(1.0).parallel(scale(1.0));
309 let out = g.process(make_chunk(alloc::vec![0.5])).unwrap();
310 assert_eq!(out.data(), &[1.0]);
311 }
312
313 #[test]
314 fn graph_ext_stack_method() {
315 let mut g = scale(1.0).stack(scale(2.0));
316 let out = g.process(make_chunk(alloc::vec![1.0])).unwrap();
317 assert_eq!(out.data(), &[1.0, 2.0]);
318 }
319}