From 59ed03ffdc0e2dfb393f0864211aab59e7efd266 Mon Sep 17 00:00:00 2001 From: Bacadam Date: Sun, 3 Mar 2024 12:09:14 +0100 Subject: [PATCH 01/49] Synchronize AutoDJ next deck with top track in queue --- src/library/autodj/autodjprocessor.cpp | 9 +++++++++ src/library/autodj/autodjprocessor.h | 1 + src/library/playlisttablemodel.cpp | 8 ++++++++ src/library/playlisttablemodel.h | 3 +++ 4 files changed, 21 insertions(+) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 27ea9b216a3..657f720dbc7 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -534,6 +534,10 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::toggleAutoDJ(bool enable) { &DeckAttributes::rateChanged, this, &AutoDJProcessor::playerRateChanged); + connect(m_pAutoDJTableModel, + &PlaylistTableModel::firstTrackChanged, + this, + &AutoDJProcessor::playlistFirstTrackChanged); if (!leftDeckPlaying && !rightDeckPlaying) { // Both decks are stopped. Load a track into deck 1 and start it @@ -1665,6 +1669,11 @@ void AutoDJProcessor::playerRateChanged(DeckAttributes* pAttributes) { calculateTransition(fromDeck, getOtherDeck(fromDeck), false); } +void AutoDJProcessor::playlistFirstTrackChanged() { + qDebug() << this << "playlistFirstTrackChanged"; + skipNext(); +} + void AutoDJProcessor::setTransitionTime(int time) { if constexpr (sDebug) { qDebug() << this << "setTransitionTime" << time; diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index 600cf7bfb70..a23f1ae9fe7 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -219,6 +219,7 @@ class AutoDJProcessor : public QObject { void playerLoadingTrack(DeckAttributes* pDeck, TrackPointer pNewTrack, TrackPointer pOldTrack); void playerEmpty(DeckAttributes* pDeck); void playerRateChanged(DeckAttributes* pDeck); + void playlistFirstTrackChanged(); void controlEnableChangeRequest(double value); void controlFadeNow(double value); diff --git a/src/library/playlisttablemodel.cpp b/src/library/playlisttablemodel.cpp index a860ebab6e6..41cdc091ea3 100644 --- a/src/library/playlisttablemodel.cpp +++ b/src/library/playlisttablemodel.cpp @@ -250,6 +250,10 @@ void PlaylistTableModel::removeTracks(const QModelIndexList& indices) { m_pTrackCollectionManager->internalCollection()->getPlaylistDAO().removeTracksFromPlaylist( m_iPlaylistId, std::move(trackPositions)); + + if (trackPositions.contains(1)) { + emit firstTrackChanged(); + } } void PlaylistTableModel::moveTrack(const QModelIndex& sourceIndex, @@ -275,6 +279,10 @@ void PlaylistTableModel::moveTrack(const QModelIndex& sourceIndex, } m_pTrackCollectionManager->internalCollection()->getPlaylistDAO().moveTrack(m_iPlaylistId, oldPosition, newPosition); + + if (oldPosition == 1 || newPosition == 1) { + emit firstTrackChanged(); + } } bool PlaylistTableModel::isLocked() { diff --git a/src/library/playlisttablemodel.h b/src/library/playlisttablemodel.h index 0443bd53577..17361d716a6 100644 --- a/src/library/playlisttablemodel.h +++ b/src/library/playlisttablemodel.h @@ -38,6 +38,9 @@ class PlaylistTableModel final : public TrackSetTableModel { private slots: void playlistsChanged(const QSet& playlistIds); + signals: + void firstTrackChanged(); + private: void initSortColumnMapping() override; From c1e7c22351e16c15aedfc1f4eb7aedcc5a008dc6 Mon Sep 17 00:00:00 2001 From: Bacadam Date: Thu, 14 Mar 2024 12:19:59 +0100 Subject: [PATCH 02/49] Remove calls to removeLoadedTrackFromTopOfQueue() --- src/library/autodj/autodjprocessor.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 657f720dbc7..d0c2c0481e2 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1671,7 +1671,22 @@ void AutoDJProcessor::playerRateChanged(DeckAttributes* pAttributes) { void AutoDJProcessor::playlistFirstTrackChanged() { qDebug() << this << "playlistFirstTrackChanged"; - skipNext(); + if (m_eState != ADJ_DISABLED) { + DeckAttributes* pLeftDeck = getLeftDeck(); + DeckAttributes* pRightDeck = getRightDeck(); + // if (!pLeftDeck || !pRightDeck) { + // // User has changed the orientation, disable Auto DJ + // toggleAutoDJ(false); + // emit autoDJError(ADJ_NOT_TWO_DECKS); + // return ADJ_NOT_TWO_DECKS; + // } + + if (!pLeftDeck->isPlaying()) { + loadNextTrackFromQueue(*pLeftDeck); + } else if (!pRightDeck->isPlaying()) { + loadNextTrackFromQueue(*pRightDeck); + } + } } void AutoDJProcessor::setTransitionTime(int time) { From 9e1eec44b0b82fe8fcbcc1326ebbb86451dd69f2 Mon Sep 17 00:00:00 2001 From: Bacadam Date: Thu, 14 Mar 2024 14:42:55 +0100 Subject: [PATCH 03/49] Synchronize also with "Add to AutoDJ" buttons in context menu --- src/library/autodj/autodjprocessor.h | 4 +++- src/library/dao/playlistdao.cpp | 11 ++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index a23f1ae9fe7..f5c82e23be8 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -207,6 +207,9 @@ class AutoDJProcessor : public QObject { void transitionTimeChanged(int time); void randomTrackRequested(int tracksToAdd); + public slots: + void playlistFirstTrackChanged(); + private slots: void crossfaderChanged(double value); void playerPositionChanged(DeckAttributes* pDeck, double position); @@ -219,7 +222,6 @@ class AutoDJProcessor : public QObject { void playerLoadingTrack(DeckAttributes* pDeck, TrackPointer pNewTrack, TrackPointer pOldTrack); void playerEmpty(DeckAttributes* pDeck); void playerRateChanged(DeckAttributes* pDeck); - void playlistFirstTrackChanged(); void controlEnableChangeRequest(double value); void controlFadeNow(double value); diff --git a/src/library/dao/playlistdao.cpp b/src/library/dao/playlistdao.cpp index c9b9d24265f..5735b61b363 100644 --- a/src/library/dao/playlistdao.cpp +++ b/src/library/dao/playlistdao.cpp @@ -1278,21 +1278,18 @@ void PlaylistDAO::addTracksToAutoDJQueue(const QList& trackIds, AutoDJS return; } - // If the first track is already loaded to the player, - // alter the playlist only below the first track - int position = - (m_pAutoDJProcessor && m_pAutoDJProcessor->nextTrackLoaded()) ? 2 : 1; - switch (loc) { case AutoDJSendLoc::TOP: - insertTracksIntoPlaylist(trackIds, iAutoDJPlaylistId, position); + insertTracksIntoPlaylist(trackIds, iAutoDJPlaylistId, 1); + m_pAutoDJProcessor->playlistFirstTrackChanged(); break; case AutoDJSendLoc::BOTTOM: appendTracksToPlaylist(trackIds, iAutoDJPlaylistId); break; case AutoDJSendLoc::REPLACE: - if (removeTracksFromPlaylist(iAutoDJPlaylistId, position)) { + if (removeTracksFromPlaylist(iAutoDJPlaylistId, 1)) { appendTracksToPlaylist(trackIds, iAutoDJPlaylistId); + m_pAutoDJProcessor->playlistFirstTrackChanged(); } break; } From d9fbb5942ab5f6a5bf904fbb1f6354f2a5b18bb2 Mon Sep 17 00:00:00 2001 From: Bacadam Date: Tue, 26 Mar 2024 21:39:18 +0100 Subject: [PATCH 04/49] Remove unnecessary comments --- src/library/autodj/autodjprocessor.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index d0c2c0481e2..97e78244241 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1674,12 +1674,6 @@ void AutoDJProcessor::playlistFirstTrackChanged() { if (m_eState != ADJ_DISABLED) { DeckAttributes* pLeftDeck = getLeftDeck(); DeckAttributes* pRightDeck = getRightDeck(); - // if (!pLeftDeck || !pRightDeck) { - // // User has changed the orientation, disable Auto DJ - // toggleAutoDJ(false); - // emit autoDJError(ADJ_NOT_TWO_DECKS); - // return ADJ_NOT_TWO_DECKS; - // } if (!pLeftDeck->isPlaying()) { loadNextTrackFromQueue(*pLeftDeck); From 9763555f13180596ac836dbd264bdb7f8b4fa323 Mon Sep 17 00:00:00 2001 From: Bacadam Date: Fri, 12 Apr 2024 22:22:08 +0200 Subject: [PATCH 05/49] Revert "Synchronize also with "Add to AutoDJ" buttons in context menu" This reverts commit 9e1eec44b0b82fe8fcbcc1326ebbb86451dd69f2. --- src/library/autodj/autodjprocessor.h | 4 +--- src/library/dao/playlistdao.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index f5c82e23be8..a23f1ae9fe7 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -207,9 +207,6 @@ class AutoDJProcessor : public QObject { void transitionTimeChanged(int time); void randomTrackRequested(int tracksToAdd); - public slots: - void playlistFirstTrackChanged(); - private slots: void crossfaderChanged(double value); void playerPositionChanged(DeckAttributes* pDeck, double position); @@ -222,6 +219,7 @@ class AutoDJProcessor : public QObject { void playerLoadingTrack(DeckAttributes* pDeck, TrackPointer pNewTrack, TrackPointer pOldTrack); void playerEmpty(DeckAttributes* pDeck); void playerRateChanged(DeckAttributes* pDeck); + void playlistFirstTrackChanged(); void controlEnableChangeRequest(double value); void controlFadeNow(double value); diff --git a/src/library/dao/playlistdao.cpp b/src/library/dao/playlistdao.cpp index 5735b61b363..c9b9d24265f 100644 --- a/src/library/dao/playlistdao.cpp +++ b/src/library/dao/playlistdao.cpp @@ -1278,18 +1278,21 @@ void PlaylistDAO::addTracksToAutoDJQueue(const QList& trackIds, AutoDJS return; } + // If the first track is already loaded to the player, + // alter the playlist only below the first track + int position = + (m_pAutoDJProcessor && m_pAutoDJProcessor->nextTrackLoaded()) ? 2 : 1; + switch (loc) { case AutoDJSendLoc::TOP: - insertTracksIntoPlaylist(trackIds, iAutoDJPlaylistId, 1); - m_pAutoDJProcessor->playlistFirstTrackChanged(); + insertTracksIntoPlaylist(trackIds, iAutoDJPlaylistId, position); break; case AutoDJSendLoc::BOTTOM: appendTracksToPlaylist(trackIds, iAutoDJPlaylistId); break; case AutoDJSendLoc::REPLACE: - if (removeTracksFromPlaylist(iAutoDJPlaylistId, 1)) { + if (removeTracksFromPlaylist(iAutoDJPlaylistId, position)) { appendTracksToPlaylist(trackIds, iAutoDJPlaylistId); - m_pAutoDJProcessor->playlistFirstTrackChanged(); } break; } From d0b5d9489a7e7b18d69a8fa8d1611996bc69992f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 22 Apr 2024 21:28:32 +0200 Subject: [PATCH 06/49] Remove autdated ASSERT fixing #13164 --- src/library/basetracktablemodel.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index 4fc2a5ab1e6..5a6401861c5 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -489,10 +489,6 @@ QVariant BaseTrackTableModel::rawSiblingValue( // FIXME: This should never happen but it does. But why?? return QVariant(); } - VERIFY_OR_DEBUG_ASSERT(siblingColumn != index.column()) { - // Prevent infinite recursion - return QVariant(); - } const auto siblingIndex = index.sibling(index.row(), siblingColumn); return rawValue(siblingIndex); } From 32c46d45c1b22947472f01be787471d585a613e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 5 May 2024 21:56:11 +0200 Subject: [PATCH 07/49] Pass the already calculated fileType string form the Track object through the call stack to avoid guessing it from the file extension. --- src/track/beatsimporter.h | 4 +++- src/track/cueinfoimporter.cpp | 6 ++++-- src/track/cueinfoimporter.h | 2 ++ src/track/serato/beatsimporter.cpp | 6 ++++-- src/track/serato/beatsimporter.h | 1 + src/track/serato/cueinfoimporter.cpp | 3 ++- src/track/serato/cueinfoimporter.h | 1 + src/track/serato/tags.cpp | 3 ++- src/track/serato/tags.h | 4 +++- src/track/track.cpp | 8 +++++--- 10 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/track/beatsimporter.h b/src/track/beatsimporter.h index 4fd8b5c4ba0..3c677d299e9 100644 --- a/src/track/beatsimporter.h +++ b/src/track/beatsimporter.h @@ -19,7 +19,9 @@ class BeatsImporter { /// Determines the timing offset and returns a Beats object. virtual BeatsPointer importBeatsAndApplyTimingOffset( - const QString& filePath, const audio::StreamInfo& streamInfo) = 0; + const QString& filePath, + const QString& fileType, + const audio::StreamInfo& streamInfo) = 0; }; typedef std::shared_ptr BeatsImporterPointer; diff --git a/src/track/cueinfoimporter.cpp b/src/track/cueinfoimporter.cpp index 5ebaa530858..94843403ff5 100644 --- a/src/track/cueinfoimporter.cpp +++ b/src/track/cueinfoimporter.cpp @@ -12,14 +12,15 @@ bool CueInfoImporter::hasCueOfType(CueType cueType) const { return true; } } - return false; } double CueInfoImporter::guessTimingOffsetMillis( const QString& filePath, + const QString& fileType, const audio::SignalInfo& signalInfo) const { Q_UNUSED(filePath); + Q_UNUSED(fileType); Q_UNUSED(signalInfo); return 0; }; @@ -34,6 +35,7 @@ bool CueInfoImporter::isEmpty() const { QList CueInfoImporter::importCueInfosAndApplyTimingOffset( const QString& filePath, + const QString& fileType, const audio::SignalInfo& signalInfo) { // Consume the collected cue points during the import QList cueInfos = m_cueInfos; @@ -43,7 +45,7 @@ QList CueInfoImporter::importCueInfosAndApplyTimingOffset( return cueInfos; } - double timingOffsetMillis = guessTimingOffsetMillis(filePath, signalInfo); + double timingOffsetMillis = guessTimingOffsetMillis(filePath, fileType, signalInfo); // If we don't have any offset, we can just return the CueInfo objects // unchanged. diff --git a/src/track/cueinfoimporter.h b/src/track/cueinfoimporter.h index 1f401dbc3fe..bcf45d333e3 100644 --- a/src/track/cueinfoimporter.h +++ b/src/track/cueinfoimporter.h @@ -22,6 +22,7 @@ class CueInfoImporter { /// in subclasses. virtual double guessTimingOffsetMillis( const QString& filePath, + const QString& fileType, const audio::SignalInfo& signalInfo) const; int size() const; @@ -29,6 +30,7 @@ class CueInfoImporter { QList importCueInfosAndApplyTimingOffset( const QString& filePath, + const QString& fileType, const audio::SignalInfo& signalInfo); private: diff --git a/src/track/serato/beatsimporter.cpp b/src/track/serato/beatsimporter.cpp index 45822d1e1d9..905b2321ea2 100644 --- a/src/track/serato/beatsimporter.cpp +++ b/src/track/serato/beatsimporter.cpp @@ -23,10 +23,12 @@ bool SeratoBeatsImporter::isEmpty() const { }; BeatsPointer SeratoBeatsImporter::importBeatsAndApplyTimingOffset( - const QString& filePath, const audio::StreamInfo& streamInfo) { + const QString& filePath, + const QString& fileType, + const audio::StreamInfo& streamInfo) { const audio::SignalInfo& signalInfo = streamInfo.getSignalInfo(); const double timingOffsetMillis = SeratoTags::guessTimingOffsetMillis( - filePath, signalInfo); + filePath, fileType, signalInfo); return importBeatsAndApplyTimingOffset(timingOffsetMillis, signalInfo); } diff --git a/src/track/serato/beatsimporter.h b/src/track/serato/beatsimporter.h index 9a4923e6b10..5dfddf19a32 100644 --- a/src/track/serato/beatsimporter.h +++ b/src/track/serato/beatsimporter.h @@ -21,6 +21,7 @@ class SeratoBeatsImporter : public BeatsImporter { bool isEmpty() const override; BeatsPointer importBeatsAndApplyTimingOffset( const QString& filePath, + const QString& fileType, const audio::StreamInfo& streamInfo) override; private: diff --git a/src/track/serato/cueinfoimporter.cpp b/src/track/serato/cueinfoimporter.cpp index e1f81b335cb..951fcf9557d 100644 --- a/src/track/serato/cueinfoimporter.cpp +++ b/src/track/serato/cueinfoimporter.cpp @@ -22,8 +22,9 @@ bool SeratoCueInfoImporter::hasCueOfType(CueType cueType) const { /// the SeratoTags for the time being. double SeratoCueInfoImporter::guessTimingOffsetMillis( const QString& filePath, + const QString& fileType, const audio::SignalInfo& signalInfo) const { - return SeratoTags::guessTimingOffsetMillis(filePath, signalInfo); + return SeratoTags::guessTimingOffsetMillis(filePath, fileType, signalInfo); } } // namespace mixxx diff --git a/src/track/serato/cueinfoimporter.h b/src/track/serato/cueinfoimporter.h index a068dc14029..05867e5a9be 100644 --- a/src/track/serato/cueinfoimporter.h +++ b/src/track/serato/cueinfoimporter.h @@ -14,6 +14,7 @@ class SeratoCueInfoImporter : public CueInfoImporter { double guessTimingOffsetMillis( const QString& filePath, + const QString& fileType, const audio::SignalInfo& signalInfo) const override; }; diff --git a/src/track/serato/tags.cpp b/src/track/serato/tags.cpp index 288dfe5c0ca..15e55fb85e1 100644 --- a/src/track/serato/tags.cpp +++ b/src/track/serato/tags.cpp @@ -73,6 +73,7 @@ namespace mixxx { double SeratoTags::guessTimingOffsetMillis( const QString& filePath, + const QString& fileType, const audio::SignalInfo& signalInfo) { // The following code accounts for timing offsets required to // correctly align timing information (e.g. cue points) exported from @@ -82,7 +83,7 @@ double SeratoTags::guessTimingOffsetMillis( // PR for more detailed information: // https://github.com/mixxxdj/mixxx/pull/2119 double timingOffset = 0; - if (taglib::getFileTypeFromFileName(filePath) == taglib::FileType::MP3) { + if (fileType == "mp3") { const QString primaryDecoderName = getPrimaryDecoderNameForFilePath(filePath); // There should always be an MP3 decoder available diff --git a/src/track/serato/tags.h b/src/track/serato/tags.h index fc187f313a3..d5461bb2c96 100644 --- a/src/track/serato/tags.h +++ b/src/track/serato/tags.h @@ -28,7 +28,9 @@ class SeratoTags final { } static double guessTimingOffsetMillis( - const QString& filePath, const audio::SignalInfo& signalInfo); + const QString& filePath, + const QString& fileType, + const audio::SignalInfo& signalInfo); bool isEmpty() const { return m_seratoBeatGrid.isEmpty() && m_seratoMarkers.isEmpty() && diff --git a/src/track/track.cpp b/src/track/track.cpp index 659f37ce717..f0e12a776e0 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -1136,7 +1136,7 @@ bool Track::importPendingBeatsWhileLocked() { m_record.getMetadata().getStreamInfo().getSignalInfo().getSampleRate()); const auto pBeats = m_pBeatsImporterPending->importBeatsAndApplyTimingOffset( - getLocation(), *m_record.getStreamInfoFromSource()); + getLocation(), getType(), *m_record.getStreamInfoFromSource()); DEBUG_ASSERT(m_pBeatsImporterPending->isEmpty()); m_pBeatsImporterPending.reset(); return setBeatsWhileLocked(pBeats); @@ -1283,7 +1283,9 @@ bool Track::importPendingCueInfosWhileLocked() { const QList cueInfos = m_pCueInfoImporterPending->importCueInfosAndApplyTimingOffset( - getLocation(), m_record.getStreamInfoFromSource()->getSignalInfo()); + getLocation(), + getType(), + m_record.getStreamInfoFromSource()->getSignalInfo()); for (const auto& cueInfo : cueInfos) { CuePointer pCue(new Cue(cueInfo, sampleRate, true)); // While this method could be called from any thread, @@ -1487,7 +1489,7 @@ bool Track::exportSeratoMetadata() { } const double timingOffset = mixxx::SeratoTags::guessTimingOffsetMillis( - getLocation(), streamInfo->getSignalInfo()); + getLocation(), getType(), streamInfo->getSignalInfo()); pSeratoTags->setCueInfos(cueInfos, timingOffset); pSeratoTags->setBeats(m_pBeats, streamInfo->getSignalInfo(), From 6d0d355d7fcab35ec2d75f81b030fa9492430b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 6 May 2024 23:36:42 +0200 Subject: [PATCH 08/49] Aligne mixxx::taglib::FileType with TagLib_File_Type --- src/sources/metadatasourcetaglib.cpp | 24 +++++++++++------------ src/test/seratobeatgridtest.cpp | 4 ++-- src/test/seratomarkers2test.cpp | 6 +++--- src/test/seratomarkerstest.cpp | 4 ++-- src/test/seratotagstest.cpp | 4 ++-- src/test/taglibtest.cpp | 4 ++-- src/track/serato/beatgrid.cpp | 4 ++-- src/track/serato/markers.cpp | 4 ++-- src/track/serato/markers2.cpp | 8 ++++---- src/track/serato/tags.h | 12 ++++++------ src/track/taglib/trackmetadata_file.cpp | 8 ++++---- src/track/taglib/trackmetadata_file.h | 25 ++++++++++++++++++------ src/track/taglib/trackmetadata_id3v2.cpp | 12 ++++++------ 13 files changed, 66 insertions(+), 53 deletions(-) diff --git a/src/sources/metadatasourcetaglib.cpp b/src/sources/metadatasourcetaglib.cpp index b0750a44bf4..fd1173c8cc1 100644 --- a/src/sources/metadatasourcetaglib.cpp +++ b/src/sources/metadatasourcetaglib.cpp @@ -111,7 +111,7 @@ MetadataSourceTagLib::importTrackMetadataAndCoverImage( // is read and data in subsequent tags is ignored. switch (m_fileType) { - case taglib::FileType::MP3: { + case taglib::FileType::MPEG: { TagLib::MPEG::File file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); if (!taglib::readAudioPropertiesFromFile(pTrackMetadata, file)) { break; @@ -198,7 +198,7 @@ MetadataSourceTagLib::importTrackMetadataAndCoverImage( } break; } - case taglib::FileType::OGG: { + case taglib::FileType::OggVorbis: { TagLib::Ogg::Vorbis::File file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); if (!taglib::readAudioPropertiesFromFile(pTrackMetadata, file)) { break; @@ -207,14 +207,14 @@ MetadataSourceTagLib::importTrackMetadataAndCoverImage( if (pTag) { taglib::xiph::importTrackMetadataFromTag(pTrackMetadata, *pTag, - taglib::FileType::OGG, + taglib::FileType::OggVorbis, resetMissingTagMetadata); taglib::xiph::importCoverImageFromTag(pCoverImage, *pTag); return afterImport(ImportResult::Succeeded); } break; } - case taglib::FileType::OPUS: { + case taglib::FileType::Opus: { TagLib::Ogg::Opus::File file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); if (!taglib::readAudioPropertiesFromFile(pTrackMetadata, file)) { break; @@ -223,14 +223,14 @@ MetadataSourceTagLib::importTrackMetadataAndCoverImage( if (pTag) { taglib::xiph::importTrackMetadataFromTag(pTrackMetadata, *pTag, - taglib::FileType::OPUS, + taglib::FileType::Opus, resetMissingTagMetadata); taglib::xiph::importCoverImageFromTag(pCoverImage, *pTag); return afterImport(ImportResult::Succeeded); } break; } - case taglib::FileType::WV: { + case taglib::FileType::WavPack: { TagLib::WavPack::File file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); if (!taglib::readAudioPropertiesFromFile(pTrackMetadata, file)) { break; @@ -474,7 +474,7 @@ class OggTagSaver : public TagSaver { #else return pFile->isOpen() && taglib::xiph::exportTrackMetadataIntoTag( - pFile->tag(), trackMetadata, taglib::FileType::OGG); + pFile->tag(), trackMetadata, taglib::FileType::OggVorbis); #endif } @@ -503,7 +503,7 @@ class OpusTagSaver : public TagSaver { const TrackMetadata& trackMetadata) { return pFile->isOpen() && taglib::xiph::exportTrackMetadataIntoTag( - pFile->tag(), trackMetadata, taglib::FileType::OPUS); + pFile->tag(), trackMetadata, taglib::FileType::Opus); } TagLib::Ogg::Opus::File m_file; @@ -636,7 +636,7 @@ MetadataSourceTagLib::exportTrackMetadata( std::unique_ptr pTagSaver; switch (m_fileType) { - case taglib::FileType::MP3: { + case taglib::FileType::MPEG: { pTagSaver = std::make_unique(safelyWritableFile.fileName(), trackMetadata); break; } @@ -648,15 +648,15 @@ MetadataSourceTagLib::exportTrackMetadata( pTagSaver = std::make_unique(safelyWritableFile.fileName(), trackMetadata); break; } - case taglib::FileType::OGG: { + case taglib::FileType::OggVorbis: { pTagSaver = std::make_unique(safelyWritableFile.fileName(), trackMetadata); break; } - case taglib::FileType::OPUS: { + case taglib::FileType::Opus: { pTagSaver = std::make_unique(safelyWritableFile.fileName(), trackMetadata); break; } - case taglib::FileType::WV: { + case taglib::FileType::WavPack: { pTagSaver = std::make_unique(safelyWritableFile.fileName(), trackMetadata); break; } diff --git a/src/test/seratobeatgridtest.cpp b/src/test/seratobeatgridtest.cpp index 92ddbef9545..517af4b3c4a 100644 --- a/src/test/seratobeatgridtest.cpp +++ b/src/test/seratobeatgridtest.cpp @@ -88,11 +88,11 @@ TEST_F(SeratoBeatGridTest, ParseBeatGridDataMP3) { parseBeatGridDataInDirectory( QDir(MixxxTest::getOrInitTestDir().filePath( QStringLiteral("serato/data/mp3/beatgrid"))), - mixxx::taglib::FileType::MP3); + mixxx::taglib::FileType::MPEG); } TEST_F(SeratoBeatGridTest, ParseEmptyDataMP3) { - parseEmptyBeatGridData(mixxx::taglib::FileType::MP3); + parseEmptyBeatGridData(mixxx::taglib::FileType::MPEG); } TEST_F(SeratoBeatGridTest, ParseBeatGridDataMP4) { diff --git a/src/test/seratomarkers2test.cpp b/src/test/seratomarkers2test.cpp index 331d9ae765d..744949395df 100644 --- a/src/test/seratomarkers2test.cpp +++ b/src/test/seratomarkers2test.cpp @@ -429,7 +429,7 @@ TEST_F(SeratoMarkers2Test, ParseMarkers2DataMP3) { parseMarkers2DataInDirectory( QDir(MixxxTest::getOrInitTestDir().filePath( QStringLiteral("serato/data/mp3/markers2"))), - mixxx::taglib::FileType::MP3); + mixxx::taglib::FileType::MPEG); } TEST_F(SeratoMarkers2Test, ParseMarkers2DataMP4) { @@ -450,11 +450,11 @@ TEST_F(SeratoMarkers2Test, ParseMarkers2DataOGG) { parseMarkers2DataInDirectory( QDir(MixxxTest::getOrInitTestDir().filePath( QStringLiteral("serato/data/ogg/markers2"))), - mixxx::taglib::FileType::OGG); + mixxx::taglib::FileType::OggVorbis); } TEST_F(SeratoMarkers2Test, ParseEmptyDataMP3) { - parseEmptyMarkers2Data(mixxx::taglib::FileType::MP3); + parseEmptyMarkers2Data(mixxx::taglib::FileType::MPEG); } TEST_F(SeratoMarkers2Test, ParseEmptyDataMP4) { diff --git a/src/test/seratomarkerstest.cpp b/src/test/seratomarkerstest.cpp index b412c56fd34..e3be2e48f09 100644 --- a/src/test/seratomarkerstest.cpp +++ b/src/test/seratomarkerstest.cpp @@ -181,7 +181,7 @@ TEST_F(SeratoMarkersTest, ParseMarkersDataMP3) { parseMarkersDataInDirectory( QDir(MixxxTest::getOrInitTestDir().filePath( QStringLiteral("serato/data/mp3/markers_"))), - mixxx::taglib::FileType::MP3); + mixxx::taglib::FileType::MPEG); } TEST_F(SeratoMarkersTest, ParseMarkersDataMP4) { @@ -192,7 +192,7 @@ TEST_F(SeratoMarkersTest, ParseMarkersDataMP4) { } TEST_F(SeratoMarkersTest, ParseEmptyDataMP3) { - parseEmptyMarkersData(mixxx::taglib::FileType::MP3); + parseEmptyMarkersData(mixxx::taglib::FileType::MPEG); } TEST_F(SeratoMarkersTest, ParseEmptyDataMP4) { diff --git a/src/test/seratotagstest.cpp b/src/test/seratotagstest.cpp index 41c997ceb36..297c013bf0f 100644 --- a/src/test/seratotagstest.cpp +++ b/src/test/seratotagstest.cpp @@ -271,7 +271,7 @@ TEST_F(SeratoTagsTest, CueColorConversionRoundtrip) { } TEST_F(SeratoTagsTest, MarkersParseDumpRoundtrip) { - const auto filetype = mixxx::taglib::FileType::MP3; + const auto filetype = mixxx::taglib::FileType::MPEG; QDir dir(MixxxTest::getOrInitTestDir().filePath(QStringLiteral("/serato/data/mp3/markers_/"))); dir.setFilter(QDir::Files); dir.setNameFilters(QStringList() << "*.octet-stream"); @@ -306,7 +306,7 @@ TEST_F(SeratoTagsTest, MarkersParseDumpRoundtrip) { } TEST_F(SeratoTagsTest, Markers2RoundTrip) { - const auto filetype = mixxx::taglib::FileType::MP3; + const auto filetype = mixxx::taglib::FileType::MPEG; QDir dir(MixxxTest::getOrInitTestDir().filePath(QStringLiteral("serato/data/mp3/markers2/"))); dir.setFilter(QDir::Files); dir.setNameFilters(QStringList() << "*.octet-stream"); diff --git a/src/test/taglibtest.cpp b/src/test/taglibtest.cpp index 4aca9ceb691..419a20b1084 100644 --- a/src/test/taglibtest.cpp +++ b/src/test/taglibtest.cpp @@ -36,7 +36,7 @@ TEST_F(TagLibTest, WriteID3v2Tag) { trackMetadata.refTrackInfo().setTitle(QStringLiteral("title")); const auto exported = mixxx::MetadataSourceTagLib( - tmpFileName, mixxx::taglib::FileType::MP3) + tmpFileName, mixxx::taglib::FileType::MPEG) .exportTrackMetadata(trackMetadata); ASSERT_EQ(mixxx::MetadataSource::ExportResult::Succeeded, exported.first); ASSERT_FALSE(exported.second.isNull()); @@ -56,7 +56,7 @@ TEST_F(TagLibTest, WriteID3v2Tag) { trackMetadata.refTrackInfo().setTitle(QStringLiteral("title2")); const auto exported2 = mixxx::MetadataSourceTagLib( - tmpFileName, mixxx::taglib::FileType::MP3) + tmpFileName, mixxx::taglib::FileType::MPEG) .exportTrackMetadata(trackMetadata); ASSERT_EQ(mixxx::MetadataSource::ExportResult::Succeeded, exported.first); ASSERT_FALSE(exported.second.isNull()); diff --git a/src/track/serato/beatgrid.cpp b/src/track/serato/beatgrid.cpp index f024883cfd6..dc9da0f9551 100644 --- a/src/track/serato/beatgrid.cpp +++ b/src/track/serato/beatgrid.cpp @@ -174,7 +174,7 @@ bool SeratoBeatGrid::parse(SeratoBeatGrid* seratoBeatGrid, } switch (fileType) { - case taglib::FileType::MP3: + case taglib::FileType::MPEG: case taglib::FileType::AIFF: return parseID3(seratoBeatGrid, data); case taglib::FileType::MP4: @@ -363,7 +363,7 @@ bool SeratoBeatGrid::parseBase64Encoded( QByteArray SeratoBeatGrid::dump(taglib::FileType fileType) const { switch (fileType) { - case taglib::FileType::MP3: + case taglib::FileType::MPEG: case taglib::FileType::AIFF: return dumpID3(); case taglib::FileType::MP4: diff --git a/src/track/serato/markers.cpp b/src/track/serato/markers.cpp index fd79bfb6967..8c12a24ec7c 100644 --- a/src/track/serato/markers.cpp +++ b/src/track/serato/markers.cpp @@ -329,7 +329,7 @@ bool SeratoMarkers::parse( } switch (fileType) { - case taglib::FileType::MP3: + case taglib::FileType::MPEG: case taglib::FileType::AIFF: return parseID3(seratoMarkers, data); case taglib::FileType::MP4: @@ -546,7 +546,7 @@ bool SeratoMarkers::parseMP4( QByteArray SeratoMarkers::dump(taglib::FileType fileType) const { switch (fileType) { - case taglib::FileType::MP3: + case taglib::FileType::MPEG: case taglib::FileType::AIFF: return dumpID3(); case taglib::FileType::MP4: diff --git a/src/track/serato/markers2.cpp b/src/track/serato/markers2.cpp index d4d714b05e5..554b8f3a382 100644 --- a/src/track/serato/markers2.cpp +++ b/src/track/serato/markers2.cpp @@ -359,14 +359,14 @@ bool SeratoMarkers2::parse( } switch (fileType) { - case taglib::FileType::MP3: + case taglib::FileType::MPEG: case taglib::FileType::AIFF: return parseID3(seratoMarkers2, data); case taglib::FileType::MP4: return parseBase64Encoded(seratoMarkers2, data); case taglib::FileType::FLAC: return parseFLAC(seratoMarkers2, data); - case taglib::FileType::OGG: + case taglib::FileType::OggVorbis: return parseCommon(seratoMarkers2, data); default: return false; @@ -494,14 +494,14 @@ bool SeratoMarkers2::parseFLAC( QByteArray SeratoMarkers2::dump(taglib::FileType fileType) const { switch (fileType) { - case taglib::FileType::MP3: + case taglib::FileType::MPEG: case taglib::FileType::AIFF: return dumpID3(); case taglib::FileType::MP4: return dumpBase64Encoded(); case taglib::FileType::FLAC: return dumpFLAC(); - case taglib::FileType::OGG: + case taglib::FileType::OggVorbis: return dumpCommon(); default: DEBUG_ASSERT(false); diff --git a/src/track/serato/tags.h b/src/track/serato/tags.h index d5461bb2c96..0f5e0392592 100644 --- a/src/track/serato/tags.h +++ b/src/track/serato/tags.h @@ -127,12 +127,12 @@ class SeratoTags final { inline bool operator==(const SeratoTags& lhs, const SeratoTags& rhs) { // FIXME: Find a more efficient way to do this - return (lhs.dumpBeatGrid(taglib::FileType::MP3) == - rhs.dumpBeatGrid(taglib::FileType::MP3) && - lhs.dumpMarkers(taglib::FileType::MP3) == - rhs.dumpMarkers(taglib::FileType::MP3) && - lhs.dumpMarkers2(taglib::FileType::MP3) == - rhs.dumpMarkers2(taglib::FileType::MP3)); + return (lhs.dumpBeatGrid(taglib::FileType::MPEG) == + rhs.dumpBeatGrid(taglib::FileType::MPEG) && + lhs.dumpMarkers(taglib::FileType::MPEG) == + rhs.dumpMarkers(taglib::FileType::MPEG) && + lhs.dumpMarkers2(taglib::FileType::MPEG) == + rhs.dumpMarkers2(taglib::FileType::MPEG)); } inline bool operator!=(const SeratoTags& lhs, const SeratoTags& rhs) { diff --git a/src/track/taglib/trackmetadata_file.cpp b/src/track/taglib/trackmetadata_file.cpp index b47c11fbbec..d17f071f681 100644 --- a/src/track/taglib/trackmetadata_file.cpp +++ b/src/track/taglib/trackmetadata_file.cpp @@ -51,7 +51,7 @@ FileType getFileTypeFromFileName( DEBUG_ASSERT(!fileName.isEmpty()); const QString fileSuffix(fileName.section(QChar('.'), -1).toLower().trimmed()); if (QStringLiteral("mp3") == fileSuffix) { - return FileType::MP3; + return FileType::MPEG; } if ((QStringLiteral("m4a") == fileSuffix) || (QStringLiteral("m4v") == fileSuffix)) { return FileType::MP4; @@ -60,16 +60,16 @@ FileType getFileTypeFromFileName( return FileType::FLAC; } if (QStringLiteral("ogg") == fileSuffix) { - return FileType::OGG; + return FileType::OggVorbis; } if (QStringLiteral("opus") == fileSuffix) { - return FileType::OPUS; + return FileType::Opus; } if (QStringLiteral("wav") == fileSuffix) { return FileType::WAV; } if (QStringLiteral("wv") == fileSuffix) { - return FileType::WV; + return FileType::WavPack; } if (fileSuffix.startsWith(QStringLiteral("aif"))) { return FileType::AIFF; diff --git a/src/track/taglib/trackmetadata_file.h b/src/track/taglib/trackmetadata_file.h index 0782db221c1..2875aca672b 100644 --- a/src/track/taglib/trackmetadata_file.h +++ b/src/track/taglib/trackmetadata_file.h @@ -16,16 +16,29 @@ class TrackMetadata; namespace taglib { +// This enum is aligned with TagLib_File_Type form the TagLib C binding enum class FileType { - Unknown, - AIFF, + Unknown = -1, + MPEG = 0, + OggVorbis, FLAC, - MP3, + MPC, + OggFlac, + WavPack, + Speex, + TrueAudio, MP4, - OGG, - OPUS, + ASF, + AIFF, WAV, - WV + APE, + IT, + Mod, + S3M, + XM, + Opus, + DSF, + DSDIFF }; QDebug operator<<(QDebug debug, FileType fileType); diff --git a/src/track/taglib/trackmetadata_id3v2.cpp b/src/track/taglib/trackmetadata_id3v2.cpp index a84c76e619f..aa85d2f0d70 100644 --- a/src/track/taglib/trackmetadata_id3v2.cpp +++ b/src/track/taglib/trackmetadata_id3v2.cpp @@ -1061,21 +1061,21 @@ void importTrackMetadataFromTag( tag, kFrameDescriptionSeratoBeatGrid); if (!seratoBeatGrid.isEmpty()) { - parseSeratoBeatGrid(pTrackMetadata, seratoBeatGrid, FileType::MP3); + parseSeratoBeatGrid(pTrackMetadata, seratoBeatGrid, FileType::MPEG); } const QByteArray seratoMarkers = readFirstGeneralEncapsulatedObjectFrame( tag, kFrameDescriptionSeratoMarkers); if (!seratoMarkers.isEmpty()) { - parseSeratoMarkers(pTrackMetadata, seratoMarkers, FileType::MP3); + parseSeratoMarkers(pTrackMetadata, seratoMarkers, FileType::MPEG); } const QByteArray seratoMarkers2 = readFirstGeneralEncapsulatedObjectFrame( tag, kFrameDescriptionSeratoMarkers2); if (!seratoMarkers2.isEmpty()) { - parseSeratoMarkers2(pTrackMetadata, seratoMarkers2, FileType::MP3); + parseSeratoMarkers2(pTrackMetadata, seratoMarkers2, FileType::MPEG); } } @@ -1346,15 +1346,15 @@ bool exportTrackMetadataIntoTag(TagLib::ID3v2::Tag* pTag, writeGeneralEncapsulatedObjectFrame( pTag, kFrameDescriptionSeratoBeatGrid, - trackMetadata.getTrackInfo().getSeratoTags().dumpBeatGrid(FileType::MP3)); + trackMetadata.getTrackInfo().getSeratoTags().dumpBeatGrid(FileType::MPEG)); writeGeneralEncapsulatedObjectFrame( pTag, kFrameDescriptionSeratoMarkers, - trackMetadata.getTrackInfo().getSeratoTags().dumpMarkers(FileType::MP3)); + trackMetadata.getTrackInfo().getSeratoTags().dumpMarkers(FileType::MPEG)); writeGeneralEncapsulatedObjectFrame( pTag, kFrameDescriptionSeratoMarkers2, - trackMetadata.getTrackInfo().getSeratoTags().dumpMarkers2(FileType::MP3)); + trackMetadata.getTrackInfo().getSeratoTags().dumpMarkers2(FileType::MPEG)); } return true; From 4add6c91faab7803b3335590c81698ac2c51648e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 7 May 2024 00:11:13 +0200 Subject: [PATCH 09/49] Mandatory require a fileType when creating MetadataSourceTagLib. No longer guess the fileType from the file extension. --- src/sources/metadatasourcetaglib.h | 9 +-- src/sources/soundsource.cpp | 2 +- src/test/taglibtest.cpp | 4 +- src/track/taglib/trackmetadata_file.cpp | 92 +++++++++++++++++-------- src/track/taglib/trackmetadata_file.h | 4 +- 5 files changed, 70 insertions(+), 41 deletions(-) diff --git a/src/sources/metadatasourcetaglib.h b/src/sources/metadatasourcetaglib.h index 7853b2b064f..12b680fc0cf 100644 --- a/src/sources/metadatasourcetaglib.h +++ b/src/sources/metadatasourcetaglib.h @@ -7,16 +7,11 @@ namespace mixxx { // Universal default implementation of IMetadataSource using TagLib. class MetadataSourceTagLib : public MetadataSource { public: - explicit MetadataSourceTagLib( - const QString& fileName) - : m_fileName(fileName), - m_fileType(taglib::getFileTypeFromFileName(fileName)) { - } MetadataSourceTagLib( const QString& fileName, - taglib::FileType fileType) + const QString& fileType) : m_fileName(fileName), - m_fileType(fileType) { + m_fileType(taglib::stringToEnumFileType(fileType)) { } std::pair importTrackMetadataAndCoverImage( diff --git a/src/sources/soundsource.cpp b/src/sources/soundsource.cpp index 320efdaf12f..772c21e2f8e 100644 --- a/src/sources/soundsource.cpp +++ b/src/sources/soundsource.cpp @@ -83,7 +83,7 @@ QString SoundSource::getTypeFromFile(const QFileInfo& fileInfo) { SoundSource::SoundSource(const QUrl& url, const QString& type) : AudioSource(validateLocalFileUrl(url)), - MetadataSourceTagLib(getLocalFileName()), + MetadataSourceTagLib(getLocalFileName(), type), m_type(type) { } diff --git a/src/test/taglibtest.cpp b/src/test/taglibtest.cpp index 419a20b1084..2db314a964e 100644 --- a/src/test/taglibtest.cpp +++ b/src/test/taglibtest.cpp @@ -36,7 +36,7 @@ TEST_F(TagLibTest, WriteID3v2Tag) { trackMetadata.refTrackInfo().setTitle(QStringLiteral("title")); const auto exported = mixxx::MetadataSourceTagLib( - tmpFileName, mixxx::taglib::FileType::MPEG) + tmpFileName, "mp3") .exportTrackMetadata(trackMetadata); ASSERT_EQ(mixxx::MetadataSource::ExportResult::Succeeded, exported.first); ASSERT_FALSE(exported.second.isNull()); @@ -56,7 +56,7 @@ TEST_F(TagLibTest, WriteID3v2Tag) { trackMetadata.refTrackInfo().setTitle(QStringLiteral("title2")); const auto exported2 = mixxx::MetadataSourceTagLib( - tmpFileName, mixxx::taglib::FileType::MPEG) + tmpFileName, "mp3") .exportTrackMetadata(trackMetadata); ASSERT_EQ(mixxx::MetadataSource::ExportResult::Succeeded, exported.first); ASSERT_FALSE(exported.second.isNull()); diff --git a/src/track/taglib/trackmetadata_file.cpp b/src/track/taglib/trackmetadata_file.cpp index d17f071f681..73d4a859eab 100644 --- a/src/track/taglib/trackmetadata_file.cpp +++ b/src/track/taglib/trackmetadata_file.cpp @@ -45,36 +45,70 @@ void readAudioProperties( namespace taglib { +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) +using namespace Qt::Literals::StringLiterals; +#else +constexpr inline QLatin1String operator""_L1(const char* str, size_t size) noexcept { + return QLatin1String{str, static_cast(size)}; +} +#endif + // Deduce the TagLib file type from the file name -FileType getFileTypeFromFileName( - const QString& fileName) { - DEBUG_ASSERT(!fileName.isEmpty()); - const QString fileSuffix(fileName.section(QChar('.'), -1).toLower().trimmed()); - if (QStringLiteral("mp3") == fileSuffix) { - return FileType::MPEG; - } - if ((QStringLiteral("m4a") == fileSuffix) || (QStringLiteral("m4v") == fileSuffix)) { - return FileType::MP4; - } - if (QStringLiteral("flac") == fileSuffix) { - return FileType::FLAC; - } - if (QStringLiteral("ogg") == fileSuffix) { - return FileType::OggVorbis; - } - if (QStringLiteral("opus") == fileSuffix) { - return FileType::Opus; - } - if (QStringLiteral("wav") == fileSuffix) { - return FileType::WAV; - } - if (QStringLiteral("wv") == fileSuffix) { - return FileType::WavPack; - } - if (fileSuffix.startsWith(QStringLiteral("aif"))) { - return FileType::AIFF; - } - return FileType::Unknown; +FileType stringToEnumFileType( + const QString& fileType) { + DEBUG_ASSERT(!fileType.isEmpty()); + + struct TypePair { + QLatin1String strType; + FileType eType; + }; + + // This table is aligned with detectByExtension() in fileref.cpp + constexpr std::array lookupTable = { + TypePair{"mp3"_L1, FileType::MPEG}, + TypePair{"mp2"_L1, FileType::MPEG}, + TypePair{"aac"_L1, FileType::MPEG}, + TypePair{"mpeg"_L1, FileType::MPEG}, + TypePair{"ogg"_L1, FileType::OggVorbis}, + TypePair{"oga"_L1, FileType::OggFlac}, + TypePair{"flac"_L1, FileType::FLAC}, + TypePair{"mpc"_L1, FileType::MPC}, + TypePair{"wv"_L1, FileType::WavPack}, + TypePair{"spx"_L1, FileType::Speex}, + TypePair{"opus"_L1, FileType::Opus}, + TypePair{"tta"_L1, FileType::TrueAudio}, + TypePair{"caf"_L1, FileType::MP4}, + TypePair{"m4a"_L1, FileType::MP4}, + TypePair{"m4r"_L1, FileType::MP4}, + TypePair{"m4b"_L1, FileType::MP4}, + TypePair{"m4p"_L1, FileType::MP4}, + TypePair{"m4v"_L1, FileType::MP4}, + TypePair{"mj2"_L1, FileType::MP4}, + TypePair{"mov"_L1, FileType::MP4}, + TypePair{"mp4"_L1, FileType::MP4}, + TypePair{"3gp"_L1, FileType::MP4}, + TypePair{"3g2"_L1, FileType::MP4}, + TypePair{"wma"_L1, FileType::ASF}, + TypePair{"asf"_L1, FileType::ASF}, + TypePair{"aiff"_L1, FileType::AIFF}, + TypePair{"aifc"_L1, FileType::AIFF}, + TypePair{"wav"_L1, FileType::WAV}, + TypePair{"ape"_L1, FileType::APE}, + TypePair{"it"_L1, FileType::IT}, + TypePair{"mod"_L1, FileType::Mod}, + TypePair{"module"_L1, FileType::Mod}, + TypePair{"nst"_L1, FileType::Mod}, + TypePair{"wow"_L1, FileType::Mod}, + TypePair{"s3m"_L1, FileType::S3M}, + TypePair{"xm"_L1, FileType::XM}, + TypePair{"dsf"_L1, FileType::DSF}, + TypePair{"dff"_L1, FileType::DSDIFF}, + TypePair{"dsdiff"_L1, FileType::DSDIFF}}; + + auto it = std::find_if(lookupTable.cbegin(), + lookupTable.cend(), + [fileType](const auto& pair) { return pair.strType == fileType; }); + return it != lookupTable.end() ? it->eType : FileType::Unknown; } QDebug operator<<(QDebug debug, FileType fileType) { diff --git a/src/track/taglib/trackmetadata_file.h b/src/track/taglib/trackmetadata_file.h index 2875aca672b..5a784cd57f8 100644 --- a/src/track/taglib/trackmetadata_file.h +++ b/src/track/taglib/trackmetadata_file.h @@ -43,8 +43,8 @@ enum class FileType { QDebug operator<<(QDebug debug, FileType fileType); -// Deduce the TagLib file type from the file name -FileType getFileTypeFromFileName(const QString& fileName); +// Deduce enum FileType from the fileType String +FileType stringToEnumFileType(const QString& fileType); #ifdef _WIN32 static_assert(sizeof(wchar_t) == sizeof(QChar), "wchar_t is not the same size than QChar"); From e2c9f040b441b0c73a3a35165d7fa2862c49136a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 7 May 2024 08:42:04 +0200 Subject: [PATCH 10/49] Comment some file types --- src/sources/soundsourceffmpeg.cpp | 20 ++++++++++---------- src/sources/soundsourcesndfile.cpp | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sources/soundsourceffmpeg.cpp b/src/sources/soundsourceffmpeg.cpp index 3c0a9ec881e..77ffbef761a 100644 --- a/src/sources/soundsourceffmpeg.cpp +++ b/src/sources/soundsourceffmpeg.cpp @@ -385,12 +385,12 @@ QStringList SoundSourceProviderFFmpeg::getSupportedFileTypes() const { list.append("mp4"); continue; } else if (!strcmp(pavInputFormat->name, "mov,mp4,m4a,3gp,3g2,mj2")) { - list.append("mov"); + list.append("mov"); // QuickTime File Format video/quicktime list.append("mp4"); list.append("m4a"); - list.append("3gp"); - list.append("3g2"); - list.append("mj2"); + list.append("3gp"); // 3GPP file format audio/3gpp + list.append("3g2"); // 3GPP2 file format audio/3gpp2 + list.append("mj2"); // Motion JPEG 2000 video/mj2 continue; } else if (!strcmp(pavInputFormat->name, "opus") || !strcmp(pavInputFormat->name, "libopus")) { @@ -419,7 +419,7 @@ QStringList SoundSourceProviderFFmpeg::getSupportedFileTypes() const { continue; } else if (!strcmp(pavInputFormat->name, "wma") || !strcmp(pavInputFormat->name, "xwma")) { - list.append("wma"); + list.append("wma"); // Windows Media Audio audio/x-ms-wma continue; */ /////////////////////////////////////////////////////////// @@ -427,22 +427,22 @@ QStringList SoundSourceProviderFFmpeg::getSupportedFileTypes() const { /////////////////////////////////////////////////////////// /* } else if (!strcmp(pavInputFormat->name, "ac3")) { - list.append("ac3"); + list.append("ac3"); // AC-3 Compressed Audio (Dolby Digital), Revision A audio/ac3 continue; } else if (!strcmp(pavInputFormat->name, "caf")) { - list.append("caf"); + list.append("caf"); // Apple Lossless continue; } else if (!strcmp(pavInputFormat->name, "mpc")) { - list.append("mpc"); + list.append("mpc"); // Musepack encoded audio audio/musepack continue; } else if (!strcmp(pavInputFormat->name, "mpeg")) { list.append("mpeg"); continue; } else if (!strcmp(pavInputFormat->name, "tak")) { - list.append("tak"); + list.append("tak"); // Tom's lossless Audio Kompressor audio/x-tak continue; } else if (!strcmp(pavInputFormat->name, "tta")) { - list.append("tta"); + list.append("tta"); // True Audio, version 2 continue; */ } diff --git a/src/sources/soundsourcesndfile.cpp b/src/sources/soundsourcesndfile.cpp index 5917a3a4def..fa120d6dd7c 100644 --- a/src/sources/soundsourcesndfile.cpp +++ b/src/sources/soundsourcesndfile.cpp @@ -16,7 +16,7 @@ const QStringList kSupportedFileTypes = { // ALAC/CAF has been added in version 1.0.26 // NOTE(uklotzde, 2015-05-26): Unfortunately ALAC in M4A containers // is still not supported https://github.com/mixxxdj/mixxx/pull/904#issuecomment-221928362 - QStringLiteral("caf"), + QStringLiteral("caf"), // Core Audio Format / Apple Lossless QStringLiteral("flac"), QStringLiteral("ogg"), QStringLiteral("wav"), From dff64e03c2dd0a72577eb70c0379bf7bac126c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 11 May 2024 14:30:08 +0200 Subject: [PATCH 11/49] use const auto it Co-authored-by: Antoine Colombier <7086688+acolombier@users.noreply.github.com> --- src/track/taglib/trackmetadata_file.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/track/taglib/trackmetadata_file.cpp b/src/track/taglib/trackmetadata_file.cpp index 73d4a859eab..73f3b64ac95 100644 --- a/src/track/taglib/trackmetadata_file.cpp +++ b/src/track/taglib/trackmetadata_file.cpp @@ -105,7 +105,7 @@ FileType stringToEnumFileType( TypePair{"dff"_L1, FileType::DSDIFF}, TypePair{"dsdiff"_L1, FileType::DSDIFF}}; - auto it = std::find_if(lookupTable.cbegin(), + const auto it = std::find_if(lookupTable.cbegin(), lookupTable.cend(), [fileType](const auto& pair) { return pair.strType == fileType; }); return it != lookupTable.end() ? it->eType : FileType::Unknown; From d95fce20c8f919442a491754196464274c5e3057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 24 May 2024 15:57:34 +0200 Subject: [PATCH 12/49] Add Test "SoundSourceProxyTest.taglibStringToEnumFileType" --- src/test/soundproxy_test.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/soundproxy_test.cpp b/src/test/soundproxy_test.cpp index 90f7191b674..22754a39621 100644 --- a/src/test/soundproxy_test.cpp +++ b/src/test/soundproxy_test.cpp @@ -7,6 +7,7 @@ #include "sources/soundsourceproxy.h" #include "test/mixxxtest.h" #include "test/soundsourceproviderregistration.h" +#include "track/taglib/trackmetadata_file.h" #include "track/track.h" #include "track/trackmetadata.h" #include "util/samplebuffer.h" @@ -1086,3 +1087,15 @@ TEST_F(SoundSourceProxyTest, freeModeGarbage) { break; } } + +TEST_F(SoundSourceProxyTest, taglibStringToEnumFileType) { + const QStringList fileTypes = SoundSourceProxy::getSupportedFileTypes(); + for (const auto& fileType : fileTypes) { + qDebug() << fileType; + if (fileType != "okt" && // Oktalyzer + fileType != "stm") { // "Scream Tracker"; + ASSERT_NE(mixxx::taglib::stringToEnumFileType(fileType), + mixxx::taglib::FileType::Unknown); + } + } +} From d1c09b5c5006ef674cb4ab8eaa4fb617200c79d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 9 Jun 2024 17:04:48 +0200 Subject: [PATCH 13/49] add missing static keyword for a local constexpr Co-authored-by: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> --- src/track/taglib/trackmetadata_file.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/track/taglib/trackmetadata_file.cpp b/src/track/taglib/trackmetadata_file.cpp index 73f3b64ac95..6e4a72481a8 100644 --- a/src/track/taglib/trackmetadata_file.cpp +++ b/src/track/taglib/trackmetadata_file.cpp @@ -64,7 +64,7 @@ FileType stringToEnumFileType( }; // This table is aligned with detectByExtension() in fileref.cpp - constexpr std::array lookupTable = { + constexpr static std::array lookupTable = { TypePair{"mp3"_L1, FileType::MPEG}, TypePair{"mp2"_L1, FileType::MPEG}, TypePair{"aac"_L1, FileType::MPEG}, From 7aaa7eb5b51c5aa14c13a8000d9e012cbcc08cce Mon Sep 17 00:00:00 2001 From: Bacadam Date: Tue, 18 Jun 2024 22:29:19 +0200 Subject: [PATCH 14/49] Wrap qDebug call in a test of sDebug --- src/library/autodj/autodjprocessor.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 97e78244241..8e795496d15 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1670,7 +1670,9 @@ void AutoDJProcessor::playerRateChanged(DeckAttributes* pAttributes) { } void AutoDJProcessor::playlistFirstTrackChanged() { - qDebug() << this << "playlistFirstTrackChanged"; + if constexpr (sDebug) { + qDebug() << this << "playlistFirstTrackChanged"; + } if (m_eState != ADJ_DISABLED) { DeckAttributes* pLeftDeck = getLeftDeck(); DeckAttributes* pRightDeck = getRightDeck(); From 9188baddad194d734943f0bce985e70531e2223a Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 19 Jul 2024 03:49:06 +0200 Subject: [PATCH 15/49] CmdLineArgs: Check TERM to determine whether to use colors --- src/util/cmdlineargs.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/util/cmdlineargs.cpp b/src/util/cmdlineargs.cpp index 9636b1028fd..f7b16684f0a 100644 --- a/src/util/cmdlineargs.cpp +++ b/src/util/cmdlineargs.cpp @@ -24,18 +24,24 @@ bool calcUseColorsAuto() { // see https://no-color.org/ if (QProcessEnvironment::systemEnvironment().contains(QLatin1String("NO_COLOR"))) { return false; - } else { + } + #ifndef __WINDOWS__ - if (isatty(fileno(stderr))) { - return true; - } + if (!isatty(fileno(stderr))) { + return false; + } #else - if (_isatty(_fileno(stderr))) { - return true; - } -#endif + if (!_isatty(_fileno(stderr))) { + return false; } - return false; +#endif + + // Check if terminal is known to support ANSI colors + QString term = QProcessEnvironment::systemEnvironment().value("TERM"); + return term == "ansi" || term == "cygwin" || term == "linux" || + term.startsWith("screen") || term.startsWith("xterm") || + term.startsWith("vt100") || term.startsWith("rxvt") || + term.endsWith("color"); } } // namespace From 71aba038220dabc21c1b84c21f84efb7afc8d7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 28 Jul 2024 12:57:57 +0200 Subject: [PATCH 16/49] silence clang_tidy suggesting "const auto* const" for it that does not work with MSVC --- src/track/taglib/trackmetadata_file.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/track/taglib/trackmetadata_file.cpp b/src/track/taglib/trackmetadata_file.cpp index 6e4a72481a8..1e1a86e4deb 100644 --- a/src/track/taglib/trackmetadata_file.cpp +++ b/src/track/taglib/trackmetadata_file.cpp @@ -105,7 +105,9 @@ FileType stringToEnumFileType( TypePair{"dff"_L1, FileType::DSDIFF}, TypePair{"dsdiff"_L1, FileType::DSDIFF}}; - const auto it = std::find_if(lookupTable.cbegin(), + // NOLINTNEXTLINE(readability-qualified-auto) + const auto it = std::find_if( + lookupTable.cbegin(), lookupTable.cend(), [fileType](const auto& pair) { return pair.strType == fileType; }); return it != lookupTable.end() ? it->eType : FileType::Unknown; From efa0224a0445ef97efa611aa22a7cd84abb9b1e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 31 Jul 2024 23:23:58 +0200 Subject: [PATCH 17/49] Remove possible recursive call of rawSiblingValue() --- src/library/basesqltablemodel.h | 1 - src/library/basetracktablemodel.cpp | 16 ++-------------- src/library/basetracktablemodel.h | 13 +++++-------- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/library/basesqltablemodel.h b/src/library/basesqltablemodel.h index 711185600dd..66a19b77a28 100644 --- a/src/library/basesqltablemodel.h +++ b/src/library/basesqltablemodel.h @@ -97,7 +97,6 @@ class BaseSqlTableModel : public BaseTrackTableModel { TrackCollectionManager* const m_pTrackCollectionManager; - protected: QList getTrackRefs(const QModelIndexList& indices) const; QSqlDatabase m_database; diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index 5a6401861c5..cdfcdaf3490 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -462,18 +462,6 @@ QVariant BaseTrackTableModel::data( return roleValue(index, rawValue(index), role); } -QVariant BaseTrackTableModel::rawValue( - const QModelIndex& index) const { - VERIFY_OR_DEBUG_ASSERT(index.isValid()) { - return QVariant(); - } - const auto field = mapColumn(index.column()); - if (field == ColumnCache::COLUMN_LIBRARYTABLE_INVALID) { - return QVariant(); - } - return rawSiblingValue(index, field); -} - QVariant BaseTrackTableModel::rawSiblingValue( const QModelIndex& index, ColumnCache::Column siblingField) const { @@ -483,13 +471,13 @@ QVariant BaseTrackTableModel::rawSiblingValue( VERIFY_OR_DEBUG_ASSERT(siblingField != ColumnCache::COLUMN_LIBRARYTABLE_INVALID) { return QVariant(); } - const auto siblingColumn = fieldIndex(siblingField); + const int siblingColumn = fieldIndex(siblingField); if (siblingColumn < 0) { // Unsupported or unknown column/field // FIXME: This should never happen but it does. But why?? return QVariant(); } - const auto siblingIndex = index.sibling(index.row(), siblingColumn); + const QModelIndex siblingIndex = index.sibling(index.row(), siblingColumn); return rawValue(siblingIndex); } diff --git a/src/library/basetracktablemodel.h b/src/library/basetracktablemodel.h index d8daf44ba22..776067a31fa 100644 --- a/src/library/basetracktablemodel.h +++ b/src/library/basetracktablemodel.h @@ -162,10 +162,6 @@ class BaseTrackTableModel : public QAbstractTableModel, public TrackModel { virtual Qt::ItemFlags readWriteFlags( const QModelIndex& index) const; - /// At least one of the following functions must be overridden, - /// because each default implementation will call the other - /// function!! - /// /// Return the raw data value at the given index. /// /// Expected types by ColumnCache field (pass-through = not validated): @@ -211,10 +207,7 @@ class BaseTrackTableModel : public QAbstractTableModel, public TrackModel { /// COLUMN_LIBRARYTABLE_LAST_PLAYED_AT: QDateTime /// COLUMN_PLAYLISTTABLE_DATETIMEADDED: QDateTime virtual QVariant rawValue( - const QModelIndex& index) const; - virtual QVariant rawSiblingValue( - const QModelIndex& index, - ColumnCache::Column siblingField) const; + const QModelIndex& index) const = 0; QVariant roleValue( const QModelIndex& index, @@ -250,6 +243,10 @@ class BaseTrackTableModel : public QAbstractTableModel, public TrackModel { const QPixmap& pixmap); private: + QVariant rawSiblingValue( + const QModelIndex& index, + ColumnCache::Column siblingField) const; + // Track models may reference tracks by an external id // TODO: TrackId should only be used for tracks from // the internal database. From b476dd9df1a4ee33deeb548cbbc5d5d79351d551 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Wed, 7 Aug 2024 00:14:04 +0200 Subject: [PATCH 18/49] improve Taglib/SoundSource logging: FileType enum as string (char*), omit 'file://' prefix for local files --- src/sources/metadatasourcetaglib.cpp | 16 ++++++++++------ src/sources/soundsourceproxy.cpp | 24 ++++++++++++------------ src/track/taglib/trackmetadata_file.cpp | 1 + src/track/taglib/trackmetadata_file.h | 2 ++ 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/sources/metadatasourcetaglib.cpp b/src/sources/metadatasourcetaglib.cpp index b0750a44bf4..7ea35bd8f93 100644 --- a/src/sources/metadatasourcetaglib.cpp +++ b/src/sources/metadatasourcetaglib.cpp @@ -62,6 +62,10 @@ class AiffFile : public TagLib::RIFF::AIFF::File { } }; +QString fileTypeToString(taglib::FileType fileType) { + return QVariant::fromValue(fileType).toString(); +} + } // anonymous namespace std::pair @@ -91,7 +95,7 @@ MetadataSourceTagLib::importTrackMetadataAndCoverImage( kLogger.warning() << "Nothing to import" << "from file" << m_fileName - << "of type" << m_fileType; + << "of type" << fileTypeToString(m_fileType); return afterImport(ImportResult::Unavailable); } if (kLogger.traceEnabled()) { @@ -101,7 +105,7 @@ MetadataSourceTagLib::importTrackMetadataAndCoverImage( : (pTrackMetadata ? "track metadata" : "cover art")) << "from file" << m_fileName - << "of type" << m_fileType; + << "of type" << fileTypeToString(m_fileType); } // Rationale: If a file contains different types of tags only @@ -285,14 +289,14 @@ MetadataSourceTagLib::importTrackMetadataAndCoverImage( kLogger.warning() << "Cannot import track metadata" << "from file" << m_fileName - << "with unknown or unsupported type" << m_fileType; + << "of unknown or unsupported type" << fileTypeToString(m_fileType); return afterImport(ImportResult::Failed); } kLogger.info() << "No track metadata or cover art found" << "in file" << m_fileName - << "with type" << m_fileType; + << "of type" << fileTypeToString(m_fileType); return afterImport(ImportResult::Unavailable); } @@ -672,8 +676,8 @@ MetadataSourceTagLib::exportTrackMetadata( kLogger.debug() << "Cannot export track metadata" << "into file" << m_fileName - << "with unknown or unsupported type" - << m_fileType; + << "of unknown or unsupported type" + << fileTypeToString(m_fileType); return afterExport(ExportResult::Unsupported); } diff --git a/src/sources/soundsourceproxy.cpp b/src/sources/soundsourceproxy.cpp index 4ec589cd21c..8a7a5742b1e 100644 --- a/src/sources/soundsourceproxy.cpp +++ b/src/sources/soundsourceproxy.cpp @@ -503,7 +503,7 @@ void SoundSourceProxy::findProviderAndInitSoundSource() { if (!getUrl().isEmpty()) { kLogger.warning() << "No SoundSourceProvider for file" - << getUrl().toString(); + << getUrl().toString(QUrl::PreferLocalFile); } } @@ -517,7 +517,7 @@ bool SoundSourceProxy::initSoundSourceWithProvider( kLogger.warning() << "SoundSourceProvider" << pProvider->getDisplayName() << "failed to create a SoundSource for file" - << getUrl().toString(); + << getUrl().toString(QUrl::PreferLocalFile); return false; } m_pProvider = pProvider; @@ -525,7 +525,7 @@ bool SoundSourceProxy::initSoundSourceWithProvider( kLogger.debug() << "SoundSourceProvider" << m_pProvider->getDisplayName() << "created a SoundSource for file" - << getUrl().toString() + << getUrl().toString(QUrl::PreferLocalFile) << "of type" << m_pSoundSource->getType(); } @@ -628,7 +628,7 @@ SoundSourceProxy::UpdateTrackFromSourceResult SoundSourceProxy::updateTrackFromS if (!m_pSoundSource) { kLogger.warning() << "Unable to update track from unsupported file type" - << getUrl().toString(); + << getUrl().toString(QUrl::PreferLocalFile); return UpdateTrackFromSourceResult::NotUpdated; } @@ -644,7 +644,7 @@ SoundSourceProxy::UpdateTrackFromSourceResult SoundSourceProxy::updateTrackFromS << "to" << newType << "for file" - << getUrl().toString(); + << getUrl().toString(QUrl::PreferLocalFile); } // Use the existing track metadata as default values. Otherwise @@ -680,7 +680,7 @@ SoundSourceProxy::UpdateTrackFromSourceResult SoundSourceProxy::updateTrackFromS if (kLogger.debugEnabled()) { kLogger.debug() << "Skip importing of embedded cover art from file" - << getUrl().toString(); + << getUrl().toString(QUrl::PreferLocalFile); } } else { // Request reimport of embedded cover art @@ -706,7 +706,7 @@ SoundSourceProxy::UpdateTrackFromSourceResult SoundSourceProxy::updateTrackFromS << "Failed to import track metadata" << (pCoverImg ? "and embedded cover art" : "") << "from file" - << getUrl().toString(); + << getUrl().toString(QUrl::PreferLocalFile); // make sure that the trackMetadata was not messed up due to the failure mixxx::TrackRecord::SourceSyncStatus sourceSyncStatusNew; trackMetadata = m_pTrack->getMetadata(&sourceSyncStatusNew); @@ -750,7 +750,7 @@ SoundSourceProxy::UpdateTrackFromSourceResult SoundSourceProxy::updateTrackFromS if (kLogger.debugEnabled()) { kLogger.debug() << "Initializing track metadata and embedded cover art from file" - << getUrl().toString(); + << getUrl().toString(QUrl::PreferLocalFile); } } else { if (kLogger.debugEnabled()) { @@ -758,7 +758,7 @@ SoundSourceProxy::UpdateTrackFromSourceResult SoundSourceProxy::updateTrackFromS << "Re-importing track metadata" << (pCoverImg ? "and embedded cover art" : "") << "from file" - << getUrl().toString(); + << getUrl().toString(QUrl::PreferLocalFile); } } @@ -870,14 +870,14 @@ bool SoundSourceProxy::openSoundSource( } kLogger.warning() << "Failed to read file" - << getUrl().toString() + << getUrl().toString(QUrl::PreferLocalFile) << "with provider" << m_pProvider->getDisplayName(); m_pSoundSource->close(); // cleanup } else { kLogger.warning() << "Failed to open file" - << getUrl().toString() + << getUrl().toString(QUrl::PreferLocalFile) << "with provider" << m_pProvider->getDisplayName() << "using mode" @@ -915,7 +915,7 @@ bool SoundSourceProxy::openSoundSource( // getting here. m_pSoundSource might already be invalid/null! kLogger.warning() << "Giving up to open file" - << getUrl().toString() + << getUrl().toString(QUrl::PreferLocalFile) << "after" << attemptCount << "unsuccessful attempts"; diff --git a/src/track/taglib/trackmetadata_file.cpp b/src/track/taglib/trackmetadata_file.cpp index b47c11fbbec..db8751d5b40 100644 --- a/src/track/taglib/trackmetadata_file.cpp +++ b/src/track/taglib/trackmetadata_file.cpp @@ -11,6 +11,7 @@ #include +#include "moc_trackmetadata_file.cpp" #include "track/taglib/trackmetadata_common.h" #include "util/logger.h" diff --git a/src/track/taglib/trackmetadata_file.h b/src/track/taglib/trackmetadata_file.h index 0782db221c1..c8877eb9ef9 100644 --- a/src/track/taglib/trackmetadata_file.h +++ b/src/track/taglib/trackmetadata_file.h @@ -15,6 +15,7 @@ namespace mixxx { class TrackMetadata; namespace taglib { +Q_NAMESPACE enum class FileType { Unknown, @@ -27,6 +28,7 @@ enum class FileType { WAV, WV }; +Q_ENUM_NS(FileType); QDebug operator<<(QDebug debug, FileType fileType); From 8edd9753b520645e8557476a941f718df31dd5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 29 Nov 2022 00:26:19 +0100 Subject: [PATCH 19/49] Add preferred notation form ID3v2 --- src/track/keyutils.cpp | 30 ++++++++++++++++++++++++++++++ src/track/keyutils.h | 3 ++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/track/keyutils.cpp b/src/track/keyutils.cpp index d58fdfd165a..d37437e1c0c 100644 --- a/src/track/keyutils.cpp +++ b/src/track/keyutils.cpp @@ -64,6 +64,34 @@ const QString s_traditionalKeyNames[] = { QString::fromUtf8("B♭m"), QString::fromUtf8("Bm")}; +// defined here: https://id3.org/id3v2.3.0 +static const QString s_standardID3v2KeyNames[] = { + QString::fromUtf8("o"), + QString::fromUtf8("C"), + QString::fromUtf8("Db"), + QString::fromUtf8("D"), + QString::fromUtf8("Eb"), + QString::fromUtf8("E"), + QString::fromUtf8("F"), + QString::fromUtf8("F#"), + QString::fromUtf8("G"), + QString::fromUtf8("Ab"), + QString::fromUtf8("A"), + QString::fromUtf8("Bb"), + QString::fromUtf8("B"), + QString::fromUtf8("Cm"), + QString::fromUtf8("C#m"), + QString::fromUtf8("Dm"), + QString::fromUtf8("Ebm"), + QString::fromUtf8("Em"), + QString::fromUtf8("Fm"), + QString::fromUtf8("F#m"), + QString::fromUtf8("Gm"), + QString::fromUtf8("G#m"), + QString::fromUtf8("Am"), + QString::fromUtf8("Bbm"), + QString::fromUtf8("Bm")}; + // Maps an OpenKey number to its major and minor key. constexpr ChromaticKey s_openKeyToKeys[][2] = { // 0 is not a valid OpenKey number. @@ -309,6 +337,8 @@ QString KeyUtils::keyToString(ChromaticKey key, return QString::number(number) + (major ? "B" : "A") + " (" + trad + ")"; } else if (notation == KeyNotation::Traditional) { return s_traditionalKeyNames[static_cast(key)]; + } else if (notation == KeyNotation::StandardID3v2) { + return s_standardID3v2KeyNames[static_cast(key)]; } return keyDebugName(key); } diff --git a/src/track/keyutils.h b/src/track/keyutils.h index f5b662cfdfe..907e573d886 100644 --- a/src/track/keyutils.h +++ b/src/track/keyutils.h @@ -21,7 +21,8 @@ class KeyUtils { Traditional = 4, OpenKeyAndTraditional = 5, LancelotAndTraditional = 6, - NumKeyNotations = 7 + StandardID3v2 = 7, + NumKeyNotations = 8 }; enum class ScaleMode { From 83c88740259ef4c478bfac9492ab599c381e0323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 2 Aug 2024 22:28:21 +0200 Subject: [PATCH 20/49] Improve comments of key functions in Keys --- src/track/keys.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/track/keys.h b/src/track/keys.h index 365e534143b..1d9804424fa 100644 --- a/src/track/keys.h +++ b/src/track/keys.h @@ -29,12 +29,11 @@ class Keys final { const QString& getSubVersion() const; void setSubVersion(const QString& subVersion); - //////////////////////////////////////////////////////////////////////////// - // Key calculations - //////////////////////////////////////////////////////////////////////////// - - // Return the average key over the entire track if the key is valid. + // Return the average key over the entire track if analyzed by Mixxx + // or the Key found in the track metadata mixxx::track::io::key::ChromaticKey getGlobalKey() const; + + // Return key text form the track metadata literally (not normalized) QString getGlobalKeyText() const; private: From 80c2982e3165dca95a124c63535d98e098ccd074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 24 Nov 2022 21:34:19 +0100 Subject: [PATCH 21/49] Read and write keyText to database, independent from the key format in preferences for GUI --- src/library/dao/trackdao.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index 4e53cce203e..020cd3e723b 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -621,7 +621,7 @@ void bindTrackLibraryValues( QString keysVersion = keys.getVersion(); QString keysSubVersion = keys.getSubVersion(); mixxx::track::io::key::ChromaticKey key = keys.getGlobalKey(); - QString keyText = KeyUtils::formatGlobalKey(keys); + QString keyText = keys.getGlobalKeyText(); pTrackLibraryQuery->bindValue(":keys", keysBlob); pTrackLibraryQuery->bindValue(":keys_version", keysVersion); pTrackLibraryQuery->bindValue(":keys_sub_version", keysSubVersion); @@ -1251,16 +1251,18 @@ void setTrackKey(const QSqlRecord& record, const int column, Track* pTrack) { QString keysVersion = record.value(column + 1).toString(); QString keysSubVersion = record.value(column + 2).toString(); QByteArray keysBlob = record.value(column + 3).toByteArray(); - Keys keys = KeyFactory::loadKeysFromByteArray( - keysVersion, keysSubVersion, &keysBlob); - if (!keysVersion.isEmpty()) { - pTrack->setKeys(keys); + pTrack->setKeys(KeyFactory::loadKeysFromByteArray( + keysVersion, + keysSubVersion, + &keysBlob)); } else if (!keyText.isEmpty()) { // Typically this happens if we are upgrading from an older (<1.12.0) // version of Mixxx that didn't support Keys. We treat all legacy data // as user-generated because that way it will be treated sensitively. - pTrack->setKeyText(keyText, mixxx::track::io::key::USER); + pTrack->setKeys(KeyFactory::makeBasicKeysKeepText( + keyText, + mixxx::track::io::key::USER)); } } From b15ac971761bc5a8c150036d342eed8d82485a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 2 Aug 2024 22:45:30 +0200 Subject: [PATCH 22/49] Use preferred notation form ID3v2 when writing key changes to files and database --- src/track/keyfactory.cpp | 3 ++- src/track/trackrecord.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/track/keyfactory.cpp b/src/track/keyfactory.cpp index cf8508b16a3..44d8b8ba70b 100644 --- a/src/track/keyfactory.cpp +++ b/src/track/keyfactory.cpp @@ -27,7 +27,8 @@ Keys KeyFactory::makeBasicKeys( mixxx::track::io::key::Source source) { KeyMap key_map; key_map.set_global_key(global_key); - QString global_key_text = KeyUtils::keyToString(global_key, KeyUtils::KeyNotation::Custom); + QString global_key_text = KeyUtils::keyToString( + global_key, KeyUtils::KeyNotation::StandardID3v2); key_map.set_global_key_text(global_key_text.toStdString()); key_map.set_source(source); return Keys(key_map); diff --git a/src/track/trackrecord.cpp b/src/track/trackrecord.cpp index 521abee151d..4b386fe6b38 100644 --- a/src/track/trackrecord.cpp +++ b/src/track/trackrecord.cpp @@ -23,7 +23,7 @@ TrackRecord::TrackRecord(TrackId id) } void TrackRecord::setKeys(const Keys& keys) { - refMetadata().refTrackInfo().setKeyText(KeyUtils::formatGlobalKey(keys)); + refMetadata().refTrackInfo().setKeyText(keys.getGlobalKeyText()); m_keys = std::move(keys); } From 5398469c327fd000cec554a583f6c82878802479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 25 Nov 2022 00:32:23 +0100 Subject: [PATCH 23/49] Remove global key functions form TrackRecord and inline them in Track. This is more efficient and easier to understand. --- src/track/track.cpp | 16 +++++++++++----- src/track/trackrecord.cpp | 15 --------------- src/track/trackrecord.h | 10 ---------- 3 files changed, 11 insertions(+), 30 deletions(-) diff --git a/src/track/track.cpp b/src/track/track.cpp index 4242fdad888..bc2fbcfd5f5 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -6,6 +6,7 @@ #include "library/library_prefs.h" #include "moc_track.cpp" #include "sources/metadatasource.h" +#include "track/keyfactory.h" #include "util/assert.h" #include "util/logger.h" #include "util/time.h" @@ -1434,22 +1435,27 @@ Keys Track::getKeys() const { void Track::setKey(mixxx::track::io::key::ChromaticKey key, mixxx::track::io::key::Source keySource) { - auto locked = lockMutex(&m_qMutex); - if (m_record.updateGlobalKey(key, keySource)) { - afterKeysUpdated(&locked); + if (key == mixxx::track::io::key::INVALID) { + return; } + const Keys keys = KeyFactory::makeBasicKeys(key, keySource); + QMutexLocker lock(&m_qMutex); + m_record.setKeys(keys); + afterKeysUpdated(&lock); } mixxx::track::io::key::ChromaticKey Track::getKey() const { const auto locked = lockMutex(&m_qMutex); - return m_record.getGlobalKey(); + return m_record.getKeys().getGlobalKey(); } +// returns the formatted key for display purpose QString Track::getKeyText() const { const auto locked = lockMutex(&m_qMutex); - return m_record.getGlobalKeyText(); + return m_record.getKeys().getGlobalKeyText(); } +// normalizes the keyText before storing void Track::setKeyText(const QString& keyText, mixxx::track::io::key::Source keySource) { auto locked = lockMutex(&m_qMutex); diff --git a/src/track/trackrecord.cpp b/src/track/trackrecord.cpp index 4b386fe6b38..bbbf957cf27 100644 --- a/src/track/trackrecord.cpp +++ b/src/track/trackrecord.cpp @@ -27,21 +27,6 @@ void TrackRecord::setKeys(const Keys& keys) { m_keys = std::move(keys); } -bool TrackRecord::updateGlobalKey( - track::io::key::ChromaticKey key, - track::io::key::Source keySource) { - if (key == track::io::key::INVALID) { - return false; - } else { - Keys keys = KeyFactory::makeBasicKeys(key, keySource); - if (m_keys.getGlobalKey() != keys.getGlobalKey()) { - setKeys(keys); - return true; - } - } - return false; -} - UpdateResult TrackRecord::updateGlobalKeyNormalizeText( const QString& keyText, track::io::key::Source keySource) { diff --git a/src/track/trackrecord.h b/src/track/trackrecord.h index ee0961b6e3c..aa310601428 100644 --- a/src/track/trackrecord.h +++ b/src/track/trackrecord.h @@ -93,16 +93,6 @@ class TrackRecord final { return m_keys; } - track::io::key::ChromaticKey getGlobalKey() const { - return getKeys().getGlobalKey(); - } - bool updateGlobalKey( - track::io::key::ChromaticKey key, - track::io::key::Source keySource); - - QString getGlobalKeyText() const { - return KeyUtils::formatGlobalKey(getKeys()); - } UpdateResult updateGlobalKeyNormalizeText( const QString& keyText, track::io::key::Source keySource); From c97a81082b8ea7f1fecbfc4dc39195df1f02ba92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 7 Aug 2024 14:35:38 +0200 Subject: [PATCH 24/49] Fix returning the formated value --- src/track/track.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/track/track.cpp b/src/track/track.cpp index bc2fbcfd5f5..cf9eb94bf42 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -1451,8 +1451,7 @@ mixxx::track::io::key::ChromaticKey Track::getKey() const { // returns the formatted key for display purpose QString Track::getKeyText() const { - const auto locked = lockMutex(&m_qMutex); - return m_record.getKeys().getGlobalKeyText(); + return KeyUtils::keyToString(getKey()); } // normalizes the keyText before storing From a687ab922e4b12e240e743a63870d07ea3d2e891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 7 Aug 2024 14:38:54 +0200 Subject: [PATCH 25/49] Keep detected key map in case the imported metadata value corresponds to it --- src/track/track.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/track/track.cpp b/src/track/track.cpp index cf9eb94bf42..54160870914 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -168,14 +168,11 @@ void Track::replaceMetadataFromSource( // Save some new values for later const auto importedBpm = importedMetadata.getTrackInfo().getBpm(); const QString importedKeyText = importedMetadata.getTrackInfo().getKeyText(); - // Parse the imported key before entering the locking scope - const mixxx::track::io::key::ChromaticKey importedKey = - KeyUtils::guessKeyFromText(importedKeyText); // enter locking scope auto locked = lockMutex(&m_qMutex); - // Preserve the both current bpm and key temporarily to avoid + // Preserve current bpm and key temporarily to avoid // overwriting with an inconsistent value. The bpm must always be // set together with the beat grid and the key text must be parsed // and validated. @@ -207,12 +204,14 @@ void Track::replaceMetadataFromSource( modified |= beatsAndBpmModified; auto keysModified = false; - if (importedKey != mixxx::track::io::key::INVALID) { + Keys newKeys = KeyFactory::makeBasicKeysKeepText( + importedKeyText, mixxx::track::io::key::FILE_METADATA); + if (newKeys.getGlobalKey() != mixxx::track::io::key::INVALID && + m_record.getMetadata().getTrackInfo().getKeyText() != importedKeyText) { // Only update the current key with a valid value. Otherwise preserve // the existing value. - keysModified = m_record.updateGlobalKeyNormalizeText(importedKeyText, - mixxx::track::io::key::FILE_METADATA) == - mixxx::UpdateResult::Updated; + setKeys(newKeys); + keysModified = true; } modified |= keysModified; From 01ca1e0d0cd98ff834c1a662669a72022687f562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 7 Aug 2024 14:40:00 +0200 Subject: [PATCH 26/49] Allow to delete the keys by removing the global key value. --- src/track/trackrecord.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/track/trackrecord.cpp b/src/track/trackrecord.cpp index bbbf957cf27..84329b65cbd 100644 --- a/src/track/trackrecord.cpp +++ b/src/track/trackrecord.cpp @@ -30,6 +30,11 @@ void TrackRecord::setKeys(const Keys& keys) { UpdateResult TrackRecord::updateGlobalKeyNormalizeText( const QString& keyText, track::io::key::Source keySource) { + if (keyText.isEmpty()) { + // User tries to delete the key + setKeys(Keys()); + return UpdateResult::Updated; + } // Try to parse the input as a key. mixxx::track::io::key::ChromaticKey newKey = KeyUtils::guessKeyFromText(keyText); From 77f3708ea168673720792bcf55a35af6dc750044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 7 Aug 2024 14:40:29 +0200 Subject: [PATCH 27/49] Comment updateGlobalKeyNormalizeText() --- src/track/trackrecord.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/track/trackrecord.h b/src/track/trackrecord.h index aa310601428..db53b63ab94 100644 --- a/src/track/trackrecord.h +++ b/src/track/trackrecord.h @@ -93,6 +93,8 @@ class TrackRecord final { return m_keys; } + // Key text will be stored as StandardID3v2 + // Invalid Keys are rejected and empty string deletes the key UpdateResult updateGlobalKeyNormalizeText( const QString& keyText, track::io::key::Source keySource); From 2342051283002787b6533f39fb440458ac4aa6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 27 Nov 2022 14:18:34 +0100 Subject: [PATCH 28/49] Adding a test that verifies that the metadata key field is kept if the user does not edit it. --- CMakeLists.txt | 1 + src/sources/soundsourceproxy.h | 1 + src/test/trackmetadataexport_test.cpp | 138 ++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 src/test/trackmetadataexport_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 07166d924f3..f1441425a1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2165,6 +2165,7 @@ add_executable(mixxx-test src/test/trackdao_test.cpp src/test/trackexport_test.cpp src/test/trackmetadata_test.cpp + src/test/trackmetadataexport_test.cpp src/test/tracknumberstest.cpp src/test/trackreftest.cpp src/test/trackupdate_test.cpp diff --git a/src/sources/soundsourceproxy.h b/src/sources/soundsourceproxy.h index 730fc584311..5f05bfeffab 100644 --- a/src/sources/soundsourceproxy.h +++ b/src/sources/soundsourceproxy.h @@ -201,6 +201,7 @@ class SoundSourceProxy { static QHash s_fileTypeByMimeType; friend class TrackCollectionManager; + friend class TrackMetadataExportTest_keepWithespaceKey_Test; static ExportTrackMetadataResult exportTrackMetadataBeforeSaving( Track* pTrack, const SyncTrackMetadataParams& syncParams); diff --git a/src/test/trackmetadataexport_test.cpp b/src/test/trackmetadataexport_test.cpp new file mode 100644 index 00000000000..53253d7bd6c --- /dev/null +++ b/src/test/trackmetadataexport_test.cpp @@ -0,0 +1,138 @@ +#include + +#include + +#include "test/mixxxtest.h" +#include "test/soundsourceproviderregistration.h" +#include "track/globaltrackcache.h" +#include "track/track.h" + +namespace { + +const QString kEmptyFile = QStringLiteral("empty.mp3"); + +void deleteTrack(Track* pTrack) { + // Delete track objects directly in unit tests with + // no main event loop + delete pTrack; +}; + +class GlobalTrackCacheHelper : public GlobalTrackCacheSaver { + public: + void saveEvictedTrack(Track* pTrack) noexcept override { + ASSERT_FALSE(pTrack == nullptr); + } + GlobalTrackCacheHelper() { + GlobalTrackCache::createInstance(this, deleteTrack); + } + ~GlobalTrackCacheHelper() override { + GlobalTrackCache::destroyInstance(); + } +}; + +} // namespace + +class TrackMetadataExportTest : public MixxxTest, SoundSourceProviderRegistration { + public: + TrackMetadataExportTest() + : m_testDataDir(QDir::current().absoluteFilePath( + "src/test/id3-test-data")) { + } + + protected: + const QDir m_testDataDir; + QTemporaryDir m_exportTempDir; + GlobalTrackCacheHelper m_globalTrackCacheHelper; +}; + +TEST_F(TrackMetadataExportTest, keepWithespaceKey) { + const QString kWhiteSpacesKey = QStringLiteral(" A#m "); + const QString kNormalizedDisplayKey = QString::fromUtf8("B♭m"); + const QString kId3Key = QStringLiteral("Bbm"); + + // Generate a file name for exporting metadata + const QString exportTrackPath = m_exportTempDir.filePath(kEmptyFile); + mixxxtest::copyFile(m_testDataDir.absoluteFilePath(kEmptyFile), exportTrackPath); + TrackPointer pTrack = Track::newTemporary(exportTrackPath); + + mixxx::TrackMetadata writeTrackMetadata; + writeTrackMetadata.refTrackInfo().setKeyText(kWhiteSpacesKey); + + // the internal value is still unchanged + EXPECT_EQ(writeTrackMetadata.getTrackInfo().getKeyText().toStdString(), + kWhiteSpacesKey.toStdString()); + + // This saves the metadata object literally, but normalizes + // the global key value as StandardID3v2 + pTrack->replaceMetadataFromSource( + std::move(writeTrackMetadata), + QDateTime::currentDateTimeUtc()); + + // getKeytext returns the normalized version suitable for GUI presentation + EXPECT_EQ(pTrack->getKeyText().toStdString(), kNormalizedDisplayKey.toStdString()); + + // the internal value is still unchanged + EXPECT_EQ(pTrack->getRecord() + .getMetadata() + .getTrackInfo() + .getKeyText() + .toStdString(), + kWhiteSpacesKey.toStdString()); + + pTrack->markForMetadataExport(); + SyncTrackMetadataParams params; + ExportTrackMetadataResult result = + SoundSourceProxy::exportTrackMetadataBeforeSaving( + pTrack.get(), params); + EXPECT_EQ(result, ExportTrackMetadataResult::Succeeded); + + // recreate the Track + mixxx::TrackMetadata readTrackMetadata; + SoundSourceProxy::importTrackMetadataAndCoverImageFromFile( + mixxx::FileAccess(mixxx::FileInfo(exportTrackPath)), + &readTrackMetadata, + nullptr, + false); + + // the internal value is still unchanged + EXPECT_EQ(readTrackMetadata.getTrackInfo().getKeyText().toStdString(), + kWhiteSpacesKey.toStdString()); + + pTrack->replaceMetadataFromSource( + readTrackMetadata, + QDateTime::currentDateTimeUtc()); + + // getKeytext returns the normalized version suitable for GUI presentation + EXPECT_EQ(pTrack->getKeyText().toStdString(), kNormalizedDisplayKey.toStdString()); + + // the internal value is still unchanged + EXPECT_EQ(pTrack->getRecord() + .getMetadata() + .getTrackInfo() + .getKeyText() + .toStdString(), + kWhiteSpacesKey.toStdString()); + + // Reject edits which results to the same key + pTrack->setKeyText(kNormalizedDisplayKey); + EXPECT_EQ(pTrack->getRecord() + .getMetadata() + .getTrackInfo() + .getKeyText() + .toStdString(), + kWhiteSpacesKey.toStdString()); + + // Allow to remove key with an empty string + pTrack->setKeyText(""); + EXPECT_EQ(pTrack->getRecord() + .getMetadata() + .getTrackInfo() + .getKeyText() + .toStdString(), + QString().toStdString()); + + // normalize user edits + pTrack->setKeyText(kWhiteSpacesKey); + // the internal value is now at the preferred ID3v2 format + EXPECT_EQ(pTrack->getKeys().getGlobalKeyText().toStdString(), kId3Key.toStdString()); +} From f79e9318d6ea2e5006a4da2ae77c631b6a6380ef Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 10 Jul 2024 23:45:08 +0200 Subject: [PATCH 29/49] Improve KeyUtils class --- src/track/keyfactory.cpp | 2 +- src/track/keyutils.cpp | 161 ++++++++++++++++++++++++++------------- src/track/keyutils.h | 45 +---------- 3 files changed, 109 insertions(+), 99 deletions(-) diff --git a/src/track/keyfactory.cpp b/src/track/keyfactory.cpp index 44d8b8ba70b..3f8ef07a962 100644 --- a/src/track/keyfactory.cpp +++ b/src/track/keyfactory.cpp @@ -28,7 +28,7 @@ Keys KeyFactory::makeBasicKeys( KeyMap key_map; key_map.set_global_key(global_key); QString global_key_text = KeyUtils::keyToString( - global_key, KeyUtils::KeyNotation::StandardID3v2); + global_key, KeyUtils::KeyNotation::ID3v2); key_map.set_global_key_text(global_key_text.toStdString()); key_map.set_source(source); return Keys(key_map); diff --git a/src/track/keyutils.cpp b/src/track/keyutils.cpp index d37437e1c0c..5d9712241f7 100644 --- a/src/track/keyutils.cpp +++ b/src/track/keyutils.cpp @@ -38,59 +38,66 @@ const QString s_sharpSymbol = QString::fromUtf8("♯"); //static const QString s_flatSymbol = QString::fromUtf8("♭"); const QString s_traditionalKeyNames[] = { - QString::fromUtf8("INVALID"), - QString::fromUtf8("C"), - QString::fromUtf8("D♭"), - QString::fromUtf8("D"), - QString::fromUtf8("E♭"), - QString::fromUtf8("E"), - QString::fromUtf8("F"), - QString::fromUtf8("F♯/G♭"), - QString::fromUtf8("G"), - QString::fromUtf8("A♭"), - QString::fromUtf8("A"), - QString::fromUtf8("B♭"), - QString::fromUtf8("B"), - QString::fromUtf8("Cm"), - QString::fromUtf8("C♯m"), - QString::fromUtf8("Dm"), - QString::fromUtf8("D♯m/E♭m"), - QString::fromUtf8("Em"), - QString::fromUtf8("Fm"), - QString::fromUtf8("F♯m"), - QString::fromUtf8("Gm"), - QString::fromUtf8("G♯m"), - QString::fromUtf8("Am"), - QString::fromUtf8("B♭m"), - QString::fromUtf8("Bm")}; - -// defined here: https://id3.org/id3v2.3.0 -static const QString s_standardID3v2KeyNames[] = { - QString::fromUtf8("o"), - QString::fromUtf8("C"), - QString::fromUtf8("Db"), - QString::fromUtf8("D"), - QString::fromUtf8("Eb"), - QString::fromUtf8("E"), - QString::fromUtf8("F"), - QString::fromUtf8("F#"), - QString::fromUtf8("G"), - QString::fromUtf8("Ab"), - QString::fromUtf8("A"), - QString::fromUtf8("Bb"), - QString::fromUtf8("B"), - QString::fromUtf8("Cm"), - QString::fromUtf8("C#m"), - QString::fromUtf8("Dm"), - QString::fromUtf8("Ebm"), - QString::fromUtf8("Em"), - QString::fromUtf8("Fm"), - QString::fromUtf8("F#m"), - QString::fromUtf8("Gm"), - QString::fromUtf8("G#m"), - QString::fromUtf8("Am"), - QString::fromUtf8("Bbm"), - QString::fromUtf8("Bm")}; + QStringLiteral(u"INVALID"), + QStringLiteral(u"C"), + QStringLiteral(u"D♭"), + QStringLiteral(u"D"), + QStringLiteral(u"E♭"), + QStringLiteral(u"E"), + QStringLiteral(u"F"), + QStringLiteral(u"F♯/G♭"), + QStringLiteral(u"G"), + QStringLiteral(u"A♭"), + QStringLiteral(u"A"), + QStringLiteral(u"B♭"), + QStringLiteral(u"B"), + QStringLiteral(u"Cm"), + QStringLiteral(u"C♯m"), + QStringLiteral(u"Dm"), + QStringLiteral(u"D♯m/E♭m"), + QStringLiteral(u"Em"), + QStringLiteral(u"Fm"), + QStringLiteral(u"F♯m"), + QStringLiteral(u"Gm"), + QStringLiteral(u"G♯m"), + QStringLiteral(u"Am"), + QStringLiteral(u"B♭m"), + QStringLiteral(u"Bm")}; + +// ID3v2.3.0 specification (https://id3.org/id3v2.3.0): +// The 'Initial key' frame contains the musical key in which the sound starts. +// It is represented as a string with a maximum length of three characters. +// The ground keys are represented with "A","B","C","D","E", "F" and "G" and halfkeys +// represented with "b" and "#". Minor is represented as "m". Example "Cbm". +// Off key is represented with an "o" only. +const std::array s_IDv3KeyNames = { + // these are QStringLiterals because they're used as QStrings below, even though they + // only contain ASCII characters + QStringLiteral("o"), + QStringLiteral("C"), + QStringLiteral("Db"), + QStringLiteral("D"), + QStringLiteral("Eb"), + QStringLiteral("E"), + QStringLiteral("F"), + QStringLiteral("F#"), + QStringLiteral("G"), + QStringLiteral("Ab"), + QStringLiteral("A"), + QStringLiteral("Bb"), + QStringLiteral("B"), + QStringLiteral("Cm"), + QStringLiteral("C#m"), + QStringLiteral("Dm"), + QStringLiteral("Ebm"), + QStringLiteral("Em"), + QStringLiteral("Fm"), + QStringLiteral("F#m"), + QStringLiteral("Gm"), + QStringLiteral("G#m"), + QStringLiteral("Am"), + QStringLiteral("Bbm"), + QStringLiteral("Bm")}; // Maps an OpenKey number to its major and minor key. constexpr ChromaticKey s_openKeyToKeys[][2] = { @@ -299,6 +306,50 @@ void KeyUtils::setNotation(const QMap& notation) { } } +// static +int KeyUtils::keyToOpenKeyNumber(mixxx::track::io::key::ChromaticKey key) { + switch (key) { + case mixxx::track::io::key::C_MAJOR: + case mixxx::track::io::key::A_MINOR: + return 1; + case mixxx::track::io::key::G_MAJOR: + case mixxx::track::io::key::E_MINOR: + return 2; + case mixxx::track::io::key::D_MAJOR: + case mixxx::track::io::key::B_MINOR: + return 3; + case mixxx::track::io::key::A_MAJOR: + case mixxx::track::io::key::F_SHARP_MINOR: + return 4; + case mixxx::track::io::key::E_MAJOR: + case mixxx::track::io::key::C_SHARP_MINOR: + return 5; + case mixxx::track::io::key::B_MAJOR: + case mixxx::track::io::key::G_SHARP_MINOR: + return 6; + case mixxx::track::io::key::F_SHARP_MAJOR: + case mixxx::track::io::key::E_FLAT_MINOR: + return 7; + case mixxx::track::io::key::D_FLAT_MAJOR: + case mixxx::track::io::key::B_FLAT_MINOR: + return 8; + case mixxx::track::io::key::A_FLAT_MAJOR: + case mixxx::track::io::key::F_MINOR: + return 9; + case mixxx::track::io::key::E_FLAT_MAJOR: + case mixxx::track::io::key::C_MINOR: + return 10; + case mixxx::track::io::key::B_FLAT_MAJOR: + case mixxx::track::io::key::G_MINOR: + return 11; + case mixxx::track::io::key::F_MAJOR: + case mixxx::track::io::key::D_MINOR: + return 12; + default: + return 0; + } +} + // static QString KeyUtils::keyToString(ChromaticKey key, KeyNotation notation) { @@ -337,8 +388,8 @@ QString KeyUtils::keyToString(ChromaticKey key, return QString::number(number) + (major ? "B" : "A") + " (" + trad + ")"; } else if (notation == KeyNotation::Traditional) { return s_traditionalKeyNames[static_cast(key)]; - } else if (notation == KeyNotation::StandardID3v2) { - return s_standardID3v2KeyNames[static_cast(key)]; + } else if (notation == KeyNotation::ID3v2) { + return s_IDv3KeyNames[static_cast(key)]; } return keyDebugName(key); } diff --git a/src/track/keyutils.h b/src/track/keyutils.h index 907e573d886..df49ef61449 100644 --- a/src/track/keyutils.h +++ b/src/track/keyutils.h @@ -21,7 +21,7 @@ class KeyUtils { Traditional = 4, OpenKeyAndTraditional = 5, LancelotAndTraditional = 6, - StandardID3v2 = 7, + ID3v2 = 7, NumKeyNotations = 8 }; @@ -126,48 +126,7 @@ class KeyUtils { static mixxx::track::io::key::ChromaticKey openKeyNumberToKey(int openKeyNumber, bool major); - static inline int keyToOpenKeyNumber(mixxx::track::io::key::ChromaticKey key) { - switch (key) { - case mixxx::track::io::key::C_MAJOR: - case mixxx::track::io::key::A_MINOR: - return 1; - case mixxx::track::io::key::G_MAJOR: - case mixxx::track::io::key::E_MINOR: - return 2; - case mixxx::track::io::key::D_MAJOR: - case mixxx::track::io::key::B_MINOR: - return 3; - case mixxx::track::io::key::A_MAJOR: - case mixxx::track::io::key::F_SHARP_MINOR: - return 4; - case mixxx::track::io::key::E_MAJOR: - case mixxx::track::io::key::C_SHARP_MINOR: - return 5; - case mixxx::track::io::key::B_MAJOR: - case mixxx::track::io::key::G_SHARP_MINOR: - return 6; - case mixxx::track::io::key::F_SHARP_MAJOR: - case mixxx::track::io::key::E_FLAT_MINOR: - return 7; - case mixxx::track::io::key::D_FLAT_MAJOR: - case mixxx::track::io::key::B_FLAT_MINOR: - return 8; - case mixxx::track::io::key::A_FLAT_MAJOR: - case mixxx::track::io::key::F_MINOR: - return 9; - case mixxx::track::io::key::E_FLAT_MAJOR: - case mixxx::track::io::key::C_MINOR: - return 10; - case mixxx::track::io::key::B_FLAT_MAJOR: - case mixxx::track::io::key::G_MINOR: - return 11; - case mixxx::track::io::key::F_MAJOR: - case mixxx::track::io::key::D_MINOR: - return 12; - default: - return 0; - } - } + static int keyToOpenKeyNumber(mixxx::track::io::key::ChromaticKey key); static int keyToCircleOfFifthsOrder(mixxx::track::io::key::ChromaticKey key, KeyNotation notation); From 86260716b59915a6c59be7d29ade965f0e679834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 12 Aug 2024 23:47:38 +0200 Subject: [PATCH 30/49] Improve TrackMetadataExportTest --- src/test/trackmetadataexport_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/trackmetadataexport_test.cpp b/src/test/trackmetadataexport_test.cpp index 53253d7bd6c..53859079d4a 100644 --- a/src/test/trackmetadataexport_test.cpp +++ b/src/test/trackmetadataexport_test.cpp @@ -32,7 +32,7 @@ class GlobalTrackCacheHelper : public GlobalTrackCacheSaver { } // namespace -class TrackMetadataExportTest : public MixxxTest, SoundSourceProviderRegistration { +class TrackMetadataExportTest : public MixxxTest, private SoundSourceProviderRegistration { public: TrackMetadataExportTest() : m_testDataDir(QDir::current().absoluteFilePath( @@ -48,7 +48,7 @@ class TrackMetadataExportTest : public MixxxTest, SoundSourceProviderRegistratio TEST_F(TrackMetadataExportTest, keepWithespaceKey) { const QString kWhiteSpacesKey = QStringLiteral(" A#m "); const QString kNormalizedDisplayKey = QString::fromUtf8("B♭m"); - const QString kId3Key = QStringLiteral("Bbm"); + constexpr std::string_view kId3Key = "Bbm"; // Generate a file name for exporting metadata const QString exportTrackPath = m_exportTempDir.filePath(kEmptyFile); @@ -134,5 +134,5 @@ TEST_F(TrackMetadataExportTest, keepWithespaceKey) { // normalize user edits pTrack->setKeyText(kWhiteSpacesKey); // the internal value is now at the preferred ID3v2 format - EXPECT_EQ(pTrack->getKeys().getGlobalKeyText().toStdString(), kId3Key.toStdString()); + EXPECT_EQ(pTrack->getKeys().getGlobalKeyText().toStdString(), kId3Key); } From d12b0d1ecd7622c30856f9e978e9b0a1ef61261f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 12 Aug 2024 23:55:17 +0200 Subject: [PATCH 31/49] make newKeys const Track::replaceMetadataFromSource --- src/track/track.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/track/track.cpp b/src/track/track.cpp index 54160870914..b3ffb3cf904 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -204,7 +204,7 @@ void Track::replaceMetadataFromSource( modified |= beatsAndBpmModified; auto keysModified = false; - Keys newKeys = KeyFactory::makeBasicKeysKeepText( + const Keys newKeys = KeyFactory::makeBasicKeysKeepText( importedKeyText, mixxx::track::io::key::FILE_METADATA); if (newKeys.getGlobalKey() != mixxx::track::io::key::INVALID && m_record.getMetadata().getTrackInfo().getKeyText() != importedKeyText) { From ab5413fa0530cc3e9b85f7b39de98f3805ed047e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 13 Aug 2024 16:16:33 +0200 Subject: [PATCH 32/49] Make use of FRIEND_TEST macro To not rely on Google Test internas Co-authored-by: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> --- src/sources/soundsourceproxy.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sources/soundsourceproxy.h b/src/sources/soundsourceproxy.h index 5f05bfeffab..2b7e324aa7d 100644 --- a/src/sources/soundsourceproxy.h +++ b/src/sources/soundsourceproxy.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include "sources/soundsourceproviderregistry.h" @@ -201,7 +203,7 @@ class SoundSourceProxy { static QHash s_fileTypeByMimeType; friend class TrackCollectionManager; - friend class TrackMetadataExportTest_keepWithespaceKey_Test; + FRIEND_TEST(TrackMetadataExportTest, keepWithespaceKey); static ExportTrackMetadataResult exportTrackMetadataBeforeSaving( Track* pTrack, const SyncTrackMetadataParams& syncParams); From e0b9d748f4ac83717f61706b0122970bc5185bfa Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 24 Feb 2022 02:48:28 +0100 Subject: [PATCH 33/49] Add optional jog wheel acceleration to MC7000 mapping The acceleration is disabled by default. --- res/controllers/Denon-MC7000-scripts.js | 39 ++++++++++++++++++------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index 264a3c5c6c3..3e2cfef580e 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -89,13 +89,25 @@ MC7000.scratchParams = { beta: (1.0/10)/32 }; -// Sensitivity factor of the jog wheel (also depends on audio latency) -// 0.5 for half, 2 for double sensitivity - Recommendation: -// set to 0.5 with audio buffer set to 50ms -// set to 1 with audio buffer set to 25ms -// set to 3 with audio buffer set to 5ms -MC7000.jogSensitivity = 1; - +// Jog wheel parameters +MC7000.jogParams = { + // Sensitivity factor of the jog wheel (also depends on audio latency) + // 0.5 for half, 2 for double sensitivity - Recommendation: + // set to 0.5 with audio buffer set to 50ms + // set to 1 with audio buffer set to 25ms + // set to 3 with audio buffer set to 5ms + sensitivity: 1, + // Acceleration settings for the jog wheel in vinyl mode + // (exponent: 0 and coefficient: 1 = no acceleration) + acceleration: { + // Toggles acceleration entirely. + enabled: false, + // Acceleration function exponent + exponent: 0.8, + // Acceleration function scaling factor + coefficient: 1 + } +}; /*///////////////////////////////// // USER VARIABLES END // @@ -705,7 +717,8 @@ MC7000.wheelTurn = function(channel, control, value, status, group) { // A: For a control that centers on 0: const numTicks = (value < 0x64) ? value : (value - 128); - const adjustedSpeed = numTicks * MC7000.jogSensitivity / 10; + const baseSpeed = numTicks * MC7000.jogParams.sensitivity; + const adjustedSpeed = baseSpeed / 10; const deckNumber = script.deckFromGroup(group); const deckIndex = deckNumber - 1; const libraryMaximized = engine.getValue("[Skin]", "show_maximized_library"); @@ -714,8 +727,14 @@ MC7000.wheelTurn = function(channel, control, value, status, group) { } else if (libraryMaximized === 1 && numTicks < 0) { engine.setValue("[Library]", "MoveUp", 1); } else if (engine.isScratching(deckNumber)) { - // Scratch! - engine.scratchTick(deckNumber, numTicks * MC7000.jogSensitivity); + // Scratch! + let scratchSpeed = baseSpeed; + const acceleration = MC7000.jogParams.acceleration; + if (acceleration && acceleration.enabled) { + const accelerationFactor = Math.pow(Math.abs(baseSpeed), acceleration.exponent) * acceleration.coefficient; + scratchSpeed *= accelerationFactor; + } + engine.scratchTick(deckNumber, scratchSpeed); } else { if (MC7000.shift[deckIndex]) { // While Shift Button pressed -> Search through track From 8102940b7089a71150eecc88ef6dc18c5dd2a47c Mon Sep 17 00:00:00 2001 From: fwcd <30873659+fwcd@users.noreply.github.com> Date: Sat, 17 Aug 2024 01:16:45 +0200 Subject: [PATCH 34/49] Add explanatory comment Co-authored-by: JoergAtGithub <64457745+JoergAtGithub@users.noreply.github.com> --- res/controllers/Denon-MC7000-scripts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index 3e2cfef580e..fa7d349329f 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -98,6 +98,7 @@ MC7000.jogParams = { // set to 3 with audio buffer set to 5ms sensitivity: 1, // Acceleration settings for the jog wheel in vinyl mode + // If enabled, the track speed will accelerate faster than the physical jogheel movement. Be aware, that the absolute track position will drift relative to the jogwheel position in this mode! // (exponent: 0 and coefficient: 1 = no acceleration) acceleration: { // Toggles acceleration entirely. From 71c1d7be1096861c09e2c239c82784ebb5c0c8cc Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 17 Aug 2024 03:48:25 +0200 Subject: [PATCH 35/49] MC7000: Add user-configurable jogwheel settings --- res/controllers/Denon-MC7000.midi.xml | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/res/controllers/Denon-MC7000.midi.xml b/res/controllers/Denon-MC7000.midi.xml index be6dd02bd88..32dc109da7c 100644 --- a/res/controllers/Denon-MC7000.midi.xml +++ b/res/controllers/Denon-MC7000.midi.xml @@ -8,6 +8,62 @@ https://github.com/mixxxdj/mixxx/wiki/Denon-MC7000 denon_mc7000 + + + + + + + + + + + + From 747a523288ab186333a48f2e65300f2fdd2f6812 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 17 Aug 2024 03:55:10 +0200 Subject: [PATCH 36/49] MC7000: Wire up user-configurable settings --- res/controllers/Denon-MC7000-scripts.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index fa7d349329f..5ae2aea9179 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -96,17 +96,17 @@ MC7000.jogParams = { // set to 0.5 with audio buffer set to 50ms // set to 1 with audio buffer set to 25ms // set to 3 with audio buffer set to 5ms - sensitivity: 1, + sensitivity: engine.getSetting("jogSensitivity") || 1, // Acceleration settings for the jog wheel in vinyl mode // If enabled, the track speed will accelerate faster than the physical jogheel movement. Be aware, that the absolute track position will drift relative to the jogwheel position in this mode! // (exponent: 0 and coefficient: 1 = no acceleration) acceleration: { // Toggles acceleration entirely. - enabled: false, + enabled: engine.getSetting("jogAccelerationEnabled") || false, // Acceleration function exponent - exponent: 0.8, + exponent: engine.getSetting("jogAccelerationExponent") || 0.8, // Acceleration function scaling factor - coefficient: 1 + coefficient: engine.getSetting("jogAccelerationCoefficient") || 1 } }; From f60d083c14657b9bd2be637c13a0c6411a0c3614 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 17 Aug 2024 03:58:47 +0200 Subject: [PATCH 37/49] MC7000: Update ranges for acceleration parameters --- res/controllers/Denon-MC7000.midi.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/res/controllers/Denon-MC7000.midi.xml b/res/controllers/Denon-MC7000.midi.xml index 32dc109da7c..460ea19dec0 100644 --- a/res/controllers/Denon-MC7000.midi.xml +++ b/res/controllers/Denon-MC7000.midi.xml @@ -43,8 +43,9 @@ variable="jogAccelerationExponent" type="real" min="0" - max="2.0" + max="20.0" default="0.8" + step="0.1" label="Acceleration exponent"> The exponent of the acceleration curve @@ -54,7 +55,8 @@ variable="jogAccelerationCoefficient" type="real" min="0.05" - max="3.0" + max="20.0" + step="0.1" default="1.0" label="Acceleration coefficient"> From b060c68ac0740be0eaf236a36c020b3dd2adfdb7 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 17 Aug 2024 03:59:25 +0200 Subject: [PATCH 38/49] MC7000: Fix small stylistic typo --- res/controllers/Denon-MC7000-scripts.js | 2 +- res/controllers/Denon-MC7000.midi.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index 5ae2aea9179..5c45d0dfdf0 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -98,7 +98,7 @@ MC7000.jogParams = { // set to 3 with audio buffer set to 5ms sensitivity: engine.getSetting("jogSensitivity") || 1, // Acceleration settings for the jog wheel in vinyl mode - // If enabled, the track speed will accelerate faster than the physical jogheel movement. Be aware, that the absolute track position will drift relative to the jogwheel position in this mode! + // If enabled, the track speed will accelerate faster than the physical jogheel movement. Be aware that the absolute track position will drift relative to the jogwheel position in this mode! // (exponent: 0 and coefficient: 1 = no acceleration) acceleration: { // Toggles acceleration entirely. diff --git a/res/controllers/Denon-MC7000.midi.xml b/res/controllers/Denon-MC7000.midi.xml index 460ea19dec0..8839b321b2c 100644 --- a/res/controllers/Denon-MC7000.midi.xml +++ b/res/controllers/Denon-MC7000.midi.xml @@ -35,7 +35,7 @@ default="false" label="Enable jogwheel acceleration"> - If enabled, the track speed will accelerate faster than the physical jogheel movement. Be aware, that the absolute track position will drift relative to the jogwheel position in this mode! + If enabled, the track speed will accelerate faster than the physical jogheel movement. Be aware that the absolute track position will drift relative to the jogwheel position in this mode! (exponent: 0 and coefficient: 1 = no acceleration) From db5e1512dffb4dc69558e46acf069154c0575580 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 17 Aug 2024 04:03:44 +0200 Subject: [PATCH 39/49] MC7000: Update sensitivity range --- res/controllers/Denon-MC7000.midi.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/controllers/Denon-MC7000.midi.xml b/res/controllers/Denon-MC7000.midi.xml index 8839b321b2c..e1465331588 100644 --- a/res/controllers/Denon-MC7000.midi.xml +++ b/res/controllers/Denon-MC7000.midi.xml @@ -14,9 +14,8 @@ From 4d8687f44ac3695c5841ee02b538e03de287d00f Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 18 Aug 2024 23:26:22 +0200 Subject: [PATCH 43/49] CmdLineArgs: Add alacritty to the list of terminals --- src/util/cmdlineargs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/cmdlineargs.cpp b/src/util/cmdlineargs.cpp index f7b16684f0a..0583406210c 100644 --- a/src/util/cmdlineargs.cpp +++ b/src/util/cmdlineargs.cpp @@ -38,7 +38,7 @@ bool calcUseColorsAuto() { // Check if terminal is known to support ANSI colors QString term = QProcessEnvironment::systemEnvironment().value("TERM"); - return term == "ansi" || term == "cygwin" || term == "linux" || + return term == "alacritty" || term == "ansi" || term == "cygwin" || term == "linux" || term.startsWith("screen") || term.startsWith("xterm") || term.startsWith("vt100") || term.startsWith("rxvt") || term.endsWith("color"); From 1b2a90c0f7b39b855948fd8ed3b4ca32055c6cd5 Mon Sep 17 00:00:00 2001 From: Leon Eckardt Date: Sun, 18 Aug 2024 17:49:56 +0200 Subject: [PATCH 44/49] MIDI for light: Implement new Active deck heuristic Detects the active Deck by sorting the Decks by their volumes. fixes #11322, closes #13138 --- res/controllers/Midi_for_light-scripts.js | 192 +++++++++------------- 1 file changed, 74 insertions(+), 118 deletions(-) diff --git a/res/controllers/Midi_for_light-scripts.js b/res/controllers/Midi_for_light-scripts.js index d55429c5266..28f170f7e21 100644 --- a/res/controllers/Midi_for_light-scripts.js +++ b/res/controllers/Midi_for_light-scripts.js @@ -41,6 +41,8 @@ var enable_vu_right_average_max = false; // set to false if you not need VU righ var enable_vu_right_average_fit = true; // set to false if you not need VU right average fit var enable_vu_right_current_meter = false; // set to false if you not need VU right current meter var enable_vu_right_average_meter = false; // set to false if you not need VU right average meter +var deck_ending_time = 15; // set a time (in seconds) in which the playing track is considered to be ending +var deck_ending_priority_factor = 0.9; // decrease the priority of the ending track by this factor /////////////////////////////////////////////////////////////// // GLOBAL FOR SCRIPT, DON'T TOUCH // @@ -65,6 +67,7 @@ if (enable_vu_mono_current === true || enable_vu_mono_average_min === true || en } else { var enable_vu_meter_global = false; // set to false if you not need complete VU-Meter } +var last_mtc_playposition = -1; /////////////////////////////////////////////////////////////// // FUNCTIONS // @@ -73,16 +76,16 @@ if (enable_vu_mono_current === true || enable_vu_mono_average_min === true || en midi_for_light.init = function(id) { // called when the MIDI device is opened & set up midi_for_light.id = id; // store the ID of this device for later use midi_for_light.directory_mode = false; - midi_for_light.deck_current = 0; - midi_for_light.crossfader_block = false; - midi_for_light.crossfader_change_block_timer = undefined; - midi_for_light.volumebeat = false; - midi_for_light.volumeBeatBlockStatus = false; - midi_for_light.volumeBeatBlock_timer = undefined; + midi_for_light.deck_current = -1; + midi_for_light.decks = [ + {id: 0, priority: 0.0, playing: false}, + {id: 1, priority: 0.0, playing: false}, + {id: 2, priority: 0.0, playing: false}, + {id: 3, priority: 0.0, playing: false} + ]; midi_for_light.vu_meter_timer = undefined; - midi_for_light.volumebeat_on_delay_timer = undefined; - engine.connectControl("[Master]", "crossfader", "midi_for_light.crossfaderChange"); + engine.connectControl("[Master]", "crossfader", "midi_for_light.calculateDeckPriority"); if (enable_vu_meter_global === true) midi_for_light.vu_meter_timer = engine.beginTimer(40, midi_for_light.vuMeter); @@ -92,13 +95,13 @@ midi_for_light.init = function(id) { // called when the MIDI device is opened & for (var i = 0; i <= 3; i++) { deck_beat_watchdog_timer[i] = engine.beginTimer(beat_watchdog_time, () => { midi_for_light.deckBeatWatchdog(i); }); - engine.connectControl("[Channel" + (i + 1) + "]", "beat_active", "midi_for_light.deckBeatOutputToMidi"); - engine.connectControl("[Channel" + (i + 1) + "]", "volume", "midi_for_light.deckVolumeChange"); - engine.connectControl("[Channel" + (i + 1) + "]", "play", "midi_for_light.deckButtonPlay"); - if (enable_mtc_timecode === true) engine.connectControl("[Channel" + (i + 1) + "]", "playposition", "midi_for_light.sendMidiMtcFullFrame"); + engine.connectControl(`[Channel${ i + 1 }]`, "beat_active", "midi_for_light.deckBeatOutputToMidi"); + engine.connectControl(`[Channel${ i + 1 }]`, "volume", "midi_for_light.calculateDeckPriority"); + engine.connectControl(`[Channel${ i + 1 }]`, "play", "midi_for_light.deckButtonPlay"); + if (enable_mtc_timecode === true) { engine.connectControl(`[Channel${ i + 1 }]`, "playposition", "midi_for_light.sendMidiMtcFullFrame"); } } - midi_for_light.crossfaderChange(); + midi_for_light.calculateDeckPriority(); }; midi_for_light.shutdown = function(id) { // called when the MIDI device is closed @@ -107,7 +110,7 @@ midi_for_light.shutdown = function(id) { // called when the MIDI device is close engine.stopTimer(deck_beat_watchdog_timer[i]); } } - for (const timer of ["vu_meter_timer", "volumeBeatBlock_timer", "crossfader_change_block_timer", "volumebeat_on_delay_timer"]) { + for (const timer of ["vu_meter_timer"]) { if (midi_for_light[timer]) { engine.stopTimer(midi_for_light[timer]); midi_for_light[timer] = undefined; @@ -115,6 +118,53 @@ midi_for_light.shutdown = function(id) { // called when the MIDI device is close } }; +midi_for_light.calculateDeckPriority = function() { + // Calculate each channels Volume to figure out the most important + const crossfader = engine.getValue("[Master]", "crossfader"); + const crossfader_left = Math.min((1 - crossfader) * 1.33, 1); + const crossfader_right = Math.min((1 + crossfader) * 1.33, 1); + const crossfader_factors = [crossfader_left, 1.0, crossfader_right]; + + for (let i = 0; i < 4; i++) { + const channel = `[Channel${ i + 1 }]`; + midi_for_light.decks[i].playing = engine.getParameter(channel, "play") === 1; + if (! midi_for_light.decks[i].playing) { + midi_for_light.decks[i].priority = 0.0; + continue; + } + + midi_for_light.decks[i].priority = engine.getParameter(channel, "volume") * crossfader_factors[engine.getValue(channel, "orientation")]; + + // Decrease Priority of ending Tracks + const duration = engine.getValue(channel, "duration"); + const playposition = duration * engine.getValue(channel, "playposition"); + if (duration - playposition < deck_ending_time) { + midi_for_light.decks[i].priority *= deck_ending_priority_factor; + } + } + + // Sort Decks by priority + const sorted = midi_for_light.decks.slice(); + sorted.sort(function(a, b) { return b.priority - a.priority; }); + if (sorted[0].priority < 0.25) { + midi_for_light.deck_current = -1; + return; + } + + // Avoid Jumping between Decks + if (midi_for_light.deck_current !== -1) { + if (sorted[0].priority < midi_for_light.decks[midi_for_light.deck_current].priority + 0.05) { + return; + } + } + + // check deck change and send change message + if (midi_for_light.deck_current !== sorted[0].id) { + midi_for_light.deck_current = sorted[0].id; + midi.sendShortMsg(0x8F + midi_channel, 0x30, 0x64 + sorted[0].id); // Note C on with 64 and add deck + } +}; + midi_for_light.deckButtonPlay = function(value, group, control) { // called when click a play button var deck = parseInt(group.substring(8, 9)) - 1; @@ -130,12 +180,7 @@ midi_for_light.deckButtonPlay = function(value, group, control) { // called when deck_beat_watchdog_timer[deck] = undefined; } - if (midi_for_light.volumebeat === true) { - midi_for_light.deckVolumeChange(); - } else { - midi_for_light.crossfaderChange(); - } - + midi_for_light.calculateDeckPriority(); }; midi_for_light.deckBeatWatchdog = function(deck) { // if current deck beat lost without reason, search a new current deck @@ -144,7 +189,7 @@ midi_for_light.deckBeatWatchdog = function(deck) { // if current deck beat lost deck_beat_watchdog_timer[deck] = undefined; } beat_watchdog[deck] = true; - if (midi_for_light.volumebeat === false) midi_for_light.crossfaderChange(); + midi_for_light.calculateDeckPriority(); }; midi_for_light.vuMeter = function() { // read, calculate and send vu-meter values @@ -400,110 +445,21 @@ midi_for_light.vuMeter = function() { // read, calculate and send vu-meter value } }; -midi_for_light.deckVolumeChange = function(value, group, control) { // deck volume changed - if (midi_for_light.volumebeat === false) return; // out if volumebeat is not active - if (midi_for_light.volumeBeatBlockStatus === true) return; // out if volumebeat is blocked - - var deckvolume = new Array(0, 0, 0, 0); - var volumemax = 0; - var deckneu = -1; - - // get volume from the decks and check it for use - for (var z = 0; z <= 3; z++) { - deckvolume[z] = engine.getValue("[Channel" + (z + 1) + "]", "volume"); - print("beat_watchdog " + z + ": " + beat_watchdog[z]); - if (deckvolume[z] > 0 && deckvolume[z] > volumemax && beat_watchdog[z] === false) { - volumemax = deckvolume[z]; - deckneu = z; - } - } - - if (deckneu == -1) return; // out if no new valid deck - - // check deck change and send change message - if (deckneu != midi_for_light.deck_current) { - midi_for_light.deck_current = deckneu; - midi.sendShortMsg(0x8F + midi_channel, 0x30, 0x64 + deckneu); // Note C on with 64 and add deck - midi_for_light.volumeBeatBlockStatus = true; - midi_for_light.volumeBeatBlock_timer = engine.beginTimer(1000, midi_for_light.volumeBeatBlock); - } -}; - -midi_for_light.volumeBeatBlock = function() { // prevent deck change for one second - if (midi_for_light.volumeBeatBlock_timer) { - engine.stopTimer(midi_for_light.volumeBeatBlock_timer); - midi_for_light.volumeBeatBlock_timer = undefined; - } - midi_for_light.volumeBeatBlockStatus = false; - midi.sendShortMsg(0x8F + midi_channel, 0x30, 0x0); // note C on with value 0 - midi.sendShortMsg(0x7F + midi_channel, 0x30, 0x0); // note C off with value 0 -}; - -midi_for_light.volumeBeatOnDelay = function() { // allow deck change with volume after 3 second fader do nothing - if (midi_for_light.volumebeat_on_delay_timer) { - engine.stopTimer(midi_for_light.volumebeat_on_delay_timer); - midi_for_light.volumebeat_on_delay_timer = undefined; - } - midi_for_light.volumebeat = true; -}; - -midi_for_light.crossfaderChange = function() { // crossfader change, check deck change - // if fader prevent, go out - if (midi_for_light.crossfader_block === true) return; - - // check changing to "deck change by volume" method - midi_for_light.volumebeat = false; - if (midi_for_light.volumebeat_on_delay_timer) { - engine.stopTimer(midi_for_light.volumebeat_on_delay_timer); - midi_for_light.volumebeat_on_delay_timer = undefined; - } - if (engine.getValue("[Master]", "crossfader") > -0.25) { // crossfader more than 25% left; - if (engine.getValue("[Master]", "crossfader") < 0.25) { // crossfader more then 25% right; - midi_for_light.volumebeat_on_delay_timer = engine.beginTimer(3000, midi_for_light.volumeBeatOnDelay); - } - } - - // if crossfader in middle position, go out - if (engine.getValue("[Master]", "crossfader") === 0) return; - - // check what deck is current, crossfader exact 0 is defined as left - var deck = 0; - if (engine.getValue("[Master]", "crossfader") > 0) { // crossfader is right, not middle - deck = 1; - if (beat_watchdog[1] === true) deck = 3; - } else { - deck = 0; - if (beat_watchdog[0] === true) deck = 2; - } - - // check if deck has been changed - if (deck != midi_for_light.deck_current) { - midi_for_light.deck_current = deck; - midi.sendShortMsg(0x8F + midi_channel, 0x30, 0x64 + deck); // note C on with value 64 + deck - midi_for_light.crossfader_block = true; - midi_for_light.crossfader_change_block_timer = engine.beginTimer(1000, midi_for_light.crossfaderChangeBlock); - } -}; - -midi_for_light.crossfaderChangeBlock = function() { // prevent deck change for one second - if (midi_for_light.crossfader_change_block_timer) { - engine.stopTimer(midi_for_light.crossfader_change_block_timer); - midi_for_light.crossfader_change_block_timer = undefined; - } - midi_for_light.crossfader_block = false; - midi.sendShortMsg(0x8F + midi_channel, 0x30, 0x0); // note C on with value 0 - midi.sendShortMsg(0x7F + midi_channel, 0x30, 0x0); // note C off with value 0 - midi_for_light.crossfaderChange(); // check deck is current -}; - midi_for_light.sendMidiMtcFullFrame = function(value, group, control) { // sends an MTC full frame var deck = parseInt(group.substring(8, 9)) - 1; - if (deck != midi_for_light.deck_current) return; + if (deck !== midi_for_light.deck_current) { return; } var fps = 2; // 2 = 25 FPS var duration = engine.getValue(group, "track_samples") / engine.getValue(group, "track_samplerate") / 2; var PlayPositionRest = duration * engine.getValue(group, "playposition"); + // Prevent outputting the same Position twice + const current_mtc_playposition = Math.floor(PlayPositionRest * 25); + if (current_mtc_playposition === last_mtc_playposition) { + return; + } + last_mtc_playposition = current_mtc_playposition; + if (PlayPositionRest < 0) PlayPositionRest = 0; // calculate position hour and stripping from PlayPositionRest From ce3dc81d368edf578be8a9026036ad9ed7467b29 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 22 Aug 2024 00:53:45 +0200 Subject: [PATCH 45/49] Denon MC7000: Only invoke StarsUp/Down logic on button down --- res/controllers/Denon-MC7000-scripts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index 264a3c5c6c3..4a6b10800aa 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -936,7 +936,7 @@ MC7000.censor = function(channel, control, value, status, group) { MC7000.StarsDown = function(channel, control, value, status, group) { const deckNumber = script.deckFromGroup(group); const deckIndex = deckNumber - 1; - if (value >= 0x00) { + if (value > 0x00) { if (MC7000.PADMode[deckIndex] === "Pitch") { for (let padIdx = 0; padIdx < 8; padIdx++) { MC7000.halftoneToPadMap[deckIndex][padIdx] = MC7000.halftoneToPadMap[deckIndex][padIdx] - 8; // pitch down @@ -950,7 +950,7 @@ MC7000.StarsDown = function(channel, control, value, status, group) { MC7000.StarsUp = function(channel, control, value, status, group) { const deckNumber = script.deckFromGroup(group); const deckIndex = deckNumber - 1; - if (value >= 0x00) { + if (value > 0x00) { if (MC7000.PADMode[deckIndex] === "Pitch") { for (let padIdx = 0; padIdx < 8; padIdx++) { MC7000.halftoneToPadMap[deckIndex][padIdx] = MC7000.halftoneToPadMap[deckIndex][padIdx] + 8; // pitch up From c670d689f518a270a8bb0477e15a237dd9cfceb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 24 Aug 2024 21:53:45 +0200 Subject: [PATCH 46/49] Update Azure code signing action to 0.4 --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 532d89cb699..99ddf8eaccb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -332,13 +332,13 @@ jobs: env: AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} if: runner.os == 'Windows' && env.AZURE_TENANT_ID - uses: azure/trusted-signing-action@v0.3.20 + uses: azure/trusted-signing-action@v0.4.0 with: azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} endpoint: https://weu.codesigning.azure.net/ - code-signing-account-name: mixxx + trusted-signing-account-name: mixxx certificate-profile-name: mixxx files-folder: build files-folder-filter: exe @@ -387,13 +387,13 @@ jobs: env: AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} if: runner.os == 'Windows' && env.AZURE_TENANT_ID - uses: azure/trusted-signing-action@v0.3.20 + uses: azure/trusted-signing-action@v0.4.0 with: azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} endpoint: https://weu.codesigning.azure.net/ - code-signing-account-name: mixxx + trusted-signing-account-name: mixxx certificate-profile-name: mixxx files-folder: build files-folder-filter: msi From 75afc7cda2a4bce9b19110bdf78d0ad65ecc7c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BLAISOT?= Date: Sun, 25 Aug 2024 23:38:28 +0200 Subject: [PATCH 47/49] Fix Reloop Beatmix 4 eject button --- res/controllers/Reloop-Beatmix-2-4-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Reloop-Beatmix-2-4-scripts.js b/res/controllers/Reloop-Beatmix-2-4-scripts.js index afe82f45307..115f87ade02 100644 --- a/res/controllers/Reloop-Beatmix-2-4-scripts.js +++ b/res/controllers/Reloop-Beatmix-2-4-scripts.js @@ -332,7 +332,7 @@ ReloopBeatmix24.LoadButton = function(channel, control, value, status, group) { if (value === DOWN) { loadButtonLongPressed[group] = false; loadButtonTimers[group] = engine.beginTimer(1000, - () => {RegloopBeatmix24.LoadButtonEject(group); }, true); + () => { ReloopBeatmix24.LoadButtonEject(group); }, true); } else { // UP if (!loadButtonLongPressed[group]) { // Short press engine.stopTimer(loadButtonTimers[group]); From fa4661e79952bea2d7c5fbaa4111c111b6c8a3e4 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 29 Aug 2024 14:01:10 +0200 Subject: [PATCH 48/49] (fix/skins/Qt6) push skin setiings to the top --- res/skins/LateNight/skin_settings.xml | 3 +++ res/skins/Tango/skin_settings.xml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/res/skins/LateNight/skin_settings.xml b/res/skins/LateNight/skin_settings.xml index d3952242963..0b2ceb611ab 100644 --- a/res/skins/LateNight/skin_settings.xml +++ b/res/skins/LateNight/skin_settings.xml @@ -484,6 +484,9 @@ Description: + + 0me,0me + diff --git a/res/skins/Tango/skin_settings.xml b/res/skins/Tango/skin_settings.xml index 97d69096231..55b7366091c 100644 --- a/res/skins/Tango/skin_settings.xml +++ b/res/skins/Tango/skin_settings.xml @@ -786,6 +786,9 @@ Description: + + 0min,0me + From 6d541fc8dd582b1800509414aa75e0288d848b42 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 29 Aug 2024 02:05:33 +0200 Subject: [PATCH 49/49] (fix) WStarRating: remove unneeded signal that may cause rating reset via track menu --- src/library/dlgtrackinfomulti.cpp | 3 --- src/widget/wstarrating.cpp | 1 - src/widget/wtrackmenu.cpp | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/library/dlgtrackinfomulti.cpp b/src/library/dlgtrackinfomulti.cpp index ceaeda499fe..529766d8673 100644 --- a/src/library/dlgtrackinfomulti.cpp +++ b/src/library/dlgtrackinfomulti.cpp @@ -313,11 +313,8 @@ void DlgTrackInfoMulti::updateFromTracks() { } } // Update the star widget - // Block signals to not set the 'modified' flag. - m_pWStarRating->blockSignals(true); m_pWStarRating->slotSetRating(commonRating); m_starRatingModified = false; - m_pWStarRating->blockSignals(false); // Same procedure for the track color mixxx::RgbColor::optional_t commonColor = m_trackRecords.first().getColor(); diff --git a/src/widget/wstarrating.cpp b/src/widget/wstarrating.cpp index 840ff7d16aa..d1d53651b78 100644 --- a/src/widget/wstarrating.cpp +++ b/src/widget/wstarrating.cpp @@ -39,7 +39,6 @@ void WStarRating::slotSetRating(int starCount) { } m_starCount = starCount; updateVisualRating(starCount); - emit ratingChangeRequest(starCount); } void WStarRating::paintEvent(QPaintEvent * /*unused*/) { diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index f9df992a90b..2854c6fa408 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -772,7 +772,7 @@ int WTrackMenu::getCommonTrackRating() const { VERIFY_OR_DEBUG_ASSERT(!isEmpty()) { return 0; } - int commonRating; + int commonRating = 0; if (m_pTrackModel) { const int column = m_pTrackModel->fieldIndex(LIBRARYTABLE_RATING);