diff --git a/src/engine/bpmcontrol.cpp b/src/engine/bpmcontrol.cpp index 82fd990d8724..db4c6a062888 100644 --- a/src/engine/bpmcontrol.cpp +++ b/src/engine/bpmcontrol.cpp @@ -400,7 +400,7 @@ double BpmControl::calcSyncedRate(double userTweak) { // Now that we have our beat distance we can also check how large the // current loop is. If we are in a <1 beat loop, don't worry about offset. - const bool loop_enabled = m_pLoopEnabled->get() > 0.0; + const bool loop_enabled = m_pLoopEnabled->toBool(); const double loop_size = (m_pLoopEndPosition->get() - m_pLoopStartPosition->get()) / dBeatLength; @@ -567,14 +567,14 @@ bool BpmControl::getBeatContextNoLookup( return true; } -double BpmControl::getPhaseOffset(double dThisPosition) { +double BpmControl::getNearestPositionInPhase(double dThisPosition, bool respectLoops) { // Without a beatgrid, we don't know the phase offset. if (!m_pBeats) { - return 0; + return dThisPosition; } // Master buffer is always in sync! if (getSyncMode() == SYNC_MASTER) { - return 0; + return dThisPosition; } // Get the current position of this deck. @@ -588,13 +588,13 @@ double BpmControl::getPhaseOffset(double dThisPosition) { if (!getBeatContext(m_pBeats, dThisPosition, &dThisPrevBeat, &dThisNextBeat, &dThisBeatLength, NULL)) { - return 0; + return dThisPosition; } } else { if (!getBeatContextNoLookup(dThisPosition, dThisPrevBeat, dThisNextBeat, &dThisBeatLength, NULL)) { - return 0; + return dThisPosition; } } @@ -606,7 +606,7 @@ double BpmControl::getPhaseOffset(double dThisPosition) { // If not, we have to figure it out EngineBuffer* pOtherEngineBuffer = pickSyncTarget(); if (pOtherEngineBuffer == NULL) { - return 0; + return dThisPosition; } TrackPointer otherTrack = pOtherEngineBuffer->getLoadedTrack(); @@ -614,7 +614,7 @@ double BpmControl::getPhaseOffset(double dThisPosition) { // If either track does not have beats, then we can't adjust the phase. if (!otherBeats) { - return 0; + return dThisPosition; } double dOtherLength = ControlObject::getControl( @@ -624,7 +624,7 @@ double BpmControl::getPhaseOffset(double dThisPosition) { if (!BpmControl::getBeatContext(otherBeats, dOtherPosition, NULL, NULL, NULL, &dOtherBeatFraction)) { - return 0.0; + return dThisPosition; } } @@ -658,51 +658,55 @@ double BpmControl::getPhaseOffset(double dThisPosition) { dNewPlaypos += dThisPrevBeat; } - // We might be seeking outside the loop. - const bool loop_enabled = m_pLoopEnabled->get() > 0.0; - const double loop_start_position = m_pLoopStartPosition->get(); - const double loop_end_position = m_pLoopEndPosition->get(); - - // Cases for sanity: - // - // CASE 1 - // Two identical 1-beat loops, out of phase by X samples. - // Other deck is at its loop start. - // This deck is half way through. We want to jump forward X samples to the loop end point. - // - // Two identical 1-beat loop, out of phase by X samples. - // Other deck is - - // If sync target is 50% through the beat, - // If we are at the loop end point and hit sync, jump forward X samples. - - - // TODO(rryan): Revise this with something that keeps a broader number of - // cases in sync. This at least prevents breaking out of the loop. - if (loop_enabled) { - const double loop_length = loop_end_position - loop_start_position; - if (loop_length <= 0.0) { - return false; - } - - // TODO(rryan): If loop_length is not a multiple of dThisBeatLength should - // we bail and not sync phase? - - // Syncing to after the loop end. - double end_delta = dNewPlaypos - loop_end_position; - if (end_delta > 0) { - int i = end_delta / loop_length; - dNewPlaypos = loop_start_position + end_delta - i * loop_length; - } - - // Syncing to before the loop beginning. - double start_delta = loop_start_position - dNewPlaypos; - if (start_delta > 0) { - int i = start_delta / loop_length; - dNewPlaypos = loop_end_position - start_delta + i * loop_length; + if (respectLoops) { + // We might be seeking outside the loop. + const bool loop_enabled = m_pLoopEnabled->toBool(); + const double loop_start_position = m_pLoopStartPosition->get(); + const double loop_end_position = m_pLoopEndPosition->get(); + + // Cases for sanity: + // + // CASE 1 + // Two identical 1-beat loops, out of phase by X samples. + // Other deck is at its loop start. + // This deck is half way through. We want to jump forward X samples to the loop end point. + // + // Two identical 1-beat loop, out of phase by X samples. + // Other deck is + + // If sync target is 50% through the beat, + // If we are at the loop end point and hit sync, jump forward X samples. + + + // TODO(rryan): Revise this with something that keeps a broader number of + // cases in sync. This at least prevents breaking out of the loop. + if (loop_enabled && + dThisPosition <= loop_end_position) { + const double loop_length = loop_end_position - loop_start_position; + const double end_delta = dNewPlaypos - loop_end_position; + + // Syncing to after the loop end. + if (end_delta > 0 && loop_length > 0.0) { + int i = end_delta / loop_length; + dNewPlaypos = loop_start_position + end_delta - i * loop_length; + + // Move new position after loop jump into phase as well. + // This is a recursive call, called only twice because of + // respectLoops = false + dNewPlaypos = getNearestPositionInPhase(dNewPlaypos, false); + } + + // Note: Syncing to before the loop beginning is allowed, because + // loops are catching } } + return dNewPlaypos; +} + +double BpmControl::getPhaseOffset(double dThisPosition) { + // This does not respect looping + double dNewPlaypos = getNearestPositionInPhase(dThisPosition, false); return dNewPlaypos - dThisPosition; } diff --git a/src/engine/bpmcontrol.h b/src/engine/bpmcontrol.h index adc3978fd32b..fc4bdfa077a2 100644 --- a/src/engine/bpmcontrol.h +++ b/src/engine/bpmcontrol.h @@ -33,7 +33,8 @@ class BpmControl : public EngineControl { // out of sync. double calcSyncedRate(double userTweak); // Get the phase offset from the specified position. - double getPhaseOffset(double reference_position); + double getNearestPositionInPhase(double dThisPosition, bool respectLoops = true); + double getPhaseOffset(double dThisPosition); double getBeatDistance(double dThisPosition) const; double getPreviousSample() const { return m_dPreviousSample; } diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 88bd1dc5dfc5..39de9556b8e9 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -1165,7 +1165,7 @@ void EngineBuffer::processSeek(bool paused) { } if ((seekType & SEEK_PHASE) && !paused && m_pQuantize->toBool()) { - position += m_pBpmControl->getPhaseOffset(position); + position = m_pBpmControl->getNearestPositionInPhase(position); } double newPlayFrame = position / kSamplesPerFrame; diff --git a/src/engine/loopingcontrol.cpp b/src/engine/loopingcontrol.cpp index 790126fd5dc0..af431d914b0a 100644 --- a/src/engine/loopingcontrol.cpp +++ b/src/engine/loopingcontrol.cpp @@ -739,7 +739,9 @@ void LoopingControl::slotLoopEndPos(double pos) { void LoopingControl::notifySeek(double dNewPlaypos) { LoopSamples loopSamples = m_loopSamples.getValue(); if (m_bLoopingEnabled) { - if (dNewPlaypos < loopSamples.start || dNewPlaypos > loopSamples.end) { + // Disable loop when we jump after it, using hot cues or waveform overview + // If we jump before, the loop it is kept enabled as catching loop + if (dNewPlaypos > loopSamples.end) { setLoopingEnabled(false); } }