-
Notifications
You must be signed in to change notification settings - Fork 1
/
Delay.h
207 lines (193 loc) · 8.11 KB
/
Delay.h
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
#ifndef DELAY_H
#define DELAY_H
#include <JuceHeader.h> // for juce::SmoothedValue
#include <cmath> // for rounding functions
#include <memory> // for unique_ptr
#include "Parameters.h" // for accessing parameters set by the user interface
/// Delay class.
/// A class instance stores samples into a buffer of a maximum delay
/// size and outputs them with a specified delay time. Can process
/// both mono and stereo audio input.
class Delay
{
public:
/// initialise parameters pointer and delay time bounds
/// @param Parameters*, pointer to the parameters class
Delay (Parameters* _param) :
param(_param),
minDelayTime(_param->apvts.getParameterRange("delayTimeLeft").start), maxDelayTime(_param->apvts.getParameterRange("delayTimeLeft").end)
{
}
/// initialise delay
/// @param float, sample rate [Hz]
void prepareToPlay (float _sampleRate)
{
// update delay line size
(*this).setSampleRate (_sampleRate);
sizeInSamples = int(std::ceil (maxDelayTime * sampleRate));
allocateBuffers();
// initialise smoothed parameters
smoothedDryWet.reset (_sampleRate, 0.2f);
smoothedDryWet.setCurrentAndTargetValue (0.0f);
smoothedFeedback.reset (_sampleRate, 0.1f);
smoothedFeedback.setCurrentAndTargetValue (0.0f);
for (int i = 0; i < 2; i++)
{
smoothedDelayTime[i].reset (_sampleRate, 0.1f);
smoothedDelayTime[i].setCurrentAndTargetValue (minDelayTime);
}
}
/// apply delay to an audio buffer
/// @param juce::AudioBuffer&, input audio buffer with samples
/// @param int, number of samples in the buffer
void processBlock (juce::AudioSampleBuffer& outputBuffer, int numSamples)
{
// check on/off switch
if (*param->delayOnParam == false)
{
if (areBuffersClear == false)
clearBuffers();
return;
}
areBuffersClear = false;
// process delay
int numChannels = outputBuffer.getNumChannels();
if (numChannels == 1)
processMono (outputBuffer.getWritePointer (0), numSamples);
else if (numChannels == 2)
processStereo (outputBuffer.getWritePointer (0), outputBuffer.getWritePointer (1), numSamples);
}
private:
// base variables
float sampleRate = 0.0f; // sample rate [Hz]
int sizeInSamples = 0; // size [samples]
int writeIndex = -1; // write location in a buffer
float dryWet = 0; // dry/wet
float feedback = 0; // feedback
std::unique_ptr<float[]> buffer[2] = {nullptr}; // unique_ptr that manages a dynamically-allocated delay line (data is deleted automatically when goes out of scope)
bool areBuffersClear = false; // flag for clear buffers state
// parameters
Parameters* param; // pointer to parameters set by the user interface
const float minDelayTime; // minimum delay time [sec]
const float maxDelayTime; // maximum delay time [sec]
// smoothed values
juce::SmoothedValue<float> smoothedDryWet; // smoothed dry/wet
juce::SmoothedValue<float> smoothedDelayTime[2]; // smoothed delay time for each channel
juce::SmoothedValue<float> smoothedFeedback; // smootehd feedback
/// set sample rate
/// @param float, sample rate
void setSampleRate (float _sampleRate)
{
jassert (_sampleRate > 0.0f); // check sample rate value
sampleRate = _sampleRate;
}
/// allocate buffers for delay lines
void allocateBuffers()
{
for (int i = 0; i < 2; i++)
buffer[i].reset (new float[sizeInSamples]);
clearBuffers();
}
/// clear buffers for delay lines
void clearBuffers()
{
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < sizeInSamples; j++)
buffer[i][j] = 0.0f;
}
areBuffersClear = true;
}
/// process delay line sample by sample
/// @param float, input sample
/// @param int, channel index (0 or 1)
/// @return float, delayed output
float processSample (float _inSample, int channelIdx)
{
// calculate current delay time
float delayTime;
if (smoothedDryWet.getCurrentValue() == 0.0f)
{
// change delay time only when the effect isn't active
delayTime = smoothedDelayTime[channelIdx].getNextValue();
}
else
{
// keep delay time fixed while fade out
delayTime = smoothedDelayTime[channelIdx].getCurrentValue();
}
// process delay line
float readTimeInSamples = float(writeIndex) - delayTime * sampleRate; // fractional read time in samples
while (readTimeInSamples < 0.0f)
readTimeInSamples += float(sizeInSamples);
float outSample = linearInterpolation (readTimeInSamples, channelIdx); // interpolation between two neighbours
buffer[channelIdx][writeIndex] = _inSample + feedback * outSample; // update buffer
return (1.0f - dryWet) * _inSample + dryWet * outSample; // output effect with specified dry/wet
}
/// perform linear interpolation if the delay time in samples isn't integer
/// @param float, read time in samples
/// @param int, channel index (0 or 1)
/// @return float, interpolated delayed sample
float linearInterpolation (float readTimeInSamples, int channelIdx)
{
int indexA = int(std::floor (readTimeInSamples));
int indexB = (indexA + 1) % sizeInSamples;
float weight = readTimeInSamples - indexA;
return (1.0f - weight) * buffer[channelIdx][indexA] + weight * buffer[channelIdx][indexB];
}
/// process mono audio buffer
/// @param float*, array with input samples
/// @param int, number of samples
void processMono (float* samples, int numSamples)
{
for (int j = 0; j < numSamples; j++)
{
updateParameters();
samples[j] = processSample(samples[j], 0);
}
}
/// process stereo audio buffer
/// @param float*, array with left input samples
/// @param float*, array with right input samples
/// @param int, number of samples
void processStereo (float* leftSamples, float* rightSamples, int numSamples)
{
for (int j = 0; j < numSamples; j++)
{
updateParameters();
leftSamples[j] = processSample(leftSamples[j], 0);
rightSamples[j] = processSample(rightSamples[j], 1);
}
}
/// update delay parameters
void updateParameters()
{
// set target value for delay time
smoothedDelayTime[0].setTargetValue (*param->delayTimeParam[0]);
if (*param->delayTimeLinkParam == true)
smoothedDelayTime[1].setTargetValue (*param->delayTimeParam[0]);
else
smoothedDelayTime[1].setTargetValue (*param->delayTimeParam[1]);
// check changing delay time
if (juce::isWithin (smoothedDelayTime[0].getCurrentValue(), smoothedDelayTime[0].getTargetValue(), 1e-6f) == false ||
juce::isWithin (smoothedDelayTime[1].getCurrentValue(), smoothedDelayTime[1].getTargetValue(), 1e-6f) == false)
{
// start fade out when the delay time change is detected
smoothedDryWet.setTargetValue (0.0f);
}
else
{
// start fade in and process dry/wet normally when the delay time is fixed
if (areBuffersClear == false && smoothedDryWet.getCurrentValue() == 0.0f)
clearBuffers();
smoothedDryWet.setTargetValue (*param->delayDryWetParam);
}
// get feedback and dry/wet values
smoothedFeedback.setTargetValue (*param->delayFeedbackParam);
feedback = smoothedFeedback.getNextValue();
dryWet = smoothedDryWet.getNextValue();
// increment buffers write position
writeIndex = (writeIndex + 1) % sizeInSamples;
}
};
#endif // DELAY_H