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

Sync Lock: Differentiate track loading from beats updating #3944

Merged
merged 20 commits into from
Oct 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 11 additions & 131 deletions src/engine/controls/bpmcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,22 +111,6 @@ BpmControl::BpmControl(const QString& group,
this, &BpmControl::slotBpmTap,
Qt::DirectConnection);

// Beat sync (scale buffer tempo relative to tempo of other buffer)
m_pButtonSync = new ControlPushButton(ConfigKey(group, "beatsync"));
connect(m_pButtonSync, &ControlObject::valueChanged,
this, &BpmControl::slotControlBeatSync,
Qt::DirectConnection);

m_pButtonSyncPhase = new ControlPushButton(ConfigKey(group, "beatsync_phase"));
connect(m_pButtonSyncPhase, &ControlObject::valueChanged,
this, &BpmControl::slotControlBeatSyncPhase,
Qt::DirectConnection);

m_pButtonSyncTempo = new ControlPushButton(ConfigKey(group, "beatsync_tempo"));
connect(m_pButtonSyncTempo, &ControlObject::valueChanged,
this, &BpmControl::slotControlBeatSyncTempo,
Qt::DirectConnection);

m_pTranslateBeats = new ControlPushButton(ConfigKey(group, "beats_translate_curpos"));
connect(m_pTranslateBeats, &ControlObject::valueChanged,
this, &BpmControl::slotBeatsTranslate,
Expand All @@ -144,15 +128,13 @@ BpmControl::BpmControl(const QString& group,
// Measures distance from last beat in percentage: 0.5 = half-beat away.
m_pThisBeatDistance = new ControlProxy(group, "beat_distance", this);
m_pSyncMode = new ControlProxy(group, "sync_mode", this);
m_pSyncEnabled = new ControlProxy(group, "sync_enabled", this);
}

BpmControl::~BpmControl() {
delete m_pLocalBpm;
delete m_pEngineBpm;
delete m_pButtonTap;
delete m_pButtonSync;
delete m_pButtonSyncPhase;
delete m_pButtonSyncTempo;
delete m_pTranslateBeats;
delete m_pBeatsTranslateMatchAlignment;
delete m_pTranslateBeatsEarlier;
Expand Down Expand Up @@ -266,115 +248,6 @@ void BpmControl::slotTapFilter(double averageLength, int numSamples) {
pTrack->trySetBeats(pBeats->setBpm(averageBpm));
}

void BpmControl::slotControlBeatSyncPhase(double value) {
if (value == 0) {
return;
}

if (isSynchronized()) {
m_dUserOffset.setValue(0.0);
}
getEngineBuffer()->requestSyncPhase();
}

void BpmControl::slotControlBeatSyncTempo(double value) {
if (value == 0) {
return;
}

syncTempo();
}

void BpmControl::slotControlBeatSync(double value) {
if (value == 0) {
return;
}

if (!syncTempo()) {
// syncTempo failed, nothing else to do
return;
}

// Also sync phase if quantize is enabled.
// this is used from controller scripts, where the latching behaviour of
// the sync_enable CO cannot be used
if (m_pPlayButton->toBool() && m_pQuantize->toBool()) {
slotControlBeatSyncPhase(value);
}
}

bool BpmControl::syncTempo() {
if (getSyncMode() == SyncMode::LeaderExplicit) {
return false;
}
EngineBuffer* pOtherEngineBuffer = pickSyncTarget();

if (!pOtherEngineBuffer) {
return false;
}

const auto thisBpm = getBpm();
const auto thisLocalBpm = getLocalBpm();

const auto otherBpm = pOtherEngineBuffer->getBpm();
const auto otherLocalBpm = pOtherEngineBuffer->getLocalBpm();

//qDebug() << "this" << "bpm" << thisBpm << "filebpm" << thisLocalBpm;
//qDebug() << "other" << "bpm" << otherBpm << "filebpm" << otherLocalBpm;

////////////////////////////////////////////////////////////////////////////
// Rough proof of how syncing works -- rryan 3/2011
// ------------------------------------------------
//
// Let this and other denote this deck versus the sync-target deck.
//
// The goal is for this deck's effective BPM to equal the other decks.
//
// thisBpm = otherBpm
///
// An effective BPM is the file-bpm times the rate:
//
// bpm = fileBpm * rate
//
// So our goal is to tweak thisRate such that this equation is true:
//
// thisFileBpm * (1.0 + thisRate) = otherFileBpm * (1.0 + otherRate)
//
// so rearrange this equation in terms of thisRate:
//
// thisRate = (otherFileBpm * (1.0 + otherRate)) / thisFileBpm - 1.0
//
// So the new rateScale to set is:
//
// thisRateScale = ((otherFileBpm * (1.0 + otherRate)) / thisFileBpm - 1.0) / (thisRateDir * thisRateRange)

if (otherBpm.isValid() && thisBpm.isValid() && thisLocalBpm.isValid()) {
// The desired rate is the other decks effective rate divided by this
// deck's file BPM. This gives us the playback rate that will produce an
// effective BPM equivalent to the other decks.
double desiredRate = otherBpm / thisLocalBpm;

// Test if this buffer's bpm is the double of the other one, and adjust
// the rate scale. I believe this is intended to account for our BPM
// algorithm sometimes finding double or half BPMs. This avoids drastic
// scales.

const double fileBpmDelta = fabs(thisLocalBpm - otherLocalBpm);
if (fabs(thisLocalBpm * 2.0 - otherLocalBpm) < fileBpmDelta) {
desiredRate /= 2.0;
} else if (fabs(thisLocalBpm - otherLocalBpm * 2.0) < fileBpmDelta) {
desiredRate *= 2.0;
}

if (desiredRate < 2.0 && desiredRate > 0.5) {
m_pEngineBpm->set(m_pLocalBpm->get() * desiredRate);
m_pRateRatio->set(desiredRate);
return true;
}
}
return false;
}

// static
double BpmControl::shortestPercentageChange(const double& current_percentage,
const double& target_percentage) {
Expand Down Expand Up @@ -1031,14 +904,21 @@ mixxx::audio::FrameDiff_t BpmControl::getPhaseOffset(mixxx::audio::FramePos this
}

void BpmControl::slotUpdateEngineBpm(double value) {
Q_UNUSED(value);
// Adjust playback bpm in response to a rate_ration update
// Adjust playback bpm in response to a rate_ratio update
double dRate = m_pRateRatio->get();

if (kLogger.traceEnabled()) {
kLogger.trace() << getGroup() << "BpmControl::slotUpdateEngineBpm"
<< value << m_pLocalBpm->get() << dRate;
}
m_pEngineBpm->set(m_pLocalBpm->get() * dRate);
}

void BpmControl::slotUpdateRateSlider(double value) {
Q_UNUSED(value);
if (kLogger.traceEnabled()) {
kLogger.trace() << getGroup() << "BpmControl::slotUpdateRateSlider"
<< value;
}
// Adjust rate slider position response to a change in rate range or m_pEngineBpm

double localBpm = m_pLocalBpm->get();
Expand Down
10 changes: 1 addition & 9 deletions src/engine/controls/bpmcontrol.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,6 @@ class BpmControl : public EngineControl {
void slotAdjustBeatsSlower(double);
void slotTranslateBeatsEarlier(double);
void slotTranslateBeatsLater(double);
void slotControlBeatSync(double);
void slotControlBeatSyncPhase(double);
void slotControlBeatSyncTempo(double);
void slotTapFilter(double,int);
void slotBpmTap(double);
void slotUpdateRateSlider(double v = 0.0);
Expand All @@ -115,7 +112,6 @@ class BpmControl : public EngineControl {
inline bool isSynchronized() const {
return toSynchronized(getSyncMode());
}
bool syncTempo();
double calcSyncAdjustment(bool userTweakingSync);
void adjustBeatsBpm(double deltaBpm);

Expand Down Expand Up @@ -150,11 +146,6 @@ class BpmControl : public EngineControl {
// Used for bpm tapping from GUI and MIDI
ControlPushButton* m_pButtonTap;

// Button for sync'ing with the other EngineBuffer
ControlPushButton* m_pButtonSync;
ControlPushButton* m_pButtonSyncPhase;
ControlPushButton* m_pButtonSyncTempo;

// Button that translates the beats so the nearest beat is on the current
// playposition.
ControlPushButton* m_pTranslateBeats;
Expand All @@ -169,6 +160,7 @@ class BpmControl : public EngineControl {
ControlValueAtomic<double> m_dUserOffset;
QAtomicInt m_resetSyncAdjustment;
ControlProxy* m_pSyncMode;
ControlProxy* m_pSyncEnabled;

TapFilter m_tapFilter; // threadsafe

Expand Down
12 changes: 6 additions & 6 deletions src/engine/enginemaster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ EngineMaster::EngineMaster(
m_pAudioLatencyOverload = new ControlPotmeter(ConfigKey(group, "audio_latency_overload"), 0.0, 1.0);

// Master sync controller
m_pMasterSync = new EngineSync(pConfig);
m_pEngineSync = new EngineSync(pConfig);

// The last-used bpm value is saved in the destructor of EngineSync.
double default_bpm = pConfig->getValue(
Expand Down Expand Up @@ -217,7 +217,7 @@ EngineMaster::~EngineMaster() {
delete m_pXFaderCurve;
delete m_pXFaderMode;

delete m_pMasterSync;
delete m_pEngineSync;
delete m_pMasterSampleRate;
delete m_pMasterLatency;
delete m_pMasterAudioBufferSize;
Expand Down Expand Up @@ -271,7 +271,7 @@ const CSAMPLE* EngineMaster::getSidechainBuffer() const {

void EngineMaster::processChannels(int iBufferSize) {
// Update internal sync lock rate.
m_pMasterSync->onCallbackStart(m_sampleRate, m_iBufferSize);
m_pEngineSync->onCallbackStart(m_sampleRate, m_iBufferSize);

m_activeBusChannels[EngineChannel::LEFT].clear();
m_activeBusChannels[EngineChannel::CENTER].clear();
Expand All @@ -281,7 +281,7 @@ void EngineMaster::processChannels(int iBufferSize) {
m_activeChannels.clear();

//ScopedTimer timer("EngineMaster::processChannels");
EngineChannel* pMasterChannel = m_pMasterSync->getLeader();
EngineChannel* pLeaderChannel = m_pEngineSync->getLeaderChannel();
// Reserve the first place for the master channel which
// should be processed first
m_activeChannels.append(NULL);
Expand Down Expand Up @@ -343,7 +343,7 @@ void EngineMaster::processChannels(int iBufferSize) {
}

// If necessary, add the channel to the list of buffers to process.
if (pChannel == pMasterChannel) {
if (pChannel == pLeaderChannel) {
// If this is the sync master, it should be processed first.
m_activeChannels.replace(0, pChannelInfo);
activeChannelsStartIndex = 0;
Expand Down Expand Up @@ -372,7 +372,7 @@ void EngineMaster::processChannels(int iBufferSize) {
// Note, because we call this on the internal clock first,
// it will have an up-to-date beatDistance, whereas the other
// Syncables will not.
m_pMasterSync->onCallbackEnd(m_sampleRate, m_iBufferSize);
m_pEngineSync->onCallbackEnd(m_sampleRate, m_iBufferSize);

// After all the engines have been processed, trigger post-processing
// which ensures that all channels are updating certain values at the
Expand Down
4 changes: 2 additions & 2 deletions src/engine/enginemaster.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class EngineMaster : public QObject, public AudioSource {

// Provide access to the sync lock so enginebuffers can know what their rate controller is.
EngineSync* getEngineSync() const{
return m_pMasterSync;
return m_pEngineSync;
}

// These are really only exposed for tests to use.
Expand Down Expand Up @@ -296,7 +296,7 @@ class EngineMaster : public QObject, public AudioSource {
CSAMPLE* m_pSidechainMix;

EngineWorkerScheduler* m_pWorkerScheduler;
EngineSync* m_pMasterSync;
EngineSync* m_pEngineSync;

ControlObject* m_pMasterGain;
ControlObject* m_pBoothGain;
Expand Down
13 changes: 6 additions & 7 deletions src/engine/sync/enginesync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,8 @@ void EngineSync::requestSyncMode(Syncable* pSyncable, SyncMode mode) {
// Second, figure out what Syncable should be used to initialize the leader
// parameters, if any. Usually this is the new leader. (Note, that pointer might be null!)
Syncable* pParamsSyncable = m_pLeaderSyncable;
// But if we are newly soft leader, we need to match to some other deck.
if (pSyncable == m_pLeaderSyncable && pSyncable != oldLeader &&
mode != SyncMode::LeaderExplicit) {
// But if we are newly leader, we need to match to some other deck.
if (pSyncable == m_pLeaderSyncable && pSyncable != oldLeader) {
pParamsSyncable = findBpmMatchTarget(pSyncable);
if (!pParamsSyncable) {
// We weren't able to find anything to match to, so set ourselves as the
Expand All @@ -111,7 +110,6 @@ void EngineSync::requestSyncMode(Syncable* pSyncable, SyncMode mode) {
pSyncable->requestSync();
}
}

}

void EngineSync::activateFollower(Syncable* pSyncable) {
Expand Down Expand Up @@ -213,7 +211,7 @@ Syncable* EngineSync::pickLeader(Syncable* enabling_syncable) {
return m_pLeaderSyncable;
}

// First preference: some other sync deck that is not playing.
// First preference: some other sync deck that is playing.
// Note, if we are using PREFER_LOCK_BPM we don't use this option.
Syncable* first_other_playing_deck = nullptr;
// Second preference: whatever the first playing sync deck is, even if it's us.
Expand Down Expand Up @@ -430,7 +428,8 @@ void EngineSync::requestBpmUpdate(Syncable* pSyncable, mixxx::Bpm bpm) {

void EngineSync::notifyInstantaneousBpmChanged(Syncable* pSyncable, mixxx::Bpm bpm) {
if (kLogger.traceEnabled()) {
kLogger.trace() << "EngineSync::notifyInstantaneousBpmChanged" << pSyncable->getGroup() << bpm;
kLogger.trace() << "EngineSync::notifyInstantaneousBpmChanged"
<< pSyncable->getGroup() << bpm;
}
if (pSyncable != m_pInternalClock) {
return;
Expand Down Expand Up @@ -534,7 +533,7 @@ void EngineSync::onCallbackEnd(mixxx::audio::SampleRate sampleRate, int bufferSi
m_pInternalClock->onCallbackEnd(sampleRate, bufferSize);
}

EngineChannel* EngineSync::getLeader() const {
EngineChannel* EngineSync::getLeaderChannel() const {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks unrelated, but but probably makes sense.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it helped clarify -- "getLeader" would seemingly return a Syncable*

return m_pLeaderSyncable ? m_pLeaderSyncable->getChannel() : nullptr;
}

Expand Down
2 changes: 1 addition & 1 deletion src/engine/sync/enginesync.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class EngineSync : public SyncableListener {
bool otherSyncedPlaying(const QString& group);

void addSyncableDeck(Syncable* pSyncable);
EngineChannel* getLeader() const;
EngineChannel* getLeaderChannel() const;
void onCallbackStart(mixxx::audio::SampleRate sampleRate, int bufferSize);
void onCallbackEnd(mixxx::audio::SampleRate sampleRate, int bufferSize);

Expand Down
Loading