Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rubberband: switch between v2 and v3 at runtime #4855

Merged
merged 8 commits into from
Aug 6, 2022
23 changes: 19 additions & 4 deletions src/engine/bufferscalers/enginebufferscalerubberband.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ EngineBufferScaleRubberBand::EngineBufferScaleRubberBand(
ReadAheadManager* pReadAheadManager)
: m_pReadAheadManager(pReadAheadManager),
m_buffer_back(SampleUtil::alloc(MAX_BUFFER_LEN)),
m_bBackwards(false) {
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
Expand Down Expand Up @@ -116,10 +117,12 @@ void EngineBufferScaleRubberBand::onSampleRateChanged() {
m_pRubberBand.reset();
return;
}
RubberBandStretcher::Options rubberbandOptions = RubberBandStretcher::OptionProcessRealTime;
RubberBandStretcher::Options rubberbandOptions =
RubberBandStretcher::OptionProcessRealTime;
#if RUBBERBANDV3
// TODO make this a runtime option
rubberbandOptions |= RubberBandStretcher::OptionEngineFiner;
if (m_useEngineFiner) {
rubberbandOptions |= RubberBandStretcher::OptionEngineFiner;
}
#endif

m_pRubberBand = std::make_unique<RubberBandStretcher>(
Expand Down Expand Up @@ -264,6 +267,18 @@ double EngineBufferScaleRubberBand::scaleBuffer(
return framesRead;
}

// static
bool EngineBufferScaleRubberBand::isEngineFinerAvailable() {
return RUBBERBANDV3;
}

void EngineBufferScaleRubberBand::useEngineFiner(bool enable) {
if (isEngineFinerAvailable()) {
m_useEngineFiner = enable;
onSampleRateChanged();
}
}

int EngineBufferScaleRubberBand::runningEngineVersion() {
#if RUBBERBANDV3
return m_pRubberBand->getEngineVersion();
Expand Down
8 changes: 8 additions & 0 deletions src/engine/bufferscalers/enginebufferscalerubberband.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ class EngineBufferScaleRubberBand : public EngineBufferScale {
ReadAheadManager* pReadAheadManager);
~EngineBufferScaleRubberBand() override;

// Let EngineBuffer know if engine v3 is available
static bool isEngineFinerAvailable();

// Enable engine v3 if available
void useEngineFiner(bool enable);

void setScaleParameters(double base_rate,
double* pTempoRatio,
double* pPitchRatio) override;
Expand Down Expand Up @@ -47,4 +53,6 @@ class EngineBufferScaleRubberBand : public EngineBufferScale {

// Holds the playback direction
bool m_bBackwards;

bool m_useEngineFiner;
};
34 changes: 20 additions & 14 deletions src/engine/enginebuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,6 @@ EngineBuffer::EngineBuffer(const QString& group,

m_pSampleRate = new ControlProxy("[Master]", "samplerate", this);

m_pKeylockEngine = new ControlProxy("[Master]", "keylock_engine", this);
m_pKeylockEngine->connectValueChanged(this, &EngineBuffer::slotKeylockEngineChanged,
Qt::DirectConnection);

m_pTrackSamples = new ControlObject(ConfigKey(m_group, "track_samples"));
m_pTrackSampleRate = new ControlObject(ConfigKey(m_group, "track_samplerate"));

Expand Down Expand Up @@ -264,15 +260,15 @@ EngineBuffer::EngineBuffer(const QString& group,
m_pLoopingControl);
m_pReadAheadManager->addRateControl(m_pRateControl);

m_pKeylockEngine = new ControlProxy("[Master]", "keylock_engine", this);
m_pKeylockEngine->connectValueChanged(this,
&EngineBuffer::slotKeylockEngineChanged,
Qt::DirectConnection);
// Construct scaling objects
m_pScaleLinear = new EngineBufferScaleLinear(m_pReadAheadManager);
m_pScaleST = new EngineBufferScaleST(m_pReadAheadManager);
m_pScaleRB = new EngineBufferScaleRubberBand(m_pReadAheadManager);
if (m_pKeylockEngine->get() == static_cast<double>(SOUNDTOUCH)) {
m_pScaleKeylock = m_pScaleST;
} else {
m_pScaleKeylock = m_pScaleRB;
}
slotKeylockEngineChanged(m_pKeylockEngine->get());
m_pScaleVinyl = m_pScaleLinear;
m_pScale = m_pScaleVinyl;
m_pScale->clear();
Expand Down Expand Up @@ -802,13 +798,23 @@ void EngineBuffer::slotKeylockEngineChanged(double dIndex) {
if (m_bScalerOverride) {
return;
}
// static_cast<KeylockEngine>(dIndex); direct cast produces a "not used" warning with gcc
int iEngine = static_cast<int>(dIndex);
KeylockEngine engine = static_cast<KeylockEngine>(iEngine);
if (engine == SOUNDTOUCH) {
const KeylockEngine engine = static_cast<KeylockEngine>(dIndex);
switch (engine) {
case KeylockEngine::SoundTouch:
m_pScaleKeylock = m_pScaleST;
} else {
break;
case KeylockEngine::RubberBandFaster:
m_pScaleRB->useEngineFiner(false);
m_pScaleKeylock = m_pScaleRB;
break;
case KeylockEngine::RubberBandFiner:
m_pScaleRB->useEngineFiner(
true); // in case of Rubberband V2 it falls back to RUBBERBAND_FASTER
m_pScaleKeylock = m_pScaleRB;
break;
default:
slotKeylockEngineChanged(static_cast<double>(defaultKeylockEngine()));
break;
}
}

Expand Down
48 changes: 41 additions & 7 deletions src/engine/enginebuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
#include <QAtomicInt>
#include <QMutex>
#include <cfloat>
#include <initializer_list>

#include "audio/frame.h"
#include "control/controlvalue.h"
#include "engine/bufferscalers/enginebufferscalerubberband.h"
#include "engine/cachingreader/cachingreader.h"
#include "engine/engineobject.h"
#include "engine/sync/syncable.h"
Expand Down Expand Up @@ -74,10 +76,19 @@ class EngineBuffer : public EngineObject {
};
Q_DECLARE_FLAGS(SeekRequests, SeekRequest);

enum KeylockEngine {
SOUNDTOUCH,
RUBBERBAND,
KEYLOCK_ENGINE_COUNT,
// This enum is also used in mixxx.cfg
// Don't remove or swap values to keep backward compatibility
enum class KeylockEngine {
SoundTouch = 0,
RubberBandFaster = 1,
RubberBandFiner = 2,
};

// intended for iteration over the KeylockEngine enum
constexpr static std::initializer_list<KeylockEngine> kKeylockEngines = {
KeylockEngine::SoundTouch,
KeylockEngine::RubberBandFaster,
KeylockEngine::RubberBandFiner,
};

EngineBuffer(const QString& group, UserSettingsPointer pConfig,
Expand Down Expand Up @@ -144,15 +155,37 @@ class EngineBuffer : public EngineObject {

static QString getKeylockEngineName(KeylockEngine engine) {
switch (engine) {
case SOUNDTOUCH:
case KeylockEngine::SoundTouch:
return tr("Soundtouch (faster)");
case RUBBERBAND:
case KeylockEngine::RubberBandFaster:
return tr("Rubberband (better)");
case KeylockEngine::RubberBandFiner:
if (EngineBufferScaleRubberBand::isEngineFinerAvailable()) {
return tr("Rubberband R3 (near-hi-fi quality)");
}
[[fallthrough]];
default:
return tr("Unknown (bad value)");
return tr("Unknown, using Rubberband (better)");
}
}

static bool isKeylockEngineAvailable(KeylockEngine engine) {
switch (engine) {
case KeylockEngine::SoundTouch:
return true;
case KeylockEngine::RubberBandFaster:
return true;
case KeylockEngine::RubberBandFiner:
return EngineBufferScaleRubberBand::isEngineFinerAvailable();
default:
return false;
}
}

constexpr static KeylockEngine defaultKeylockEngine() {
return KeylockEngine::RubberBandFaster;
}

// Request that the EngineBuffer load a track. Since the process is
// asynchronous, EngineBuffer will emit a trackLoaded signal when the load
// has completed.
Expand Down Expand Up @@ -414,4 +447,5 @@ class EngineBuffer : public EngineObject {
QSharedPointer<VisualPlayPosition> m_visualPlayPos;
};

Q_DECLARE_METATYPE(EngineBuffer::KeylockEngine)
Q_DECLARE_OPERATORS_FOR_FLAGS(EngineBuffer::SeekRequests)
7 changes: 3 additions & 4 deletions src/engine/enginemaster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,9 @@ EngineMaster::EngineMaster(
ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderReverse"));
m_pXFaderReverse->setButtonMode(ControlPushButton::TOGGLE);

m_pKeylockEngine = new ControlObject(ConfigKey(group, "keylock_engine"),
true, false, true);
m_pKeylockEngine->set(pConfig->getValueString(
ConfigKey(group, "keylock_engine")).toDouble());
m_pKeylockEngine = new ControlObject(ConfigKey(group, "keylock_engine"), true, false, true);
m_pKeylockEngine->set(pConfig->getValue(ConfigKey(group, "keylock_engine"),
static_cast<double>(EngineBuffer::defaultKeylockEngine())));

// TODO: Make this read only and make EngineMaster decide whether
// processing the master mix is necessary.
Expand Down
40 changes: 28 additions & 12 deletions src/preferences/dialog/dlgprefsound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,11 @@ DlgPrefSound::DlgPrefSound(QWidget* pParent,
&DlgPrefSound::engineClockChanged);

keylockComboBox->clear();
for (int i = 0; i < EngineBuffer::KEYLOCK_ENGINE_COUNT; ++i) {
keylockComboBox->addItem(
EngineBuffer::getKeylockEngineName(
static_cast<EngineBuffer::KeylockEngine>(i)));
for (const auto engine : EngineBuffer::kKeylockEngines) {
if (EngineBuffer::isKeylockEngineAvailable(engine)) {
keylockComboBox->addItem(
EngineBuffer::getKeylockEngineName(engine), QVariant::fromValue(engine));
}
}

m_pLatencyCompensation = new ControlProxy("[Master]", "microphoneLatencyCompensation", this);
Expand Down Expand Up @@ -311,9 +312,9 @@ void DlgPrefSound::slotApply() {
SoundDeviceError err = SOUNDDEVICE_ERROR_OK;
{
ScopedWaitCursor cursor;
m_pKeylockEngine->set(keylockComboBox->currentIndex());
m_pKeylockEngine->set(keylockComboBox->currentData().toDouble());
m_pSettings->set(ConfigKey("[Master]", "keylock_engine"),
ConfigValue(keylockComboBox->currentIndex()));
ConfigValue(keylockComboBox->currentData().toInt()));

err = m_pSoundManager->setConfig(m_config);
}
Expand Down Expand Up @@ -496,10 +497,19 @@ void DlgPrefSound::loadSettings(const SoundManagerConfig &config) {
engineClockComboBox->setCurrentIndex(0);
}

// Default keylock is Rubberband.
int keylock_engine = m_pSettings->getValue(
ConfigKey("[Master]", "keylock_engine"), 1);
keylockComboBox->setCurrentIndex(keylock_engine);
// Default keylock engine is Rubberband Faster (v2)
const auto keylockEngine = static_cast<EngineBuffer::KeylockEngine>(
m_pSettings->getValue(ConfigKey("[Master]", "keylock_engine"),
static_cast<int>(EngineBuffer::defaultKeylockEngine())));
const auto keylockEngineVariant = QVariant::fromValue(keylockEngine);
const int index = keylockComboBox->findData(keylockEngineVariant);
if (index >= 0) {
keylockComboBox->setCurrentIndex(index);
} else {
keylockComboBox->addItem(
EngineBuffer::getKeylockEngineName(keylockEngine), keylockEngineVariant);
keylockComboBox->setCurrentIndex(keylockComboBox->count() - 1);
}

m_loading = false;
// DlgPrefSoundItem has it's own inhibit flag
Expand Down Expand Up @@ -681,8 +691,14 @@ void DlgPrefSound::slotResetToDefaults() {
SoundManagerConfig newConfig(m_pSoundManager.get());
newConfig.loadDefaults(m_pSoundManager.get(), SoundManagerConfig::ALL);
loadSettings(newConfig);
keylockComboBox->setCurrentIndex(EngineBuffer::RUBBERBAND);
m_pKeylockEngine->set(EngineBuffer::RUBBERBAND);

int keylock_engine = static_cast<int>(EngineBuffer::defaultKeylockEngine());
int index = keylockComboBox->findData(keylock_engine);
DEBUG_ASSERT(index >= 0);
if (index >= 0) {
keylockComboBox->setCurrentIndex(index);
}
m_pKeylockEngine->set(keylock_engine);
Swiftb0y marked this conversation as resolved.
Show resolved Hide resolved

masterMixComboBox->setCurrentIndex(1);
m_pMasterEnabled->set(1.0);
Expand Down
18 changes: 9 additions & 9 deletions src/test/enginebuffertest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ TEST_F(EngineBufferTest, SlowRubberBand) {

// With Soundtouch, it should switch the scaler as well
ControlObject::set(ConfigKey("[Master]", "keylock_engine"),
static_cast<double>(EngineBuffer::SOUNDTOUCH));
static_cast<double>(EngineBuffer::KeylockEngine::SoundTouch));
ProcessBuffer();
EXPECT_EQ(m_pMockScaleVinyl1, m_pChannel1->getEngineBuffer()->m_pScale);

Expand All @@ -113,14 +113,14 @@ TEST_F(EngineBufferTest, SlowRubberBand) {

// With Rubberband, and transport stopped it should be still keylock
ControlObject::set(ConfigKey("[Master]", "keylock_engine"),
static_cast<double>(EngineBuffer::RUBBERBAND));
static_cast<double>(EngineBuffer::KeylockEngine::RubberBandFaster));
ControlObject::set(ConfigKey(m_sGroup1, "rateSearch"), 0.0);
ProcessBuffer();
EXPECT_EQ(m_pMockScaleKeylock1, m_pChannel1->getEngineBuffer()->m_pScale);

ControlObject::set(ConfigKey(m_sGroup1, "rateSearch"), 0.0072);

// Paying at low rate, the vinyl scaler should be used
// Playing at low rate, the vinyl scaler should be used
ProcessBuffer();
EXPECT_EQ(m_pMockScaleVinyl1, m_pChannel1->getEngineBuffer()->m_pScale);
}
Expand Down Expand Up @@ -205,7 +205,7 @@ TEST_F(EngineBufferE2ETest, SoundTouchCrashTest) {
// Soundtouch has a bug where a pitch value of zero causes an infinite loop
// and crash.
ControlObject::set(ConfigKey("[Master]", "keylock_engine"),
static_cast<double>(EngineBuffer::SOUNDTOUCH));
static_cast<double>(EngineBuffer::KeylockEngine::SoundTouch));
ControlObject::set(ConfigKey(m_sGroup1, "pitch"), 1.2);
ControlObject::set(ConfigKey(m_sGroup1, "rate"), 0.05);
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
Expand Down Expand Up @@ -279,7 +279,7 @@ TEST_F(EngineBufferE2ETest, ReverseTest) {
TEST_F(EngineBufferE2ETest, DISABLED_SoundTouchToggleTest) {
// Test various cases where SoundTouch toggles on and off.
ControlObject::set(ConfigKey("[Master]", "keylock_engine"),
static_cast<double>(EngineBuffer::SOUNDTOUCH));
static_cast<double>(EngineBuffer::KeylockEngine::SoundTouch));
ControlObject::set(ConfigKey(m_sGroup1, "rate"), 0.5);
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
ProcessBuffer();
Expand All @@ -305,7 +305,7 @@ TEST_F(EngineBufferE2ETest, DISABLED_SoundTouchToggleTest) {
TEST_F(EngineBufferE2ETest, DISABLED_RubberbandToggleTest) {
// Test various cases where Rubberband toggles on and off.
ControlObject::set(ConfigKey("[Master]", "keylock_engine"),
static_cast<double>(EngineBuffer::RUBBERBAND));
static_cast<double>(EngineBuffer::KeylockEngine::RubberBandFaster));
ControlObject::set(ConfigKey(m_sGroup1, "rate"), 0.5);
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
ProcessBuffer();
Expand Down Expand Up @@ -336,7 +336,7 @@ TEST_F(EngineBufferE2ETest, DISABLED_KeylockReverseTest) {
// Confirm that when toggling reverse while keylock is on, interpolation
// is smooth.
ControlObject::set(ConfigKey("[Master]", "keylock_engine"),
static_cast<double>(EngineBuffer::SOUNDTOUCH));
static_cast<double>(EngineBuffer::KeylockEngine::SoundTouch));
ControlObject::set(ConfigKey(m_sGroup1, "keylockMode"),
0.0);
ControlObject::set(ConfigKey(m_sGroup1, "rate"), 0.5);
Expand Down Expand Up @@ -365,7 +365,7 @@ TEST_F(EngineBufferE2ETest, SoundTouchReverseTest) {
// This test must not crash when changing to reverse while pitch is tweaked
// Testing bug #1458263
ControlObject::set(ConfigKey("[Master]", "keylock_engine"),
static_cast<double>(EngineBuffer::SOUNDTOUCH));
static_cast<double>(EngineBuffer::KeylockEngine::SoundTouch));
ControlObject::set(ConfigKey(m_sGroup1, "pitch"), -1);
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
ProcessBuffer();
Expand All @@ -379,7 +379,7 @@ TEST_F(EngineBufferE2ETest, RubberbandReverseTest) {
// This test must not crash when changing to reverse while pitch is tweaked
// Testing bug #1458263
ControlObject::set(ConfigKey("[Master]", "keylock_engine"),
static_cast<double>(EngineBuffer::RUBBERBAND));
static_cast<double>(EngineBuffer::KeylockEngine::RubberBandFaster));
ControlObject::set(ConfigKey(m_sGroup1, "pitch"), -1);
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
ProcessBuffer();
Expand Down