diff --git a/src/library/columncache.cpp b/src/library/columncache.cpp index d112c12c7e1a..24930d33507f 100644 --- a/src/library/columncache.cpp +++ b/src/library/columncache.cpp @@ -127,6 +127,9 @@ constexpr ColumnProperties kColumnPropertiesByEnum[] = { DI(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK){&LIBRARYTABLE_BPM_LOCK, nullptr, 0}, + DI(ColumnCache::COLUMN_LIBRARYTABLE_BEATS_VERSION){&LIBRARYTABLE_BEATS_VERSION, + nullptr, + 0}, DI(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW){&LIBRARYTABLE_PREVIEW, QT_TRANSLATE_NOOP("BaseTrackTableModel", "Preview"), kDefaultColumnWidth / 2}, diff --git a/src/library/columncache.h b/src/library/columncache.h index 235a8e7b75e3..8057107e3376 100644 --- a/src/library/columncache.h +++ b/src/library/columncache.h @@ -9,6 +9,8 @@ // Caches the index of frequently used columns and provides a lookup-table of // column name to index. +// When you add columns, remember to also add them to +// constexpr ColumnProperties kColumnPropertiesByEnum class ColumnCache : public QObject { Q_OBJECT public: @@ -44,6 +46,7 @@ class ColumnCache : public QObject { COLUMN_LIBRARYTABLE_KEY, COLUMN_LIBRARYTABLE_KEY_ID, COLUMN_LIBRARYTABLE_BPM_LOCK, + COLUMN_LIBRARYTABLE_BEATS_VERSION, COLUMN_LIBRARYTABLE_PREVIEW, COLUMN_LIBRARYTABLE_COLOR, COLUMN_LIBRARYTABLE_COVERART, diff --git a/src/library/dao/trackschema.h b/src/library/dao/trackschema.h index 0122a1239757..dcafdc42c4e1 100644 --- a/src/library/dao/trackschema.h +++ b/src/library/dao/trackschema.h @@ -42,6 +42,7 @@ const QString LIBRARYTABLE_RATING = QStringLiteral("rating"); const QString LIBRARYTABLE_KEY = QStringLiteral("key"); const QString LIBRARYTABLE_KEY_ID = QStringLiteral("key_id"); const QString LIBRARYTABLE_BPM_LOCK = QStringLiteral("bpm_lock"); +const QString LIBRARYTABLE_BEATS_VERSION = QStringLiteral("beats_version"); const QString LIBRARYTABLE_PREVIEW = QStringLiteral("preview"); const QString LIBRARYTABLE_COLOR = QStringLiteral("color"); const QString LIBRARYTABLE_COVERART = QStringLiteral("coverart"); diff --git a/src/library/librarytablemodel.cpp b/src/library/librarytablemodel.cpp index 4ed18d3c2dc8..4c8746801c0b 100644 --- a/src/library/librarytablemodel.cpp +++ b/src/library/librarytablemodel.cpp @@ -74,6 +74,7 @@ bool LibraryTableModel::isColumnInternal(int column) { column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY_ID) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BEATS_VERSION) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_CHANNELS) || column == fieldIndex(ColumnCache::COLUMN_TRACKLOCATIONSTABLE_FSDELETED) || (PlayerManager::numPreviewDecks() == 0 && diff --git a/src/library/missing_hidden/hiddentablemodel.cpp b/src/library/missing_hidden/hiddentablemodel.cpp index 6155299e5d39..4d277613202d 100644 --- a/src/library/missing_hidden/hiddentablemodel.cpp +++ b/src/library/missing_hidden/hiddentablemodel.cpp @@ -78,6 +78,7 @@ bool HiddenTableModel::isColumnInternal(int column) { return column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ID) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BEATS_VERSION) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_MIXXXDELETED) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY_ID) || column == fieldIndex(ColumnCache::COLUMN_TRACKLOCATIONSTABLE_FSDELETED) || diff --git a/src/library/missing_hidden/missingtablemodel.cpp b/src/library/missing_hidden/missingtablemodel.cpp index 39b9fbd2a56e..8c3f23b318f9 100644 --- a/src/library/missing_hidden/missingtablemodel.cpp +++ b/src/library/missing_hidden/missingtablemodel.cpp @@ -71,6 +71,7 @@ bool MissingTableModel::isColumnInternal(int column) { return column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ID) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BEATS_VERSION) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY_ID) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_MIXXXDELETED) || column == fieldIndex(ColumnCache::COLUMN_TRACKLOCATIONSTABLE_FSDELETED) || diff --git a/src/library/mixxxlibraryfeature.cpp b/src/library/mixxxlibraryfeature.cpp index 838f0fd0480c..27e6972155ff 100644 --- a/src/library/mixxxlibraryfeature.cpp +++ b/src/library/mixxxlibraryfeature.cpp @@ -54,6 +54,7 @@ MixxxLibraryFeature::MixxxLibraryFeature(Library* pLibrary, LIBRARYTABLE_KEY_ID, LIBRARYTABLE_BPM, LIBRARYTABLE_BPM_LOCK, + LIBRARYTABLE_BEATS_VERSION, LIBRARYTABLE_DURATION, LIBRARYTABLE_BITRATE, LIBRARYTABLE_REPLAYGAIN, diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index 55ff34db3c44..9eb4b559f957 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -532,8 +532,13 @@ inline std::pair rangeFromTrailingDecimal(double bpm) { } // namespace -BpmFilterNode::BpmFilterNode(QString& argument, bool fuzzy, bool negate) - : m_matchMode(MatchMode::Invalid), +BpmFilterNode::BpmFilterNode( + QString& argument, + bool fuzzy, + bool negate, + const QSqlDatabase& database) + : m_database(database), + m_matchMode(MatchMode::Invalid), m_operator("="), m_bpm(0.0), m_rangeLower(0.0), @@ -555,6 +560,17 @@ BpmFilterNode::BpmFilterNode(QString& argument, bool fuzzy, bool negate) return; } + if (argument == QStringLiteral("const") || argument == QStringLiteral("constant")) { + VERIFY_OR_DEBUG_ASSERT(database.isValid()) { + qWarning() << "BpmFilterNode constructed with 'const' arg" + "but no valid database provided!"; + m_matchMode = MatchMode::Invalid; + return; + } + m_matchMode = MatchMode::Constant; + return; + } + QRegularExpressionMatch opMatch = kNumericOperatorRegex.match(argument); if (opMatch.hasMatch()) { if (fuzzy) { @@ -697,6 +713,10 @@ bool BpmFilterNode::match(const TrackPointer& pTrack) const { return pTrack->isBpmLocked(); } + if (m_matchMode == MatchMode::Constant) { + return pTrack->getBeats()->hasConstantTempo(); + } + double value = pTrack->getBpm(); switch (m_matchMode) { @@ -740,6 +760,15 @@ QString BpmFilterNode::toSql() const { case MatchMode::Locked: { return QStringLiteral("%1 IS 1").arg(LIBRARYTABLE_BPM_LOCK); } + case MatchMode::Constant: { + FieldEscaper escaper(m_database); + // 'BeatGrid-[version]' means we have constant BPM + // 'BeatMap-[version]' means (likely) variable BPM + const QString beatGridEscaped = escaper.escapeString( + kSqlLikeMatchAll + "BeatGrid" + kSqlLikeMatchAll); + return QStringLiteral("%1 LIKE %2") + .arg(LIBRARYTABLE_BEATS_VERSION, beatGridEscaped); + } case MatchMode::Null: { return QStringLiteral("bpm IS 0"); } diff --git a/src/library/searchquery.h b/src/library/searchquery.h index ec7c9701f953..f359ee9df89d 100644 --- a/src/library/searchquery.h +++ b/src/library/searchquery.h @@ -193,7 +193,11 @@ class BpmFilterNode : public QueryNode { static constexpr double kRelativeRangeDefault = 0.06; static void setBpmRelativeRange(double range); - BpmFilterNode(QString& argument, bool fuzzy, bool negate = false); + BpmFilterNode( + QString& argument, + bool fuzzy, + bool negate = false, + const QSqlDatabase& database = QSqlDatabase()); enum class MatchMode { Invalid, @@ -206,6 +210,7 @@ class BpmFilterNode : public QueryNode { HalveDoubleStrict, // bpm:120.0 Operator, // bpm:<=120 Locked, // bpm:locked + Constant, // bpm:const|constant }; // Allows WSearchRelatedTracksMenu to construct the QAction title @@ -218,6 +223,8 @@ class BpmFilterNode : public QueryNode { private: bool match(const TrackPointer& pTrack) const override; + QSqlDatabase m_database; + MatchMode m_matchMode; QString m_operator; diff --git a/src/library/searchqueryparser.cpp b/src/library/searchqueryparser.cpp index 519b2c4ef830..b7390e00ebbb 100644 --- a/src/library/searchqueryparser.cpp +++ b/src/library/searchqueryparser.cpp @@ -262,7 +262,11 @@ void SearchQueryParser::parseTokens(QStringList tokens, // restore = operator removed by getTextArgument() argument.prepend('='); } - pNode = std::make_unique(argument, fuzzy, negate); + pNode = std::make_unique( + argument, + fuzzy, + negate, + m_pTrackCollection->database()); } } } else { diff --git a/src/library/trackset/tracksettablemodel.cpp b/src/library/trackset/tracksettablemodel.cpp index 1e53c73ca601..405776e6b55e 100644 --- a/src/library/trackset/tracksettablemodel.cpp +++ b/src/library/trackset/tracksettablemodel.cpp @@ -16,6 +16,7 @@ bool TrackSetTableModel::isColumnInternal(int column) { column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_MIXXXDELETED) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BEATS_VERSION) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY_ID) || column == fieldIndex(ColumnCache::COLUMN_TRACKLOCATIONSTABLE_FSDELETED) || (PlayerManager::numPreviewDecks() == 0 &&