diff --git a/src/engine/loopingcontrol.cpp b/src/engine/loopingcontrol.cpp index d8a92fc536e0..d5eedaebd1e5 100644 --- a/src/engine/loopingcontrol.cpp +++ b/src/engine/loopingcontrol.cpp @@ -683,6 +683,7 @@ void LoopingControl::slotReloopToggle(double val) { if (m_bLoopRollActive) { m_pSlipEnabled->set(0); m_bLoopRollActive = false; + m_activeLoopRolls.clear(); } setLoopingEnabled(false); //qDebug() << "reloop_toggle looping off"; @@ -846,10 +847,11 @@ void LoopingControl::slotBeatLoopActivateRoll(BeatLoopingControl* pBeatLoopContr return; } - // Disregard existing loops. + // Disregard existing loops (except beatlooprolls). m_pSlipEnabled->set(1); - slotBeatLoop(pBeatLoopControl->getSize(), false, true); + slotBeatLoop(pBeatLoopControl->getSize(), m_bLoopRollActive, true); m_bLoopRollActive = true; + m_activeLoopRolls.push(pBeatLoopControl->getSize()); } void LoopingControl::slotBeatLoopDeactivate(BeatLoopingControl* pBeatLoopControl) { @@ -858,14 +860,29 @@ void LoopingControl::slotBeatLoopDeactivate(BeatLoopingControl* pBeatLoopControl } void LoopingControl::slotBeatLoopDeactivateRoll(BeatLoopingControl* pBeatLoopControl) { - Q_UNUSED(pBeatLoopControl); - setLoopingEnabled(false); + pBeatLoopControl->deactivate(); + const double size = pBeatLoopControl->getSize(); + auto i = m_activeLoopRolls.begin(); + while (i != m_activeLoopRolls.end()) { + if (size == *i) { + i = m_activeLoopRolls.erase(i); + } else { + ++i; + } + } + // Make sure slip mode is not turned off if it was turned on // by something that was not a rolling beatloop. - if (m_bLoopRollActive) { + if (m_bLoopRollActive && m_activeLoopRolls.empty()) { + setLoopingEnabled(false); m_pSlipEnabled->set(0); m_bLoopRollActive = false; } + + // Return to the previous beatlooproll if necessary. + if (!m_activeLoopRolls.empty()) { + slotBeatLoop(m_activeLoopRolls.top(), m_bLoopRollActive, true); + } } void LoopingControl::clearActiveBeatLoop() { @@ -1055,6 +1072,7 @@ void LoopingControl::slotBeatLoopRollActivate(double pressed) { if (m_bLoopRollActive) { m_pSlipEnabled->set(0.0); m_bLoopRollActive = false; + m_activeLoopRolls.clear(); } } else { m_pSlipEnabled->set(1.0); @@ -1068,6 +1086,7 @@ void LoopingControl::slotBeatLoopRollActivate(double pressed) { if (m_bLoopRollActive) { m_pSlipEnabled->set(0.0); m_bLoopRollActive = false; + m_activeLoopRolls.clear(); } } } diff --git a/src/engine/loopingcontrol.h b/src/engine/loopingcontrol.h index 01d3644b1216..2b7da2296869 100644 --- a/src/engine/loopingcontrol.h +++ b/src/engine/loopingcontrol.h @@ -6,6 +6,7 @@ #define LOOPINGCONTROL_H #include +#include #include "preferences/usersettings.h" #include "engine/enginecontrol.h" @@ -131,6 +132,7 @@ class LoopingControl : public EngineControl { bool m_bAdjustingLoopInOld; bool m_bAdjustingLoopOutOld; bool m_bLoopOutPressedWhileLoopDisabled; + QStack m_activeLoopRolls; ControlValueAtomic m_loopSamples; LoopSamples m_oldLoopSamples; ControlValueAtomic m_currentSample; diff --git a/src/test/looping_control_test.cpp b/src/test/looping_control_test.cpp index b2f50c840569..12c3595b533d 100644 --- a/src/test/looping_control_test.cpp +++ b/src/test/looping_control_test.cpp @@ -50,6 +50,7 @@ class LoopingControlTest : public MockedEngineBackendTest { m_pButtonBeatMoveBackward = std::make_unique(m_sGroup1, "loop_move_1_backward"); m_pButtonBeatLoop2Activate = std::make_unique(m_sGroup1, "beatloop_2_activate"); m_pButtonBeatLoop4Activate = std::make_unique(m_sGroup1, "beatloop_4_activate"); + m_pBeatLoop1Enabled = std::make_unique(m_sGroup1, "beatloop_1_enabled"); m_pBeatLoop2Enabled = std::make_unique(m_sGroup1, "beatloop_2_enabled"); m_pBeatLoop4Enabled = std::make_unique(m_sGroup1, "beatloop_4_enabled"); m_pBeatLoop64Enabled = std::make_unique(m_sGroup1, "beatloop_64_enabled"); @@ -59,6 +60,9 @@ class LoopingControlTest : public MockedEngineBackendTest { m_pBeatJumpSize = std::make_unique(m_sGroup1, "beatjump_size"); m_pButtonBeatJumpForward = std::make_unique(m_sGroup1, "beatjump_forward"); m_pButtonBeatJumpBackward = std::make_unique(m_sGroup1, "beatjump_backward"); + m_pButtonBeatLoopRoll1Activate = std::make_unique(m_sGroup1, "beatlooproll_1_activate"); + m_pButtonBeatLoopRoll2Activate = std::make_unique(m_sGroup1, "beatlooproll_2_activate"); + m_pButtonBeatLoopRoll4Activate = std::make_unique(m_sGroup1, "beatlooproll_4_activate"); } bool isLoopEnabled() { @@ -92,6 +96,7 @@ class LoopingControlTest : public MockedEngineBackendTest { std::unique_ptr m_pButtonBeatMoveBackward; std::unique_ptr m_pButtonBeatLoop2Activate; std::unique_ptr m_pButtonBeatLoop4Activate; + std::unique_ptr m_pBeatLoop1Enabled; std::unique_ptr m_pBeatLoop2Enabled; std::unique_ptr m_pBeatLoop4Enabled; std::unique_ptr m_pBeatLoop64Enabled; @@ -101,6 +106,9 @@ class LoopingControlTest : public MockedEngineBackendTest { std::unique_ptr m_pBeatJumpSize; std::unique_ptr m_pButtonBeatJumpForward; std::unique_ptr m_pButtonBeatJumpBackward; + std::unique_ptr m_pButtonBeatLoopRoll1Activate; + std::unique_ptr m_pButtonBeatLoopRoll2Activate; + std::unique_ptr m_pButtonBeatLoopRoll4Activate; }; TEST_F(LoopingControlTest, LoopSet) { @@ -874,3 +882,109 @@ TEST_F(LoopingControlTest, LoopEscape) { seekToSampleAndProcess(50); EXPECT_FALSE(isLoopEnabled()); } + +TEST_F(LoopingControlTest, BeatLoopRoll_Activation) { + m_pTrack1->setBpm(120.0); + + m_pButtonBeatLoopRoll2Activate->set(1.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop2Enabled->toBool()); + + m_pButtonBeatLoopRoll2Activate->set(0.0); + EXPECT_FALSE(m_pLoopEnabled->toBool()); + EXPECT_FALSE(m_pBeatLoop2Enabled->toBool()); +} + +TEST_F(LoopingControlTest, BeatLoopRoll_Overlap) { + m_pTrack1->setBpm(120.0); + + m_pButtonBeatLoopRoll2Activate->set(1.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop2Enabled->toBool()); + + m_pButtonBeatLoopRoll4Activate->set(1.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop4Enabled->toBool()); + EXPECT_FALSE(m_pBeatLoop2Enabled->toBool()); + + m_pButtonBeatLoopRoll2Activate->set(0.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop4Enabled->toBool()); + EXPECT_FALSE(m_pBeatLoop2Enabled->toBool()); + + m_pButtonBeatLoopRoll4Activate->set(0.0); + EXPECT_FALSE(m_pLoopEnabled->toBool()); + EXPECT_FALSE(m_pBeatLoop4Enabled->toBool()); +} + +TEST_F(LoopingControlTest, BeatLoopRoll_OverlapStackUnwind) { + m_pTrack1->setBpm(120.0); + + // start a 2 beat loop roll + m_pButtonBeatLoopRoll2Activate->set(1.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop2Enabled->toBool()); + + // start a 4 beat loop roll on top of the previous loop + m_pButtonBeatLoopRoll4Activate->set(1.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop4Enabled->toBool()); + EXPECT_FALSE(m_pBeatLoop2Enabled->toBool()); + + // start a 1 beat loop roll on top of the previous loop + m_pButtonBeatLoopRoll1Activate->set(1.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop1Enabled->toBool()); + EXPECT_FALSE(m_pBeatLoop4Enabled->toBool()); + + // stop the 4 beat loop roll, the 1 beat roll should continue + m_pButtonBeatLoopRoll4Activate->set(0.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop1Enabled->toBool()); + + // stop the 1 beat loop roll, the 2 beat roll should continue + m_pButtonBeatLoopRoll1Activate->set(0.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop2Enabled->toBool()); + EXPECT_FALSE(m_pBeatLoop1Enabled->toBool()); + + // stop the 2 beat loop roll + m_pButtonBeatLoopRoll2Activate->set(0.0); + EXPECT_FALSE(m_pLoopEnabled->toBool()); + EXPECT_FALSE(m_pBeatLoop2Enabled->toBool()); +} + +TEST_F(LoopingControlTest, BeatLoopRoll_StartPoint) { + m_pTrack1->setBpm(120.0); + + // start a 4 beat loop roll, start point should be overriden to play position + m_pLoopStartPoint->slotSet(8); + m_pButtonBeatLoopRoll4Activate->set(1.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop4Enabled->toBool()); + EXPECT_EQ(0, m_pLoopStartPoint->get()); + + // move the start point, activate a 1 beat loop roll, new start point be preserved + m_pLoopStartPoint->slotSet(8); + m_pButtonBeatLoopRoll1Activate->set(1.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop1Enabled->toBool()); + EXPECT_EQ(8, m_pLoopStartPoint->get()); + + // end the 1 beat loop roll + m_pButtonBeatLoopRoll1Activate->set(0.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_EQ(8, m_pLoopStartPoint->get()); + EXPECT_TRUE(m_pBeatLoop4Enabled->toBool()); + + // end the 4 beat loop roll + m_pButtonBeatLoopRoll4Activate->set(0.0); + EXPECT_FALSE(m_pLoopEnabled->toBool()); + EXPECT_FALSE(m_pBeatLoop4Enabled->toBool()); + + // new loop should start back at 0 + m_pButtonBeatLoopRoll4Activate->set(1.0); + EXPECT_TRUE(m_pLoopEnabled->toBool()); + EXPECT_TRUE(m_pBeatLoop4Enabled->toBool()); + EXPECT_EQ(0, m_pLoopStartPoint->get()); +}