Skip to main content

sim_lib_audio_dsp/
gain.rs

1use std::f32::consts::FRAC_PI_4;
2
3use sim_lib_audio_graph_core::{PrepareConfig, ProcessBlock, Processor};
4
5use crate::common::{input_sample, output_channels, prepare_channels};
6
7/// A [`Processor`] that scales every channel by a fixed linear gain.
8///
9/// # Examples
10///
11/// ```
12/// use sim_lib_audio_dsp::Gain;
13///
14/// let gain = Gain::new(0.5);
15/// assert_eq!(gain.gain(), 0.5);
16/// ```
17#[derive(Clone, Debug, PartialEq)]
18pub struct Gain {
19    gain: f32,
20}
21
22impl Gain {
23    /// Creates a gain processor with the given linear gain.
24    pub fn new(gain: f32) -> Self {
25        Self { gain }
26    }
27
28    /// Returns the linear gain.
29    pub fn gain(&self) -> f32 {
30        self.gain
31    }
32}
33
34impl Processor for Gain {
35    fn prepare(&mut self, _cfg: PrepareConfig) {}
36
37    fn reset(&mut self) {}
38
39    fn process(&mut self, block: &mut ProcessBlock<'_>) {
40        let frames = block.frames as usize;
41        for channel in 0..output_channels(block) {
42            for frame in 0..frames {
43                block.out_audio[channel][frame] = input_sample(block, channel, frame) * self.gain;
44            }
45        }
46    }
47}
48
49/// A [`Processor`] applying equal-power stereo panning.
50#[derive(Clone, Debug, PartialEq)]
51pub struct Pan {
52    pan: f32,
53}
54
55impl Pan {
56    /// Creates a pan processor; `pan` is clamped to `-1.0..=1.0` (left to
57    /// right).
58    pub fn new(pan: f32) -> Self {
59        Self {
60            pan: pan.clamp(-1.0, 1.0),
61        }
62    }
63
64    /// Returns the equal-power left and right channel gains.
65    pub fn gains(&self) -> (f32, f32) {
66        let angle = (self.pan + 1.0) * FRAC_PI_4;
67        (angle.cos(), angle.sin())
68    }
69}
70
71impl Processor for Pan {
72    fn prepare(&mut self, _cfg: PrepareConfig) {}
73
74    fn reset(&mut self) {}
75
76    fn process(&mut self, block: &mut ProcessBlock<'_>) {
77        let frames = block.frames as usize;
78        let (left_gain, right_gain) = self.gains();
79        match output_channels(block) {
80            0 => {}
81            1 => {
82                for frame in 0..frames {
83                    let mono = input_sample(block, 0, frame);
84                    block.out_audio[0][frame] = mono * (left_gain + right_gain) * 0.5;
85                }
86            }
87            _ => {
88                for frame in 0..frames {
89                    let left = input_sample(block, 0, frame);
90                    let right = if block.in_audio.len() > 1 {
91                        input_sample(block, 1, frame)
92                    } else {
93                        left
94                    };
95                    block.out_audio[0][frame] = left * left_gain;
96                    block.out_audio[1][frame] = right * right_gain;
97                }
98            }
99        }
100    }
101}
102
103#[derive(Clone, Copy, Debug, Default, PartialEq)]
104struct DcState {
105    x1: f32,
106    y1: f32,
107}
108
109/// A [`Processor`] that removes DC offset with a per-channel one-pole
110/// high-pass.
111#[derive(Clone, Debug, PartialEq)]
112pub struct DcBlocker {
113    coefficient: f32,
114    states: Vec<DcState>,
115}
116
117impl DcBlocker {
118    /// Creates a DC blocker; `coefficient` is clamped to `0.0..=0.9999`.
119    pub fn new(coefficient: f32) -> Self {
120        Self {
121            coefficient: coefficient.clamp(0.0, 0.9999),
122            states: Vec::new(),
123        }
124    }
125}
126
127impl Default for DcBlocker {
128    fn default() -> Self {
129        Self::new(0.995)
130    }
131}
132
133impl Processor for DcBlocker {
134    fn prepare(&mut self, cfg: PrepareConfig) {
135        prepare_channels(
136            &mut self.states,
137            cfg.out_channels as usize,
138            DcState::default(),
139        );
140    }
141
142    fn reset(&mut self) {
143        self.states.fill(DcState::default());
144    }
145
146    fn process(&mut self, block: &mut ProcessBlock<'_>) {
147        let channels = output_channels(block);
148        if self.states.len() < channels {
149            self.states.resize(channels, DcState::default());
150        }
151        let frames = block.frames as usize;
152        for channel in 0..channels {
153            let state = &mut self.states[channel];
154            for frame in 0..frames {
155                let input = input_sample(block, channel, frame);
156                let output = input - state.x1 + self.coefficient * state.y1;
157                state.x1 = input;
158                state.y1 = output;
159                block.out_audio[channel][frame] = output;
160            }
161        }
162    }
163}