Skip to content

Commit

Permalink
Revert "Merge pull request mixxxdj#11120 from robbert-vdh/fix/missing…
Browse files Browse the repository at this point in the history
…-rubberband-padding"

This reverts commit 0a15ae8, reversing
changes made to f0d9272.
  • Loading branch information
napaalm committed Dec 30, 2023
1 parent 5474aae commit 58f7cc4
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 156 deletions.
184 changes: 65 additions & 119 deletions src/engine/bufferscalers/enginebufferscalerubberband.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "engine/bufferscalers/enginebufferscalerubberband.h"

#include <rubberband/RubberBandStretcher.h>

#include <QtDebug>

#include "engine/readaheadmanager.h"
Expand All @@ -11,21 +13,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) {
Expand Down Expand Up @@ -114,6 +131,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.
Expand All @@ -125,54 +145,33 @@ 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 frames_available = m_pRubberBand->available();
SINT frames_to_read = math_min(frames_available, frames);
SINT received_frames = static_cast<SINT>(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;
}
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) {
DEBUG_ASSERT(frames <= static_cast<SINT>(m_buffers[0].size()));
//DEBUG_ASSERT(frames <= static_cast<SINT>(m_buffers[0].size())); //TODO

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(),
m_pRubberBand->process(m_retrieve_buffer,
frames,
false);
}
Expand All @@ -196,46 +195,50 @@ 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;
readFramesProcessed += m_effectiveRate * received_frames;
read += getOutputSignal().frames2samples(received_frames);

const SINT next_block_frames_required =
static_cast<SINT>(m_pRubberBand->getSamplesRequired());
if (remaining_frames > 0 && next_block_frames_required > 0) {
// The requested setting becomes effective after all previous frames have been processed
m_effectiveRate = m_dBaseRate * m_dTempoRatio;
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) {
SINT iLenFramesRequired = static_cast<SINT>(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);
deinterleaveAndProcess(m_buffer_back, iAvailFrames);
} else {
// We may get 0 samples once if we just hit a loop trigger, e.g.
// when reloop_toggle jumps back to loop_in, or when moving a
// loop causes the play position to be moved along.
if (last_read_failed) {
// If we get 0 samples repeatedly, flush and break out after
// the next retrieval. If we are at EOF this serves to get
// the last samples out of RubberBand.
qDebug() << "ReadAheadManager::getNextSamples() returned "
"zero samples repeatedly. Padding with silence.";
SampleUtil::clear(
m_interleavedReadBuffer.data(),
getOutputSignal().frames2samples(next_block_frames_required));
deinterleaveAndProcess(m_interleavedReadBuffer.data(),
next_block_frames_required);
// 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);
}
last_read_failed = true;
}
Expand Down Expand Up @@ -266,67 +269,10 @@ 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<size_t>(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();
#else
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<size_t>(remaining_padding, m_buffers[0].size());
std::fill_n(m_buffers[0].span().begin(), block_size, 0.0f);
std::fill_n(m_buffers[1].span().begin(), block_size, 0.0f);
while (remaining_padding > 0) {
const size_t pad_samples = std::min<size_t>(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<SINT>(getStartDelay());
}
45 changes: 8 additions & 37 deletions src/engine/bufferscalers/enginebufferscalerubberband.h
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
#pragma once

#include <rubberband/RubberBandStretcher.h>

#include <array>

#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();
Expand All @@ -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);
SINT retrieveAndDeinterleave(CSAMPLE* pBuffer, SINT frames);
Expand All @@ -64,24 +48,11 @@ class EngineBufferScaleRubberBand final : public EngineBufferScale {

std::unique_ptr<RubberBand::RubberBandStretcher> 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<mixxx::SampleBuffer, 2> 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<float*, 2> 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;
};

0 comments on commit 58f7cc4

Please sign in to comment.