Skip to main content

sim_lib_audio_dsp/
smoothing.rs

1use sim_lib_audio_graph_core::{BlockEvent, PrepareConfig, ProcessBlock, Processor};
2
3use crate::common::{input_sample, output_channels};
4
5/// A linearly ramped scalar that glides from its current value to a target over
6/// a fixed number of samples.
7#[derive(Clone, Copy, Debug, PartialEq)]
8pub struct SmoothValue {
9    current: f32,
10    target: f32,
11    step: f32,
12    remaining: u32,
13}
14
15impl SmoothValue {
16    /// Creates a smoothed value starting and resting at `value`.
17    pub fn new(value: f32) -> Self {
18        Self {
19            current: value,
20            target: value,
21            step: 0.0,
22            remaining: 0,
23        }
24    }
25
26    /// Returns the current (possibly mid-ramp) value.
27    pub fn current(&self) -> f32 {
28        self.current
29    }
30
31    /// Returns the target value being ramped toward.
32    pub fn target(&self) -> f32 {
33        self.target
34    }
35
36    /// Sets a new target reached over `samples` samples; `0` jumps immediately.
37    pub fn set_target(&mut self, target: f32, samples: u32) {
38        self.target = target;
39        if samples == 0 {
40            self.current = target;
41            self.step = 0.0;
42            self.remaining = 0;
43            return;
44        }
45        self.remaining = samples;
46        self.step = (target - self.current) / samples as f32;
47    }
48
49    /// Advances one sample along the ramp and returns the new current value.
50    pub fn next_sample(&mut self) -> f32 {
51        if self.remaining > 0 {
52            self.current += self.step;
53            self.remaining -= 1;
54            if self.remaining == 0 {
55                self.current = self.target;
56            }
57        }
58        self.current
59    }
60
61    /// Resets the ramp to rest immediately at `value`.
62    pub fn reset(&mut self, value: f32) {
63        *self = Self::new(value);
64    }
65}
66
67/// A [`Processor`] applying a click-free gain that ramps toward parameter-set
68/// targets received as block events.
69#[derive(Clone, Debug, PartialEq)]
70pub struct SmoothedGain {
71    gain: SmoothValue,
72    ramp_ms: f32,
73    param_id: u32,
74    sample_rate_hz: f32,
75}
76
77impl SmoothedGain {
78    /// Creates a smoothed gain at `initial_gain` ramping over `ramp_ms`.
79    pub fn new(initial_gain: f32, ramp_ms: f32) -> Self {
80        Self {
81            gain: SmoothValue::new(initial_gain),
82            ramp_ms,
83            param_id: 0,
84            sample_rate_hz: 48_000.0,
85        }
86    }
87
88    /// Returns the gain bound to react to the given parameter id.
89    pub fn with_param(mut self, param_id: u32) -> Self {
90        self.param_id = param_id;
91        self
92    }
93
94    fn ramp_samples(&self) -> u32 {
95        ((self.sample_rate_hz * self.ramp_ms.max(0.0)) / 1000.0).round() as u32
96    }
97}
98
99impl Processor for SmoothedGain {
100    fn prepare(&mut self, cfg: PrepareConfig) {
101        self.sample_rate_hz = cfg.sample_rate_hz as f32;
102    }
103
104    fn reset(&mut self) {
105        let value = self.gain.target();
106        self.gain.reset(value);
107    }
108
109    fn process(&mut self, block: &mut ProcessBlock<'_>) {
110        let frames = block.frames as usize;
111        let channels = output_channels(block);
112        for frame in 0..frames {
113            for event in block.in_events {
114                if let BlockEvent::ParamSet {
115                    offset,
116                    param,
117                    value,
118                } = *event
119                    && offset as usize == frame
120                    && param == self.param_id
121                {
122                    self.gain.set_target(value as f32, self.ramp_samples());
123                }
124            }
125            let gain = self.gain.next_sample();
126            for channel in 0..channels {
127                block.out_audio[channel][frame] = input_sample(block, channel, frame) * gain;
128            }
129        }
130    }
131}