diff --git a/src/engine/bufferscalers/enginebufferscalerubberband.cpp b/src/engine/bufferscalers/enginebufferscalerubberband.cpp index fce0b91cc705..017ab47318a8 100644 --- a/src/engine/bufferscalers/enginebufferscalerubberband.cpp +++ b/src/engine/bufferscalers/enginebufferscalerubberband.cpp @@ -1,5 +1,7 @@ #include "engine/bufferscalers/enginebufferscalerubberband.h" +#include + #include #include "control/controlobject.h" @@ -13,21 +15,36 @@ using RubberBand::RubberBandStretcher; +namespace { + +// TODO (XXX): this should be removed. It is only needed to work around +// a Rubberband 1.3 bug. +// This is the default increment from RubberBand 1.8.1. +size_t kRubberBandBlockSize = 256; + #define RUBBERBANDV3 (RUBBERBAND_API_MAJOR_VERSION >= 2 && RUBBERBAND_API_MINOR_VERSION >= 7) +} // namespace + EngineBufferScaleRubberBand::EngineBufferScaleRubberBand( ReadAheadManager* pReadAheadManager) : m_pReadAheadManager(pReadAheadManager), - m_buffers{mixxx::SampleBuffer(MAX_BUFFER_LEN), mixxx::SampleBuffer(MAX_BUFFER_LEN)}, - m_bufferPtrs{m_buffers[0].data(), m_buffers[1].data()}, - m_interleavedReadBuffer(MAX_BUFFER_LEN), + m_buffer_back(SampleUtil::alloc(MAX_BUFFER_LEN)), m_bBackwards(false), m_useEngineFiner(false) { + m_retrieve_buffer[0] = SampleUtil::alloc(MAX_BUFFER_LEN); + m_retrieve_buffer[1] = SampleUtil::alloc(MAX_BUFFER_LEN); // Initialize the internal buffers to prevent re-allocations // in the real-time thread. onSampleRateChanged(); } +EngineBufferScaleRubberBand::~EngineBufferScaleRubberBand() { + SampleUtil::free(m_buffer_back); + SampleUtil::free(m_retrieve_buffer[0]); + SampleUtil::free(m_retrieve_buffer[1]); +} + void EngineBufferScaleRubberBand::setScaleParameters(double base_rate, double* pTempoRatio, double* pPitchRatio) { @@ -112,6 +129,9 @@ void EngineBufferScaleRubberBand::onSampleRateChanged() { getOutputSignal().getSampleRate(), getOutputSignal().getChannelCount(), rubberbandOptions); + // TODO (XXX): we should always be able to provide rubberband as + // many samples as it wants. So remove this. + 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. @@ -123,52 +143,32 @@ void EngineBufferScaleRubberBand::clear() { VERIFY_OR_DEBUG_ASSERT(m_pRubberBand) { return; } - reset(); + m_pRubberBand->reset(); } SINT EngineBufferScaleRubberBand::retrieveAndDeinterleave( CSAMPLE* pBuffer, SINT frames) { - const SINT frames_available = m_pRubberBand->available(); - // NOTE: If we still need to throw away padding, then we can also - // immediately read those frames in addition to the frames we actually - // need for the output - const SINT frames_to_read = math_min(frames_available, frames + m_remainingPaddingInOutput); - DEBUG_ASSERT(frames_to_read <= m_buffers[0].size()); - SINT received_frames = static_cast(m_pRubberBand->retrieve( - m_bufferPtrs.data(), frames_to_read)); - SINT frame_offset = 0; - - // As explained below in `reset()`, the first time this is called we need to - // drop the silence we fed into the time stretcher as padding from the - // output - if (m_remainingPaddingInOutput > 0) { - const SINT drop_num_frames = std::min(received_frames, m_remainingPaddingInOutput); - - m_remainingPaddingInOutput -= drop_num_frames; - received_frames -= drop_num_frames; - frame_offset += drop_num_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); - DEBUG_ASSERT(received_frames <= frames); SampleUtil::interleaveBuffer(pBuffer, - m_buffers[0].data() + frame_offset, - m_buffers[1].data() + frame_offset, - received_frames); - + m_retrieve_buffer[0], + m_retrieve_buffer[1], + received_frames); return received_frames; } void EngineBufferScaleRubberBand::deinterleaveAndProcess( const CSAMPLE* pBuffer, SINT frames, bool flush) { - DEBUG_ASSERT(frames <= static_cast(m_buffers[0].size())); SampleUtil::deinterleaveBuffer( - m_buffers[0].data(), m_buffers[1].data(), pBuffer, frames); + m_retrieve_buffer[0], m_retrieve_buffer[1], pBuffer, frames); - m_pRubberBand->process(m_bufferPtrs.data(), - frames, - flush); + m_pRubberBand->process((const float* const*)m_retrieve_buffer, + frames, flush); } double EngineBufferScaleRubberBand::scaleBuffer( @@ -192,9 +192,6 @@ double EngineBufferScaleRubberBand::scaleBuffer( // 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. - // If the time stretcher has just been reset then this will throw away - // the first `m_remainingPaddingInOutput` samples of silence padding - // from the output. SINT received_frames = retrieveAndDeinterleave( read, remaining_frames); remaining_frames -= received_frames; @@ -202,33 +199,48 @@ double EngineBufferScaleRubberBand::scaleBuffer( read += getOutputSignal().frames2samples(received_frames); if (break_out_after_retrieve_and_reset_rubberband) { - // qDebug() << "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. - reset(); + m_pRubberBand->reset(); break; } - const SINT next_block_frames_required = - static_cast(m_pRubberBand->getSamplesRequired()); - if (remaining_frames > 0 && next_block_frames_required > 0) { - const SINT available_samples = 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_interleavedReadBuffer.data(), - getOutputSignal().frames2samples(next_block_frames_required)); - const SINT available_frames = getOutputSignal().samples2frames(available_samples); - - if (available_frames > 0) { + size_t iLenFramesRequired = m_pRubberBand->getSamplesRequired(); + if (iLenFramesRequired == 0) { + // TODO (XXX): Rubberband 1.3 is not being packaged anymore. + // Remove this workaround. + // + // 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, + getOutputSignal().frames2samples(iLenFramesRequired)); + SINT iAvailFrames = getOutputSignal().samples2frames(iAvailSamples); + + if (iAvailFrames > 0) { last_read_failed = false; - deinterleaveAndProcess(m_interleavedReadBuffer.data(), available_frames, false); + 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_interleavedReadBuffer.data(), 0, true); + deinterleaveAndProcess(m_buffer_back, 0, true); break_out_after_retrieve_and_reset_rubberband = true; } last_read_failed = true; @@ -267,34 +279,6 @@ void EngineBufferScaleRubberBand::useEngineFiner(bool enable) { } } -// See -// https://github.com/breakfastquay/rubberband/commit/72654b04ea4f0707e214377515119e933efbdd6c -// for how these two functions were implemented within librubberband itself -size_t EngineBufferScaleRubberBand::getPreferredStartPad() const { -#if RUBBERBANDV3 - return m_pRubberBand->getPreferredStartPad(); -#else - // `getPreferredStartPad()` returns `window_size / 2`, while with - // `getLatency()` both time stretching engines return `window_size / 2 / - // pitch_scale` - return static_cast(std::ceil( - m_pRubberBand->getLatency() * m_pRubberBand->getPitchScale())); -#endif -} - -size_t EngineBufferScaleRubberBand::getStartDelay() const { -#if RUBBERBANDV3 - return m_pRubberBand->getStartDelay(); -#else - // In newer Rubber Band versions `getLatency()` is a deprecated alias for - // `getStartDelay()`, so they should behave the same. In the commit linked - // above the behavior was different for the R3 stretcher, but that was only - // during the initial betas of Rubberband 3.0 so we shouldn't have to worry - // about that. - return m_pRubberBand->getLatency(); -#endif -} - int EngineBufferScaleRubberBand::runningEngineVersion() { #if RUBBERBANDV3 return m_pRubberBand->getEngineVersion(); @@ -302,32 +286,3 @@ int EngineBufferScaleRubberBand::runningEngineVersion() { return 2; #endif } - -void EngineBufferScaleRubberBand::reset() { - m_pRubberBand->reset(); - - // As mentioned in the docs (https://breakfastquay.com/rubberband/code-doc/) - // and FAQ (https://breakfastquay.com/rubberband/integration.html#faqs), you - // need to run some silent samples through the time stretching engine first - // before using it. Otherwise it will eat add a short fade-in, destroying - // the initial transient. - // - // See https://github.com/mixxxdj/mixxx/pull/11120#discussion_r1050011104 - // for more information. - size_t remaining_padding = getPreferredStartPad(); - const size_t block_size = std::min(remaining_padding, m_buffers[0].size()); - std::fill_n(m_buffers[0].data(), block_size, 0.0f); - std::fill_n(m_buffers[1].data(), block_size, 0.0f); - while (remaining_padding > 0) { - const size_t pad_samples = std::min(remaining_padding, block_size); - m_pRubberBand->process(m_bufferPtrs.data(), pad_samples, false); - - remaining_padding -= pad_samples; - } - - // The silence we just added covers half a window (see the last paragraph of - // https://github.com/mixxxdj/mixxx/pull/11120#discussion_r1050011104). This - // silence should be dropped from the result when the `retrieve()` in - // `retrieveAndDeinterleave()` first starts producing audio. - m_remainingPaddingInOutput = static_cast(getStartDelay()); -} diff --git a/src/engine/bufferscalers/enginebufferscalerubberband.h b/src/engine/bufferscalers/enginebufferscalerubberband.h index 483741093703..ec5e2248c888 100644 --- a/src/engine/bufferscalers/enginebufferscalerubberband.h +++ b/src/engine/bufferscalers/enginebufferscalerubberband.h @@ -1,27 +1,21 @@ #pragma once -#include - -#include - #include "engine/bufferscalers/enginebufferscale.h" #include "util/memory.h" -#include "util/samplebuffer.h" + +namespace RubberBand { +class RubberBandStretcher; +} // namespace RubberBand class ReadAheadManager; // Uses librubberband to scale audio. This class is not thread safe. -class EngineBufferScaleRubberBand final : public EngineBufferScale { +class EngineBufferScaleRubberBand : public EngineBufferScale { Q_OBJECT public: explicit EngineBufferScaleRubberBand( ReadAheadManager* pReadAheadManager); - - EngineBufferScaleRubberBand(const EngineBufferScaleRubberBand&) = delete; - EngineBufferScaleRubberBand& operator=(const EngineBufferScaleRubberBand&) = delete; - - EngineBufferScaleRubberBand(EngineBufferScaleRubberBand&&) = delete; - EngineBufferScaleRubberBand& operator=(EngineBufferScaleRubberBand&&) = delete; + ~EngineBufferScaleRubberBand() override; // Let EngineBuffer know if engine v3 is available static bool isEngineFinerAvailable(); @@ -44,17 +38,7 @@ class EngineBufferScaleRubberBand final : public EngineBufferScale { // Reset RubberBand library with new audio signal void onSampleRateChanged() override; - /// Calls `m_pRubberBand->getPreferredStartPad()`, with backwards - /// compatibility for older librubberband versions. - size_t getPreferredStartPad() const; - /// Calls `m_pRubberBand->getStartDelay()`, with backwards compatibility for - /// older librubberband versions. - size_t getStartDelay() const; int runningEngineVersion(); - /// Reset the rubberband instance and run the prerequisite amount of padding - /// through it. This should be used instead of calling - /// `m_pRubberBand->reset()` directly. - void reset(); void deinterleaveAndProcess(const CSAMPLE* pBuffer, SINT frames, bool flush); SINT retrieveAndDeinterleave(CSAMPLE* pBuffer, SINT frames); @@ -64,24 +48,11 @@ class EngineBufferScaleRubberBand final : public EngineBufferScale { std::unique_ptr m_pRubberBand; - /// The audio buffers samples used to send audio to Rubber Band and to - /// receive processed audio from Rubber Band. This is needed because Mixxx - /// uses interleaved buffers in most other places. - std::array m_buffers; - /// These point to the buffers in `m_buffers`. They can be defined here - /// since this object cannot be moved or copied. - std::array m_bufferPtrs; - - /// Contains interleaved samples read from `m_pReadAheadManager`. These need - /// to be deinterleaved before they can be passed to Rubber Band. - mixxx::SampleBuffer m_interleavedReadBuffer; + CSAMPLE* m_retrieve_buffer[2]; + CSAMPLE* m_buffer_back; // Holds the playback direction bool m_bBackwards; - /// The amount of silence padding that still needs to be dropped from the - /// retrieve samples in `retrieveAndDeinterleave()`. See the `reset()` - /// function for an explanation. - SINT m_remainingPaddingInOutput = 0; bool m_useEngineFiner; };