summaryrefslogtreecommitdiffstats
path: root/src/engine/enginebufferscalerubberband.cpp
blob: ee3125feec4bfb06bc5c94ce0852d78386bc7712 (plain)
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
#include "engine/enginebufferscalerubberband.h"

#include <rubberband/RubberBandStretcher.h>

#include <QtDebug>

#include "control/controlobject.h"
#include "engine/readaheadmanager.h"
#include "track/keyutils.h"
#include "util/counter.h"
#include "util/defs.h"
#include "util/math.h"
#include "util/sample.h"

using RubberBand::RubberBandStretcher;

namespace {

// This is the default increment from RubberBand 1.8.1.
size_t kRubberBandBlockSize = 256;

}  // namespace

EngineBufferScaleRubberBand::EngineBufferScaleRubberBand(
        ReadAheadManager* pReadAheadManager)
        : m_pReadAheadManager(pReadAheadManager),
          m_buffer_back(SampleUtil::alloc(MAX_BUFFER_LEN)),
          m_bBackwards(false) {
    m_retrieve_buffer[0] = SampleUtil::alloc(MAX_BUFFER_LEN);
    m_retrieve_buffer[1] = SampleUtil::alloc(MAX_BUFFER_LEN);
    initRubberBand();
}

EngineBufferScaleRubberBand::~EngineBufferScaleRubberBand() {
    SampleUtil::free(m_buffer_back);
    SampleUtil::free(m_retrieve_buffer[0]);
    SampleUtil::free(m_retrieve_buffer[1]);
}

void EngineBufferScaleRubberBand::initRubberBand() {
    m_pRubberBand = std::make_unique<RubberBandStretcher>(
            getAudioSignal().sampleRate(),
            getAudioSignal().channelCount(),
            RubberBandStretcher::OptionProcessRealTime);
    m_pRubberBand->setMaxProcessSize(kRubberBandBlockSize);
    // Setting the time ratio to a very high value will cause RubberBand
    // to preallocate buffers large enough to (almost certainly)
    // avoid memory reallocations during playback.
    m_pRubberBand->setTimeRatio(2.0);
    m_pRubberBand->setTimeRatio(1.0);
}

void EngineBufferScaleRubberBand::setScaleParameters(double base_rate,
                                                     double* pTempoRatio,
                                                     double* pPitchRatio) {
    // Negative speed means we are going backwards. pitch does not affect
    // the playback direction.
    m_bBackwards = *pTempoRatio < 0;

    // Due to a bug in RubberBand, setting the timeRatio to a large value can
    // cause division-by-zero SIGFPEs. We limit the minimum seek speed to
    // prevent exceeding RubberBand's limits.
    //
    // References:
    // https://bugs.launchpad.net/ubuntu/+bug/1263233
    // https://bitbucket.org/breakfastquay/rubberband/issue/4/sigfpe-zero-division-with-high-time-ratios
    const double kMinSeekSpeed = 1.0 / 128.0;
    double speed_abs = fabs(*pTempoRatio);
    if (speed_abs < kMinSeekSpeed) {
        // Let the caller know we ignored their speed.
        speed_abs = *pTempoRatio = 0;
    }

    // RubberBand handles checking for whether the change in pitchScale is a
    // no-op.
    double pitchScale = fabs(base_rate * *pPitchRatio);

    if (pitchScale > 0) {
        //qDebug() << "EngineBufferScaleRubberBand setPitchScale" << *pitch << pitchScale;
        m_pRubberBand->setPitchScale(pitchScale);
    }

    // RubberBand handles checking for whether the change in timeRatio is a
    // no-op. Time ratio is the ratio of stretched to unstretched duration. So 1
    // second in real duration is 0.5 seconds in stretched duration if tempo is
    // 2.
    double timeRatioInverse = base_rate * speed_abs;
    if (timeRatioInverse > 0) {
        //qDebug() << "EngineBufferScaleRubberBand setTimeRatio" << 1 / timeRatioInverse;
        m_pRubberBand->setTimeRatio(1.0 / timeRatioInverse);
    }

    if (m_pRubberBand->getInputIncrement() == 0) {
        qWarning() << "EngineBufferScaleRubberBand inputIncrement is 0."
                   << "On RubberBand <=1.8.1 a SIGFPE is imminent despite"
                   << "our workaround. Taking evasive action."
                   << "Please report this message to mixxx-devel@lists.sourceforge.net.";

        // This is much slower than the minimum seek speed workaround above.
        while (m_pRubberBand->getInputIncrement() == 0) {
            timeRatioInverse += 0.001;
            m_pRubberBand->setTimeRatio(1.0 / timeRatioInverse);
        }
        speed_abs = timeRatioInverse / base_rate;
        *pTempoRatio = m_bBackwards ? -speed_abs : speed_abs;
    }

    // Used by other methods so we need to keep them up to date.
    m_dBaseRate = base_rate;
    m_dTempoRatio = speed_abs;
    m_dPitchRatio = *pPitchRatio;
}

void EngineBufferScaleRubberBand::setSampleRate(SINT iSampleRate) {
    EngineBufferScale::setSampleRate(iSampleRate);
    initRubberBand();
}

void EngineBufferScaleRubberBand::clear() {
    m_pRubberBand->reset();
}

SINT EngineBufferScaleRubberBand::retrieveAndDeinterleave(
        CSAMPLE* pBuffer,
        SINT frames) {
    SINT frames_available = m_pRubberBand->available();
    SINT frames_to_read = math_min(frames_available, frames);
    SINT received_frames = m_pRubberBand->retrieve(
            (float* const*)m_retrieve_buffer, frames_to_read);

    SampleUtil::interleaveBuffer(pBuffer,
                                 m_retrieve_buffer[0],
                                 m_retrieve_buffer[1],
                                 received_frames);
    return received_frames;
}

void EngineBufferScaleRubberBand::deinterleaveAndProcess(
        const CSAMPLE* pBuffer, SINT frames, bool flush) {

    SampleUtil::deinterleaveBuffer(
            m_retrieve_buffer[0], m_retrieve_buffer[1], pBuffer, frames);

    m_pRubberBand->process((const float* const*)m_retrieve_buffer,
                           frames, flush);
}

double EngineBufferScaleRubberBand::scaleBuffer(
        CSAMPLE* pOutputBuffer,
        SINT iOutputBufferSize) {
    if (m_dBaseRate == 0.0 || m_dTempoRatio == 0.0) {
        SampleUtil::clear(pOutputBuffer, iOutputBufferSize);
        // No actual samples/frames have been read from the
        // unscaled input buffer!
        return 0.0;
    }

    SINT total_received_frames = 0;
    SINT total_read_frames = 0;

    SINT remaining_frames = getAudioSignal().samples2frames(iOutputBufferSize);
    CSAMPLE* read = pOutputBuffer;
    bool last_read_failed = false;
    bool break_out_after_retrieve_and_reset_rubberband = false;
    while (remaining_frames > 0) {
        // ReadAheadManager will eventually read the requested frames with
        // enough calls to retrieveAndDeinterleave because CachingReader returns
        // zeros for reads that are not in cache. So it's safe to loop here
        // without any checks for failure in retrieveAndDeinterleave.
        SINT received_frames = retrieveAndDeinterleave(
                read, remaining_frames);
        remaining_frames -= received_frames;
        total_received_frames += received_frames;
        read += getAudioSignal().frames2samples(received_frames);

        if (break_out_after_retrieve_and_reset_rubberband) {
            //qDebug() << "break_out_after_retrieve_and_reset_rubberband";
            // If we break out early then we have flushed RubberBand and need to
            // reset it.
            m_pRubberBand->reset();
            break;
        }

        size_t iLenFramesRequired = m_pRubberBand->getSamplesRequired();
        if (iLenFramesRequired == 0) {
            // rubberband 1.3 (packaged up through Ubuntu Quantal) has a bug
            // where it can report 0 samples needed forever which leads us to an
            // infinite loop. To work around this, we check if available() is
            // zero. If it is, then we submit a fixed block size of
            // kRubberBandBlockSize.
            int available = m_pRubberBand->available();
            if (available == 0) {
                iLenFramesRequired = kRubberBandBlockSize;
            }
        }
        //qDebug() << "iLenFramesRequired" << iLenFramesRequired;

        if (remaining_frames > 0 && iLenFramesRequired > 0) {
            SINT iAvailSamples = m_pReadAheadManager->getNextSamples(
                        // The value doesn't matter here. All that matters is we
                        // are going forward or backward.
                        (m_bBackwards ? -1.0 : 1.0) * m_dBaseRate * m_dTempoRatio,
                        m_buffer_back,
                        getAudioSignal().frames2samples(iLenFramesRequired));
            SINT iAvailFrames = getAudioSignal().samples2frames(iAvailSamples);

            if (iAvailFrames > 0) {
                last_read_failed = false;
                total_read_frames += iAvailFrames;
                deinterleaveAndProcess(m_buffer_back, iAvailFrames, false);
            } else {
                if (last_read_failed) {
                    // Flush and break out after the next retrieval. If we are
                    // at EOF this serves to get the last samples out of
                    // RubberBand.
                    deinterleaveAndProcess(m_buffer_back, 0, true);
                    break_out_after_retrieve_and_reset_rubberband = true;
                }
                last_read_failed = true;
            }
        }
    }

    if (remaining_frames > 0) {
        SampleUtil::clear(read, getAudioSignal().frames2samples(remaining_frames));
        Counter counter("EngineBufferScaleRubberBand::getScaled underflow");
        counter.increment();
    }

    // framesRead is interpreted as the total number of virtual sample frames
    // consumed to produce the scaled buffer. Due to this, we do not take into
    // account directionality or starting point.
    // NOTE(rryan): Why no m_dPitchAdjust here? Pitch does not change the time
    // ratio. m_dSpeedAdjust is the ratio of unstretched time to stretched
    // time. So, if we used total_received_frames in stretched time, then
    // multiplying that by the ratio of unstretched time to stretched time
    // will get us the unstretched sample frames read.
    double framesRead = m_dBaseRate * m_dTempoRatio * total_received_frames;

    return framesRead;
}