Skip to main content

wavecraft_dsp/
processor.rs

1//! Audio processor - reference implementation with gain control.
2//!
3//! The `GainProcessor` struct is a reference implementation of a simple gain
4//! processor. It demonstrates real-time safe audio processing patterns.
5
6use crate::gain::db_to_linear;
7
8/// Reference gain processor implementation for VstKit.
9///
10/// This struct maintains processing state and provides a simple gain effect.
11/// All methods are designed to be real-time safe (no allocations, no locks, no syscalls).
12///
13/// This serves as a reference implementation for the `Processor` trait.
14pub struct GainProcessor {
15    sample_rate: f32,
16}
17
18impl GainProcessor {
19    /// Create a new gain processor with the given sample rate.
20    ///
21    /// # Arguments
22    /// * `sample_rate` - The audio sample rate in Hz (e.g., 44100.0)
23    pub fn new(sample_rate: f32) -> Self {
24        Self { sample_rate }
25    }
26
27    /// Update the sample rate.
28    ///
29    /// Call this when the host changes sample rate (e.g., in `initialize()`).
30    ///
31    /// # Arguments
32    /// * `sample_rate` - The new sample rate in Hz
33    pub fn set_sample_rate(&mut self, sample_rate: f32) {
34        self.sample_rate = sample_rate;
35    }
36
37    /// Get the current sample rate.
38    #[inline]
39    pub fn sample_rate(&self) -> f32 {
40        self.sample_rate
41    }
42
43    /// Process stereo audio buffers in-place.
44    ///
45    /// # Arguments
46    /// * `left` - Left channel audio buffer (modified in-place)
47    /// * `right` - Right channel audio buffer (modified in-place)
48    /// * `gain_db` - Gain to apply in decibels
49    ///
50    /// # Real-Time Safety
51    /// This method is real-time safe:
52    /// - No allocations
53    /// - No locks
54    /// - No syscalls
55    /// - No panics (uses debug_assert only)
56    #[inline]
57    pub fn process(&self, left: &mut [f32], right: &mut [f32], gain_db: f32) {
58        debug_assert_eq!(
59            left.len(),
60            right.len(),
61            "Left and right buffers must have equal length"
62        );
63
64        let gain_linear = db_to_linear(gain_db);
65
66        // Apply gain to left channel
67        for sample in left.iter_mut() {
68            *sample *= gain_linear;
69        }
70
71        // Apply gain to right channel
72        for sample in right.iter_mut() {
73            *sample *= gain_linear;
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn test_passthrough_at_0db() {
84        let processor = GainProcessor::new(44100.0);
85        let mut left = [0.5, -0.5, 0.25, -0.25];
86        let mut right = [0.3, -0.3, 0.1, -0.1];
87
88        let left_original = left;
89        let right_original = right;
90
91        processor.process(&mut left, &mut right, 0.0);
92
93        for (i, (l, r)) in left.iter().zip(right.iter()).enumerate() {
94            assert!(
95                (l - left_original[i]).abs() < 1e-6,
96                "Left sample {} changed at 0dB gain",
97                i
98            );
99            assert!(
100                (r - right_original[i]).abs() < 1e-6,
101                "Right sample {} changed at 0dB gain",
102                i
103            );
104        }
105    }
106
107    #[test]
108    fn test_gain_applied() {
109        let processor = GainProcessor::new(44100.0);
110        let mut left = [1.0, 1.0, 1.0, 1.0];
111        let mut right = [1.0, 1.0, 1.0, 1.0];
112
113        // -6 dB ≈ 0.501187 linear gain
114        processor.process(&mut left, &mut right, -6.0);
115
116        let expected_gain = db_to_linear(-6.0);
117        for (i, (l, r)) in left.iter().zip(right.iter()).enumerate() {
118            assert!(
119                (l - expected_gain).abs() < 0.001,
120                "Left sample {} has incorrect gain",
121                i
122            );
123            assert!(
124                (r - expected_gain).abs() < 0.001,
125                "Right sample {} has incorrect gain",
126                i
127            );
128        }
129    }
130
131    #[test]
132    fn test_negative_gain() {
133        let processor = GainProcessor::new(44100.0);
134        let mut left = [1.0];
135        let mut right = [1.0];
136
137        processor.process(&mut left, &mut right, -12.0);
138
139        let expected = db_to_linear(-12.0);
140        assert!(
141            (left[0] - expected).abs() < 0.001,
142            "Attenuation not applied correctly"
143        );
144    }
145
146    #[test]
147    fn test_positive_gain() {
148        let processor = GainProcessor::new(44100.0);
149        let mut left = [0.5];
150        let mut right = [0.5];
151
152        processor.process(&mut left, &mut right, 6.0);
153
154        let expected = 0.5 * db_to_linear(6.0);
155        assert!(
156            (left[0] - expected).abs() < 0.001,
157            "Boost not applied correctly"
158        );
159    }
160
161    #[test]
162    fn test_sample_rate_update() {
163        let mut processor = GainProcessor::new(44100.0);
164        assert!((processor.sample_rate() - 44100.0).abs() < 1e-6);
165
166        processor.set_sample_rate(48000.0);
167        assert!((processor.sample_rate() - 48000.0).abs() < 1e-6);
168    }
169}