From 409267e4fe2ae394618a6cca087ff20a0ad4ccbd Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sat, 27 Apr 2024 09:38:39 +0000 Subject: [PATCH 01/18] AutoDJProcessor: Feature: Add control objects for Auto DJ queue status The time_remaining and tracks_remaining controls in the [AutoDJ] can be used to display the information about the Auto DJ queue in custom skins. The current calculation method for the time_remaining control only considers the total duration of the tracks by themselves, and does not take the transition times between tracks into account. --- src/library/autodj/autodjprocessor.cpp | 19 +++++++++++++++++++ src/library/autodj/autodjprocessor.h | 5 +++++ src/library/playlisttablemodel.cpp | 14 ++++++++++++++ src/library/playlisttablemodel.h | 6 ++++++ 4 files changed, 44 insertions(+) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 0910d6f9b89..24ac24e0be3 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -153,6 +153,15 @@ AutoDJProcessor::AutoDJProcessor( m_pEnabledAutoDJ->connectValueChangeRequest(this, &AutoDJProcessor::controlEnableChangeRequest); + m_pTracksRemaining = new ControlObject( + ConfigKey("[AutoDJ]", "tracks_remaining")); + m_pTimeRemaining = new ControlObject( + ConfigKey("[AutoDJ]", "time_remaining")); + connect(m_pAutoDJTableModel, + &PlaylistTableModel::playlistTracksChanged, + this, + &AutoDJProcessor::playlistTracksChanged); + // TODO(rryan) listen to signals from PlayerManager and add/remove as decks // are created. for (unsigned int i = 0; i < pPlayerManager->numberOfDecks(); ++i) { @@ -178,6 +187,9 @@ AutoDJProcessor::AutoDJProcessor( m_transitionMode = m_pConfig->getValue( ConfigKey(kConfigKey, kTransitionModePreferenceName), TransitionMode::FullIntroOutro); + + // Calculate the initial values for track count and time remaining + playlistTracksChanged(); } AutoDJProcessor::~AutoDJProcessor() { @@ -190,6 +202,8 @@ AutoDJProcessor::~AutoDJProcessor() { delete m_pAddRandomTrack; delete m_pShufflePlaylist; delete m_pEnabledAutoDJ; + delete m_pTracksRemaining; + delete m_pTimeRemaining; delete m_pFadeNow; delete m_pAutoDJTableModel; @@ -209,6 +223,11 @@ void AutoDJProcessor::setCrossfader(double value) { m_pCOCrossfader->set(value); } +void AutoDJProcessor::playlistTracksChanged() { + m_pTracksRemaining->set(m_pAutoDJTableModel->rowCount()); + m_pTimeRemaining->set(m_pAutoDJTableModel->getTotalDuration().toDoubleSeconds()); +} + AutoDJProcessor::AutoDJError AutoDJProcessor::shufflePlaylist( const QModelIndexList& selectedIndices) { QModelIndex exclude; diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index 600cf7bfb70..c6aabb3f513 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -220,6 +220,8 @@ class AutoDJProcessor : public QObject { void playerEmpty(DeckAttributes* pDeck); void playerRateChanged(DeckAttributes* pDeck); + void playlistTracksChanged(); + void controlEnableChangeRequest(double value); void controlFadeNow(double value); void controlShuffle(double value); @@ -297,5 +299,8 @@ class AutoDJProcessor : public QObject { ControlPushButton* m_pShufflePlaylist; ControlPushButton* m_pEnabledAutoDJ; + ControlObject* m_pTracksRemaining; + ControlObject* m_pTimeRemaining; + DISALLOW_COPY_AND_ASSIGN(AutoDJProcessor); }; diff --git a/src/library/playlisttablemodel.cpp b/src/library/playlisttablemodel.cpp index 069a460d903..8e9cd3a203e 100644 --- a/src/library/playlisttablemodel.cpp +++ b/src/library/playlisttablemodel.cpp @@ -331,6 +331,19 @@ void PlaylistTableModel::shuffleTracks(const QModelIndexList& shuffle, const QMo m_pTrackCollectionManager->internalCollection()->getPlaylistDAO().shuffleTracks(m_iPlaylistId, positions, allIds); } +mixxx::Duration PlaylistTableModel::getTotalDuration() { + double durationTotal = 0.0; + const int durationColumnIndex = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION); + int numOfTracks = rowCount(); + for (int i = 0; i < numOfTracks; i++) { + durationTotal += index(i, durationColumnIndex) + .data(Qt::EditRole) + .toDouble(); + } + + return mixxx::Duration::fromSeconds(durationTotal); +} + mixxx::Duration PlaylistTableModel::getTotalDuration(const QModelIndexList& indices) { if (indices.isEmpty()) { return mixxx::Duration::empty(); @@ -411,5 +424,6 @@ QString PlaylistTableModel::modelKey(bool noSearch) const { void PlaylistTableModel::playlistsChanged(const QSet& playlistIds) { if (playlistIds.contains(m_iPlaylistId)) { select(); // Repopulate the data model. + emit playlistTracksChanged(); } } diff --git a/src/library/playlisttablemodel.h b/src/library/playlisttablemodel.h index 1f8efddb69a..74faf156947 100644 --- a/src/library/playlisttablemodel.h +++ b/src/library/playlisttablemodel.h @@ -33,6 +33,9 @@ class PlaylistTableModel final : public TrackSetTableModel { int* pOutInsertionPos) final; bool isLocked() final; + /// Get the total duration of all tracks in the selected playlist + mixxx::Duration getTotalDuration(); + /// Get the total duration of all tracks referenced by the given model indices mixxx::Duration getTotalDuration(const QModelIndexList& indices); @@ -40,6 +43,9 @@ class PlaylistTableModel final : public TrackSetTableModel { QString modelKey(bool noSearch) const override; + signals: + void playlistTracksChanged(); + private slots: void playlistsChanged(const QSet& playlistIds); From 5c6ce7a41d615f28782ae17b8341ca6c412ca9fd Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sat, 27 Apr 2024 14:16:57 +0000 Subject: [PATCH 02/18] AutoDJProcessor: Add TrackOrDeckAttributes This is a first step in generalizing the transition logic in AutoDJProcessor to enable it to also be applied to tracks in the queue instead of just the current decks. --- src/library/autodj/autodjprocessor.cpp | 199 ++++++++++++++++--------- src/library/autodj/autodjprocessor.h | 79 +++++++--- 2 files changed, 192 insertions(+), 86 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 24ac24e0be3..e8f022b05a0 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -23,6 +23,68 @@ constexpr double kMinimumTrackDurationSec = 0.2; constexpr bool sDebug = false; } // anonymous namespace +TrackOrDeckAttributes::~TrackOrDeckAttributes() { +} + +TrackAttributes::TrackAttributes(TrackPointer pTrack) + : m_pTrack(pTrack) { +} + +TrackAttributes::~TrackAttributes() { +} + +mixxx::audio::FramePos TrackAttributes::introStartPosition() const { + auto pIntro = m_pTrack->findCueByType(mixxx::CueType::Intro); + if (pIntro) { + return pIntro->getPosition(); + } + return mixxx::audio::FramePos(); +} + +mixxx::audio::FramePos TrackAttributes::introEndPosition() const { + auto pIntro = m_pTrack->findCueByType(mixxx::CueType::Intro); + if (pIntro) { + return pIntro->getEndPosition(); + } + return mixxx::audio::FramePos(); +} + +mixxx::audio::FramePos TrackAttributes::outroStartPosition() const { + auto pOutro = m_pTrack->findCueByType(mixxx::CueType::Outro); + if (pOutro) { + return pOutro->getPosition(); + } + return mixxx::audio::FramePos(); +} + +mixxx::audio::FramePos TrackAttributes::outroEndPosition() const { + auto pOutro = m_pTrack->findCueByType(mixxx::CueType::Outro); + if (pOutro) { + return pOutro->getEndPosition(); + } + return mixxx::audio::FramePos(); +} + +mixxx::audio::SampleRate TrackAttributes::sampleRate() const { + return m_pTrack->getSampleRate(); +} + +double TrackAttributes::rateRatio() const { + return 1.0; +} + +mixxx::audio::FramePos TrackAttributes::trackEndPosition() const { + // Instead of actually loading the file, we simply infer + // the number of frames from duration and sample rate stored + // in the database. This isn't entirely accurate due to + // rounding errors, but more than accurate enough for + // estimating the remaining play time of the AutoDJ queue. + double approxNumSamples = + m_pTrack->getSampleRate() * m_pTrack->getDuration(); + + return mixxx::audio::FramePos(approxNumSamples); +} + DeckAttributes::DeckAttributes(int index, BaseTrackPlayer* pPlayer) : index(index), @@ -273,8 +335,8 @@ void AutoDJProcessor::fadeNow() { pFromDeck->isFromDeck = true; pToDeck->isFromDeck = false; - const double fromDeckEndSecond = getEndSecond(pFromDeck); - const double toDeckEndSecond = getEndSecond(pToDeck); + const double fromDeckEndSecond = getEndSecond(*pFromDeck); + const double toDeckEndSecond = getEndSecond(*pToDeck); // Since the end position is measured in seconds from 0:00 it is also // the track duration. Use this alias for better readability. const double fromDeckDuration = fromDeckEndSecond; @@ -314,9 +376,9 @@ void AutoDJProcessor::fadeNow() { // there and do not seek back to the intro start. If they have seeked // past the introEnd or the introEnd is not marked, fall back to the // spinbox time. - double outroEnd = getOutroEndSecond(pFromDeck); - double introEnd = getIntroEndSecond(pToDeck); - double introStart = getIntroStartSecond(pToDeck); + double outroEnd = getOutroEndSecond(*pFromDeck); + double introEnd = getIntroEndSecond(*pToDeck); + double introStart = getIntroStartSecond(*pToDeck); double timeUntilOutroEnd = outroEnd - fromDeckCurrentSecond; // IntroStart ends up being equal to introEnd when pToDeck is @@ -652,7 +714,7 @@ void AutoDJProcessor::crossfaderChanged(double value) { if ((crossfaderPosition == 1.0 && pFromDeck->isLeft()) || // crossfader right (crossfaderPosition == -1.0 && pFromDeck->isRight())) { // crossfader left if (!pToDeck->isPlaying()) { - if (getEndSecond(pToDeck) >= kMinimumTrackDurationSec) { + if (getEndSecond(*pToDeck) >= kMinimumTrackDurationSec) { // Re-cue the track if the user has seeked it to the very end if (pToDeck->playPosition() >= pToDeck->fadeBeginPos) { pToDeck->setPlayPosition(pToDeck->startPos); @@ -817,7 +879,7 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, const double toDeckFadeDistance = (thisDeck->fadeEndPos - thisDeck->fadeBeginPos) * - getEndSecond(thisDeck) / getEndSecond(otherDeck); + getEndSecond(*thisDeck) / getEndSecond(*otherDeck); // Re-cue the track if the user has seeked forward and will miss the fadeBeginPos if (otherDeck->playPosition() >= otherDeck->fadeBeginPos - toDeckFadeDistance) { otherDeck->setPlayPosition(otherDeck->startPos); @@ -1108,64 +1170,64 @@ void AutoDJProcessor::playerOutroEndChanged(DeckAttributes* pAttributes, double calculateTransition(fromDeck, getOtherDeck(fromDeck), false); } -double AutoDJProcessor::getIntroStartSecond(DeckAttributes* pDeck) { - const mixxx::audio::FramePos trackEndPosition = pDeck->trackEndPosition(); - const mixxx::audio::FramePos introStartPosition = pDeck->introStartPosition(); - const mixxx::audio::FramePos introEndPosition = pDeck->introEndPosition(); +double AutoDJProcessor::getIntroStartSecond(const TrackOrDeckAttributes& track) { + const mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); + const mixxx::audio::FramePos introStartPosition = track.introStartPosition(); + const mixxx::audio::FramePos introEndPosition = track.introEndPosition(); if (!introStartPosition.isValid() || introStartPosition > trackEndPosition) { - double firstSoundSecond = getFirstSoundSecond(pDeck); + double firstSoundSecond = getFirstSoundSecond(track); if (!introEndPosition.isValid() || introEndPosition > trackEndPosition) { // No intro start and intro end set, use First Sound. return firstSoundSecond; } - double introEndSecond = framePositionToSeconds(introEndPosition, pDeck); + double introEndSecond = framePositionToSeconds(introEndPosition, track); if (m_transitionTime >= 0) { return introEndSecond - m_transitionTime; } return introEndSecond; } - return framePositionToSeconds(introStartPosition, pDeck); + return framePositionToSeconds(introStartPosition, track); } -double AutoDJProcessor::getIntroEndSecond(DeckAttributes* pDeck) { - const mixxx::audio::FramePos trackEndPosition = pDeck->trackEndPosition(); - const mixxx::audio::FramePos introEndPosition = pDeck->introEndPosition(); +double AutoDJProcessor::getIntroEndSecond(const TrackOrDeckAttributes& track) { + const mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); + const mixxx::audio::FramePos introEndPosition = track.introEndPosition(); if (!introEndPosition.isValid() || introEndPosition > trackEndPosition) { // Assume a zero length intro if introEnd is not set. // The introStart is automatically placed by AnalyzerSilence, so use // that as a fallback if the user has not placed outroStart. If it has // not been placed, getIntroStartPosition will return 0:00. - return getIntroStartSecond(pDeck); + return getIntroStartSecond(track); } - return framePositionToSeconds(introEndPosition, pDeck); + return framePositionToSeconds(introEndPosition, track); } -double AutoDJProcessor::getOutroStartSecond(DeckAttributes* pDeck) { - const mixxx::audio::FramePos trackEndPosition = pDeck->trackEndPosition(); - const mixxx::audio::FramePos outroStartPosition = pDeck->outroStartPosition(); +double AutoDJProcessor::getOutroStartSecond(const TrackOrDeckAttributes& track) { + const mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); + const mixxx::audio::FramePos outroStartPosition = track.outroStartPosition(); if (!outroStartPosition.isValid() || outroStartPosition > trackEndPosition) { // Assume a zero length outro if outroStart is not set. // The outroEnd is automatically placed by AnalyzerSilence, so use // that as a fallback if the user has not placed outroStart. If it has // not been placed, getOutroEndPosition will return the end of the track. - return getOutroEndSecond(pDeck); + return getOutroEndSecond(track); } - return framePositionToSeconds(outroStartPosition, pDeck); + return framePositionToSeconds(outroStartPosition, track); } -double AutoDJProcessor::getOutroEndSecond(DeckAttributes* pDeck) { - const mixxx::audio::FramePos trackEndPosition = pDeck->trackEndPosition(); - const mixxx::audio::FramePos outroStartPosition = pDeck->outroStartPosition(); - const mixxx::audio::FramePos outroEndPosition = pDeck->outroEndPosition(); +double AutoDJProcessor::getOutroEndSecond(const TrackOrDeckAttributes& track) { + const mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); + const mixxx::audio::FramePos outroStartPosition = track.outroStartPosition(); + const mixxx::audio::FramePos outroEndPosition = track.outroEndPosition(); if (!outroEndPosition.isValid() || outroEndPosition > trackEndPosition) { - double lastSoundSecond = getLastSoundSecond(pDeck); - DEBUG_ASSERT(lastSoundSecond <= framePositionToSeconds(trackEndPosition, pDeck)); + double lastSoundSecond = getLastSoundSecond(track); + DEBUG_ASSERT(lastSoundSecond <= framePositionToSeconds(trackEndPosition, track)); if (!outroStartPosition.isValid() || outroStartPosition > trackEndPosition) { // No outro start and outro end set, use Last Sound. return lastSoundSecond; } // Try to find a better Outro End using Outro Start and transition time - double outroStartSecond = framePositionToSeconds(outroStartPosition, pDeck); + double outroStartSecond = framePositionToSeconds(outroStartPosition, track); if (m_transitionTime >= 0 && lastSoundSecond > outroStartSecond) { double outroEndFromTime = outroStartSecond + m_transitionTime; if (outroEndFromTime < lastSoundSecond) { @@ -1178,25 +1240,25 @@ double AutoDJProcessor::getOutroEndSecond(DeckAttributes* pDeck) { } return outroStartSecond; } - return framePositionToSeconds(outroEndPosition, pDeck); + return framePositionToSeconds(outroEndPosition, track); } -double AutoDJProcessor::getFirstSoundSecond(DeckAttributes* pDeck) { - TrackPointer pTrack = pDeck->getLoadedTrack(); - if (!pTrack) { +double AutoDJProcessor::getFirstSoundSecond(const TrackOrDeckAttributes& track) { + TrackPointer trackData = track.getLoadedTrack(); + if (!trackData) { return 0.0; } - CuePointer pFromTrackN60dBSound = pTrack->findCueByType(mixxx::CueType::N60dBSound); + CuePointer pFromTrackN60dBSound = trackData->findCueByType(mixxx::CueType::N60dBSound); if (pFromTrackN60dBSound) { const mixxx::audio::FramePos firstSound = pFromTrackN60dBSound->getPosition(); if (firstSound.isValid()) { - const mixxx::audio::FramePos trackEndPosition = pDeck->trackEndPosition(); + const mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); if (firstSound <= trackEndPosition) { - return framePositionToSeconds(firstSound, pDeck); + return framePositionToSeconds(firstSound, track); } else { qWarning() << "-60 dB Sound Cue starts after track end in:" - << pTrack->getLocation() + << trackData->getLocation() << "Using the first sample instead."; } } @@ -1204,47 +1266,46 @@ double AutoDJProcessor::getFirstSoundSecond(DeckAttributes* pDeck) { return 0.0; } -double AutoDJProcessor::getLastSoundSecond(DeckAttributes* pDeck) { - TrackPointer pTrack = pDeck->getLoadedTrack(); - if (!pTrack) { +double AutoDJProcessor::getLastSoundSecond(const TrackOrDeckAttributes& track) { + TrackPointer trackData = track.getLoadedTrack(); + if (!trackData) { return 0.0; } - const mixxx::audio::FramePos trackEndPosition = pDeck->trackEndPosition(); - CuePointer pFromTrackN60dBSound = pTrack->findCueByType(mixxx::CueType::N60dBSound); + const mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); + CuePointer pFromTrackN60dBSound = trackData->findCueByType(mixxx::CueType::N60dBSound); if (pFromTrackN60dBSound && pFromTrackN60dBSound->getLengthFrames() > 0.0) { const mixxx::audio::FramePos lastSound = pFromTrackN60dBSound->getEndPosition(); if (lastSound > mixxx::audio::FramePos(0.0)) { if (lastSound <= trackEndPosition) { - return framePositionToSeconds(lastSound, pDeck); + return framePositionToSeconds(lastSound, track); } else { qWarning() << "-60 dB Sound Cue ends after track end in:" - << pTrack->getLocation() + << trackData->getLocation() << "Using the last sample instead."; } } } - return framePositionToSeconds(trackEndPosition, pDeck); + return framePositionToSeconds(trackEndPosition, track); } -double AutoDJProcessor::getEndSecond(DeckAttributes* pDeck) { - TrackPointer pTrack = pDeck->getLoadedTrack(); - if (!pTrack) { +double AutoDJProcessor::getEndSecond(const TrackOrDeckAttributes& track) { + if (track.isEmpty()) { return 0.0; } - mixxx::audio::FramePos trackEndPosition = pDeck->trackEndPosition(); - return framePositionToSeconds(trackEndPosition, pDeck); + mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); + return framePositionToSeconds(trackEndPosition, track); } double AutoDJProcessor::framePositionToSeconds( - mixxx::audio::FramePos position, DeckAttributes* pDeck) { - mixxx::audio::SampleRate sampleRate = pDeck->sampleRate(); + mixxx::audio::FramePos position, const TrackOrDeckAttributes& track) { + mixxx::audio::SampleRate sampleRate = track.sampleRate(); if (!sampleRate.isValid() || !position.isValid()) { return 0.0; } - return position.value() / sampleRate / pDeck->rateRatio(); + return position.value() / sampleRate / track.rateRatio(); } void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, @@ -1265,8 +1326,8 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, return; } - const double fromDeckEndPosition = getEndSecond(pFromDeck); - const double toDeckEndPosition = getEndSecond(pToDeck); + const double fromDeckEndPosition = getEndSecond(*pFromDeck); + const double toDeckEndPosition = getEndSecond(*pToDeck); // Since the end position is measured in seconds from 0:00 it is also // the track duration. Use this alias for better readability. const double fromDeckDuration = fromDeckEndPosition; @@ -1295,8 +1356,8 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, // Within this function, the outro refers to the outro of the currently // playing track and the intro refers to the intro of the next track. - double outroEnd = getOutroEndSecond(pFromDeck); - double outroStart = getOutroStartSecond(pFromDeck); + double outroEnd = getOutroEndSecond(*pFromDeck); + double outroStart = getOutroStartSecond(*pFromDeck); const double fromDeckPosition = fromDeckDuration * pFromDeck->playPosition(); VERIFY_OR_DEBUG_ASSERT(outroEnd <= fromDeckEndPosition) { @@ -1318,8 +1379,8 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, // This is used to check if it will be possible or a re-cue is required. // here it is done for FullIntroOutro and FadeAtOutroStart. // It is adjusted below for the other modes. - pToDeck->fadeEndPos = getOutroEndSecond(pToDeck); - double toDeckOutroStartSecond = getOutroStartSecond(pToDeck); + pToDeck->fadeEndPos = getOutroEndSecond(*pToDeck); + double toDeckOutroStartSecond = getOutroStartSecond(*pToDeck); if (pToDeck->fadeEndPos == toDeckOutroStartSecond) { // outro not defined, use transition time. toDeckOutroStartSecond -= m_transitionTime; @@ -1327,8 +1388,8 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, pToDeck->fadeBeginPos = toDeckOutroStartSecond; double toDeckStartSeconds = toDeckPositionSeconds; - const double introStart = getIntroStartSecond(pToDeck); - const double introEnd = getIntroEndSecond(pToDeck); + const double introStart = getIntroStartSecond(*pToDeck); + const double introEnd = getIntroEndSecond(*pToDeck); if (seekToStartPoint || toDeckPositionSeconds >= pToDeck->fadeBeginPos) { // toDeckPosition >= pToDeck->fadeBeginPos happens when the // user has seeked or played the to track behind fadeBeginPos of @@ -1458,13 +1519,13 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, } break; case TransitionMode::FixedSkipSilence: { double toDeckStartSecond; - pToDeck->fadeBeginPos = getLastSoundSecond(pToDeck); + pToDeck->fadeBeginPos = getLastSoundSecond(*pToDeck); if (seekToStartPoint || toDeckPositionSeconds >= pToDeck->fadeBeginPos) { // toDeckPosition >= pToDeck->fadeBeginPos happens when the // user has seeked or played the to track behind fadeBeginPos of // the fade after the next. // In this case we recue the track just before the transition. - toDeckStartSecond = getFirstSoundSecond(pToDeck); + toDeckStartSecond = getFirstSoundSecond(*pToDeck); } else { toDeckStartSecond = toDeckPositionSeconds; } @@ -1472,7 +1533,7 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, pFromDeck, pToDeck, fromDeckPosition, - getLastSoundSecond(pFromDeck), + getLastSoundSecond(*pFromDeck), toDeckStartSecond); } break; case TransitionMode::FixedFullTrack: @@ -1531,10 +1592,10 @@ void AutoDJProcessor::useFixedFadeTime( // we have already passed the outro start // Check OutroEnd as alternative, which is for all transition mode // better than directly default to duration() - double end = getOutroEndSecond(pToDeck); + double end = getOutroEndSecond(*pToDeck); if (end <= toDeckStartSecond + kMinimumTrackDurationSec) { // we have also passed the outro end - end = getEndSecond(pToDeck); + end = getEndSecond(*pToDeck); VERIFY_OR_DEBUG_ASSERT(end > toDeckStartSecond + kMinimumTrackDurationSec) { // as last resort move start point // The caller makes sure that this never happens @@ -1571,7 +1632,7 @@ void AutoDJProcessor::playerTrackLoaded(DeckAttributes* pDeck, TrackPointer pTra // Since the end position is measured in seconds from 0:00 it is also // the track duration. - double duration = getEndSecond(pDeck); + double duration = getEndSecond(*pDeck); if (duration < kMinimumTrackDurationSec) { qWarning() << "Skip track with" << duration << "Duration" << pTrack->getLocation(); diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index c6aabb3f513..c10714e70b3 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -17,7 +17,51 @@ class BaseTrackPlayer; class PlaylistTableModel; typedef QList QModelIndexList; -class DeckAttributes : public QObject { +class TrackOrDeckAttributes : public QObject { + Q_OBJECT + public: + virtual ~TrackOrDeckAttributes(); + + virtual mixxx::audio::FramePos introStartPosition() const = 0; + virtual mixxx::audio::FramePos introEndPosition() const = 0; + virtual mixxx::audio::FramePos outroStartPosition() const = 0; + virtual mixxx::audio::FramePos outroEndPosition() const = 0; + virtual mixxx::audio::SampleRate sampleRate() const = 0; + virtual mixxx::audio::FramePos trackEndPosition() const = 0; + virtual double rateRatio() const = 0; + + virtual TrackPointer getLoadedTrack() const = 0; + + bool isEmpty() const { + return !getLoadedTrack(); + } +}; + +/// Exposes the attributes of a track from the Auto DJ queue +class TrackAttributes : public TrackOrDeckAttributes { + Q_OBJECT + public: + TrackAttributes(TrackPointer pTrack); + virtual ~TrackAttributes(); + + virtual mixxx::audio::FramePos introStartPosition() const override; + virtual mixxx::audio::FramePos introEndPosition() const override; + virtual mixxx::audio::FramePos outroStartPosition() const override; + virtual mixxx::audio::FramePos outroEndPosition() const override; + virtual mixxx::audio::SampleRate sampleRate() const override; + virtual mixxx::audio::FramePos trackEndPosition() const override; + virtual double rateRatio() const override; + + TrackPointer getLoadedTrack() const override { + return m_pTrack; + } + + private: + TrackPointer m_pTrack; +}; + +/// Exposes the attributes of the track loaded in a certain player deck +class DeckAttributes : public TrackOrDeckAttributes { Q_OBJECT public: DeckAttributes(int index, @@ -60,35 +104,35 @@ class DeckAttributes : public QObject { m_repeat.set(enabled ? 1.0 : 0.0); } - mixxx::audio::FramePos introStartPosition() const { + mixxx::audio::FramePos introStartPosition() const override { return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_introStartPos.get()); } - mixxx::audio::FramePos introEndPosition() const { + mixxx::audio::FramePos introEndPosition() const override { return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_introEndPos.get()); } - mixxx::audio::FramePos outroStartPosition() const { + mixxx::audio::FramePos outroStartPosition() const override { return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_outroStartPos.get()); } - mixxx::audio::FramePos outroEndPosition() const { + mixxx::audio::FramePos outroEndPosition() const override { return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_outroEndPos.get()); } - mixxx::audio::SampleRate sampleRate() const { + mixxx::audio::SampleRate sampleRate() const override { return mixxx::audio::SampleRate::fromDouble(m_sampleRate.get()); } - mixxx::audio::FramePos trackEndPosition() const { + mixxx::audio::FramePos trackEndPosition() const override { return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_trackSamples.get()); } - double rateRatio() const { + double rateRatio() const override { return m_rateRatio.get(); } - TrackPointer getLoadedTrack() const; + TrackPointer getLoadedTrack() const override; signals: void playChanged(DeckAttributes* pDeck, bool playing); @@ -247,14 +291,15 @@ class AutoDJProcessor : public QObject { // Following functions return seconds computed from samples or -1 if // track in deck has invalid sample rate (<= 0) - double getIntroStartSecond(DeckAttributes* pDeck); - double getIntroEndSecond(DeckAttributes* pDeck); - double getOutroStartSecond(DeckAttributes* pDeck); - double getOutroEndSecond(DeckAttributes* pDeck); - double getFirstSoundSecond(DeckAttributes* pDeck); - double getLastSoundSecond(DeckAttributes* pDeck); - double getEndSecond(DeckAttributes* pDeck); - double framePositionToSeconds(mixxx::audio::FramePos position, DeckAttributes* pDeck); + double getIntroStartSecond(const TrackOrDeckAttributes& track); + double getIntroEndSecond(const TrackOrDeckAttributes& track); + double getOutroStartSecond(const TrackOrDeckAttributes& track); + double getOutroEndSecond(const TrackOrDeckAttributes& track); + double getFirstSoundSecond(const TrackOrDeckAttributes& track); + double getLastSoundSecond(const TrackOrDeckAttributes& track); + double getEndSecond(const TrackOrDeckAttributes& track); + double framePositionToSeconds(mixxx::audio::FramePos position, + const TrackOrDeckAttributes& track); TrackPointer getNextTrackFromQueue(); bool loadNextTrackFromQueue(const DeckAttributes& pDeck, bool play = false); From b6466311beddf45dfcf62fe47989c0414aa616bf Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sat, 27 Apr 2024 14:32:05 +0000 Subject: [PATCH 03/18] AutoDJProcessor: Add FadeableTrackOrDeckAttributes This is another step in generalizing the transition logic in AutoDJProcessor to enable calculating transition times for the whole queue. --- src/library/autodj/autodjprocessor.cpp | 48 +++++++++++++++----------- src/library/autodj/autodjprocessor.h | 25 +++++++++----- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index e8f022b05a0..244c9493a7c 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -26,6 +26,15 @@ constexpr bool sDebug = false; TrackOrDeckAttributes::~TrackOrDeckAttributes() { } +FadeableTrackOrDeckAttributes::FadeableTrackOrDeckAttributes() + : startPos(kKeepPosition), + fadeBeginPos(1.0), + fadeEndPos(1.0) { +} + +FadeableTrackOrDeckAttributes::~FadeableTrackOrDeckAttributes() { +} + TrackAttributes::TrackAttributes(TrackPointer pTrack) : m_pTrack(pTrack) { } @@ -89,9 +98,6 @@ DeckAttributes::DeckAttributes(int index, BaseTrackPlayer* pPlayer) : index(index), group(pPlayer->getGroup()), - startPos(kKeepPosition), - fadeBeginPos(1.0), - fadeEndPos(1.0), isFromDeck(false), loading(false), m_orientation(group, "orientation"), @@ -1463,7 +1469,7 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, pFromDeck->fadeEndPos = outroEnd; pToDeck->startPos = toDeckStartSeconds; } else { - useFixedFadeTime(pFromDeck, pToDeck, fromDeckPosition, outroEnd, toDeckStartSeconds); + useFixedFadeTime(*pFromDeck, *pToDeck, fromDeckPosition, outroEnd, toDeckStartSeconds); } } break; case TransitionMode::FadeAtOutroStart: { @@ -1514,7 +1520,7 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, pFromDeck->fadeEndPos = outroEnd; pToDeck->startPos = toDeckStartSeconds; } else { - useFixedFadeTime(pFromDeck, pToDeck, fromDeckPosition, outroEnd, toDeckStartSeconds); + useFixedFadeTime(*pFromDeck, *pToDeck, fromDeckPosition, outroEnd, toDeckStartSeconds); } } break; case TransitionMode::FixedSkipSilence: { @@ -1530,8 +1536,8 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, toDeckStartSecond = toDeckPositionSeconds; } useFixedFadeTime( - pFromDeck, - pToDeck, + *pFromDeck, + *pToDeck, fromDeckPosition, getLastSoundSecond(*pFromDeck), toDeckStartSecond); @@ -1549,7 +1555,7 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, } else { startPoint = toDeckPositionSeconds; } - useFixedFadeTime(pFromDeck, pToDeck, fromDeckPosition, fromDeckEndPosition, startPoint); + useFixedFadeTime(*pFromDeck, *pToDeck, fromDeckPosition, fromDeckEndPosition, startPoint); } } @@ -1575,16 +1581,16 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, } void AutoDJProcessor::useFixedFadeTime( - DeckAttributes* pFromDeck, - DeckAttributes* pToDeck, + FadeableTrackOrDeckAttributes& fromTrack, + FadeableTrackOrDeckAttributes& toTrack, double fromDeckSecond, double fadeEndSecond, double toDeckStartSecond) { if (m_transitionTime > 0.0) { // Guard against the next track being too short. This transition must finish // before the next transition starts. - double toDeckOutroStart = pToDeck->fadeBeginPos; - if (pToDeck->fadeBeginPos >= pToDeck->fadeEndPos) { + double toDeckOutroStart = toTrack.fadeBeginPos; + if (toTrack.fadeBeginPos >= toTrack.fadeEndPos) { // no outro defined, the toDeck will also use the transition time toDeckOutroStart -= m_transitionTime; } @@ -1592,10 +1598,10 @@ void AutoDJProcessor::useFixedFadeTime( // we have already passed the outro start // Check OutroEnd as alternative, which is for all transition mode // better than directly default to duration() - double end = getOutroEndSecond(*pToDeck); + double end = getOutroEndSecond(toTrack); if (end <= toDeckStartSecond + kMinimumTrackDurationSec) { // we have also passed the outro end - end = getEndSecond(*pToDeck); + end = getEndSecond(toTrack); VERIFY_OR_DEBUG_ASSERT(end > toDeckStartSecond + kMinimumTrackDurationSec) { // as last resort move start point // The caller makes sure that this never happens @@ -1610,15 +1616,15 @@ void AutoDJProcessor::useFixedFadeTime( VERIFY_OR_DEBUG_ASSERT(transitionTime >= kMinimumTrackDurationSec / 2) { transitionTime = kMinimumTrackDurationSec / 2; } - // Note: pFromDeck->fadeBeginPos >= pFromDeck->fadeEndPos is handled in + // Note: fromDeck.fadeBeginPos >= fromDeck.fadeEndPos is handled in // playerPositionChanged() causing a jump cut. - pFromDeck->fadeBeginPos = math_max(fadeEndSecond - transitionTime, fromDeckSecond); - pFromDeck->fadeEndPos = fadeEndSecond; - pToDeck->startPos = toDeckStartSecond; + fromTrack.fadeBeginPos = math_max(fadeEndSecond - transitionTime, fromDeckSecond); + fromTrack.fadeEndPos = fadeEndSecond; + toTrack.startPos = toDeckStartSecond; } else { - pFromDeck->fadeBeginPos = fadeEndSecond; - pFromDeck->fadeEndPos = fadeEndSecond; - pToDeck->startPos = toDeckStartSecond + m_transitionTime; + fromTrack.fadeBeginPos = fadeEndSecond; + fromTrack.fadeEndPos = fadeEndSecond; + toTrack.startPos = toDeckStartSecond + m_transitionTime; } } diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index c10714e70b3..12e9d12f409 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -37,8 +37,19 @@ class TrackOrDeckAttributes : public QObject { } }; +class FadeableTrackOrDeckAttributes : public TrackOrDeckAttributes { + Q_OBJECT + public: + FadeableTrackOrDeckAttributes(); + virtual ~FadeableTrackOrDeckAttributes(); + + double startPos; // Set in toDeck nature + double fadeBeginPos; // set in fromDeck nature + double fadeEndPos; // set in fromDeck nature +}; + /// Exposes the attributes of a track from the Auto DJ queue -class TrackAttributes : public TrackOrDeckAttributes { +class TrackAttributes : public FadeableTrackOrDeckAttributes { Q_OBJECT public: TrackAttributes(TrackPointer pTrack); @@ -61,7 +72,7 @@ class TrackAttributes : public TrackOrDeckAttributes { }; /// Exposes the attributes of the track loaded in a certain player deck -class DeckAttributes : public TrackOrDeckAttributes { +class DeckAttributes : public FadeableTrackOrDeckAttributes { Q_OBJECT public: DeckAttributes(int index, @@ -161,9 +172,6 @@ class DeckAttributes : public TrackOrDeckAttributes { public: int index; QString group; - double startPos; // Set in toDeck nature - double fadeBeginPos; // set in fromDeck nature - double fadeEndPos; // set in fromDeck nature bool isFromDeck; bool loading; // The data is inconsistent during loading a deck @@ -303,12 +311,13 @@ class AutoDJProcessor : public QObject { TrackPointer getNextTrackFromQueue(); bool loadNextTrackFromQueue(const DeckAttributes& pDeck, bool play = false); - void calculateTransition(DeckAttributes* pFromDeck, + void calculateTransition( + DeckAttributes* pFromDeck, DeckAttributes* pToDeck, bool seekToStartPoint); void useFixedFadeTime( - DeckAttributes* pFromDeck, - DeckAttributes* pToDeck, + FadeableTrackOrDeckAttributes& fromTrack, + FadeableTrackOrDeckAttributes& toTrack, double fromDeckSecond, double fadeEndSecond, double toDeckStartSecond); From 3eba5f2ae4f9da382d3826b761a3c9568d571007 Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sat, 27 Apr 2024 15:18:48 +0000 Subject: [PATCH 04/18] AutoDJProcessor: Add calculateTransitionImpl This is the final step in generalizing the core transition timing logic in AutoDJProcessor, so that it can be applied to a whole playlist instead of just the current decks. --- src/library/autodj/autodjprocessor.cpp | 160 +++++++++++++++---------- src/library/autodj/autodjprocessor.h | 11 +- 2 files changed, 107 insertions(+), 64 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 244c9493a7c..ddd2cc31452 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -16,6 +16,7 @@ const char* kTransitionPreferenceName = "Transition"; const char* kTransitionModePreferenceName = "TransitionMode"; constexpr double kTransitionPreferenceDefault = 10.0; constexpr double kKeepPosition = -1.0; +constexpr double kSkipToNextTrack = -2.0; // A track needs to be longer than two callbacks to not stop AutoDJ constexpr double kMinimumTrackDurationSec = 0.2; @@ -29,7 +30,9 @@ TrackOrDeckAttributes::~TrackOrDeckAttributes() { FadeableTrackOrDeckAttributes::FadeableTrackOrDeckAttributes() : startPos(kKeepPosition), fadeBeginPos(1.0), - fadeEndPos(1.0) { + fadeEndPos(1.0), + fadeDurationSeconds(0.0), + isFromDeck(false) { } FadeableTrackOrDeckAttributes::~FadeableTrackOrDeckAttributes() { @@ -78,6 +81,10 @@ mixxx::audio::SampleRate TrackAttributes::sampleRate() const { return m_pTrack->getSampleRate(); } +double TrackAttributes::playPosition() const { + return 0.0; +} + double TrackAttributes::rateRatio() const { return 1.0; } @@ -98,7 +105,6 @@ DeckAttributes::DeckAttributes(int index, BaseTrackPlayer* pPlayer) : index(index), group(pPlayer->getGroup()), - isFromDeck(false), loading(false), m_orientation(group, "orientation"), m_playPos(group, "playposition"), @@ -1332,8 +1338,32 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, return; } - const double fromDeckEndPosition = getEndSecond(*pFromDeck); - const double toDeckEndPosition = getEndSecond(*pToDeck); + calculateTransitionImpl(*pFromDeck, *pToDeck, seekToStartPoint); + + if (pToDeck->startPos == kSkipToNextTrack) { + // This is a safety measure to handle tracks that are too short. + // Immediately skip to the next track instead. + loadNextTrackFromQueue(*pToDeck, false); + return; + } + + if constexpr (sDebug) { + qDebug() << this << "calculateTransition" << pFromDeck->group + << pFromDeck->fadeBeginPos << pFromDeck->fadeEndPos + << pToDeck->startPos; + } +} + +void AutoDJProcessor::calculateTransitionImpl( + FadeableTrackOrDeckAttributes& fromTrack, + FadeableTrackOrDeckAttributes& toTrack, + bool seekToStartPoint) { + // =================================== + // Check for tracks that are too short + // =================================== + + const double fromDeckEndPosition = getEndSecond(fromTrack); + const double toDeckEndPosition = getEndSecond(toTrack); // Since the end position is measured in seconds from 0:00 it is also // the track duration. Use this alias for better readability. const double fromDeckDuration = fromDeckEndPosition; @@ -1342,29 +1372,35 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, VERIFY_OR_DEBUG_ASSERT(fromDeckDuration >= kMinimumTrackDurationSec) { // Track has no duration or too short. This should not happen, because short // tracks are skipped after load. Play ToDeck immediately. - pFromDeck->fadeBeginPos = 0; - pFromDeck->fadeEndPos = 0; - pToDeck->startPos = kKeepPosition; + fromTrack.fadeBeginPos = 0; + fromTrack.fadeEndPos = 0; + toTrack.startPos = kKeepPosition; return; } + if (toDeckDuration == 0) { // This is a seek call to zero after ejecting the track // this signal is received before the track pointer becomes null return; } + VERIFY_OR_DEBUG_ASSERT(toDeckDuration >= kMinimumTrackDurationSec) { // Track has no duration or too short. This should not happen, because short - // tracks are skipped after load. - loadNextTrackFromQueue(*pToDeck, false); + // tracks are skipped after load. Immediately pick next track from queue. + toTrack.startPos = kSkipToNextTrack; return; } + // ======================== + // Handle intros and outros + // ======================== + // Within this function, the outro refers to the outro of the currently // playing track and the intro refers to the intro of the next track. - double outroEnd = getOutroEndSecond(*pFromDeck); - double outroStart = getOutroStartSecond(*pFromDeck); - const double fromDeckPosition = fromDeckDuration * pFromDeck->playPosition(); + double outroEnd = getOutroEndSecond(fromTrack); + double outroStart = getOutroStartSecond(fromTrack); + const double fromDeckPosition = fromDeckDuration * fromTrack.playPosition(); VERIFY_OR_DEBUG_ASSERT(outroEnd <= fromDeckEndPosition) { outroEnd = fromDeckEndPosition; @@ -1380,24 +1416,24 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, } double outroLength = outroEnd - outroStart; - double toDeckPositionSeconds = toDeckDuration * pToDeck->playPosition(); + double toDeckPositionSeconds = toDeckDuration * toTrack.playPosition(); // Store here a possible fadeBeginPos for the transition after next // This is used to check if it will be possible or a re-cue is required. // here it is done for FullIntroOutro and FadeAtOutroStart. // It is adjusted below for the other modes. - pToDeck->fadeEndPos = getOutroEndSecond(*pToDeck); - double toDeckOutroStartSecond = getOutroStartSecond(*pToDeck); - if (pToDeck->fadeEndPos == toDeckOutroStartSecond) { + toTrack.fadeEndPos = getOutroEndSecond(toTrack); + double toDeckOutroStartSecond = getOutroStartSecond(toTrack); + if (toTrack.fadeEndPos == toDeckOutroStartSecond) { // outro not defined, use transition time. toDeckOutroStartSecond -= m_transitionTime; } - pToDeck->fadeBeginPos = toDeckOutroStartSecond; + toTrack.fadeBeginPos = toDeckOutroStartSecond; double toDeckStartSeconds = toDeckPositionSeconds; - const double introStart = getIntroStartSecond(*pToDeck); - const double introEnd = getIntroEndSecond(*pToDeck); - if (seekToStartPoint || toDeckPositionSeconds >= pToDeck->fadeBeginPos) { - // toDeckPosition >= pToDeck->fadeBeginPos happens when the + const double introStart = getIntroStartSecond(toTrack); + const double introEnd = getIntroEndSecond(toTrack); + if (seekToStartPoint || toDeckPositionSeconds >= toTrack.fadeBeginPos) { + // toDeckPosition >= toTrack.fadeBeginPos happens when the // user has seeked or played the to track behind fadeBeginPos of // the fade after the next. // In this case we recue the track just before the transition. @@ -1457,19 +1493,19 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, } if (transitionLength > 0) { const double transitionEnd = toDeckStartSeconds + transitionLength; - if (transitionEnd > pToDeck->fadeBeginPos) { + if (transitionEnd > toTrack.fadeBeginPos) { // End intro before next outro starts - transitionLength = pToDeck->fadeBeginPos - toDeckStartSeconds; + transitionLength = toTrack.fadeBeginPos - toDeckStartSeconds; VERIFY_OR_DEBUG_ASSERT(transitionLength > 0) { // We seek to intro start above in this case so this never happens transitionLength = 1; } } - pFromDeck->fadeBeginPos = outroEnd - transitionLength; - pFromDeck->fadeEndPos = outroEnd; - pToDeck->startPos = toDeckStartSeconds; + fromTrack.fadeBeginPos = outroEnd - transitionLength; + fromTrack.fadeEndPos = outroEnd; + toTrack.startPos = toDeckStartSeconds; } else { - useFixedFadeTime(*pFromDeck, *pToDeck, fromDeckPosition, outroEnd, toDeckStartSeconds); + useFixedFadeTime(fromTrack, toTrack, fromDeckPosition, outroEnd, toDeckStartSeconds); } } break; case TransitionMode::FadeAtOutroStart: { @@ -1503,51 +1539,51 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, } } const double transitionEnd = toDeckStartSeconds + transitionLength; - if (transitionEnd > pToDeck->fadeBeginPos) { + if (transitionEnd > toTrack.fadeBeginPos) { // End intro before next outro starts - transitionLength = pToDeck->fadeBeginPos - toDeckStartSeconds; + transitionLength = toTrack.fadeBeginPos - toDeckStartSeconds; VERIFY_OR_DEBUG_ASSERT(transitionLength > 0) { // We seek to intro start above in this case so this never happens transitionLength = 1; } } - pFromDeck->fadeBeginPos = outroStart; - pFromDeck->fadeEndPos = outroStart + transitionLength; - pToDeck->startPos = toDeckStartSeconds; + fromTrack.fadeBeginPos = outroStart; + fromTrack.fadeEndPos = outroStart + transitionLength; + toTrack.startPos = toDeckStartSeconds; } else if (introLength > 0) { transitionLength = introLength; - pFromDeck->fadeBeginPos = outroEnd - transitionLength; - pFromDeck->fadeEndPos = outroEnd; - pToDeck->startPos = toDeckStartSeconds; + fromTrack.fadeBeginPos = outroEnd - transitionLength; + fromTrack.fadeEndPos = outroEnd; + toTrack.startPos = toDeckStartSeconds; } else { - useFixedFadeTime(*pFromDeck, *pToDeck, fromDeckPosition, outroEnd, toDeckStartSeconds); + useFixedFadeTime(fromTrack, toTrack, fromDeckPosition, outroEnd, toDeckStartSeconds); } } break; case TransitionMode::FixedSkipSilence: { double toDeckStartSecond; - pToDeck->fadeBeginPos = getLastSoundSecond(*pToDeck); - if (seekToStartPoint || toDeckPositionSeconds >= pToDeck->fadeBeginPos) { - // toDeckPosition >= pToDeck->fadeBeginPos happens when the + toTrack.fadeBeginPos = getLastSoundSecond(toTrack); + if (seekToStartPoint || toDeckPositionSeconds >= toTrack.fadeBeginPos) { + // toDeckPosition >= toTrack.fadeBeginPos happens when the // user has seeked or played the to track behind fadeBeginPos of // the fade after the next. // In this case we recue the track just before the transition. - toDeckStartSecond = getFirstSoundSecond(*pToDeck); + toDeckStartSecond = getFirstSoundSecond(toTrack); } else { toDeckStartSecond = toDeckPositionSeconds; } useFixedFadeTime( - *pFromDeck, - *pToDeck, + fromTrack, + toTrack, fromDeckPosition, - getLastSoundSecond(*pFromDeck), + getLastSoundSecond(fromTrack), toDeckStartSecond); } break; case TransitionMode::FixedFullTrack: default: { double startPoint; - pToDeck->fadeBeginPos = toDeckEndPosition; - if (seekToStartPoint || toDeckPositionSeconds >= pToDeck->fadeBeginPos) { - // toDeckPosition >= pToDeck->fadeBeginPos happens when the + toTrack.fadeBeginPos = toDeckEndPosition; + if (seekToStartPoint || toDeckPositionSeconds >= toTrack.fadeBeginPos) { + // toDeckPosition >= toTrack.fadeBeginPos happens when the // user has seeked or played the to track behind fadeBeginPos of // the fade after the next. // In this case we recue the track just before the transition. @@ -1555,28 +1591,28 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, } else { startPoint = toDeckPositionSeconds; } - useFixedFadeTime(*pFromDeck, *pToDeck, fromDeckPosition, fromDeckEndPosition, startPoint); + useFixedFadeTime(fromTrack, toTrack, fromDeckPosition, fromDeckEndPosition, startPoint); } } - // These are expected to be a fraction of the track length. - pFromDeck->fadeBeginPos /= fromDeckDuration; - pFromDeck->fadeEndPos /= fromDeckDuration; - pToDeck->startPos /= toDeckDuration; - pToDeck->fadeBeginPos /= toDeckDuration; - pToDeck->fadeEndPos /= toDeckDuration; + // A negative startPos (and therefore fadeDuration) indicates + // that there is silence between the tracks instead of an overlap. + fromTrack.fadeDurationSeconds = + (fromTrack.fadeEndPos - fromTrack.fadeBeginPos) + + math_max(0.0, toTrack.startPos); - pFromDeck->isFromDeck = true; - pToDeck->isFromDeck = false; + // The positions are expected to be a fraction of the track length. + fromTrack.fadeBeginPos /= fromDeckDuration; + fromTrack.fadeEndPos /= fromDeckDuration; + toTrack.startPos /= toDeckDuration; + toTrack.fadeBeginPos /= toDeckDuration; + toTrack.fadeEndPos /= toDeckDuration; - VERIFY_OR_DEBUG_ASSERT(pFromDeck->fadeBeginPos <= 1) { - pFromDeck->fadeBeginPos = 1; - } + fromTrack.isFromDeck = true; + toTrack.isFromDeck = false; - if constexpr (sDebug) { - qDebug() << this << "calculateTransition" << pFromDeck->group - << pFromDeck->fadeBeginPos << pFromDeck->fadeEndPos - << pToDeck->startPos; + VERIFY_OR_DEBUG_ASSERT(fromTrack.fadeBeginPos <= 1) { + fromTrack.fadeBeginPos = 1; } } diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index 12e9d12f409..c5274edc478 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -28,6 +28,7 @@ class TrackOrDeckAttributes : public QObject { virtual mixxx::audio::FramePos outroEndPosition() const = 0; virtual mixxx::audio::SampleRate sampleRate() const = 0; virtual mixxx::audio::FramePos trackEndPosition() const = 0; + virtual double playPosition() const = 0; virtual double rateRatio() const = 0; virtual TrackPointer getLoadedTrack() const = 0; @@ -46,6 +47,8 @@ class FadeableTrackOrDeckAttributes : public TrackOrDeckAttributes { double startPos; // Set in toDeck nature double fadeBeginPos; // set in fromDeck nature double fadeEndPos; // set in fromDeck nature + double fadeDurationSeconds; + bool isFromDeck; }; /// Exposes the attributes of a track from the Auto DJ queue @@ -61,6 +64,7 @@ class TrackAttributes : public FadeableTrackOrDeckAttributes { virtual mixxx::audio::FramePos outroEndPosition() const override; virtual mixxx::audio::SampleRate sampleRate() const override; virtual mixxx::audio::FramePos trackEndPosition() const override; + virtual double playPosition() const override; virtual double rateRatio() const override; TrackPointer getLoadedTrack() const override { @@ -99,7 +103,7 @@ class DeckAttributes : public FadeableTrackOrDeckAttributes { m_play.set(1.0); } - double playPosition() const { + double playPosition() const override { return m_playPos.get(); } @@ -172,7 +176,6 @@ class DeckAttributes : public FadeableTrackOrDeckAttributes { public: int index; QString group; - bool isFromDeck; bool loading; // The data is inconsistent during loading a deck private: @@ -315,6 +318,10 @@ class AutoDJProcessor : public QObject { DeckAttributes* pFromDeck, DeckAttributes* pToDeck, bool seekToStartPoint); + void calculateTransitionImpl( + FadeableTrackOrDeckAttributes& pFromDeck, + FadeableTrackOrDeckAttributes& pToDeck, + bool seekToStartPoint); void useFixedFadeTime( FadeableTrackOrDeckAttributes& fromTrack, FadeableTrackOrDeckAttributes& toTrack, From e02dd542df50171956d8b4b140ebb9c8f21598e8 Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sat, 27 Apr 2024 15:34:40 +0000 Subject: [PATCH 05/18] AutoDJProcessor: Feature: Accurately calculate the remaining play time --- src/library/autodj/autodjprocessor.cpp | 45 +++++++++++++++++++++++++- src/library/autodj/autodjprocessor.h | 5 +++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index ddd2cc31452..407d225a24f 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -299,7 +299,50 @@ void AutoDJProcessor::setCrossfader(double value) { void AutoDJProcessor::playlistTracksChanged() { m_pTracksRemaining->set(m_pAutoDJTableModel->rowCount()); - m_pTimeRemaining->set(m_pAutoDJTableModel->getTotalDuration().toDoubleSeconds()); + m_pTimeRemaining->set(calculateRemainingTime().toDoubleSeconds()); +} + +mixxx::Duration AutoDJProcessor::calculateRemainingTime() { + if (m_transitionMode == TransitionMode::FullIntroOutro || + m_transitionMode == TransitionMode::FadeAtOutroStart || + m_transitionMode == TransitionMode::FixedSkipSilence) { + // The transition time between two tracks depends on both + // tracks for some transition modes + TrackPointer previousTrack; + + double durationTotal = 0.0; + const int numOfTracks = m_pAutoDJTableModel->rowCount(); + for (int i = 0; i < numOfTracks; i++) { + TrackPointer track = m_pAutoDJTableModel->getTrack(m_pAutoDJTableModel->index(i, 0)); + if (previousTrack) { + TrackAttributes fromTrack(previousTrack); + TrackAttributes toTrack(track); + calculateTransitionImpl(fromTrack, toTrack, true); + durationTotal += track->getDuration() - fromTrack.fadeDurationSeconds; + } else { + // TODO: Take the transition between an already playing deck + // and the top of the Auto DJ queue into account? + durationTotal += track->getDuration(); + } + previousTrack = track; + } + + return mixxx::Duration::fromSeconds(durationTotal); + } else { + // This is the simplest case of the tracks' actual play time + // being equal to their duration minus the fixed fade time. + // No fade time is applied to the last track. + int numTracks = m_pAutoDJTableModel->rowCount(); + if (numTracks >= 2) { + // A negative transition time causes silence to be inserted + // between the tracks, which is accurately reflected here + // as an increase of the total playtime. + return m_pAutoDJTableModel->getTotalDuration() - + mixxx::Duration::fromSeconds((numTracks - 1) * m_transitionTime); + } else { + return m_pAutoDJTableModel->getTotalDuration(); + } + } } AutoDJProcessor::AutoDJError AutoDJProcessor::shufflePlaylist( diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index c5274edc478..e50f46b70e8 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -9,6 +9,7 @@ #include "preferences/usersettings.h" #include "track/track_decl.h" #include "util/class.h" +#include "util/duration.h" class ControlPushButton; class TrackCollectionManager; @@ -333,6 +334,10 @@ class AutoDJProcessor : public QObject { DeckAttributes* getOtherDeck(const DeckAttributes* pThisDeck); DeckAttributes* getFromDeck(); + /// Calculates the total remaining duration of tracks in the AutoDJ playlist, + /// excluding the track that is currently playing already. + mixxx::Duration calculateRemainingTime(); + // Removes the track loaded to the player group from the top of the AutoDJ // queue if it is present. bool removeLoadedTrackFromTopOfQueue(const DeckAttributes& deck); From 322628ac19b0f4730715d8ae8060283e728ffdba Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sat, 27 Apr 2024 19:34:01 +0000 Subject: [PATCH 06/18] AutoDJProcessor: Trigger recalculation of the remaining play time when relevant The following data points are used as inputs for the "remaining time" calculation, and should therefore trigger a recalculation: * The list of tracks in the Auto DJ playlist (both the set of tracks and their order are important). We subscribe to PlaylistTableModel::tracksChanged to receive the relevant notifications. * The AutoDJ transition mode and transition time settings. We will be notified via ::setTransitionMode and ::setTransitionTime, respectively. * The Intro, Outro & N60dBSound cues of all tracks that are contained in the Auto DJ queue. We subscribe to TrackCollection::tracksChanged and TrackCollection::multipleTracksChanged to receive notifications about possible changes. As of now, we do not filter the notifications, and simply trigger a recalculation when ANY track has changed. --- src/library/autodj/autodjprocessor.cpp | 61 +++++++++++++++++++++++++- src/library/autodj/autodjprocessor.h | 4 ++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 407d225a24f..3014c9ccc16 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -4,6 +4,8 @@ #include "control/controlpushbutton.h" #include "engine/channels/enginedeck.h" #include "library/playlisttablemodel.h" +#include "library/trackcollection.h" +#include "library/trackcollectionmanager.h" #include "mixer/basetrackplayer.h" #include "mixer/playermanager.h" #include "moc_autodjprocessor.cpp" @@ -235,6 +237,14 @@ AutoDJProcessor::AutoDJProcessor( &PlaylistTableModel::playlistTracksChanged, this, &AutoDJProcessor::playlistTracksChanged); + connect(pTrackCollectionManager->internalCollection(), + &TrackCollection::tracksChanged, + this, + &AutoDJProcessor::tracksChanged); + connect(pTrackCollectionManager->internalCollection(), + &TrackCollection::multipleTracksChanged, + this, + &AutoDJProcessor::multipleTracksChanged); // TODO(rryan) listen to signals from PlayerManager and add/remove as decks // are created. @@ -299,6 +309,43 @@ void AutoDJProcessor::setCrossfader(double value) { void AutoDJProcessor::playlistTracksChanged() { m_pTracksRemaining->set(m_pAutoDJTableModel->rowCount()); + updateRemainingTime(); +} + +void AutoDJProcessor::tracksChanged(const QSet& tracks) { + Q_UNUSED(tracks); + updateRemainingTime(); +} + +void AutoDJProcessor::multipleTracksChanged() { + updateRemainingTime(); +} + +void AutoDJProcessor::updateRemainingTime() { + // The following data points are used as inputs for the "remaining time" + // calculation, and should therefore trigger a recalculation: + // + // * The list of tracks in the Auto DJ playlist (both + // the set of tracks and their order are important). + // + // We subscribe to PlaylistTableModel::tracksChanged + // to receive the relevant notifications. + // + // * The AutoDJ transition mode and transition time settings. + // We will be notified via ::setTransitionMode + // and ::setTransitionTime, respectively. + // + // * The Intro, Outro & N60dBSound cues of all tracks + // that are contained in the Auto DJ queue. + // + // We subscribe to TrackCollection::tracksChanged + // and TrackCollection::multipleTracksChanged to + // receive notifications about possible changes. + // + // As of now, we do not filter the notifications, + // and simply trigger a recalculation when ANY + // track has changed. + // m_pTimeRemaining->set(calculateRemainingTime().toDoubleSeconds()); } @@ -1847,6 +1894,7 @@ void AutoDJProcessor::setTransitionTime(int time) { // User has changed the orientation, disable Auto DJ toggleAutoDJ(false); emit autoDJError(ADJ_NOT_TWO_DECKS); + updateRemainingTime(); return; } if (pLeftDeck->isPlaying()) { @@ -1856,6 +1904,10 @@ void AutoDJProcessor::setTransitionTime(int time) { calculateTransition(pRightDeck, pLeftDeck, false); } } + + // Recalculate the duration of the Auto DJ playlist, + // which may have been affected by the transition time change + updateRemainingTime(); } void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { @@ -1864,7 +1916,9 @@ void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { m_transitionMode = newMode; if (m_eState != ADJ_IDLE) { - // We don't want to recalculate a running transition + // We don't want to recalculate a running transition, + // only the remaining queue play time + updateRemainingTime(); return; } @@ -1876,6 +1930,7 @@ void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { // User has changed the orientation, disable Auto DJ toggleAutoDJ(false); emit autoDJError(ADJ_NOT_TWO_DECKS); + updateRemainingTime(); return; } @@ -1897,6 +1952,10 @@ void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { // user has manually started the other deck or stopped both. // don't know what to do. } + + // Recalculate the duration of the Auto DJ playlist, + // which may have been affected by the transition mode change + updateRemainingTime(); } DeckAttributes* AutoDJProcessor::getLeftDeck() { diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index e50f46b70e8..bd9e7ff2d0b 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -8,6 +8,7 @@ #include "engine/channels/enginechannel.h" #include "preferences/usersettings.h" #include "track/track_decl.h" +#include "track/trackid.h" #include "util/class.h" #include "util/duration.h" @@ -277,6 +278,9 @@ class AutoDJProcessor : public QObject { void playerRateChanged(DeckAttributes* pDeck); void playlistTracksChanged(); + void tracksChanged(const QSet& tracks); + void multipleTracksChanged(); + void updateRemainingTime(); void controlEnableChangeRequest(double value); void controlFadeNow(double value); From 6712b63436815c9746c334a38d3a5a92f8da609a Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sun, 19 May 2024 14:40:33 +0000 Subject: [PATCH 07/18] AutoDJProcessor: Expose getter methods in addition to ControlObjects for the remaining time & tracks --- src/library/autodj/autodjprocessor.cpp | 9 ++++++++- src/library/autodj/autodjprocessor.h | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 3014c9ccc16..f89dbd1362a 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -321,6 +321,10 @@ void AutoDJProcessor::multipleTracksChanged() { updateRemainingTime(); } +int AutoDJProcessor::getRemainingTracks() const { + return m_pAutoDJTableModel->rowCount(); +} + void AutoDJProcessor::updateRemainingTime() { // The following data points are used as inputs for the "remaining time" // calculation, and should therefore trigger a recalculation: @@ -346,7 +350,10 @@ void AutoDJProcessor::updateRemainingTime() { // and simply trigger a recalculation when ANY // track has changed. // - m_pTimeRemaining->set(calculateRemainingTime().toDoubleSeconds()); + mixxx::Duration remainingTime = calculateRemainingTime(); + m_pTimeRemaining->set(remainingTime.toDoubleSeconds()); + m_timeRemaining = remainingTime; + emit remainingTimeChanged(getRemainingTracks(), remainingTime); } mixxx::Duration AutoDJProcessor::calculateRemainingTime() { diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index bd9e7ff2d0b..e6a581ab85e 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -246,6 +246,12 @@ class AutoDJProcessor : public QObject { return m_pAutoDJTableModel; } + mixxx::Duration getRemainingTime() const { + return m_timeRemaining; + } + + int getRemainingTracks() const; + bool nextTrackLoaded(); void setTransitionTime(int seconds); @@ -261,6 +267,7 @@ class AutoDJProcessor : public QObject { void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play); void autoDJStateChanged(AutoDJProcessor::AutoDJState state); void autoDJError(AutoDJProcessor::AutoDJError error); + void remainingTimeChanged(int numTracks, mixxx::Duration duration); void transitionTimeChanged(int time); void randomTrackRequested(int tracksToAdd); @@ -371,6 +378,7 @@ class AutoDJProcessor : public QObject { ControlObject* m_pTracksRemaining; ControlObject* m_pTimeRemaining; + mixxx::Duration m_timeRemaining; DISALLOW_COPY_AND_ASSIGN(AutoDJProcessor); }; From 7324a767d9f878945f3d14c09abb49090d0964e7 Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sat, 18 May 2024 11:05:31 +0000 Subject: [PATCH 08/18] AutoDJProcessor: Readability: Append "Second" suffix to variables --- src/library/autodj/autodjprocessor.cpp | 68 ++++++++++++++------------ 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index f89dbd1362a..5d4c77dc277 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1459,12 +1459,12 @@ void AutoDJProcessor::calculateTransitionImpl( // Check for tracks that are too short // =================================== - const double fromDeckEndPosition = getEndSecond(fromTrack); - const double toDeckEndPosition = getEndSecond(toTrack); + const double fromDeckEndSecond = getEndSecond(fromTrack); + const double toDeckEndSecond = getEndSecond(toTrack); // Since the end position is measured in seconds from 0:00 it is also // the track duration. Use this alias for better readability. - const double fromDeckDuration = fromDeckEndPosition; - const double toDeckDuration = toDeckEndPosition; + const double fromDeckDuration = fromDeckEndSecond; + const double toDeckDuration = toDeckEndSecond; VERIFY_OR_DEBUG_ASSERT(fromDeckDuration >= kMinimumTrackDurationSec) { // Track has no duration or too short. This should not happen, because short @@ -1495,23 +1495,23 @@ void AutoDJProcessor::calculateTransitionImpl( // Within this function, the outro refers to the outro of the currently // playing track and the intro refers to the intro of the next track. - double outroEnd = getOutroEndSecond(fromTrack); - double outroStart = getOutroStartSecond(fromTrack); + double outroEndSecond = getOutroEndSecond(fromTrack); + double outroStartSecond = getOutroStartSecond(fromTrack); const double fromDeckPosition = fromDeckDuration * fromTrack.playPosition(); - VERIFY_OR_DEBUG_ASSERT(outroEnd <= fromDeckEndPosition) { - outroEnd = fromDeckEndPosition; + VERIFY_OR_DEBUG_ASSERT(outroEndSecond <= fromDeckEndSecond) { + outroEndSecond = fromDeckEndSecond; } - if (fromDeckPosition > outroStart) { + if (fromDeckPosition > outroStartSecond) { // We have already passed outroStart // This can happen if we have just enabled auto DJ - outroStart = fromDeckPosition; - if (fromDeckPosition > outroEnd) { - outroEnd = math_min(outroStart + fabs(m_transitionTime), fromDeckEndPosition); + outroStartSecond = fromDeckPosition; + if (fromDeckPosition > outroEndSecond) { + outroEndSecond = math_min(outroStartSecond + fabs(m_transitionTime), fromDeckEndSecond); } } - double outroLength = outroEnd - outroStart; + double outroLength = outroEndSecond - outroStartSecond; double toDeckPositionSeconds = toDeckDuration * toTrack.playPosition(); // Store here a possible fadeBeginPos for the transition after next @@ -1527,25 +1527,25 @@ void AutoDJProcessor::calculateTransitionImpl( toTrack.fadeBeginPos = toDeckOutroStartSecond; double toDeckStartSeconds = toDeckPositionSeconds; - const double introStart = getIntroStartSecond(toTrack); - const double introEnd = getIntroEndSecond(toTrack); + const double introStartSecond = getIntroStartSecond(toTrack); + const double introEndSecond = getIntroEndSecond(toTrack); if (seekToStartPoint || toDeckPositionSeconds >= toTrack.fadeBeginPos) { // toDeckPosition >= toTrack.fadeBeginPos happens when the // user has seeked or played the to track behind fadeBeginPos of // the fade after the next. // In this case we recue the track just before the transition. - toDeckStartSeconds = introStart; + toDeckStartSeconds = introStartSecond; } double introLength = 0; // introEnd is equal introStart in case it has not yet been set - if (toDeckStartSeconds < introEnd && introStart < introEnd) { + if (toDeckStartSeconds < introEndSecond && introStartSecond < introEndSecond) { // Limit the intro length that results from a revers seek // to a reasonable values. If the seek was too big, ignore it. - introLength = introEnd - toDeckStartSeconds; - if (introLength > (introEnd - introStart) * 2 && - introLength > (introEnd - introStart) + m_transitionTime && + introLength = introEndSecond - toDeckStartSeconds; + if (introLength > (introEndSecond - introStartSecond) * 2 && + introLength > (introEndSecond - introStartSecond) + m_transitionTime && introLength > outroLength) { introLength = 0; } @@ -1598,11 +1598,15 @@ void AutoDJProcessor::calculateTransitionImpl( transitionLength = 1; } } - fromTrack.fadeBeginPos = outroEnd - transitionLength; - fromTrack.fadeEndPos = outroEnd; + fromTrack.fadeBeginPos = outroEndSecond - transitionLength; + fromTrack.fadeEndPos = outroEndSecond; toTrack.startPos = toDeckStartSeconds; } else { - useFixedFadeTime(fromTrack, toTrack, fromDeckPosition, outroEnd, toDeckStartSeconds); + useFixedFadeTime(fromTrack, + toTrack, + fromDeckPosition, + outroEndSecond, + toDeckStartSeconds); } } break; case TransitionMode::FadeAtOutroStart: { @@ -1644,16 +1648,20 @@ void AutoDJProcessor::calculateTransitionImpl( transitionLength = 1; } } - fromTrack.fadeBeginPos = outroStart; - fromTrack.fadeEndPos = outroStart + transitionLength; + fromTrack.fadeBeginPos = outroStartSecond; + fromTrack.fadeEndPos = outroStartSecond + transitionLength; toTrack.startPos = toDeckStartSeconds; } else if (introLength > 0) { transitionLength = introLength; - fromTrack.fadeBeginPos = outroEnd - transitionLength; - fromTrack.fadeEndPos = outroEnd; + fromTrack.fadeBeginPos = outroEndSecond - transitionLength; + fromTrack.fadeEndPos = outroEndSecond; toTrack.startPos = toDeckStartSeconds; } else { - useFixedFadeTime(fromTrack, toTrack, fromDeckPosition, outroEnd, toDeckStartSeconds); + useFixedFadeTime(fromTrack, + toTrack, + fromDeckPosition, + outroEndSecond, + toDeckStartSeconds); } } break; case TransitionMode::FixedSkipSilence: { @@ -1678,7 +1686,7 @@ void AutoDJProcessor::calculateTransitionImpl( case TransitionMode::FixedFullTrack: default: { double startPoint; - toTrack.fadeBeginPos = toDeckEndPosition; + toTrack.fadeBeginPos = toDeckEndSecond; if (seekToStartPoint || toDeckPositionSeconds >= toTrack.fadeBeginPos) { // toDeckPosition >= toTrack.fadeBeginPos happens when the // user has seeked or played the to track behind fadeBeginPos of @@ -1688,7 +1696,7 @@ void AutoDJProcessor::calculateTransitionImpl( } else { startPoint = toDeckPositionSeconds; } - useFixedFadeTime(fromTrack, toTrack, fromDeckPosition, fromDeckEndPosition, startPoint); + useFixedFadeTime(fromTrack, toTrack, fromDeckPosition, fromDeckEndSecond, startPoint); } } From 8d37fb4455623aa7c71326a19fba80936a25f612 Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sat, 18 May 2024 11:06:42 +0000 Subject: [PATCH 09/18] AutoDJProcessor: Readability: Shorten variable names --- src/library/autodj/autodjprocessor.cpp | 56 +++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 5d4c77dc277..09615bd4df6 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1280,63 +1280,63 @@ void AutoDJProcessor::playerOutroEndChanged(DeckAttributes* pAttributes, double } double AutoDJProcessor::getIntroStartSecond(const TrackOrDeckAttributes& track) { - const mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); - const mixxx::audio::FramePos introStartPosition = track.introStartPosition(); - const mixxx::audio::FramePos introEndPosition = track.introEndPosition(); - if (!introStartPosition.isValid() || introStartPosition > trackEndPosition) { + const mixxx::audio::FramePos trackEnd = track.trackEndPosition(); + const mixxx::audio::FramePos introStart = track.introStartPosition(); + const mixxx::audio::FramePos introEnd = track.introEndPosition(); + if (!introStart.isValid() || introStart > trackEnd) { double firstSoundSecond = getFirstSoundSecond(track); - if (!introEndPosition.isValid() || introEndPosition > trackEndPosition) { + if (!introEnd.isValid() || introEnd > trackEnd) { // No intro start and intro end set, use First Sound. return firstSoundSecond; } - double introEndSecond = framePositionToSeconds(introEndPosition, track); + double introEndSecond = framePositionToSeconds(introEnd, track); if (m_transitionTime >= 0) { return introEndSecond - m_transitionTime; } return introEndSecond; } - return framePositionToSeconds(introStartPosition, track); + return framePositionToSeconds(introStart, track); } double AutoDJProcessor::getIntroEndSecond(const TrackOrDeckAttributes& track) { - const mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); - const mixxx::audio::FramePos introEndPosition = track.introEndPosition(); - if (!introEndPosition.isValid() || introEndPosition > trackEndPosition) { + const mixxx::audio::FramePos trackEnd = track.trackEndPosition(); + const mixxx::audio::FramePos introEnd = track.introEndPosition(); + if (!introEnd.isValid() || introEnd > trackEnd) { // Assume a zero length intro if introEnd is not set. // The introStart is automatically placed by AnalyzerSilence, so use // that as a fallback if the user has not placed outroStart. If it has // not been placed, getIntroStartPosition will return 0:00. return getIntroStartSecond(track); } - return framePositionToSeconds(introEndPosition, track); + return framePositionToSeconds(introEnd, track); } double AutoDJProcessor::getOutroStartSecond(const TrackOrDeckAttributes& track) { - const mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); - const mixxx::audio::FramePos outroStartPosition = track.outroStartPosition(); - if (!outroStartPosition.isValid() || outroStartPosition > trackEndPosition) { + const mixxx::audio::FramePos trackEnd = track.trackEndPosition(); + const mixxx::audio::FramePos outroStart = track.outroStartPosition(); + if (!outroStart.isValid() || outroStart > trackEnd) { // Assume a zero length outro if outroStart is not set. // The outroEnd is automatically placed by AnalyzerSilence, so use // that as a fallback if the user has not placed outroStart. If it has // not been placed, getOutroEndPosition will return the end of the track. return getOutroEndSecond(track); } - return framePositionToSeconds(outroStartPosition, track); + return framePositionToSeconds(outroStart, track); } double AutoDJProcessor::getOutroEndSecond(const TrackOrDeckAttributes& track) { - const mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); - const mixxx::audio::FramePos outroStartPosition = track.outroStartPosition(); - const mixxx::audio::FramePos outroEndPosition = track.outroEndPosition(); - if (!outroEndPosition.isValid() || outroEndPosition > trackEndPosition) { + const mixxx::audio::FramePos trackEnd = track.trackEndPosition(); + const mixxx::audio::FramePos outroStart = track.outroStartPosition(); + const mixxx::audio::FramePos outroEnd = track.outroEndPosition(); + if (!outroEnd.isValid() || outroEnd > trackEnd) { double lastSoundSecond = getLastSoundSecond(track); - DEBUG_ASSERT(lastSoundSecond <= framePositionToSeconds(trackEndPosition, track)); - if (!outroStartPosition.isValid() || outroStartPosition > trackEndPosition) { + DEBUG_ASSERT(lastSoundSecond <= framePositionToSeconds(trackEnd, track)); + if (!outroStart.isValid() || outroStart > trackEnd) { // No outro start and outro end set, use Last Sound. return lastSoundSecond; } // Try to find a better Outro End using Outro Start and transition time - double outroStartSecond = framePositionToSeconds(outroStartPosition, track); + double outroStartSecond = framePositionToSeconds(outroStart, track); if (m_transitionTime >= 0 && lastSoundSecond > outroStartSecond) { double outroEndFromTime = outroStartSecond + m_transitionTime; if (outroEndFromTime < lastSoundSecond) { @@ -1349,7 +1349,7 @@ double AutoDJProcessor::getOutroEndSecond(const TrackOrDeckAttributes& track) { } return outroStartSecond; } - return framePositionToSeconds(outroEndPosition, track); + return framePositionToSeconds(outroEnd, track); } double AutoDJProcessor::getFirstSoundSecond(const TrackOrDeckAttributes& track) { @@ -1381,12 +1381,12 @@ double AutoDJProcessor::getLastSoundSecond(const TrackOrDeckAttributes& track) { return 0.0; } - const mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); + const mixxx::audio::FramePos trackEnd = track.trackEndPosition(); CuePointer pFromTrackN60dBSound = trackData->findCueByType(mixxx::CueType::N60dBSound); if (pFromTrackN60dBSound && pFromTrackN60dBSound->getLengthFrames() > 0.0) { const mixxx::audio::FramePos lastSound = pFromTrackN60dBSound->getEndPosition(); if (lastSound > mixxx::audio::FramePos(0.0)) { - if (lastSound <= trackEndPosition) { + if (lastSound <= trackEnd) { return framePositionToSeconds(lastSound, track); } else { qWarning() << "-60 dB Sound Cue ends after track end in:" @@ -1395,7 +1395,7 @@ double AutoDJProcessor::getLastSoundSecond(const TrackOrDeckAttributes& track) { } } } - return framePositionToSeconds(trackEndPosition, track); + return framePositionToSeconds(trackEnd, track); } double AutoDJProcessor::getEndSecond(const TrackOrDeckAttributes& track) { @@ -1403,8 +1403,8 @@ double AutoDJProcessor::getEndSecond(const TrackOrDeckAttributes& track) { return 0.0; } - mixxx::audio::FramePos trackEndPosition = track.trackEndPosition(); - return framePositionToSeconds(trackEndPosition, track); + mixxx::audio::FramePos trackEnd = track.trackEndPosition(); + return framePositionToSeconds(trackEnd, track); } double AutoDJProcessor::framePositionToSeconds( From 02c1e914755f160abbf02ac52d721c6ebc356d3f Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sat, 27 Apr 2024 09:37:39 +0000 Subject: [PATCH 10/18] AutoDJFeature: Fix spelling of 'findOrCreateAutoDjPlaylistId' --- src/library/autodj/autodjfeature.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/library/autodj/autodjfeature.cpp b/src/library/autodj/autodjfeature.cpp index cdca01516af..bfc8ead0dc3 100644 --- a/src/library/autodj/autodjfeature.cpp +++ b/src/library/autodj/autodjfeature.cpp @@ -28,18 +28,18 @@ const QString kViewName = QStringLiteral("Auto DJ"); namespace { constexpr int kMaxRetrieveAttempts = 3; - int findOrCrateAutoDjPlaylistId(PlaylistDAO& playlistDAO) { - int playlistId = playlistDAO.getPlaylistIdFromName(AUTODJ_TABLE); - // If the AutoDJ playlist does not exist yet then create it. - if (playlistId < 0) { - playlistId = playlistDAO.createPlaylist( - AUTODJ_TABLE, PlaylistDAO::PLHT_AUTO_DJ); - VERIFY_OR_DEBUG_ASSERT(playlistId >= 0) { - qWarning() << "Failed to create Auto DJ playlist!"; - } +int findOrCreateAutoDjPlaylistId(PlaylistDAO& playlistDAO) { + int playlistId = playlistDAO.getPlaylistIdFromName(AUTODJ_TABLE); + // If the AutoDJ playlist does not exist yet then create it. + if (playlistId < 0) { + playlistId = playlistDAO.createPlaylist( + AUTODJ_TABLE, PlaylistDAO::PLHT_AUTO_DJ); + VERIFY_OR_DEBUG_ASSERT(playlistId >= 0) { + qWarning() << "Failed to create Auto DJ playlist!"; } - return playlistId; } + return playlistId; +} } // anonymous namespace AutoDJFeature::AutoDJFeature(Library* pLibrary, @@ -48,7 +48,7 @@ AutoDJFeature::AutoDJFeature(Library* pLibrary, : LibraryFeature(pLibrary, pConfig, QStringLiteral("autodj")), m_pTrackCollection(pLibrary->trackCollectionManager()->internalCollection()), m_playlistDao(m_pTrackCollection->getPlaylistDAO()), - m_iAutoDJPlaylistId(findOrCrateAutoDjPlaylistId(m_playlistDao)), + m_iAutoDJPlaylistId(findOrCreateAutoDjPlaylistId(m_playlistDao)), m_pAutoDJProcessor(nullptr), m_pSidebarModel(make_parented(this)), m_pAutoDJView(nullptr), From eb362665b8fee6188702cf713c51eea12a8a8516 Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sat, 27 Apr 2024 09:47:27 +0000 Subject: [PATCH 11/18] AutoDJFeature: Feature: Append the duration & track count to the AutoDJ tree item --- src/library/autodj/autodjfeature.cpp | 51 ++++++++++++++++++++++++++-- src/library/autodj/autodjfeature.h | 6 ++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/library/autodj/autodjfeature.cpp b/src/library/autodj/autodjfeature.cpp index bfc8ead0dc3..cc9ba553551 100644 --- a/src/library/autodj/autodjfeature.cpp +++ b/src/library/autodj/autodjfeature.cpp @@ -40,6 +40,31 @@ int findOrCreateAutoDjPlaylistId(PlaylistDAO& playlistDAO) { } return playlistId; } + +/// Create a title for the Auto DJ node +QString createAutoDjTitle(const QString& name, + int count, + mixxx::Duration duration, + bool showCountRemaining, + bool showTimeRemaining) { + QString result(name); + + // Show duration and track count only if Auto DJ queue has tracks + if (count > 0 && showCountRemaining) { + result.append(QStringLiteral(" (")); + result.append(QString::number(count)); + result.append(QStringLiteral(")")); + } + + if (count > 0 && showTimeRemaining) { + result.append(QStringLiteral(" ")); + result.append(mixxx::Duration::formatTime( + duration.toDoubleSeconds(), + mixxx::Duration::Precision::SECONDS)); + } + + return result; +} } // anonymous namespace AutoDJFeature::AutoDJFeature(Library* pLibrary, @@ -68,6 +93,13 @@ AutoDJFeature::AutoDJFeature(Library* pLibrary, &LibraryFeature::loadTrackToPlayer, Qt::QueuedConnection); + // Update the title of the "Auto DJ" node when the + // list of queued tracks or their properties have changed. + connect(m_pAutoDJProcessor, + &AutoDJProcessor::remainingTimeChanged, + this, + &AutoDJFeature::slotRemainingQueueDurationChanged); + m_playlistDao.setAutoDJProcessor(m_pAutoDJProcessor); // Create the "Crates" tree-item under the root item. @@ -94,8 +126,8 @@ AutoDJFeature::AutoDJFeature(Library* pLibrary, this, &AutoDJFeature::slotCrateChanged); - // Create context-menu items to allow crates to be added to, and removed - // from, the auto-DJ queue. + // Create context menu items to allow crates to be added to, + // and removed from, the auto-DJ queue. m_pRemoveCrateFromAutoDj = new QAction(tr("Remove Crate as Track Source"), this); connect(m_pRemoveCrateFromAutoDj, &QAction::triggered, @@ -109,7 +141,20 @@ AutoDJFeature::~AutoDJFeature() { } QVariant AutoDJFeature::title() { - return tr("Auto DJ"); + return createAutoDjTitle(tr("Auto DJ"), + m_pAutoDJProcessor->getRemainingTracks(), + m_pAutoDJProcessor->getRemainingTime(), + true, + true); +} + +void AutoDJFeature::slotRemainingQueueDurationChanged(int numTracks, mixxx::Duration duration) { + Q_UNUSED(numTracks); + Q_UNUSED(duration); + + // As documented by the code docs for featureIsLoading, + // it is intended to indicate when the title() has changed. + emit featureIsLoading(this, false); } void AutoDJFeature::bindLibraryWidget( diff --git a/src/library/autodj/autodjfeature.h b/src/library/autodj/autodjfeature.h index 70451a49d7a..3db6e89913a 100644 --- a/src/library/autodj/autodjfeature.h +++ b/src/library/autodj/autodjfeature.h @@ -10,6 +10,7 @@ #include "library/libraryfeature.h" #include "library/trackset/crate/crate.h" #include "preferences/usersettings.h" +#include "util/duration.h" #include "util/parented_ptr.h" class DlgAutoDJ; @@ -99,4 +100,9 @@ class AutoDJFeature : public LibraryFeature { // Adds a random track from the queue upon hitting minimum number // of tracks in the playlist void slotRandomQueue(int numTracksToAdd); + + // Updates the title of the "Auto DJ" node with the number of tracks + // and remaining duration when tracks are added to or removed from + // the Auto DJ queue. + void slotRemainingQueueDurationChanged(int len, mixxx::Duration duration); }; From 2f5f0ea952d996fd4866ee45b3772bcd4e698f0d Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Sun, 19 May 2024 14:46:29 +0000 Subject: [PATCH 12/18] DlgAutoDJ: Feature: Display the total duration of the Auto DJ queue (accounting for transition times) --- src/library/autodj/dlgautodj.cpp | 47 +++++++++++++++++++++++--------- src/library/autodj/dlgautodj.h | 1 + 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/library/autodj/dlgautodj.cpp b/src/library/autodj/dlgautodj.cpp index 43c0dbb94fe..88b73953d73 100644 --- a/src/library/autodj/dlgautodj.cpp +++ b/src/library/autodj/dlgautodj.cpp @@ -207,6 +207,11 @@ DlgAutoDJ::DlgAutoDJ(WLibrary* parent, this, &DlgAutoDJ::transitionTimeChanged); + connect(m_pAutoDJProcessor, + &AutoDJProcessor::remainingTimeChanged, + this, + &DlgAutoDJ::remainingTimeChanged); + connect(m_pAutoDJProcessor, &AutoDJProcessor::autoDJError, this, @@ -317,6 +322,12 @@ void DlgAutoDJ::transitionSliderChanged(int value) { m_pAutoDJProcessor->setTransitionTime(value); } +void DlgAutoDJ::remainingTimeChanged(int numTracks, mixxx::Duration duration) { + Q_UNUSED(numTracks); + Q_UNUSED(duration); + updateSelectionInfo(); +} + void DlgAutoDJ::autoDJStateChanged(AutoDJProcessor::AutoDJState state) { if (state == AutoDJProcessor::ADJ_DISABLED) { pushButtonAutoDJ->setChecked(false); @@ -362,24 +373,34 @@ void DlgAutoDJ::slotRepeatPlaylistChanged(bool checked) { } void DlgAutoDJ::updateSelectionInfo() { + // Obtain the total duration of the whole remaining Auto DJ queue + // from the Auto DJ processor. The calculated time is exact and + // takes transition times, intros, outros etc. into account. + mixxx::Duration totalDuration = m_pAutoDJProcessor->getRemainingTime(); + int totalTracks = m_pAutoDJTableModel->rowCount(); + + // Derive total duration of the selected tracks from the table model. + // This is much faster than getting the duration from individual track + // objects (but does not take transition times into account...) QModelIndexList indices = m_pTrackTableView->selectionModel()->selectedRows(); + mixxx::Duration selectedDuration = m_pAutoDJTableModel->getTotalDuration(indices); + int selectedTracks = indices.size(); - // Derive total duration from the table model. This is much faster than - // getting the duration from individual track objects. - mixxx::Duration duration = m_pAutoDJTableModel->getTotalDuration(indices); - + // Selected tracks QString label; - if (!indices.isEmpty()) { - label.append(mixxx::DurationBase::formatTime(duration.toDoubleSeconds())); - label.append(QString(" (%1)").arg(indices.size())); - labelSelectionInfo->setToolTip(tr("Displays the duration and number of selected tracks.")); - labelSelectionInfo->setText(label); - labelSelectionInfo->setEnabled(true); - } else { - labelSelectionInfo->setText(""); - labelSelectionInfo->setEnabled(false); + label.append(mixxx::DurationBase::formatTime(selectedDuration.toDoubleSeconds())); + label.append(QString(" (%1)").arg(selectedTracks)); + label.append(tr(" / ")); } + + // Total tracks + label.append(mixxx::DurationBase::formatTime(totalDuration.toDoubleSeconds())); + label.append(QString(" (%1)").arg(totalTracks)); + + labelSelectionInfo->setToolTip(tr("Displays the duration and number of selected tracks.")); + labelSelectionInfo->setText(label); + labelSelectionInfo->setEnabled(true); } bool DlgAutoDJ::hasFocus() const { diff --git a/src/library/autodj/dlgautodj.h b/src/library/autodj/dlgautodj.h index a2d976dc774..01b787d59e6 100644 --- a/src/library/autodj/dlgautodj.h +++ b/src/library/autodj/dlgautodj.h @@ -44,6 +44,7 @@ class DlgAutoDJ : public QWidget, public Ui::DlgAutoDJ, public LibraryView { void autoDJError(AutoDJProcessor::AutoDJError error); void transitionTimeChanged(int time); void transitionSliderChanged(int value); + void remainingTimeChanged(int numTracks, mixxx::Duration duration); void autoDJStateChanged(AutoDJProcessor::AutoDJState state); void updateSelectionInfo(); void slotTransitionModeChanged(int comboboxIndex); From 746b5b87796eebe28a59b030822825d2b995dab4 Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Fri, 4 Oct 2024 11:11:30 +0000 Subject: [PATCH 13/18] AutoDJConstants: Extract common constants into AutoDJConstants. --- src/library/autodj/autodjconstants.h | 7 +++++++ src/library/autodj/autodjprocessor.cpp | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 src/library/autodj/autodjconstants.h diff --git a/src/library/autodj/autodjconstants.h b/src/library/autodj/autodjconstants.h new file mode 100644 index 00000000000..132f85ff391 --- /dev/null +++ b/src/library/autodj/autodjconstants.h @@ -0,0 +1,7 @@ +#pragma once + +class AutoDJConstants { + public: + static constexpr double kKeepPosition = -1.0; + static constexpr double kSkipToNextTrack = -2.0; +}; diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 4444c1d9c9c..d1f07b429a7 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -3,6 +3,7 @@ #include "control/controlproxy.h" #include "control/controlpushbutton.h" #include "engine/channels/enginedeck.h" +#include "library/autodj/autodjconstants.h" #include "library/playlisttablemodel.h" #include "library/trackcollection.h" #include "library/trackcollectionmanager.h" @@ -17,8 +18,8 @@ namespace { const char* kTransitionPreferenceName = "Transition"; const char* kTransitionModePreferenceName = "TransitionMode"; constexpr double kTransitionPreferenceDefault = 10.0; -constexpr double kKeepPosition = -1.0; -constexpr double kSkipToNextTrack = -2.0; +constexpr double kKeepPosition = AutoDJConstants::kKeepPosition; +constexpr double kSkipToNextTrack = AutoDJConstants::kSkipToNextTrack; // A track needs to be longer than two callbacks to not stop AutoDJ constexpr double kMinimumTrackDurationSec = 0.2; From 70a926d7982a160343ff24abcd7b63818c58e282 Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Fri, 4 Oct 2024 11:40:31 +0000 Subject: [PATCH 14/18] TrackOrDeckAttributes: Move TrackOrDeckAttributes and subclasses into separate files. --- CMakeLists.txt | 4 + src/library/autodj/autodjprocessor.cpp | 158 --------------- src/library/autodj/autodjprocessor.h | 180 +----------------- src/library/autodj/track/deckattributes.cpp | 82 ++++++++ src/library/autodj/track/deckattributes.h | 124 ++++++++++++ .../track/fadeabletrackordeckattributes.cpp | 12 ++ .../track/fadeabletrackordeckattributes.h | 15 ++ src/library/autodj/track/trackattributes.cpp | 64 +++++++ src/library/autodj/track/trackattributes.h | 26 +++ .../autodj/track/trackordeckattributes.cpp | 6 + .../autodj/track/trackordeckattributes.h | 29 +++ 11 files changed, 364 insertions(+), 336 deletions(-) create mode 100644 src/library/autodj/track/deckattributes.cpp create mode 100644 src/library/autodj/track/deckattributes.h create mode 100644 src/library/autodj/track/fadeabletrackordeckattributes.cpp create mode 100644 src/library/autodj/track/fadeabletrackordeckattributes.h create mode 100644 src/library/autodj/track/trackattributes.cpp create mode 100644 src/library/autodj/track/trackattributes.h create mode 100644 src/library/autodj/track/trackordeckattributes.cpp create mode 100644 src/library/autodj/track/trackordeckattributes.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b319dcbe3a..af43377d949 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -902,6 +902,10 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/library/autodj/autodjprocessor.cpp src/library/autodj/dlgautodj.cpp src/library/autodj/dlgautodj.ui + src/library/autodj/track/deckattributes.cpp + src/library/autodj/track/fadeabletrackordeckattributes.cpp + src/library/autodj/track/trackattributes.cpp + src/library/autodj/track/trackordeckattributes.cpp src/library/banshee/bansheedbconnection.cpp src/library/banshee/bansheefeature.cpp src/library/banshee/bansheeplaylistmodel.cpp diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index d1f07b429a7..9ec281b87c1 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -27,164 +27,6 @@ constexpr double kMinimumTrackDurationSec = 0.2; constexpr bool sDebug = false; } // anonymous namespace -TrackOrDeckAttributes::~TrackOrDeckAttributes() { -} - -FadeableTrackOrDeckAttributes::FadeableTrackOrDeckAttributes() - : startPos(kKeepPosition), - fadeBeginPos(1.0), - fadeEndPos(1.0), - fadeDurationSeconds(0.0), - isFromDeck(false) { -} - -FadeableTrackOrDeckAttributes::~FadeableTrackOrDeckAttributes() { -} - -TrackAttributes::TrackAttributes(TrackPointer pTrack) - : m_pTrack(pTrack) { -} - -TrackAttributes::~TrackAttributes() { -} - -mixxx::audio::FramePos TrackAttributes::introStartPosition() const { - auto pIntro = m_pTrack->findCueByType(mixxx::CueType::Intro); - if (pIntro) { - return pIntro->getPosition(); - } - return mixxx::audio::FramePos(); -} - -mixxx::audio::FramePos TrackAttributes::introEndPosition() const { - auto pIntro = m_pTrack->findCueByType(mixxx::CueType::Intro); - if (pIntro) { - return pIntro->getEndPosition(); - } - return mixxx::audio::FramePos(); -} - -mixxx::audio::FramePos TrackAttributes::outroStartPosition() const { - auto pOutro = m_pTrack->findCueByType(mixxx::CueType::Outro); - if (pOutro) { - return pOutro->getPosition(); - } - return mixxx::audio::FramePos(); -} - -mixxx::audio::FramePos TrackAttributes::outroEndPosition() const { - auto pOutro = m_pTrack->findCueByType(mixxx::CueType::Outro); - if (pOutro) { - return pOutro->getEndPosition(); - } - return mixxx::audio::FramePos(); -} - -mixxx::audio::SampleRate TrackAttributes::sampleRate() const { - return m_pTrack->getSampleRate(); -} - -double TrackAttributes::playPosition() const { - return 0.0; -} - -double TrackAttributes::rateRatio() const { - return 1.0; -} - -mixxx::audio::FramePos TrackAttributes::trackEndPosition() const { - // Instead of actually loading the file, we simply infer - // the number of frames from duration and sample rate stored - // in the database. This isn't entirely accurate due to - // rounding errors, but more than accurate enough for - // estimating the remaining play time of the AutoDJ queue. - double approxNumSamples = - m_pTrack->getSampleRate() * m_pTrack->getDuration(); - - return mixxx::audio::FramePos(approxNumSamples); -} - -DeckAttributes::DeckAttributes(int index, - BaseTrackPlayer* pPlayer) - : index(index), - group(pPlayer->getGroup()), - loading(false), - m_orientation(group, "orientation"), - m_playPos(group, "playposition"), - m_play(group, "play"), - m_repeat(group, "repeat"), - m_introStartPos(group, "intro_start_position"), - m_introEndPos(group, "intro_end_position"), - m_outroStartPos(group, "outro_start_position"), - m_outroEndPos(group, "outro_end_position"), - m_trackSamples(group, "track_samples"), - m_sampleRate(group, "track_samplerate"), - m_rateRatio(group, "rate_ratio"), - m_pPlayer(pPlayer) { - connect(m_pPlayer, &BaseTrackPlayer::newTrackLoaded, - this, &DeckAttributes::slotTrackLoaded); - connect(m_pPlayer, &BaseTrackPlayer::loadingTrack, - this, &DeckAttributes::slotLoadingTrack); - connect(m_pPlayer, &BaseTrackPlayer::playerEmpty, - this, &DeckAttributes::slotPlayerEmpty); - m_playPos.connectValueChanged(this, &DeckAttributes::slotPlayPosChanged); - m_play.connectValueChanged(this, &DeckAttributes::slotPlayChanged); - m_introStartPos.connectValueChanged(this, &DeckAttributes::slotIntroStartPositionChanged); - m_introEndPos.connectValueChanged(this, &DeckAttributes::slotIntroEndPositionChanged); - m_outroStartPos.connectValueChanged(this, &DeckAttributes::slotOutroStartPositionChanged); - m_outroEndPos.connectValueChanged(this, &DeckAttributes::slotOutroEndPositionChanged); - m_rateRatio.connectValueChanged(this, &DeckAttributes::slotRateChanged); -} - -DeckAttributes::~DeckAttributes() { -} - -void DeckAttributes::slotPlayChanged(double v) { - emit playChanged(this, v > 0.0); -} - -void DeckAttributes::slotPlayPosChanged(double v) { - emit playPositionChanged(this, v); -} - -void DeckAttributes::slotIntroStartPositionChanged(double v) { - emit introStartPositionChanged(this, v); -} - -void DeckAttributes::slotIntroEndPositionChanged(double v) { - emit introEndPositionChanged(this, v); -} - -void DeckAttributes::slotOutroStartPositionChanged(double v) { - emit outroStartPositionChanged(this, v); -} - -void DeckAttributes::slotOutroEndPositionChanged(double v) { - emit outroEndPositionChanged(this, v); -} - -void DeckAttributes::slotTrackLoaded(TrackPointer pTrack) { - emit trackLoaded(this, pTrack); -} - -void DeckAttributes::slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack) { - //qDebug() << "DeckAttributes::slotLoadingTrack"; - emit loadingTrack(this, pNewTrack, pOldTrack); -} - -void DeckAttributes::slotPlayerEmpty() { - emit playerEmpty(this); -} - -void DeckAttributes::slotRateChanged(double v) { - Q_UNUSED(v); - emit rateChanged(this); -} - -TrackPointer DeckAttributes::getLoadedTrack() const { - return m_pPlayer != nullptr ? m_pPlayer->getLoadedTrack() : TrackPointer(); -} - AutoDJProcessor::AutoDJProcessor( QObject* pParent, UserSettingsPointer pConfig, diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index df5d8e6d1db..8544685045f 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -5,7 +5,8 @@ #include "audio/frame.h" #include "control/controlproxy.h" -#include "engine/channels/enginechannel.h" +#include "library/autodj/track/deckattributes.h" +#include "library/autodj/track/trackattributes.h" #include "preferences/usersettings.h" #include "track/track_decl.h" #include "track/trackid.h" @@ -15,186 +16,9 @@ class ControlPushButton; class TrackCollectionManager; class PlayerManagerInterface; -class BaseTrackPlayer; class PlaylistTableModel; typedef QList QModelIndexList; -class TrackOrDeckAttributes : public QObject { - Q_OBJECT - public: - virtual ~TrackOrDeckAttributes(); - - virtual mixxx::audio::FramePos introStartPosition() const = 0; - virtual mixxx::audio::FramePos introEndPosition() const = 0; - virtual mixxx::audio::FramePos outroStartPosition() const = 0; - virtual mixxx::audio::FramePos outroEndPosition() const = 0; - virtual mixxx::audio::SampleRate sampleRate() const = 0; - virtual mixxx::audio::FramePos trackEndPosition() const = 0; - virtual double playPosition() const = 0; - virtual double rateRatio() const = 0; - - virtual TrackPointer getLoadedTrack() const = 0; - - bool isEmpty() const { - return !getLoadedTrack(); - } -}; - -class FadeableTrackOrDeckAttributes : public TrackOrDeckAttributes { - Q_OBJECT - public: - FadeableTrackOrDeckAttributes(); - virtual ~FadeableTrackOrDeckAttributes(); - - double startPos; // Set in toDeck nature - double fadeBeginPos; // set in fromDeck nature - double fadeEndPos; // set in fromDeck nature - double fadeDurationSeconds; - bool isFromDeck; -}; - -/// Exposes the attributes of a track from the Auto DJ queue -class TrackAttributes : public FadeableTrackOrDeckAttributes { - Q_OBJECT - public: - TrackAttributes(TrackPointer pTrack); - virtual ~TrackAttributes(); - - virtual mixxx::audio::FramePos introStartPosition() const override; - virtual mixxx::audio::FramePos introEndPosition() const override; - virtual mixxx::audio::FramePos outroStartPosition() const override; - virtual mixxx::audio::FramePos outroEndPosition() const override; - virtual mixxx::audio::SampleRate sampleRate() const override; - virtual mixxx::audio::FramePos trackEndPosition() const override; - virtual double playPosition() const override; - virtual double rateRatio() const override; - - TrackPointer getLoadedTrack() const override { - return m_pTrack; - } - - private: - TrackPointer m_pTrack; -}; - -/// Exposes the attributes of the track loaded in a certain player deck -class DeckAttributes : public FadeableTrackOrDeckAttributes { - Q_OBJECT - public: - DeckAttributes(int index, - BaseTrackPlayer* pPlayer); - virtual ~DeckAttributes(); - - bool isLeft() const { - return m_orientation.get() == static_cast(EngineChannel::LEFT); - } - - bool isRight() const { - return m_orientation.get() == static_cast(EngineChannel::RIGHT); - } - - bool isPlaying() const { - return m_play.toBool(); - } - - void stop() { - m_play.set(0.0); - } - - void play() { - m_play.set(1.0); - } - - double playPosition() const override { - return m_playPos.get(); - } - - void setPlayPosition(double playpos) { - m_playPos.set(playpos); - } - - bool isRepeat() const { - return m_repeat.toBool(); - } - - void setRepeat(bool enabled) { - m_repeat.set(enabled ? 1.0 : 0.0); - } - - mixxx::audio::FramePos introStartPosition() const override { - return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_introStartPos.get()); - } - - mixxx::audio::FramePos introEndPosition() const override { - return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_introEndPos.get()); - } - - mixxx::audio::FramePos outroStartPosition() const override { - return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_outroStartPos.get()); - } - - mixxx::audio::FramePos outroEndPosition() const override { - return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_outroEndPos.get()); - } - - mixxx::audio::SampleRate sampleRate() const override { - return mixxx::audio::SampleRate::fromDouble(m_sampleRate.get()); - } - - mixxx::audio::FramePos trackEndPosition() const override { - return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_trackSamples.get()); - } - - double rateRatio() const override { - return m_rateRatio.get(); - } - - TrackPointer getLoadedTrack() const override; - - signals: - void playChanged(DeckAttributes* pDeck, bool playing); - void playPositionChanged(DeckAttributes* pDeck, double playPosition); - void introStartPositionChanged(DeckAttributes* pDeck, double introStartPosition); - void introEndPositionChanged(DeckAttributes* pDeck, double introEndPosition); - void outroStartPositionChanged(DeckAttributes* pDeck, double outtroStartPosition); - void outroEndPositionChanged(DeckAttributes* pDeck, double outroEndPosition); - void trackLoaded(DeckAttributes* pDeck, TrackPointer pTrack); - void loadingTrack(DeckAttributes* pDeck, TrackPointer pNewTrack, TrackPointer pOldTrack); - void playerEmpty(DeckAttributes* pDeck); - void rateChanged(DeckAttributes* pDeck); - - private slots: - void slotPlayPosChanged(double v); - void slotPlayChanged(double v); - void slotIntroStartPositionChanged(double v); - void slotIntroEndPositionChanged(double v); - void slotOutroStartPositionChanged(double v); - void slotOutroEndPositionChanged(double v); - void slotTrackLoaded(TrackPointer pTrack); - void slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack); - void slotPlayerEmpty(); - void slotRateChanged(double v); - - public: - int index; - QString group; - bool loading; // The data is inconsistent during loading a deck - - private: - ControlProxy m_orientation; - ControlProxy m_playPos; - ControlProxy m_play; - ControlProxy m_repeat; - ControlProxy m_introStartPos; - ControlProxy m_introEndPos; - ControlProxy m_outroStartPos; - ControlProxy m_outroEndPos; - ControlProxy m_trackSamples; - ControlProxy m_sampleRate; - ControlProxy m_rateRatio; - BaseTrackPlayer* m_pPlayer; -}; - class AutoDJProcessor : public QObject { Q_OBJECT public: diff --git a/src/library/autodj/track/deckattributes.cpp b/src/library/autodj/track/deckattributes.cpp new file mode 100644 index 00000000000..f46c802ae4c --- /dev/null +++ b/src/library/autodj/track/deckattributes.cpp @@ -0,0 +1,82 @@ +#include "library/autodj/track/deckattributes.h" + +#include "mixer/basetrackplayer.h" +#include "moc_deckattributes.cpp" + +DeckAttributes::DeckAttributes(int index, + BaseTrackPlayer* pPlayer) + : index(index), + group(pPlayer->getGroup()), + loading(false), + m_orientation(group, "orientation"), + m_playPos(group, "playposition"), + m_play(group, "play"), + m_repeat(group, "repeat"), + m_introStartPos(group, "intro_start_position"), + m_introEndPos(group, "intro_end_position"), + m_outroStartPos(group, "outro_start_position"), + m_outroEndPos(group, "outro_end_position"), + m_trackSamples(group, "track_samples"), + m_sampleRate(group, "track_samplerate"), + m_rateRatio(group, "rate_ratio"), + m_pPlayer(pPlayer) { + connect(m_pPlayer, &BaseTrackPlayer::newTrackLoaded, this, &DeckAttributes::slotTrackLoaded); + connect(m_pPlayer, &BaseTrackPlayer::loadingTrack, this, &DeckAttributes::slotLoadingTrack); + connect(m_pPlayer, &BaseTrackPlayer::playerEmpty, this, &DeckAttributes::slotPlayerEmpty); + m_playPos.connectValueChanged(this, &DeckAttributes::slotPlayPosChanged); + m_play.connectValueChanged(this, &DeckAttributes::slotPlayChanged); + m_introStartPos.connectValueChanged(this, &DeckAttributes::slotIntroStartPositionChanged); + m_introEndPos.connectValueChanged(this, &DeckAttributes::slotIntroEndPositionChanged); + m_outroStartPos.connectValueChanged(this, &DeckAttributes::slotOutroStartPositionChanged); + m_outroEndPos.connectValueChanged(this, &DeckAttributes::slotOutroEndPositionChanged); + m_rateRatio.connectValueChanged(this, &DeckAttributes::slotRateChanged); +} + +DeckAttributes::~DeckAttributes() { +} + +void DeckAttributes::slotPlayChanged(double v) { + emit playChanged(this, v > 0.0); +} + +void DeckAttributes::slotPlayPosChanged(double v) { + emit playPositionChanged(this, v); +} + +void DeckAttributes::slotIntroStartPositionChanged(double v) { + emit introStartPositionChanged(this, v); +} + +void DeckAttributes::slotIntroEndPositionChanged(double v) { + emit introEndPositionChanged(this, v); +} + +void DeckAttributes::slotOutroStartPositionChanged(double v) { + emit outroStartPositionChanged(this, v); +} + +void DeckAttributes::slotOutroEndPositionChanged(double v) { + emit outroEndPositionChanged(this, v); +} + +void DeckAttributes::slotTrackLoaded(TrackPointer pTrack) { + emit trackLoaded(this, pTrack); +} + +void DeckAttributes::slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack) { + // qDebug() << "DeckAttributes::slotLoadingTrack"; + emit loadingTrack(this, pNewTrack, pOldTrack); +} + +void DeckAttributes::slotPlayerEmpty() { + emit playerEmpty(this); +} + +void DeckAttributes::slotRateChanged(double v) { + Q_UNUSED(v); + emit rateChanged(this); +} + +TrackPointer DeckAttributes::getLoadedTrack() const { + return m_pPlayer != nullptr ? m_pPlayer->getLoadedTrack() : TrackPointer(); +} diff --git a/src/library/autodj/track/deckattributes.h b/src/library/autodj/track/deckattributes.h new file mode 100644 index 00000000000..05f3fa0ff6d --- /dev/null +++ b/src/library/autodj/track/deckattributes.h @@ -0,0 +1,124 @@ +#pragma once + +#include "control/controlproxy.h" +#include "engine/channels/enginechannel.h" +#include "library/autodj/track/fadeabletrackordeckattributes.h" + +class BaseTrackPlayer; + +/// Exposes the attributes of the track loaded in a certain player deck +class DeckAttributes : public FadeableTrackOrDeckAttributes { + Q_OBJECT + public: + DeckAttributes(int index, BaseTrackPlayer* pPlayer); + virtual ~DeckAttributes(); + + bool isLeft() const { + return m_orientation.get() == static_cast(EngineChannel::LEFT); + } + + bool isRight() const { + return m_orientation.get() == static_cast(EngineChannel::RIGHT); + } + + bool isPlaying() const { + return m_play.toBool(); + } + + void stop() { + m_play.set(0.0); + } + + void play() { + m_play.set(1.0); + } + + double playPosition() const override { + return m_playPos.get(); + } + + void setPlayPosition(double playpos) { + m_playPos.set(playpos); + } + + bool isRepeat() const { + return m_repeat.toBool(); + } + + void setRepeat(bool enabled) { + m_repeat.set(enabled ? 1.0 : 0.0); + } + + mixxx::audio::FramePos introStartPosition() const override { + return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_introStartPos.get()); + } + + mixxx::audio::FramePos introEndPosition() const override { + return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_introEndPos.get()); + } + + mixxx::audio::FramePos outroStartPosition() const override { + return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_outroStartPos.get()); + } + + mixxx::audio::FramePos outroEndPosition() const override { + return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_outroEndPos.get()); + } + + mixxx::audio::SampleRate sampleRate() const override { + return mixxx::audio::SampleRate::fromDouble(m_sampleRate.get()); + } + + mixxx::audio::FramePos trackEndPosition() const override { + return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_trackSamples.get()); + } + + double rateRatio() const override { + return m_rateRatio.get(); + } + + TrackPointer getLoadedTrack() const override; + + signals: + void playChanged(DeckAttributes* pDeck, bool playing); + void playPositionChanged(DeckAttributes* pDeck, double playPosition); + void introStartPositionChanged(DeckAttributes* pDeck, double introStartPosition); + void introEndPositionChanged(DeckAttributes* pDeck, double introEndPosition); + void outroStartPositionChanged(DeckAttributes* pDeck, double outtroStartPosition); + void outroEndPositionChanged(DeckAttributes* pDeck, double outroEndPosition); + void trackLoaded(DeckAttributes* pDeck, TrackPointer pTrack); + void loadingTrack(DeckAttributes* pDeck, TrackPointer pNewTrack, TrackPointer pOldTrack); + void playerEmpty(DeckAttributes* pDeck); + void rateChanged(DeckAttributes* pDeck); + + private slots: + void slotPlayPosChanged(double v); + void slotPlayChanged(double v); + void slotIntroStartPositionChanged(double v); + void slotIntroEndPositionChanged(double v); + void slotOutroStartPositionChanged(double v); + void slotOutroEndPositionChanged(double v); + void slotTrackLoaded(TrackPointer pTrack); + void slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack); + void slotPlayerEmpty(); + void slotRateChanged(double v); + + public: + int index; + QString group; + bool loading; // The data is inconsistent during loading a deck + + private: + ControlProxy m_orientation; + ControlProxy m_playPos; + ControlProxy m_play; + ControlProxy m_repeat; + ControlProxy m_introStartPos; + ControlProxy m_introEndPos; + ControlProxy m_outroStartPos; + ControlProxy m_outroEndPos; + ControlProxy m_trackSamples; + ControlProxy m_sampleRate; + ControlProxy m_rateRatio; + BaseTrackPlayer* m_pPlayer; +}; diff --git a/src/library/autodj/track/fadeabletrackordeckattributes.cpp b/src/library/autodj/track/fadeabletrackordeckattributes.cpp new file mode 100644 index 00000000000..8232894656d --- /dev/null +++ b/src/library/autodj/track/fadeabletrackordeckattributes.cpp @@ -0,0 +1,12 @@ +#include "library/autodj/track/fadeabletrackordeckattributes.h" + +#include "library/autodj/autodjconstants.h" +#include "moc_fadeabletrackordeckattributes.cpp" + +FadeableTrackOrDeckAttributes::FadeableTrackOrDeckAttributes() + : startPos(AutoDJConstants::kKeepPosition), + fadeBeginPos(1.0), + fadeEndPos(1.0), + fadeDurationSeconds(0.0), + isFromDeck(false) { +} diff --git a/src/library/autodj/track/fadeabletrackordeckattributes.h b/src/library/autodj/track/fadeabletrackordeckattributes.h new file mode 100644 index 00000000000..541c9d565aa --- /dev/null +++ b/src/library/autodj/track/fadeabletrackordeckattributes.h @@ -0,0 +1,15 @@ +#pragma once + +#include "library/autodj/track/trackordeckattributes.h" + +class FadeableTrackOrDeckAttributes : public TrackOrDeckAttributes { + Q_OBJECT + public: + FadeableTrackOrDeckAttributes(); + + double startPos; // Set in toDeck nature + double fadeBeginPos; // set in fromDeck nature + double fadeEndPos; // set in fromDeck nature + double fadeDurationSeconds; + bool isFromDeck; +}; diff --git a/src/library/autodj/track/trackattributes.cpp b/src/library/autodj/track/trackattributes.cpp new file mode 100644 index 00000000000..bd579eb3af3 --- /dev/null +++ b/src/library/autodj/track/trackattributes.cpp @@ -0,0 +1,64 @@ +#include "library/autodj/track/trackattributes.h" + +#include "moc_trackattributes.cpp" +#include "track/track.h" + +TrackAttributes::TrackAttributes(TrackPointer pTrack) + : m_pTrack(pTrack) { +} + +mixxx::audio::FramePos TrackAttributes::introStartPosition() const { + auto pIntro = m_pTrack->findCueByType(mixxx::CueType::Intro); + if (pIntro) { + return pIntro->getPosition(); + } + return mixxx::audio::FramePos(); +} + +mixxx::audio::FramePos TrackAttributes::introEndPosition() const { + auto pIntro = m_pTrack->findCueByType(mixxx::CueType::Intro); + if (pIntro) { + return pIntro->getEndPosition(); + } + return mixxx::audio::FramePos(); +} + +mixxx::audio::FramePos TrackAttributes::outroStartPosition() const { + auto pOutro = m_pTrack->findCueByType(mixxx::CueType::Outro); + if (pOutro) { + return pOutro->getPosition(); + } + return mixxx::audio::FramePos(); +} + +mixxx::audio::FramePos TrackAttributes::outroEndPosition() const { + auto pOutro = m_pTrack->findCueByType(mixxx::CueType::Outro); + if (pOutro) { + return pOutro->getEndPosition(); + } + return mixxx::audio::FramePos(); +} + +mixxx::audio::SampleRate TrackAttributes::sampleRate() const { + return m_pTrack->getSampleRate(); +} + +double TrackAttributes::playPosition() const { + return 0.0; +} + +double TrackAttributes::rateRatio() const { + return 1.0; +} + +mixxx::audio::FramePos TrackAttributes::trackEndPosition() const { + // Instead of actually loading the file, we simply infer + // the number of frames from duration and sample rate stored + // in the database. This isn't entirely accurate due to + // rounding errors, but more than accurate enough for + // estimating the remaining play time of the AutoDJ queue. + double approxNumSamples = + m_pTrack->getSampleRate() * m_pTrack->getDuration(); + + return mixxx::audio::FramePos(approxNumSamples); +} diff --git a/src/library/autodj/track/trackattributes.h b/src/library/autodj/track/trackattributes.h new file mode 100644 index 00000000000..9b1b0a1c962 --- /dev/null +++ b/src/library/autodj/track/trackattributes.h @@ -0,0 +1,26 @@ +#pragma once + +#include "library/autodj/track/fadeabletrackordeckattributes.h" + +/// Exposes the attributes of a track from the Auto DJ queue. +class TrackAttributes : public FadeableTrackOrDeckAttributes { + Q_OBJECT + public: + TrackAttributes(TrackPointer pTrack); + + virtual mixxx::audio::FramePos introStartPosition() const override; + virtual mixxx::audio::FramePos introEndPosition() const override; + virtual mixxx::audio::FramePos outroStartPosition() const override; + virtual mixxx::audio::FramePos outroEndPosition() const override; + virtual mixxx::audio::SampleRate sampleRate() const override; + virtual mixxx::audio::FramePos trackEndPosition() const override; + virtual double playPosition() const override; + virtual double rateRatio() const override; + + TrackPointer getLoadedTrack() const override { + return m_pTrack; + } + + private: + TrackPointer m_pTrack; +}; diff --git a/src/library/autodj/track/trackordeckattributes.cpp b/src/library/autodj/track/trackordeckattributes.cpp new file mode 100644 index 00000000000..87b703cfb9f --- /dev/null +++ b/src/library/autodj/track/trackordeckattributes.cpp @@ -0,0 +1,6 @@ +#include "library/autodj/track/trackordeckattributes.h" + +#include "moc_trackordeckattributes.cpp" + +TrackOrDeckAttributes::~TrackOrDeckAttributes() { +} diff --git a/src/library/autodj/track/trackordeckattributes.h b/src/library/autodj/track/trackordeckattributes.h new file mode 100644 index 00000000000..83ac9d23d9e --- /dev/null +++ b/src/library/autodj/track/trackordeckattributes.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "audio/frame.h" +#include "track/track_decl.h" + +/// Represents either a track in the Auto DJ queue, +/// or a player deck controlled by the Auto DJ processor. +class TrackOrDeckAttributes : public QObject { + Q_OBJECT + public: + virtual ~TrackOrDeckAttributes(); + + virtual mixxx::audio::FramePos introStartPosition() const = 0; + virtual mixxx::audio::FramePos introEndPosition() const = 0; + virtual mixxx::audio::FramePos outroStartPosition() const = 0; + virtual mixxx::audio::FramePos outroEndPosition() const = 0; + virtual mixxx::audio::SampleRate sampleRate() const = 0; + virtual mixxx::audio::FramePos trackEndPosition() const = 0; + virtual double playPosition() const = 0; + virtual double rateRatio() const = 0; + + virtual TrackPointer getLoadedTrack() const = 0; + + inline bool isEmpty() const { + return !getLoadedTrack(); + } +}; From 503d0c605e22d91d80ddfd83ba9e004985276fad Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Fri, 4 Oct 2024 13:34:24 +0000 Subject: [PATCH 15/18] AutoDJProcessor: Rename tracks_remaining/time_remaining COs to queue_tracks/queue_duration. --- src/library/autodj/autodjfeature.cpp | 6 ++-- src/library/autodj/autodjprocessor.cpp | 44 +++++++++++++------------- src/library/autodj/autodjprocessor.h | 21 ++++++------ src/library/autodj/dlgautodj.cpp | 8 ++--- src/library/autodj/dlgautodj.h | 2 +- 5 files changed, 42 insertions(+), 39 deletions(-) diff --git a/src/library/autodj/autodjfeature.cpp b/src/library/autodj/autodjfeature.cpp index 54e27ae2a63..8cecb38c0db 100644 --- a/src/library/autodj/autodjfeature.cpp +++ b/src/library/autodj/autodjfeature.cpp @@ -99,7 +99,7 @@ AutoDJFeature::AutoDJFeature(Library* pLibrary, // Update the title of the "Auto DJ" node when the // list of queued tracks or their properties have changed. connect(m_pAutoDJProcessor, - &AutoDJProcessor::remainingTimeChanged, + &AutoDJProcessor::queueDurationChanged, this, &AutoDJFeature::slotRemainingQueueDurationChanged); @@ -171,8 +171,8 @@ AutoDJFeature::~AutoDJFeature() { QVariant AutoDJFeature::title() { return createAutoDjTitle(tr("Auto DJ"), - m_pAutoDJProcessor->getRemainingTracks(), - m_pAutoDJProcessor->getRemainingTime(), + m_pAutoDJProcessor->getQueueTrackCount(), + m_pAutoDJProcessor->getQueueDuration(), true, true); } diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 9ec281b87c1..23281c774f2 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -72,10 +72,10 @@ AutoDJProcessor::AutoDJProcessor( m_pEnabledAutoDJ->connectValueChangeRequest(this, &AutoDJProcessor::controlEnableChangeRequest); - m_pTracksRemaining = new ControlObject( - ConfigKey("[AutoDJ]", "tracks_remaining")); - m_pTimeRemaining = new ControlObject( - ConfigKey("[AutoDJ]", "time_remaining")); + m_pQueueRemainingTracks = new ControlObject( + ConfigKey("[AutoDJ]", "queue_tracks")); + m_pQueueRemainingDuration = new ControlObject( + ConfigKey("[AutoDJ]", "queue_duration")); connect(m_pAutoDJTableModel, &PlaylistTableModel::playlistTracksChanged, this, @@ -129,8 +129,8 @@ AutoDJProcessor::~AutoDJProcessor() { delete m_pAddRandomTrack; delete m_pShufflePlaylist; delete m_pEnabledAutoDJ; - delete m_pTracksRemaining; - delete m_pTimeRemaining; + delete m_pQueueRemainingTracks; + delete m_pQueueRemainingDuration; delete m_pFadeNow; delete m_pAutoDJTableModel; @@ -151,24 +151,24 @@ void AutoDJProcessor::setCrossfader(double value) { } void AutoDJProcessor::playlistTracksChanged() { - m_pTracksRemaining->set(m_pAutoDJTableModel->rowCount()); - updateRemainingTime(); + m_pQueueRemainingTracks->set(m_pAutoDJTableModel->rowCount()); + updateQueueDuration(); } void AutoDJProcessor::tracksChanged(const QSet& tracks) { Q_UNUSED(tracks); - updateRemainingTime(); + updateQueueDuration(); } void AutoDJProcessor::multipleTracksChanged() { - updateRemainingTime(); + updateQueueDuration(); } -int AutoDJProcessor::getRemainingTracks() const { +int AutoDJProcessor::getQueueTrackCount() const { return m_pAutoDJTableModel->rowCount(); } -void AutoDJProcessor::updateRemainingTime() { +void AutoDJProcessor::updateQueueDuration() { // The following data points are used as inputs for the "remaining time" // calculation, and should therefore trigger a recalculation: // @@ -193,13 +193,13 @@ void AutoDJProcessor::updateRemainingTime() { // and simply trigger a recalculation when ANY // track has changed. // - mixxx::Duration remainingTime = calculateRemainingTime(); - m_pTimeRemaining->set(remainingTime.toDoubleSeconds()); - m_timeRemaining = remainingTime; - emit remainingTimeChanged(getRemainingTracks(), remainingTime); + mixxx::Duration queueDuration = calculateQueueDuration(); + m_pQueueRemainingDuration->set(queueDuration.toDoubleSeconds()); + m_queueDuration = queueDuration; + emit queueDurationChanged(getQueueTrackCount(), queueDuration); } -mixxx::Duration AutoDJProcessor::calculateRemainingTime() { +mixxx::Duration AutoDJProcessor::calculateQueueDuration() { if (m_transitionMode == TransitionMode::FullIntroOutro || m_transitionMode == TransitionMode::FadeAtOutroStart || m_transitionMode == TransitionMode::FixedSkipSilence) { @@ -1776,7 +1776,7 @@ void AutoDJProcessor::setTransitionTime(int time) { // User has changed the orientation, disable Auto DJ toggleAutoDJ(false); emit autoDJError(ADJ_NOT_TWO_DECKS); - updateRemainingTime(); + updateQueueDuration(); return; } if (pLeftDeck->isPlaying()) { @@ -1789,7 +1789,7 @@ void AutoDJProcessor::setTransitionTime(int time) { // Recalculate the duration of the Auto DJ playlist, // which may have been affected by the transition time change - updateRemainingTime(); + updateQueueDuration(); } void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { @@ -1800,7 +1800,7 @@ void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { if (m_eState != ADJ_IDLE) { // We don't want to recalculate a running transition, // only the remaining queue play time - updateRemainingTime(); + updateQueueDuration(); return; } @@ -1812,7 +1812,7 @@ void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { // User has changed the orientation, disable Auto DJ toggleAutoDJ(false); emit autoDJError(ADJ_NOT_TWO_DECKS); - updateRemainingTime(); + updateQueueDuration(); return; } @@ -1837,7 +1837,7 @@ void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { // Recalculate the duration of the Auto DJ playlist, // which may have been affected by the transition mode change - updateRemainingTime(); + updateQueueDuration(); } DeckAttributes* AutoDJProcessor::getLeftDeck() { diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index 8544685045f..9d456d2a84d 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -70,11 +70,14 @@ class AutoDJProcessor : public QObject { return m_pAutoDJTableModel; } - mixxx::Duration getRemainingTime() const { - return m_timeRemaining; + /// Gets the total remaining duration of tracks in the AutoDJ playlist, + /// excluding the track that is currently playing already. + mixxx::Duration getQueueDuration() const { + return m_queueDuration; } - int getRemainingTracks() const; + /// Gets the number of tracks remaining in the Auto DJ queue. + int getQueueTrackCount() const; bool nextTrackLoaded(); @@ -91,7 +94,7 @@ class AutoDJProcessor : public QObject { void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play); void autoDJStateChanged(AutoDJProcessor::AutoDJState state); void autoDJError(AutoDJProcessor::AutoDJError error); - void remainingTimeChanged(int numTracks, mixxx::Duration duration); + void queueDurationChanged(int numTracks, mixxx::Duration duration); void transitionTimeChanged(int time); void randomTrackRequested(int tracksToAdd); @@ -112,7 +115,7 @@ class AutoDJProcessor : public QObject { void playlistTracksChanged(); void tracksChanged(const QSet& tracks); void multipleTracksChanged(); - void updateRemainingTime(); + void updateQueueDuration(); void controlEnableChangeRequest(double value); void controlFadeNow(double value); @@ -172,7 +175,7 @@ class AutoDJProcessor : public QObject { /// Calculates the total remaining duration of tracks in the AutoDJ playlist, /// excluding the track that is currently playing already. - mixxx::Duration calculateRemainingTime(); + mixxx::Duration calculateQueueDuration(); // Removes the track loaded to the player group from the top of the AutoDJ // queue if it is present. @@ -201,9 +204,9 @@ class AutoDJProcessor : public QObject { ControlPushButton* m_pShufflePlaylist; ControlPushButton* m_pEnabledAutoDJ; - ControlObject* m_pTracksRemaining; - ControlObject* m_pTimeRemaining; - mixxx::Duration m_timeRemaining; + ControlObject* m_pQueueRemainingTracks; + ControlObject* m_pQueueRemainingDuration; + mixxx::Duration m_queueDuration; DISALLOW_COPY_AND_ASSIGN(AutoDJProcessor); }; diff --git a/src/library/autodj/dlgautodj.cpp b/src/library/autodj/dlgautodj.cpp index 05b1c20ebd8..e3751955d48 100644 --- a/src/library/autodj/dlgautodj.cpp +++ b/src/library/autodj/dlgautodj.cpp @@ -208,9 +208,9 @@ DlgAutoDJ::DlgAutoDJ(WLibrary* parent, &DlgAutoDJ::transitionTimeChanged); connect(m_pAutoDJProcessor, - &AutoDJProcessor::remainingTimeChanged, + &AutoDJProcessor::queueDurationChanged, this, - &DlgAutoDJ::remainingTimeChanged); + &DlgAutoDJ::queueDurationChanged); connect(m_pAutoDJProcessor, &AutoDJProcessor::autoDJError, @@ -310,7 +310,7 @@ void DlgAutoDJ::transitionSliderChanged(int value) { m_pAutoDJProcessor->setTransitionTime(value); } -void DlgAutoDJ::remainingTimeChanged(int numTracks, mixxx::Duration duration) { +void DlgAutoDJ::queueDurationChanged(int numTracks, mixxx::Duration duration) { Q_UNUSED(numTracks); Q_UNUSED(duration); updateSelectionInfo(); @@ -364,7 +364,7 @@ void DlgAutoDJ::updateSelectionInfo() { // Obtain the total duration of the whole remaining Auto DJ queue // from the Auto DJ processor. The calculated time is exact and // takes transition times, intros, outros etc. into account. - mixxx::Duration totalDuration = m_pAutoDJProcessor->getRemainingTime(); + mixxx::Duration totalDuration = m_pAutoDJProcessor->getQueueDuration(); int totalTracks = m_pAutoDJTableModel->rowCount(); // Derive total duration of the selected tracks from the table model. diff --git a/src/library/autodj/dlgautodj.h b/src/library/autodj/dlgautodj.h index ca8f5d34826..9ac7ef1ec2a 100644 --- a/src/library/autodj/dlgautodj.h +++ b/src/library/autodj/dlgautodj.h @@ -41,7 +41,7 @@ class DlgAutoDJ : public QWidget, public Ui::DlgAutoDJ, public LibraryView { void autoDJError(AutoDJProcessor::AutoDJError error); void transitionTimeChanged(int time); void transitionSliderChanged(int value); - void remainingTimeChanged(int numTracks, mixxx::Duration duration); + void queueDurationChanged(int numTracks, mixxx::Duration duration); void autoDJStateChanged(AutoDJProcessor::AutoDJState state); void updateSelectionInfo(); void slotTransitionModeChanged(int comboboxIndex); From 5c6861d422aacfa9d928070b01c445c9cb6e3fcb Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Fri, 4 Oct 2024 13:53:56 +0000 Subject: [PATCH 16/18] AutoDJProcessor: Avoid possible race condition: Update getQueueDuration() before triggering external code. --- src/library/autodj/autodjprocessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 23281c774f2..d79fb1f24a6 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -194,8 +194,8 @@ void AutoDJProcessor::updateQueueDuration() { // track has changed. // mixxx::Duration queueDuration = calculateQueueDuration(); - m_pQueueRemainingDuration->set(queueDuration.toDoubleSeconds()); m_queueDuration = queueDuration; + m_pQueueRemainingDuration->set(queueDuration.toDoubleSeconds()); emit queueDurationChanged(getQueueTrackCount(), queueDuration); } From 7a00a77b0327fda12e0cf2ff1cb3fd8d98f8a929 Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Fri, 4 Oct 2024 14:12:28 +0000 Subject: [PATCH 17/18] AutoDJProcessor: Fix a few spelling and grammar errors. --- src/library/autodj/autodjprocessor.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index d79fb1f24a6..4b51de2dd5b 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -741,7 +741,7 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, if (leftDeckPlaying || rightDeckPlaying || leftDeckReachesEnd) { // One of left and right is playing. Switch to IDLE mode and make - // sure our thresholds are configured (by calling calculateFadeThresholds + // sure our thresholds are configured (by calling calculateTransition // for the playing deck). m_eState = ADJ_IDLE; @@ -1589,7 +1589,7 @@ void AutoDJProcessor::useFixedFadeTime( if (toDeckOutroStart <= toDeckStartSecond + kMinimumTrackDurationSec) { // we have already passed the outro start // Check OutroEnd as alternative, which is for all transition mode - // better than directly default to duration() + // better than directly defaulting to duration() double end = getOutroEndSecond(toTrack); if (end <= toDeckStartSecond + kMinimumTrackDurationSec) { // we have also passed the outro end @@ -1634,7 +1634,7 @@ void AutoDJProcessor::playerTrackLoaded(DeckAttributes* pDeck, TrackPointer pTra if (duration < kMinimumTrackDurationSec) { qWarning() << "Skip track with" << duration << "Duration" << pTrack->getLocation(); - // Remove Tack with duration smaller than two callbacks + // Remove Track with duration smaller than two callbacks removeTrackFromTopOfQueue(pTrack); // Load the next track. If we are the first AutoDJ track @@ -1657,10 +1657,10 @@ void AutoDJProcessor::playerTrackLoaded(DeckAttributes* pDeck, TrackPointer pTra calculateTransition(fromDeck, pDeck, true); if (pDeck->startPos != kKeepPosition) { // Note: this seek will trigger the playerPositionChanged slot - // which may calls the calculateTransition() again without seek = true; + // which may call the calculateTransition() again without seek = true; pDeck->setPlayPosition(pDeck->startPos); } - // we are her in the relative domain 0..1 + // we are here in the relative domain 0..1 if (!fromDeck->isPlaying() && fromDeck->playPosition() >= 1.0) { // repeat a probably missed update playerPositionChanged(fromDeck, 1.0); @@ -1700,11 +1700,11 @@ void AutoDJProcessor::playerLoadingTrack(DeckAttributes* pDeck, if (!pNewTrack) { // If a track is ejected because of a manual eject command or a load failure - // this track seams to be undesired. Remove the bad track from the queue. + // this track seems to be undesired. Remove the bad track from the queue. removeTrackFromTopOfQueue(pOldTrack); - // wait until the track is fully unloaded and the playerEmpty() - // slot is called before load an alternative track. + // Wait until the track is fully unloaded and the playerEmpty() + // slot is called before loading an alternative track. } } @@ -1713,7 +1713,7 @@ void AutoDJProcessor::playerEmpty(DeckAttributes* pDeck) { qDebug() << this << "playerEmpty()" << pDeck->group; } - // The Deck has ejected a track and no new one is loaded + // The Deck has ejected a track and no new one is loaded. // This happens if loading fails or the user manually ejected the track // and would normally stop the AutoDJ flow, which is not desired. // It should be safe to load a new track from the queue. The only case where From 77ea46f3a68715814fdd2f8aee8ac579c8ae6348 Mon Sep 17 00:00:00 2001 From: Lukas Waslowski Date: Fri, 4 Oct 2024 15:34:19 +0000 Subject: [PATCH 18/18] AutoDJProcessor: Fix: Correctly calculate the time adjustment due to transitions --- src/library/autodj/autodjprocessor.cpp | 30 +++++++++++++++---- .../track/fadeabletrackordeckattributes.cpp | 2 +- .../track/fadeabletrackordeckattributes.h | 8 ++--- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 4b51de2dd5b..8b523e1ffbc 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -215,7 +215,7 @@ mixxx::Duration AutoDJProcessor::calculateQueueDuration() { TrackAttributes fromTrack(previousTrack); TrackAttributes toTrack(track); calculateTransitionImpl(fromTrack, toTrack, true); - durationTotal += track->getDuration() - fromTrack.fadeDurationSeconds; + durationTotal += track->getDuration() + fromTrack.adjustDurationSeconds; } else { // TODO: Take the transition between an already playing deck // and the top of the Auto DJ queue into account? @@ -1551,11 +1551,29 @@ void AutoDJProcessor::calculateTransitionImpl( } } - // A negative startPos (and therefore fadeDuration) indicates - // that there is silence between the tracks instead of an overlap. - fromTrack.fadeDurationSeconds = - (fromTrack.fadeEndPos - fromTrack.fadeBeginPos) + - math_max(0.0, toTrack.startPos); + // adjustDurationSeconds is the time that the transition takes up from + // (or adds to) the combined total duration of fromTrack+toTrack. + // + // + // +---+ fromTrack start position + // | | + // +---+ toTrack start position + // + // + // This means that, due to the transition: + // - toTrack is shortened by toTrack.startPos (which may be negative when + // silence is inserted between tracks, in which case it is actually + // elongated). + // + // - fromTrack is shortened by the time between fromTrack.fadeBeginPos and + // fromTrack.duration. (the transition duration is already taken into + // account by not subtracting it from toTrack). + // + // - The fromTrack.startPos and toTrack.fadeBeginPos/fadeEndPos are not + // accounted for here, but are taken into account by the calculation + // of the previous/next transition, respectively. + fromTrack.adjustDurationSeconds = 0.0 - + (fromDeckDuration - fromTrack.fadeBeginPos) - toTrack.startPos; // The positions are expected to be a fraction of the track length. fromTrack.fadeBeginPos /= fromDeckDuration; diff --git a/src/library/autodj/track/fadeabletrackordeckattributes.cpp b/src/library/autodj/track/fadeabletrackordeckattributes.cpp index 8232894656d..4803bc95122 100644 --- a/src/library/autodj/track/fadeabletrackordeckattributes.cpp +++ b/src/library/autodj/track/fadeabletrackordeckattributes.cpp @@ -7,6 +7,6 @@ FadeableTrackOrDeckAttributes::FadeableTrackOrDeckAttributes() : startPos(AutoDJConstants::kKeepPosition), fadeBeginPos(1.0), fadeEndPos(1.0), - fadeDurationSeconds(0.0), + adjustDurationSeconds(0.0), isFromDeck(false) { } diff --git a/src/library/autodj/track/fadeabletrackordeckattributes.h b/src/library/autodj/track/fadeabletrackordeckattributes.h index 541c9d565aa..8d756951fd2 100644 --- a/src/library/autodj/track/fadeabletrackordeckattributes.h +++ b/src/library/autodj/track/fadeabletrackordeckattributes.h @@ -7,9 +7,9 @@ class FadeableTrackOrDeckAttributes : public TrackOrDeckAttributes { public: FadeableTrackOrDeckAttributes(); - double startPos; // Set in toDeck nature - double fadeBeginPos; // set in fromDeck nature - double fadeEndPos; // set in fromDeck nature - double fadeDurationSeconds; + double startPos; // Set for toDeck (resp. toTrack) + double fadeBeginPos; // Set for fromDeck (resp. fromTrack) + double fadeEndPos; // Set for fromDeck (resp. fromTrack) + double adjustDurationSeconds; // Set for fromDeck (resp. fromTrack) bool isFromDeck; };