Vital
Loading...
Searching...
No Matches
delay.cpp
Go to the documentation of this file.
1#include "delay.h"
2
3#include "futils.h"
4#include "memory.h"
5#include "synth_constants.h"
6
7namespace vital {
8
9 namespace {
16 force_inline poly_float saturate(poly_float value) {
17 return futils::hardTanh(value);
18 }
19
26 force_inline poly_float saturateLarge(poly_float value) {
27 static constexpr float kRatio = 8.0f;
28 static constexpr float kMult = 1.0f / kRatio;
29 return futils::hardTanh(value * kMult) * kRatio;
30 }
31 }
32
33 //============================================================================
34 //
35 // Delay - Templated Methods
36 //
37 //============================================================================
38
42 template<class MemoryType>
44 memory_->clearAll();
45
46 filter_gain_ = 0.0f;
47 low_pass_.reset(constants::kFullMask);
48 high_pass_.reset(constants::kFullMask);
49 }
50
56 template<class MemoryType>
57 void Delay<MemoryType>::setMaxSamples(int max_samples) {
58 memory_ = std::make_unique<MemoryType>(max_samples);
59 period_ = utils::min(period_, max_samples - 1);
60 }
61
67 template<class MemoryType>
68 void Delay<MemoryType>::process(int num_samples) {
69 VITAL_ASSERT(inputMatchesBufferSize(kAudio));
70 processWithInput(input(kAudio)->source->buffer, num_samples);
71 }
72
82 template<class MemoryType>
83 void Delay<MemoryType>::processWithInput(const poly_float* audio_in, int num_samples) {
84 VITAL_ASSERT(checkInputAndOutputSize(num_samples));
85
86 // Cache current values
87 poly_float current_wet = wet_;
88 poly_float current_dry = dry_;
89 poly_float current_feedback = feedback_;
90 poly_float current_period = period_;
91 poly_float current_filter_gain = filter_gain_;
92 poly_float current_low_coefficient = low_coefficient_;
93 poly_float current_high_coefficient = high_coefficient_;
94
95 // Retrieve target frequency
96 poly_float target_frequency = input(kFrequency)->at(0);
97 Style style = static_cast<Style>(static_cast<int>(input(kStyle)->at(0)[0]));
98
99 // Use auxiliary frequency for stereo-based styles
100 if (style == kStereo || style == kPingPong || style == kMidPingPong)
101 target_frequency = utils::maskLoad(target_frequency, input(kFrequencyAux)->at(0), constants::kRightMask);
102
103 // Smooth frequency changes
104 poly_float decay = futils::exp_half(num_samples / (kDelayHalfLife * getSampleRate()));
105 last_frequency_ = utils::interpolate(target_frequency, last_frequency_, decay);
106
107 // Compute new wet/dry feedback
108 poly_float wet = utils::clamp(input(kWet)->at(0), 0.0f, 1.0f);
109 wet_ = futils::equalPowerFade(wet);
111 feedback_ = utils::clamp(input(kFeedback)->at(0), -1.0f, 1.0f);
112
113 // Convert frequency to sample-based delay period
114 poly_float samples = poly_float(getSampleRate()) / last_frequency_;
115
116 // Adjust per-style (e.g. mid ping-pong offsets, forced feedback for ping-pong)
117 if (style == kMidPingPong)
118 samples += utils::swapStereo(samples) & constants::kLeftMask;
119 if (style == kPingPong) {
120 current_feedback = utils::maskLoad(current_feedback, 1.0f, constants::kRightMask);
121 feedback_ = utils::maskLoad(feedback_, 1.0f, constants::kRightMask);
122 }
123
124 // Clamp to valid memory range
125 period_ = utils::clamp(samples, 3.0f, memory_->getMaxPeriod());
126 // Smooth period
127 period_ = utils::interpolate(current_period, period_, 0.5f);
128
129 // Setup filter coefficients
130 poly_float filter_cutoff = input(kFilterCutoff)->at(0);
131 poly_float filter_radius = getFilterRadius(input(kFilterSpread)->at(0));
132
133 poly_float low_frequency = utils::midiNoteToFrequency(filter_cutoff + filter_radius);
134 float min_nyquist = getSampleRate() * kMinNyquistMult;
135 low_frequency = utils::clamp(low_frequency, 1.0f, min_nyquist);
136 low_coefficient_ = OnePoleFilter<>::computeCoefficient(low_frequency, getSampleRate());
137
138 poly_float high_frequency = utils::midiNoteToFrequency(filter_cutoff - filter_radius);
139 high_frequency = utils::clamp(high_frequency, 1.0f, min_nyquist);
140 high_coefficient_ = OnePoleFilter<>::computeCoefficient(high_frequency, getSampleRate());
141
142 filter_gain_ = high_frequency / low_frequency + 1.0f;
143
144 // Get damping frequency
145 poly_float damping = utils::clamp(input(kDamping)->at(0), 0.0f, 1.0f);
146 poly_float damping_note = utils::interpolate(kMinDampNote, kMaxDampNote, damping);
147 poly_float damping_frequency = utils::midiNoteToFrequency(damping_note);
148
149 // Choose processing style
150 switch (style) {
151 case kMono:
152 case kStereo:
153 process(audio_in, num_samples, current_period, current_feedback, current_filter_gain,
154 current_low_coefficient, current_high_coefficient, current_wet, current_dry);
155 break;
156 case kPingPong:
157 processMonoPingPong(audio_in, num_samples, current_period, current_feedback, current_filter_gain,
158 current_low_coefficient, current_high_coefficient, current_wet, current_dry);
159 break;
160 case kMidPingPong:
161 processPingPong(audio_in, num_samples, current_period, current_feedback, current_filter_gain,
162 current_low_coefficient, current_high_coefficient, current_wet, current_dry);
163 break;
164 case kClampedDampened:
165 damping_frequency = utils::clamp(damping_frequency, 1.0f, min_nyquist);
166 low_coefficient_ = OnePoleFilter<>::computeCoefficient(damping_frequency, getSampleRate());
167 processDamped(audio_in, num_samples, current_period, current_feedback,
168 current_low_coefficient, current_wet, current_dry);
169 break;
170 case kUnclampedUnfiltered:
171 processCleanUnfiltered(audio_in, num_samples, current_period, current_feedback, current_wet, current_dry);
172 break;
173 default:
174 processUnfiltered(audio_in, num_samples, current_period, current_feedback, current_wet, current_dry);
175 break;
176 }
177 }
178
182 template<class MemoryType>
183 void Delay<MemoryType>::processCleanUnfiltered(const poly_float* audio_in, int num_samples,
184 poly_float current_period, poly_float current_feedback,
185 poly_float current_wet, poly_float current_dry) {
186 mono_float tick_increment = 1.0f / num_samples;
187 poly_float delta_wet = (wet_ - current_wet) * tick_increment;
188 poly_float delta_dry = (dry_ - current_dry) * tick_increment;
189 poly_float delta_feedback = (feedback_ - current_feedback) * tick_increment;
190 poly_float delta_period = (period_ - current_period) * tick_increment;
191
192 poly_float* dest = output()->buffer;
193
194 for (int i = 0; i < num_samples; ++i) {
195 current_feedback += delta_feedback;
196 current_wet += delta_wet;
197 current_dry += delta_dry;
198
199 dest[i] = tickCleanUnfiltered(audio_in[i], current_period, current_feedback, current_wet, current_dry);
200 current_period += delta_period;
201 }
202 }
203
207 template<class MemoryType>
208 void Delay<MemoryType>::processUnfiltered(const poly_float* audio_in, int num_samples,
209 poly_float current_period, poly_float current_feedback,
210 poly_float current_wet, poly_float current_dry) {
211 mono_float tick_increment = 1.0f / num_samples;
212 poly_float delta_wet = (wet_ - current_wet) * tick_increment;
213 poly_float delta_dry = (dry_ - current_dry) * tick_increment;
214 poly_float delta_feedback = (feedback_ - current_feedback) * tick_increment;
215 poly_float delta_period = (period_ - current_period) * tick_increment;
216
217 poly_float* dest = output()->buffer;
218
219 for (int i = 0; i < num_samples; ++i) {
220 current_feedback += delta_feedback;
221 current_wet += delta_wet;
222 current_dry += delta_dry;
223
224 dest[i] = tickUnfiltered(audio_in[i], current_period, current_feedback, current_wet, current_dry);
225 current_period += delta_period;
226 }
227 }
228
232 template<class MemoryType>
233 void Delay<MemoryType>::process(const poly_float* audio_in, int num_samples,
234 poly_float current_period, poly_float current_feedback,
235 poly_float current_filter_gain,
236 poly_float current_low_coefficient, poly_float current_high_coefficient,
237 poly_float current_wet, poly_float current_dry) {
238 mono_float tick_increment = 1.0f / num_samples;
239 poly_float delta_wet = (wet_ - current_wet) * tick_increment;
240 poly_float delta_dry = (dry_ - current_dry) * tick_increment;
241 poly_float delta_feedback = (feedback_ - current_feedback) * tick_increment;
242 poly_float delta_period = (period_ - current_period) * tick_increment;
243 poly_float delta_filter_gain = (filter_gain_ - current_filter_gain) * tick_increment;
244 poly_float delta_low_coefficient = (low_coefficient_ - current_low_coefficient) * tick_increment;
245 poly_float delta_high_coefficient = (high_coefficient_ - current_high_coefficient) * tick_increment;
246
247 poly_float* dest = output()->buffer;
248
249 for (int i = 0; i < num_samples; ++i) {
250 current_feedback += delta_feedback;
251 current_wet += delta_wet;
252 current_dry += delta_dry;
253 current_filter_gain += delta_filter_gain;
254 current_low_coefficient += delta_low_coefficient;
255 current_high_coefficient += delta_high_coefficient;
256
257 dest[i] = tick(audio_in[i], current_period, current_feedback, current_filter_gain,
258 current_low_coefficient, current_high_coefficient, current_wet, current_dry);
259
260 current_period += delta_period;
261 }
262 }
263
267 template<class MemoryType>
268 void Delay<MemoryType>::processDamped(const poly_float* audio_in, int num_samples,
269 poly_float current_period, poly_float current_feedback,
270 poly_float current_low_coefficient,
271 poly_float current_wet, poly_float current_dry) {
272 mono_float tick_increment = 1.0f / num_samples;
273 poly_float delta_wet = (wet_ - current_wet) * tick_increment;
274 poly_float delta_dry = (dry_ - current_dry) * tick_increment;
275 poly_float delta_feedback = (feedback_ - current_feedback) * tick_increment;
276 poly_float delta_period = (period_ - current_period) * tick_increment;
277 poly_float delta_low_coefficient = (low_coefficient_ - current_low_coefficient) * tick_increment;
278
279 poly_float* dest = output()->buffer;
280
281 for (int i = 0; i < num_samples; ++i) {
282 current_feedback += delta_feedback;
283 current_wet += delta_wet;
284 current_dry += delta_dry;
285 current_low_coefficient += delta_low_coefficient;
286
287 dest[i] = tickDamped(audio_in[i], current_period, current_feedback,
288 current_low_coefficient, current_wet, current_dry);
289
290 current_period += delta_period;
291 }
292 }
293
297 template<class MemoryType>
298 void Delay<MemoryType>::processPingPong(const poly_float* audio_in, int num_samples,
299 poly_float current_period, poly_float current_feedback,
300 poly_float current_filter_gain,
301 poly_float current_low_coefficient, poly_float current_high_coefficient,
302 poly_float current_wet, poly_float current_dry) {
303 mono_float tick_increment = 1.0f / num_samples;
304 poly_float delta_wet = (wet_ - current_wet) * tick_increment;
305 poly_float delta_dry = (dry_ - current_dry) * tick_increment;
306 poly_float delta_feedback = (feedback_ - current_feedback) * tick_increment;
307 poly_float delta_period = (period_ - current_period) * tick_increment;
308 poly_float delta_filter_gain = (filter_gain_ - current_filter_gain) * tick_increment;
309 poly_float delta_low_coefficient = (low_coefficient_ - current_low_coefficient) * tick_increment;
310 poly_float delta_high_coefficient = (high_coefficient_ - current_high_coefficient) * tick_increment;
311
312 poly_float* dest = output()->buffer;
313
314 for (int i = 0; i < num_samples; ++i) {
315 current_feedback += delta_feedback;
316 current_wet += delta_wet;
317 current_dry += delta_dry;
318 current_filter_gain += delta_filter_gain;
319 current_low_coefficient += delta_low_coefficient;
320 current_high_coefficient += delta_high_coefficient;
321
322 dest[i] = tickPingPong(audio_in[i], current_period, current_feedback, current_filter_gain,
323 current_low_coefficient, current_high_coefficient, current_wet, current_dry);
324
325 current_period += delta_period;
326 }
327 }
328
332 template<class MemoryType>
333 void Delay<MemoryType>::processMonoPingPong(const poly_float* audio_in, int num_samples,
334 poly_float current_period, poly_float current_feedback,
335 poly_float current_filter_gain,
336 poly_float current_low_coefficient, poly_float current_high_coefficient,
337 poly_float current_wet, poly_float current_dry) {
338 mono_float tick_increment = 1.0f / num_samples;
339 poly_float delta_wet = (wet_ - current_wet) * tick_increment;
340 poly_float delta_dry = (dry_ - current_dry) * tick_increment;
341 poly_float delta_feedback = (feedback_ - current_feedback) * tick_increment;
342 poly_float delta_period = (period_ - current_period) * tick_increment;
343 poly_float delta_filter_gain = (filter_gain_ - current_filter_gain) * tick_increment;
344 poly_float delta_low_coefficient = (low_coefficient_ - current_low_coefficient) * tick_increment;
345 poly_float delta_high_coefficient = (high_coefficient_ - current_high_coefficient) * tick_increment;
346
347 poly_float* dest = output()->buffer;
348
349 for (int i = 0; i < num_samples; ++i) {
350 current_feedback += delta_feedback;
351 current_wet += delta_wet;
352 current_dry += delta_dry;
353 current_filter_gain += delta_filter_gain;
354 current_low_coefficient += delta_low_coefficient;
355 current_high_coefficient += delta_high_coefficient;
356
357 dest[i] = tickMonoPingPong(audio_in[i], current_period, current_feedback, current_filter_gain,
358 current_low_coefficient, current_high_coefficient, current_wet, current_dry);
359
360 current_period += delta_period;
361 }
362 }
363
367 template<class MemoryType>
369 poly_float feedback,
370 poly_float wet, poly_float dry) {
371 poly_float read = memory_->get(period);
372 memory_->push(audio_in + read * feedback);
373 return dry * audio_in + wet * read;
374 }
375
379 template<class MemoryType>
381 poly_float feedback,
382 poly_float wet, poly_float dry) {
383 poly_float read = memory_->get(period);
384 memory_->push(saturate(audio_in + read * feedback));
385 return dry * audio_in + wet * read;
386 }
387
391 template<class MemoryType>
393 poly_float filter_gain, poly_float low_coefficient,
394 poly_float high_coefficient,
395 poly_float wet, poly_float dry) {
396 poly_float read = memory_->get(period);
397 poly_float write_raw_value = saturateLarge(audio_in + read * feedback);
398 poly_float low_pass_result = low_pass_.tickBasic(write_raw_value * filter_gain, low_coefficient);
399 poly_float second_pass_result = high_pass_.tickBasic(low_pass_result, high_coefficient);
400 memory_->push(low_pass_result - second_pass_result);
401 return dry * audio_in + wet * read;
402 }
403
407 template<class MemoryType>
409 poly_float feedback, poly_float low_coefficient,
410 poly_float wet, poly_float dry) {
411 poly_float read = memory_->get(period);
412 poly_float write_raw_value = saturateLarge(audio_in + read * feedback);
413 poly_float low_pass_result = low_pass_.tickBasic(write_raw_value, low_coefficient);
414 memory_->push(low_pass_result);
415 return dry * audio_in + wet * read;
416 }
417
421 template<class MemoryType>
423 poly_float filter_gain, poly_float low_coefficient,
424 poly_float high_coefficient,
425 poly_float wet, poly_float dry) {
426 poly_float read = memory_->get(period);
427 poly_float write_raw_value = utils::swapStereo(saturateLarge(audio_in + read * feedback));
428 poly_float low_pass_result = low_pass_.tickBasic(write_raw_value * filter_gain, low_coefficient);
429 poly_float second_pass_result = high_pass_.tickBasic(low_pass_result, high_coefficient);
430 memory_->push(low_pass_result - second_pass_result);
431 return dry * audio_in + wet * read;
432 }
433
437 template<class MemoryType>
439 poly_float feedback,
440 poly_float filter_gain, poly_float low_coefficient,
441 poly_float high_coefficient,
442 poly_float wet, poly_float dry) {
443 poly_float read = memory_->get(period);
444 poly_float mono_in = (audio_in + utils::swapStereo(audio_in)) * (1.0f / kSqrt2) & constants::kLeftMask;
445 poly_float write_raw_value = utils::swapStereo(saturateLarge(mono_in + read * feedback));
446 poly_float low_pass_result = low_pass_.tickBasic(write_raw_value * filter_gain, low_coefficient);
447 poly_float second_pass_result = high_pass_.tickBasic(low_pass_result, high_coefficient);
448 memory_->push(low_pass_result - second_pass_result);
449 return dry * audio_in + wet * read;
450 }
451
452 // Explicit instantiations of Delay for StereoMemory and Memory types.
453 template class Delay<StereoMemory>;
454 template class Delay<Memory>;
455} // namespace vital
A flexible delay line effect processor that can operate in various styles and apply filtering.
Definition delay.h:47
poly_float tickCleanUnfiltered(poly_float audio_in, poly_float period, poly_float feedback, poly_float wet, poly_float dry)
A single-sample tick for a clean, unfiltered delay line.
Definition delay.cpp:368
void processCleanUnfiltered(const poly_float *audio_in, int num_samples, poly_float current_period, poly_float current_feedback, poly_float current_wet, poly_float current_dry)
Processes a clean, unfiltered delay without clamping or filtering.
Definition delay.cpp:183
poly_float tickMonoPingPong(poly_float audio_in, poly_float period, poly_float feedback, poly_float filter_gain, poly_float low_coefficient, poly_float high_coefficient, poly_float wet, poly_float dry)
A single-sample tick for a mono ping-pong delay line.
Definition delay.cpp:438
Style
Styles of delay.
Definition delay.h:81
void processPingPong(const poly_float *audio_in, int num_samples, poly_float current_period, poly_float current_feedback, poly_float current_filter_gain, poly_float current_low_coefficient, poly_float current_high_coefficient, poly_float current_wet, poly_float current_dry)
Processes a ping-pong delay, alternating the delayed signal between channels.
Definition delay.cpp:298
virtual void processWithInput(const poly_float *audio_in, int num_samples) override
Processes a block of audio from a given input buffer.
Definition delay.cpp:83
poly_float tick(poly_float audio_in, poly_float period, poly_float feedback, poly_float filter_gain, poly_float low_coefficient, poly_float high_coefficient, poly_float wet, poly_float dry)
A single-sample tick for a filtered delay line.
Definition delay.cpp:392
void processMonoPingPong(const poly_float *audio_in, int num_samples, poly_float current_period, poly_float current_feedback, poly_float current_filter_gain, poly_float current_low_coefficient, poly_float current_high_coefficient, poly_float current_wet, poly_float current_dry)
Processes a mono ping-pong delay, collapsing input before ping-ponging.
Definition delay.cpp:333
virtual void process(int num_samples) override
Processes a block of audio using the connected inputs.
Definition delay.cpp:68
poly_float tickDamped(poly_float audio_in, poly_float period, poly_float feedback, poly_float low_coefficient, poly_float wet, poly_float dry)
A single-sample tick for a damped delay line using a low-pass filter.
Definition delay.cpp:408
void hardReset() override
Hard-resets the delay line and internal filters.
Definition delay.cpp:43
void processUnfiltered(const poly_float *audio_in, int num_samples, poly_float current_period, poly_float current_feedback, poly_float current_wet, poly_float current_dry)
Processes an unfiltered delay with possible feedback saturation.
Definition delay.cpp:208
void setMaxSamples(int max_samples)
Sets the maximum number of samples for the delay.
Definition delay.cpp:57
poly_float tickUnfiltered(poly_float audio_in, poly_float period, poly_float feedback, poly_float wet, poly_float dry)
A single-sample tick for an unfiltered delay line with saturation.
Definition delay.cpp:380
void processDamped(const poly_float *audio_in, int num_samples, poly_float current_period, poly_float current_feedback, poly_float current_low_coefficient, poly_float current_wet, poly_float current_dry)
Processes a damped delay line using a low-pass filter for damping.
Definition delay.cpp:268
poly_float tickPingPong(poly_float audio_in, poly_float period, poly_float feedback, poly_float filter_gain, poly_float low_coefficient, poly_float high_coefficient, poly_float wet, poly_float dry)
A single-sample tick for a ping-pong delay line.
Definition delay.cpp:422
static force_inline poly_float computeCoefficient(poly_float cutoff_frequency, int sample_rate)
Computes the filter coefficient for a given cutoff frequency and sample rate.
Definition one_pole_filter.h:131
#define VITAL_ASSERT(x)
Definition common.h:11
#define force_inline
Definition common.h:23
Contains faster but less accurate versions of utility math functions, such as exponential,...
Declares classes for time-domain memory storage and retrieval with cubic interpolation.
const poly_mask kFullMask
A mask covering all lanes of a poly_float vector.
Definition synth_constants.h:257
const poly_mask kRightMask
A mask identifying the right channel when comparing to kRightOne.
Definition synth_constants.h:263
const poly_mask kLeftMask
A mask identifying the left channel when comparing to kLeftOne.
Definition synth_constants.h:260
force_inline poly_float hardTanh(poly_float value)
Another saturation function using half-range tanh.
Definition futils.h:373
force_inline poly_float equalPowerFadeInverse(poly_float t)
The inverse equal-power fade from t to t+1.0.
Definition futils.h:448
force_inline poly_float equalPowerFade(poly_float t)
Produces an equal-power crossfade (sin-based) between 0.0 and 1.0.
Definition futils.h:436
force_inline mono_float exp_half(mono_float exponent)
Definition futils.h:138
force_inline poly_float clamp(poly_float value, mono_float min, mono_float max)
Clamps each lane of a vector to [min, max].
Definition poly_utils.h:306
force_inline poly_float min(poly_float left, poly_float right)
Returns the minimum of two poly_floats lane-by-lane.
Definition poly_utils.h:334
force_inline poly_float maskLoad(poly_float zero_value, poly_float one_value, poly_mask reset_mask)
Selects between two values (zero_value or one_value) based on a mask in each lane.
Definition poly_utils.h:351
force_inline poly_float midiNoteToFrequency(poly_float value)
Converts a MIDI note to a frequency (vectorized).
Definition poly_utils.h:123
force_inline poly_float interpolate(poly_float from, poly_float to, mono_float t)
Performs a linear interpolation between two poly_floats using a scalar t in [0..1].
Definition poly_utils.h:182
force_inline poly_float swapStereo(poly_float value)
Swaps the left and right channels of a stereo poly_float.
Definition poly_utils.h:411
Contains classes and functions used within the Vital synthesizer framework.
constexpr mono_float kSqrt2
Square root of 2.
Definition common.h:37
constexpr mono_float kMinNyquistMult
Minimum ratio relative to Nyquist frequency.
Definition common.h:42
float mono_float
Definition common.h:33
Represents a vector of floating-point values using SIMD instructions.
Definition poly_values.h:600