From 514b71eab48f33861ac3271d7775e5856f6f3ed8 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 14 Mar 2024 14:11:55 +0100 Subject: [PATCH 1/6] Looping/Beatjump: use seconds if track has no beats --- src/engine/controls/loopingcontrol.cpp | 21 ++++++++++++--------- src/engine/controls/loopingcontrol.h | 10 ++++++++++ src/test/hotcuecontrol_test.cpp | 22 ---------------------- src/test/looping_control_test.cpp | 21 --------------------- 4 files changed, 22 insertions(+), 52 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index bee3e8e32d7..436f9b6acbc 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -1228,15 +1228,18 @@ void LoopingControl::trackLoaded(TrackPointer pNewTrack) { void LoopingControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) { clearActiveBeatLoop(); - m_pBeats = pBeats; - if (m_pBeats) { - LoopInfo loopInfo = m_loopInfo.getValue(); - if (loopInfo.startPosition.isValid() && loopInfo.endPosition.isValid()) { - double loaded_loop_size = findBeatloopSizeForLoop( - loopInfo.startPosition, loopInfo.endPosition); - if (loaded_loop_size != -1) { - m_pCOBeatLoopSize->setAndConfirm(loaded_loop_size); - } + if (pBeats) { + m_pBeats = pBeats; + } else { + m_pBeats = getFake60BpmBeats(); + } + // TODO All "if (m_pBeats)" checks are now obsolete actually... + LoopInfo loopInfo = m_loopInfo.getValue(); + if (loopInfo.startPosition.isValid() && loopInfo.endPosition.isValid()) { + double loaded_loop_size = findBeatloopSizeForLoop( + loopInfo.startPosition, loopInfo.endPosition); + if (loaded_loop_size != -1) { + m_pCOBeatLoopSize->setAndConfirm(loaded_loop_size); } } } diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h index 96167a41927..3df3650bdba 100644 --- a/src/engine/controls/loopingcontrol.h +++ b/src/engine/controls/loopingcontrol.h @@ -150,6 +150,16 @@ class LoopingControl : public EngineControl { void updateBeatLoopingControls(); bool currentLoopMatchesBeatloopSize(const LoopInfo& loopInfo) const; + // Fake beats that allow using looping/beatjump controls with no beats: + // one 'beat' = one second + mixxx::BeatsPointer getFake60BpmBeats() { + auto fakeBeats = mixxx::Beats::fromConstTempo( + frameInfo().sampleRate, + mixxx::audio::kStartFramePos, + mixxx::Bpm(60.0)); + return fakeBeats; + } + // Given loop in and out points, determine if this is a beatloop of a particular // size. double findBeatloopSizeForLoop(mixxx::audio::FramePos startPosition, diff --git a/src/test/hotcuecontrol_test.cpp b/src/test/hotcuecontrol_test.cpp index ec29f4d11ad..73eca9f01fe 100644 --- a/src/test/hotcuecontrol_test.cpp +++ b/src/test/hotcuecontrol_test.cpp @@ -1092,28 +1092,6 @@ TEST_F(HotcueControlTest, CueLoopWithSavedLoopToggles) { EXPECT_TRUE(m_pLoopEnabled->toBool()); } -TEST_F(HotcueControlTest, CueLoopWithoutLoopOrBeats) { - createAndLoadFakeTrack(); - - EXPECT_DOUBLE_EQ(static_cast(HotcueControl::Status::Empty), m_pHotcue1Enabled->get()); - EXPECT_FALSE(mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pHotcue1Position->get()) - .isValid()); - EXPECT_FALSE(mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pHotcue1EndPosition->get()) - .isValid()); - - m_pHotcue1CueLoop->set(1); - m_pHotcue1CueLoop->set(0); - - EXPECT_DOUBLE_EQ(static_cast(HotcueControl::Status::Set), m_pHotcue1Enabled->get()); - EXPECT_FRAMEPOS_EQ_CONTROL(mixxx::audio::kStartFramePos, m_pHotcue1Position); - EXPECT_FALSE(mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pHotcue1EndPosition->get()) - .isValid()); - EXPECT_FALSE(m_pLoopEnabled->toBool()); -} - TEST_F(HotcueControlTest, SavedLoopToggleDoesNotSeek) { // Setup fake track with 120 bpm and calculate loop size TrackPointer pTrack = loadTestTrackWithBpm(120.0); diff --git a/src/test/looping_control_test.cpp b/src/test/looping_control_test.cpp index 7c56735ad7e..e051e920c02 100644 --- a/src/test/looping_control_test.cpp +++ b/src/test/looping_control_test.cpp @@ -246,16 +246,6 @@ TEST_F(LoopingControlTest, LoopInButton_QuantizeDisabled) { EXPECT_FRAMEPOS_EQ_CONTROL(mixxx::audio::FramePos{50}, m_pLoopStartPoint); } -TEST_F(LoopingControlTest, LoopInButton_QuantizeEnabledNoBeats) { - m_pQuantizeEnabled->set(1); - m_pClosestBeat->set(-1); - m_pNextBeat->set(-1); - setCurrentPosition(mixxx::audio::FramePos{50}); - m_pButtonLoopIn->set(1); - m_pButtonLoopIn->set(0); - EXPECT_FRAMEPOS_EQ_CONTROL(mixxx::audio::FramePos{50}, m_pLoopStartPoint); -} - TEST_F(LoopingControlTest, LoopInButton_AdjustLoopInPointOutsideLoop) { m_pLoopStartPoint->set(mixxx::audio::FramePos{1000}.toEngineSamplePos()); m_pLoopEndPoint->set(mixxx::audio::FramePos{2000}.toEngineSamplePos()); @@ -289,17 +279,6 @@ TEST_F(LoopingControlTest, LoopOutButton_QuantizeDisabled) { EXPECT_FRAMEPOS_EQ_CONTROL(mixxx::audio::FramePos{500}, m_pLoopEndPoint); } -TEST_F(LoopingControlTest, LoopOutButton_QuantizeEnabledNoBeats) { - m_pQuantizeEnabled->set(1); - m_pClosestBeat->set(-1); - m_pNextBeat->set(-1); - setCurrentPosition(mixxx::audio::FramePos{500}); - m_pLoopStartPoint->set(mixxx::audio::kStartFramePos.toEngineSamplePos()); - m_pButtonLoopOut->set(1); - m_pButtonLoopOut->set(0); - EXPECT_FRAMEPOS_EQ_CONTROL(mixxx::audio::FramePos{500}, m_pLoopEndPoint); -} - TEST_F(LoopingControlTest, LoopOutButton_AdjustLoopOutPointOutsideLoop) { m_pLoopStartPoint->set(mixxx::audio::FramePos{1000}.toEngineSamplePos()); m_pLoopEndPoint->set(mixxx::audio::FramePos{2000}.toEngineSamplePos()); From 1cfcc1b0966734010e1024611edb1910011e8824 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 14 Mar 2024 14:13:21 +0100 Subject: [PATCH 2/6] Looping: fix tests after implementing fake beats for tracks with no beats --- src/test/looping_control_test.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/test/looping_control_test.cpp b/src/test/looping_control_test.cpp index e051e920c02..693fe5d4e68 100644 --- a/src/test/looping_control_test.cpp +++ b/src/test/looping_control_test.cpp @@ -247,6 +247,7 @@ TEST_F(LoopingControlTest, LoopInButton_QuantizeDisabled) { } TEST_F(LoopingControlTest, LoopInButton_AdjustLoopInPointOutsideLoop) { + m_pQuantizeEnabled->set(0); m_pLoopStartPoint->set(mixxx::audio::FramePos{1000}.toEngineSamplePos()); m_pLoopEndPoint->set(mixxx::audio::FramePos{2000}.toEngineSamplePos()); m_pButtonReloopToggle->set(1); @@ -258,6 +259,7 @@ TEST_F(LoopingControlTest, LoopInButton_AdjustLoopInPointOutsideLoop) { } TEST_F(LoopingControlTest, LoopInButton_AdjustLoopInPointInsideLoop) { + m_pQuantizeEnabled->set(0); m_pLoopStartPoint->set(mixxx::audio::FramePos{1000}.toEngineSamplePos()); m_pLoopEndPoint->set(mixxx::audio::FramePos{2000}.toEngineSamplePos()); m_pButtonReloopToggle->set(1); @@ -280,6 +282,7 @@ TEST_F(LoopingControlTest, LoopOutButton_QuantizeDisabled) { } TEST_F(LoopingControlTest, LoopOutButton_AdjustLoopOutPointOutsideLoop) { + m_pQuantizeEnabled->set(0); m_pLoopStartPoint->set(mixxx::audio::FramePos{1000}.toEngineSamplePos()); m_pLoopEndPoint->set(mixxx::audio::FramePos{2000}.toEngineSamplePos()); m_pButtonReloopToggle->set(1); @@ -291,6 +294,7 @@ TEST_F(LoopingControlTest, LoopOutButton_AdjustLoopOutPointOutsideLoop) { } TEST_F(LoopingControlTest, LoopOutButton_AdjustLoopOutPointInsideLoop) { + m_pQuantizeEnabled->set(0); m_pLoopStartPoint->set(mixxx::audio::FramePos{100}.toEngineSamplePos()); m_pLoopEndPoint->set(mixxx::audio::FramePos{2000}.toEngineSamplePos()); m_pButtonReloopToggle->set(1); @@ -322,7 +326,7 @@ TEST_F(LoopingControlTest, LoopInOutButtons_QuantizeEnabled) { m_pButtonLoopOut->set(1); m_pButtonLoopOut->set(0); ProcessBuffer(); // first process to schedule seek in a stopped deck - ProcessBuffer(); // them seek + ProcessBuffer(); // then seek EXPECT_EQ(m_pLoopEndPoint->get(), 44100 * 2 * 4); EXPECT_FRAMEPOS_EQ(currentFramePos(), mixxx::audio::FramePos{250}); // Should adopt the loop size and enable the correct loop control @@ -338,7 +342,7 @@ TEST_F(LoopingControlTest, LoopInOutButtons_QuantizeEnabled) { m_pButtonLoopOut->set(1); m_pButtonLoopOut->set(0); ProcessBuffer(); // first process to schedule seek in a stopped deck - ProcessBuffer(); // them seek + ProcessBuffer(); // then seek EXPECT_FRAMEPOS_EQ(currentFramePos(), mixxx::audio::FramePos{250}); EXPECT_EQ(m_pLoopEndPoint->get(), 44100 * 2 * 4); EXPECT_TRUE(m_pBeatLoop4Enabled->toBool()); @@ -410,6 +414,7 @@ TEST_F(LoopingControlTest, ReloopAndStopButton) { } TEST_F(LoopingControlTest, LoopScale_DoublesLoop) { + m_pQuantizeEnabled->set(0); setCurrentPosition(mixxx::audio::kStartFramePos); m_pButtonLoopIn->set(1); m_pButtonLoopIn->set(0); @@ -484,6 +489,7 @@ TEST_F(LoopingControlTest, LoopDoubleButton_DoublesBeatloopSize) { } TEST_F(LoopingControlTest, LoopDoubleButton_DoesNotResizeManualLoop) { + m_pQuantizeEnabled->set(0); setCurrentPosition(mixxx::audio::FramePos{500}); m_pButtonLoopIn->set(1.0); m_pButtonLoopIn->set(0.0); @@ -535,6 +541,7 @@ TEST_F(LoopingControlTest, LoopHalveButton_HalvesBeatloopSize) { } TEST_F(LoopingControlTest, LoopHalveButton_DoesNotResizeManualLoop) { + m_pQuantizeEnabled->set(0); setCurrentPosition(mixxx::audio::FramePos{500}); m_pButtonLoopIn->set(1.0); m_pButtonLoopIn->set(0.0); From 079cf44e8b95ae345be6ea8d421e4cc019c97e9d Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 14 Mar 2024 14:55:32 +0100 Subject: [PATCH 3/6] update tooltips and control descriptions for 'no beats, use seconds' case --- src/controllers/controlpickermenu.cpp | 26 ++++++++++++++++------- src/skin/legacy/tooltips.cpp | 30 ++++++++++++++++++++------- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/controllers/controlpickermenu.cpp b/src/controllers/controlpickermenu.cpp index 28eaf9a5d9c..3d4b5fa2b23 100644 --- a/src/controllers/controlpickermenu.cpp +++ b/src/controllers/controlpickermenu.cpp @@ -578,14 +578,18 @@ ControlPickerMenu::ControlPickerMenu(QWidget* pParent) QMenu* pLoopMenu = addSubmenu(tr("Looping")); // add beatloop_activate and beatlooproll_activate to both the // Loop and Beat-Loop menus to make sure users can find them. + QString noBeatsSeconds = tr(" (if track has no beats the unit is seconds)"); QString beatloopActivateTitle = tr("Loop Selected Beats"); - QString beatloopActivateDescription = tr("Create a beat loop of selected beat size"); + QString beatloopActivateDescription = + tr("Create a beat loop of selected beat size") + noBeatsSeconds; QString beatloopRollActivateTitle = tr("Loop Roll Selected Beats"); - QString beatloopRollActivateDescription = tr("Create a rolling beat loop of selected beat size"); + QString beatloopRollActivateDescription = + tr("Create a rolling beat loop of selected beat size") + noBeatsSeconds; QString beatLoopTitle = tr("Loop %1 Beats"); QString beatLoopRollTitle = tr("Loop Roll %1 Beats"); QString beatLoopDescription = tr("Create %1-beat loop"); - QString beatLoopRollDescription = tr("Create temporary %1-beat loop roll"); + QString beatLoopRollDescription = + tr("Create temporary %1-beat loop roll") + noBeatsSeconds; QList beatSizes = LoopingControl::getBeatSizes(); @@ -653,8 +657,14 @@ ControlPickerMenu::ControlPickerMenu(QWidget* pParent) QMenu* pBeatJumpMenu = addSubmenu(tr("Beat Jump / Loop Move")); QString beatJumpForwardTitle = tr("Jump / Move Loop Forward %1 Beats"); QString beatJumpBackwardTitle = tr("Jump / Move Loop Backward %1 Beats"); - QString beatJumpForwardDescription = tr("Jump forward by %1 beats, or if a loop is enabled, move the loop forward %1 beats"); - QString beatJumpBackwardDescription = tr("Jump backward by %1 beats, or if a loop is enabled, move the loop backward %1 beats"); + QString beatJumpForwardDescription = + tr("Jump forward by %1 beats, or if a loop is enabled, move the " + "loop forward %1 beats") + + noBeatsSeconds; + QString beatJumpBackwardDescription = + tr("Jump backward by %1 beats, or if a loop is enabled, move the " + "loop backward %1 beats") + + noBeatsSeconds; addDeckControl("beatjump_forward", tr("Beat Jump / Loop Move Forward Selected Beats"), tr("Jump forward by the selected number of beats, or if a loop is " @@ -690,8 +700,10 @@ ControlPickerMenu::ControlPickerMenu(QWidget* pParent) // Loop moving QString loopMoveForwardTitle = tr("Move Loop +%1 Beats"); QString loopMoveBackwardTitle = tr("Move Loop -%1 Beats"); - QString loopMoveForwardDescription = tr("Move loop forward by %1 beats"); - QString loopMoveBackwardDescription = tr("Move loop backward by %1 beats"); + QString loopMoveForwardDescription = tr("Move loop forward by %1 beats") + + noBeatsSeconds; + QString loopMoveBackwardDescription = tr("Move loop backward by %1 beats") + + noBeatsSeconds; QMenu* pLoopmoveFwdSubmenu = addSubmenu(tr("Loop Move Forward"), pBeatJumpMenu); foreach (double beats, beatSizes) { diff --git a/src/skin/legacy/tooltips.cpp b/src/skin/legacy/tooltips.cpp index 388f8b49da8..65ae6d8d463 100644 --- a/src/skin/legacy/tooltips.cpp +++ b/src/skin/legacy/tooltips.cpp @@ -722,9 +722,12 @@ void Tooltips::addStandardTooltips() { << tr("Loop Double") << tr("Doubles the current loop's length by moving the end marker."); + QString noBeatsSeconds = tr(" If track has no beats the unit is seconds."); + add("beatloop_size") << tr("Beatloop Size") << tr("Select the size of the loop in beats to set with the Beatloop button.") + << noBeatsSeconds << tr("Changing this resizes the loop if the loop already matches this size."); add("beatloop_halve") @@ -743,21 +746,32 @@ void Tooltips::addStandardTooltips() { add("beatjump_size") << tr("Beatjump/Loop Move Size") + << noBeatsSeconds << tr("Select the number of beats to jump or move the loop with the Beatjump Forward/Backward buttons."); add("beatjump_forward") << tr("Beatjump Forward") - << QString("%1: %2").arg(leftClick + " " + loopInactive, tr("Jump forward by the set number of beats.")) - << QString("%1: %2").arg(leftClick + " " + loopActive, tr("Move the loop forward by the set number of beats.")) - << QString("%1: %2").arg(rightClick + " " + loopInactive, tr("Jump forward by 1 beat.")) - << QString("%1: %2").arg(rightClick + " " + loopActive, tr("Move the loop forward by 1 beat.")); + << QString("%1: %2").arg(leftClick + " " + loopInactive, + tr("Jump forward by the set number of beats.")) + << QString("%1: %2").arg(leftClick + " " + loopActive, + tr("Move the loop forward by the set number of beats.")) + << QString("%1: %2").arg(rightClick + " " + loopInactive, + tr("Jump forward by 1 beat.")) + << QString("%1: %2").arg(rightClick + " " + loopActive, + tr("Move the loop forward by 1 beat.")) + << noBeatsSeconds; add("beatjump_backward") << tr("Beatjump Backward") - << QString("%1: %2").arg(leftClick + " " + loopInactive, tr("Jump backward by the set number of beats.")) - << QString("%1: %2").arg(leftClick + " " + loopActive, tr("Move the loop backward by the set number of beats.")) - << QString("%1: %2").arg(rightClick + " " + loopInactive, tr("Jump backward by 1 beat.")) - << QString("%1: %2").arg(rightClick + " " + loopActive, tr("Move the loop backward by 1 beat.")); + << QString("%1: %2").arg(leftClick + " " + loopInactive, + tr("Jump backward by the set number of beats.")) + << QString("%1: %2").arg(leftClick + " " + loopActive, + tr("Move the loop backward by the set number of beats.")) + << QString("%1: %2").arg(rightClick + " " + loopInactive, + tr("Jump backward by 1 beat.")) + << QString("%1: %2").arg(rightClick + " " + loopActive, + tr("Move the loop backward by 1 beat.")) + << noBeatsSeconds; add("loop_exit") << tr("Loop Exit") From 01ed90b1275a5275e27cdaad0ec08cff4f9349e7 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Fri, 15 Mar 2024 13:08:17 +0100 Subject: [PATCH 4/6] Looping: act quantized only if we have true track beats ... not just the fake beats we use for looping/jumping by seconds. This will prevent unexpectedly placing loop markers on (invisible) fake beat markers. --- src/engine/controls/loopingcontrol.cpp | 31 +++++++++++++++++--------- src/engine/controls/loopingcontrol.h | 4 ++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 436f9b6acbc..25fb25a87c6 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -54,7 +54,8 @@ LoopingControl::LoopingControl(const QString& group, m_bAdjustingLoopOut(false), m_bAdjustingLoopInOld(false), m_bAdjustingLoopOutOld(false), - m_bLoopOutPressedWhileLoopDisabled(false) { + m_bLoopOutPressedWhileLoopDisabled(false), + m_trueTrackBeats(false) { m_oldLoopInfo = {mixxx::audio::kInvalidFramePos, mixxx::audio::kInvalidFramePos, LoopSeekMode::MovedOut}; @@ -417,7 +418,7 @@ mixxx::audio::FramePos LoopingControl::nextTrigger(bool reverse, // When the LoopIn button is released in reverse mode we jump to the end of the loop to not fall out and disable the active loop // This must not happen in quantized mode. The newly set start is always ahead (in time, but behind spacially) of the current position so we don't jump. // Jumping to the end is then handled when the loop's start is reached later in this function. - if (reverse && !m_bAdjustingLoopIn && !m_pQuantizeEnabled->toBool()) { + if (reverse && !m_bAdjustingLoopIn && !(quantizeEnabledAndHasTrueTrackBeats())) { m_oldLoopInfo = loopInfo; *pTargetPosition = loopInfo.endPosition; return currentPosition; @@ -431,7 +432,8 @@ mixxx::audio::FramePos LoopingControl::nextTrigger(bool reverse, // When the LoopOut button is released in forward mode we jump to the start of the loop to not fall out and disable the active loop // This must not happen in quantized mode. The newly set end is always ahead of the current position so we don't jump. // Jumping to the start is then handled when the loop's end is reached later in this function. - if (!reverse && !m_bAdjustingLoopOut && !m_pQuantizeEnabled->toBool()) { + if (!reverse && !m_bAdjustingLoopOut && + !(quantizeEnabledAndHasTrueTrackBeats())) { m_oldLoopInfo = loopInfo; *pTargetPosition = loopInfo.startPosition; return currentPosition; @@ -696,7 +698,7 @@ void LoopingControl::setLoopInToCurrentPosition() { // silence of the last buffer. This position might be not reachable in // a future runs, depending on the buffering. mixxx::audio::FramePos position = math_min(info.currentPosition, info.trackEndPosition); - if (m_pQuantizeEnabled->toBool() && pBeats) { + if (quantizeEnabledAndHasTrueTrackBeats()) { mixxx::audio::FramePos prevBeatPosition; mixxx::audio::FramePos nextBeatPosition; if (pBeats->findPrevNextBeats(position, &prevBeatPosition, &nextBeatPosition, false)) { @@ -761,9 +763,10 @@ void LoopingControl::setLoopInToCurrentPosition() { loopInfo.seekMode = LoopSeekMode::MovedOut; } - if (m_pQuantizeEnabled->toBool() && loopInfo.startPosition.isValid() && + if (quantizeEnabledAndHasTrueTrackBeats() && + loopInfo.startPosition.isValid() && loopInfo.endPosition.isValid() && - loopInfo.startPosition < loopInfo.endPosition && pBeats) { + loopInfo.startPosition < loopInfo.endPosition) { m_pCOBeatLoopSize->setAndConfirm(pBeats->numBeatsInRange( loopInfo.startPosition, loopInfo.endPosition)); updateBeatLoopingControls(); @@ -854,7 +857,7 @@ void LoopingControl::setLoopOutToCurrentPosition() { // silence of the last buffer. This position might be not reachable in // a future runs, depending on the buffering. mixxx::audio::FramePos position = math_min(info.currentPosition, info.trackEndPosition); - if (m_pQuantizeEnabled->toBool() && pBeats) { + if (quantizeEnabledAndHasTrueTrackBeats()) { mixxx::audio::FramePos prevBeatPosition; mixxx::audio::FramePos nextBeatPosition; if (pBeats->findPrevNextBeats(position, &prevBeatPosition, &nextBeatPosition, false)) { @@ -930,7 +933,7 @@ void LoopingControl::setLoopOutToCurrentPosition() { loopInfo.seekMode = LoopSeekMode::MovedOut; } - if (m_pQuantizeEnabled->toBool() && pBeats) { + if (quantizeEnabledAndHasTrueTrackBeats()) { m_pCOBeatLoopSize->setAndConfirm(pBeats->numBeatsInRange( loopInfo.startPosition, loopInfo.endPosition)); updateBeatLoopingControls(); @@ -1230,8 +1233,10 @@ void LoopingControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) { clearActiveBeatLoop(); if (pBeats) { m_pBeats = pBeats; + m_trueTrackBeats = true; } else { m_pBeats = getFake60BpmBeats(); + m_trueTrackBeats = false; } // TODO All "if (m_pBeats)" checks are now obsolete actually... LoopInfo loopInfo = m_loopInfo.getValue(); @@ -1327,6 +1332,10 @@ bool LoopingControl::currentLoopMatchesBeatloopSize(const LoopInfo& loopInfo) co return positionNear(loopInfo.endPosition, loopEndPosition); } +bool LoopingControl::quantizeEnabledAndHasTrueTrackBeats() const { + return m_pQuantizeEnabled->toBool() && m_trueTrackBeats; +} + double LoopingControl::findBeatloopSizeForLoop( mixxx::audio::FramePos startPosition, mixxx::audio::FramePos endPosition) const { @@ -1472,12 +1481,12 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable currentPosition = pBeats->findNBeatsFromPosition(currentPosition, -beats); } - if (!m_pQuantizeEnabled->toBool()) { - newloopInfo.startPosition = currentPosition; - } else { + if (quantizeEnabledAndHasTrueTrackBeats()) { // loop_in is set to the closest beat if quantize is on and the loop size is >= 1 beat. // The closest beat might be ahead of play position and will cause a catching loop. newloopInfo.startPosition = findQuantizedBeatloopStart(pBeats, currentPosition, beats); + } else { + newloopInfo.startPosition = currentPosition; } } diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h index 3df3650bdba..082b2687364 100644 --- a/src/engine/controls/loopingcontrol.h +++ b/src/engine/controls/loopingcontrol.h @@ -149,6 +149,7 @@ class LoopingControl : public EngineControl { void clearActiveBeatLoop(); void updateBeatLoopingControls(); bool currentLoopMatchesBeatloopSize(const LoopInfo& loopInfo) const; + bool quantizeEnabledAndHasTrueTrackBeats() const; // Fake beats that allow using looping/beatjump controls with no beats: // one 'beat' = one second @@ -237,6 +238,9 @@ class LoopingControl : public EngineControl { // objects below are written from an engine worker thread TrackPointer m_pTrack; mixxx::BeatsPointer m_pBeats; + // Flag that allows to act quantized only if we have true track beats. + // See quantizeEnabledAndHasTrueTrackBeats() + bool m_trueTrackBeats; friend class LoopingControlTest; }; From e7ffb5bdd8abe58a0264ef5163db7a39f7136a77 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 28 May 2024 00:25:29 +0200 Subject: [PATCH 5/6] adjust looping/seconds tooltip and control description --- src/controllers/controlpickermenu.cpp | 3 ++- src/skin/legacy/tooltips.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/controllers/controlpickermenu.cpp b/src/controllers/controlpickermenu.cpp index 00fac2bda16..1c5de6e2ef5 100644 --- a/src/controllers/controlpickermenu.cpp +++ b/src/controllers/controlpickermenu.cpp @@ -639,7 +639,8 @@ ControlPickerMenu::ControlPickerMenu(QWidget* pParent) QMenu* pLoopMenu = addSubmenu(tr("Looping")); // add beatloop_activate and beatlooproll_activate to both the // Loop and Beat-Loop menus to make sure users can find them. - QString noBeatsSeconds = tr(" (if track has no beats the unit is seconds)"); + QString noBeatsSeconds = QChar('(') + + tr("if the track has no beats the unit is seconds") + QChar(')'); QString beatloopActivateTitle = tr("Loop Selected Beats"); QString beatloopActivateDescription = tr("Create a beat loop of selected beat size") + noBeatsSeconds; diff --git a/src/skin/legacy/tooltips.cpp b/src/skin/legacy/tooltips.cpp index 5685f2b232a..14002d3c40d 100644 --- a/src/skin/legacy/tooltips.cpp +++ b/src/skin/legacy/tooltips.cpp @@ -786,7 +786,7 @@ void Tooltips::addStandardTooltips() { << tr("Loop Double") << tr("Doubles the current loop's length by moving the end marker."); - QString noBeatsSeconds = tr(" If track has no beats the unit is seconds."); + QString noBeatsSeconds = tr("If the track has no beats the unit is seconds."); add("beatloop_size") << tr("Beatloop Size") @@ -804,6 +804,7 @@ void Tooltips::addStandardTooltips() { add("beatloop_activate") << tr("Beatloop") << QString("%1: %2").arg(leftClick, tr("Start a loop over the set number of beats.")) + << noBeatsSeconds << quantizeSnap << QString("%1: %2").arg(rightClick, tr("Temporarily enable a rolling loop over the set number of beats.")) << tr("Playback will resume where the track would have been if it had not entered the loop."); From 761c7d6cde0bd1893981fddbda3e54c5f52ff1d0 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 28 May 2024 01:00:01 +0200 Subject: [PATCH 6/6] Looping: revert to beats nullptr if no track is loaded --- src/engine/controls/loopingcontrol.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 7d4d50a51e1..3213f58de3f 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -1255,11 +1255,15 @@ void LoopingControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) { if (pBeats) { m_pBeats = pBeats; m_trueTrackBeats = true; - } else { + } else if (m_pTrack) { + // no beats, use fake beats so we can use seconds as beat unit m_pBeats = getFake60BpmBeats(); m_trueTrackBeats = false; + } else { + // no track, no beats + m_pBeats = pBeats; + m_trueTrackBeats = false; } - // TODO All "if (m_pBeats)" checks are now obsolete actually... LoopInfo loopInfo = m_loopInfo.getValue(); if (loopInfo.startPosition.isValid() && loopInfo.endPosition.isValid()) { double loaded_loop_size = findBeatloopSizeForLoop(