bbx_dsp/blocks/effectors/
channel_splitter.rs

1//! Channel splitter block for separating multi-channel audio.
2
3use std::marker::PhantomData;
4
5use crate::{
6    block::Block, channel::ChannelConfig, context::DspContext, graph::MAX_BLOCK_OUTPUTS, parameter::ModulationOutput,
7    sample::Sample,
8};
9
10/// Splits multi-channel input into individual mono outputs.
11///
12/// Each input channel is copied to the corresponding output port,
13/// allowing downstream blocks to process channels independently.
14///
15/// # Example
16/// A 4-channel splitter takes 4 input channels and outputs them
17/// as 4 separate mono signals that can be routed to different blocks.
18pub struct ChannelSplitterBlock<S: Sample> {
19    channel_count: usize,
20    _phantom: PhantomData<S>,
21}
22
23impl<S: Sample> ChannelSplitterBlock<S> {
24    /// Create a new channel splitter for the given number of channels.
25    ///
26    /// # Panics
27    /// Panics if `channels` is 0 or greater than `MAX_BLOCK_OUTPUTS` (16).
28    pub fn new(channels: usize) -> Self {
29        assert!(channels > 0 && channels <= MAX_BLOCK_OUTPUTS);
30        Self {
31            channel_count: channels,
32            _phantom: PhantomData,
33        }
34    }
35
36    /// Returns the number of channels this splitter handles.
37    pub fn channel_count(&self) -> usize {
38        self.channel_count
39    }
40}
41
42impl<S: Sample> Block<S> for ChannelSplitterBlock<S> {
43    fn process(&mut self, inputs: &[&[S]], outputs: &mut [&mut [S]], _modulation_values: &[S], _context: &DspContext) {
44        let num_channels = self.channel_count.min(inputs.len()).min(outputs.len());
45
46        for ch in 0..num_channels {
47            let input = inputs[ch];
48            let output = &mut outputs[ch];
49            let num_samples = input.len().min(output.len());
50
51            output[..num_samples].copy_from_slice(&input[..num_samples]);
52        }
53    }
54
55    #[inline]
56    fn input_count(&self) -> usize {
57        self.channel_count
58    }
59
60    #[inline]
61    fn output_count(&self) -> usize {
62        self.channel_count
63    }
64
65    #[inline]
66    fn modulation_outputs(&self) -> &[ModulationOutput] {
67        &[]
68    }
69
70    #[inline]
71    fn channel_config(&self) -> ChannelConfig {
72        ChannelConfig::Explicit
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::channel::ChannelLayout;
80
81    fn test_context() -> DspContext {
82        DspContext {
83            sample_rate: 44100.0,
84            num_channels: 2,
85            buffer_size: 4,
86            current_sample: 0,
87            channel_layout: ChannelLayout::Stereo,
88        }
89    }
90
91    #[test]
92    fn test_channel_splitter_stereo() {
93        let mut splitter = ChannelSplitterBlock::<f32>::new(2);
94        let context = test_context();
95
96        let left_in = [1.0f32, 2.0, 3.0, 4.0];
97        let right_in = [5.0f32, 6.0, 7.0, 8.0];
98        let mut left_out = [0.0f32; 4];
99        let mut right_out = [0.0f32; 4];
100
101        let inputs: [&[f32]; 2] = [&left_in, &right_in];
102        let mut outputs: [&mut [f32]; 2] = [&mut left_out, &mut right_out];
103
104        splitter.process(&inputs, &mut outputs, &[], &context);
105
106        assert_eq!(left_out, left_in);
107        assert_eq!(right_out, right_in);
108    }
109
110    #[test]
111    fn test_channel_splitter_quad() {
112        let splitter = ChannelSplitterBlock::<f32>::new(4);
113        assert_eq!(splitter.input_count(), 4);
114        assert_eq!(splitter.output_count(), 4);
115        assert_eq!(splitter.channel_config(), ChannelConfig::Explicit);
116    }
117
118    #[test]
119    #[should_panic]
120    fn test_channel_splitter_zero_channels_panics() {
121        let _ = ChannelSplitterBlock::<f32>::new(0);
122    }
123
124    #[test]
125    #[should_panic]
126    fn test_channel_splitter_too_many_channels_panics() {
127        let _ = ChannelSplitterBlock::<f32>::new(17);
128    }
129}