diff --git a/src/engine/controls/bpmcontrol.cpp b/src/engine/controls/bpmcontrol.cpp index 2a6160637ef..7c161341f6d 100644 --- a/src/engine/controls/bpmcontrol.cpp +++ b/src/engine/controls/bpmcontrol.cpp @@ -723,8 +723,7 @@ mixxx::audio::FramePos BpmControl::getNearestPositionInPhase( return thisPosition; } - const auto otherPosition = mixxx::audio::FramePos::fromEngineSamplePos( - pOtherEngineBuffer->getExactPlayPos()); + const auto otherPosition = pOtherEngineBuffer->getExactPlayPos(); if (!BpmControl::getBeatContext(otherBeats, otherPosition, nullptr, @@ -909,8 +908,7 @@ mixxx::audio::FramePos BpmControl::getBeatMatchPosition( return thisPosition; } - const auto otherPosition = mixxx::audio::FramePos::fromEngineSamplePos( - pOtherEngineBuffer->getExactPlayPos()); + const auto otherPosition = pOtherEngineBuffer->getExactPlayPos(); const mixxx::audio::SampleRate thisSampleRate = m_pBeats->getSampleRate(); // Seek our next beat to the other next beat near our beat. diff --git a/src/engine/controls/enginecontrol.cpp b/src/engine/controls/enginecontrol.cpp index 26d3c11fd4e..21c8635f25a 100644 --- a/src/engine/controls/enginecontrol.cpp +++ b/src/engine/controls/enginecontrol.cpp @@ -85,13 +85,13 @@ void EngineControl::setLoop(mixxx::audio::FramePos startPosition, void EngineControl::seekAbs(mixxx::audio::FramePos position) { if (m_pEngineBuffer) { - m_pEngineBuffer->slotControlSeekAbs(position.toEngineSamplePos()); + m_pEngineBuffer->seekAbs(position); } } void EngineControl::seekExact(mixxx::audio::FramePos position) { if (m_pEngineBuffer) { - m_pEngineBuffer->slotControlSeekExact(position.toEngineSamplePos()); + m_pEngineBuffer->seekExact(position); } } diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 786369ce5ed..1f584a54cdf 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -45,6 +45,13 @@ namespace { const mixxx::Logger kLogger("EngineBuffer"); +// This value is used to make sure the initial seek after loading a track is +// not omitted. Therefore this value must be different for 0.0 or any likely +// value for the main cue +constexpr auto kInitialPlayPosition = + mixxx::audio::FramePos::fromEngineSamplePos( + std::numeric_limits::lowest()); + constexpr double kLinearScalerElipsis = 1.00058; // 2^(0.01/12): changes < 1 cent allows a linear scaler @@ -69,7 +76,7 @@ EngineBuffer::EngineBuffer(const QString& group, m_pKeyControl(nullptr), m_pReadAheadManager(nullptr), m_pReader(nullptr), - m_filepos_play(kInitalSamplePosition), + m_playPosition(kInitialPlayPosition), m_speed_old(0), m_tempo_ratio_old(1.), m_scratching_old(false), @@ -77,8 +84,8 @@ EngineBuffer::EngineBuffer(const QString& group, m_pitch_old(0), m_baserate_old(0), m_rate_old(0.), - m_trackSamplesOld(0), - m_dSlipPosition(0.), + m_trackEndPositionOld(mixxx::audio::kInvalidFramePos), + m_slipPosition(mixxx::audio::kStartFramePos), m_dSlipRate(1.0), m_bSlipEnabledProcessing(false), m_pRepeat(nullptr), @@ -93,6 +100,9 @@ EngineBuffer::EngineBuffer(const QString& group, m_pCrossfadeBuffer(SampleUtil::alloc(MAX_BUFFER_LEN)), m_bCrossfadeReady(false), m_iLastBufferSize(0) { + // This should be a static assertion, but isValid() is not constexpr. + DEBUG_ASSERT(kInitialPlayPosition.isValid()); + m_queuedSeek.setValue(kNoQueuedSeek); // zero out crossfade buffer @@ -452,32 +462,30 @@ void EngineBuffer::readToCrossfadeBuffer(const int iBufferSize) { // (Must be called only once per callback) m_pScale->scaleBuffer(m_pCrossfadeBuffer, iBufferSize); // Restore the original position that was lost due to scaleBuffer() above - m_pReadAheadManager->notifySeek(m_filepos_play); + m_pReadAheadManager->notifySeek(m_playPosition); m_bCrossfadeReady = true; } } void EngineBuffer::seekCloneBuffer(EngineBuffer* pOtherBuffer) { - const auto position = mixxx::audio::FramePos::fromEngineSamplePos( - pOtherBuffer->getExactPlayPos()); - doSeekPlayPos(position, SEEK_EXACT); + doSeekPlayPos(pOtherBuffer->getExactPlayPos(), SEEK_EXACT); } // WARNING: This method is not thread safe and must not be called from outside // the engine callback! -void EngineBuffer::setNewPlaypos(double newpos) { +void EngineBuffer::setNewPlaypos(mixxx::audio::FramePos position) { if (kLogger.traceEnabled()) { - kLogger.trace() << m_group << "EngineBuffer::setNewPlaypos" << newpos; + kLogger.trace() << m_group << "EngineBuffer::setNewPlaypos" << position; } - m_filepos_play = newpos; + m_playPosition = position; if (m_rate_old != 0.0) { // Before seeking, read extra buffer for crossfading // this also sets m_pReadAheadManager to newpos readToCrossfadeBuffer(m_iLastBufferSize); } else { - m_pReadAheadManager->notifySeek(m_filepos_play); + m_pReadAheadManager->notifySeek(m_playPosition); } m_pScale->clear(); @@ -485,9 +493,8 @@ 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(playPosition); + pControl->notifySeek(m_playPosition); } verifyPlay(); // verify or update play button and indicator @@ -520,7 +527,7 @@ void EngineBuffer::slotTrackLoading() { // Set play here, to signal the user that the play command is adopted m_playButton->set((double)m_bPlayAfterLoading); - m_pTrackSamples->set(0); // Stop renderer + setTrackEndPosition(mixxx::audio::kInvalidFramePos); // Stop renderer } void EngineBuffer::loadFakeTrack(TrackPointer pTrack, bool bPlay) { @@ -546,7 +553,7 @@ void EngineBuffer::slotTrackLoaded(TrackPointer pTrack, m_pause.lock(); m_visualPlayPos->setInvalid(); - m_filepos_play = kInitalSamplePosition; // for execute seeks to 0.0 + m_playPosition = kInitialPlayPosition; // for execute seeks to 0.0 m_pCurrentTrack = pTrack; m_pTrackSamples->set(iTrackNumSamples); m_pTrackSampleRate->set(iTrackSampleRate); @@ -555,7 +562,7 @@ void EngineBuffer::slotTrackLoaded(TrackPointer pTrack, // Reset slip mode m_pSlipButton->set(0); m_bSlipEnabledProcessing = false; - m_dSlipPosition = 0.; + m_slipPosition = mixxx::audio::kStartFramePos; m_dSlipRate = 0; m_queuedSeek.setValue(kNoQueuedSeek); @@ -591,7 +598,7 @@ void EngineBuffer::ejectTrack() { doSeekPlayPos(mixxx::audio::kStartFramePos, SEEK_EXACT); m_pCurrentTrack.reset(); - m_pTrackSamples->set(0); + setTrackEndPosition(mixxx::audio::kInvalidFramePos); m_pTrackSampleRate->set(0); m_pTrackLoaded->forceSet(0); @@ -624,14 +631,11 @@ 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 trackEndPosition = getTrackEndPosition(); const auto sampleRate = mixxx::audio::SampleRate::fromDouble(m_pTrackSampleRate->get()); for (const auto& pControl : qAsConst(m_engineControls)) { pControl->trackLoaded(pNewTrack); - pControl->setFrameInfo(currentPosition, trackEndPosition, sampleRate); + pControl->setFrameInfo(m_playPosition, trackEndPosition, sampleRate); } if (pNewTrack) { @@ -659,27 +663,25 @@ void EngineBuffer::slotControlSeek(double fractionalPos) { doSeekFractional(fractionalPos, SEEK_STANDARD); } -// WARNING: This method runs from SyncWorker and Engine Worker -void EngineBuffer::slotControlSeekAbs(double playPosition) { - // TODO: Check if we can assert a valid play position here - const auto position = mixxx::audio::FramePos::fromEngineSamplePos(playPosition); +// WARNING: This method is called by EngineControl and runs in the engine thread +void EngineBuffer::seekAbs(mixxx::audio::FramePos position) { + DEBUG_ASSERT(position.isValid()); doSeekPlayPos(position, SEEK_STANDARD); } -// WARNING: This method runs from SyncWorker and Engine Worker -void EngineBuffer::slotControlSeekExact(double playPosition) { - // TODO: Check if we can assert a valid play position here - const auto position = mixxx::audio::FramePos::fromEngineSamplePos(playPosition); +// WARNING: This method is called by EngineControl and runs in the engine thread +void EngineBuffer::seekExact(mixxx::audio::FramePos position) { + DEBUG_ASSERT(position.isValid()); doSeekPlayPos(position, SEEK_EXACT); } -double EngineBuffer::fractionalPlayposFromAbsolute(double absolutePlaypos) { - double fFractionalPlaypos = 0.0; - if (m_trackSamplesOld != 0) { - fFractionalPlaypos = math_min(absolutePlaypos, m_trackSamplesOld); - fFractionalPlaypos /= m_trackSamplesOld; +double EngineBuffer::fractionalPlayposFromAbsolute(mixxx::audio::FramePos absolutePlaypos) { + if (!m_trackEndPositionOld.isValid()) { + return 0.0; } - return fFractionalPlaypos; + + const auto position = std::min(absolutePlaypos, m_trackEndPositionOld); + return position.value() / m_trackEndPositionOld.value(); } void EngineBuffer::doSeekFractional(double fractionalPos, enum SeekRequest seekType) { @@ -689,8 +691,7 @@ void EngineBuffer::doSeekFractional(double fractionalPos, enum SeekRequest seekT } // FIXME: Use maybe invalid here - const auto trackEndPosition = - mixxx::audio::FramePos::fromEngineSamplePos(m_pTrackSamples->get()); + const mixxx::audio::FramePos trackEndPosition = getTrackEndPosition(); VERIFY_OR_DEBUG_ASSERT(trackEndPosition.isValid()) { return; } @@ -718,7 +719,7 @@ bool EngineBuffer::updateIndicatorsAndModifyPlay(bool newPlay, bool oldPlay) { const QueuedSeek queuedSeek = m_queuedSeek.getValue(); if ((!m_pCurrentTrack && atomicLoadRelaxed(m_iTrackLoading) == 0) || (m_pCurrentTrack && atomicLoadRelaxed(m_iTrackLoading) == 0 && - m_filepos_play >= m_pTrackSamples->get() && + m_playPosition >= getTrackEndPosition() && queuedSeek.seekType == SEEK_NONE) || m_pPassthroughEnabled->toBool()) { // play not possible @@ -810,7 +811,7 @@ void EngineBuffer::processTrackLocked( ScopedTimer t("EngineBuffer::process_pauselock"); m_trackSampleRateOld = mixxx::audio::SampleRate::fromDouble(m_pTrackSampleRate->get()); - m_trackSamplesOld = m_pTrackSamples->get(); + m_trackEndPositionOld = getTrackEndPosition(); double baserate = 0.0; if (sampleRate.isValid()) { @@ -836,7 +837,7 @@ void EngineBuffer::processTrackLocked( // Update the slipped position and seek if it was disabled. processSlip(iBufferSize); - // Note: This may effects the m_filepos_play, play, scaler and crossfade buffer + // Note: This may effects the m_playPosition, play, scaler and crossfade buffer processSeek(paused); // speed is the ratio between track-time and real-time @@ -1016,11 +1017,12 @@ void EngineBuffer::processTrackLocked( rate = m_rate_old; } - bool at_end = m_filepos_play >= m_trackSamplesOld; + const mixxx::audio::FramePos trackEndPosition = getTrackEndPosition(); + bool atEnd = m_playPosition >= trackEndPosition; bool backwards = rate < 0; bool bCurBufferPaused = false; - if (at_end && !backwards) { + if (atEnd && !backwards) { // do not play past end bCurBufferPaused = true; } else if (rate == 0 && !is_scratching) { @@ -1035,28 +1037,25 @@ void EngineBuffer::processTrackLocked( // If the buffer is not paused, then scale the audio. if (!bCurBufferPaused) { // Perform scaling of Reader buffer into buffer. - double framesRead = - m_pScale->scaleBuffer(pOutput, iBufferSize); + const auto framesRead = m_pScale->scaleBuffer(pOutput, iBufferSize); // TODO(XXX): The result framesRead might not be an integer value. // Converting to samples here does not make sense. All positional // calculations should be done in frames instead of samples! Otherwise // rounding errors might occur when converting from samples back to // frames later. - double samplesRead = framesRead * kSamplesPerFrame; if (m_bScalerOverride) { // If testing, we don't have a real log so we fake the position. - m_filepos_play += samplesRead; + m_playPosition += framesRead; } else { // Adjust filepos_play by the amount we processed. - m_filepos_play = - m_pReadAheadManager->getFilePlaypositionFromLog( - m_filepos_play, samplesRead); + m_playPosition = + m_pReadAheadManager->getFilePlaypositionFromLog(m_playPosition, framesRead); } // Note: The last buffer of a track is padded with silence. // This silence is played together with the last samples in the last - // callback and the m_filepos_play is advanced behind the end of the track. + // callback and the m_playPosition is advanced behind the end of the track. if (m_bCrossfadeReady) { // Bring pOutput with the new parameters in and fade out the old one, @@ -1081,29 +1080,27 @@ 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->setFrameInfo(currentPosition, trackEndPosition, m_trackSampleRateOld); - pControl->process(rate, currentPosition, iBufferSize); + pControl->setFrameInfo(m_playPosition, trackEndPosition, m_trackSampleRateOld); + pControl->process(rate, m_playPosition, iBufferSize); } m_scratching_old = is_scratching; // Handle repeat mode - bool at_start = m_filepos_play <= 0; - at_end = m_filepos_play >= m_trackSamplesOld; + const bool atStart = m_playPosition <= mixxx::audio::kStartFramePos; + atEnd = m_playPosition >= trackEndPosition; bool repeat_enabled = m_pRepeat->toBool(); bool end_of_track = //(at_start && backwards) || - (at_end && !backwards); + (atEnd && !backwards); // If playbutton is pressed, check if we are at start or end of track if ((m_playButton->toBool() || (m_fwdButton->toBool() || m_backButton->toBool())) && end_of_track) { if (repeat_enabled) { - double fractionalPos = at_start ? 1.0 : 0; + double fractionalPos = atStart ? 1.0 : 0; doSeekFractional(fractionalPos, SEEK_STANDARD); } else { m_playButton->set(0.); @@ -1191,20 +1188,27 @@ void EngineBuffer::processSlip(int iBufferSize) { if (enabled != m_bSlipEnabledProcessing) { m_bSlipEnabledProcessing = enabled; if (enabled) { - m_dSlipPosition = m_filepos_play; + m_slipPosition = m_playPosition; m_dSlipRate = m_rate_old; } else { // TODO(owen) assuming that looping will get canceled properly - double newPlayFrame = m_dSlipPosition / kSamplesPerFrame; - double roundedSlip = round(newPlayFrame) * kSamplesPerFrame; - slotControlSeekExact(roundedSlip); - m_dSlipPosition = 0; + seekExact(m_slipPosition.toNearestFrameBoundary()); + m_slipPosition = mixxx::audio::kStartFramePos; } } // Increment slip position even if it was just toggled -- this ensures the position is correct. if (enabled) { - m_dSlipPosition += static_cast(iBufferSize) * m_dSlipRate; + // `iBufferSize` originates from `SoundManager::onDeviceOutputCallback` + // and is always a multiple of 2, so we can safely use integer division + // to find the number of frames per buffer here. + // + // TODO: Check if we can replace `iBufferSize` with the number of + // frames per buffer in most engine method signatures to avoid this + // back and forth calculations. + const int bufferFrameCount = iBufferSize / mixxx::kEngineChannelCount; + DEBUG_ASSERT(bufferFrameCount * mixxx::kEngineChannelCount == iBufferSize); + m_slipPosition += static_cast(bufferFrameCount) * m_dSlipRate; } } @@ -1258,7 +1262,7 @@ void EngineBuffer::processSeek(bool paused) { return; case SEEK_PHASE: // only adjust phase - position = mixxx::audio::FramePos::fromEngineSamplePos(m_filepos_play); + position = m_playPosition; break; case SEEK_STANDARD: if (m_pQuantize->toBool()) { @@ -1281,12 +1285,8 @@ void EngineBuffer::processSeek(bool paused) { return; } - const auto trackEndPosition = mixxx::audio::FramePos::fromEngineSamplePos(m_trackSamplesOld); - // Don't allow the playposition to go past the end. - if (position > trackEndPosition) { - position = trackEndPosition; - } + position = std::min(position, m_trackEndPositionOld); if (!paused && (seekType & SEEK_PHASE)) { if (kLogger.traceEnabled()) { @@ -1297,15 +1297,15 @@ void EngineBuffer::processSeek(bool paused) { position = m_pLoopingControl->getSyncPositionInsideLoop(position, syncPosition); if (kLogger.traceEnabled()) { kLogger.trace() - << "EngineBuffer::processSeek" << getGroup() << "seek info:" << m_filepos_play + << "EngineBuffer::processSeek" << getGroup() << "seek info:" << m_playPosition << "->" << position; } } - if (position.toEngineSamplePos() != m_filepos_play) { + if (position != m_playPosition) { if (kLogger.traceEnabled()) { kLogger.trace() << "EngineBuffer::processSeek" << getGroup() << "Seek to" << position; } - setNewPlaypos(position.toEngineSamplePos()); + setNewPlaypos(position); m_previousBufferSeek = true; } // Reset the m_queuedSeek value after it has been processed in @@ -1363,10 +1363,10 @@ void EngineBuffer::updateIndicators(double speed, int iBufferSize) { // Increase samplesCalculated by the buffer size m_iSamplesSinceLastIndicatorUpdate += iBufferSize; - const double fFractionalPlaypos = fractionalPlayposFromAbsolute(m_filepos_play); + const double fFractionalPlaypos = fractionalPlayposFromAbsolute(m_playPosition); - const double tempoTrackSeconds = m_trackSamplesOld / kSamplesPerFrame - / m_trackSampleRateOld / m_tempo_ratio_old; + const double tempoTrackSeconds = m_trackEndPositionOld.value() / + m_trackSampleRateOld / m_tempo_ratio_old; if(speed > 0 && fFractionalPlaypos == 1.0) { // At Track end speed = 0; @@ -1389,18 +1389,19 @@ void EngineBuffer::updateIndicators(double speed, int iBufferSize) { // Update visual control object, this needs to be done more often than the // playpos slider - m_visualPlayPos->set(fFractionalPlaypos, speed * m_baserate_old, - (double)iBufferSize / m_trackSamplesOld, - fractionalPlayposFromAbsolute(m_dSlipPosition), + m_visualPlayPos->set(fFractionalPlaypos, + speed * m_baserate_old, + static_cast(iBufferSize) / + m_trackEndPositionOld.toEngineSamplePos(), + fractionalPlayposFromAbsolute(m_slipPosition), tempoTrackSeconds); // TODO: Especially with long audio buffers, jitter is visible. This can be fixed by moving the // ClockControl::updateIndicators into the waveform update loop which is synced with the display refresh rate. // Via the visual play position it's possible to access to the sample that is currently played, // and not the one that have been processed as in the current solution. - const auto currentPosition = mixxx::audio::FramePos::fromEngineSamplePos(m_filepos_play); const auto sampleRate = mixxx::audio::SampleRate::fromDouble(m_pSampleRate->get()); - m_pClockControl->updateIndicators(speed * m_baserate_old, currentPosition, sampleRate); + m_pClockControl->updateIndicators(speed * m_baserate_old, m_playPosition, sampleRate); } void EngineBuffer::hintReader(const double dRate) { @@ -1410,7 +1411,7 @@ void EngineBuffer::hintReader(const double dRate) { //if slipping, hint about virtual position so we're ready for it if (m_bSlipEnabledProcessing) { Hint hint; - hint.frame = SampleUtil::floorPlayPosToFrame(m_dSlipPosition); + hint.frame = static_cast(m_slipPosition.toLowerFrameBoundary().value()); hint.priority = 1; if (m_dSlipRate >= 0) { hint.frameCount = Hint::kFrameCountForward; @@ -1467,19 +1468,23 @@ void EngineBuffer::slotEjectTrack(double v) { } } -double EngineBuffer::getExactPlayPos() const { +mixxx::audio::FramePos EngineBuffer::getExactPlayPos() const { if (!m_visualPlayPos->isValid()) { - return 0.0; + return mixxx::audio::kStartFramePos; } - return m_visualPlayPos->getEnginePlayPos() * getTrackSamples(); + return getTrackEndPosition() * m_visualPlayPos->getEnginePlayPos(); } double EngineBuffer::getVisualPlayPos() const { return m_visualPlayPos->getEnginePlayPos(); } -double EngineBuffer::getTrackSamples() const { - return m_pTrackSamples->get(); +mixxx::audio::FramePos EngineBuffer::getTrackEndPosition() const { + return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_pTrackSamples->get()); +} + +void EngineBuffer::setTrackEndPosition(mixxx::audio::FramePos position) { + m_pTrackSamples->set(position.toEngineSamplePosMaybeInvalid()); } double EngineBuffer::getUserOffset() const { diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h index 7f40d44220b..9683a7d3b75 100644 --- a/src/engine/enginebuffer.h +++ b/src/engine/enginebuffer.h @@ -80,11 +80,6 @@ class EngineBuffer : public EngineObject { KEYLOCK_ENGINE_COUNT, }; - // This value is used to make sure the initial seek after loading a track is - // not omitted. Therefore this value must be different for 0.0 or any likely - // value for the main cue - static constexpr double kInitalSamplePosition = -DBL_MAX; - EngineBuffer(const QString& group, UserSettingsPointer pConfig, EngineChannel* pChannel, EngineMaster* pMixingEngine); virtual ~EngineBuffer(); @@ -128,9 +123,10 @@ class EngineBuffer : public EngineObject { bool isTrackLoaded() const; TrackPointer getLoadedTrack() const; - double getExactPlayPos() const; + mixxx::audio::FramePos getExactPlayPos() const; double getVisualPlayPos() const; - double getTrackSamples() const; + mixxx::audio::FramePos getTrackEndPosition() const; + void setTrackEndPosition(mixxx::audio::FramePos position); double getUserOffset() const; double getRateRatio() const; @@ -165,6 +161,9 @@ class EngineBuffer : public EngineObject { m_channelIndex = channelIndex; } + void seekAbs(mixxx::audio::FramePos); + void seekExact(mixxx::audio::FramePos); + public slots: void slotControlPlayRequest(double); void slotControlPlayFromStart(double); @@ -173,8 +172,6 @@ class EngineBuffer : public EngineObject { void slotControlStart(double); void slotControlEnd(double); void slotControlSeek(double); - void slotControlSeekAbs(double); - void slotControlSeekExact(double); void slotKeylockEngineChanged(double); void slotEjectTrack(double); @@ -212,7 +209,7 @@ class EngineBuffer : public EngineObject { void ejectTrack(); - double fractionalPlayposFromAbsolute(double absolutePlaypos); + double fractionalPlayposFromAbsolute(mixxx::audio::FramePos position); void doSeekFractional(double fractionalPos, enum SeekRequest seekType); void doSeekPlayPos(mixxx::audio::FramePos position, enum SeekRequest seekType); @@ -226,7 +223,7 @@ class EngineBuffer : public EngineObject { void seekCloneBuffer(EngineBuffer* pOtherBuffer); // Reset buffer playpos and set file playpos. - void setNewPlaypos(double playpos); + void setNewPlaypos(mixxx::audio::FramePos playpos); void processSyncRequests(); void processSeek(bool paused); @@ -283,7 +280,7 @@ class EngineBuffer : public EngineObject { HintVector m_hintList; // The current sample to play in the file. - double m_filepos_play; + mixxx::audio::FramePos m_playPosition; // The previous callback's speed. Used to check if the scaler parameters // need updating. @@ -310,7 +307,7 @@ class EngineBuffer : public EngineObject { double m_rate_old; // Copy of length of file - double m_trackSamplesOld; + mixxx::audio::FramePos m_trackEndPositionOld; // Copy of file sample rate mixxx::audio::SampleRate m_trackSampleRateOld; @@ -322,7 +319,7 @@ class EngineBuffer : public EngineObject { int m_iSamplesSinceLastIndicatorUpdate; // The location where the track would have been had slip not been engaged - double m_dSlipPosition; + mixxx::audio::FramePos m_slipPosition; // Saved value of rate for slip mode double m_dSlipRate; // m_bSlipEnabledProcessing is only used by the engine processing thread. diff --git a/src/engine/readaheadmanager.cpp b/src/engine/readaheadmanager.cpp index 2462bded14b..8958ea44ac8 100644 --- a/src/engine/readaheadmanager.cpp +++ b/src/engine/readaheadmanager.cpp @@ -279,3 +279,12 @@ double ReadAheadManager::getFilePlaypositionFromLog( return filePlayposition; } + +mixxx::audio::FramePos ReadAheadManager::getFilePlaypositionFromLog( + mixxx::audio::FramePos currentPosition, + mixxx::audio::FrameDiff_t numConsumedFrames) { + const double positionSamples = + getFilePlaypositionFromLog(currentPosition.toEngineSamplePos(), + numConsumedFrames * mixxx::kEngineChannelCount); + return mixxx::audio::FramePos::fromEngineSamplePos(positionSamples); +} diff --git a/src/engine/readaheadmanager.h b/src/engine/readaheadmanager.h index 49f5b98c64f..fa673dcca85 100644 --- a/src/engine/readaheadmanager.h +++ b/src/engine/readaheadmanager.h @@ -4,6 +4,7 @@ #include #include +#include "audio/frame.h" #include "engine/cachingreader/cachingreader.h" #include "util/math.h" #include "util/types.h" @@ -46,6 +47,9 @@ class ReadAheadManager { } virtual void notifySeek(double seekPosition); + virtual void notifySeek(mixxx::audio::FramePos position) { + notifySeek(position.toEngineSamplePos()); + } /// hintReader allows the ReadAheadManager to provide hints to the reader to /// indicate that the given portion of a song is about to be read. @@ -54,6 +58,9 @@ class ReadAheadManager { virtual double getFilePlaypositionFromLog( double currentFilePlayposition, double numConsumedSamples); + mixxx::audio::FramePos getFilePlaypositionFromLog( + mixxx::audio::FramePos currentPosition, + mixxx::audio::FrameDiff_t numConsumedFrames); private: /// An entry in the read log indicates the virtual playposition the read diff --git a/src/test/beatstranslatetest.cpp b/src/test/beatstranslatetest.cpp index 464bb60b131..80ea81203c7 100644 --- a/src/test/beatstranslatetest.cpp +++ b/src/test/beatstranslatetest.cpp @@ -22,8 +22,8 @@ TEST_F(BeatsTranslateTest, SimpleTranslateMatch) { grid2->findClosestBeat(mixxx::audio::kStartFramePos).value()); // Seek deck 1 forward a bit. - const double deltaFrames = 1111.0; - m_pChannel1->getEngineBuffer()->slotControlSeekAbs(deltaFrames * 2.0); + const auto seekPosition = mixxx::audio::FramePos{1111.0}; + m_pChannel1->getEngineBuffer()->seekAbs(seekPosition); ProcessBuffer(); EXPECT_TRUE(m_pChannel1->getEngineBuffer()->getVisualPlayPos() > 0); @@ -45,9 +45,9 @@ TEST_F(BeatsTranslateTest, SimpleTranslateMatch) { pTranslateMatchAlignment->set(1.0); ProcessBuffer(); - // Deck 1 is +deltaFrames away from its closest beat (which is at 0). - // Deck 2 was left at 0. We translated grid 2 so that it is also +deltaFrames - // away from its closest beat, so that beat should be at -deltaFrames. - mixxx::BeatsPointer beats = m_pTrack2->getBeats(); - ASSERT_DOUBLE_EQ(-deltaFrames, beats->findClosestBeat(mixxx::audio::kStartFramePos).value()); + // Deck 1 is +seekPosition away from its closest beat (which is at 0). + // Deck 2 was left at 0. We translated grid 2 so that it is also +seekPosition + // away from its closest beat, so that beat should be at -seekPosition. + mixxx::BeatsPointer pBeats = m_pTrack2->getBeats(); + EXPECT_FRAMEPOS_EQ(seekPosition * -1, pBeats->findClosestBeat(mixxx::audio::kStartFramePos)); }