Skip to content

Commit

Permalink
feat: multithreaded rubberband for stem
Browse files Browse the repository at this point in the history
  • Loading branch information
acolombier committed May 13, 2024
1 parent c48bf15 commit c3f57ee
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 30 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3239,6 +3239,7 @@ if(RUBBERBAND)
target_sources(mixxx-lib PRIVATE
src/effects/backends/builtin/pitchshifteffect.cpp
src/engine/bufferscalers/enginebufferscalerubberband.cpp
src/engine/bufferscalers/enginebufferscalerubberbandthread.cpp
)
endif()

Expand Down
9 changes: 9 additions & 0 deletions src/coreservices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
#include "database/mixxxdb.h"
#include "effects/effectsmanager.h"
#include "engine/enginemixer.h"
#ifdef __RUBBERBAND__
#include "engine/bufferscalers/enginebufferscalerubberband.h"
#endif
#include "library/coverartcache.h"
#include "library/library.h"
#include "library/library_prefs.h"
Expand Down Expand Up @@ -267,6 +270,9 @@ void CoreServices::initialize(QApplication* pApp) {
m_pEffectsManager.get(),
pChannelHandleFactory,
true);
#ifdef __RUBBERBAND__
RubberBandWorkerPool::createInstance();
#endif

emit initializationProgressUpdate(30, tr("audio interface"));
// Although m_pSoundManager is created here, m_pSoundManager->setupDevices()
Expand Down Expand Up @@ -609,6 +615,9 @@ void CoreServices::finalize() {
// EngineMixer depends on Config and m_pEffectsManager.
qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting EngineMixer";
CLEAR_AND_CHECK_DELETED(m_pEngine);
#ifdef __RUBBERBAND__
RubberBandWorkerPool::destroy();
#endif

// Destroy PlayerInfo explicitly to release the track
// pointers of tracks that were still loaded in decks
Expand Down
70 changes: 41 additions & 29 deletions src/engine/bufferscalers/enginebufferscalerubberband.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "util/counter.h"
#include "util/defs.h"
#include "util/math.h"
#include "util/mutex.h"
#include "util/sample.h"
#include "util/timer.h"

Expand Down Expand Up @@ -57,7 +58,7 @@ void EngineBufferScaleRubberBand::setScaleParameters(double base_rate,

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

// RubberBand handles checking for whether the change in timeRatio is a
Expand All @@ -67,20 +68,20 @@ void EngineBufferScaleRubberBand::setScaleParameters(double base_rate,
double timeRatioInverse = base_rate * speed_abs;
if (timeRatioInverse > 0) {
//qDebug() << "EngineBufferScaleRubberBand setTimeRatio" << 1 / timeRatioInverse;
m_pRubberBand->setTimeRatio(1.0 / timeRatioInverse);
m_pRubberBand.setTimeRatio(1.0 / timeRatioInverse);
}

if (runningEngineVersion() == 2) {
if (m_pRubberBand->getInputIncrement() == 0) {
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 file an issue on https://github.com/mixxxdj/mixxx/issues";

// This is much slower than the minimum seek speed workaround above.
while (m_pRubberBand->getInputIncrement() == 0) {
while (m_pRubberBand.getInputIncrement() == 0) {
timeRatioInverse += 0.001;
m_pRubberBand->setTimeRatio(1.0 / timeRatioInverse);
m_pRubberBand.setTimeRatio(1.0 / timeRatioInverse);
}
speed_abs = timeRatioInverse / base_rate;
*pTempoRatio = m_bBackwards ? -speed_abs : speed_abs;
Expand All @@ -97,7 +98,7 @@ void EngineBufferScaleRubberBand::onOutputSignalChanged() {
// memory allocations that may block the real-time thread.
// When is this function actually invoked??
if (!getOutputSignal().isValid()) {
m_pRubberBand.reset();
// m_pRubberBand.clear();
return;
}

Expand All @@ -110,7 +111,7 @@ void EngineBufferScaleRubberBand::onOutputSignalChanged() {
m_bufferPtrs.resize(channelCount);
}

m_pRubberBand.reset();
m_pRubberBand.clear();

for (int c = 0; c < channelCount; c++) {
if (m_buffers[c].size() == MAX_BUFFER_LEN) {
Expand All @@ -132,19 +133,19 @@ void EngineBufferScaleRubberBand::onOutputSignalChanged() {
}
#endif

m_pRubberBand = std::make_unique<RubberBandStretcher>(
m_pRubberBand.setup(
getOutputSignal().getSampleRate(),
getOutputSignal().getChannelCount(),
rubberbandOptions);
// 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);
m_pRubberBand.setTimeRatio(2.0);
m_pRubberBand.setTimeRatio(1.0);
}

void EngineBufferScaleRubberBand::clear() {
VERIFY_OR_DEBUG_ASSERT(m_pRubberBand) {
VERIFY_OR_DEBUG_ASSERT(m_pRubberBand.isValid()) {
return;
}
reset();
Expand All @@ -153,17 +154,21 @@ void EngineBufferScaleRubberBand::clear() {
SINT EngineBufferScaleRubberBand::retrieveAndDeinterleave(
CSAMPLE* pBuffer,
SINT frames) {
VERIFY_OR_DEBUG_ASSERT(m_pRubberBand) {
VERIFY_OR_DEBUG_ASSERT(m_pRubberBand.isValid()) {
return 0;
}
const SINT frames_available = m_pRubberBand->available();
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<SINT>(m_pRubberBand->retrieve(
m_bufferPtrs.data(), frames_to_read));
DEBUG_ASSERT(frames_to_read <= MAX_BUFFER_LEN);
SINT received_frames;
{
ScopedTimer t(u"RubberBand::retrieve");
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
Expand Down Expand Up @@ -215,7 +220,7 @@ SINT EngineBufferScaleRubberBand::retrieveAndDeinterleave(
void EngineBufferScaleRubberBand::deinterleaveAndProcess(
const CSAMPLE* pBuffer,
SINT frames) {
VERIFY_OR_DEBUG_ASSERT(m_pRubberBand) {
VERIFY_OR_DEBUG_ASSERT(m_pRubberBand.isValid()) {
return;
}
DEBUG_ASSERT(frames <= static_cast<SINT>(m_buffers[0].size()));
Expand Down Expand Up @@ -252,17 +257,21 @@ void EngineBufferScaleRubberBand::deinterleaveAndProcess(
} break;
}

m_pRubberBand->process(m_bufferPtrs.data(),
frames,
false);
{
ScopedTimer t(u"RubberBand::process");
m_pRubberBand.process(m_bufferPtrs.data(),
frames,
false);
}
}

double EngineBufferScaleRubberBand::scaleBuffer(
CSAMPLE* pOutputBuffer,
SINT iOutputBufferSize) {
VERIFY_OR_DEBUG_ASSERT(m_pRubberBand) {
VERIFY_OR_DEBUG_ASSERT(m_pRubberBand.isValid()) {
return 0.0;
}
ScopedTimer t(u"EngineBufferScaleRubberBand::scaleBuffer");
if (m_dBaseRate == 0.0 || m_dTempoRatio == 0.0) {
SampleUtil::clear(pOutputBuffer, iOutputBufferSize);
// No actual samples/frames have been read from the
Expand All @@ -289,7 +298,7 @@ double EngineBufferScaleRubberBand::scaleBuffer(
read += getOutputSignal().frames2samples(received_frames);

const SINT next_block_frames_required =
static_cast<SINT>(m_pRubberBand->getSamplesRequired());
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;
Expand Down Expand Up @@ -355,39 +364,39 @@ void EngineBufferScaleRubberBand::useEngineFiner(bool enable) {
// for how these two functions were implemented within librubberband itself
size_t EngineBufferScaleRubberBand::getPreferredStartPad() const {
#if RUBBERBANDV3
return m_pRubberBand->getPreferredStartPad();
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()));
m_pRubberBand.getLatency() * m_pRubberBand.getPitchScale()));
#endif
}

size_t EngineBufferScaleRubberBand::getStartDelay() const {
#if RUBBERBANDV3
return m_pRubberBand->getStartDelay();
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();
return m_pRubberBand.getLatency();
#endif
}

int EngineBufferScaleRubberBand::runningEngineVersion() {
#if RUBBERBANDV3
return m_pRubberBand->getEngineVersion();
return m_pRubberBand.getEngineVersion();
#else
return 2;
#endif
}

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

// As mentioned in the docs (https://breakfastquay.com/rubberband/code-doc/)
// and FAQ (https://breakfastquay.com/rubberband/integration.html#faqs), you
Expand All @@ -404,7 +413,10 @@ void EngineBufferScaleRubberBand::reset() {
}
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);
{
ScopedTimer t(u"RubberBand::process");
m_pRubberBand.process(m_bufferPtrs.data(), pad_samples, false);
}

remaining_padding -= pad_samples;
}
Expand Down
3 changes: 2 additions & 1 deletion src/engine/bufferscalers/enginebufferscalerubberband.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <array>

#include "engine/bufferscalers/enginebufferscale.h"
#include "engine/bufferscalers/enginebufferscalerubberbandthread.h"
#include "util/memory.h"
#include "util/samplebuffer.h"

Expand Down Expand Up @@ -62,7 +63,7 @@ class EngineBufferScaleRubberBand final : public EngineBufferScale {
// The read-ahead manager that we use to fetch samples
ReadAheadManager* m_pReadAheadManager;

std::unique_ptr<RubberBand::RubberBandStretcher> m_pRubberBand;
RubberBandWrapper 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
Expand Down
Loading

0 comments on commit c3f57ee

Please sign in to comment.