1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
use crate::synthesis::automation::Automation;
use crate::track::PRIORITY_SPATIAL;
/// Standard audio sample rate
const DEFAULT_SAMPLE_RATE: f32 = 44100.0;
/// Simple reverb using multiple comb filters
#[derive(Debug, Clone)]
pub struct Reverb {
pub room_size: f32, // Room size (0.0 to 1.0)
pub damping: f32, // High frequency damping (0.0 to 1.0)
pub mix: f32, // Wet/dry mix (0.0 = dry, 1.0 = wet)
pub priority: u8, // Processing priority (lower = earlier in signal chain)
comb_buffers: Vec<Vec<f32>>,
comb_positions: Vec<usize>,
comb_masks: Vec<usize>, // Bit masks for fast modulo
filter_state: Vec<f32>,
// Automation (optional)
mix_automation: Option<Automation>,
room_size_automation: Option<Automation>,
damping_automation: Option<Automation>,
}
impl Reverb {
/// Create a new reverb effect with default sample rate (44100 Hz)
pub fn new(room_size: f32, damping: f32, mix: f32) -> Self {
Self::with_sample_rate(room_size, damping, mix, DEFAULT_SAMPLE_RATE)
}
/// Create a new reverb effect with custom sample rate
pub fn with_sample_rate(room_size: f32, damping: f32, mix: f32, sample_rate: f32) -> Self {
// Prime numbers for comb filter delays (in samples) - scaled by room size
let base_delays = [1557, 1617, 1491, 1422, 1277, 1356, 1188, 1116];
let scale = 1.0 + room_size * 2.0;
let comb_buffers: Vec<Vec<f32>> = base_delays
.iter()
.map(|&delay| {
let size = ((delay as f32 * scale * sample_rate) / 44100.0) as usize;
// Round up to next power of 2 for fast modulo via bitwise AND
let size = size.max(1).next_power_of_two();
vec![0.0; size]
})
.collect();
// Calculate masks for each buffer (size - 1)
let comb_masks: Vec<usize> = comb_buffers.iter().map(|buf| buf.len() - 1).collect();
Self {
room_size: room_size.clamp(0.0, 1.0),
damping: damping.clamp(0.0, 1.0),
mix: mix.clamp(0.0, 1.0),
priority: PRIORITY_SPATIAL, // Reverb typically comes last in chain
comb_positions: vec![0; comb_buffers.len()],
comb_masks,
filter_state: vec![0.0; comb_buffers.len()],
comb_buffers,
mix_automation: None,
room_size_automation: None,
damping_automation: None,
}
}
/// Set the processing priority (lower = earlier in signal chain)
pub fn with_priority(mut self, priority: u8) -> Self {
self.priority = priority;
self
}
/// Add automation for the mix parameter
///
/// # Example
/// ```no_run
/// use tunes::prelude::*;
///
/// let reverb = Reverb::new(0.7, 0.6, 0.0)
/// .with_mix_automation(Automation::linear(&[
/// (0.0, 0.0), // Start dry
/// (4.0, 0.8), // Fade in over 4 seconds
/// ]));
/// ```
pub fn with_mix_automation(mut self, automation: Automation) -> Self {
self.mix_automation = Some(automation);
self
}
/// Add automation for the room size parameter
pub fn with_room_size_automation(mut self, automation: Automation) -> Self {
self.room_size_automation = Some(automation);
self
}
/// Add automation for the damping parameter
pub fn with_damping_automation(mut self, automation: Automation) -> Self {
self.damping_automation = Some(automation);
self
}
/// Process a single sample
///
/// # Arguments
/// * `input` - Input sample
/// * `time` - Current time in seconds (for automation)
/// * `sample_count` - Global sample counter (for quantized automation lookups)
#[inline]
pub fn process(&mut self, input: f32, time: f32, sample_count: u64) -> f32 {
// Quantized automation lookups (every 64 samples = 1.45ms @ 44.1kHz)
// This reduces automation overhead by 64x with no perceptible quality loss
// Use bitwise AND instead of modulo for power-of-2
if sample_count & 63 == 0 {
if let Some(auto) = &self.mix_automation {
self.mix = auto.value_at(time).clamp(0.0, 1.0);
}
if let Some(auto) = &self.room_size_automation {
self.room_size = auto.value_at(time).clamp(0.0, 1.0);
}
if let Some(auto) = &self.damping_automation {
self.damping = auto.value_at(time).clamp(0.0, 1.0);
}
}
if self.mix < 0.0001 {
return input;
}
let mut output = 0.0;
let feedback = self.room_size.mul_add(0.48, 0.5);
let inv_damping = 1.0 - self.damping;
// Process through all comb filters
for i in 0..self.comb_buffers.len() {
let buffer = &mut self.comb_buffers[i];
let pos = self.comb_positions[i];
// Read from buffer
let delayed = buffer[pos];
// Apply damping filter (simple lowpass) using FMA
self.filter_state[i] =
delayed.mul_add(inv_damping, self.filter_state[i] * self.damping);
// Write to buffer with feedback using FMA
buffer[pos] = self.filter_state[i].mul_add(feedback, input);
// Advance position using bitwise AND (~10x faster than modulo!)
self.comb_positions[i] = (pos + 1) & self.comb_masks[i];
// Accumulate output
output += delayed;
}
// Average and mix using FMA
output /= self.comb_buffers.len() as f32;
input.mul_add(1.0 - self.mix, output * self.mix)
}
/// Process a block of samples with optimized buffer processing
///
/// # Arguments
/// * `buffer` - Buffer of samples to process in-place
/// * `time` - Starting time in seconds (for automation)
/// * `sample_count` - Starting sample counter (for quantized automation lookups)
/// * `sample_rate` - Sample rate in Hz (unused but kept for API consistency)
#[inline]
pub fn process_block(&mut self, buffer: &mut [f32], time: f32, sample_count: u64, _sample_rate: f32) {
// Update automation parameters once at buffer start (quantized to 64-sample blocks)
if sample_count & 63 == 0 {
if let Some(auto) = &self.mix_automation {
self.mix = auto.value_at(time).clamp(0.0, 1.0);
}
if let Some(auto) = &self.room_size_automation {
self.room_size = auto.value_at(time).clamp(0.0, 1.0);
}
if let Some(auto) = &self.damping_automation {
self.damping = auto.value_at(time).clamp(0.0, 1.0);
}
}
// Early exit if effect is bypassed
if self.mix < 0.0001 {
return;
}
// Pre-calculate constants once for entire buffer
let feedback = self.room_size.mul_add(0.48, 0.5);
let inv_damping = 1.0 - self.damping;
let num_combs = self.comb_buffers.len();
let mix_wet = self.mix;
let mix_dry = 1.0 - self.mix;
let output_scale = 1.0 / num_combs as f32;
// Process entire buffer
for sample in buffer.iter_mut() {
let input = *sample;
let mut output = 0.0;
// Process through all comb filters
for i in 0..num_combs {
let buffer = &mut self.comb_buffers[i];
let pos = self.comb_positions[i];
// Read from delay buffer
let delayed = buffer[pos];
// Apply damping filter (simple lowpass) using FMA
self.filter_state[i] =
delayed.mul_add(inv_damping, self.filter_state[i] * self.damping);
// Write to buffer with feedback using FMA
buffer[pos] = self.filter_state[i].mul_add(feedback, input);
// Advance position using bitwise AND (~10x faster than modulo!)
self.comb_positions[i] = (pos + 1) & self.comb_masks[i];
// Accumulate output
output += delayed;
}
// Average and mix using FMA
output *= output_scale;
*sample = input.mul_add(mix_dry, output * mix_wet);
}
}
/// Reset the reverb state
pub fn reset(&mut self) {
for buffer in &mut self.comb_buffers {
buffer.fill(0.0);
}
self.comb_positions.fill(0);
self.filter_state.fill(0.0);
}
// ========== PRESETS ==========
/// Small room reverb - intimate, subtle space
pub fn room() -> Self {
Self::new(0.3, 0.7, 0.2)
}
/// Large concert hall - spacious, balanced
pub fn hall() -> Self {
Self::new(0.8, 0.5, 0.3)
}
/// Plate reverb - bright, dense reflections
pub fn plate() -> Self {
Self::new(0.5, 0.3, 0.25)
}
/// Chamber reverb - medium space with warmth
pub fn chamber() -> Self {
Self::new(0.6, 0.6, 0.25)
}
/// Cathedral/church - huge, long decay
pub fn cathedral() -> Self {
Self::new(0.95, 0.4, 0.4)
}
/// Ambient soundscape - massive space, lush
pub fn ambient() -> Self {
Self::new(0.9, 0.4, 0.5)
}
/// Subtle room presence - barely noticeable
pub fn subtle() -> Self {
Self::new(0.4, 0.6, 0.15)
}
/// Spring reverb - vintage, characteristic boing
pub fn spring() -> Self {
Self::new(0.4, 0.8, 0.3)
}
}