-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathDelaySmooth.hpp
158 lines (142 loc) · 6.63 KB
/
DelaySmooth.hpp
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
/*******************************************************************************
*
* Mono-input, mono-output delay line with an efficient wrap-around method.
* The reading and writing heads use fixed-width unsigned int types,
* which can overflow safely. The limitation of this design is that buffers
* allocation is limited to 2^8, 2^16, 2^32, or 2^64 elements. Of course,
* only the first two sizes are feasible, hence this technique is convenient
* for delays that are 256 or 65536 samples maximum.
*
* The delay line uses two parallel delay lines among which crossfade takes
* place for click-free and Doppler-free delay variations.
*
* Copyright (c) 2022 Dario Sanfilippo - [email protected]
*
* ****************************************************************************/
#pragma once
#include <cstdint>
#include <cmath>
#include <vector>
#include <algorithm>
template<typename head, typename real>
class DelaySmooth {
private:
/* Compute the buffer size as the largest representable
* int with a head-type variable. */
const size_t bufferLen = 2l << (8 * sizeof(head) - 1);
size_t delay = 0; // System output delay in samples.
size_t interpolationTime = 1024; // Interpolation time in samples.
size_t lowerDelay = 0; // Lower head delay.
size_t upperDelay = 0; // Upper head delay.
real interpolation = 0.0; // Interpolation coefficient in the range [0; 1].
real interpolationStep = 1.0 / real(interpolationTime); // Interpolation segment slope.
real increment = interpolationStep; // Helper var for interpolation coefficient calculation.
/* Reading and writing heads. These will cycle continuously from 0
* to bufferLen - 1. The reading heads will be offset appropriately
* to set the delay. */
head lowerReadPtr = 0;
head upperReadPtr = 0;
head writePtr = 0;
std::vector<real> bufferLeft;
std::vector<real> bufferRight;
public:
void SetDelay(size_t _delay) { delay = _delay; };
void SetInterpolationTime(size_t _interpolationTime) {
interpolationTime = std::max<size_t>(1, _interpolationTime);
interpolationStep = 1.0 / real(interpolationTime);
};
void Reset() {
std::fill(bufferLeft.begin(), bufferLeft.end(), .0);
std::fill(bufferRight.begin(), bufferRight.end(), .0);
};
void Process(real** xVec, real** yVec, size_t vecLen);
DelaySmooth() {
bufferLeft.resize(bufferLen);
bufferRight.resize(bufferLen);
};
DelaySmooth(size_t _delay, size_t _interpolationTime);
};
/* This function computes a click-free and Doppler-free variable delay line
* by linearly crossfading between two independent integer delay lines.
* Once a crossfade has been completed, the inactive delay line can be
* set with a new delay and a new crossafed can start. During the crossfade,
* neither the delay or interpolation time can be changed. */
template<typename head, typename real>
void DelaySmooth<head, real>::Process(real** xVec, real** yVec, size_t vecLen) {
for (size_t n = 0; n < vecLen; n++) { // Level-0 for-loop.
real* xLeft = xVec[0];
real* xRight = xVec[1];
real* yLeft = yVec[0];
real* yRight = yVec[1];
/* Fill the delay buffers with the input signals. */
bufferLeft[writePtr] = xLeft[n];
bufferRight[writePtr] = xRight[n];
/* Compute the necessary Boolean conditions to trigger a new
* interpolation and set a new delay or interpolation time.
* The mechanism for smooth delay variations works by linearly
* interpolating from the currently active delay line to the
* inactive one, which is set with the new delay. Note that a new delay
* or interpolation time can be set only after the transition has been
* completed. */
bool lowerReach = interpolation == 0.0;
bool upperReach = interpolation == 1.0;
bool lowerDelayChanged = delay != lowerDelay;
bool upperDelayChanged = delay != upperDelay;
bool startDownwardInterp = upperReach && upperDelayChanged;
bool startUpwardInterp = lowerReach && lowerDelayChanged;
/* Following a branchless programming paradigm, we compute the paths
* for the variables with bifurcation and assign the results using
* Boolean array-fetching, which is faster than two multiplications
* by bools.
*
* If we have completed an upward interpolation and the delay has
* changed, we assign a negative incremental step to trigger a
* downward interpolation; alternatively, if we are at the bottom
* edge and the delay has changed, we assign a positive incremental
* step; otherwise, we leave the incremental step unaltered.
*
* The delay is set to whatever delay line becomes inactive after
* completing the interpolation. Otherwise, it stays unaltered
* during the transition. */
real incrementPathsUp[2] = {
increment,
interpolationStep
};
real incrementPathsDown[2] = {
incrementPathsUp[startUpwardInterp],
-interpolationStep
};
increment = incrementPathsDown[startDownwardInterp];
size_t lowerDelayPaths[2] = {
lowerDelay,
delay
};
size_t upperDelayPaths[2] = {
upperDelay,
delay
};
lowerDelay = lowerDelayPaths[upperReach];
upperDelay = upperDelayPaths[lowerReach];
/* Compute the delays reading heads and increment the writing head. */
lowerReadPtr = writePtr - lowerDelay;
upperReadPtr = writePtr - upperDelay;
writePtr++;
/* Compute the interpolation and assign the result to the output. */
interpolation =
std::max<real>(0.0, std::min<real>(1.0, interpolation + increment));
yLeft[n] = interpolation *
(bufferLeft[upperReadPtr] - bufferLeft[lowerReadPtr]) +
bufferLeft[lowerReadPtr];
yRight[n] = interpolation *
(bufferRight[upperReadPtr] - bufferRight[lowerReadPtr]) +
bufferRight[lowerReadPtr];
} // End of level-0 for-loop.
}
template<typename head, typename real>
DelaySmooth<head, real>::DelaySmooth(size_t _delay, size_t _interpolationTime) {
bufferLeft.resize(bufferLen);
bufferRight.resize(bufferLen);
delay = _delay;
interpolationTime = std::max<size_t>(1, _interpolationTime);
interpolationStep = 1.0 / real(interpolationTime);
}