sim_lib_audio_dsp/
modulation.rs1use std::f32::consts::TAU;
2
3use sim_lib_audio_graph_core::{PrepareConfig, ProcessBlock, Processor};
4
5use crate::{
6 common::{input_sample, prepare_channels, prepared_output_channels},
7 delay::DelayLine,
8};
9
10#[derive(Clone, Debug, PartialEq)]
13pub struct ModulatedDelayProcessor {
14 base_delay_seconds: f32,
15 depth_seconds: f32,
16 rate_hz: f32,
17 feedback: f32,
18 wet: f32,
19 dry: f32,
20 sample_rate_hz: f32,
21 phase: f32,
22 lines: Vec<DelayLine>,
23}
24
25impl ModulatedDelayProcessor {
26 pub fn new(base_delay_ms: f32, depth_ms: f32, rate_hz: f32) -> Self {
29 Self {
30 base_delay_seconds: (base_delay_ms / 1000.0).max(0.0),
31 depth_seconds: (depth_ms / 1000.0).max(0.0),
32 rate_hz: rate_hz.max(0.0),
33 feedback: 0.0,
34 wet: 0.5,
35 dry: 0.5,
36 sample_rate_hz: 48_000.0,
37 phase: 0.0,
38 lines: Vec::new(),
39 }
40 }
41
42 pub fn with_feedback(mut self, feedback: f32) -> Self {
44 self.feedback = feedback.clamp(-0.99, 0.99);
45 self
46 }
47
48 pub fn with_mix(mut self, dry: f32, wet: f32) -> Self {
50 self.dry = dry;
51 self.wet = wet;
52 self
53 }
54
55 fn max_delay_samples(&self) -> usize {
56 ((self.base_delay_seconds + self.depth_seconds) * self.sample_rate_hz).ceil() as usize + 2
57 }
58
59 fn current_delay_samples(&self) -> f32 {
60 let lfo = self.phase.sin() * 0.5 + 0.5;
61 (self.base_delay_seconds + self.depth_seconds * lfo) * self.sample_rate_hz
62 }
63
64 fn advance_phase(&mut self) {
65 if self.sample_rate_hz > 0.0 {
66 self.phase = (self.phase + TAU * self.rate_hz / self.sample_rate_hz).rem_euclid(TAU);
67 }
68 }
69
70 #[cfg(all(test, not(debug_assertions)))]
71 pub(crate) fn realtime_state_snapshot(&self) -> Vec<usize> {
72 let mut snapshot = Vec::with_capacity(self.lines.len() + 1);
73 snapshot.push(self.lines.capacity());
74 snapshot.extend(self.lines.iter().map(DelayLine::allocated_capacity));
75 snapshot
76 }
77}
78
79impl Processor for ModulatedDelayProcessor {
80 fn prepare(&mut self, cfg: PrepareConfig) {
81 self.sample_rate_hz = cfg.sample_rate_hz as f32;
82 let line = DelayLine::new(self.max_delay_samples());
83 prepare_channels(&mut self.lines, cfg.out_channels as usize, line);
84 }
85
86 fn reset(&mut self) {
87 self.phase = 0.0;
88 for line in &mut self.lines {
89 line.reset();
90 }
91 }
92
93 fn process(&mut self, block: &mut ProcessBlock<'_>) {
94 let channels = prepared_output_channels(block, self.lines.len(), "ModulatedDelayProcessor");
95 let frames = block.frames as usize;
96 for frame in 0..frames {
97 let delay = self.current_delay_samples();
98 for channel in 0..channels {
99 let input = input_sample(block, channel, frame);
100 let line = &mut self.lines[channel];
101 let delayed = line.read(delay);
102 line.push(input + delayed * self.feedback);
103 block.out_audio[channel][frame] = input * self.dry + delayed * self.wet;
104 }
105 self.advance_phase();
106 }
107 }
108}
109
110#[derive(Clone, Debug, PartialEq)]
112pub struct Chorus {
113 inner: ModulatedDelayProcessor,
114}
115
116impl Chorus {
117 pub fn new(rate_hz: f32, depth_ms: f32) -> Self {
119 Self {
120 inner: ModulatedDelayProcessor::new(18.0, depth_ms, rate_hz).with_mix(0.65, 0.35),
121 }
122 }
123
124 #[cfg(all(test, not(debug_assertions)))]
125 pub(crate) fn realtime_state_snapshot(&self) -> Vec<usize> {
126 self.inner.realtime_state_snapshot()
127 }
128}
129
130impl Processor for Chorus {
131 fn prepare(&mut self, cfg: PrepareConfig) {
132 self.inner.prepare(cfg);
133 }
134
135 fn reset(&mut self) {
136 self.inner.reset();
137 }
138
139 fn process(&mut self, block: &mut ProcessBlock<'_>) {
140 self.inner.process(block);
141 }
142}
143
144#[derive(Clone, Debug, PartialEq)]
146pub struct Flanger {
147 inner: ModulatedDelayProcessor,
148}
149
150impl Flanger {
151 pub fn new(rate_hz: f32, depth_ms: f32, feedback: f32) -> Self {
153 Self {
154 inner: ModulatedDelayProcessor::new(2.5, depth_ms, rate_hz)
155 .with_feedback(feedback)
156 .with_mix(0.55, 0.45),
157 }
158 }
159
160 #[cfg(all(test, not(debug_assertions)))]
161 pub(crate) fn realtime_state_snapshot(&self) -> Vec<usize> {
162 self.inner.realtime_state_snapshot()
163 }
164}
165
166impl Processor for Flanger {
167 fn prepare(&mut self, cfg: PrepareConfig) {
168 self.inner.prepare(cfg);
169 }
170
171 fn reset(&mut self) {
172 self.inner.reset();
173 }
174
175 fn process(&mut self, block: &mut ProcessBlock<'_>) {
176 self.inner.process(block);
177 }
178}
179
180#[derive(Clone, Debug, PartialEq)]
182pub struct Vibrato {
183 inner: ModulatedDelayProcessor,
184}
185
186impl Vibrato {
187 pub fn new(rate_hz: f32, depth_ms: f32) -> Self {
189 Self {
190 inner: ModulatedDelayProcessor::new(depth_ms, depth_ms, rate_hz).with_mix(0.0, 1.0),
191 }
192 }
193
194 #[cfg(all(test, not(debug_assertions)))]
195 pub(crate) fn realtime_state_snapshot(&self) -> Vec<usize> {
196 self.inner.realtime_state_snapshot()
197 }
198}
199
200impl Processor for Vibrato {
201 fn prepare(&mut self, cfg: PrepareConfig) {
202 self.inner.prepare(cfg);
203 }
204
205 fn reset(&mut self) {
206 self.inner.reset();
207 }
208
209 fn process(&mut self, block: &mut ProcessBlock<'_>) {
210 self.inner.process(block);
211 }
212}