proteus_lib/dsp/effects/diffusion_reverb/
mod.rs1use log::info;
12use serde::{Deserialize, Serialize};
13
14use super::EffectContext;
15
16const DEFAULT_PRE_DELAY_MS: u64 = 12;
17const DEFAULT_ROOM_SIZE_MS: u64 = 48;
18const DEFAULT_DECAY: f32 = 0.72;
19const DEFAULT_DAMPING: f32 = 0.35;
20const DEFAULT_DIFFUSION: f32 = 0.72;
21const MAX_DECAY: f32 = 0.98;
22const MAX_DAMPING: f32 = 0.99;
23const MAX_DIFFUSION: f32 = 0.9;
24
25const COMB_TUNING_MULTIPLIERS: [f32; 4] = [1.0, 1.33, 1.58, 1.91];
26const ALLPASS_TUNING_MULTIPLIERS: [f32; 2] = [0.28, 0.52];
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(default)]
31pub struct DiffusionReverbSettings {
32 pub pre_delay_ms: u64,
34 pub room_size_ms: u64,
36 pub decay: f32,
38 pub damping: f32,
40 pub diffusion: f32,
42}
43
44impl DiffusionReverbSettings {
45 pub fn new(
57 pre_delay_ms: u64,
58 room_size_ms: u64,
59 decay: f32,
60 damping: f32,
61 diffusion: f32,
62 ) -> Self {
63 Self {
64 pre_delay_ms: pre_delay_ms.clamp(0, u64::MAX),
65 room_size_ms: room_size_ms.clamp(0, u64::MAX),
66 decay: decay.clamp(0.0, MAX_DECAY),
67 damping: damping.clamp(0.0, MAX_DAMPING),
68 diffusion: diffusion.clamp(0.0, MAX_DIFFUSION),
69 }
70 }
71
72 fn decay(&self) -> f32 {
73 self.decay.clamp(0.0, MAX_DECAY)
74 }
75
76 fn damping(&self) -> f32 {
77 self.damping.clamp(0.0, MAX_DAMPING)
78 }
79
80 fn diffusion(&self) -> f32 {
81 self.diffusion.clamp(0.0, MAX_DIFFUSION)
82 }
83}
84
85impl Default for DiffusionReverbSettings {
86 fn default() -> Self {
87 Self {
88 pre_delay_ms: DEFAULT_PRE_DELAY_MS,
89 room_size_ms: DEFAULT_ROOM_SIZE_MS,
90 decay: DEFAULT_DECAY,
91 damping: DEFAULT_DAMPING,
92 diffusion: DEFAULT_DIFFUSION,
93 }
94 }
95}
96
97#[derive(Clone, Serialize, Deserialize)]
99#[serde(default)]
100pub struct DiffusionReverbEffect {
101 pub enabled: bool,
102 #[serde(alias = "dry_wet", alias = "wet_dry")]
103 pub mix: f32,
104 #[serde(flatten)]
105 pub settings: DiffusionReverbSettings,
106 #[serde(skip)]
107 state: Option<DiffusionReverbState>,
108}
109
110impl Default for DiffusionReverbEffect {
111 fn default() -> Self {
112 Self {
113 enabled: true,
114 mix: 0.0,
115 settings: DiffusionReverbSettings::default(),
116 state: None,
117 }
118 }
119}
120
121impl std::fmt::Debug for DiffusionReverbEffect {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 f.debug_struct("DiffusionReverbEffect")
124 .field("enabled", &self.enabled)
125 .field("mix", &self.mix)
126 .field("settings", &self.settings)
127 .finish()
128 }
129}
130
131impl DiffusionReverbEffect {
132 pub fn new(mix: f32) -> Self {
140 Self {
141 mix: mix.clamp(0.0, 1.0),
142 ..Default::default()
143 }
144 }
145
146 pub fn process(&mut self, samples: &[f32], context: &EffectContext, drain: bool) -> Vec<f32> {
156 self.ensure_state(context);
157 if !self.enabled || self.mix <= 0.0 {
158 return samples.to_vec();
159 }
160
161 let Some(state) = self.state.as_mut() else {
162 return samples.to_vec();
163 };
164
165 if samples.is_empty() {
166 if drain {
167 return state.drain_tail(
168 self.settings.decay(),
169 self.settings.damping(),
170 self.settings.diffusion(),
171 );
172 }
173 return Vec::new();
174 }
175
176 let mix = self.mix.clamp(0.0, 1.0);
177 let mut output = Vec::with_capacity(samples.len());
178 state.process_samples(
179 samples,
180 mix,
181 self.settings.decay(),
182 self.settings.damping(),
183 self.settings.diffusion(),
184 &mut output,
185 );
186 output
187 }
188
189 pub fn reset_state(&mut self) {
194 if let Some(state) = self.state.as_mut() {
195 state.reset();
196 }
197 self.state = None;
198 }
199
200 pub fn settings_mut(&mut self) -> &mut DiffusionReverbSettings {
202 &mut self.settings
203 }
204
205 fn ensure_state(&mut self, context: &EffectContext) {
206 let pre_delay_samples = delay_samples(
207 context.sample_rate,
208 context.channels,
209 self.settings.pre_delay_ms,
210 );
211 let room_size_samples = delay_samples(
212 context.sample_rate,
213 context.channels,
214 self.settings.room_size_ms,
215 );
216 let tuning = Tuning::new(pre_delay_samples, room_size_samples);
217 let needs_reset = self
218 .state
219 .as_ref()
220 .map(|state| state.tuning != tuning)
221 .unwrap_or(true);
222 if needs_reset {
223 self.state = Some(DiffusionReverbState::new(tuning));
224 }
225 }
226}
227
228#[derive(Debug, Clone, Copy, PartialEq)]
229struct Tuning {
230 pre_delay_samples: usize,
231 comb_samples: [usize; 4],
232 allpass_samples: [usize; 2],
233 max_delay: usize,
234}
235
236impl Tuning {
237 fn new(pre_delay_samples: usize, room_size_samples: usize) -> Self {
238 let comb_samples = COMB_TUNING_MULTIPLIERS
239 .map(|multiplier| (room_size_samples as f32 * multiplier).round() as usize);
240 let allpass_samples = ALLPASS_TUNING_MULTIPLIERS
241 .map(|multiplier| (room_size_samples as f32 * multiplier).round() as usize);
242 let max_delay = comb_samples
243 .iter()
244 .copied()
245 .chain(allpass_samples.iter().copied())
246 .chain([pre_delay_samples])
247 .max()
248 .unwrap_or(1)
249 .max(1);
250 Self {
251 pre_delay_samples: pre_delay_samples.max(1),
252 comb_samples: comb_samples.map(|value| value.max(1)),
253 allpass_samples: allpass_samples.map(|value| value.max(1)),
254 max_delay,
255 }
256 }
257}
258
259#[derive(Clone)]
260struct DiffusionReverbState {
261 tuning: Tuning,
262 pre_delay: DelayLine,
263 combs: [CombFilter; 4],
264 allpass: [AllpassFilter; 2],
265}
266
267impl DiffusionReverbState {
268 fn new(tuning: Tuning) -> Self {
269 info!("Using Diffusion Reverb!");
270 Self {
271 tuning,
272 pre_delay: DelayLine::new(tuning.pre_delay_samples),
273 combs: [
274 CombFilter::new(tuning.comb_samples[0]),
275 CombFilter::new(tuning.comb_samples[1]),
276 CombFilter::new(tuning.comb_samples[2]),
277 CombFilter::new(tuning.comb_samples[3]),
278 ],
279 allpass: [
280 AllpassFilter::new(tuning.allpass_samples[0]),
281 AllpassFilter::new(tuning.allpass_samples[1]),
282 ],
283 }
284 }
285
286 fn reset(&mut self) {
287 self.pre_delay.reset();
288 for comb in &mut self.combs {
289 comb.reset();
290 }
291 for allpass in &mut self.allpass {
292 allpass.reset();
293 }
294 }
295
296 fn process_samples(
297 &mut self,
298 samples: &[f32],
299 mix: f32,
300 decay: f32,
301 damping: f32,
302 diffusion: f32,
303 out: &mut Vec<f32>,
304 ) {
305 for &sample in samples {
306 let delayed = self.pre_delay.process(sample);
307 let mut comb_sum = 0.0;
308 for comb in &mut self.combs {
309 comb_sum += comb.process(delayed, decay, damping);
310 }
311 let mut wet = comb_sum * 0.25;
312 for allpass in &mut self.allpass {
313 wet = allpass.process(wet, diffusion);
314 }
315 let output = sample * (1.0 - mix) + wet * mix;
316 out.push(output);
317 }
318 }
319
320 fn drain_tail(&mut self, decay: f32, damping: f32, diffusion: f32) -> Vec<f32> {
321 let tail_samples = self.tuning.max_delay.saturating_mul(4).max(1);
322 let mut out = Vec::with_capacity(tail_samples);
323 for _ in 0..tail_samples {
324 let delayed = self.pre_delay.process(0.0);
325 let mut comb_sum = 0.0;
326 for comb in &mut self.combs {
327 comb_sum += comb.process(delayed, decay, damping);
328 }
329 let mut wet = comb_sum * 0.25;
330 for allpass in &mut self.allpass {
331 wet = allpass.process(wet, diffusion);
332 }
333 out.push(wet);
334 }
335 out
336 }
337}
338
339#[derive(Clone)]
340struct DelayLine {
341 buffer: Vec<f32>,
342 index: usize,
343}
344
345impl DelayLine {
346 fn new(len: usize) -> Self {
347 Self {
348 buffer: vec![0.0; len.max(1)],
349 index: 0,
350 }
351 }
352
353 fn reset(&mut self) {
354 self.buffer.fill(0.0);
355 self.index = 0;
356 }
357
358 fn process(&mut self, input: f32) -> f32 {
359 let output = self.buffer[self.index];
360 self.buffer[self.index] = input;
361 self.index += 1;
362 if self.index >= self.buffer.len() {
363 self.index = 0;
364 }
365 output
366 }
367}
368
369#[derive(Clone)]
370struct CombFilter {
371 buffer: Vec<f32>,
372 index: usize,
373 lowpass: f32,
374}
375
376impl CombFilter {
377 fn new(len: usize) -> Self {
378 Self {
379 buffer: vec![0.0; len.max(1)],
380 index: 0,
381 lowpass: 0.0,
382 }
383 }
384
385 fn reset(&mut self) {
386 self.buffer.fill(0.0);
387 self.index = 0;
388 self.lowpass = 0.0;
389 }
390
391 fn process(&mut self, input: f32, feedback: f32, damping: f32) -> f32 {
392 let delayed = self.buffer[self.index];
393 self.lowpass = delayed * (1.0 - damping) + self.lowpass * damping;
394 let output = self.lowpass;
395 self.buffer[self.index] = input + output * feedback;
396 self.index += 1;
397 if self.index >= self.buffer.len() {
398 self.index = 0;
399 }
400 output
401 }
402}
403
404#[derive(Clone)]
405struct AllpassFilter {
406 buffer: Vec<f32>,
407 index: usize,
408}
409
410impl AllpassFilter {
411 fn new(len: usize) -> Self {
412 Self {
413 buffer: vec![0.0; len.max(1)],
414 index: 0,
415 }
416 }
417
418 fn reset(&mut self) {
419 self.buffer.fill(0.0);
420 self.index = 0;
421 }
422
423 fn process(&mut self, input: f32, feedback: f32) -> f32 {
424 let delayed = self.buffer[self.index];
425 let output = delayed - feedback * input;
426 self.buffer[self.index] = input + delayed * feedback;
427 self.index += 1;
428 if self.index >= self.buffer.len() {
429 self.index = 0;
430 }
431 output
432 }
433}
434
435fn delay_samples(sample_rate: u32, channels: usize, duration_ms: u64) -> usize {
436 if duration_ms == 0 {
437 return 0;
438 }
439 let ns = duration_ms.saturating_mul(1_000_000);
440 let samples = ns.saturating_mul(sample_rate as u64) / 1_000_000_000 * channels as u64;
441 samples as usize
442}