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
use crate::synthesis::automation::Automation;
use crate::track::PRIORITY_TIME_BASED;
/// Standard audio sample rate
const DEFAULT_SAMPLE_RATE: f32 = 44100.0;
/// Delay effect with feedback
#[derive(Debug, Clone)]
pub struct Delay {
pub delay_time: f32, // Delay time in seconds
pub feedback: f32, // Feedback amount (0.0 to 0.99)
pub mix: f32, // Wet/dry mix (0.0 = dry, 1.0 = wet)
pub priority: u8, // Processing priority (lower = earlier in signal chain)
buffer: Vec<f32>,
write_pos: usize,
buffer_mask: usize, // Bit mask for fast modulo (buffer_len - 1)
// Automation (optional)
delay_time_automation: Option<Automation>,
feedback_automation: Option<Automation>,
mix_automation: Option<Automation>,
}
impl Delay {
/// Create a new delay effect with default sample rate (44100 Hz)
pub fn new(delay_time: f32, feedback: f32, mix: f32) -> Self {
Self::with_sample_rate(delay_time, feedback, mix, DEFAULT_SAMPLE_RATE)
}
/// Create a new delay effect with custom sample rate
pub fn with_sample_rate(delay_time: f32, feedback: f32, mix: f32, sample_rate: f32) -> Self {
let buffer_size = (delay_time * sample_rate) as usize;
// Round up to next power of 2 for fast modulo via bitwise AND
let buffer_size = buffer_size.max(1).next_power_of_two();
let buffer_mask = buffer_size - 1;
Self {
delay_time,
feedback: feedback.clamp(0.0, 0.99),
mix: mix.clamp(0.0, 1.0),
priority: PRIORITY_TIME_BASED, // Time-based effects typically come late in chain
buffer: vec![0.0; buffer_size],
write_pos: 0,
buffer_mask,
delay_time_automation: None,
feedback_automation: None,
mix_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
pub fn with_mix_automation(mut self, automation: Automation) -> Self {
self.mix_automation = Some(automation);
self
}
/// Add automation for the feedback parameter
pub fn with_feedback_automation(mut self, automation: Automation) -> Self {
self.feedback_automation = Some(automation);
self
}
/// Add automation for the delay time parameter
pub fn with_delay_time_automation(mut self, automation: Automation) -> Self {
self.delay_time_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)
// 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.feedback_automation {
self.feedback = auto.value_at(time).clamp(0.0, 0.99);
}
if let Some(auto) = &self.delay_time_automation {
self.delay_time = auto.value_at(time).clamp(0.001, 10.0);
}
}
// Early exit for bypassed effect
if self.mix < 0.0001 {
return input;
}
// Read from delay buffer
let delayed = self.buffer[self.write_pos];
// Write input + feedback to buffer using FMA
self.buffer[self.write_pos] = delayed.mul_add(self.feedback, input);
// Advance write position using bitwise AND (~10x faster than modulo!)
self.write_pos = (self.write_pos + 1) & self.buffer_mask;
// Mix dry and wet signals using FMA
input.mul_add(1.0 - self.mix, delayed * 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.feedback_automation {
self.feedback = auto.value_at(time).clamp(0.0, 0.99);
}
if let Some(auto) = &self.delay_time_automation {
self.delay_time = auto.value_at(time).clamp(0.001, 10.0);
}
}
// Early exit if effect is bypassed
if self.mix < 0.0001 {
return;
}
// Pre-calculate constants once for entire buffer
let feedback = self.feedback;
let mix_wet = self.mix;
let mix_dry = 1.0 - self.mix;
// Process entire buffer
for sample in buffer.iter_mut() {
let input = *sample;
// Read from delay buffer
let delayed = self.buffer[self.write_pos];
// Write input + feedback to buffer using FMA
self.buffer[self.write_pos] = delayed.mul_add(feedback, input);
// Advance write position using bitwise AND (~10x faster than modulo!)
self.write_pos = (self.write_pos + 1) & self.buffer_mask;
// Mix dry and wet signals using FMA
*sample = input.mul_add(mix_dry, delayed * mix_wet);
}
}
/// Reset the delay buffer
pub fn reset(&mut self) {
self.buffer.fill(0.0);
self.write_pos = 0;
}
// ========== PRESETS ==========
/// Eighth note delay (125ms at 120 BPM) with subtle feedback
pub fn eighth_note() -> Self {
Self::new(0.125, 0.35, 0.3)
}
/// Quarter note delay (250ms at 120 BPM) with moderate feedback
pub fn quarter_note() -> Self {
Self::new(0.25, 0.4, 0.35)
}
/// Dotted eighth delay (187.5ms at 120 BPM) - classic U2/Edge sound
pub fn dotted_eighth() -> Self {
Self::new(0.1875, 0.35, 0.3)
}
/// Half note delay (500ms at 120 BPM) with spacious feedback
pub fn half_note() -> Self {
Self::new(0.5, 0.45, 0.4)
}
/// Slapback delay (80ms) with no feedback - classic rockabilly/country sound
pub fn slapback() -> Self {
Self::new(0.08, 0.0, 0.3)
}
/// Ping-pong style delay with higher feedback for multiple repeats
pub fn ping_pong() -> Self {
Self::new(0.375, 0.5, 0.4)
}
/// Subtle doubling effect (30ms) for thickening vocals/instruments
pub fn doubling() -> Self {
Self::new(0.03, 0.0, 0.2)
}
/// Long ambient delay (1 second) with high feedback for soundscapes
pub fn ambient() -> Self {
Self::new(1.0, 0.6, 0.5)
}
}