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}