bbx_dsp/blocks/effectors/
channel_merger.rs

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