Skip to main content

audio_engine_core/processor/
dsp_chain.rs

1//! DSP Processing Chain
2//!
3//! Manages a collection of audio processors in a pipeline.
4//! Provides:
5//! - Guaranteed continuous processing (no lock-induced skips)
6//! - Unified statistics and debugging
7//! - Easy dynamic configuration
8//!
9//! # Architecture
10//!
11//! ```text
12//! Input Buffer
13//!      │
14//!      ▼
15//! ┌─────────────────────────────────────────────────────┐
16//! │                    DspChain                          │
17//! │                                                      │
18//! │  ┌──────────┐   ┌──────────┐   ┌──────────┐        │
19//! │  │    EQ    │ → │ Saturation│ → │ Crossfeed│ → ...  │
20//! │  └──────────┘   └──────────┘   └──────────┘        │
21//! │                                                      │
22//! │  Each processor:                                     │
23//! │  - Reads lock-free params                           │
24//! │  - Processes without blocking                       │
25//! │  - Never skips due to contention                    │
26//! │                                                      │
27//! └─────────────────────────────────────────────────────┘
28//!      │
29//!      ▼
30//! Output Buffer
31//! ```
32
33use super::traits::AudioProcessor;
34
35/// DSP processing chain
36///
37/// Manages multiple audio processors in sequence.
38/// All processors share the same buffer, processed in-place.
39pub struct DspChain {
40    /// Processors in execution order
41    processors: Vec<Box<dyn AudioProcessor>>,
42}
43
44impl DspChain {
45    /// Create an empty DSP chain
46    pub fn new(_sample_rate: f64) -> Self {
47        Self {
48            processors: Vec::new(),
49        }
50    }
51
52    /// Create a chain with pre-allocated capacity
53    pub fn with_capacity(capacity: usize, _sample_rate: f64) -> Self {
54        Self {
55            processors: Vec::with_capacity(capacity),
56        }
57    }
58
59    /// Add a processor to the end of the chain
60    pub fn add<P: AudioProcessor + 'static>(&mut self, processor: P) -> &mut Self {
61        self.processors.push(Box::new(processor));
62        self
63    }
64
65    /// Process audio through all processors
66    ///
67    /// # Key Properties
68    ///
69    /// 1. **Continuous**: Never skips processors due to lock contention
70    /// 2. **In-place**: Modifies buffer directly
71    /// 3. **Lock-free**: All parameter updates use atomic operations
72    ///
73    /// # Arguments
74    ///
75    /// * `buffer` - Interleaved audio samples [L, R, L, R, ...]
76    /// * `channels` - Number of audio channels
77    pub fn process(&mut self, buffer: &mut [f64], channels: usize) {
78        for processor in &mut self.processors {
79            processor.process(buffer, channels);
80        }
81    }
82
83    /// Reset all processors
84    pub fn reset(&mut self) {
85        for processor in &mut self.processors {
86            processor.reset();
87        }
88    }
89
90    /// Update sample rate for all processors
91    pub fn set_sample_rate(&mut self, sample_rate: f64) {
92        for processor in &mut self.processors {
93            processor.set_sample_rate(sample_rate);
94        }
95    }
96
97    /// Get number of processors
98    pub fn len(&self) -> usize {
99        self.processors.len()
100    }
101
102    /// Check if chain is empty
103    pub fn is_empty(&self) -> bool {
104        self.processors.is_empty()
105    }
106
107    /// Clear all processors
108    pub fn clear(&mut self) {
109        self.processors.clear();
110    }
111}
112
113impl Default for DspChain {
114    fn default() -> Self {
115        Self::new(44100.0)
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::super::traits::ProcessResult;
122    use super::*;
123
124    // Test processor that doubles samples
125    struct DoublerProcessor {
126        enabled: bool,
127        processed_count: u64,
128    }
129
130    impl DoublerProcessor {
131        fn new() -> Self {
132            Self {
133                enabled: true,
134                processed_count: 0,
135            }
136        }
137    }
138
139    impl AudioProcessor for DoublerProcessor {
140        fn name(&self) -> &'static str {
141            "Doubler"
142        }
143
144        fn process(&mut self, buffer: &mut [f64], _channels: usize) -> ProcessResult {
145            if !self.enabled {
146                return ProcessResult::Bypassed;
147            }
148            for sample in buffer.iter_mut() {
149                *sample *= 2.0;
150            }
151            self.processed_count += 1;
152            ProcessResult::Ok
153        }
154
155        fn reset(&mut self) {
156            self.processed_count = 0;
157        }
158
159        fn is_enabled(&self) -> bool {
160            self.enabled
161        }
162
163        fn set_enabled(&mut self, enabled: bool) {
164            self.enabled = enabled;
165        }
166    }
167
168    // Test processor that adds 1.0
169    struct AdderProcessor {
170        enabled: bool,
171    }
172
173    impl AdderProcessor {
174        fn new() -> Self {
175            Self { enabled: true }
176        }
177    }
178
179    impl AudioProcessor for AdderProcessor {
180        fn name(&self) -> &'static str {
181            "Adder"
182        }
183
184        fn process(&mut self, buffer: &mut [f64], _channels: usize) -> ProcessResult {
185            if !self.enabled {
186                return ProcessResult::Bypassed;
187            }
188            for sample in buffer.iter_mut() {
189                *sample += 1.0;
190            }
191            ProcessResult::Ok
192        }
193
194        fn reset(&mut self) {}
195
196        fn is_enabled(&self) -> bool {
197            self.enabled
198        }
199
200        fn set_enabled(&mut self, enabled: bool) {
201            self.enabled = enabled;
202        }
203    }
204
205    #[test]
206    fn test_empty_chain() {
207        let mut chain = DspChain::new(44100.0);
208        let mut buffer = vec![1.0, 2.0, 3.0];
209        chain.process(&mut buffer, 1);
210        assert_eq!(buffer, vec![1.0, 2.0, 3.0]);
211    }
212
213    #[test]
214    fn test_single_processor() {
215        let mut chain = DspChain::new(44100.0);
216        chain.add(DoublerProcessor::new());
217
218        let mut buffer = vec![1.0, 2.0, 3.0];
219        chain.process(&mut buffer, 1);
220
221        assert_eq!(buffer, vec![2.0, 4.0, 6.0]);
222    }
223
224    #[test]
225    fn test_chain_order() {
226        let mut chain = DspChain::new(44100.0);
227        chain.add(DoublerProcessor::new()); // Doubles first
228        chain.add(AdderProcessor::new()); // Then adds 1
229
230        // Start with 1.0 -> 2.0 (double) -> 3.0 (add 1)
231        let mut buffer = vec![1.0];
232        chain.process(&mut buffer, 1);
233        assert_eq!(buffer, vec![3.0]);
234    }
235
236    #[test]
237    fn test_bypassed_processor() {
238        let mut chain = DspChain::new(44100.0);
239        let mut doubler = DoublerProcessor::new();
240        doubler.set_enabled(false);
241        chain.add(doubler);
242
243        let mut buffer = vec![5.0];
244        chain.process(&mut buffer, 1);
245
246        // Should be unchanged (bypassed)
247        assert_eq!(buffer, vec![5.0]);
248    }
249
250    #[test]
251    fn test_reset() {
252        let mut chain = DspChain::new(44100.0);
253        chain.add(DoublerProcessor::new());
254
255        let mut buffer = vec![1.0; 100];
256        chain.process(&mut buffer, 1);
257        chain.reset();
258        // Should not panic
259    }
260}