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