Skip to content

Commit

Permalink
Merge pull request #3944 from ywwg/synclock-05-explicitload
Browse files Browse the repository at this point in the history
 Sync Lock: Differentiate track loading from beats updating
  • Loading branch information
daschuer committed Oct 7, 2021
2 parents 64a9049 + 5d6dbc2 commit bc3d041
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 221 deletions.
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 {
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

0 comments on commit bc3d041

Please sign in to comment.