Skip to content

Commit

Permalink
Merge pull request #11435 from ronso0/slip-loop
Browse files Browse the repository at this point in the history
Slip mode: consider active (regular) loop
  • Loading branch information
daschuer authored May 15, 2023
2 parents 79acc08 + 0ea8a93 commit c4090e4
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 36 deletions.
133 changes: 102 additions & 31 deletions src/engine/controls/loopingcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -378,8 +377,10 @@ 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,
loopInfo.startPosition,
loopInfo.endPosition);
if (targetPosition.isValid()) {
Expand Down Expand Up @@ -445,8 +446,10 @@ 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,
loopInfo.startPosition,
loopInfo.endPosition);
break;
Expand All @@ -463,8 +466,10 @@ mixxx::audio::FramePos LoopingControl::nextTrigger(bool reverse,
}
}
if (movedOut) {
*pTargetPosition = seekInsideAdjustedLoop(currentPosition,
*pTargetPosition = adjustedPositionInsideAdjustedLoop(currentPosition,
reverse,
loopInfo.startPosition,
loopInfo.endPosition,
loopInfo.startPosition,
loopInfo.endPosition);
}
Expand Down Expand Up @@ -1059,22 +1064,38 @@ 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) {
// 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) {
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.
// 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);
}
}
Expand Down Expand Up @@ -1103,6 +1124,10 @@ bool LoopingControl::isLoopingEnabled() {
return m_bLoopingEnabled;
}

bool LoopingControl::isLoopRollActive() {
return m_bLoopRollActive;
}

void LoopingControl::trackLoaded(TrackPointer pNewTrack) {
m_pTrack = pNewTrack;
mixxx::BeatsPointer pBeats;
Expand Down Expand Up @@ -1347,10 +1372,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);
}
Expand Down Expand Up @@ -1587,22 +1613,63 @@ void LoopingControl::slotLoopMove(double beats) {
}
}

// Must be called from the engine thread only
mixxx::audio::FramePos LoopingControl::seekInsideAdjustedLoop(
// 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,
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;
Expand All @@ -1619,8 +1686,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: adjustedPositionInsideAdjustedLoop "
"couldn't find a new position --"
<< " seeking to in point";
adjustedPosition = newLoopStartPosition;
}
} else if (adjustedPosition < newLoopStartPosition) {
Expand All @@ -1632,8 +1701,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: adjustedPositionInsideAdjustedLoop "
"couldn't find a new position --"
<< " seeking to in point";
adjustedPosition = newLoopStartPosition;
}
}
Expand Down
13 changes: 11 additions & 2 deletions src/engine/controls/loopingcontrol.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -130,10 +137,12 @@ 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,
mixxx::audio::FramePos oldLoopOutPosition,
mixxx::audio::FramePos newLoopInPosition,
mixxx::audio::FramePos newLoopOutPosition);
mixxx::audio::FramePos findQuantizedBeatloopStart(
Expand Down
17 changes: 14 additions & 3 deletions src/engine/enginebuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<mixxx::audio::FrameDiff_t>(bufferFrameCount) * m_dSlipRate;
const mixxx::audio::FrameDiff_t slipDelta =
static_cast<mixxx::audio::FrameDiff_t>(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;
}
}
}

Expand Down

0 comments on commit c4090e4

Please sign in to comment.