diff --git a/src/engine/controls/bpmcontrol.cpp b/src/engine/controls/bpmcontrol.cpp index 0a7bbbc0155..2887525a044 100644 --- a/src/engine/controls/bpmcontrol.cpp +++ b/src/engine/controls/bpmcontrol.cpp @@ -208,7 +208,7 @@ void BpmControl::slotTranslateBeatsEarlier(double v) { } const mixxx::BeatsPointer pBeats = pTrack->getBeats(); if (pBeats) { - const double sampleOffset = getSampleOfTrack().rate * -0.01; + const double sampleOffset = frameInfo().sampleRate * -0.01; const mixxx::audio::FrameDiff_t frameOffset = sampleOffset / mixxx::kEngineChannelCount; pTrack->trySetBeats(pBeats->translate(frameOffset)); } @@ -224,8 +224,8 @@ void BpmControl::slotTranslateBeatsLater(double v) { } const mixxx::BeatsPointer pBeats = pTrack->getBeats(); if (pBeats) { - // TODO(rryan): Track::getSampleRate is possibly inaccurate! - const double sampleOffset = getSampleOfTrack().rate * 0.01; + // TODO(rryan): Track::frameInfo is possibly inaccurate! + const double sampleOffset = frameInfo().sampleRate * 0.01; const mixxx::audio::FrameDiff_t frameOffset = sampleOffset / mixxx::kEngineChannelCount; pTrack->trySetBeats(pBeats->translate(frameOffset)); } @@ -1040,10 +1040,7 @@ void BpmControl::slotBeatsTranslate(double v) { } const mixxx::BeatsPointer pBeats = pTrack->getBeats(); if (pBeats) { - const auto currentPosition = - mixxx::audio::FramePos::fromEngineSamplePos( - getSampleOfTrack().current) - .toLowerFrameBoundary(); + const auto currentPosition = frameInfo().currentPosition.toLowerFrameBoundary(); const auto closestBeat = pBeats->findClosestBeat(currentPosition); const mixxx::audio::FrameDiff_t delta = currentPosition - closestBeat; pTrack->trySetBeats(pBeats->translate(delta)); @@ -1064,7 +1061,7 @@ void BpmControl::slotBeatsTranslateMatchAlignment(double v) { // otherwise it will always return 0 if sync lock is active. m_dUserOffset.setValue(0.0); - double sampleOffset = getPhaseOffset(getSampleOfTrack().current); + double sampleOffset = getPhaseOffset(frameInfo().currentPosition.toEngineSamplePos()); const mixxx::audio::FrameDiff_t frameOffset = sampleOffset / mixxx::kEngineChannelCount; pTrack->trySetBeats(pBeats->translate(-frameOffset)); } @@ -1075,10 +1072,7 @@ mixxx::Bpm BpmControl::updateLocalBpm() { mixxx::Bpm localBpm; const mixxx::BeatsPointer pBeats = m_pBeats; if (pBeats) { - const auto currentPosition = - mixxx::audio::FramePos::fromEngineSamplePos( - getSampleOfTrack().current); - localBpm = pBeats->getBpmAroundPosition(currentPosition, kLocalBpmSpan); + localBpm = pBeats->getBpmAroundPosition(frameInfo().currentPosition, kLocalBpmSpan); if (!localBpm.isValid()) { localBpm = pBeats->getBpm(); } @@ -1099,7 +1093,7 @@ mixxx::Bpm BpmControl::updateLocalBpm() { } double BpmControl::updateBeatDistance() { - double beatDistance = getBeatDistance(getSampleOfTrack().current); + double beatDistance = getBeatDistance(frameInfo().currentPosition.toEngineSamplePos()); m_pThisBeatDistance->set(beatDistance); if (!isSynchronized() && m_dUserOffset.getValue() != 0.0) { m_dUserOffset.setValue(0.0); @@ -1134,8 +1128,8 @@ void BpmControl::resetSyncAdjustment() { void BpmControl::collectFeatures(GroupFeatureState* pGroupFeatures) const { // Without a beatgrid we don't know any beat details. - SampleOfTrack sot = getSampleOfTrack(); - if (sot.rate == 0 || !m_pBeats) { + FrameInfo info = frameInfo(); + if (!info.sampleRate.isValid() || !m_pBeats) { return; } @@ -1144,16 +1138,17 @@ void BpmControl::collectFeatures(GroupFeatureState* pGroupFeatures) const { double dThisNextBeat = m_pNextBeat->get(); double dThisBeatLength; double dThisBeatFraction; - if (getBeatContextNoLookup(sot.current, - dThisPrevBeat, dThisNextBeat, - &dThisBeatLength, &dThisBeatFraction)) { - + if (getBeatContextNoLookup(info.currentPosition.toEngineSamplePos(), + dThisPrevBeat, + dThisNextBeat, + &dThisBeatLength, + &dThisBeatFraction)) { // Note: dThisBeatLength is fractional frames count * 2 (stereo samples) - double sotPerSec = kSamplesPerFrame * sot.rate * m_pRateRatio->get(); - if (sotPerSec != 0.0) { - pGroupFeatures->beat_length_sec = dThisBeatLength / sotPerSec; + double samplesPerSecond = kSamplesPerFrame * info.sampleRate * m_pRateRatio->get(); + if (samplesPerSecond != 0.0) { + pGroupFeatures->beat_length_sec = dThisBeatLength / samplesPerSecond; pGroupFeatures->has_beat_length_sec = true; - } else { + } else { pGroupFeatures->has_beat_length_sec = false; } diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index ddab1e441a2..ee6472bfe38 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -528,7 +528,7 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { void CueControl::seekOnLoad(mixxx::audio::FramePos seekOnLoadPosition) { DEBUG_ASSERT(seekOnLoadPosition.isValid()); - seekExact(seekOnLoadPosition.toEngineSamplePos()); + seekExact(seekOnLoadPosition); m_usedSeekOnLoadPosition.setValue(seekOnLoadPosition.toEngineSamplePos()); } @@ -674,8 +674,7 @@ void CueControl::loadCuesFromTrack() { } void CueControl::trackAnalyzed() { - SampleOfTrack sampleOfTrack = getSampleOfTrack(); - if (sampleOfTrack.current != m_usedSeekOnLoadPosition.getValue()) { + if (frameInfo().currentPosition.toEngineSamplePos() != m_usedSeekOnLoadPosition.getValue()) { // the track is already manual cued, don't re-cue return; } @@ -736,14 +735,14 @@ void CueControl::quantizeChanged(double v) { mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( m_pCuePoint->get()); if (wasTrackAtCue && cuePosition.isValid()) { - seekExact(cuePosition.toEngineSamplePos()); + seekExact(cuePosition); } // Retrieve new intro start pos and follow const auto introPosition = mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( m_pIntroStartPosition->get()); if (wasTrackAtIntro && introPosition.isValid()) { - seekExact(introPosition.toEngineSamplePos()); + seekExact(introPosition); } } @@ -863,7 +862,7 @@ void CueControl::hotcueSet(HotcueControl* pControl, double value, HotcueSetMode if (!playing && m_pQuantizeEnabled->toBool()) { lock.unlock(); // prevent deadlock. // Enginebuffer will quantize more exactly than we can. - seekAbs(cueStartPosition.toEngineSamplePos()); + seekAbs(cueStartPosition); } } @@ -873,7 +872,7 @@ void CueControl::hotcueGoto(HotcueControl* pControl, double value) { } const mixxx::audio::FramePos position = pControl->getPosition(); if (position.isValid()) { - seekAbs(position.toEngineSamplePos()); + seekAbs(position); } } @@ -889,7 +888,7 @@ void CueControl::hotcueGotoAndStop(HotcueControl* pControl, double value) { if (m_currentlyPreviewingIndex == Cue::kNoHotCue) { m_pPlay->set(0.0); - seekExact(position.toEngineSamplePos()); + seekExact(position); } else { // this becomes a play latch command if we are previewing m_pPlay->set(0.0); @@ -902,7 +901,7 @@ void CueControl::hotcueGotoAndPlay(HotcueControl* pControl, double value) { } const mixxx::audio::FramePos position = pControl->getPosition(); if (position.isValid()) { - seekAbs(position.toEngineSamplePos()); + seekAbs(position); // End previewing to not jump back if a sticking finger on a cue // button is released (just in case) updateCurrentlyPreviewingIndex(Cue::kNoHotCue); @@ -931,11 +930,11 @@ void CueControl::hotcueGotoAndLoop(HotcueControl* pControl, double value) { } if (pCue->getType() == mixxx::CueType::Loop) { - seekAbs(startPosition.toEngineSamplePos()); + seekAbs(startPosition); setCurrentSavedLoopControlAndActivate(pControl); } else if (pCue->getType() == mixxx::CueType::HotCue) { - seekAbs(startPosition.toEngineSamplePos()); - setBeatLoop(startPosition.toEngineSamplePos(), true); + seekAbs(startPosition); + setBeatLoop(startPosition, true); } else { return; } @@ -977,9 +976,7 @@ void CueControl::hotcueCueLoop(HotcueControl* pControl, double value) { } else { bool loopActive = pControl->getStatus() == HotcueControl::Status::Active; Cue::StartAndEndPositions pos = pCue->getStartAndEndPosition(); - setLoop(pos.startPosition.toEngineSamplePosMaybeInvalid(), - pos.endPosition.toEngineSamplePosMaybeInvalid(), - !loopActive); + setLoop(pos.startPosition, pos.endPosition, !loopActive); } } break; case mixxx::CueType::HotCue: { @@ -992,7 +989,7 @@ void CueControl::hotcueCueLoop(HotcueControl* pControl, double value) { startPosition == mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( m_pLoopStartPosition->get()); - setBeatLoop(startPosition.toEngineSamplePosMaybeInvalid(), !loopActive); + setBeatLoop(startPosition, !loopActive); break; } default: @@ -1023,9 +1020,7 @@ void CueControl::hotcueActivate(HotcueControl* pControl, double value, HotcueSet bool loopActive = pControl->getStatus() == HotcueControl::Status::Active; Cue::StartAndEndPositions pos = pCue->getStartAndEndPosition(); - setLoop(pos.startPosition.toEngineSamplePos(), - pos.endPosition.toEngineSamplePos(), - !loopActive); + setLoop(pos.startPosition, pos.endPosition, !loopActive); } break; default: @@ -1063,7 +1058,7 @@ void CueControl::hotcueActivatePreview(HotcueControl* pControl, double value) { } else if (pControl->getStatus() == HotcueControl::Status::Set) { pControl->setStatus(HotcueControl::Status::Active); } - seekAbs(position.toEngineSamplePos()); + seekAbs(position); m_pPlay->set(1.0); } } @@ -1072,7 +1067,9 @@ void CueControl::hotcueActivatePreview(HotcueControl* pControl, double value) { const mixxx::audio::FramePos position = pControl->getPreviewingPosition(); updateCurrentlyPreviewingIndex(Cue::kNoHotCue); m_pPlay->set(0.0); - seekExact(position.toEngineSamplePosMaybeInvalid()); + if (position.isValid()) { + seekExact(position); + } } setHotcueFocusIndex(pControl->getHotcueIndex()); @@ -1235,14 +1232,18 @@ void CueControl::cueGoto(double value) { QMutexLocker lock(&m_trackMutex); // Seek to cue point - double cuePoint = m_pCuePoint->get(); + const auto mainCuePosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_pCuePoint->get()); // Note: We do not mess with play here, we continue playing or previewing. // Need to unlock before emitting any signals to prevent deadlock. lock.unlock(); - seekAbs(cuePoint); + if (mainCuePosition.isValid()) { + seekAbs(mainCuePosition); + } } void CueControl::cueGotoAndPlay(double value) { @@ -1271,8 +1272,12 @@ void CueControl::cueGotoAndStop(double value) { if (m_currentlyPreviewingIndex == Cue::kNoHotCue) { m_pPlay->set(0.0); - double position = m_pCuePoint->get(); - seekExact(position); + const auto mainCuePosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_pCuePoint->get()); + if (mainCuePosition.isValid()) { + seekExact(mainCuePosition); + } } else { // this becomes a play latch command if we are previewing m_pPlay->set(0.0); @@ -1280,16 +1285,25 @@ void CueControl::cueGotoAndStop(double value) { } void CueControl::cuePreview(double value) { + const auto mainCuePosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_pCuePoint->get()); + if (!mainCuePosition.isValid()) { + return; + } + if (value > 0) { - if (m_currentlyPreviewingIndex != kMainCueIndex) { - updateCurrentlyPreviewingIndex(kMainCueIndex); - seekAbs(m_pCuePoint->get()); - m_pPlay->set(1.0); + if (m_currentlyPreviewingIndex == kMainCueIndex) { + return; } + + updateCurrentlyPreviewingIndex(kMainCueIndex); + seekAbs(mainCuePosition); + m_pPlay->set(1.0); } else if (m_currentlyPreviewingIndex == kMainCueIndex) { updateCurrentlyPreviewingIndex(Cue::kNoHotCue); m_pPlay->set(0.0); - seekExact(m_pCuePoint->get()); + seekExact(mainCuePosition); } } @@ -1304,6 +1318,13 @@ void CueControl::cueCDJ(double value) { m_pPlay->toBool() && !getEngineBuffer()->getScratching(); TrackAt trackAt = getTrackAt(); + const auto mainCuePosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_pCuePoint->get()); + if (!mainCuePosition.isValid()) { + return; + } + if (value > 0) { if (m_currentlyPreviewingIndex == kMainCueIndex) { // already previewing, do nothing @@ -1312,11 +1333,11 @@ void CueControl::cueCDJ(double value) { // we are already previewing by hotcues // just jump to cue point and continue previewing updateCurrentlyPreviewingIndex(kMainCueIndex); - seekAbs(m_pCuePoint->get()); + seekAbs(mainCuePosition); } else if (freely_playing || trackAt == TrackAt::End) { // Jump to cue when playing or when at end position m_pPlay->set(0.0); - seekAbs(m_pCuePoint->get()); + seekAbs(mainCuePosition); } else if (trackAt == TrackAt::Cue) { // paused at cue point updateCurrentlyPreviewingIndex(kMainCueIndex); @@ -1329,14 +1350,14 @@ void CueControl::cueCDJ(double value) { // necessarily where we currently are if (m_pQuantizeEnabled->toBool()) { // Enginebuffer will quantize more exactly than we can. - seekAbs(m_pCuePoint->get()); + seekAbs(mainCuePosition); } } } else if (m_currentlyPreviewingIndex == kMainCueIndex) { updateCurrentlyPreviewingIndex(Cue::kNoHotCue); m_pPlay->set(0.0); // Need to unlock before emitting any signals to prevent deadlock. - seekExact(m_pCuePoint->get()); + seekExact(mainCuePosition); } // indicator may flash because the delayed adoption of seekAbs @@ -1357,6 +1378,13 @@ void CueControl::cueDenon(double value) { bool playing = (m_pPlay->toBool()); TrackAt trackAt = getTrackAt(); + const auto mainCuePosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_pCuePoint->get()); + if (!mainCuePosition.isValid()) { + return; + } + if (value > 0) { if (m_currentlyPreviewingIndex == kMainCueIndex) { // already previewing, do nothing @@ -1365,19 +1393,19 @@ void CueControl::cueDenon(double value) { // we are already previewing by hotcues // just jump to cue point and continue previewing updateCurrentlyPreviewingIndex(kMainCueIndex); - seekAbs(m_pCuePoint->get()); + seekAbs(mainCuePosition); } else if (!playing && trackAt == TrackAt::Cue) { // paused at cue point updateCurrentlyPreviewingIndex(kMainCueIndex); m_pPlay->set(1.0); } else { m_pPlay->set(0.0); - seekExact(m_pCuePoint->get()); + seekExact(mainCuePosition); } } else if (m_currentlyPreviewingIndex == kMainCueIndex) { updateCurrentlyPreviewingIndex(Cue::kNoHotCue); m_pPlay->set(0.0); - seekExact(m_pCuePoint->get()); + seekExact(mainCuePosition); } } @@ -1391,12 +1419,19 @@ void CueControl::cuePlay(double value) { m_pPlay->toBool() && !getEngineBuffer()->getScratching(); TrackAt trackAt = getTrackAt(); + const auto mainCuePosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_pCuePoint->get()); + if (!mainCuePosition.isValid()) { + return; + } + // pressed if (value > 0) { if (freely_playing) { updateCurrentlyPreviewingIndex(Cue::kNoHotCue); m_pPlay->set(0.0); - seekAbs(m_pCuePoint->get()); + seekAbs(mainCuePosition); } else if (trackAt == TrackAt::ElseWhere) { // Pause not at cue point and not at end position cueSet(value); @@ -1407,7 +1442,7 @@ void CueControl::cuePlay(double value) { // necessarily where we currently are if (m_pQuantizeEnabled->toBool()) { // Enginebuffer will quantize more exactly than we can. - seekAbs(m_pCuePoint->get()); + seekAbs(mainCuePosition); } } } else if (trackAt == TrackAt::Cue) { @@ -1551,7 +1586,7 @@ void CueControl::introStartActivate(double value) { mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( m_pIntroStartPosition->get()); if (introStartPosition.isValid()) { - seekAbs(introStartPosition.toEngineSamplePos()); + seekAbs(introStartPosition); } else { introStartSet(1.0); } @@ -1654,7 +1689,7 @@ void CueControl::introEndActivate(double value) { lock.unlock(); if (introEnd.isValid()) { - seekAbs(introEnd.toEngineSamplePos()); + seekAbs(introEnd); } else { introEndSet(1.0); } @@ -1757,7 +1792,7 @@ void CueControl::outroStartActivate(double value) { lock.unlock(); if (outroStart.isValid()) { - seekAbs(outroStart.toEngineSamplePos()); + seekAbs(outroStart); } else { outroStartSet(1.0); } @@ -1860,7 +1895,7 @@ void CueControl::outroEndActivate(double value) { lock.unlock(); if (outroEnd.isValid()) { - seekAbs(outroEnd.toEngineSamplePos()); + seekAbs(outroEnd); } else { outroEndSet(1.0); } @@ -2032,46 +2067,43 @@ void CueControl::resetIndicators() { } CueControl::TrackAt CueControl::getTrackAt() const { - SampleOfTrack sot = getSampleOfTrack(); + FrameInfo info = frameInfo(); // Note: current can be in the padded silence after the track end > total. - if (sot.current >= sot.total) { + if (info.trackEndPosition.isValid() && info.currentPosition >= info.trackEndPosition) { return TrackAt::End; } - double cue = m_pCuePoint->get(); - if (cue != Cue::kNoPosition && fabs(sot.current - cue) < 1.0f) { + const auto mainCuePosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_pCuePoint->get()); + if (mainCuePosition.isValid() && fabs(info.currentPosition - mainCuePosition) < 0.5) { return TrackAt::Cue; } return TrackAt::ElseWhere; } mixxx::audio::FramePos CueControl::getQuantizedCurrentPosition() { - SampleOfTrack sampleOfTrack = getSampleOfTrack(); + FrameInfo info = frameInfo(); // Note: currentPos can be past the end of the track, in the padded // silence of the last buffer. This position might be not reachable in // a future runs, depending on the buffering. - const auto currentPos = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - sampleOfTrack.current); // Don't quantize if quantization is disabled. if (!m_pQuantizeEnabled->toBool()) { - return currentPos; + return info.currentPosition; } - const auto trackEndPosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - sampleOfTrack.total); const auto closestBeat = mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( m_pClosestBeat->get()); // Note: closestBeat can be an interpolated beat past the end of the track, // which cannot be reached. - if (closestBeat.isValid() && trackEndPosition.isValid() && closestBeat <= trackEndPosition) { + if (closestBeat.isValid() && info.trackEndPosition.isValid() && + closestBeat <= info.trackEndPosition) { return closestBeat; } - return currentPos; + return info.currentPosition; } mixxx::audio::FramePos CueControl::quantizeCuePoint(mixxx::audio::FramePos position) { @@ -2080,8 +2112,8 @@ mixxx::audio::FramePos CueControl::quantizeCuePoint(mixxx::audio::FramePos posit return mixxx::audio::kInvalidFramePos; } - // we need to use m_pTrackSamples here because SampleOfTrack - // is set later by the engine and not during EngineBuffer::slotTrackLoaded + // We need to use m_pTrackSamples here because FrameInfo is set later by + // the engine and not during EngineBuffer::slotTrackLoaded. const auto trackEndPosition = mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( m_pTrackSamples->get()); @@ -2117,8 +2149,11 @@ mixxx::audio::FramePos CueControl::quantizeCuePoint(mixxx::audio::FramePos posit } bool CueControl::isTrackAtIntroCue() { - return (fabs(getSampleOfTrack().current - m_pIntroStartPosition->get()) < - 1.0f); + const auto introStartPosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_pIntroStartPosition->get()); + return introStartPosition.isValid() && + (fabs(frameInfo().currentPosition - introStartPosition) < 0.5); } SeekOnLoadMode CueControl::getSeekOnLoadPreference() { @@ -2213,7 +2248,7 @@ void CueControl::setCurrentSavedLoopControlAndActivate(HotcueControl* pControl) } // Set new control as active - setLoop(pos.startPosition.toEngineSamplePos(), pos.endPosition.toEngineSamplePos(), true); + setLoop(pos.startPosition, pos.endPosition, true); pControl->setStatus(HotcueControl::Status::Active); m_pCurrentSavedLoopControl.storeRelease(pControl); } diff --git a/src/engine/controls/enginecontrol.cpp b/src/engine/controls/enginecontrol.cpp index c400cd285a9..3ad3f2e788a 100644 --- a/src/engine/controls/enginecontrol.cpp +++ b/src/engine/controls/enginecontrol.cpp @@ -12,7 +12,9 @@ EngineControl::EngineControl(const QString& group, m_pConfig(pConfig), m_pEngineMaster(nullptr), m_pEngineBuffer(nullptr) { - setCurrentSample(EngineBuffer::kInitalSamplePosition, 0.0, 0.0); + setFrameInfo(mixxx::audio::kStartFramePos, + mixxx::audio::kInvalidFramePos, + mixxx::audio::SampleRate()); } EngineControl::~EngineControl() { @@ -45,13 +47,10 @@ void EngineControl::setEngineBuffer(EngineBuffer* pEngineBuffer) { m_pEngineBuffer = pEngineBuffer; } -void EngineControl::setCurrentSample( - const double dCurrentSample, const double dTotalSamples, const double dTrackSampleRate) { - SampleOfTrack sot; - sot.current = dCurrentSample; - sot.total = dTotalSamples; - sot.rate = dTrackSampleRate; - m_sampleOfTrack.setValue(sot); +void EngineControl::setFrameInfo(mixxx::audio::FramePos currentPosition, + mixxx::audio::FramePos trackEndPosition, + mixxx::audio::SampleRate sampleRate) { + m_frameInfo.setValue(FrameInfo{currentPosition, trackEndPosition, sampleRate}); } QString EngineControl::getGroup() const { @@ -70,33 +69,35 @@ EngineBuffer* EngineControl::getEngineBuffer() { return m_pEngineBuffer; } -void EngineControl::setBeatLoop(double startPosition, bool enabled) { +void EngineControl::setBeatLoop(mixxx::audio::FramePos startPosition, bool enabled) { if (m_pEngineBuffer) { return m_pEngineBuffer->setBeatLoop(startPosition, enabled); } } -void EngineControl::setLoop(double startPosition, double endPosition, bool enabled) { +void EngineControl::setLoop(mixxx::audio::FramePos startPosition, + mixxx::audio::FramePos endPosition, + bool enabled) { if (m_pEngineBuffer) { return m_pEngineBuffer->setLoop(startPosition, endPosition, enabled); } } -void EngineControl::seekAbs(double samplePosition) { +void EngineControl::seekAbs(mixxx::audio::FramePos position) { if (m_pEngineBuffer) { - m_pEngineBuffer->slotControlSeekAbs(samplePosition); + m_pEngineBuffer->slotControlSeekAbs(position.toEngineSamplePos()); } } -void EngineControl::seekExact(double playPosition) { +void EngineControl::seekExact(mixxx::audio::FramePos position) { if (m_pEngineBuffer) { - m_pEngineBuffer->slotControlSeekExact(playPosition); + m_pEngineBuffer->slotControlSeekExact(position.toEngineSamplePos()); } } -void EngineControl::seek(double sample) { +void EngineControl::seek(double fractionalPosition) { if (m_pEngineBuffer) { - m_pEngineBuffer->slotControlSeek(sample); + m_pEngineBuffer->slotControlSeek(fractionalPosition); } } diff --git a/src/engine/controls/enginecontrol.h b/src/engine/controls/enginecontrol.h index 099d30d14f2..765dacdfb9a 100644 --- a/src/engine/controls/enginecontrol.h +++ b/src/engine/controls/enginecontrol.h @@ -57,12 +57,15 @@ class EngineControl : public QObject { virtual void setEngineMaster(EngineMaster* pEngineMaster); void setEngineBuffer(EngineBuffer* pEngineBuffer); - virtual void setCurrentSample(const double dCurrentSample, - const double dTotalSamples, const double dTrackSampleRate); + virtual void setFrameInfo(mixxx::audio::FramePos currentPosition, + mixxx::audio::FramePos trackEndPosition, + mixxx::audio::SampleRate sampleRate); QString getGroup() const; - void setBeatLoop(double startPosition, bool enabled); - void setLoop(double startPosition, double endPosition, bool enabled); + void setBeatLoop(mixxx::audio::FramePos startPosition, bool enabled); + void setLoop(mixxx::audio::FramePos startPosition, + mixxx::audio::FramePos endPosition, + bool enabled); // Called to collect player features for effects processing. virtual void collectFeatureState(GroupFeatureState* pGroupFeatures) const { @@ -70,27 +73,32 @@ class EngineControl : public QObject { } /// Called whenever a seek occurs to allow the EngineControl to respond. - virtual void notifySeek(double dNewPlaypos) { - Q_UNUSED(dNewPlaypos); + virtual void notifySeek(mixxx::audio::FramePos position) { + Q_UNUSED(position); }; virtual void trackLoaded(TrackPointer pNewTrack); virtual void trackBeatsUpdated(mixxx::BeatsPointer pBeats); protected: - struct SampleOfTrack { - double current; - double total; - double rate; + struct FrameInfo { + /// The current playback position. Guaranteed to be valid. If no track + /// is loaded, this equals `mixxx::audio::kStartFramePos`. + mixxx::audio::FramePos currentPosition; + /// The track's end position (a.k.a. the length of the track in frames). + /// This may be invalid if no track is loaded. + mixxx::audio::FramePos trackEndPosition; + /// The track's sample rate. This may be invalid if no track is loaded. + mixxx::audio::SampleRate sampleRate; }; - SampleOfTrack getSampleOfTrack() const { - return m_sampleOfTrack.getValue(); + FrameInfo frameInfo() const { + return m_frameInfo.getValue(); } void seek(double fractionalPosition); - void seekAbs(double sample); + void seekAbs(mixxx::audio::FramePos position); // Seek to an exact sample and don't allow quantizing adjustment. - void seekExact(double sample); + void seekExact(mixxx::audio::FramePos position); // Returns an EngineBuffer to target for syncing. Returns nullptr if none found EngineBuffer* pickSyncTarget(); @@ -102,7 +110,7 @@ class EngineControl : public QObject { UserSettingsPointer m_pConfig; private: - ControlValueAtomic m_sampleOfTrack; + ControlValueAtomic m_frameInfo; EngineMaster* m_pEngineMaster; EngineBuffer* m_pEngineBuffer; diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 962bbdcfb8b..dcbedbeb626 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -330,11 +330,15 @@ void LoopingControl::process(const double dRate, if (loopSamples.seekMode == LoopSeekMode::Changed) { // here the loop has changed and the play position // should be moved with it - double target = seekInsideAdjustedLoop(currentSample, - m_oldLoopSamples.start, loopSamples.start, loopSamples.end); - if (target != kNoTrigger) { + const auto targetPosition = mixxx::audio::FramePos:: + fromEngineSamplePosMaybeInvalid( + seekInsideAdjustedLoop(currentSample, + m_oldLoopSamples.start, + loopSamples.start, + loopSamples.end)); + if (targetPosition.isValid()) { // jump immediately - seekAbs(target); + seekAbs(targetPosition); } } m_oldLoopSamples = loopSamples; @@ -527,8 +531,8 @@ double LoopingControl::getSyncPositionInsideLoop(double dRequestedPlaypos, doubl return dSyncedPlayPos; } -void LoopingControl::setBeatLoop(double startPositionSamples, bool enabled) { - VERIFY_OR_DEBUG_ASSERT(startPositionSamples != Cue::kNoPosition) { +void LoopingControl::setBeatLoop(mixxx::audio::FramePos startPosition, bool enabled) { + VERIFY_OR_DEBUG_ASSERT(startPosition.isValid()) { return; } @@ -541,25 +545,28 @@ void LoopingControl::setBeatLoop(double startPositionSamples, bool enabled) { // TODO(XXX): This is not realtime safe. See this Zulip discussion for details: // https://mixxx.zulipchat.com/#narrow/stream/109171-development/topic/getting.20locks.20out.20of.20Beats - const auto startPosition = mixxx::audio::FramePos::fromEngineSamplePos(startPositionSamples); const auto endPosition = pBeats->findNBeatsFromPosition(startPosition, beatloopSize); - - if (startPosition.isValid() && endPosition.isValid()) { - setLoop(startPositionSamples, endPosition.toEngineSamplePos(), enabled); + if (endPosition.isValid()) { + setLoop(startPosition, endPosition, enabled); } } -void LoopingControl::setLoop(double startPosition, double endPosition, bool enabled) { - VERIFY_OR_DEBUG_ASSERT(startPosition != Cue::kNoPosition && - endPosition != Cue::kNoPosition && startPosition < endPosition) { +void LoopingControl::setLoop(mixxx::audio::FramePos startPosition, + mixxx::audio::FramePos endPosition, + bool enabled) { + VERIFY_OR_DEBUG_ASSERT(startPosition.isValid() && endPosition.isValid() && + startPosition < endPosition) { return; } + const double startPositionSamples = startPosition.toEngineSamplePos(); + const double endPositionSamples = endPosition.toEngineSamplePos(); + LoopSamples loopSamples = m_loopSamples.getValue(); - if (loopSamples.start != startPosition || loopSamples.end != endPosition) { + if (loopSamples.start != startPositionSamples || loopSamples.end != endPositionSamples) { // Copy saved loop parameters to active loop - loopSamples.start = startPosition; - loopSamples.end = endPosition; + loopSamples.start = startPositionSamples; + loopSamples.end = endPositionSamples; loopSamples.seekMode = LoopSeekMode::None; clearActiveBeatLoop(); m_loopSamples.setValue(loopSamples); @@ -579,7 +586,8 @@ void LoopingControl::setLoop(double startPosition, double endPosition, bool enab slotLoopInGoto(1); } - m_pCOBeatLoopSize->setAndConfirm(findBeatloopSizeForLoop(startPosition, endPosition)); + m_pCOBeatLoopSize->setAndConfirm( + findBeatloopSizeForLoop(startPositionSamples, endPositionSamples)); } void LoopingControl::setLoopInToCurrentPosition() { @@ -690,9 +698,15 @@ void LoopingControl::slotLoopIn(double pressed) { } void LoopingControl::slotLoopInGoto(double pressed) { - if (pressed > 0.0) { - seekAbs(static_cast( - m_loopSamples.getValue().start)); + if (pressed == 0.0) { + return; + } + + const auto loopInPosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_loopSamples.getValue().start); + if (loopInPosition.isValid()) { + seekAbs(loopInPosition); } } @@ -811,9 +825,15 @@ void LoopingControl::slotLoopOut(double pressed) { } void LoopingControl::slotLoopOutGoto(double pressed) { - if (pressed > 0.0) { - seekAbs(static_cast( - m_loopSamples.getValue().end)); + if (pressed == 0.0) { + return; + } + + const auto loopOutPosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_loopSamples.getValue().end); + if (loopOutPosition.isValid()) { + seekAbs(loopOutPosition); } } @@ -900,12 +920,19 @@ void LoopingControl::slotReloopToggle(double val) { } void LoopingControl::slotReloopAndStop(double pressed) { - if (pressed > 0) { - m_pPlayButton->set(0.0); - seekAbs(static_cast( - m_loopSamples.getValue().start)); - setLoopingEnabled(true); + if (pressed == 0.0) { + return; + } + + m_pPlayButton->set(0.0); + + const auto loopInPosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_loopSamples.getValue().start); + if (loopInPosition.isValid()) { + seekAbs(loopInPosition); } + setLoopingEnabled(true); } void LoopingControl::slotLoopStartPos(double pos) { @@ -968,7 +995,7 @@ void LoopingControl::slotLoopEndPos(double pos) { } // This is called from the engine thread -void LoopingControl::notifySeek(double dNewPlaypos) { +void LoopingControl::notifySeek(mixxx::audio::FramePos position) { LoopSamples loopSamples = m_loopSamples.getValue(); double currentSample = m_currentSample.getValue(); if (m_bLoopingEnabled) { @@ -977,12 +1004,12 @@ void LoopingControl::notifySeek(double dNewPlaypos) { // Jumping to the exact end of a loop is considered jumping out. if (currentSample >= loopSamples.start && currentSample <= loopSamples.end && - dNewPlaypos < loopSamples.start) { + position.toEngineSamplePos() < loopSamples.start) { // jumping out of loop in backwards setLoopingEnabled(false); } if (currentSample <= loopSamples.end && - dNewPlaypos >= loopSamples.end) { + position.toEngineSamplePos() >= loopSamples.end) { // jumping out or to the exact end of a loop or over a catching loop forward setLoopingEnabled(false); } @@ -1392,7 +1419,7 @@ void LoopingControl::slotBeatJump(double beats) { const auto currentPosition = mixxx::audio::FramePos::fromEngineSamplePos(currentSample); const auto seekPosition = pBeats->findNBeatsFromPosition(currentPosition, beats); if (seekPosition.isValid()) { - seekExact(seekPosition.toEngineSamplePos()); + seekExact(seekPosition); } } } diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h index 3d492f306fa..6bf4c15f23d 100644 --- a/src/engine/controls/loopingcontrol.h +++ b/src/engine/controls/loopingcontrol.h @@ -46,10 +46,12 @@ class LoopingControl : public EngineControl { void hintReader(HintVector* pHintList) override; double getSyncPositionInsideLoop(double dRequestedPlaypos, double dSyncedPlayPos); - void notifySeek(double dNewPlaypos) override; + void notifySeek(mixxx::audio::FramePos position) override; - void setBeatLoop(double startPosition, bool enabled); - void setLoop(double startPosition, double endPosition, bool enabled); + 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(); @@ -184,6 +186,8 @@ class LoopingControl : public EngineControl { // objects below are written from an engine worker thread TrackPointer m_pTrack; mixxx::BeatsPointer m_pBeats; + + friend class LoopingControlTest; }; // Class for handling loop moves of a set size. This allows easy access from diff --git a/src/engine/controls/quantizecontrol.cpp b/src/engine/controls/quantizecontrol.cpp index 3b67e574616..06cf5b61377 100644 --- a/src/engine/controls/quantizecontrol.cpp +++ b/src/engine/controls/quantizecontrol.cpp @@ -47,16 +47,16 @@ void QuantizeControl::trackLoaded(TrackPointer pNewTrack) { void QuantizeControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) { m_pBeats = pBeats; - double current = getSampleOfTrack().current; + const double current = frameInfo().currentPosition.toEngineSamplePos(); lookupBeatPositions(current); updateClosestBeat(current); } -void QuantizeControl::setCurrentSample(const double dCurrentSample, - const double dTotalSamples, - const double dTrackSampleRate) { - EngineControl::setCurrentSample(dCurrentSample, dTotalSamples, dTrackSampleRate); - playPosChanged(dCurrentSample); +void QuantizeControl::setFrameInfo(mixxx::audio::FramePos currentPosition, + mixxx::audio::FramePos trackEndPosition, + mixxx::audio::SampleRate sampleRate) { + EngineControl::setFrameInfo(currentPosition, trackEndPosition, sampleRate); + playPosChanged(currentPosition.toEngineSamplePos()); } void QuantizeControl::playPosChanged(double dNewPlaypos) { diff --git a/src/engine/controls/quantizecontrol.h b/src/engine/controls/quantizecontrol.h index 51d7408bf7a..af5628a6a8e 100644 --- a/src/engine/controls/quantizecontrol.h +++ b/src/engine/controls/quantizecontrol.h @@ -16,10 +16,9 @@ class QuantizeControl : public EngineControl { QuantizeControl(const QString& group, UserSettingsPointer pConfig); ~QuantizeControl() override; - void setCurrentSample( - const double dCurrentSample, - const double dTotalSamples, - const double dTrackSampleRate) override; + void setFrameInfo(mixxx::audio::FramePos currentPosition, + mixxx::audio::FramePos trackEndPosition, + mixxx::audio::SampleRate sampleRate) override; void trackLoaded(TrackPointer pNewTrack) override; void trackBeatsUpdated(mixxx::BeatsPointer pBeats) override; diff --git a/src/engine/controls/ratecontrol.cpp b/src/engine/controls/ratecontrol.cpp index 65cf0fde0e7..51716267f84 100644 --- a/src/engine/controls/ratecontrol.cpp +++ b/src/engine/controls/ratecontrol.cpp @@ -453,7 +453,7 @@ double RateControl::calculateSpeed(double baserate, double speed, bool paused, } } - double currentSample = getSampleOfTrack().current; + double currentSample = frameInfo().currentPosition.toEngineSamplePos(); m_pScratchController->process(currentSample, rate, iSamplesPerBuffer, baserate); // If waveform scratch is enabled, override all other controls diff --git a/src/engine/controls/vinylcontrolcontrol.cpp b/src/engine/controls/vinylcontrolcontrol.cpp index f7c3ef50f29..b7f5987a83f 100644 --- a/src/engine/controls/vinylcontrolcontrol.cpp +++ b/src/engine/controls/vinylcontrolcontrol.cpp @@ -85,25 +85,20 @@ void VinylControlControl::slotControlVinylSeek(double fractionalPos) { // Do nothing if no track is loaded. TrackPointer pTrack = m_pTrack; - if (!pTrack) { + FrameInfo info = frameInfo(); + if (!pTrack || !info.trackEndPosition.isValid()) { return; } - const auto trackEndPosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - getSampleOfTrack().total); - if (!trackEndPosition.isValid()) { - return; - } const auto newPlayPos = mixxx::audio::kStartFramePos + - (trackEndPosition - mixxx::audio::kStartFramePos) * fractionalPos; + (info.trackEndPosition - mixxx::audio::kStartFramePos) * fractionalPos; if (m_pControlVinylEnabled->get() > 0.0 && m_pControlVinylMode->get() == MIXXX_VCMODE_RELATIVE) { int cuemode = (int)m_pControlVinylCueing->get(); //if in preroll, always seek if (newPlayPos < mixxx::audio::kStartFramePos) { - seekExact(newPlayPos.toEngineSamplePos()); + seekExact(newPlayPos); return; } @@ -114,7 +109,7 @@ void VinylControlControl::slotControlVinylSeek(double fractionalPos) { //if onecue, just seek to the regular cue const mixxx::audio::FramePos mainCuePosition = pTrack->getMainCuePosition(); if (mainCuePosition.isValid()) { - seekExact(mainCuePosition.toEngineSamplePos()); + seekExact(mainCuePosition); } return; } @@ -156,7 +151,7 @@ void VinylControlControl::slotControlVinylSeek(double fractionalPos) { //if negative, allow a seek by falling down to the bottom } else { m_bSeekRequested = true; - seekExact(nearestPlayPos.toEngineSamplePos()); + seekExact(nearestPlayPos); m_bSeekRequested = false; return; } @@ -164,7 +159,7 @@ void VinylControlControl::slotControlVinylSeek(double fractionalPos) { // Just seek where it wanted to originally. m_bSeekRequested = true; - seekExact(newPlayPos.toEngineSamplePos()); + seekExact(newPlayPos); m_bSeekRequested = false; } diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index bd0c0824ab0..3638f99624e 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -369,11 +369,13 @@ mixxx::Bpm EngineBuffer::getLocalBpm() const { return m_pBpmControl->getLocalBpm(); } -void EngineBuffer::setBeatLoop(double startPosition, bool enabled) { +void EngineBuffer::setBeatLoop(mixxx::audio::FramePos startPosition, bool enabled) { return m_pLoopingControl->setBeatLoop(startPosition, enabled); } -void EngineBuffer::setLoop(double startPosition, double endPositon, bool enabled) { +void EngineBuffer::setLoop(mixxx::audio::FramePos startPosition, + mixxx::audio::FramePos endPositon, + bool enabled) { return m_pLoopingControl->setLoop(startPosition, endPositon, enabled); } @@ -484,8 +486,9 @@ void EngineBuffer::setNewPlaypos(double newpos) { m_iSamplesSinceLastIndicatorUpdate = 1000000; // Must hold the engineLock while using m_engineControls + const auto playPosition = mixxx::audio::FramePos::fromEngineSamplePos(m_filepos_play); for (const auto& pControl: qAsConst(m_engineControls)) { - pControl->notifySeek(m_filepos_play); + pControl->notifySeek(playPosition); } verifyPlay(); // verify or update play button and indicator @@ -622,11 +625,14 @@ void EngineBuffer::notifyTrackLoaded( // First inform engineControls directly // Note: we are still in a worker thread. + const auto currentPosition = mixxx::audio::FramePos::fromEngineSamplePos(m_filepos_play); + const auto trackEndPosition = + mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( + m_pTrackSamples->get()); + const auto sampleRate = mixxx::audio::SampleRate::fromDouble(m_pTrackSampleRate->get()); for (const auto& pControl : qAsConst(m_engineControls)) { pControl->trackLoaded(pNewTrack); - pControl->setCurrentSample(m_filepos_play, - m_pTrackSamples->get(), - m_pTrackSampleRate->get()); + pControl->setFrameInfo(currentPosition, trackEndPosition, sampleRate); } if (pNewTrack) { @@ -1076,8 +1082,10 @@ void EngineBuffer::processTrackLocked( } } + const auto currentPosition = mixxx::audio::FramePos::fromEngineSamplePos(m_filepos_play); + const auto trackEndPosition = mixxx::audio::FramePos::fromEngineSamplePos(m_trackSamplesOld); for (const auto& pControl: qAsConst(m_engineControls)) { - pControl->setCurrentSample(m_filepos_play, m_trackSamplesOld, m_trackSampleRateOld); + pControl->setFrameInfo(currentPosition, trackEndPosition, m_trackSampleRateOld); pControl->process(rate, m_filepos_play, iBufferSize); } diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h index 21fb4d2ca55..7f40d44220b 100644 --- a/src/engine/enginebuffer.h +++ b/src/engine/enginebuffer.h @@ -101,9 +101,11 @@ class EngineBuffer : public EngineObject { /// Returns the BPM of the loaded track around the current position (not thread-safe) mixxx::Bpm getLocalBpm() const; /// Sets a beatloop for the loaded track (not thread safe) - void setBeatLoop(double startPosition, bool enabled); + void setBeatLoop(mixxx::audio::FramePos startPosition, bool enabled); /// Sets a loop for the loaded track (not thread safe) - void setLoop(double startPosition, double endPositon, bool enabled); + void setLoop(mixxx::audio::FramePos startPosition, + mixxx::audio::FramePos endPositon, + bool enabled); // Sets pointer to other engine buffer/channel void setEngineMaster(EngineMaster*); @@ -248,14 +250,10 @@ class EngineBuffer : public EngineObject { friend class CueControlTest; friend class HotcueControlTest; + friend class LoopingControlTest; LoopingControl* m_pLoopingControl; // used for testes FRIEND_TEST(LoopingControlTest, LoopScale_HalvesLoop); - FRIEND_TEST(LoopingControlTest, LoopMoveTest); - FRIEND_TEST(LoopingControlTest, LoopResizeSeek); - FRIEND_TEST(LoopingControlTest, ReloopToggleButton_DoesNotJumpAhead); - FRIEND_TEST(LoopingControlTest, ReloopAndStopButton); - FRIEND_TEST(LoopingControlTest, Beatjump_JumpsByBeats); FRIEND_TEST(SyncControlTest, TestDetermineBpmMultiplier); FRIEND_TEST(EngineSyncTest, HalfDoubleBpmTest); FRIEND_TEST(EngineSyncTest, HalfDoubleThenPlay); diff --git a/src/engine/positionscratchcontroller.cpp b/src/engine/positionscratchcontroller.cpp index cba4a28bbd6..0edc4a7ac58 100644 --- a/src/engine/positionscratchcontroller.cpp +++ b/src/engine/positionscratchcontroller.cpp @@ -274,8 +274,9 @@ double PositionScratchController::getRate() { return m_dRate; } -void PositionScratchController::notifySeek(double currentSample) { +void PositionScratchController::notifySeek(mixxx::audio::FramePos position) { + DEBUG_ASSERT(position.isValid()); // scratching continues after seek due to calculating the relative distance traveled // in m_dPositionDeltaSum - m_dLastPlaypos = currentSample; + m_dLastPlaypos = position.toEngineSamplePos(); } diff --git a/src/engine/positionscratchcontroller.h b/src/engine/positionscratchcontroller.h index 9c463d9b3cc..9d5a8bd0695 100644 --- a/src/engine/positionscratchcontroller.h +++ b/src/engine/positionscratchcontroller.h @@ -3,6 +3,7 @@ #include #include +#include "audio/frame.h" #include "control/controlobject.h" class VelocityController; @@ -17,7 +18,7 @@ class PositionScratchController : public QObject { int iBufferSize, double baserate); bool isEnabled(); double getRate(); - void notifySeek(double currentSample); + void notifySeek(mixxx::audio::FramePos position); private: const QString m_group; diff --git a/src/engine/readaheadmanager.cpp b/src/engine/readaheadmanager.cpp index 5c3d15e2ba1..08c83a29b32 100644 --- a/src/engine/readaheadmanager.cpp +++ b/src/engine/readaheadmanager.cpp @@ -254,7 +254,10 @@ double ReadAheadManager::getFilePlaypositionFromLog( // (Not looping control) if (shouldNotifySeek) { if (m_pRateControl) { - m_pRateControl->notifySeek(entry.virtualPlaypositionStart); + const auto seekPosition = + mixxx::audio::FramePos::fromEngineSamplePos( + entry.virtualPlaypositionStart); + m_pRateControl->notifySeek(seekPosition); } } diff --git a/src/engine/sync/synccontrol.cpp b/src/engine/sync/synccontrol.cpp index cc898a0db55..6eb0b1c4dc5 100644 --- a/src/engine/sync/synccontrol.cpp +++ b/src/engine/sync/synccontrol.cpp @@ -296,7 +296,8 @@ void SyncControl::updateTargetBeatDistance() { } else if (m_leaderBpmAdjustFactor == kBpmHalve) { targetDistance *= kBpmHalve; // Our beat distance CO is still a buffer behind, so take the current value. - if (m_pBpmControl->getBeatDistance(getSampleOfTrack().current) >= 0.5) { + if (m_pBpmControl->getBeatDistance( + frameInfo().currentPosition.toEngineSamplePos()) >= 0.5) { targetDistance += 0.5; } } diff --git a/src/test/cuecontrol_test.cpp b/src/test/cuecontrol_test.cpp index c0871724644..75cfec7f72a 100644 --- a/src/test/cuecontrol_test.cpp +++ b/src/test/cuecontrol_test.cpp @@ -52,10 +52,7 @@ class CueControlTest : public BaseSignalPathTest { } mixxx::audio::FramePos getCurrentFramePos() { - return mixxx::audio::FramePos::fromEngineSamplePos( - m_pChannel1->getEngineBuffer() - ->m_pCueControl->getSampleOfTrack() - .current); + return m_pChannel1->getEngineBuffer()->m_pCueControl->frameInfo().currentPosition; } void setCurrentFramePos(mixxx::audio::FramePos position) { diff --git a/src/test/hotcuecontrol_test.cpp b/src/test/hotcuecontrol_test.cpp index 63ea74a3398..0a07fac12a4 100644 --- a/src/test/hotcuecontrol_test.cpp +++ b/src/test/hotcuecontrol_test.cpp @@ -79,10 +79,7 @@ class HotcueControlTest : public BaseSignalPathTest { } mixxx::audio::FramePos currentFramePosition() { - return mixxx::audio::FramePos::fromEngineSamplePos( - m_pChannel1->getEngineBuffer() - ->m_pCueControl->getSampleOfTrack() - .current); + return m_pChannel1->getEngineBuffer()->m_pCueControl->frameInfo().currentPosition; } void setCurrentFramePosition(mixxx::audio::FramePos position) { @@ -266,10 +263,7 @@ TEST_F(HotcueControlTest, SetLoopAuto) { constexpr mixxx::audio::FramePos loopStartPosition(100); constexpr mixxx::audio::FramePos loopEndPosition(200); - m_pChannel1->getEngineBuffer()->setLoop( - loopStartPosition.toEngineSamplePosMaybeInvalid(), - loopEndPosition.toEngineSamplePosMaybeInvalid(), - true); + m_pChannel1->getEngineBuffer()->setLoop(loopStartPosition, loopEndPosition, true); m_pHotcue1Set->set(1); m_pHotcue1Set->set(0); @@ -291,10 +285,7 @@ TEST_F(HotcueControlTest, SetLoopManualWithLoop) { constexpr mixxx::audio::FramePos loopStartPosition(100); constexpr mixxx::audio::FramePos loopEndPosition(200); - m_pChannel1->getEngineBuffer()->setLoop( - loopStartPosition.toEngineSamplePosMaybeInvalid(), - loopEndPosition.toEngineSamplePosMaybeInvalid(), - true); + m_pChannel1->getEngineBuffer()->setLoop(loopStartPosition, loopEndPosition, true); m_pHotcue1SetLoop->set(1); m_pHotcue1SetLoop->set(0); @@ -677,10 +668,7 @@ TEST_F(HotcueControlTest, SavedLoopStatus) { constexpr auto loopStartPositon = mixxx::audio::FramePos(100); constexpr auto loopEndPosition = mixxx::audio::FramePos(200); - m_pChannel1->getEngineBuffer()->setLoop( - loopStartPositon.toEngineSamplePosMaybeInvalid(), - loopEndPosition.toEngineSamplePosMaybeInvalid(), - true); + m_pChannel1->getEngineBuffer()->setLoop(loopStartPositon, loopEndPosition, true); m_pHotcue1SetLoop->set(1); m_pHotcue1SetLoop->set(0); @@ -1003,10 +991,7 @@ TEST_F(HotcueControlTest, SavedLoopCueLoopWithExistingLoop) { constexpr auto loopStartPosition = mixxx::audio::FramePos(100); constexpr auto loopEndPosition = mixxx::audio::FramePos(200); - m_pChannel1->getEngineBuffer()->setLoop( - loopStartPosition.toEngineSamplePos(), - loopEndPosition.toEngineSamplePos(), - true); + m_pChannel1->getEngineBuffer()->setLoop(loopStartPosition, loopEndPosition, true); m_pHotcue1SetLoop->set(1); m_pHotcue1SetLoop->set(0); diff --git a/src/test/looping_control_test.cpp b/src/test/looping_control_test.cpp index 4380239c52f..e6c3b8b0ff6 100644 --- a/src/test/looping_control_test.cpp +++ b/src/test/looping_control_test.cpp @@ -65,6 +65,10 @@ class LoopingControlTest : public MockedEngineBackendTest { m_pButtonBeatLoopRoll4Activate = std::make_unique(m_sGroup1, "beatlooproll_4_activate"); } + mixxx::audio::FramePos currentFramePos() { + return m_pChannel1->getEngineBuffer()->m_pLoopingControl->frameInfo().currentPosition; + } + bool isLoopEnabled() { return m_pLoopEnabled->get() > 0.0; } @@ -348,7 +352,7 @@ TEST_F(LoopingControlTest, ReloopToggleButton_DoesNotJumpAhead) { m_pButtonReloopToggle->set(1); m_pButtonReloopToggle->set(0); seekToSampleAndProcess(50); - EXPECT_LE(m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current, m_pLoopStartPoint->get()); + EXPECT_LE(currentFramePos().toEngineSamplePos(), m_pLoopStartPoint->get()); } TEST_F(LoopingControlTest, ReloopAndStopButton) { @@ -360,7 +364,7 @@ TEST_F(LoopingControlTest, ReloopAndStopButton) { m_pButtonReloopAndStop->set(1); m_pButtonReloopAndStop->set(0); ProcessBuffer(); - EXPECT_EQ(m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current, m_pLoopStartPoint->get()); + EXPECT_EQ(currentFramePos().toEngineSamplePos(), m_pLoopStartPoint->get()); EXPECT_TRUE(m_pLoopEnabled->toBool()); } @@ -387,7 +391,7 @@ TEST_F(LoopingControlTest, LoopScale_HalvesLoop) { seekToSampleAndProcess(1800); EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(2000, m_pLoopEndPoint->get()); - EXPECT_EQ(1800, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); + EXPECT_EQ(1800, currentFramePos().toEngineSamplePos()); EXPECT_FALSE(isLoopEnabled()); m_pLoopScale->set(0.5); ProcessBuffer(); @@ -396,7 +400,7 @@ TEST_F(LoopingControlTest, LoopScale_HalvesLoop) { // The loop was not enabled so halving the loop should not move the playhead // even though it is outside the loop. - EXPECT_EQ(1800, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); + EXPECT_EQ(1800, currentFramePos().toEngineSamplePos()); m_pButtonReloopToggle->set(1); EXPECT_TRUE(isLoopEnabled()); @@ -524,7 +528,7 @@ TEST_F(LoopingControlTest, LoopMoveTest) { EXPECT_TRUE(isLoopEnabled()); EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(300, m_pLoopEndPoint->get()); - EXPECT_EQ(10, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); + EXPECT_EQ(10, currentFramePos().toEngineSamplePos()); // Move the loop out from under the playposition. m_pButtonBeatMoveForward->set(1.0); @@ -534,7 +538,7 @@ TEST_F(LoopingControlTest, LoopMoveTest) { EXPECT_EQ(44400, m_pLoopEndPoint->get()); ProcessBuffer(); // Should seek to the corresponding offset within the moved loop - EXPECT_EQ(44110, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); + EXPECT_EQ(44110, currentFramePos().toEngineSamplePos()); // Move backward so that the current position is outside the new location of the loop seekToSampleAndProcess(44300); @@ -545,10 +549,10 @@ TEST_F(LoopingControlTest, LoopMoveTest) { EXPECT_NEAR(300, m_pLoopEndPoint->get(), kLoopPositionMaxAbsError); ProcessBuffer(); EXPECT_NEAR(200, - m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current, + currentFramePos().toEngineSamplePos(), kLoopPositionMaxAbsError); - // Now repeat the test with looping disabled (should not affect the + // Now repeat the test with looping disabled (should not affect the // playhead). m_pButtonReloopToggle->set(1); EXPECT_FALSE(isLoopEnabled()); @@ -561,7 +565,7 @@ TEST_F(LoopingControlTest, LoopMoveTest) { EXPECT_EQ(44400, m_pLoopEndPoint->get()); // Should not seek inside the moved loop when the loop is disabled EXPECT_NEAR(200, - m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current, + currentFramePos().toEngineSamplePos(), kLoopPositionMaxAbsError); // Move backward so that the current position is outside the new location of the loop @@ -572,7 +576,7 @@ TEST_F(LoopingControlTest, LoopMoveTest) { EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_NEAR(300, m_pLoopEndPoint->get(), kLoopPositionMaxAbsError); EXPECT_NEAR(500, - m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current, + currentFramePos().toEngineSamplePos(), kLoopPositionMaxAbsError); } @@ -592,7 +596,7 @@ TEST_F(LoopingControlTest, LoopResizeSeek) { EXPECT_TRUE(isLoopEnabled()); EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(600, m_pLoopEndPoint->get()); - EXPECT_EQ(500, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); + EXPECT_EQ(500, currentFramePos().toEngineSamplePos()); // Activate a shorter loop m_pButtonBeatLoop2Activate->set(1.0); @@ -604,7 +608,7 @@ TEST_F(LoopingControlTest, LoopResizeSeek) { EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(450, m_pLoopEndPoint->get()); ProcessBuffer(); - EXPECT_EQ(50, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); + EXPECT_EQ(50, currentFramePos().toEngineSamplePos()); // But if looping is not enabled, no warping occurs. m_pLoopStartPoint->set(0); @@ -614,14 +618,14 @@ TEST_F(LoopingControlTest, LoopResizeSeek) { EXPECT_FALSE(isLoopEnabled()); EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(600, m_pLoopEndPoint->get()); - EXPECT_EQ(500, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); + EXPECT_EQ(500, currentFramePos().toEngineSamplePos()); m_pButtonBeatLoop2Activate->set(1.0); ProcessBuffer(); EXPECT_EQ(500, m_pLoopStartPoint->get()); EXPECT_EQ(950, m_pLoopEndPoint->get()); - EXPECT_EQ(500, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); + EXPECT_EQ(500, currentFramePos().toEngineSamplePos()); } TEST_F(LoopingControlTest, BeatLoopSize_SetAndToggle) { @@ -801,11 +805,11 @@ TEST_F(LoopingControlTest, Beatjump_JumpsByBeats) { m_pButtonBeatJumpForward->set(1.0); m_pButtonBeatJumpForward->set(0.0); ProcessBuffer(); - EXPECT_EQ(beatLength * 4, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); + EXPECT_EQ(beatLength * 4, currentFramePos().toEngineSamplePos()); m_pButtonBeatJumpBackward->set(1.0); m_pButtonBeatJumpBackward->set(0.0); ProcessBuffer(); - EXPECT_EQ(0, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); + EXPECT_EQ(0, currentFramePos().toEngineSamplePos()); } TEST_F(LoopingControlTest, Beatjump_MovesActiveLoop) {