Audacity 3.2.0
LookAheadGainReduction.cpp
Go to the documentation of this file.
1/*
2 This file is part of the SimpleCompressor project.
3 https://github.com/DanielRudrich/SimpleCompressor
4 Copyright (c) 2019 Daniel Rudrich
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, version 3.
9
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
21#include <cmath>
22#include <algorithm>
23
24namespace DanielRudrich {
25void LookAheadGainReduction::setDelayTime (float delayTimeInSeconds)
26{
27 if (delayTimeInSeconds <= 0.0f)
28 delay = 0.0f;
29 else
30 delay = delayTimeInSeconds;
31
32 if (sampleRate != 0.0)
34}
35
36void LookAheadGainReduction::prepare (const double newSampleRate, const int newBlockSize)
37{
38 sampleRate = newSampleRate;
39 blockSize = newBlockSize;
40
41 delayInSamples = static_cast<int> (delay * sampleRate);
42
44 std::fill (buffer.begin(), buffer.end(), 0.0f);
45 writePosition = 0;
46}
47
48void LookAheadGainReduction::pushSamples (const float* src, const int numSamples)
49{
50 int startIndex, blockSize1, blockSize2;
51
52 // write in delay line
53 getWritePositions (numSamples, startIndex, blockSize1, blockSize2);
54
55 for (int i = 0; i < blockSize1; ++i)
56 buffer[startIndex + i] = src[i];
57
58 if (blockSize2 > 0)
59 for (int i = 0; i < blockSize2; ++i)
60 buffer[i] = src[blockSize1 + i];
61
62 writePosition += numSamples;
64
65 lastPushedSamples = numSamples;
66}
67
69{
82 // As we don't know any samples of the future, yet, we assume we don't have to apply a fade-in right now, and initialize both `nextGainReductionValue` and step (slope of the fade-in) with zero.
83 float nextGainReductionValue = 0.0f;
84 float step = 0.0f;
85
86
87 // Get the position of the last sample in the buffer, which is the sample right before our new write position.
88 int index = writePosition - 1;
89 if (index < 0) // in case it's negative...
90 index += static_cast<int> (buffer.size()); // ... add the buffersize so we wrap around.
91
92 // == FIRST STEP: Process all recently pushed samples.
93
94 // We want to process all `lastPushedSamples` many samples, so let's find out, how many we can process in a first run before we have to wrap around our `index` variable (ring-buffer).
95 int size1, size2;
96 getProcessPositions (index, lastPushedSamples, size1, size2);
97
98 // first run
99 for (int i = 0; i < size1; ++i)
100 {
101 const float smpl = buffer[index];
102
103 if (smpl > nextGainReductionValue) // in case the sample is above our ramp...
104 {
105 buffer[index] = nextGainReductionValue; // ... replace it with the current ramp value
106 nextGainReductionValue += step; // and update the next ramp value
107 }
108 else // otherwise... (new peak)
109 {
110 step = - smpl / delayInSamples; // calculate the new slope
111 nextGainReductionValue = smpl + step; // and also the new ramp value
112 }
113 --index;
114 }
115
116 // second run
117 if (size2 > 0) // in case we have some samples left for the second run
118 {
119 index = static_cast<int> (buffer.size()) - 1; // wrap around: start from the last sample of the buffer
120
121 // exactly the same procedure as before... I guess I could have written that better...
122 for (int i = 0; i < size2; ++i)
123 {
124 const float smpl = buffer[index];
125
126 if (smpl > nextGainReductionValue)
127 {
128 buffer[index] = nextGainReductionValue;
129 nextGainReductionValue += step;
130 }
131 else
132 {
133 step = - smpl / delayInSamples;
134 nextGainReductionValue = smpl + step;
135 }
136 --index;
137 }
138 }
139
140 /*
141 At this point, we have processed all the new gain-reduction values. For this, we actually don't need a delay/lookahead at all.
142 !! However, we are not finished, yet !!
143 What if the first pushed sample has such a high gain-reduction value, that itself needs a fade-in? So we have to apply a gain-ramp even further into the past. And that is exactly the reason why we need lookahead, why we need to buffer our signal for a short amount of time: so we can apply that gain ramp for the first handful of gain-reduction samples.
144 */
145
146 if (index < 0) // it's possible the index is exactly -1
147 index = static_cast<int> (buffer.size()) - 1; // so let's take care of that
148
149 /*
150 This time we only need to check `delayInSamples` many samples.
151 And there's another cool thing!
152 We know that the samples have been processed already, so in case one of the samples is below our ramp value, that's the new minimum, which has been faded-in already! So what we do is hit the break, and call it a day!
153 */
154 getProcessPositions (index, delayInSamples, size1, size2);
155 bool breakWasUsed = false;
156
157 // first run
158 for (int i = 0; i < size1; ++i) // we iterate over the first size1 samples
159 {
160 const float smpl = buffer[index];
161
162 if (smpl > nextGainReductionValue) // in case the sample is above our ramp...
163 {
164 buffer[index] = nextGainReductionValue; // ... replace it with the current ramp value
165 nextGainReductionValue += step; // and update the next ramp value
166 }
167 else // otherwise... JACKPOT! Nothing left to do here!
168 {
169 breakWasUsed = true; // let the guys know we are finished
170 break;
171 }
172 --index;
173 }
174
175 // second run
176 if (! breakWasUsed && size2 > 0) // is there still some work to do?
177 {
178 index = static_cast<int> (buffer.size()) - 1; // wrap around (ring-buffer)
179 for (int i = 0; i < size2; ++i)
180 {
181 const float smpl = buffer[index];
182
183 // same as before
184 if (smpl > nextGainReductionValue) // in case the sample is above our ramp...
185 {
186 buffer[index] = nextGainReductionValue; // ... replace it with the current ramp value
187 nextGainReductionValue += step; // and update the next ramp value
188 }
189 else // otherwise... already processed -> byebye!
190 break;
191 --index;
192 }
193 }
194}
195
196
197void LookAheadGainReduction::readSamples (float* dest, int numSamples)
198{
199 int startIndex, blockSize1, blockSize2;
200
201 // read from delay line
202 getReadPositions (numSamples, startIndex, blockSize1, blockSize2);
203
204 for (int i = 0; i < blockSize1; ++i)
205 dest[i] = buffer[startIndex + i];
206
207 if (blockSize2 > 0)
208 for (int i = 0; i < blockSize2; ++i)
209 dest[blockSize1 + i] = buffer[i];
210}
211
212
213inline void LookAheadGainReduction::getProcessPositions (int startIndex, int numSamples, int& blockSize1, int& blockSize2)
214{
215 if (numSamples <= 0)
216 {
217 blockSize1 = 0;
218 blockSize2 = 0;
219 }
220 else
221 {
222 blockSize1 = std::min (startIndex + 1, numSamples);
223 numSamples -= blockSize1;
224 blockSize2 = numSamples <= 0 ? 0 : numSamples;
225 }
226}
227
228inline void LookAheadGainReduction::getWritePositions (int numSamples, int& startIndex, int& blockSize1, int& blockSize2)
229{
230 const int L = static_cast<int> (buffer.size());
231 int pos = writePosition;
232
233 if (pos < 0)
234 pos = pos + L;
235 pos = pos % L;
236
237 if (numSamples <= 0)
238 {
239 startIndex = 0;
240 blockSize1 = 0;
241 blockSize2 = 0;
242 }
243 else
244 {
245 startIndex = pos;
246 blockSize1 = std::min (L - pos, numSamples);
247 numSamples -= blockSize1;
248 blockSize2 = numSamples <= 0 ? 0 : numSamples;
249 }
250}
251
252inline void LookAheadGainReduction::getReadPositions (int numSamples, int& startIndex, int& blockSize1, int& blockSize2)
253{
254 const int L = static_cast<int> (buffer.size());
256
257 if (pos < 0)
258 pos = pos + L;
259 pos = pos % L;
260
261 if (numSamples <= 0)
262 {
263 startIndex = 0;
264 blockSize1 = 0;
265 blockSize2 = 0;
266 }
267 else
268 {
269 startIndex = pos;
270 blockSize1 = std::min (L - pos, numSamples);
271 numSamples -= blockSize1;
272 blockSize2 = numSamples <= 0 ? 0 : numSamples;
273 }
274}
275} // namespace DanielRudrich
int min(int a, int b)
void getReadPositions(int numSamples, int &startIndex, int &blockSize1, int &blockSize2)
void setDelayTime(float delayTimeInSeconds)
void getWritePositions(int numSamples, int &startIndex, int &blockSize1, int &blockSize2)
void prepare(const double sampleRate, const int blockSize)
void pushSamples(const float *src, const int numSamples)
void readSamples(float *dest, const int numSamples)
void getProcessPositions(int startIndex, int numSamples, int &blockSize1, int &blockSize2)