From 1ffbbfba801ca036734cb52466f8a187a4792d9a Mon Sep 17 00:00:00 2001 From: ronso0 Date: Wed, 14 May 2025 01:31:17 +0200 Subject: [PATCH] Looping: press 'beatloop_activate' while a looproll is active to adopt the loop and quit slip mode (without seeking) --- src/engine/controls/loopingcontrol.cpp | 51 +++++++++++++++++++------- src/engine/enginebuffer.cpp | 14 ++++++- src/engine/enginebuffer.h | 3 ++ 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index cb1c7436e3c4..f5a099790c70 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -1309,6 +1309,15 @@ void LoopingControl::slotBeatLoopDeactivate(BeatLoopingControl* pBeatLoopControl void LoopingControl::slotBeatLoopDeactivateRoll(BeatLoopingControl* pBeatLoopControl) { pBeatLoopControl->deactivate(); + + if (!m_bLoopRollActive) { + // beatloop_activate was pressed while rolling and slotBeatLoopToggle() + // did already reset roll status (m_activeLoopRolls, m_bLoopRollActive) + // and EngineBuffer quit slip mode (but didn't seek). + // So nothing to do here, just leave the adopted loop active. + return; + } + const double size = pBeatLoopControl->getSize(); // clang-tidy wants auto to be auto* because QStack inherits from QVector // and QVector::iterator is a pointer type in Qt5, but QStack inherits @@ -1325,18 +1334,15 @@ void LoopingControl::slotBeatLoopDeactivateRoll(BeatLoopingControl* pBeatLoopCon // Make sure slip mode is not turned off if it was turned on // by something that was not a rolling beatloop. - if (m_bLoopRollActive && m_activeLoopRolls.empty()) { + if (m_activeLoopRolls.empty()) { setLoopingEnabled(false); m_pSlipEnabled->set(0); m_bLoopRollActive = false; - } - - // Return to the previous beatlooproll if necessary. - // Else previous regular beatloop if no rolling loops are active. - if (!m_activeLoopRolls.empty()) { - slotBeatLoop(m_activeLoopRolls.top(), m_bLoopRollActive, true); - } else { restoreLoopInfo(); + } else { + // Return to the previous beatlooproll if necessary. + // Else previous regular beatloop if no rolling loops are active. + slotBeatLoop(m_activeLoopRolls.top(), m_bLoopRollActive, true); } } @@ -1694,14 +1700,25 @@ void LoopingControl::slotBeatLoopSizeChangeRequest(double beats) { } void LoopingControl::slotBeatLoopToggle(double pressed) { - if (pressed > 0) { - if (m_bLoopingEnabled) { + if (pressed <= 0) { + return; + } + + if (m_bLoopingEnabled) { + // If we're in a rolling loop, quit slip mode and adopt it as regular loop. + // Use case is to have a looproll button pressed, then press loop_activate + // and nothing should happen when releasing the looproll button. + if (m_bLoopRollActive) { + m_bLoopRollActive = false; + m_activeLoopRolls.clear(); + getEngineBuffer()->slipQuitAndAdopt(); + } else { // Deactivate the loop if we're already looping setLoopingEnabled(false); - } else { - // Create a loop at current position - slotBeatLoop(m_pCOBeatLoopSize->get()); } + } else { + // Create a loop at current position + slotBeatLoop(m_pCOBeatLoopSize->get()); } } @@ -1723,6 +1740,14 @@ void LoopingControl::slotBeatLoopRollActivate(double pressed) { m_bLoopRollActive = true; } } else { + if (!m_bLoopRollActive) { + // beatloop_activate was pressed while rolling and slotBeatLoopToggle() + // did already reset roll status (m_activeLoopRolls, m_bLoopRollActive) + // and EngineBuffer quit slip mode (but didn't seek). + // So nothing to do here, just leave the adopted loop active. + return; + } + setLoopingEnabled(false); // Make sure slip mode is not turned off if it was turned on // by something that was not a rolling beatloop. diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 9461197e4d5c..103a03b8eada 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -90,6 +90,7 @@ EngineBuffer::EngineBuffer(const QString& group, m_iSeekPhaseQueued(0), m_iEnableSyncQueued(SYNC_REQUEST_NONE), m_iSyncModeQueued(static_cast(SyncMode::Invalid)), + m_slipQuitAndAdopt(0), m_bPlayAfterLoading(false), m_channelCount(mixxx::kEngineChannelOutputCount), m_pCrossfadeBuffer(SampleUtil::alloc( @@ -864,6 +865,11 @@ void EngineBuffer::slotKeylockEngineChanged(double dIndex) { } } +void EngineBuffer::slipQuitAndAdopt() { + m_slipQuitAndAdopt.storeRelease(1); + m_pSlipButton->set(0); +} + void EngineBuffer::processTrackLocked( CSAMPLE* pOutput, const std::size_t bufferSize, mixxx::audio::SampleRate sampleRate) { ScopedTimer t(QStringLiteral("EngineBuffer::process_pauselock")); @@ -1257,8 +1263,12 @@ void EngineBuffer::processSlip(std::size_t bufferSize) { m_slipPos = m_playPos; m_dSlipRate = m_rate_old; } else { - // TODO(owen) assuming that looping will get canceled properly - seekExact(m_slipPos.toNearestFrameBoundary()); + // If m_slipQuitAndAdopt is 1 we've already quit slip mode + // but we don't seek in that case. + if (m_slipQuitAndAdopt.fetchAndStoreAcquire(0) == 0) { + // TODO(owen) assuming that looping will get canceled properly + seekExact(m_slipPos.toNearestFrameBoundary()); + } m_slipPos = mixxx::audio::kStartFramePos; } } diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h index 9d4480609a15..046b2ff8dec5 100644 --- a/src/engine/enginebuffer.h +++ b/src/engine/enginebuffer.h @@ -236,6 +236,8 @@ class EngineBuffer : public EngineObject { void verifyPlay(); + void slipQuitAndAdopt(); + public slots: void slotControlPlayRequest(double); void slotControlPlayFromStart(double); @@ -466,6 +468,7 @@ class EngineBuffer : public EngineObject { ControlValueAtomic m_queuedSeek; bool m_previousBufferSeek = false; + QAtomicInt m_slipQuitAndAdopt; /// Indicates that no seek is queued static constexpr QueuedSeek kNoQueuedSeek = {mixxx::audio::kInvalidFramePos, SEEK_NONE}; /// indicates a clone seek on a bosition from another deck