File: LookAheadGainReduction.cpp

package info (click to toggle)
audacity 3.7.7%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 134,800 kB
  • sloc: cpp: 366,277; ansic: 198,323; lisp: 7,761; sh: 3,414; python: 1,501; xml: 1,385; perl: 854; makefile: 125
file content (275 lines) | stat: -rw-r--r-- 9,735 bytes parent folder | download | duplicates (2)
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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/*
 This file is part of the SimpleCompressor project.
 https://github.com/DanielRudrich/SimpleCompressor
 Copyright (c) 2019 Daniel Rudrich

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, version 3.

 This program is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program. If not, see <http://www.gnu.org/licenses/>.
 */


#include "LookAheadGainReduction.h"
#include <cmath>
#include <algorithm>

namespace DanielRudrich {
void LookAheadGainReduction::setDelayTime (float delayTimeInSeconds)
{
    if (delayTimeInSeconds <= 0.0f)
        delay = 0.0f;
    else
        delay = delayTimeInSeconds;

    if (sampleRate != 0.0)
        prepare (sampleRate, blockSize);
}

void LookAheadGainReduction::prepare (const double newSampleRate, const int newBlockSize)
{
    sampleRate = newSampleRate;
    blockSize = newBlockSize;

    delayInSamples = static_cast<int> (delay * sampleRate);

    buffer.resize (blockSize + delayInSamples);
    std::fill (buffer.begin(), buffer.end(), 0.0f);
    writePosition = 0;
}

void LookAheadGainReduction::pushSamples (const float* src, const int numSamples)
{
    int startIndex, blockSize1, blockSize2;

    // write in delay line
    getWritePositions (numSamples, startIndex, blockSize1, blockSize2);

    for (int i = 0; i < blockSize1; ++i)
        buffer[startIndex + i] = src[i];

    if (blockSize2 > 0)
        for (int i = 0; i < blockSize2; ++i)
            buffer[i] = src[blockSize1 + i];

    writePosition += numSamples;
    writePosition = writePosition % buffer.size();

    lastPushedSamples = numSamples;
}

void LookAheadGainReduction::process()
{
    /** The basic idea here is to look for high gain-reduction values in the signal, and apply a fade which starts exactly  `delayInSamples` many samples before that value appears. Depending on the value itself, the slope of the fade will vary.

     Some things to note:
        - as the samples are gain-reduction values in decibel, we actually look for negative peaks or local minima.
        - it's easier for us to time-reverse our search, so we start with the last sample
        - once we find a minimum, we calculate the slope, which will be called `step`
        - with that slope, we can calculate the next value of our fade-in `nextGainReductionValue`
        - once a value in the buffer is below our fade-in value, we found a new minimum, which might not be as deep as the previous one, but as it comes in earlier, it needs more attention, so we update our fade-in slope
        - our buffer is a ring-buffer which makes things a little bit messy
     */


    // 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.
    float nextGainReductionValue = 0.0f;
    float step = 0.0f;


    // Get the position of the last sample in the buffer, which is the sample right before our new write position.
    int index = writePosition - 1;
    if (index < 0) // in case it's negative...
        index += static_cast<int> (buffer.size()); // ... add the buffersize so we wrap around.

    // == FIRST STEP: Process all recently pushed samples.

    // 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).
    int size1, size2;
    getProcessPositions (index, lastPushedSamples, size1, size2);

    // first run
    for (int i = 0; i < size1; ++i)
    {
        const float smpl = buffer[index];

        if (smpl > nextGainReductionValue) // in case the sample is above our ramp...
        {
            buffer[index] = nextGainReductionValue; // ... replace it with the current ramp value
            nextGainReductionValue += step; // and update the next ramp value
        }
        else // otherwise... (new peak)
        {
            step = - smpl / delayInSamples; // calculate the new slope
            nextGainReductionValue = smpl + step; // and also the new ramp value
        }
        --index;
    }

    // second run
    if (size2 > 0) // in case we have some samples left for the second run
    {
        index = static_cast<int> (buffer.size()) - 1; // wrap around: start from the last sample of the buffer

        // exactly the same procedure as before... I guess I could have written that better...
        for (int i = 0; i < size2; ++i)
        {
            const float smpl = buffer[index];

            if (smpl > nextGainReductionValue)
            {
                buffer[index] = nextGainReductionValue;
                nextGainReductionValue += step;
            }
            else
            {
                step = - smpl / delayInSamples;
                nextGainReductionValue = smpl + step;
            }
            --index;
        }
    }

    /*
     At this point, we have processed all the new gain-reduction values. For this, we actually don't need a delay/lookahead at all.
     !! However, we are not finished, yet !!
     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.
     */

    if (index < 0) // it's possible the index is exactly -1
        index = static_cast<int> (buffer.size()) - 1; // so let's take care of that

    /*
     This time we only need to check `delayInSamples` many samples.
     And there's another cool thing!
        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!
     */
    getProcessPositions (index, delayInSamples, size1, size2);
    bool breakWasUsed = false;

    // first run
    for (int i = 0; i < size1; ++i) // we iterate over the first size1 samples
    {
        const float smpl = buffer[index];

        if (smpl > nextGainReductionValue) // in case the sample is above our ramp...
        {
            buffer[index] = nextGainReductionValue; // ... replace it with the current ramp value
            nextGainReductionValue += step; // and update the next ramp value
        }
        else // otherwise... JACKPOT! Nothing left to do here!
        {
            breakWasUsed = true; // let the guys know we are finished
            break;
        }
        --index;
    }

    // second run
    if (! breakWasUsed && size2 > 0) // is there still some work to do?
    {
        index = static_cast<int> (buffer.size()) - 1; // wrap around (ring-buffer)
        for (int i = 0; i < size2; ++i)
        {
            const float smpl = buffer[index];

            // same as before
            if (smpl > nextGainReductionValue) // in case the sample is above our ramp...
            {
                buffer[index] = nextGainReductionValue; // ... replace it with the current ramp value
                nextGainReductionValue += step; // and update the next ramp value
            }
            else // otherwise... already processed -> byebye!
                break;
            --index;
        }
    }
}


void LookAheadGainReduction::readSamples (float* dest, int numSamples)
{
    int startIndex, blockSize1, blockSize2;

    // read from delay line
    getReadPositions (numSamples, startIndex, blockSize1, blockSize2);

    for (int i = 0; i < blockSize1; ++i)
        dest[i] = buffer[startIndex + i];

    if (blockSize2 > 0)
        for (int i = 0; i < blockSize2; ++i)
            dest[blockSize1 + i] = buffer[i];
}


inline void LookAheadGainReduction::getProcessPositions (int startIndex, int numSamples, int& blockSize1, int& blockSize2)
{
    if (numSamples <= 0)
    {
        blockSize1 = 0;
        blockSize2 = 0;
    }
    else
    {
        blockSize1 = std::min (startIndex + 1, numSamples);
        numSamples -= blockSize1;
        blockSize2 = numSamples <= 0 ? 0 : numSamples;
    }
}

inline void LookAheadGainReduction::getWritePositions (int numSamples, int& startIndex, int& blockSize1, int& blockSize2)
{
    const int L = static_cast<int> (buffer.size());
    int pos = writePosition;

    if (pos < 0)
        pos = pos + L;
    pos = pos % L;

    if (numSamples <= 0)
    {
        startIndex = 0;
        blockSize1 = 0;
        blockSize2 = 0;
    }
    else
    {
        startIndex = pos;
        blockSize1 = std::min (L - pos, numSamples);
        numSamples -= blockSize1;
        blockSize2 = numSamples <= 0 ? 0 : numSamples;
    }
}

inline void LookAheadGainReduction::getReadPositions (int numSamples, int& startIndex, int& blockSize1, int& blockSize2)
{
    const int L = static_cast<int> (buffer.size());
    int pos = writePosition - lastPushedSamples - delayInSamples;

    if (pos < 0)
        pos = pos + L;
    pos = pos % L;

    if (numSamples <= 0)
    {
        startIndex = 0;
        blockSize1 = 0;
        blockSize2 = 0;
    }
    else
    {
        startIndex = pos;
        blockSize1 = std::min (L - pos, numSamples);
        numSamples -= blockSize1;
        blockSize2 = numSamples <= 0 ? 0 : numSamples;
    }
}
} // namespace DanielRudrich