From 2aeee0a9843d25091e7e0a88633a98cfb95cf0d5 Mon Sep 17 00:00:00 2001 From: "Antoine C." Date: Mon, 24 Mar 2025 02:13:10 +0000 Subject: [PATCH] feat: refactor player proxy and create separated track proxy --- CMakeLists.txt | 13 +- res/qml/Mixxx/Controls/WaveformOverview.qml | 3 +- src/coreservices.cpp | 4 - src/qml/qmlplayermanagerproxy.cpp | 28 ++ src/qml/qmlplayermanagerproxy.h | 6 + src/qml/qmlplayerproxy.cpp | 355 ++---------------- src/qml/qmlplayerproxy.h | 126 +------ src/qml/qmltrackproxy.cpp | 244 ++++++++++++ src/qml/qmltrackproxy.h | 148 ++++++++ src/qml/qmlwaveformoverview.cpp | 83 +--- src/qml/qmlwaveformoverview.h | 23 +- .../controller_mapping_validation_test.cpp | 1 + 12 files changed, 508 insertions(+), 526 deletions(-) create mode 100644 src/qml/qmltrackproxy.cpp create mode 100644 src/qml/qmltrackproxy.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 128de4c23187..9c3fb67dd3c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3436,24 +3436,25 @@ if(QML) src/qml/qmlapplication.cpp src/qml/qmlautoreload.cpp src/qml/qmlbeatsmodel.cpp - src/qml/qmlcuesmodel.cpp - src/qml/qmlcontrolproxy.cpp + src/qml/qmlchainpresetmodel.cpp src/qml/qmlconfigproxy.cpp + src/qml/qmlcontrolproxy.cpp + src/qml/qmlcuesmodel.cpp src/qml/qmldlgpreferencesproxy.cpp src/qml/qmleffectmanifestparametersmodel.cpp - src/qml/qmleffectsmanagerproxy.cpp src/qml/qmleffectslotproxy.cpp + src/qml/qmleffectsmanagerproxy.cpp src/qml/qmllibraryproxy.cpp src/qml/qmllibrarytracklistmodel.cpp + src/qml/qmlmixxxcontrollerscreen.cpp src/qml/qmlplayermanagerproxy.cpp src/qml/qmlplayerproxy.cpp src/qml/qmlvisibleeffectsmodel.cpp - src/qml/qmlchainpresetmodel.cpp - src/qml/qmlwaveformoverview.cpp - src/qml/qmlmixxxcontrollerscreen.cpp src/qml/qmlwaveformdisplay.cpp + src/qml/qmlwaveformoverview.cpp src/qml/qmlwaveformrenderer.cpp src/qml/qmlsettingparameter.cpp + src/qml/qmltrackproxy.cpp src/waveform/renderers/allshader/digitsrenderer.cpp src/waveform/renderers/allshader/waveformrenderbeat.cpp src/waveform/renderers/allshader/waveformrenderer.cpp diff --git a/res/qml/Mixxx/Controls/WaveformOverview.qml b/res/qml/Mixxx/Controls/WaveformOverview.qml index 1c217986451a..0ed78fea1562 100644 --- a/res/qml/Mixxx/Controls/WaveformOverview.qml +++ b/res/qml/Mixxx/Controls/WaveformOverview.qml @@ -6,8 +6,9 @@ Mixxx.WaveformOverview { id: root required property string group + readonly property var player: Mixxx.PlayerManager.getPlayer(root.group) - player: Mixxx.PlayerManager.getPlayer(root.group) + track: player.currentTrack Mixxx.ControlProxy { id: trackLoadedControl diff --git a/src/coreservices.cpp b/src/coreservices.cpp index c9d404c7b46f..040b9dad15b9 100644 --- a/src/coreservices.cpp +++ b/src/coreservices.cpp @@ -40,13 +40,9 @@ #include "controllers/scripting/controllerscriptenginebase.h" #include "qml/qmlconfigproxy.h" -#include "qml/qmlcontrolproxy.h" -#include "qml/qmldlgpreferencesproxy.h" -#include "qml/qmleffectslotproxy.h" #include "qml/qmleffectsmanagerproxy.h" #include "qml/qmllibraryproxy.h" #include "qml/qmlplayermanagerproxy.h" -#include "qml/qmlplayerproxy.h" #endif #include "soundio/soundmanager.h" #include "sources/soundsourceproxy.h" diff --git a/src/qml/qmlplayermanagerproxy.cpp b/src/qml/qmlplayermanagerproxy.cpp index 2544b34f3ad5..cc2d1b3a8e97 100644 --- a/src/qml/qmlplayermanagerproxy.cpp +++ b/src/qml/qmlplayermanagerproxy.cpp @@ -5,6 +5,7 @@ #include "mixer/playermanager.h" #include "moc_qmlplayermanagerproxy.cpp" #include "qml/qmlplayerproxy.h" +#include "track/track_decl.h" namespace mixxx { namespace qml { @@ -31,6 +32,20 @@ QmlPlayerProxy* QmlPlayerManagerProxy::getPlayer(const QString& group) { [this, group](const QString& trackLocation, bool play) { loadLocationToPlayer(trackLocation, group, play); }); + connect(pPlayerProxy, + &QmlPlayerProxy::loadTrackRequested, + this, + [this, group](TrackPointer track, +#ifdef __STEM__ + mixxx::StemChannelSelection stemSelection, +#endif + bool play) { + loadTrackToPlayer(track, group, +#ifdef __STEM__ + stemSelection, +#endif + play); + }); connect(pPlayerProxy, &QmlPlayerProxy::cloneFromGroup, this, @@ -59,6 +74,19 @@ void QmlPlayerManagerProxy::loadLocationToPlayer( m_pPlayerManager->slotLoadLocationToPlayer(location, group, play); } +void QmlPlayerManagerProxy::loadTrackToPlayer(TrackPointer track, + const QString& group, +#ifdef __STEM__ + mixxx::StemChannelSelection stemSelection, +#endif + bool play) { + m_pPlayerManager->slotLoadTrackToPlayer(track, group, +#ifdef __STEM__ + stemSelection, +#endif + play); +} + // static QmlPlayerManagerProxy* QmlPlayerManagerProxy::create(QQmlEngine* pQmlEngine, QJSEngine* pJsEngine) { // The implementation of this method is mostly taken from the code example diff --git a/src/qml/qmlplayermanagerproxy.h b/src/qml/qmlplayermanagerproxy.h index 1cf650b7ceb8..3f23ad664867 100644 --- a/src/qml/qmlplayermanagerproxy.h +++ b/src/qml/qmlplayermanagerproxy.h @@ -24,6 +24,12 @@ class QmlPlayerManagerProxy : public QObject { const QUrl& locationUrl, bool play = false); Q_INVOKABLE void loadLocationToPlayer( const QString& location, const QString& group, bool play = false); + Q_INVOKABLE void loadTrackToPlayer(TrackPointer track, + const QString& group, +#ifdef __STEM__ + mixxx::StemChannelSelection stemSelection, +#endif + bool play); static QmlPlayerManagerProxy* create(QQmlEngine* pQmlEngine, QJSEngine* pJsEngine); static void registerPlayerManager(std::shared_ptr pPlayerManager) { diff --git a/src/qml/qmlplayerproxy.cpp b/src/qml/qmlplayerproxy.cpp index d3b6056fda6b..ba5b58ea2b6d 100644 --- a/src/qml/qmlplayerproxy.cpp +++ b/src/qml/qmlplayerproxy.cpp @@ -1,42 +1,19 @@ #include "qml/qmlplayerproxy.h" #include +#include #include "mixer/basetrackplayer.h" #include "moc_qmlplayerproxy.cpp" -#include "qml/asyncimageprovider.h" - -#define PROPERTY_IMPL_GETTER(TYPE, NAME, GETTER) \ - TYPE QmlPlayerProxy::GETTER() const { \ - const TrackPointer pTrack = m_pCurrentTrack; \ - if (pTrack == nullptr) { \ - return TYPE(); \ - } \ - return pTrack->GETTER(); \ - } - -#define PROPERTY_IMPL(TYPE, NAME, GETTER, SETTER) \ - PROPERTY_IMPL_GETTER(TYPE, NAME, GETTER) \ - void QmlPlayerProxy::SETTER(const TYPE& value) { \ - const TrackPointer pTrack = m_pCurrentTrack; \ - if (pTrack != nullptr) { \ - pTrack->SETTER(value); \ - } \ - } +#include "qmltrackproxy.h" +#include "track/track.h" namespace mixxx { namespace qml { QmlPlayerProxy::QmlPlayerProxy(BaseTrackPlayer* pTrackPlayer, QObject* parent) : QObject(parent), - m_pTrackPlayer(pTrackPlayer), - m_pBeatsModel(new QmlBeatsModel(this)), - m_pHotcuesModel(new QmlCuesModel(this)) -#ifdef __STEM__ - , - m_pStemsModel(std::make_unique(this)) -#endif -{ + m_pTrackPlayer(pTrackPlayer) { connect(m_pTrackPlayer, &BaseTrackPlayer::loadingTrack, this, @@ -46,15 +23,25 @@ QmlPlayerProxy::QmlPlayerProxy(BaseTrackPlayer* pTrackPlayer, QObject* parent) this, &QmlPlayerProxy::slotTrackLoaded); connect(m_pTrackPlayer, - &BaseTrackPlayer::playerEmpty, + &BaseTrackPlayer::trackUnloaded, this, - &QmlPlayerProxy::trackUnloaded); - connect(this, &QmlPlayerProxy::trackChanged, this, &QmlPlayerProxy::slotTrackChanged); + &QmlPlayerProxy::slotTrackUnloaded); if (m_pTrackPlayer && m_pTrackPlayer->getLoadedTrack()) { slotTrackLoaded(pTrackPlayer->getLoadedTrack()); } } +void QmlPlayerProxy::loadTrack(QmlTrackProxy* track, bool play) { + if (track == nullptr || track->internal() == nullptr) { + return; + } + emit loadTrackRequested(track->internal(), +#ifdef __STEM__ + mixxx::StemChannel::All, +#endif + play); +} + void QmlPlayerProxy::loadTrackFromLocation(const QString& trackLocation, bool play) { emit loadTrackFromLocationRequested(trackLocation, play); } @@ -69,323 +56,47 @@ void QmlPlayerProxy::loadTrackFromLocationUrl(const QUrl& trackLocationUrl, bool void QmlPlayerProxy::slotTrackLoaded(TrackPointer pTrack) { m_pCurrentTrack = pTrack; - if (pTrack != nullptr) { - connect(pTrack.get(), - &Track::artistChanged, - this, - &QmlPlayerProxy::artistChanged); - connect(pTrack.get(), - &Track::titleChanged, - this, - &QmlPlayerProxy::titleChanged); - connect(pTrack.get(), - &Track::albumChanged, - this, - &QmlPlayerProxy::albumChanged); - connect(pTrack.get(), - &Track::albumArtistChanged, - this, - &QmlPlayerProxy::albumArtistChanged); - connect(pTrack.get(), - &Track::genreChanged, - this, - &QmlPlayerProxy::genreChanged); - connect(pTrack.get(), - &Track::composerChanged, - this, - &QmlPlayerProxy::composerChanged); - connect(pTrack.get(), - &Track::groupingChanged, - this, - &QmlPlayerProxy::groupingChanged); - connect(pTrack.get(), - &Track::yearChanged, - this, - &QmlPlayerProxy::yearChanged); - connect(pTrack.get(), - &Track::trackNumberChanged, - this, - &QmlPlayerProxy::trackNumberChanged); - connect(pTrack.get(), - &Track::trackTotalChanged, - this, - &QmlPlayerProxy::trackTotalChanged); - connect(pTrack.get(), - &Track::commentChanged, - this, - &QmlPlayerProxy::commentChanged); - connect(pTrack.get(), - &Track::keyChanged, - this, - &QmlPlayerProxy::keyTextChanged); - connect(pTrack.get(), - &Track::colorUpdated, - this, - &QmlPlayerProxy::colorChanged); - connect(pTrack.get(), - &Track::waveformUpdated, - this, - &QmlPlayerProxy::slotWaveformChanged); - connect(pTrack.get(), - &Track::beatsUpdated, - this, - &QmlPlayerProxy::slotBeatsChanged); - connect(pTrack.get(), - &Track::cuesUpdated, - this, - &QmlPlayerProxy::slotHotcuesChanged); -#ifdef __STEM__ - connect(pTrack.get(), - &Track::stemsUpdated, - this, - &QmlPlayerProxy::slotStemsChanged); -#endif - slotBeatsChanged(); - slotHotcuesChanged(); -#ifdef __STEM__ - slotStemsChanged(); -#endif - slotWaveformChanged(); - } emit trackChanged(); emit trackLoaded(); } -void QmlPlayerProxy::slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack) { +void QmlPlayerProxy::slotTrackUnloaded(TrackPointer pOldTrack) { VERIFY_OR_DEBUG_ASSERT(pOldTrack == m_pCurrentTrack) { qWarning() << "QML Player proxy was expected to contain " << pOldTrack.get() << "as active track but got" << m_pCurrentTrack.get(); } - - if (pNewTrack.get() == m_pCurrentTrack.get()) { - emit trackLoading(); - return; - } - - const TrackPointer pTrack = m_pCurrentTrack; - if (pTrack != nullptr) { - disconnect(pTrack.get(), nullptr, this, nullptr); + if (m_pCurrentTrack != nullptr) { + disconnect(m_pCurrentTrack.get(), nullptr, this, nullptr); } m_pCurrentTrack.reset(); - m_pCurrentTrack = pNewTrack; - m_waveformTexture = QImage(); emit trackChanged(); - emit trackLoading(); -} - -void QmlPlayerProxy::slotTrackChanged() { - emit artistChanged(); - emit titleChanged(); - emit albumChanged(); - emit albumArtistChanged(); - emit genreChanged(); - emit composerChanged(); - emit groupingChanged(); - emit yearChanged(); - emit trackNumberChanged(); - emit trackTotalChanged(); - emit commentChanged(); - emit keyTextChanged(); - emit colorChanged(); - emit coverArtUrlChanged(); - emit trackLocationUrlChanged(); -#ifdef __STEM__ - emit stemsChanged(); -#endif - - emit waveformLengthChanged(); - emit waveformTextureChanged(); - emit waveformTextureSizeChanged(); - emit waveformTextureStrideChanged(); + emit trackUnloaded(); } -void QmlPlayerProxy::slotWaveformChanged() { - emit waveformLengthChanged(); - emit waveformTextureSizeChanged(); - emit waveformTextureStrideChanged(); - - const TrackPointer pTrack = m_pCurrentTrack; - if (!pTrack) { - return; - } - const ConstWaveformPointer pWaveform = - pTrack->getWaveform(); - if (!pWaveform) { - return; - } - const int textureWidth = pWaveform->getTextureStride(); - const int textureHeight = pWaveform->getTextureSize() / pWaveform->getTextureStride(); - - const WaveformData* data = pWaveform->data(); - // Make a copy of the waveform data, stripping the stems portion. Note that the datasize is - // different from the texture size -- we want the full texture size so the upload works. See - // m_data in waveform/waveform.h. - m_waveformData.resize(pWaveform->getTextureSize()); - for (int i = 0; i < pWaveform->getDataSize(); i++) { - m_waveformData[i] = data[i].filtered; - } - - m_waveformTexture = - QImage(reinterpret_cast(m_waveformData.data()), - textureWidth, - textureHeight, - QImage::Format_RGBA8888); - DEBUG_ASSERT(!m_waveformTexture.isNull()); - emit waveformTextureChanged(); -} - -void QmlPlayerProxy::slotBeatsChanged() { - VERIFY_OR_DEBUG_ASSERT(m_pBeatsModel != nullptr) { - return; - } - - const TrackPointer pTrack = m_pCurrentTrack; - if (pTrack) { - const auto trackEndPosition = mixxx::audio::FramePos{ - pTrack->getDuration() * pTrack->getSampleRate()}; - const auto pBeats = pTrack->getBeats(); - m_pBeatsModel->setBeats(pBeats, trackEndPosition); - } else { - m_pBeatsModel->setBeats(nullptr, audio::kStartFramePos); - } +QmlTrackProxy* QmlPlayerProxy::currentTrack() { + auto* pTrack = new QmlTrackProxy(m_pCurrentTrack, this); + QQmlEngine::setObjectOwnership(pTrack, QQmlEngine::JavaScriptOwnership); + return pTrack; } -#ifdef __STEM__ -void QmlPlayerProxy::slotStemsChanged() { - VERIFY_OR_DEBUG_ASSERT(m_pStemsModel != nullptr) { - return; - } - - const TrackPointer pTrack = m_pCurrentTrack; - if (pTrack) { - m_pStemsModel->setStems(pTrack->getStemInfo()); - emit stemsChanged(); - } -} -#endif - -void QmlPlayerProxy::slotHotcuesChanged() { - VERIFY_OR_DEBUG_ASSERT(m_pHotcuesModel != nullptr) { +void QmlPlayerProxy::slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack) { + if (pNewTrack.get() == m_pCurrentTrack.get()) { + emit trackLoading(); return; } - QList hotcues; - - const TrackPointer pTrack = m_pCurrentTrack; - if (pTrack) { - const auto& cuePoints = pTrack->getCuePoints(); - for (const auto& cuePoint : cuePoints) { - if (cuePoint->getHotCue() == Cue::kNoHotCue) - continue; - hotcues.append(cuePoint); - } + if (m_pCurrentTrack != nullptr) { + disconnect(m_pCurrentTrack.get(), nullptr, this, nullptr); } - m_pHotcuesModel->setCues(hotcues); - emit cuesChanged(); -} - -int QmlPlayerProxy::getWaveformLength() const { - const TrackPointer pTrack = m_pCurrentTrack; - if (pTrack) { - const ConstWaveformPointer pWaveform = pTrack->getWaveform(); - if (pWaveform) { - return pWaveform->getDataSize(); - } - } - return 0; -} - -QString QmlPlayerProxy::getWaveformTexture() const { - if (m_waveformTexture.isNull()) { - return QString(); - } - QByteArray byteArray; - QBuffer buffer(&byteArray); - buffer.open(QIODevice::WriteOnly); - m_waveformTexture.save(&buffer, "png"); - - QString imageData = QString::fromLatin1(byteArray.toBase64().data()); - if (imageData.isEmpty()) { - return QString(); - } - - return QStringLiteral("data:image/png;base64,") + imageData; -} - -int QmlPlayerProxy::getWaveformTextureSize() const { - const TrackPointer pTrack = m_pCurrentTrack; - if (pTrack) { - const ConstWaveformPointer pWaveform = pTrack->getWaveform(); - if (pWaveform) { - return pWaveform->getTextureSize(); - } - } - return 0; -} - -int QmlPlayerProxy::getWaveformTextureStride() const { - const TrackPointer pTrack = m_pCurrentTrack; - if (pTrack) { - const ConstWaveformPointer pWaveform = pTrack->getWaveform(); - if (pWaveform) { - return pWaveform->getTextureStride(); - } - } - return 0; + m_pCurrentTrack = pNewTrack; + emit trackChanged(); + emit trackLoading(); } bool QmlPlayerProxy::isLoaded() const { return m_pCurrentTrack != nullptr; } -PROPERTY_IMPL(QString, artist, getArtist, setArtist) -PROPERTY_IMPL(QString, title, getTitle, setTitle) -PROPERTY_IMPL(QString, album, getAlbum, setAlbum) -PROPERTY_IMPL(QString, albumArtist, getAlbumArtist, setAlbumArtist) -PROPERTY_IMPL_GETTER(QString, genre, getGenre) -PROPERTY_IMPL(QString, composer, getComposer, setComposer) -PROPERTY_IMPL(QString, grouping, getGrouping, setGrouping) -PROPERTY_IMPL(QString, year, getYear, setYear) -PROPERTY_IMPL(QString, trackNumber, getTrackNumber, setTrackNumber) -PROPERTY_IMPL(QString, trackTotal, getTrackTotal, setTrackTotal) -PROPERTY_IMPL(QString, comment, getComment, setComment) -PROPERTY_IMPL(QString, keyText, getKeyText, setKeyText) - -QColor QmlPlayerProxy::getColor() const { - const TrackPointer pTrack = m_pCurrentTrack; - if (pTrack == nullptr) { - return QColor(); - } - return RgbColor::toQColor(pTrack->getColor()); -} - -void QmlPlayerProxy::setColor(const QColor& value) { - const TrackPointer pTrack = m_pTrackPlayer->getLoadedTrack(); - if (pTrack != nullptr) { - std::optional color = RgbColor::fromQColor(value); - pTrack->setColor(color); - } -} - -QUrl QmlPlayerProxy::getCoverArtUrl() const { - const TrackPointer pTrack = m_pCurrentTrack; - if (pTrack == nullptr) { - return QUrl(); - } - - const CoverInfo coverInfo = pTrack->getCoverInfoWithLocation(); - return AsyncImageProvider::trackLocationToCoverArtUrl(coverInfo.trackLocation); -} - -QUrl QmlPlayerProxy::getTrackLocationUrl() const { - const TrackPointer pTrack = m_pCurrentTrack; - if (pTrack == nullptr) { - return QUrl(); - } - - return QUrl::fromLocalFile(pTrack->getLocation()); -} - } // namespace qml } // namespace mixxx diff --git a/src/qml/qmlplayerproxy.h b/src/qml/qmlplayerproxy.h index 54f42bb9c9a3..538639bdb0fa 100644 --- a/src/qml/qmlplayerproxy.h +++ b/src/qml/qmlplayerproxy.h @@ -7,115 +7,36 @@ #include #include "mixer/basetrackplayer.h" -#include "qml/qmlbeatsmodel.h" -#include "qml/qmlcuesmodel.h" -#include "qml/qmlstemsmodel.h" -#include "track/cueinfo.h" -#include "track/track.h" -#include "waveform/waveform.h" +#include "qmltrackproxy.h" +#include "track/track_decl.h" namespace mixxx { namespace qml { class QmlPlayerProxy : public QObject { Q_OBJECT + Q_PROPERTY(QmlTrackProxy* currentTrack READ currentTrack NOTIFY trackChanged) Q_PROPERTY(bool isLoaded READ isLoaded NOTIFY trackChanged) - Q_PROPERTY(QString artist READ getArtist WRITE setArtist NOTIFY artistChanged) - Q_PROPERTY(QString title READ getTitle WRITE setTitle NOTIFY titleChanged) - Q_PROPERTY(QString album READ getAlbum WRITE setAlbum NOTIFY albumChanged) - Q_PROPERTY(QString albumArtist READ getAlbumArtist WRITE setAlbumArtist - NOTIFY albumArtistChanged) - Q_PROPERTY(QString genre READ getGenre STORED false NOTIFY genreChanged) - Q_PROPERTY(QString composer READ getComposer WRITE setComposer NOTIFY composerChanged) - Q_PROPERTY(QString grouping READ getGrouping WRITE setGrouping NOTIFY groupingChanged) - Q_PROPERTY(QString year READ getYear WRITE setYear NOTIFY yearChanged) - Q_PROPERTY(QString trackNumber READ getTrackNumber WRITE setTrackNumber - NOTIFY trackNumberChanged) - Q_PROPERTY(QString trackTotal READ getTrackTotal WRITE setTrackTotal NOTIFY trackTotalChanged) - Q_PROPERTY(QString comment READ getComment WRITE setComment NOTIFY commentChanged) - Q_PROPERTY(QString keyText READ getKeyText WRITE setKeyText NOTIFY keyTextChanged) - Q_PROPERTY(QColor color READ getColor WRITE setColor NOTIFY colorChanged) - Q_PROPERTY(QUrl coverArtUrl READ getCoverArtUrl NOTIFY coverArtUrlChanged) - Q_PROPERTY(QUrl trackLocationUrl READ getTrackLocationUrl NOTIFY trackLocationUrlChanged) QML_NAMED_ELEMENT(Player) QML_UNCREATABLE("Only accessible via Mixxx.PlayerManager.getPlayer(group)") - Q_PROPERTY(int waveformLength READ getWaveformLength NOTIFY waveformLengthChanged) - Q_PROPERTY(QString waveformTexture READ getWaveformTexture NOTIFY waveformTextureChanged) - Q_PROPERTY(int waveformTextureSize READ getWaveformTextureSize NOTIFY - waveformTextureSizeChanged) - Q_PROPERTY(int waveformTextureStride READ getWaveformTextureStride NOTIFY - waveformTextureStrideChanged) - - Q_PROPERTY(mixxx::qml::QmlBeatsModel* beatsModel MEMBER m_pBeatsModel CONSTANT); - Q_PROPERTY(mixxx::qml::QmlCuesModel* hotcuesModel MEMBER m_pHotcuesModel CONSTANT); -#ifdef __STEM__ - Q_PROPERTY(mixxx::qml::QmlStemsModel* stemsModel READ getStemsModel CONSTANT); -#endif - public: explicit QmlPlayerProxy(BaseTrackPlayer* pTrackPlayer, QObject* parent = nullptr); bool isLoaded() const; - QString getTrack() const; - QString getTitle() const; - QString getArtist() const; - QString getAlbum() const; - QString getAlbumArtist() const; - QString getGenre() const; - QString getComposer() const; - QString getGrouping() const; - QString getYear() const; - QString getTrackNumber() const; - QString getTrackTotal() const; - QString getComment() const; - QString getKeyText() const; - QColor getColor() const; - QUrl getCoverArtUrl() const; - QUrl getTrackLocationUrl() const; - - int getWaveformLength() const; - QString getWaveformTexture() const; - int getWaveformTextureSize() const; - int getWaveformTextureStride() const; - /// Needed for interacting with the raw track player object. BaseTrackPlayer* internalTrackPlayer() const { return m_pTrackPlayer; } + Q_INVOKABLE void loadTrack(mixxx::qml::QmlTrackProxy* track, bool play = false); Q_INVOKABLE void loadTrackFromLocation(const QString& trackLocation, bool play = false); Q_INVOKABLE void loadTrackFromLocationUrl(const QUrl& trackLocationUrl, bool play = false); -#ifdef __STEM__ - QmlStemsModel* getStemsModel() const { - return m_pStemsModel.get(); - } -#endif - public slots: void slotTrackLoaded(TrackPointer pTrack); + void slotTrackUnloaded(TrackPointer pOldTrack); void slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack); - void slotTrackChanged(); - void slotWaveformChanged(); - void slotBeatsChanged(); - void slotHotcuesChanged(); -#ifdef __STEM__ - void slotStemsChanged(); -#endif - - void setArtist(const QString& artist); - void setTitle(const QString& title); - void setAlbum(const QString& album); - void setAlbumArtist(const QString& albumArtist); - void setComposer(const QString& composer); - void setGrouping(const QString& grouping); - void setYear(const QString& year); - void setTrackNumber(const QString& trackNumber); - void setTrackTotal(const QString& trackTotal); - void setComment(const QString& comment); - void setKeyText(const QString& keyText); - void setColor(const QColor& color); signals: void trackLoading(); @@ -124,43 +45,18 @@ class QmlPlayerProxy : public QObject { void trackChanged(); void cloneFromGroup(const QString& group); - void albumChanged(); - void titleChanged(); - void artistChanged(); - void albumArtistChanged(); - void genreChanged(); - void composerChanged(); - void groupingChanged(); - void yearChanged(); - void trackNumberChanged(); - void trackTotalChanged(); - void commentChanged(); - void keyTextChanged(); - void colorChanged(); - void coverArtUrlChanged(); - void trackLocationUrlChanged(); - void cuesChanged(); + void loadTrackFromLocationRequested(const QString& trackLocation, bool play); + void loadTrackRequested(TrackPointer track, #ifdef __STEM__ - void stemsChanged(); + mixxx::StemChannelSelection stemSelection, #endif - - void loadTrackFromLocationRequested(const QString& trackLocation, bool play); - - void waveformLengthChanged(); - void waveformTextureChanged(); - void waveformTextureSizeChanged(); - void waveformTextureStrideChanged(); + bool play); private: - std::vector m_waveformData; - QImage m_waveformTexture; + QmlTrackProxy* currentTrack(); + QPointer m_pTrackPlayer; TrackPointer m_pCurrentTrack; - QmlBeatsModel* m_pBeatsModel; - QmlCuesModel* m_pHotcuesModel; -#ifdef __STEM__ - std::unique_ptr m_pStemsModel; -#endif }; } // namespace qml diff --git a/src/qml/qmltrackproxy.cpp b/src/qml/qmltrackproxy.cpp new file mode 100644 index 000000000000..61adb7af19b8 --- /dev/null +++ b/src/qml/qmltrackproxy.cpp @@ -0,0 +1,244 @@ +#include "qml/qmltrackproxy.h" + +#include + +#include "mixer/basetrackplayer.h" +#include "moc_qmltrackproxy.cpp" +#include "qml/asyncimageprovider.h" +#include "track/track.h" +#include "util/parented_ptr.h" + +#define PROPERTY_IMPL_GETTER(TYPE, NAME, GETTER) \ + TYPE QmlTrackProxy::GETTER() const { \ + const TrackPointer pTrack = m_pTrack; \ + if (pTrack == nullptr) { \ + return TYPE(); \ + } \ + return pTrack->GETTER(); \ + } + +#define PROPERTY_IMPL(TYPE, NAME, GETTER, SETTER) \ + PROPERTY_IMPL_GETTER(TYPE, NAME, GETTER) \ + void QmlTrackProxy::SETTER(const TYPE& value) { \ + const TrackPointer pTrack = m_pTrack; \ + if (pTrack != nullptr) { \ + pTrack->SETTER(value); \ + } \ + } + +namespace mixxx { +namespace qml { + +QmlTrackProxy::QmlTrackProxy(TrackPointer track, QObject* parent) + : QObject(parent), + m_pTrack(track), + m_pBeatsModel(make_parented(this)), + m_pHotcuesModel(make_parented(this)) +#ifdef __STEM__ + , + m_pStemsModel(make_parented(this)) +#endif +{ + if (m_pTrack == nullptr) { + return; + } + connect(m_pTrack.get(), + &Track::artistChanged, + this, + &QmlTrackProxy::artistChanged); + connect(m_pTrack.get(), + &Track::titleChanged, + this, + &QmlTrackProxy::titleChanged); + connect(m_pTrack.get(), + &Track::albumChanged, + this, + &QmlTrackProxy::albumChanged); + connect(m_pTrack.get(), + &Track::albumArtistChanged, + this, + &QmlTrackProxy::albumArtistChanged); + connect(m_pTrack.get(), + &Track::genreChanged, + this, + &QmlTrackProxy::genreChanged); + connect(m_pTrack.get(), + &Track::composerChanged, + this, + &QmlTrackProxy::composerChanged); + connect(m_pTrack.get(), + &Track::groupingChanged, + this, + &QmlTrackProxy::groupingChanged); + connect(m_pTrack.get(), + &Track::yearChanged, + this, + &QmlTrackProxy::yearChanged); + connect(m_pTrack.get(), + &Track::trackNumberChanged, + this, + &QmlTrackProxy::trackNumberChanged); + connect(m_pTrack.get(), + &Track::trackTotalChanged, + this, + &QmlTrackProxy::trackTotalChanged); + connect(m_pTrack.get(), + &Track::commentChanged, + this, + &QmlTrackProxy::commentChanged); + connect(m_pTrack.get(), + &Track::keyChanged, + this, + &QmlTrackProxy::keyTextChanged); + connect(m_pTrack.get(), + &Track::colorUpdated, + this, + &QmlTrackProxy::colorChanged); + connect(m_pTrack.get(), + &Track::beatsUpdated, + this, + &QmlTrackProxy::slotBeatsChanged); + connect(m_pTrack.get(), + &Track::cuesUpdated, + this, + &QmlTrackProxy::slotHotcuesChanged); + connect(m_pTrack.get(), + &Track::durationChanged, + this, + &QmlTrackProxy::durationChanged); +#ifdef __STEM__ + connect(m_pTrack.get(), + &Track::stemsUpdated, + this, + &QmlTrackProxy::slotStemsChanged); +#endif + slotBeatsChanged(); + slotHotcuesChanged(); +#ifdef __STEM__ + slotStemsChanged(); +#endif +} + +void QmlTrackProxy::slotBeatsChanged() { + VERIFY_OR_DEBUG_ASSERT(m_pBeatsModel) { + return; + } + + const TrackPointer pTrack = m_pTrack; + if (pTrack) { + const auto trackEndPosition = mixxx::audio::FramePos{ + pTrack->getDuration() * pTrack->getSampleRate()}; + const auto pBeats = pTrack->getBeats(); + m_pBeatsModel->setBeats(pBeats, trackEndPosition); + } else { + m_pBeatsModel->setBeats(nullptr, audio::kStartFramePos); + } +} + +#ifdef __STEM__ +void QmlTrackProxy::slotStemsChanged() { + VERIFY_OR_DEBUG_ASSERT(m_pStemsModel) { + return; + } + + if (m_pTrack) { + m_pStemsModel->setStems(m_pTrack->getStemInfo()); + emit stemsChanged(); + } +} +#endif + +void QmlTrackProxy::slotHotcuesChanged() { + VERIFY_OR_DEBUG_ASSERT(m_pHotcuesModel) { + return; + } + + QList hotcues; + + if (m_pTrack) { + const auto& cuePoints = m_pTrack->getCuePoints(); + for (const auto& cuePoint : cuePoints) { + if (cuePoint->getHotCue() == Cue::kNoHotCue) { + continue; + } + hotcues.append(cuePoint); + } + } + m_pHotcuesModel->setCues(hotcues); + emit cuesChanged(); +} + +PROPERTY_IMPL(QString, artist, getArtist, setArtist) +PROPERTY_IMPL(QString, title, getTitle, setTitle) +PROPERTY_IMPL(QString, album, getAlbum, setAlbum) +PROPERTY_IMPL(QString, albumArtist, getAlbumArtist, setAlbumArtist) +PROPERTY_IMPL_GETTER(QString, genre, getGenre) +PROPERTY_IMPL(QString, composer, getComposer, setComposer) +PROPERTY_IMPL(QString, grouping, getGrouping, setGrouping) +PROPERTY_IMPL(QString, year, getYear, setYear) +PROPERTY_IMPL(QString, trackNumber, getTrackNumber, setTrackNumber) +PROPERTY_IMPL(QString, trackTotal, getTrackTotal, setTrackTotal) +PROPERTY_IMPL(QString, comment, getComment, setComment) +PROPERTY_IMPL(QString, keyText, getKeyText, setKeyText) + +QColor QmlTrackProxy::getColor() const { + if (m_pTrack == nullptr) { + return QColor(); + } + return RgbColor::toQColor(m_pTrack->getColor()); +} + +double QmlTrackProxy::getDuration() const { + if (m_pTrack == nullptr) { + return -1; + } + return m_pTrack->getDuration(); +} + +int QmlTrackProxy::getSampleRate() const { + if (m_pTrack == nullptr) { + return 0; + } + return m_pTrack->getSampleRate(); +} + +void QmlTrackProxy::setColor(const QColor& value) { + if (m_pTrack) { + std::optional color = RgbColor::fromQColor(value); + m_pTrack->setColor(color); + } +} + +int QmlTrackProxy::getStars() const { + if (m_pTrack == nullptr) { + return -1; + } + return m_pTrack->getRating(); +} + +void QmlTrackProxy::setStars(int value) { + if (m_pTrack && value <= mixxx::TrackRecord::kMaxRating && + value >= mixxx::TrackRecord::kMinRating) { + m_pTrack->setRating(value); + } +} + +QUrl QmlTrackProxy::getCoverArtUrl() const { + if (m_pTrack == nullptr) { + return QUrl(); + } + + const CoverInfo coverInfo = m_pTrack->getCoverInfoWithLocation(); + return AsyncImageProvider::trackLocationToCoverArtUrl(coverInfo.trackLocation); +} + +QUrl QmlTrackProxy::getTrackLocationUrl() const { + if (m_pTrack == nullptr) { + return QUrl(); + } + + return QUrl::fromLocalFile(m_pTrack->getLocation()); +} + +} // namespace qml +} // namespace mixxx diff --git a/src/qml/qmltrackproxy.h b/src/qml/qmltrackproxy.h new file mode 100644 index 000000000000..8b0e292d355c --- /dev/null +++ b/src/qml/qmltrackproxy.h @@ -0,0 +1,148 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include "mixer/basetrackplayer.h" +#include "qml/qmlbeatsmodel.h" +#include "qml/qmlcuesmodel.h" +#include "qml/qmlstemsmodel.h" +#include "track/track_decl.h" +#include "util/parented_ptr.h" + +namespace mixxx { +namespace qml { + +class QmlTrackProxy : public QObject { + Q_OBJECT + + Q_PROPERTY(QString artist READ getArtist WRITE setArtist NOTIFY artistChanged) + Q_PROPERTY(QString title READ getTitle WRITE setTitle NOTIFY titleChanged) + Q_PROPERTY(QString album READ getAlbum WRITE setAlbum NOTIFY albumChanged) + Q_PROPERTY(QString albumArtist READ getAlbumArtist WRITE setAlbumArtist + NOTIFY albumArtistChanged) + Q_PROPERTY(QString genre READ getGenre STORED false NOTIFY genreChanged) + Q_PROPERTY(QString composer READ getComposer WRITE setComposer NOTIFY composerChanged) + Q_PROPERTY(QString grouping READ getGrouping WRITE setGrouping NOTIFY groupingChanged) + Q_PROPERTY(int stars READ getStars WRITE setStars NOTIFY starsChanged) + Q_PROPERTY(QString year READ getYear WRITE setYear NOTIFY yearChanged) + Q_PROPERTY(QString trackNumber READ getTrackNumber WRITE setTrackNumber + NOTIFY trackNumberChanged) + Q_PROPERTY(QString trackTotal READ getTrackTotal WRITE setTrackTotal NOTIFY trackTotalChanged) + Q_PROPERTY(QString comment READ getComment WRITE setComment NOTIFY commentChanged) + Q_PROPERTY(QString keyText READ getKeyText WRITE setKeyText NOTIFY keyTextChanged) + Q_PROPERTY(QColor color READ getColor WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(double duration READ getDuration NOTIFY durationChanged) + Q_PROPERTY(int sampleRate READ getSampleRate NOTIFY sampleRateChanged) + Q_PROPERTY(QUrl coverArtUrl READ getCoverArtUrl NOTIFY coverArtUrlChanged) + Q_PROPERTY(QUrl trackLocationUrl READ getTrackLocationUrl NOTIFY trackLocationUrlChanged) + + Q_PROPERTY(mixxx::qml::QmlBeatsModel* beatsModel READ getBeatsModel CONSTANT); + Q_PROPERTY(mixxx::qml::QmlCuesModel* hotcuesModel READ getCuesModel CONSTANT); +#ifdef __STEM__ + Q_PROPERTY(mixxx::qml::QmlStemsModel* stemsModel READ getStemsModel CONSTANT); +#endif + + QML_NAMED_ELEMENT(Track) + QML_UNCREATABLE("Only accessible via Mixxx.PlayerManager and Mixxx.Library") + public: + explicit QmlTrackProxy(TrackPointer track, QObject* parent = nullptr); + + QString getTrack() const; + QString getTitle() const; + QString getArtist() const; + QString getAlbum() const; + QString getAlbumArtist() const; + QString getGenre() const; + QString getComposer() const; + QString getGrouping() const; + QString getYear() const; + int getStars() const; + QString getTrackNumber() const; + QString getTrackTotal() const; + QString getComment() const; + QString getKeyText() const; + QColor getColor() const; + double getDuration() const; + int getSampleRate() const; + QUrl getCoverArtUrl() const; + QUrl getTrackLocationUrl() const; + + QmlBeatsModel* getBeatsModel() const { + return m_pBeatsModel.get(); + } + + QmlCuesModel* getCuesModel() const { + return m_pHotcuesModel.get(); + } + +#ifdef __STEM__ + QmlStemsModel* getStemsModel() const { + return m_pStemsModel.get(); + } +#endif + + TrackPointer internal() const { + return m_pTrack; + } + + public slots: + void slotBeatsChanged(); + void slotHotcuesChanged(); +#ifdef __STEM__ + void slotStemsChanged(); +#endif + + void setArtist(const QString& artist); + void setTitle(const QString& title); + void setAlbum(const QString& album); + void setAlbumArtist(const QString& albumArtist); + void setComposer(const QString& composer); + void setGrouping(const QString& grouping); + void setStars(int stars); + void setYear(const QString& year); + void setTrackNumber(const QString& trackNumber); + void setTrackTotal(const QString& trackTotal); + void setComment(const QString& comment); + void setKeyText(const QString& keyText); + void setColor(const QColor& color); + + signals: + void albumChanged(); + void titleChanged(); + void artistChanged(); + void albumArtistChanged(); + void genreChanged(); + void composerChanged(); + void groupingChanged(); + void starsChanged(); + void yearChanged(); + void trackNumberChanged(); + void trackTotalChanged(); + void commentChanged(); + void keyTextChanged(); + void colorChanged(); + void durationChanged(); + void sampleRateChanged(); + void coverArtUrlChanged(); + void trackLocationUrlChanged(); + void cuesChanged(); +#ifdef __STEM__ + void stemsChanged(); +#endif + + private: + TrackPointer m_pTrack; + parented_ptr m_pBeatsModel; + parented_ptr m_pHotcuesModel; +#ifdef __STEM__ + parented_ptr m_pStemsModel; +#endif +}; + +} // namespace qml +} // namespace mixxx diff --git a/src/qml/qmlwaveformoverview.cpp b/src/qml/qmlwaveformoverview.cpp index 54d2d1051ca2..133f627ed637 100644 --- a/src/qml/qmlwaveformoverview.cpp +++ b/src/qml/qmlwaveformoverview.cpp @@ -1,7 +1,9 @@ #include "qml/qmlwaveformoverview.h" -#include "mixer/basetrackplayer.h" #include "moc_qmlwaveformoverview.cpp" +#include "qmlplayerproxy.h" +#include "qmltrackproxy.h" +#include "track/track.h" namespace { constexpr double kDesiredChannelHeight = 255; @@ -12,7 +14,7 @@ namespace qml { QmlWaveformOverview::QmlWaveformOverview(QQuickItem* parent) : QQuickPaintedItem(parent), - m_pPlayer(nullptr), + m_pTrack(nullptr), m_channels(ChannelFlag::BothChannels), m_renderer(Renderer::RGB), m_colorHigh(0xFF0000), @@ -20,39 +22,28 @@ QmlWaveformOverview::QmlWaveformOverview(QQuickItem* parent) m_colorLow(0x0000FF) { } -QmlPlayerProxy* QmlWaveformOverview::getPlayer() const { - return m_pPlayer; +QmlTrackProxy* QmlWaveformOverview::getTrack() const { + return m_pTrack; } -void QmlWaveformOverview::setPlayer(QmlPlayerProxy* pPlayer) { - if (m_pPlayer == pPlayer) { +void QmlWaveformOverview::setTrack(QmlTrackProxy* pTrack) { + if (m_pTrack == pTrack) { return; } - if (m_pPlayer != nullptr) { - m_pPlayer->internalTrackPlayer()->disconnect(this); + if (m_pTrack != nullptr && m_pTrack->internal() != nullptr) { + m_pTrack->internal()->disconnect(this); } - m_pPlayer = pPlayer; + m_pTrack = pTrack; - if (m_pPlayer != nullptr) { - setCurrentTrack(m_pPlayer->internalTrackPlayer()->getLoadedTrack()); - connect(m_pPlayer->internalTrackPlayer(), - &BaseTrackPlayer::newTrackLoaded, - this, - &QmlWaveformOverview::slotTrackLoaded); - connect(m_pPlayer->internalTrackPlayer(), - &BaseTrackPlayer::loadingTrack, - this, - &QmlWaveformOverview::slotTrackLoading); - connect(m_pPlayer->internalTrackPlayer(), - &BaseTrackPlayer::playerEmpty, + if (m_pTrack != nullptr && pTrack->internal() != nullptr) { + connect(pTrack->internal().get(), + &Track::waveformSummaryUpdated, this, - &QmlWaveformOverview::slotTrackUnloaded); + &QmlWaveformOverview::slotWaveformUpdated); } - - emit playerChanged(); - update(); + slotWaveformUpdated(); } QmlWaveformOverview::Channels QmlWaveformOverview::getChannels() const { @@ -68,49 +59,15 @@ void QmlWaveformOverview::setChannels(QmlWaveformOverview::Channels channels) { emit channelsChanged(channels); } -void QmlWaveformOverview::slotTrackLoaded(TrackPointer pTrack) { - // TODO: Investigate if it's a bug that this debug assertion fails when - // passing tracks on the command line - // DEBUG_ASSERT(m_pCurrentTrack == pTrack); - setCurrentTrack(pTrack); -} - -void QmlWaveformOverview::slotTrackLoading(TrackPointer pNewTrack, TrackPointer pOldTrack) { - Q_UNUSED(pOldTrack); // only used in DEBUG_ASSERT - DEBUG_ASSERT(m_pCurrentTrack == pOldTrack); - setCurrentTrack(pNewTrack); -} - -void QmlWaveformOverview::slotTrackUnloaded() { - setCurrentTrack(nullptr); -} - -void QmlWaveformOverview::setCurrentTrack(TrackPointer pTrack) { - // TODO: Check if this is actually possible - if (m_pCurrentTrack == pTrack) { - return; - } - - if (m_pCurrentTrack != nullptr) { - disconnect(m_pCurrentTrack.get(), nullptr, this, nullptr); - } - - m_pCurrentTrack = pTrack; - if (pTrack != nullptr) { - connect(pTrack.get(), - &Track::waveformSummaryUpdated, - this, - &QmlWaveformOverview::slotWaveformUpdated); - } - slotWaveformUpdated(); -} - void QmlWaveformOverview::slotWaveformUpdated() { update(); } void QmlWaveformOverview::paint(QPainter* pPainter) { - TrackPointer pTrack = m_pCurrentTrack; + if (!m_pTrack) { + return; + } + TrackPointer pTrack = m_pTrack->internal(); if (!pTrack) { return; } diff --git a/src/qml/qmlwaveformoverview.h b/src/qml/qmlwaveformoverview.h index 4b51bb83747d..1b3ebe745095 100644 --- a/src/qml/qmlwaveformoverview.h +++ b/src/qml/qmlwaveformoverview.h @@ -6,18 +6,17 @@ #include #include -#include "qml/qmlplayerproxy.h" -#include "track/track.h" +#include "qmlplayerproxy.h" +#include "waveform/waveform.h" namespace mixxx { namespace qml { - class QmlWaveformOverview : public QQuickPaintedItem { Q_OBJECT Q_FLAGS(Channels) - Q_PROPERTY(mixxx::qml::QmlPlayerProxy* player READ getPlayer WRITE setPlayer - NOTIFY playerChanged REQUIRED) + Q_PROPERTY(mixxx::qml::QmlTrackProxy* track READ getTrack WRITE setTrack + NOTIFY trackChanged REQUIRED) Q_PROPERTY(Channels channels READ getChannels WRITE setChannels NOTIFY channelsChanged) Q_PROPERTY(Renderer renderer MEMBER m_renderer NOTIFY rendererChanged) Q_PROPERTY(QColor colorHigh MEMBER m_colorHigh NOTIFY colorHighChanged) @@ -44,19 +43,16 @@ class QmlWaveformOverview : public QQuickPaintedItem { void paint(QPainter* painter) override; - void setPlayer(QmlPlayerProxy* player); - QmlPlayerProxy* getPlayer() const; + void setTrack(QmlTrackProxy* track); + QmlTrackProxy* getTrack() const; void setChannels(Channels channels); Channels getChannels() const; private slots: - void slotTrackLoaded(TrackPointer pLoadedTrack); - void slotTrackLoading(TrackPointer pNewTrack, TrackPointer pOldTrack); - void slotTrackUnloaded(); void slotWaveformUpdated(); signals: - void playerChanged(); + void trackChanged(); void channelsChanged(mixxx::qml::QmlWaveformOverview::Channels channels); #if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) void rendererChanged(Renderer renderer); @@ -68,7 +64,6 @@ class QmlWaveformOverview : public QQuickPaintedItem { void colorLowChanged(const QColor& color); private: - void setCurrentTrack(TrackPointer pTrack); void drawFiltered(QPainter* pPainter, Channels channels, ConstWaveformPointer pWaveform, @@ -78,9 +73,7 @@ class QmlWaveformOverview : public QQuickPaintedItem { ConstWaveformPointer pWaveform, int completion) const; QColor getRgbPenColor(ConstWaveformPointer pWaveform, int completion) const; - - QPointer m_pPlayer; - TrackPointer m_pCurrentTrack; + QmlTrackProxy* m_pTrack; Channels m_channels; Renderer m_renderer; QColor m_colorHigh; diff --git a/src/test/controller_mapping_validation_test.cpp b/src/test/controller_mapping_validation_test.cpp index 1b2d7b37d72d..2b886449412a 100644 --- a/src/test/controller_mapping_validation_test.cpp +++ b/src/test/controller_mapping_validation_test.cpp @@ -7,6 +7,7 @@ #include "controllers/defs_controllers.h" #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" +#include "track/track.h" #ifdef MIXXX_USE_QML #include "effects/effectsmanager.h" #include "engine/channelhandle.h"