1 #include "engine/bufferscalers/enginebufferscalerubberband.h"
2
3 #include <rubberband/RubberBandStretcher.h>
4
5 #include <QtDebug>
6
7 #include "control/controlobject.h"
8 #include "engine/readaheadmanager.h"
9 #include "moc_enginebufferscalerubberband.cpp"
10 #include "track/keyutils.h"
11 #include "util/counter.h"
12 #include "util/defs.h"
13 #include "util/math.h"
14 #include "util/sample.h"
15
16 using RubberBand::RubberBandStretcher;
17
18 namespace {
19
20 // This is the default increment from RubberBand 1.8.1.
21 size_t kRubberBandBlockSize = 256;
22
23 } // namespace
24
EngineBufferScaleRubberBand(ReadAheadManager * pReadAheadManager)25 EngineBufferScaleRubberBand::EngineBufferScaleRubberBand(
26 ReadAheadManager* pReadAheadManager)
27 : m_pReadAheadManager(pReadAheadManager),
28 m_buffer_back(SampleUtil::alloc(MAX_BUFFER_LEN)),
29 m_bBackwards(false) {
30 m_retrieve_buffer[0] = SampleUtil::alloc(MAX_BUFFER_LEN);
31 m_retrieve_buffer[1] = SampleUtil::alloc(MAX_BUFFER_LEN);
32 // Initialize the internal buffers to prevent re-allocations
33 // in the real-time thread.
34 onSampleRateChanged();
35 }
36
~EngineBufferScaleRubberBand()37 EngineBufferScaleRubberBand::~EngineBufferScaleRubberBand() {
38 SampleUtil::free(m_buffer_back);
39 SampleUtil::free(m_retrieve_buffer[0]);
40 SampleUtil::free(m_retrieve_buffer[1]);
41 }
42
setScaleParameters(double base_rate,double * pTempoRatio,double * pPitchRatio)43 void EngineBufferScaleRubberBand::setScaleParameters(double base_rate,
44 double* pTempoRatio,
45 double* pPitchRatio) {
46 // Negative speed means we are going backwards. pitch does not affect
47 // the playback direction.
48 m_bBackwards = *pTempoRatio < 0;
49
50 // Due to a bug in RubberBand, setting the timeRatio to a large value can
51 // cause division-by-zero SIGFPEs. We limit the minimum seek speed to
52 // prevent exceeding RubberBand's limits.
53 //
54 // References:
55 // https://bugs.launchpad.net/ubuntu/+bug/1263233
56 // https://bitbucket.org/breakfastquay/rubberband/issue/4/sigfpe-zero-division-with-high-time-ratios
57 const double kMinSeekSpeed = 1.0 / 128.0;
58 double speed_abs = fabs(*pTempoRatio);
59 if (speed_abs < kMinSeekSpeed) {
60 // Let the caller know we ignored their speed.
61 speed_abs = *pTempoRatio = 0;
62 }
63
64 // RubberBand handles checking for whether the change in pitchScale is a
65 // no-op.
66 double pitchScale = fabs(base_rate * *pPitchRatio);
67
68 if (pitchScale > 0) {
69 //qDebug() << "EngineBufferScaleRubberBand setPitchScale" << *pitch << pitchScale;
70 m_pRubberBand->setPitchScale(pitchScale);
71 }
72
73 // RubberBand handles checking for whether the change in timeRatio is a
74 // no-op. Time ratio is the ratio of stretched to unstretched duration. So 1
75 // second in real duration is 0.5 seconds in stretched duration if tempo is
76 // 2.
77 double timeRatioInverse = base_rate * speed_abs;
78 if (timeRatioInverse > 0) {
79 //qDebug() << "EngineBufferScaleRubberBand setTimeRatio" << 1 / timeRatioInverse;
80 m_pRubberBand->setTimeRatio(1.0 / timeRatioInverse);
81 }
82
83 if (m_pRubberBand->getInputIncrement() == 0) {
84 qWarning() << "EngineBufferScaleRubberBand inputIncrement is 0."
85 << "On RubberBand <=1.8.1 a SIGFPE is imminent despite"
86 << "our workaround. Taking evasive action."
87 << "Please report this message to mixxx-devel@lists.sourceforge.net.";
88
89 // This is much slower than the minimum seek speed workaround above.
90 while (m_pRubberBand->getInputIncrement() == 0) {
91 timeRatioInverse += 0.001;
92 m_pRubberBand->setTimeRatio(1.0 / timeRatioInverse);
93 }
94 speed_abs = timeRatioInverse / base_rate;
95 *pTempoRatio = m_bBackwards ? -speed_abs : speed_abs;
96 }
97
98 // Used by other methods so we need to keep them up to date.
99 m_dBaseRate = base_rate;
100 m_dTempoRatio = speed_abs;
101 m_dPitchRatio = *pPitchRatio;
102 }
103
onSampleRateChanged()104 void EngineBufferScaleRubberBand::onSampleRateChanged() {
105 // TODO: Resetting the sample rate will cause internal
106 // memory allocations that may block the real-time thread.
107 // When is this function actually invoked??
108 if (!getOutputSignal().isValid()) {
109 m_pRubberBand.reset();
110 return;
111 }
112 m_pRubberBand = std::make_unique<RubberBandStretcher>(
113 getOutputSignal().getSampleRate(),
114 getOutputSignal().getChannelCount(),
115 RubberBandStretcher::OptionProcessRealTime);
116 m_pRubberBand->setMaxProcessSize(kRubberBandBlockSize);
117 // Setting the time ratio to a very high value will cause RubberBand
118 // to preallocate buffers large enough to (almost certainly)
119 // avoid memory reallocations during playback.
120 m_pRubberBand->setTimeRatio(2.0);
121 m_pRubberBand->setTimeRatio(1.0);
122 }
123
clear()124 void EngineBufferScaleRubberBand::clear() {
125 VERIFY_OR_DEBUG_ASSERT(m_pRubberBand) {
126 return;
127 }
128 m_pRubberBand->reset();
129 }
130
retrieveAndDeinterleave(CSAMPLE * pBuffer,SINT frames)131 SINT EngineBufferScaleRubberBand::retrieveAndDeinterleave(
132 CSAMPLE* pBuffer,
133 SINT frames) {
134 SINT frames_available = m_pRubberBand->available();
135 SINT frames_to_read = math_min(frames_available, frames);
136 SINT received_frames = m_pRubberBand->retrieve(
137 (float* const*)m_retrieve_buffer, frames_to_read);
138
139 SampleUtil::interleaveBuffer(pBuffer,
140 m_retrieve_buffer[0],
141 m_retrieve_buffer[1],
142 received_frames);
143 return received_frames;
144 }
145
deinterleaveAndProcess(const CSAMPLE * pBuffer,SINT frames,bool flush)146 void EngineBufferScaleRubberBand::deinterleaveAndProcess(
147 const CSAMPLE* pBuffer, SINT frames, bool flush) {
148
149 SampleUtil::deinterleaveBuffer(
150 m_retrieve_buffer[0], m_retrieve_buffer[1], pBuffer, frames);
151
152 m_pRubberBand->process((const float* const*)m_retrieve_buffer,
153 frames, flush);
154 }
155
scaleBuffer(CSAMPLE * pOutputBuffer,SINT iOutputBufferSize)156 double EngineBufferScaleRubberBand::scaleBuffer(
157 CSAMPLE* pOutputBuffer,
158 SINT iOutputBufferSize) {
159 if (m_dBaseRate == 0.0 || m_dTempoRatio == 0.0) {
160 SampleUtil::clear(pOutputBuffer, iOutputBufferSize);
161 // No actual samples/frames have been read from the
162 // unscaled input buffer!
163 return 0.0;
164 }
165
166 SINT total_received_frames = 0;
167 SINT total_read_frames = 0;
168
169 SINT remaining_frames = getOutputSignal().samples2frames(iOutputBufferSize);
170 CSAMPLE* read = pOutputBuffer;
171 bool last_read_failed = false;
172 bool break_out_after_retrieve_and_reset_rubberband = false;
173 while (remaining_frames > 0) {
174 // ReadAheadManager will eventually read the requested frames with
175 // enough calls to retrieveAndDeinterleave because CachingReader returns
176 // zeros for reads that are not in cache. So it's safe to loop here
177 // without any checks for failure in retrieveAndDeinterleave.
178 SINT received_frames = retrieveAndDeinterleave(
179 read, remaining_frames);
180 remaining_frames -= received_frames;
181 total_received_frames += received_frames;
182 read += getOutputSignal().frames2samples(received_frames);
183
184 if (break_out_after_retrieve_and_reset_rubberband) {
185 //qDebug() << "break_out_after_retrieve_and_reset_rubberband";
186 // If we break out early then we have flushed RubberBand and need to
187 // reset it.
188 m_pRubberBand->reset();
189 break;
190 }
191
192 size_t iLenFramesRequired = m_pRubberBand->getSamplesRequired();
193 if (iLenFramesRequired == 0) {
194 // rubberband 1.3 (packaged up through Ubuntu Quantal) has a bug
195 // where it can report 0 samples needed forever which leads us to an
196 // infinite loop. To work around this, we check if available() is
197 // zero. If it is, then we submit a fixed block size of
198 // kRubberBandBlockSize.
199 int available = m_pRubberBand->available();
200 if (available == 0) {
201 iLenFramesRequired = kRubberBandBlockSize;
202 }
203 }
204 //qDebug() << "iLenFramesRequired" << iLenFramesRequired;
205
206 if (remaining_frames > 0 && iLenFramesRequired > 0) {
207 SINT iAvailSamples = m_pReadAheadManager->getNextSamples(
208 // The value doesn't matter here. All that matters is we
209 // are going forward or backward.
210 (m_bBackwards ? -1.0 : 1.0) * m_dBaseRate * m_dTempoRatio,
211 m_buffer_back,
212 getOutputSignal().frames2samples(iLenFramesRequired));
213 SINT iAvailFrames = getOutputSignal().samples2frames(iAvailSamples);
214
215 if (iAvailFrames > 0) {
216 last_read_failed = false;
217 total_read_frames += iAvailFrames;
218 deinterleaveAndProcess(m_buffer_back, iAvailFrames, false);
219 } else {
220 if (last_read_failed) {
221 // Flush and break out after the next retrieval. If we are
222 // at EOF this serves to get the last samples out of
223 // RubberBand.
224 deinterleaveAndProcess(m_buffer_back, 0, true);
225 break_out_after_retrieve_and_reset_rubberband = true;
226 }
227 last_read_failed = true;
228 }
229 }
230 }
231
232 if (remaining_frames > 0) {
233 SampleUtil::clear(read, getOutputSignal().frames2samples(remaining_frames));
234 Counter counter("EngineBufferScaleRubberBand::getScaled underflow");
235 counter.increment();
236 }
237
238 // framesRead is interpreted as the total number of virtual sample frames
239 // consumed to produce the scaled buffer. Due to this, we do not take into
240 // account directionality or starting point.
241 // NOTE(rryan): Why no m_dPitchAdjust here? Pitch does not change the time
242 // ratio. m_dSpeedAdjust is the ratio of unstretched time to stretched
243 // time. So, if we used total_received_frames in stretched time, then
244 // multiplying that by the ratio of unstretched time to stretched time
245 // will get us the unstretched sample frames read.
246 double framesRead = m_dBaseRate * m_dTempoRatio * total_received_frames;
247
248 return framesRead;
249 }
250