From 2c129ea0af838543b987d55538b903e115f08ba5 Mon Sep 17 00:00:00 2001 From: be_ Date: Mon, 8 May 2017 02:40:01 -0500 Subject: [PATCH] measure beatloop_size when setting manual loops with quantize Plus: * make loop_double/halve always update beatloop_size * remove beatloop_double/halve ControlPushButtons Now, beatloop_size can only be out of sync with the loop in two cases: * a new track is loaded with a loop of a different size * a manual loop is set using loop_in/out without quantize It is no longer possible to resize manual loops that are not quantized with loop_double/halve. As discussed on PR #1187, that is not very useful behavior. It is still possible to resize manual loops that are not quantized by setting loop_scale. That will not update beatloop_size. --- src/engine/enginebuffer.h | 2 +- src/engine/loopingcontrol.cpp | 55 ++++-------- src/engine/loopingcontrol.h | 4 - src/test/looping_control_test.cpp | 135 ++++++++++-------------------- src/track/beats.h | 12 +++ 5 files changed, 72 insertions(+), 136 deletions(-) diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h index 571e4f28104..fb65aa00f2c 100644 --- a/src/engine/enginebuffer.h +++ b/src/engine/enginebuffer.h @@ -231,7 +231,7 @@ class EngineBuffer : public EngineObject { UserSettingsPointer m_pConfig; LoopingControl* m_pLoopingControl; - FRIEND_TEST(LoopingControlTest, LoopHalveButton_HalvesLoop); + FRIEND_TEST(LoopingControlTest, LoopScale_HalvesLoop); FRIEND_TEST(LoopingControlTest, LoopMoveTest); FRIEND_TEST(LoopingControlTest, LoopResizeSeek); FRIEND_TEST(LoopingControlTest, ReloopToggleButton_DoesNotJumpAhead); diff --git a/src/engine/loopingcontrol.cpp b/src/engine/loopingcontrol.cpp index 34fe8ca1e6b..bcfdc81fec9 100644 --- a/src/engine/loopingcontrol.cpp +++ b/src/engine/loopingcontrol.cpp @@ -119,12 +119,6 @@ LoopingControl::LoopingControl(QString group, true, false, false, 4.0); m_pCOBeatLoopSize->connectValueChangeRequest(this, SLOT(slotBeatLoopSizeChangeRequest(double)), Qt::DirectConnection); - m_pCOBeatLoopDouble = new ControlPushButton(ConfigKey(group, "beatloop_double")); - connect(m_pCOBeatLoopDouble, SIGNAL(valueChanged(double)), - this, SLOT(slotBeatLoopDouble(double))); - m_pCOBeatLoopHalve = new ControlPushButton(ConfigKey(group, "beatloop_halve")); - connect(m_pCOBeatLoopHalve, SIGNAL(valueChanged(double)), - this, SLOT(slotBeatLoopHalve(double))); m_pCOBeatLoopToggle = new ControlPushButton(ConfigKey(group, "beatloop_toggle")); connect(m_pCOBeatLoopToggle, SIGNAL(valueChanged(double)), this, SLOT(slotBeatLoopToggle(double))); @@ -233,8 +227,6 @@ LoopingControl::~LoopingControl() { delete pBeatLoop; } delete m_pCOBeatLoopSize; - delete m_pCOBeatLoopDouble; - delete m_pCOBeatLoopHalve; delete m_pCOBeatLoopToggle; delete m_pCOBeatLoopRollActivate; @@ -315,14 +307,7 @@ void LoopingControl::slotLoopHalve(double pressed) { return; } - LoopSamples loopSamples = m_loopSamples.getValue(); - bool noLoopSet = loopSamples.start == kNoTrigger || loopSamples.end == kNoTrigger; - - if (noLoopSet || currentLoopMatchesBeatloopSize()) { - slotBeatLoop(m_pCOBeatLoopSize->get() / 2.0, true, false); - } else { - slotLoopScale(0.5); - } + slotBeatLoop(m_pCOBeatLoopSize->get() / 2.0, true, false); } void LoopingControl::slotLoopDouble(double pressed) { @@ -330,14 +315,7 @@ void LoopingControl::slotLoopDouble(double pressed) { return; } - LoopSamples loopSamples = m_loopSamples.getValue(); - bool noLoopSet = loopSamples.start == kNoTrigger || loopSamples.end == kNoTrigger; - - if (noLoopSet || currentLoopMatchesBeatloopSize()) { - slotBeatLoop(m_pCOBeatLoopSize->get() * 2.0, true, false); - } else { - slotLoopScale(2.0); - } + slotBeatLoop(m_pCOBeatLoopSize->get() * 2.0, true, false); } double LoopingControl::process(const double dRate, @@ -499,6 +477,13 @@ void LoopingControl::setLoopInToCurrentPosition() { loopSamples.start = pos; m_pCOLoopStartPosition->set(loopSamples.start); + if (m_pQuantizeEnabled->toBool() + && loopSamples.start < loopSamples.end + && m_pBeats != nullptr) { + m_pCOBeatLoopSize->setAndConfirm( + m_pBeats->numBeatsInRange(loopSamples.start, loopSamples.end)); + } + m_loopSamples.setValue(loopSamples); //qDebug() << "set loop_in to " << loopSamples.start; } @@ -575,6 +560,10 @@ void LoopingControl::setLoopOutToCurrentPosition() { loopSamples.end = pos; m_pCOLoopEndPosition->set(loopSamples.end); m_loopSamples.setValue(loopSamples); + if (m_pQuantizeEnabled->toBool() && m_pBeats != nullptr) { + m_pCOBeatLoopSize->setAndConfirm( + m_pBeats->numBeatsInRange(loopSamples.start, loopSamples.end)); + } // start looping if (loopSamples.start != kNoTrigger && @@ -902,7 +891,7 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable double nextBeat; m_pBeats->findPrevNextBeats(cur_pos, &prevBeat, &nextBeat); - if (m_pQuantizeEnabled->get() > 0.0 && prevBeat != -1) { + if (m_pQuantizeEnabled->toBool() && prevBeat != -1) { if (beats >= 1.0) { newloopSamples.start = prevBeat; } else { @@ -960,7 +949,7 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable return; } - // When loading a new track or after setting a manual loop, + // When loading a new track or after setting a manual loop without quantize, // do not resize the existing loop until beatloop_size matches // the size of the existing loop. // Do not return immediately so beatloop_size can be updated. @@ -1020,20 +1009,6 @@ void LoopingControl::slotBeatLoopSizeChangeRequest(double beats) { slotBeatLoop(beats, true, false); } -void LoopingControl::slotBeatLoopDouble(double pressed) { - if (pressed <= 0.0) { - return; - } - slotBeatLoop(m_pCOBeatLoopSize->get() * 2.0, true, false); -} - -void LoopingControl::slotBeatLoopHalve(double pressed) { - if (pressed <= 0.0) { - return; - } - slotBeatLoop(m_pCOBeatLoopSize->get() / 2.0, true, false); -} - void LoopingControl::slotBeatLoopToggle(double pressed) { if (pressed > 0) { if (m_bLoopingEnabled) { diff --git a/src/engine/loopingcontrol.h b/src/engine/loopingcontrol.h index 815d0bb7943..7c0801c4eea 100644 --- a/src/engine/loopingcontrol.h +++ b/src/engine/loopingcontrol.h @@ -75,8 +75,6 @@ class LoopingControl : public EngineControl { // beatslicing effect. void slotBeatLoop(double loopSize, bool keepStartPoint=false, bool enable=true); void slotBeatLoopSizeChangeRequest(double beats); - void slotBeatLoopHalve(double pressed); - void slotBeatLoopDouble(double pressed); void slotBeatLoopToggle(double pressed); void slotBeatLoopRollActivate(double pressed); void slotBeatLoopActivate(BeatLoopingControl* pBeatLoopControl); @@ -156,8 +154,6 @@ class LoopingControl : public EngineControl { // Base BeatLoop Control Object. ControlObject* m_pCOBeatLoop; ControlObject* m_pCOBeatLoopSize; - ControlPushButton* m_pCOBeatLoopDouble; - ControlPushButton* m_pCOBeatLoopHalve; // Different sizes for Beat Loops/Seeks. static double s_dBeatSizes[]; // Array of BeatLoopingControls, one for each size. diff --git a/src/test/looping_control_test.cpp b/src/test/looping_control_test.cpp index 25b8621514a..963cb0f0f7c 100644 --- a/src/test/looping_control_test.cpp +++ b/src/test/looping_control_test.cpp @@ -39,6 +39,7 @@ class LoopingControlTest : public MockedEngineBackendTest { m_pLoopEnabled = std::make_unique(m_sGroup1, "loop_enabled"); m_pLoopStartPoint = std::make_unique(m_sGroup1, "loop_start_position"); m_pLoopEndPoint = std::make_unique(m_sGroup1, "loop_end_position"); + m_pLoopScale = std::make_unique(m_sGroup1, "loop_scale"); m_pButtonPlay = std::make_unique(m_sGroup1, "play"); m_pPlayPosition = std::make_unique(m_sGroup1, "playposition"); m_pButtonBeatMoveForward = std::make_unique(m_sGroup1, "loop_move_1_forward"); @@ -50,8 +51,6 @@ class LoopingControlTest : public MockedEngineBackendTest { m_pBeatLoop64Enabled = std::make_unique(m_sGroup1, "beatloop_64_enabled"); m_pBeatLoop = std::make_unique(m_sGroup1, "beatloop"); m_pBeatLoopSize = std::make_unique(m_sGroup1, "beatloop_size"); - m_pButtonBeatLoopDouble = std::make_unique(m_sGroup1, "beatloop_double"); - m_pButtonBeatLoopHalve = std::make_unique(m_sGroup1, "beatloop_halve"); m_pButtonBeatLoopToggle = std::make_unique(m_sGroup1, "beatloop_toggle"); } @@ -79,6 +78,7 @@ class LoopingControlTest : public MockedEngineBackendTest { std::unique_ptr m_pLoopEnabled; std::unique_ptr m_pLoopStartPoint; std::unique_ptr m_pLoopEndPoint; + std::unique_ptr m_pLoopScale; std::unique_ptr m_pPlayPosition; std::unique_ptr m_pButtonPlay; std::unique_ptr m_pButtonBeatMoveForward; @@ -90,8 +90,6 @@ class LoopingControlTest : public MockedEngineBackendTest { std::unique_ptr m_pBeatLoop64Enabled; std::unique_ptr m_pBeatLoop; std::unique_ptr m_pBeatLoopSize; - std::unique_ptr m_pButtonBeatLoopDouble; - std::unique_ptr m_pButtonBeatLoopHalve; std::unique_ptr m_pButtonBeatLoopToggle; }; @@ -336,7 +334,7 @@ TEST_F(LoopingControlTest, ReloopAndStopButton) { EXPECT_TRUE(m_pLoopEnabled->toBool()); } -TEST_F(LoopingControlTest, LoopDoubleButton_DoublesLoop) { +TEST_F(LoopingControlTest, LoopScale_DoublesLoop) { seekToSampleAndProcess(0); m_pButtonLoopIn->set(1); m_pButtonLoopIn->set(0); @@ -345,16 +343,42 @@ TEST_F(LoopingControlTest, LoopDoubleButton_DoublesLoop) { m_pButtonLoopOut->set(0); EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(500, m_pLoopEndPoint->get()); - m_pButtonLoopDouble->slotSet(1); - m_pButtonLoopDouble->slotSet(0); + m_pLoopScale->set(2.0); EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(1000, m_pLoopEndPoint->get()); - m_pButtonLoopDouble->slotSet(1); - m_pButtonLoopDouble->slotSet(0); + m_pLoopScale->set(2.0); EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(2000, m_pLoopEndPoint->get()); } +TEST_F(LoopingControlTest, LoopScale_HalvesLoop) { + m_pLoopStartPoint->slotSet(0); + m_pLoopEndPoint->slotSet(2000); + seekToSampleAndProcess(1800); + EXPECT_EQ(0, m_pLoopStartPoint->get()); + EXPECT_EQ(2000, m_pLoopEndPoint->get()); + EXPECT_EQ(1800, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + EXPECT_FALSE(isLoopEnabled()); + m_pLoopScale->set(0.5); + ProcessBuffer(); + EXPECT_EQ(0, m_pLoopStartPoint->get()); + EXPECT_EQ(1000, m_pLoopEndPoint->get()); + + // The loop was not enabled so halving the loop should not move the playhead + // even though it is outside the loop. + EXPECT_EQ(1800, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + + m_pButtonReloopToggle->slotSet(1); + EXPECT_TRUE(isLoopEnabled()); + m_pLoopScale->set(0.5); + ProcessBuffer(); + EXPECT_EQ(0, m_pLoopStartPoint->get()); + EXPECT_EQ(500, m_pLoopEndPoint->get()); + // Since the current sample was out of range of the new loop, + // the current sample should reseek based on the new loop size. + EXPECT_EQ(300, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); +} + TEST_F(LoopingControlTest, LoopDoubleButton_IgnoresPastTrackEnd) { seekToSampleAndProcess(50); m_pLoopStartPoint->slotSet(kTrackLengthSamples / 2.0); @@ -377,18 +401,19 @@ TEST_F(LoopingControlTest, LoopDoubleButton_DoublesBeatloopSize) { EXPECT_EQ(32.0, m_pBeatLoopSize->get()); } -TEST_F(LoopingControlTest, LoopDoubleButton_DoesNotDoubleBeatloopSizeForManualLoop) { - m_pTrack1->setBpm(120.0); - m_pBeatLoopSize->set(8.0); +TEST_F(LoopingControlTest, LoopDoubleButton_DoesNotResizeManualLoop) { seekToSampleAndProcess(500); m_pButtonLoopIn->set(1.0); m_pButtonLoopIn->set(0.0); seekToSampleAndProcess(1000); m_pButtonLoopOut->set(1.0); m_pButtonLoopOut->set(0.0); + EXPECT_EQ(500, m_pLoopStartPoint->get()); + EXPECT_EQ(1000, m_pLoopEndPoint->get()); m_pButtonLoopDouble->slotSet(1); m_pButtonLoopDouble->slotSet(0); - EXPECT_EQ(8.0, m_pBeatLoopSize->get()); + EXPECT_EQ(500, m_pLoopStartPoint->get()); + EXPECT_EQ(1000, m_pLoopEndPoint->get()); } TEST_F(LoopingControlTest, LoopDoubleButton_UpdatesNumberedBeatloopActivationControls) { @@ -405,36 +430,6 @@ TEST_F(LoopingControlTest, LoopDoubleButton_UpdatesNumberedBeatloopActivationCon EXPECT_TRUE(m_pBeatLoop4Enabled->toBool()); } -TEST_F(LoopingControlTest, LoopHalveButton_HalvesLoop) { - m_pLoopStartPoint->slotSet(0); - m_pLoopEndPoint->slotSet(2000); - seekToSampleAndProcess(1800); - EXPECT_EQ(0, m_pLoopStartPoint->get()); - EXPECT_EQ(2000, m_pLoopEndPoint->get()); - EXPECT_EQ(1800, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); - EXPECT_FALSE(isLoopEnabled()); - m_pButtonLoopHalve->slotSet(1); - m_pButtonLoopHalve->slotSet(0); - ProcessBuffer(); - EXPECT_EQ(0, m_pLoopStartPoint->get()); - EXPECT_EQ(1000, m_pLoopEndPoint->get()); - - // The loop was not enabled so halving the loop should not move the playhead - // even though it is outside the loop. - EXPECT_EQ(1800, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); - - m_pButtonReloopToggle->slotSet(1); - EXPECT_TRUE(isLoopEnabled()); - m_pButtonLoopHalve->slotSet(1); - m_pButtonLoopHalve->slotSet(0); - ProcessBuffer(); - EXPECT_EQ(0, m_pLoopStartPoint->get()); - EXPECT_EQ(500, m_pLoopEndPoint->get()); - // Since the current sample was out of range of the new loop, - // the current sample should reseek based on the new loop size. - EXPECT_EQ(300, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); -} - TEST_F(LoopingControlTest, LoopHalveButton_IgnoresTooSmall) { ProcessBuffer(); m_pLoopStartPoint->slotSet(0); @@ -457,18 +452,19 @@ TEST_F(LoopingControlTest, LoopHalveButton_HalvesBeatloopSize) { EXPECT_EQ(32.0, m_pBeatLoopSize->get()); } -TEST_F(LoopingControlTest, LoopHalveButton_DoesNotHalveBeatloopSizeForManualLoop) { - m_pTrack1->setBpm(120.0); - m_pBeatLoopSize->set(64.0); +TEST_F(LoopingControlTest, LoopHalveButton_DoesNotResizeManualLoop) { seekToSampleAndProcess(500); m_pButtonLoopIn->set(1.0); m_pButtonLoopIn->set(0.0); seekToSampleAndProcess(1000); m_pButtonLoopOut->set(1.0); m_pButtonLoopOut->set(0.0); + EXPECT_EQ(500, m_pLoopStartPoint->get()); + EXPECT_EQ(1000, m_pLoopEndPoint->get()); m_pButtonLoopHalve->slotSet(1); m_pButtonLoopHalve->slotSet(0); - EXPECT_EQ(64.0, m_pBeatLoopSize->get()); + EXPECT_EQ(500, m_pLoopStartPoint->get()); + EXPECT_EQ(1000, m_pLoopEndPoint->get()); } TEST_F(LoopingControlTest, LoopHalveButton_UpdatesNumberedBeatloopActivationControls) { @@ -716,64 +712,21 @@ TEST_F(LoopingControlTest, BeatLoopSize_ValueChangeResizesBeatLoop) { TEST_F(LoopingControlTest, BeatLoopSize_ValueChangeDoesNotResizeManualLoop) { seekToSampleAndProcess(50); m_pTrack1->setBpm(160.0); + m_pQuantizeEnabled->set(0); m_pBeatLoopSize->set(4.0); m_pButtonLoopIn->slotSet(1); m_pButtonLoopIn->slotSet(0); seekToSampleAndProcess(500); m_pButtonLoopOut->slotSet(1); m_pButtonLoopOut->slotSet(0); - EXPECT_TRUE(m_pLoopEnabled->toBool()); double oldLoopStart = m_pLoopStartPoint->get(); double oldLoopEnd = m_pLoopEndPoint->get(); - double oldLoopLength = oldLoopEnd - oldLoopStart; - m_pButtonBeatLoopToggle->set(1.0); - m_pButtonBeatLoopToggle->set(0.0); - EXPECT_FALSE(m_pLoopEnabled->toBool()); m_pBeatLoopSize->set(8.0); - double newLoopStart = m_pLoopStartPoint->get(); double newLoopEnd = m_pLoopEndPoint->get(); - double newLoopLength = newLoopEnd - newLoopStart; EXPECT_EQ(oldLoopStart, newLoopStart); EXPECT_EQ(oldLoopEnd, newLoopEnd); - EXPECT_EQ(oldLoopLength, newLoopLength); -} - -TEST_F(LoopingControlTest, BeatLoopDoubleButton_DoublesBeatloopSize) { - m_pTrack1->setBpm(120.0); - m_pBeatLoopSize->set(3.0); - m_pButtonBeatLoopDouble->set(1.0); - m_pButtonBeatLoopDouble->set(0.0); - EXPECT_EQ(6.0, m_pBeatLoopSize->get()); -} - -TEST_F(LoopingControlTest, BeatLoopDoubleButton_DoublesBeatloopSizeWhenNoLoopIsSet) { - m_pTrack1->setBpm(120.0); - m_pBeatLoopSize->set(64.0); - m_pLoopStartPoint->set(kNoTrigger); - m_pLoopEndPoint->set(kNoTrigger); - m_pButtonBeatLoopDouble->slotSet(1); - m_pButtonBeatLoopDouble->slotSet(0); - EXPECT_EQ(128.0, m_pBeatLoopSize->get()); -} - -TEST_F(LoopingControlTest, BeatLoopHalveButton_HalvesBeatloopSize) { - m_pTrack1->setBpm(120.0); - m_pBeatLoopSize->set(6.0); - m_pButtonBeatLoopHalve->set(1.0); - m_pButtonBeatLoopHalve->set(0.0); - EXPECT_EQ(3.0, m_pBeatLoopSize->get()); -} - -TEST_F(LoopingControlTest, BeatLoopHalveButton_HalvesBeatloopSizeWhenNoLoopIsSet) { - m_pTrack1->setBpm(120.0); - m_pBeatLoopSize->set(64.0); - m_pLoopStartPoint->set(kNoTrigger); - m_pLoopEndPoint->set(kNoTrigger); - m_pButtonBeatLoopHalve->slotSet(1); - m_pButtonBeatLoopHalve->slotSet(0); - EXPECT_EQ(32.0, m_pBeatLoopSize->get()); } TEST_F(LoopingControlTest, LegacyBeatLoopControl) { diff --git a/src/track/beats.h b/src/track/beats.h index 9457f85d7d3..112dd986194 100644 --- a/src/track/beats.h +++ b/src/track/beats.h @@ -104,6 +104,18 @@ class Beats { // then dSamples is returned. If no beat can be found, returns -1. virtual double findNthBeat(double dSamples, int n) const = 0; + inline int numBeatsInRange(double dStartSample, double dEndSample) { + double dLastCountedBeat = 0.0; + int iBeatsCounter; + for (iBeatsCounter = 1; dLastCountedBeat < dEndSample; iBeatsCounter++) { + dLastCountedBeat = findNthBeat(dStartSample, iBeatsCounter); + if (dLastCountedBeat == -1) { + break; + } + } + return iBeatsCounter - 2; + }; + // Find the sample N beats away from dSample. The number of beats may be // negative and does not need to be an integer. inline double findNBeatsFromSample(double dSample, double beats) const {