Skip to main content

audiograph/
processor.rs

1use crate::{
2    buffer::{AudioBuffer, FrameSize},
3    channel::ChannelSelection,
4    sample::Sample,
5};
6
7/// Trait for audio processing nodes
8///
9/// A processor implements the `process` method that operates directly on audio data
10/// provided through the `ProcessingContext`.
11pub trait Processor<T: Sample> {
12    /// Processes audio data from the input buffer to the output buffer of the provided context
13    fn process(&mut self, context: &mut ProcessingContext<T>);
14}
15
16/// The audio processing context used by a [`Processor`] to perform its processing operation
17///
18/// The context includes references to input and output buffers, an optional channel selection
19/// to specify which channels to process, and the number of frames to process.
20#[non_exhaustive]
21pub struct ProcessingContext<'a, T: Sample> {
22    pub input_buffer: &'a dyn AudioBuffer<T>,
23    pub output_buffer: &'a mut dyn AudioBuffer<T>,
24    pub channel_selection: Option<ChannelSelection>,
25
26    /// Number of frames to process from each channel. This may be less than or equal to the total number of frames
27    /// available in the buffers.
28    pub num_frames: FrameSize,
29}
30
31impl<'a, T: Sample> ProcessingContext<'a, T> {
32    /// Creates a new processing context without validation
33    ///
34    /// This constructor does not validate that the channel selection and frame size
35    /// are within the bounds of the provided buffers. Use this method when you have
36    /// already validated these parameters.
37    pub fn create_unchecked(
38        input_buffer: &'a dyn AudioBuffer<T>,
39        output_buffer: &'a mut dyn AudioBuffer<T>,
40        channel_selection: Option<ChannelSelection>,
41        num_frames: FrameSize,
42    ) -> Self {
43        Self {
44            input_buffer,
45            output_buffer,
46            channel_selection,
47            num_frames,
48        }
49    }
50
51    /// Creates a new processing context with validation and clamping of channel selection and frame size
52    ///
53    /// This constructor automatically adjusts the channel selection and frame count to ensure they do not exceed
54    /// the capabilities of the provided buffers.
55    pub fn create_checked(
56        input_buffer: &'a dyn AudioBuffer<T>,
57        output_buffer: &'a mut dyn AudioBuffer<T>,
58        mut channel_selection: ChannelSelection,
59        mut num_frames: FrameSize,
60    ) -> Self {
61        let max_channels = input_buffer
62            .num_channels()
63            .min(output_buffer.num_channels());
64        channel_selection.clamp(max_channels);
65
66        let max_frames = input_buffer
67            .num_frames()
68            .0
69            .min(output_buffer.num_frames().0);
70        num_frames = FrameSize(num_frames.0.min(max_frames));
71
72        Self {
73            input_buffer,
74            output_buffer,
75            channel_selection: Some(channel_selection),
76            num_frames,
77        }
78    }
79
80    /// Iterates over selected channels, applying a function to each input/output channel pair
81    ///
82    /// This helper method simplifies the common pattern of processing audio data channel by channel.
83    /// If a channel selection is specified, only those channels are processed. Otherwise, all channels
84    /// up to the minimum of input and output channel counts are processed.
85    ///
86    /// # Example
87    ///
88    /// ```rust,ignore
89    /// context.for_each_channel(|input, output| {
90    ///     for (i, o) in input.iter().zip(output.iter_mut()) {
91    ///         *o = *i * 0.5; // Apply 0.5 gain
92    ///     }
93    /// });
94    /// ```
95    pub fn for_each_channel(&mut self, mut f: impl FnMut(&[T], &mut [T])) {
96        match &self.channel_selection {
97            Some(selection) => {
98                for ch in selection.iter() {
99                    f(
100                        self.input_buffer.channel(ch).unwrap(),
101                        self.output_buffer.channel_mut(ch).unwrap(),
102                    );
103                }
104            }
105            None => {
106                let num_channels = self
107                    .input_buffer
108                    .num_channels()
109                    .min(self.output_buffer.num_channels());
110
111                for ch in 0..num_channels {
112                    f(
113                        self.input_buffer.channel(ch).unwrap(),
114                        self.output_buffer.channel_mut(ch).unwrap(),
115                    );
116                }
117            }
118        }
119    }
120}
121
122/// A passthrough processor that copies input to output without modification
123pub struct PassThrough;
124
125impl<T: Sample> Processor<T> for PassThrough {
126    fn process(&mut self, context: &mut ProcessingContext<T>) {
127        context.for_each_channel(|input, output| {
128            output.copy_from_slice(input);
129        });
130    }
131}
132
133/// A no-operation processor that does nothing
134pub struct NoOp;
135
136impl<T: Sample> Processor<T> for NoOp {
137    fn process(&mut self, _context: &mut ProcessingContext<T>) {}
138}