From 9eb2773a0b8167e0a1c62fa1712af3216d1a032f Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sun, 14 May 2023 23:51:32 +0200 Subject: [PATCH 1/5] LoopingControl: assert RateControl pointer --- src/engine/controls/loopingcontrol.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index de52d76b44e..c28800ad05d 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -1062,6 +1062,11 @@ void LoopingControl::slotLoopEndPos(double positionSamples) { void LoopingControl::notifySeek(mixxx::audio::FramePos position) { LoopInfo loopInfo = m_loopInfo.getValue(); const auto currentPosition = m_currentPosition.getValue(); + VERIFY_OR_DEBUG_ASSERT(m_pRateControl) { + qWarning() << "LoopingControl: RateControl not set!"; + return; + } + bool reverse = m_pRateControl->isReverseButtonPressed(); if (m_bLoopingEnabled) { // Disable loop when we jumping out, or over a catching loop, // using hot cues or waveform overview. @@ -1347,10 +1352,11 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable } else { // If running reverse, move the loop one loop size to the left. // Thus, the loops end will be closest to the current position - bool reverse = false; - if (m_pRateControl != nullptr) { - reverse = m_pRateControl->isReverseButtonPressed(); + VERIFY_OR_DEBUG_ASSERT(m_pRateControl) { + qWarning() << "LoopingControl: RateControl not set!"; + return; } + bool reverse = m_pRateControl->isReverseButtonPressed(); if (reverse) { currentPosition = pBeats->findNBeatsFromPosition(currentPosition, -beats); } From 0c035cf9c9b5f0d95fdb3e5d5ed07b77036e219a Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sun, 14 May 2023 23:58:39 +0200 Subject: [PATCH 2/5] LoopingControl: make more functions consider reverse (playing backwards) --- src/engine/controls/loopingcontrol.cpp | 80 ++++++++++++++++++-------- src/engine/controls/loopingcontrol.h | 2 + 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index c28800ad05d..44679539e25 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -358,7 +358,6 @@ void LoopingControl::process(const double dRate, mixxx::audio::FramePos currentPosition, const int iBufferSize) { Q_UNUSED(iBufferSize); - Q_UNUSED(dRate); const auto previousPosition = m_currentPosition.getValue(); @@ -379,7 +378,9 @@ void LoopingControl::process(const double dRate, // should be moved with it const auto targetPosition = seekInsideAdjustedLoop(currentPosition, + dRate < 0, // reverse m_oldLoopInfo.startPosition, + m_oldLoopInfo.endPosition, loopInfo.startPosition, loopInfo.endPosition); if (targetPosition.isValid()) { @@ -446,7 +447,9 @@ mixxx::audio::FramePos LoopingControl::nextTrigger(bool reverse, // here the loop has changed and the play position // should be moved with it *pTargetPosition = seekInsideAdjustedLoop(currentPosition, + reverse, m_oldLoopInfo.startPosition, + m_oldLoopInfo.endPosition, loopInfo.startPosition, loopInfo.endPosition); break; @@ -464,7 +467,9 @@ mixxx::audio::FramePos LoopingControl::nextTrigger(bool reverse, } if (movedOut) { *pTargetPosition = seekInsideAdjustedLoop(currentPosition, + reverse, loopInfo.startPosition, + loopInfo.endPosition, loopInfo.startPosition, loopInfo.endPosition); } @@ -1059,7 +1064,7 @@ void LoopingControl::slotLoopEndPos(double positionSamples) { } // This is called from the engine thread -void LoopingControl::notifySeek(mixxx::audio::FramePos position) { +void LoopingControl::notifySeek(mixxx::audio::FramePos newPosition) { LoopInfo loopInfo = m_loopInfo.getValue(); const auto currentPosition = m_currentPosition.getValue(); VERIFY_OR_DEBUG_ASSERT(m_pRateControl) { @@ -1072,14 +1077,19 @@ void LoopingControl::notifySeek(mixxx::audio::FramePos position) { // using hot cues or waveform overview. // Jumping to the exact end of a loop is considered jumping out. if (currentPosition >= loopInfo.startPosition && - currentPosition <= loopInfo.endPosition && - position < loopInfo.startPosition) { - // jumping out of loop in backwards - setLoopingEnabled(false); + currentPosition <= loopInfo.endPosition) { + if ((reverse && newPosition > loopInfo.endPosition) || + (!reverse && newPosition < loopInfo.startPosition)) { + // jumping out of loop in "backwards" + setLoopingEnabled(false); + } } - if (currentPosition <= loopInfo.endPosition && - position >= loopInfo.endPosition) { - // jumping out or to the exact end of a loop or over a catching loop forward + if ((reverse && currentPosition >= loopInfo.startPosition && + newPosition <= loopInfo.startPosition) || + (!reverse && currentPosition <= loopInfo.endPosition && + newPosition >= loopInfo.endPosition)) { + // jumping out or to the exact "end" of a loop or + // over a catching loop "forward" setLoopingEnabled(false); } } @@ -1596,19 +1606,39 @@ void LoopingControl::slotLoopMove(double beats) { // Must be called from the engine thread only mixxx::audio::FramePos LoopingControl::seekInsideAdjustedLoop( mixxx::audio::FramePos currentPosition, + bool reverse, mixxx::audio::FramePos oldLoopStartPosition, + mixxx::audio::FramePos oldLoopEndPosition, mixxx::audio::FramePos newLoopStartPosition, mixxx::audio::FramePos newLoopEndPosition) { - if (currentPosition >= newLoopStartPosition && currentPosition <= newLoopEndPosition) { - // playposition already is inside the loop - return mixxx::audio::kInvalidFramePos; - } - if (oldLoopStartPosition.isValid() && - currentPosition < oldLoopStartPosition && - currentPosition <= newLoopEndPosition) { - // Playposition was before a catching loop and is still a catching loop - // nothing to do - return mixxx::audio::kInvalidFramePos; + if (reverse) { + if (currentPosition <= newLoopEndPosition && currentPosition > newLoopStartPosition) { + // playposition already is inside the loop + return mixxx::audio::kInvalidFramePos; + } + if (oldLoopEndPosition.isValid() && + currentPosition > oldLoopEndPosition && + currentPosition > newLoopStartPosition) { + // Playposition was after) a catching loop and is still + // a catching loop. nothing to do + return mixxx::audio::kInvalidFramePos; + } + if (currentPosition == newLoopStartPosition) { + // wrap around since the "end" is considered outside the loop + return newLoopEndPosition; + } + } else { + if (currentPosition >= newLoopStartPosition && currentPosition < newLoopEndPosition) { + return mixxx::audio::kInvalidFramePos; + } + if (oldLoopStartPosition.isValid() && + currentPosition < oldLoopStartPosition && + currentPosition < newLoopEndPosition) { + return mixxx::audio::kInvalidFramePos; + } + if (currentPosition == newLoopEndPosition) { + return newLoopStartPosition; + } } const mixxx::audio::FrameDiff_t newLoopSize = newLoopEndPosition - newLoopStartPosition; @@ -1625,8 +1655,10 @@ mixxx::audio::FramePos LoopingControl::seekInsideAdjustedLoop( VERIFY_OR_DEBUG_ASSERT(adjustedPosition > newLoopStartPosition) { // I'm not even sure this is possible. The new loop would have to be bigger than the // old loop, and the playhead was somehow outside the old loop. - qWarning() << "SHOULDN'T HAPPEN: seekInsideAdjustedLoop couldn't find a new position --" - << " seeking to in point"; + qWarning() + << "SHOULDN'T HAPPEN: seekInsideAdjustedLoop " + "couldn't find a new position --" + << " seeking to in point"; adjustedPosition = newLoopStartPosition; } } else if (adjustedPosition < newLoopStartPosition) { @@ -1638,8 +1670,10 @@ mixxx::audio::FramePos LoopingControl::seekInsideAdjustedLoop( adjustedPosition += adjustSteps * newLoopSize; DEBUG_ASSERT(adjustedPosition >= newLoopStartPosition); VERIFY_OR_DEBUG_ASSERT(adjustedPosition < newLoopEndPosition) { - qWarning() << "SHOULDN'T HAPPEN: seekInsideAdjustedLoop couldn't find a new position --" - << " seeking to in point"; + qWarning() + << "SHOULDN'T HAPPEN: seekInsideAdjustedLoop " + "couldn't find a new position --" + << " seeking to in point"; adjustedPosition = newLoopStartPosition; } } diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h index 7aa7cebf8a0..f26367b604c 100644 --- a/src/engine/controls/loopingcontrol.h +++ b/src/engine/controls/loopingcontrol.h @@ -133,7 +133,9 @@ class LoopingControl : public EngineControl { // the beat. It will even keep multi-bar phrasing correct with 4/4 tracks. mixxx::audio::FramePos seekInsideAdjustedLoop( mixxx::audio::FramePos currentPosition, + bool reverse, mixxx::audio::FramePos oldLoopInPosition, + mixxx::audio::FramePos oldLoopOutPosition, mixxx::audio::FramePos newLoopInPosition, mixxx::audio::FramePos newLoopOutPosition); mixxx::audio::FramePos findQuantizedBeatloopStart( From b5f7e8985af588338b2ec1481a6e783d95385335 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 15 May 2023 00:22:40 +0200 Subject: [PATCH 3/5] LoopingControl: seekInsideAdjustedLoop >> adjustedPositionInsideAdjustedLoop --- src/engine/controls/loopingcontrol.cpp | 13 ++++++------- src/engine/controls/loopingcontrol.h | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 44679539e25..cb90abbba62 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -377,7 +377,7 @@ void LoopingControl::process(const double dRate, // here the loop has changed and the play position // should be moved with it const auto targetPosition = - seekInsideAdjustedLoop(currentPosition, + adjustedPositionInsideAdjustedLoop(currentPosition, dRate < 0, // reverse m_oldLoopInfo.startPosition, m_oldLoopInfo.endPosition, @@ -446,7 +446,7 @@ mixxx::audio::FramePos LoopingControl::nextTrigger(bool reverse, case LoopSeekMode::Changed: // here the loop has changed and the play position // should be moved with it - *pTargetPosition = seekInsideAdjustedLoop(currentPosition, + *pTargetPosition = adjustedPositionInsideAdjustedLoop(currentPosition, reverse, m_oldLoopInfo.startPosition, m_oldLoopInfo.endPosition, @@ -466,7 +466,7 @@ mixxx::audio::FramePos LoopingControl::nextTrigger(bool reverse, } } if (movedOut) { - *pTargetPosition = seekInsideAdjustedLoop(currentPosition, + *pTargetPosition = adjustedPositionInsideAdjustedLoop(currentPosition, reverse, loopInfo.startPosition, loopInfo.endPosition, @@ -1603,8 +1603,7 @@ void LoopingControl::slotLoopMove(double beats) { } } -// Must be called from the engine thread only -mixxx::audio::FramePos LoopingControl::seekInsideAdjustedLoop( +mixxx::audio::FramePos LoopingControl::adjustedPositionInsideAdjustedLoop( mixxx::audio::FramePos currentPosition, bool reverse, mixxx::audio::FramePos oldLoopStartPosition, @@ -1656,7 +1655,7 @@ mixxx::audio::FramePos LoopingControl::seekInsideAdjustedLoop( // I'm not even sure this is possible. The new loop would have to be bigger than the // old loop, and the playhead was somehow outside the old loop. qWarning() - << "SHOULDN'T HAPPEN: seekInsideAdjustedLoop " + << "SHOULDN'T HAPPEN: adjustedPositionInsideAdjustedLoop " "couldn't find a new position --" << " seeking to in point"; adjustedPosition = newLoopStartPosition; @@ -1671,7 +1670,7 @@ mixxx::audio::FramePos LoopingControl::seekInsideAdjustedLoop( DEBUG_ASSERT(adjustedPosition >= newLoopStartPosition); VERIFY_OR_DEBUG_ASSERT(adjustedPosition < newLoopEndPosition) { qWarning() - << "SHOULDN'T HAPPEN: seekInsideAdjustedLoop " + << "SHOULDN'T HAPPEN: adjustedPositionInsideAdjustedLoop " "couldn't find a new position --" << " seeking to in point"; adjustedPosition = newLoopStartPosition; diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h index f26367b604c..6a333ecb1b2 100644 --- a/src/engine/controls/loopingcontrol.h +++ b/src/engine/controls/loopingcontrol.h @@ -130,8 +130,8 @@ class LoopingControl : public EngineControl { mixxx::audio::FramePos endPosition) const; // When a loop changes size such that the playposition is outside of the loop, // we can figure out the best place in the new loop to seek to maintain - // the beat. It will even keep multi-bar phrasing correct with 4/4 tracks. - mixxx::audio::FramePos seekInsideAdjustedLoop( + // the beat. It will even keep multi-bar phrasing correct with 4/4 tracks. + mixxx::audio::FramePos adjustedPositionInsideAdjustedLoop( mixxx::audio::FramePos currentPosition, bool reverse, mixxx::audio::FramePos oldLoopInPosition, From c5127eb4b4ff63b4331e6be4fde6f7a8e568bdf2 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 15 May 2023 00:25:25 +0200 Subject: [PATCH 4/5] Slip mode: simulate looping if there is an active regular loop --- src/engine/controls/loopingcontrol.cpp | 26 ++++++++++++++++++++++++++ src/engine/controls/loopingcontrol.h | 7 +++++++ src/engine/enginebuffer.cpp | 17 ++++++++++++++--- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index cb90abbba62..5f80b74676c 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -1118,6 +1118,10 @@ bool LoopingControl::isLoopingEnabled() { return m_bLoopingEnabled; } +bool LoopingControl::isLoopRollActive() { + return m_bLoopRollActive; +} + void LoopingControl::trackLoaded(TrackPointer pNewTrack) { m_pTrack = pNewTrack; mixxx::BeatsPointer pBeats; @@ -1603,6 +1607,28 @@ void LoopingControl::slotLoopMove(double beats) { } } +// Used to simulate looping while slip mode is enabled +mixxx::audio::FramePos LoopingControl::adjustedPositionForCurrentLoop( + mixxx::audio::FramePos currentPosition, + bool reverse) { + if (!m_bLoopingEnabled) { + return currentPosition; + } + LoopInfo loopInfo = m_loopInfo.getValue(); + const auto targetPosition = adjustedPositionInsideAdjustedLoop( + currentPosition, + reverse, + loopInfo.startPosition, + loopInfo.endPosition, + loopInfo.startPosition, + loopInfo.endPosition); + if (targetPosition.isValid()) { + return targetPosition; + } else { + return currentPosition; + } +} + mixxx::audio::FramePos LoopingControl::adjustedPositionInsideAdjustedLoop( mixxx::audio::FramePos currentPosition, bool reverse, diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h index 6a333ecb1b2..3f239e70aa9 100644 --- a/src/engine/controls/loopingcontrol.h +++ b/src/engine/controls/loopingcontrol.h @@ -48,12 +48,19 @@ class LoopingControl : public EngineControl { void notifySeek(mixxx::audio::FramePos position) override; + // Wrapper to use adjustedPositionInsideAdjustedLoop() with the current loop. + // Called from EngineBuffer while slip mode is enabled + mixxx::audio::FramePos adjustedPositionForCurrentLoop( + mixxx::audio::FramePos newPosition, + bool reverse); + void setBeatLoop(mixxx::audio::FramePos startPosition, bool enabled); void setLoop(mixxx::audio::FramePos startPosition, mixxx::audio::FramePos endPosition, bool enabled); void setRateControl(RateControl* rateControl); bool isLoopingEnabled(); + bool isLoopRollActive(); void trackLoaded(TrackPointer pNewTrack) override; void trackBeatsUpdated(mixxx::BeatsPointer pBeats) override; diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 94fe7cfa20a..380b625e46c 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -846,10 +846,10 @@ void EngineBuffer::processTrackLocked( bool is_scratching = false; bool is_reverse = false; - // Update the slipped position and seek if it was disabled. + // Update the slipped position and seek to it if slip mode was disabled. processSlip(iBufferSize); - // Note: This may effects the m_playPosition, play, scaler and crossfade buffer + // Note: This may affect the m_playPosition, play, scaler and crossfade buffer processSeek(paused); // speed is the ratio between track-time and real-time @@ -1207,7 +1207,18 @@ void EngineBuffer::processSlip(int iBufferSize) { // back and forth calculations. const int bufferFrameCount = iBufferSize / mixxx::kEngineChannelCount; DEBUG_ASSERT(bufferFrameCount * mixxx::kEngineChannelCount == iBufferSize); - m_slipPosition += static_cast(bufferFrameCount) * m_dSlipRate; + const mixxx::audio::FrameDiff_t slipDelta = + static_cast(bufferFrameCount) * m_dSlipRate; + // Simulate looping if a regular loop is active + if (m_pLoopingControl->isLoopingEnabled() && + !m_pLoopingControl->isLoopRollActive()) { + const mixxx::audio::FramePos newPos = m_slipPosition + slipDelta; + m_slipPosition = m_pLoopingControl->adjustedPositionForCurrentLoop( + newPos, + m_dSlipRate < 0); + } else { + m_slipPosition += slipDelta; + } } } From 0ea8a936c97bb883a6ed6cba4524324f0af47343 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 15 May 2023 00:25:49 +0200 Subject: [PATCH 5/5] Slip mode: keep current loop enabled when seeking outside of it --- src/engine/controls/loopingcontrol.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 5f80b74676c..695d2dca2c4 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -1065,6 +1065,12 @@ void LoopingControl::slotLoopEndPos(double positionSamples) { // This is called from the engine thread void LoopingControl::notifySeek(mixxx::audio::FramePos newPosition) { + // Leave loop alone if we're in slip mode and if it was turned on + // by something that was not a rolling beatloop. + if (m_pSlipEnabled->toBool() && !m_bLoopRollActive) { + return; + } + LoopInfo loopInfo = m_loopInfo.getValue(); const auto currentPosition = m_currentPosition.getValue(); VERIFY_OR_DEBUG_ASSERT(m_pRateControl) {