diff --git a/src/engine/sync/enginesync.cpp b/src/engine/sync/enginesync.cpp index 4ec67255c063..d6d04fa47e6c 100644 --- a/src/engine/sync/enginesync.cpp +++ b/src/engine/sync/enginesync.cpp @@ -397,3 +397,19 @@ EngineChannel* EngineSync::pickNonSyncSyncTarget(EngineChannel* pDontPick) const // had a BPM. return pFirstNonplayingDeck; } + +bool EngineSync::otherSyncedPlaying(const QString& group) { + bool othersInSync = false; + for (Syncable* theSyncable : m_syncables) { + if (theSyncable->getGroup() == group) { + if (theSyncable->getSyncMode() == SYNC_NONE) { + return false; + } + continue; + } + if (theSyncable->isPlaying() && (theSyncable->getSyncMode() != SYNC_NONE)) { + othersInSync = true; + } + } + return othersInSync; +} diff --git a/src/engine/sync/enginesync.h b/src/engine/sync/enginesync.h index 289ff557b426..dc7d9aab541e 100644 --- a/src/engine/sync/enginesync.h +++ b/src/engine/sync/enginesync.h @@ -50,6 +50,10 @@ class EngineSync : public BaseSyncableListener { // Used to pick a sync target for non-master-sync mode. EngineChannel* pickNonSyncSyncTarget(EngineChannel* pDontPick) const; + // Used to test whether changing the rate of a Syncable would change the rate + // of other Syncables that are playing + bool otherSyncedPlaying(const QString& group); + private: // Activate a specific syncable as master. void activateMaster(Syncable* pSyncable); diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index 0fc7bd51d367..a043d251fcb1 100644 --- a/src/mixer/basetrackplayer.cpp +++ b/src/mixer/basetrackplayer.cpp @@ -17,6 +17,7 @@ #include "util/sandbox.h" #include "effects/effectsmanager.h" #include "vinylcontrol/defs_vinylcontrol.h" +#include "engine/sync/enginesync.h" BaseTrackPlayer::BaseTrackPlayer(QObject* pParent, const QString& group) : BasePlayer(pParent, group) { @@ -32,6 +33,7 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(QObject* pParent, bool defaultHeadphones) : BaseTrackPlayer(pParent, group), m_pConfig(pConfig), + m_pEngineMaster(pMixingEngine), m_pLoadedTrack(), m_pLowFilter(NULL), m_pMidFilter(NULL), @@ -289,24 +291,19 @@ void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack, int reset = m_pConfig->getValueString(ConfigKey( "[Controls]", "SpeedAutoReset"), QString("%1").arg(RESET_PITCH)).toInt(); - switch (reset) { - case RESET_PITCH_AND_SPEED: - // Note: speed may affect pitch - if (m_pRateSlider != NULL) { - m_pRateSlider->set(0.0); + if (reset == RESET_SPEED || reset == RESET_PITCH_AND_SPEED) { + // Avoid reseting speed if master sync is enabled and other decks with sync enabled + // are playing, as this would change the speed of already playing decks. + if (! m_pEngineMaster->getEngineSync()->otherSyncedPlaying(getGroup())) { + if (m_pRateSlider != NULL) { + m_pRateSlider->set(0.0); + } } - M_FALLTHROUGH_INTENDED; - case RESET_PITCH: + } + if (reset == RESET_PITCH || reset == RESET_PITCH_AND_SPEED) { if (m_pPitchAdjust != NULL) { m_pPitchAdjust->set(0.0); } - break; - case RESET_SPEED: - // Note: speed may affect pitch - if (m_pRateSlider != NULL) { - m_pRateSlider->set(0.0); - } - break; } emit(newTrackLoaded(m_pLoadedTrack)); } else { diff --git a/src/mixer/basetrackplayer.h b/src/mixer/basetrackplayer.h index 5c72cd506358..2a9855be119c 100644 --- a/src/mixer/basetrackplayer.h +++ b/src/mixer/basetrackplayer.h @@ -81,6 +81,7 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer { void setReplayGain(double value); UserSettingsPointer m_pConfig; + EngineMaster* m_pEngineMaster; TrackPointer m_pLoadedTrack; // Waveform display related controls diff --git a/src/test/enginesynctest.cpp b/src/test/enginesynctest.cpp index f304a0891a10..06c638fc94a4 100644 --- a/src/test/enginesynctest.cpp +++ b/src/test/enginesynctest.cpp @@ -17,6 +17,7 @@ #include "test/mockedenginebackendtest.h" #include "test/mixxxtest.h" #include "track/beatfactory.h" +#include "mixer/basetrackplayer.h" class EngineSyncTest : public MockedEngineBackendTest { @@ -792,6 +793,121 @@ TEST_F(EngineSyncTest, LoadTrackInitializesMaster) { ControlObject::getControl(ConfigKey(m_sGroup2, "bpm"))->get()); } +TEST_F(EngineSyncTest, LoadTrackResetTempoOption) { + // Make sure playing decks with master sync enabled do not change tempo when + // the "Reset Speed/Tempo" preference is set and a track is loaded to another + // deck with master sync enabled. + m_pConfig->set(ConfigKey("[Controls]", "SpeedAutoReset"), + ConfigValue(BaseTrackPlayer::RESET_SPEED)); + + // Enable sync on two stopped decks + m_pMixerDeck1->setupEqControls(); + QScopedPointer pButtonSyncEnabled1(getControlProxy( + ConfigKey(m_sGroup1, "sync_enabled"))); + pButtonSyncEnabled1->slotSet(1.0); + + m_pMixerDeck2->setupEqControls(); + QScopedPointer pButtonSyncEnabled2(getControlProxy( + ConfigKey(m_sGroup2, "sync_enabled"))); + pButtonSyncEnabled2->slotSet(1.0); + + // If sync is on and we load a track, that should initialize master. + TrackPointer track1 = m_pChannel1->getEngineBuffer()->loadFakeTrack(140.0); + m_pMixerDeck1->slotLoadTrack(track1, true); + m_pMixerDeck1->slotTrackLoaded(track1, m_pTrack1); + + EXPECT_FLOAT_EQ(140.0, + ControlObject::getControl(ConfigKey(m_sInternalClockGroup, "bpm"))->get()); + EXPECT_FLOAT_EQ(140.0, + ControlObject::getControl(ConfigKey(m_sGroup1, "bpm"))->get()); + + // If sync is on two decks and we load a track while one is playing, + // that should not change the playing deck. + ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(1.0); + + TrackPointer track2 = m_pChannel2->getEngineBuffer()->loadFakeTrack(128.0); + m_pMixerDeck2->slotLoadTrack(track2, false); + m_pMixerDeck2->slotTrackLoaded(track2, m_pTrack2); + + EXPECT_FLOAT_EQ(140.0, + ControlObject::getControl(ConfigKey(m_sInternalClockGroup, "bpm"))->get()); + EXPECT_FLOAT_EQ(140.0, + ControlObject::getControl(ConfigKey(m_sGroup1, "bpm"))->get()); + EXPECT_FLOAT_EQ(140.0, + ControlObject::getControl(ConfigKey(m_sGroup2, "bpm"))->get()); + + // Repeat with RESET_PITCH_AND_SPEED + m_pConfig->set(ConfigKey("[Controls]", "SpeedAutoReset"), + ConfigValue(BaseTrackPlayer::RESET_PITCH_AND_SPEED)); + ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(0.0); + ControlObject::getControl(ConfigKey(m_sGroup1, "rate"))->set(getRateSliderValue(1.0)); + m_pMixerDeck1->slotLoadTrack(track1, true); + m_pMixerDeck1->slotTrackLoaded(track1, m_pTrack1); + ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(1.0); + m_pMixerDeck2->slotLoadTrack(track2, false); + m_pMixerDeck2->slotTrackLoaded(track2, m_pTrack2); + EXPECT_FLOAT_EQ(140.0, + ControlObject::getControl(ConfigKey(m_sInternalClockGroup, "bpm"))->get()); + EXPECT_FLOAT_EQ(140.0, + ControlObject::getControl(ConfigKey(m_sGroup1, "bpm"))->get()); + EXPECT_FLOAT_EQ(140.0, + ControlObject::getControl(ConfigKey(m_sGroup2, "bpm"))->get()); + + // Repeat with RESET_NONE + m_pConfig->set(ConfigKey("[Controls]", "SpeedAutoReset"), + ConfigValue(BaseTrackPlayer::RESET_NONE)); + ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(0.0); + ControlObject::getControl(ConfigKey(m_sGroup1, "rate"))->set(getRateSliderValue(1.0)); + ControlObject::getControl(ConfigKey(m_sGroup2, "rate"))->set(getRateSliderValue(1.0)); + m_pMixerDeck1->slotLoadTrack(track1, true); + m_pMixerDeck1->slotTrackLoaded(track1, m_pTrack1); + ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(1.0); + m_pMixerDeck2->slotLoadTrack(track2, false); + m_pMixerDeck2->slotTrackLoaded(track2, m_pTrack2); + EXPECT_FLOAT_EQ(128.0, + ControlObject::getControl(ConfigKey(m_sInternalClockGroup, "bpm"))->get()); + EXPECT_FLOAT_EQ(128.0, + ControlObject::getControl(ConfigKey(m_sGroup1, "bpm"))->get()); + EXPECT_FLOAT_EQ(128.0, + ControlObject::getControl(ConfigKey(m_sGroup2, "bpm"))->get()); + + // Load two tracks with sync off and RESET_SPEED + m_pConfig->set(ConfigKey("[Controls]", "SpeedAutoReset"), + ConfigValue(BaseTrackPlayer::RESET_SPEED)); + ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(0.0); + ControlObject::getControl(ConfigKey(m_sGroup1, "rate"))->set(getRateSliderValue(1.5)); + ControlObject::getControl(ConfigKey(m_sGroup2, "rate"))->set(getRateSliderValue(1.5)); + pButtonSyncEnabled1->slotSet(0.0); + pButtonSyncEnabled2->slotSet(0.0); + m_pMixerDeck1->slotLoadTrack(track1, true); + m_pMixerDeck1->slotTrackLoaded(track1, m_pTrack1); + ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(1.0); + m_pMixerDeck2->slotLoadTrack(track2, false); + m_pMixerDeck2->slotTrackLoaded(track2, m_pTrack2); + EXPECT_FLOAT_EQ(140.0, + ControlObject::getControl(ConfigKey(m_sGroup1, "bpm"))->get()); + EXPECT_FLOAT_EQ(128.0, + ControlObject::getControl(ConfigKey(m_sGroup2, "bpm"))->get()); + + // Load two tracks with sync off and RESET_PITCH_AND_SPEED + m_pConfig->set(ConfigKey("[Controls]", "SpeedAutoReset"), + ConfigValue(BaseTrackPlayer::RESET_PITCH_AND_SPEED)); + ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(0.0); + ControlObject::getControl(ConfigKey(m_sGroup1, "rate"))->set(getRateSliderValue(1.5)); + ControlObject::getControl(ConfigKey(m_sGroup2, "rate"))->set(getRateSliderValue(1.5)); + pButtonSyncEnabled1->slotSet(0.0); + pButtonSyncEnabled2->slotSet(0.0); + m_pMixerDeck1->slotLoadTrack(track1, true); + m_pMixerDeck1->slotTrackLoaded(track1, m_pTrack1); + ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(1.0); + m_pMixerDeck2->slotLoadTrack(track2, false); + m_pMixerDeck2->slotTrackLoaded(track2, m_pTrack2); + EXPECT_FLOAT_EQ(140.0, + ControlObject::getControl(ConfigKey(m_sGroup1, "bpm"))->get()); + EXPECT_FLOAT_EQ(128.0, + ControlObject::getControl(ConfigKey(m_sGroup2, "bpm"))->get()); +} + TEST_F(EngineSyncTest, EnableOneDeckSliderUpdates) { // If we enable a deck to be master, the internal slider should immediately update. QScopedPointer pButtonSyncEnabled1(getControlProxy( diff --git a/src/test/mockedenginebackendtest.h b/src/test/mockedenginebackendtest.h index a19711fd2623..3a4981b133d0 100644 --- a/src/test/mockedenginebackendtest.h +++ b/src/test/mockedenginebackendtest.h @@ -17,6 +17,7 @@ #include "engine/enginemaster.h" #include "engine/ratecontrol.h" #include "engine/sync/enginesync.h" +#include "mixer/deck.h" #include "mixer/previewdeck.h" #include "mixer/sampler.h" #include "test/mixxxtest.h" @@ -67,18 +68,16 @@ class MockedEngineBackendTest : public MixxxTest { m_pEngineMaster = new EngineMaster(m_pConfig, "[Master]", m_pEffectsManager, false, false); - m_pChannel1 = new EngineDeck( - m_pEngineMaster->registerChannelGroup(m_sGroup1), - m_pConfig, m_pEngineMaster, m_pEffectsManager, - EngineChannel::CENTER); - m_pChannel2 = new EngineDeck( - m_pEngineMaster->registerChannelGroup(m_sGroup2), - m_pConfig, m_pEngineMaster, m_pEffectsManager, - EngineChannel::CENTER); - m_pChannel3 = new EngineDeck( - m_pEngineMaster->registerChannelGroup(m_sGroup3), - m_pConfig, m_pEngineMaster, m_pEffectsManager, - EngineChannel::CENTER); + m_pMixerDeck1 = new Deck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager, + EngineChannel::CENTER, m_sGroup1); + m_pMixerDeck2 = new Deck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager, + EngineChannel::CENTER, m_sGroup2); + m_pMixerDeck3 = new Deck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager, + EngineChannel::CENTER, m_sGroup3); + m_pChannel1 = m_pMixerDeck1->getEngineDeck(); + m_pChannel2 = m_pMixerDeck2->getEngineDeck(); + m_pChannel3 = m_pMixerDeck3->getEngineDeck(); + m_pPreview1 = new PreviewDeck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager, EngineChannel::CENTER, m_sPreviewGroup); @@ -114,7 +113,6 @@ class MockedEngineBackendTest : public MixxxTest { } void addDeck(EngineDeck* pDeck) { - m_pEngineMaster->addChannel(pDeck); ControlObject::getControl(ConfigKey(pDeck->getGroup(), "master")) ->set(1.0); ControlObject::getControl(ConfigKey(pDeck->getGroup(), "rate_dir")) @@ -125,6 +123,9 @@ class MockedEngineBackendTest : public MixxxTest { } virtual void TearDown() { + delete m_pMixerDeck1; + delete m_pMixerDeck2; + delete m_pMixerDeck3; m_pChannel1 = NULL; m_pChannel2 = NULL; m_pChannel3 = NULL; @@ -155,6 +156,7 @@ class MockedEngineBackendTest : public MixxxTest { EffectsManager* m_pEffectsManager; EngineSync* m_pEngineSync; EngineMaster* m_pEngineMaster; + Deck *m_pMixerDeck1, *m_pMixerDeck2, *m_pMixerDeck3; EngineDeck *m_pChannel1, *m_pChannel2, *m_pChannel3; MockScaler *m_pMockScaleVinyl1, *m_pMockScaleVinyl2, *m_pMockScaleVinyl3; MockScaler *m_pMockScaleKeylock1, *m_pMockScaleKeylock2, *m_pMockScaleKeylock3;