Skip to main content

wavecraft_processors/
gain.rs

1//! Gain processor - amplifies or attenuates audio signals.
2
3use wavecraft_dsp::{ParamRange, ParamSpec, Processor, ProcessorParams, Transport};
4
5/// Parameter struct for gain processor.
6#[derive(Debug, Default, Clone)]
7pub struct GainParams {
8    /// Gain level in linear amplitude (0.0 = silence, 1.0 = unity, >1.0 = boost).
9    pub level: f32,
10}
11
12impl ProcessorParams for GainParams {
13    fn param_specs() -> &'static [ParamSpec] {
14        static SPECS: [ParamSpec; 1] = [ParamSpec {
15            name: "Level",
16            id_suffix: "level",
17            range: ParamRange::Skewed {
18                min: 0.0,
19                max: 2.0,
20                factor: 2.5, // Logarithmic feel
21            },
22            default: 1.0,
23            unit: "x",
24            group: None,
25        }];
26        &SPECS
27    }
28
29    fn from_param_defaults() -> Self {
30        Self { level: 1.0 }
31    }
32
33    fn apply_plain_values(&mut self, values: &[f32]) {
34        if let Some(level) = values.first() {
35            self.level = *level;
36        }
37    }
38}
39
40/// Gain processor - applies amplitude scaling to audio.
41///
42/// This is a simple but essential DSP building block that multiplies
43/// all samples by a gain factor.
44#[derive(Debug, Default)]
45pub struct GainDsp {
46    _sample_rate: f32,
47}
48
49impl Processor for GainDsp {
50    type Params = GainParams;
51
52    fn process(
53        &mut self,
54        buffer: &mut [&mut [f32]],
55        _transport: &Transport,
56        params: &Self::Params,
57    ) {
58        let gain = params.level;
59
60        for channel in buffer.iter_mut() {
61            apply_gain_to_channel(channel, gain);
62        }
63    }
64
65    fn set_sample_rate(&mut self, sample_rate: f32) {
66        self._sample_rate = sample_rate;
67    }
68
69    fn reset(&mut self) {
70        // No state to reset
71    }
72}
73
74#[inline]
75fn apply_gain_to_channel(channel: &mut [f32], gain: f32) {
76    for sample in channel.iter_mut() {
77        *sample *= gain;
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    fn process_with_gain(buffer: &mut [&mut [f32]], level: f32) {
86        let mut processor = GainDsp::default();
87        let params = GainParams { level };
88        let transport = Transport::default();
89        processor.process(buffer, &transport, &params);
90    }
91
92    fn assert_close(actual: f32, expected: f32) {
93        assert!((actual - expected).abs() < 1e-6);
94    }
95
96    #[test]
97    fn test_unity_gain() {
98        let mut left = [0.5, -0.5, 0.25];
99        let mut right = [0.3, -0.3, 0.1];
100        let mut buffer = [&mut left[..], &mut right[..]];
101
102        process_with_gain(&mut buffer, 1.0);
103
104        assert_close(left[0], 0.5);
105        assert_close(left[1], -0.5);
106        assert_close(right[0], 0.3);
107    }
108
109    #[test]
110    fn test_boost() {
111        let mut left = [1.0];
112        let mut buffer = [&mut left[..]];
113
114        process_with_gain(&mut buffer, 2.0);
115
116        assert_close(left[0], 2.0);
117    }
118
119    #[test]
120    fn test_attenuation() {
121        let mut left = [1.0];
122        let mut buffer = [&mut left[..]];
123
124        process_with_gain(&mut buffer, 0.5);
125
126        assert_close(left[0], 0.5);
127    }
128
129    #[test]
130    fn test_param_specs() {
131        let specs = GainParams::param_specs();
132        assert_eq!(specs.len(), 1);
133        assert_eq!(specs[0].name, "Level");
134        assert_eq!(specs[0].id_suffix, "level");
135        assert_eq!(specs[0].default, 1.0);
136        assert_eq!(specs[0].unit, "x");
137    }
138
139    #[test]
140    fn test_from_param_defaults_uses_spec_default() {
141        let defaults = GainParams::from_param_defaults();
142        assert!((defaults.level - 1.0).abs() < 1e-6);
143    }
144}