diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index b8c7603e27c..76fda97ddc2 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -7,6 +7,7 @@ #include "moc_cuecontrol.cpp" #include "preferences/colorpalettesettings.h" #include "track/track.h" +#include "util/assert.h" #include "util/color/predefinedcolorpalettes.h" #include "vinylcontrol/defs_vinylcontrol.h" @@ -98,7 +99,8 @@ CueControl::CueControl(const QString& group, m_bypassCueSetByPlay(false), m_iNumHotCues(NUM_HOT_CUES), m_pCurrentSavedLoopControl(nullptr), - m_trackMutex(QT_RECURSIVE_MUTEX_INIT) { + m_trackMutex(QT_RECURSIVE_MUTEX_INIT), + m_pBeats(nullptr) { // To silence a compiler warning about CUE_MODE_PIONEER. Q_UNUSED(CUE_MODE_PIONEER); @@ -139,7 +141,6 @@ CueControl::~CueControl() { void CueControl::process(const double rate, mixxx::audio::FramePos currentPosition, const std::size_t bufferSize) { - Q_UNUSED(rate); Q_UNUSED(bufferSize); for (const auto& pControl : std::as_const(m_hotcueControls)) { if (!pControl->getCue() || @@ -148,23 +149,107 @@ void CueControl::process(const double rate, !pControl->getEndPosition().isValid()) { continue; } - // Saved jumps store the position to jump from as their end position - if (pControl->getEndPosition() > m_lastProcessedPosition && + if (rate >= 0 + // Saved jumps store the position to jump from as their end position + && pControl->getEndPosition() > m_lastProcessedPosition && pControl->getEndPosition() <= currentPosition) { - auto delta = pControl->getEndPosition() - currentPosition; - seekAbs(pControl->getPosition() + delta); - if (pControl->getPosition() < pControl->getEndPosition()) { - // If the saved jump is backward, we make the cue idle so it - // prevent creating a fake loop - pControl->setStatus(HotcueControl::Status::Set); - } + jumpTo(currentPosition, pControl->getEndPosition(), pControl->getPosition()); + } else if (rate < 0 + // Saved jumps store the position to jump from as their end position + && pControl->getPosition() < m_lastProcessedPosition && + pControl->getPosition() >= currentPosition) { + jumpTo(currentPosition, pControl->getPosition(), pControl->getEndPosition()); + } else { + continue; + } + if (pControl->getPosition() < pControl->getEndPosition()) { + // If the saved jump is backward, we make the cue idle so it + // prevent creating a fake loop + pControl->setStatus(HotcueControl::Status::Set); } } m_lastProcessedPosition = currentPosition; } +void CueControl::jumpTo(mixxx::audio::FramePos currentPosition, + mixxx::audio::FramePos source, + mixxx::audio::FramePos target) { + if (m_pQuantizeEnabled->toBool()) { + VERIFY_OR_DEBUG_ASSERT(m_pBeats != nullptr) { + // FIXME early return or default to no quantizing? + return; + } + // Assuming the following unaligned jump + // |.....[|......|......|...]..|......| + // When quantize is enabled, the target is adjusted to match a beat rounded jump + // |.....[!......|......|.....]!......| + // The closest beat might be ahead of play position. + mixxx::audio::FramePos prevBeatPosition; + mixxx::audio::FramePos nextBeatPosition; + if (!m_pBeats->findPrevNextBeats(source, + &prevBeatPosition, + &nextBeatPosition, + false)) { + // FIXME early return or default to no quantizing? + return; + } + + const mixxx::audio::FramePos closestSourceBeatPosition = + (nextBeatPosition - source > + source - prevBeatPosition) + ? prevBeatPosition + : nextBeatPosition; + + if (!m_pBeats->findPrevNextBeats(target, + &prevBeatPosition, + &nextBeatPosition, + false)) { + // FIXME early return or default to no quantizing? + return; + } + + const mixxx::audio::FramePos closestTargetBeatPosition = + (nextBeatPosition - target > + target - prevBeatPosition) + ? prevBeatPosition + : nextBeatPosition; + + auto delta = closestSourceBeatPosition - currentPosition; + m_lastProcessedPosition = mixxx::audio::FramePos(); + m_jumpCueSeekRequest = closestTargetBeatPosition + delta; + seekAbs(m_jumpCueSeekRequest); + return; + } + auto delta = source - currentPosition; + m_jumpCueSeekRequest = target + delta; + seekAbs(m_jumpCueSeekRequest); +} + void CueControl::notifySeek(mixxx::audio::FramePos position) { m_lastProcessedPosition = position; + + qDebug() << m_jumpCueSeekRequest << position; + if (m_jumpCueSeekRequest == position) { + m_jumpCueSeekRequest = mixxx::audio::FramePos(); + return; + } + + for (const auto& pControl : std::as_const(m_hotcueControls)) { + if (!pControl->getCue() || + pControl->getStatus() != HotcueControl::Status::Active || + pControl->getCue()->getType() != mixxx::CueType::Jump || + !pControl->getEndPosition().isValid()) { + continue; + } + if ((pControl->getPosition() < pControl->getEndPosition() && + pControl->getPosition() <= position && + pControl->getEndPosition() > position) || + (pControl->getPosition() > pControl->getEndPosition() && + pControl->getPosition() > position && + pControl->getEndPosition() <= position)) { + pControl->setStatus(HotcueControl::Status::Set); + } + } } void CueControl::createControls() { @@ -542,6 +627,7 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { return; } m_pLoadedTrack = pNewTrack; + trackBeatsUpdated(pNewTrack->getBeats()); connect(m_pLoadedTrack.get(), &Track::analyzed, @@ -830,7 +916,7 @@ void CueControl::trackCuesUpdated() { } void CueControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) { - Q_UNUSED(pBeats); + m_pBeats = pBeats; loadCuesFromTrack(); } diff --git a/src/engine/controls/cuecontrol.h b/src/engine/controls/cuecontrol.h index a59464efdc1..52d29eabdac 100644 --- a/src/engine/controls/cuecontrol.h +++ b/src/engine/controls/cuecontrol.h @@ -298,6 +298,10 @@ class CueControl : public EngineControl { int getHotcueFocusIndex() const; mixxx::RgbColor colorFromConfig(const ConfigKey& configKey); + void jumpTo(mixxx::audio::FramePos currentPosition, + mixxx::audio::FramePos source, + mixxx::audio::FramePos target); + UserSettingsPointer m_pConfig; ColorPaletteSettings m_colorPaletteSettings; QAtomicInt m_currentlyPreviewingIndex; @@ -372,6 +376,7 @@ class CueControl : public EngineControl { QAtomicPointer m_pCurrentSavedJumpControl; mixxx::audio::FramePos m_lastProcessedPosition; + mixxx::audio::FramePos m_jumpCueSeekRequest; // Tells us which controls map to which hotcue QMap m_controlMap; @@ -380,5 +385,8 @@ class CueControl : public EngineControl { QT_RECURSIVE_MUTEX m_trackMutex; TrackPointer m_pLoadedTrack; // is written from an engine worker thread + // m_pBeats is written from an engine worker thread + mixxx::BeatsPointer m_pBeats; + friend class HotcueControlTest; };