summaryrefslogtreecommitdiffstats
path: root/src/track
diff options
context:
space:
mode:
authorJan Holthuis <jan.holthuis@ruhr-uni-bochum.de>2020-03-09 11:14:37 +0100
committerJan Holthuis <jan.holthuis@ruhr-uni-bochum.de>2020-03-09 11:17:55 +0100
commit20ce76f02f5c67190e5dcc2575656a31083658c6 (patch)
treed933204664affc85cb415fce2355c3f90157427b /src/track
parent90b1109d32edad4c055c01406e0cfadfb5939b39 (diff)
parent7a3095d87adb27e7d6a162b1cd0f0f376eb474f7 (diff)
Merge branch 'master' of github.com:mixxxdj/mixxx into hotcue-rgb-colors
Diffstat (limited to 'src/track')
-rw-r--r--src/track/cue.cpp85
-rw-r--r--src/track/cue.h50
-rw-r--r--src/track/cueinfo.cpp19
-rw-r--r--src/track/cueinfo.h25
-rw-r--r--src/track/serato/markers.cpp10
-rw-r--r--src/track/serato/markers.h1
-rw-r--r--src/track/serato/markers2.cpp42
-rw-r--r--src/track/serato/markers2.h40
-rw-r--r--src/track/serato/tags.cpp81
-rw-r--r--src/track/serato/tags.h68
-rw-r--r--src/track/track.cpp45
-rw-r--r--src/track/track.h8
-rw-r--r--src/track/trackinfo.cpp5
-rw-r--r--src/track/trackinfo.h6
-rw-r--r--src/track/trackmetadatataglib.cpp28
-rw-r--r--src/track/tracknumbers.cpp4
-rw-r--r--src/track/tracknumbers.h2
-rw-r--r--src/track/trackrecord.cpp2
18 files changed, 391 insertions, 130 deletions
diff --git a/src/track/cue.cpp b/src/track/cue.cpp
index 8831d68596..a923ab7a94 100644
--- a/src/track/cue.cpp
+++ b/src/track/cue.cpp
@@ -12,7 +12,32 @@
#include "util/color/colorpalette.h"
namespace {
- const QString kDefaultLabel = ""; // empty string, not null
+
+inline std::optional<double> positionSamplesToMillis(
+ double positionSamples,
+ mixxx::AudioSignal::SampleRate sampleRate) {
+ VERIFY_OR_DEBUG_ASSERT(sampleRate.valid()) {
+ return Cue::kNoPosition;
+ }
+ if (positionSamples == Cue::kNoPosition) {
+ return std::nullopt;
+ }
+ // Try to avoid rounding errors
+ return (positionSamples * 1000) / (sampleRate * mixxx::kEngineChannelCount);
+}
+
+inline double positionMillisToSamples(
+ std::optional<double> positionMillis,
+ mixxx::AudioSignal::SampleRate sampleRate) {
+ VERIFY_OR_DEBUG_ASSERT(sampleRate.valid()) {
+ return Cue::kNoPosition;
+ }
+ if (!positionMillis) {
+ return Cue::kNoPosition;
+ }
+ // Try to avoid rounding errors
+ return (*positionMillis * sampleRate * mixxx::kEngineChannelCount) / 1000;
+}
}
//static
@@ -22,20 +47,18 @@ void CuePointer::deleteLater(Cue* pCue) {
}
}
-Cue::Cue(TrackId trackId)
+Cue::Cue()
: m_bDirty(false),
m_iId(-1),
- m_trackId(trackId),
m_type(mixxx::CueType::Invalid),
m_sampleStartPosition(Cue::kNoPosition),
m_sampleEndPosition(Cue::kNoPosition),
- m_iHotCue(-1),
- m_label(kDefaultLabel),
+ m_iHotCue(Cue::kNoHotCue),
m_color(ColorPalette::kDefaultCueColor) {
- DEBUG_ASSERT(!m_label.isNull());
}
-Cue::Cue(int id,
+Cue::Cue(
+ int id,
TrackId trackId,
mixxx::CueType type,
double position,
@@ -51,7 +74,6 @@ Cue::Cue(int id,
m_iHotCue(hotCue),
m_label(label),
m_color(color) {
- DEBUG_ASSERT(!m_label.isNull());
if (length) {
if (position != Cue::kNoPosition) {
m_sampleEndPosition = position + length;
@@ -63,34 +85,35 @@ Cue::Cue(int id,
}
}
-Cue::Cue(TrackId trackId, mixxx::AudioSignal::SampleRate sampleRate, const mixxx::CueInfo& cueInfo)
+Cue::Cue(
+ const mixxx::CueInfo& cueInfo,
+ mixxx::AudioSignal::SampleRate sampleRate)
: m_bDirty(false),
m_iId(-1),
- m_trackId(trackId),
m_type(cueInfo.getType()),
- m_sampleStartPosition(Cue::kNoPosition),
- m_sampleEndPosition(Cue::kNoPosition),
- m_iHotCue(Cue::kNoHotCue),
+ m_sampleStartPosition(
+ positionMillisToSamples(
+ cueInfo.getStartPositionMillis(),
+ sampleRate)),
+ m_sampleEndPosition(
+ positionMillisToSamples(
+ cueInfo.getEndPositionMillis(),
+ sampleRate)),
+ m_iHotCue(cueInfo.getHotCueNumber() ? *cueInfo.getHotCueNumber() : kNoHotCue),
m_label(cueInfo.getLabel()),
m_color(cueInfo.getColor().value_or(ColorPalette::kDefaultCueColor)) {
- DEBUG_ASSERT(!m_label.isNull());
- DEBUG_ASSERT(sampleRate.valid());
-
- const double sampleRateKhz = sampleRate / 1000.0;
- const double millisecsToSamplesFactor = sampleRateKhz * mixxx::kEngineChannelCount;
- DEBUG_ASSERT(millisecsToSamplesFactor > 0);
-
- if (cueInfo.getStartPositionMillis()) {
- m_sampleStartPosition = (*cueInfo.getStartPositionMillis()) * millisecsToSamplesFactor;
- }
-
- if (cueInfo.getEndPositionMillis()) {
- m_sampleEndPosition = (*cueInfo.getEndPositionMillis()) * millisecsToSamplesFactor;
- }
+}
- if (cueInfo.getHotCueNumber()) {
- m_iHotCue = *cueInfo.getHotCueNumber();
- }
+mixxx::CueInfo Cue::getCueInfo(
+ mixxx::AudioSignal::SampleRate sampleRate) const {
+ QMutexLocker lock(&m_mutex);
+ return mixxx::CueInfo(
+ m_type,
+ positionSamplesToMillis(m_sampleStartPosition, sampleRate),
+ positionSamplesToMillis(m_sampleEndPosition, sampleRate),
+ m_iHotCue == kNoHotCue ? std::nullopt : std::make_optional(m_iHotCue),
+ m_label,
+ m_color);
}
int Cue::getId() const {
@@ -184,8 +207,6 @@ QString Cue::getLabel() const {
}
void Cue::setLabel(const QString label) {
- //qDebug() << "setLabel()" << m_label << "-" << label;
- DEBUG_ASSERT(!label.isNull());
QMutexLocker lock(&m_mutex);
m_label = label;
m_bDirty = true;
diff --git a/src/track/cue.h b/src/track/cue.h
index 71e1387e03..14f605c28f 100644
--- a/src/track/cue.h
+++ b/src/track/cue.h
@@ -1,5 +1,4 @@
-#ifndef MIXXX_CUE_H
-#define MIXXX_CUE_H
+#pragma once
#include <QColor>
#include <QMutex>
@@ -22,6 +21,10 @@ class Cue : public QObject {
static constexpr double kNoPosition = -1.0;
static constexpr int kNoHotCue = -1;
+ Cue();
+ Cue(
+ const mixxx::CueInfo& cueInfo,
+ mixxx::AudioSignal::SampleRate sampleRate);
~Cue() override = default;
bool isDirty() const;
@@ -32,32 +35,35 @@ class Cue : public QObject {
void setType(mixxx::CueType type);
double getPosition() const;
- void setStartPosition(double samplePosition);
- void setEndPosition(double samplePosition);
+ void setStartPosition(
+ double samplePosition = kNoPosition);
+ void setEndPosition(
+ double samplePosition = kNoPosition);
double getLength() const;
int getHotCue() const;
- void setHotCue(int hotCue);
+ void setHotCue(
+ int hotCue = kNoHotCue);
QString getLabel() const;
- void setLabel(QString label);
+ void setLabel(
+ QString label = QString());
mixxx::RgbColor getColor() const;
void setColor(mixxx::RgbColor color);
double getEndPosition() const;
+ mixxx::CueInfo getCueInfo(
+ mixxx::AudioSignal::SampleRate sampleRate) const;
+
signals:
void updated();
private:
- explicit Cue(TrackId trackId);
- explicit Cue(
- TrackId trackId,
- mixxx::AudioSignal::SampleRate sampleRate,
- const mixxx::CueInfo& cueInfo);
- Cue(int id,
+ Cue(
+ int id,
TrackId trackId,
mixxx::CueType type,
double position,
@@ -65,7 +71,9 @@ class Cue : public QObject {
int hotCue,
QString label,
mixxx::RgbColor color);
+
void setDirty(bool dirty);
+
void setId(int id);
void setTrackId(TrackId trackId);
@@ -85,11 +93,11 @@ class Cue : public QObject {
friend class CueDAO;
};
-class CuePointer: public std::shared_ptr<Cue> {
+class CuePointer : public std::shared_ptr<Cue> {
public:
CuePointer() = default;
explicit CuePointer(Cue* pCue)
- : std::shared_ptr<Cue>(pCue, deleteLater) {
+ : std::shared_ptr<Cue>(pCue, deleteLater) {
}
private:
@@ -99,9 +107,11 @@ class CuePointer: public std::shared_ptr<Cue> {
class CuePosition {
public:
CuePosition()
- : m_position(0.0) {}
+ : m_position(0.0) {
+ }
CuePosition(double position)
- : m_position(position) {}
+ : m_position(position) {
+ }
double getPosition() const {
return m_position;
@@ -125,14 +135,10 @@ class CuePosition {
bool operator==(const CuePosition& lhs, const CuePosition& rhs);
-inline
-bool operator!=(const CuePosition& lhs, const CuePosition& rhs) {
+inline bool operator!=(const CuePosition& lhs, const CuePosition& rhs) {
return !(lhs == rhs);
}
-inline
-QDebug operator<<(QDebug dbg, const CuePosition& arg) {
+inline QDebug operator<<(QDebug dbg, const CuePosition& arg) {
return dbg << "position =" << arg.getPosition();
}
-
-#endif // MIXXX_CUE_H
diff --git a/src/track/cueinfo.cpp b/src/track/cueinfo.cpp
index 66e092a2b6..c22c65f23e 100644
--- a/src/track/cueinfo.cpp
+++ b/src/track/cueinfo.cpp
@@ -2,10 +2,6 @@
#include "util/assert.h"
-namespace {
-const QString kDefaultLabel = QStringLiteral(""); // empty string, not null
-} // anonymous namespace
-
namespace mixxx {
CueInfo::CueInfo()
@@ -13,9 +9,7 @@ CueInfo::CueInfo()
m_startPositionMillis(std::nullopt),
m_endPositionMillis(std::nullopt),
m_hotCueNumber(std::nullopt),
- m_label(kDefaultLabel),
m_color(std::nullopt) {
- DEBUG_ASSERT(!m_label.isNull());
}
CueInfo::CueInfo(
@@ -31,7 +25,6 @@ CueInfo::CueInfo(
m_hotCueNumber(hotCueNumber),
m_label(label),
m_color(color) {
- DEBUG_ASSERT(!m_label.isNull());
}
CueType CueInfo::getType() const {
@@ -71,7 +64,6 @@ QString CueInfo::getLabel() const {
}
void CueInfo::setLabel(QString label) {
- DEBUG_ASSERT(!label.isNull());
m_label = label;
}
@@ -83,4 +75,15 @@ void CueInfo::setColor(RgbColor::optional_t color) {
m_color = color;
}
+bool operator==(
+ const CueInfo& lhs,
+ const CueInfo& rhs) {
+ return lhs.getType() == rhs.getType() &&
+ lhs.getStartPositionMillis() == rhs.getStartPositionMillis() &&
+ lhs.getEndPositionMillis() == rhs.getEndPositionMillis() &&
+ lhs.getHotCueNumber() == rhs.getHotCueNumber() &&
+ lhs.getLabel() == rhs.getLabel() &&
+ lhs.getColor() == rhs.getColor();
+}
+
} // namespace mixxx
diff --git a/src/track/cueinfo.h b/src/track/cueinfo.h
index 409acd8fd2..5a06688b3d 100644
--- a/src/track/cueinfo.h
+++ b/src/track/cueinfo.h
@@ -34,20 +34,25 @@ class CueInfo {
CueType getType() const;
void setType(CueType type);
- void setStartPositionMillis(std::optional<double> positionMillis);
std::optional<double> getStartPositionMillis() const;
+ void setStartPositionMillis(
+ std::optional<double> positionMillis = std::nullopt);
- void setEndPositionMillis(std::optional<double> positionMillis);
std::optional<double> getEndPositionMillis() const;
+ void setEndPositionMillis(
+ std::optional<double> positionMillis = std::nullopt);
std::optional<int> getHotCueNumber() const;
- void setHotCueNumber(std::optional<int> hotCueNumber);
+ void setHotCueNumber(
+ std::optional<int> hotCueNumber = std::nullopt);
QString getLabel() const;
- void setLabel(QString label);
+ void setLabel(
+ QString label = QString());
mixxx::RgbColor::optional_t getColor() const;
- void setColor(mixxx::RgbColor::optional_t color);
+ void setColor(
+ mixxx::RgbColor::optional_t color = std::nullopt);
private:
CueType m_type;
@@ -58,4 +63,14 @@ class CueInfo {
RgbColor::optional_t m_color;
};
+bool operator==(
+ const CueInfo& lhs,
+ const CueInfo& rhs);
+
+inline bool operator!=(
+ const CueInfo& lhs,
+ const CueInfo& rhs) {
+ return !(lhs == rhs);
+}
+
} // namespace mixxx
diff --git a/src/track/serato/markers.cpp b/src/track/serato/markers.cpp
index d3675b7a4c..6c8ee717d0 100644
--- a/src/track/serato/markers.cpp
+++ b/src/track/serato/markers.cpp
@@ -2,7 +2,7 @@
#include <QtEndian>
-#include "util/color/rgbcolor.h"
+#include "track/serato/tags.h"
namespace {
@@ -10,7 +10,6 @@ const int kNumEntries = 14;
const int kLoopEntryStartIndex = 5;
const int kEntrySize = 22;
const quint16 kVersion = 0x0205;
-constexpr mixxx::RgbColor kDefaultTrackColor = mixxx::RgbColor(0xFF9999);
// These functions conversion between the 4-byte "Serato Markers_" color format
// and RgbColor (3-Byte RGB, transparency disabled).
@@ -252,6 +251,11 @@ bool SeratoMarkers::parse(SeratoMarkers* seratoMarkers, const QByteArray& data)
QByteArray SeratoMarkers::dump() const {
QByteArray data;
+ if (isEmpty()) {
+ // Return empty QByteArray
+ return data;
+ }
+
data.resize(sizeof(quint16) + 2 * sizeof(quint32) + kEntrySize * m_entries.size());
QDataStream stream(&data, QIODevice::WriteOnly);
@@ -262,7 +266,7 @@ QByteArray SeratoMarkers::dump() const {
SeratoMarkersEntryPointer pEntry = m_entries.at(i);
stream.writeRawData(pEntry->dump(), kEntrySize);
}
- stream << seratoColorFromRgb(m_trackColor.value_or(kDefaultTrackColor));
+ stream << seratoColorFromRgb(m_trackColor.value_or(SeratoTags::kDefaultTrackColor));
return data;
}
diff --git a/src/track/serato/markers.h b/src/track/serato/markers.h
index b81f7b5563..48e6700978 100644
--- a/src/track/serato/markers.h
+++ b/src/track/serato/markers.h
@@ -11,7 +11,6 @@
namespace mixxx {
-// Forward declaration
class SeratoMarkersEntry;
typedef std::shared_ptr<SeratoMarkersEntry> SeratoMarkersEntryPointer;
diff --git a/src/track/serato/markers2.cpp b/src/track/serato/markers2.cpp
index e9e93a75ff..fb6728b0b5 100644
--- a/src/track/serato/markers2.cpp
+++ b/src/track/serato/markers2.cpp
@@ -372,6 +372,18 @@ bool SeratoMarkers2::parse(SeratoMarkers2* seratoMarkers2, const QByteArray& out
QByteArray SeratoMarkers2::dump() const {
QByteArray data;
+
+ // To reduce disk fragmentation, Serato pre-allocates at least 470 bytes
+ // for the "Markers2" tag. Unused bytes are filled with null-bytes.
+ // Hence, it's possible to have a valid tag that does not contain actual
+ // marker information. The allocated size is set after successfully parsing
+ // the tag, so if the tag is valid but does not contain entries we
+ // shouldn't delete the tag content.
+ if (isEmpty() && getAllocatedSize() == 0) {
+ // Return empty QByteArray
+ return data;
+ }
+
QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_0);
stream.setByteOrder(QDataStream::BigEndian);
@@ -426,4 +438,34 @@ QByteArray SeratoMarkers2::dump() const {
return outerData.leftJustified(size, '\0');
}
+RgbColor::optional_t SeratoMarkers2::getTrackColor() const {
+ qDebug() << "Reading track color from 'Serato Markers2' tag data...";
+
+ for (auto& pEntry : m_entries) {
+ DEBUG_ASSERT(pEntry);
+ if (pEntry->typeId() != SeratoMarkers2Entry::TypeId::Color) {
+ continue;
+ }
+ const SeratoMarkers2ColorEntry* pColorEntry = static_cast<SeratoMarkers2ColorEntry*>(pEntry.get());
+ return RgbColor::optional(pColorEntry->getColor());
+ }
+
+ return std::nullopt;
+}
+
+bool SeratoMarkers2::isBpmLocked() const {
+ qDebug() << "Reading bpmlock state from 'Serato Markers2' tag data...";
+
+ for (auto& pEntry : m_entries) {
+ DEBUG_ASSERT(pEntry);
+ if (pEntry->typeId() != SeratoMarkers2Entry::TypeId::Bpmlock) {
+ continue;
+ }
+ const SeratoMarkers2BpmlockEntry* pBpmlockEntry = static_cast<SeratoMarkers2BpmlockEntry*>(pEntry.get());
+ return pBpmlockEntry->isLocked();
+ }
+
+ return false;
+}
+
} //namespace mixxx
diff --git a/src/track/serato/markers2.h b/src/track/serato/markers2.h
index 3b8fa99e00..e6800a39b2 100644
--- a/src/track/serato/markers2.h
+++ b/src/track/serato/markers2.h
@@ -9,11 +9,6 @@
#include "util/color/rgbcolor.h"
#include "util/types.h"
-namespace {
-constexpr mixxx::RgbColor kDefaultTrackColor = mixxx::RgbColor(0xFF9999);
-constexpr mixxx::RgbColor kDefaultCueColor = mixxx::RgbColor(0xCC0000);
-} // namespace
-
namespace mixxx {
// Enum values need to appear in the same order as the corresponding entries
@@ -61,6 +56,7 @@ class SeratoMarkers2UnknownEntry : public SeratoMarkers2Entry {
: m_type(std::move(type)),
m_data(std::move(data)) {
}
+ SeratoMarkers2UnknownEntry() = delete;
~SeratoMarkers2UnknownEntry() override = default;
QString type() const override {
@@ -89,10 +85,7 @@ class SeratoMarkers2BpmlockEntry : public SeratoMarkers2Entry {
SeratoMarkers2BpmlockEntry(bool locked)
: m_locked(locked) {
}
-
- SeratoMarkers2BpmlockEntry()
- : m_locked(false) {
- }
+ SeratoMarkers2BpmlockEntry() = delete;
static SeratoMarkers2EntryPointer parse(const QByteArray& data);
@@ -139,10 +132,7 @@ class SeratoMarkers2ColorEntry : public SeratoMarkers2Entry {
SeratoMarkers2ColorEntry(RgbColor color)
: m_color(color) {
}
-
- SeratoMarkers2ColorEntry()
- : m_color(kDefaultTrackColor) {
- }
+ SeratoMarkers2ColorEntry() = delete;
static SeratoMarkers2EntryPointer parse(const QByteArray& data);
@@ -192,13 +182,7 @@ class SeratoMarkers2CueEntry : public SeratoMarkers2Entry {
m_color(color),
m_label(label) {
}
-
- SeratoMarkers2CueEntry()
- : m_index(0),
- m_position(0),
- m_color(kDefaultCueColor),
- m_label(QString("")) {
- }
+ SeratoMarkers2CueEntry() = delete;
static SeratoMarkers2EntryPointer parse(const QByteArray& data);
@@ -282,14 +266,7 @@ class SeratoMarkers2LoopEntry : public SeratoMarkers2Entry {
m_locked(locked),
m_label(label) {
}
-
- SeratoMarkers2LoopEntry()
- : m_index(0),
- m_startposition(0),
- m_endposition(0),
- m_locked(false),
- m_label(QString("")) {
- }
+ SeratoMarkers2LoopEntry() = delete;
static SeratoMarkers2EntryPointer parse(const QByteArray& data);
@@ -386,7 +363,9 @@ inline QDebug operator<<(QDebug dbg, const SeratoMarkers2LoopEntry& arg) {
//
class SeratoMarkers2 final {
public:
- SeratoMarkers2() = default;
+ SeratoMarkers2()
+ : m_allocatedSize(0) {
+ }
explicit SeratoMarkers2(
QList<std::shared_ptr<SeratoMarkers2Entry>> entries)
: m_allocatedSize(0),
@@ -419,6 +398,9 @@ class SeratoMarkers2 final {
m_entries = std::move(entries);
}
+ RgbColor::optional_t getTrackColor() const;
+ bool isBpmLocked() const;
+
private:
int m_allocatedSize;
QList<std::shared_ptr<SeratoMarkers2Entry>> m_entries;
diff --git a/src/track/serato/tags.cpp b/src/track/serato/tags.cpp
new file mode 100644
index 0000000000..b621a5d81e
--- /dev/null
+++ b/src/track/serato/tags.cpp
@@ -0,0 +1,81 @@
+#include "track/serato/tags.h"
+
+namespace mixxx {
+
+RgbColor::optional_t SeratoTags::storedToDisplayedTrackColor(RgbColor color) {
+ // Serato stores Track colors differently from how they are displayed in
+ // the library column. Instead of the color from the library view, the
+ // value from the color picker is stored instead (which is different).
+ // To make sure that the track looks the same in both Mixxx' and Serato's
+ // libraries, we need to convert between the two values.
+ //
+ // See this for details:
+ // https://github.com/Holzhaus/serato-tags/blob/master/docs/colors.md#track-colors
+
+ if (color == 0xFFFFFF) {
+ return RgbColor::nullopt();
+ }
+
+ if (color == 0x999999) {
+ return RgbColor::optional(0x090909);
+ }
+
+ if (color == 0x000000) {
+ return RgbColor::optional(0x333333);
+ }
+
+ RgbColor::code_t colorCode = color;
+ colorCode = (colorCode < 0x666666) ? colorCode + 0x99999A : colorCode - 0x666666;
+ return RgbColor::optional(colorCode);
+}
+
+RgbColor SeratoTags::displayedToStoredTrackColor(RgbColor::optional_t color) {
+ if (!color) {
+ return RgbColor(0xFFFFFF);
+ }
+
+ RgbColor::code_t colorCode = *color;
+
+ if (colorCode == 0x090909) {
+ return RgbColor(0x999999);
+ }
+
+ if (colorCode == 0x333333) {
+ return RgbColor(0x000000);
+ }
+
+ // Special case: 0x999999 and 0x99999a are not representable as Serato
+ // track color We'll just modify them a little, so that the look the
+ // same in Serato.
+ if (colorCode == 0x999999) {
+ return RgbColor(0x999998);
+ }
+
+ if (colorCode == 0x99999a) {
+ return RgbColor(0x99999b);
+ }
+
+ colorCode = (colorCode < 0x99999A) ? colorCode + 0x666666 : colorCode - 0x99999A;
+ return RgbColor(colorCode);
+}
+
+RgbColor::optional_t SeratoTags::getTrackColor() const {
+ RgbColor::optional_t color = m_seratoMarkers.getTrackColor();
+
+ if (!color) {
+ // Markers_ is empty, but we may have a color in Markers2
+ color = m_seratoMarkers2.getTrackColor();
+ }
+
+ if (color) {
+ color = SeratoTags::storedToDisplayedTrackColor(*color);
+ }
+
+ return color;
+}
+
+bool SeratoTags::isBpmLocked() const {
+ return m_seratoMarkers2.isBpmLocked();
+}
+
+} // namespace mixxx
diff --git a/src/track/serato/tags.h b/src/track/serato/tags.h
new file mode 100644
index 0000000000..d2bd460cc0
--- /dev/null
+++ b/src/track/serato/tags.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include "track/serato/markers.h"
+#include "track/serato/markers2.h"
+
+namespace mixxx {
+
+// DTO for storing information from the SeratoMarkers_/2 tags used by the
+// Serato DJ Pro software.
+//
+class SeratoTags final {
+ public:
+ static constexpr RgbColor kDefaultTrackColor = RgbColor(0xFF9999);
+ static constexpr RgbColor kDefaultCueColor = RgbColor(0xCC0000);
+
+ SeratoTags() = default;
+
+ static RgbColor::optional_t storedToDisplayedTrackColor(RgbColor color);
+ static RgbColor displayedToStoredTrackColor(RgbColor::optional_t color);
+
+ bool isEmpty() const {
+ return m_seratoMarkers.isEmpty() && m_seratoMarkers2.isEmpty();
+ }
+
+ bool parseMarkers(const QByteArray& data) {
+ return SeratoMarkers::parse(&m_seratoMarkers, data);
+ }
+
+ bool parseMarkers2(const QByteArray& data) {
+ return SeratoMarkers2::parse(&m_seratoMarkers2, data);
+ }
+
+ QByteArray dumpMarkers() const {
+ return m_seratoMarkers.dump();
+ }
+
+ QByteArray dumpMarkers2() const {
+ return m_seratoMarkers2.dump();
+ }
+
+ RgbColor::optional_t getTrackColor() const;
+ bool isBpmLocked() const;
+
+ private:
+ SeratoMarkers m_seratoMarkers;
+ SeratoMarkers2 m_seratoMarkers2;
+};
+
+inline bool operator==(const SeratoTags& lhs, const SeratoTags& rhs) {
+ // FIXME: Find a more efficient way to do this
+ return (lhs.dumpMarkers() == rhs.dumpMarkers() && lhs.dumpMarkers2() == rhs.dumpMarkers2());
+}
+
+inline bool operator!=(const SeratoTags& lhs, const SeratoTags& rhs) {
+ return !(lhs == rhs);
+}
+
+inline QDebug operator<<(QDebug dbg, const SeratoTags& arg) {
+ Q_UNUSED(arg);
+
+ // TODO
+ return dbg << "SeratoTags";
+}
+
+} // namespace mixxx
+
+Q_DECLARE_TYPEINFO(mixxx::SeratoTags, Q_MOVABLE_TYPE);
+Q_DECLARE_METATYPE(mixxx::SeratoTags)
diff --git a/src/track/track.cpp b/src/track/track.cpp
index ef9fc9d593..27e04c8282 100644
--- a/src/track/track.cpp
+++ b/src/track/track.cpp
@@ -128,7 +128,9 @@ void Track::importMetadata(
const auto newBpm = importedMetadata.getTrackInfo().getBpm();
const auto newKey = importedMetadata.getTrackInfo().getKey();
const auto newReplayGain = importedMetadata.getTrackInfo().getReplayGain();
-
+#ifdef __EXTRA_METADATA__
+ const auto newSeratoTags = importedMetadata.getTrackInfo().getSeratoTags();
+#endif // __EXTRA_METADATA__
{
// enter locking scope
QMutexLocker lock(&m_qMutex);
@@ -151,6 +153,12 @@ void Track::importMetadata(
emit ReplayGainUpdated(newReplayGain);
}
}
+
+#ifdef __EXTRA_METADATA__
+ setColor(newSeratoTags.getTrackColor());
+ setBpmLocked(newSeratoTags.isBpmLocked());
+#endif // __EXTRA_METADATA__
+
// implicitly unlocked when leaving scope
}
@@ -675,7 +683,8 @@ void Track::setCuePoint(CuePosition cue) {
double position = cue.getPosition();
if (position != -1.0) {
if (!pLoadCue) {
- pLoadCue = CuePointer(new Cue(m_record.getId()));
+ pLoadCue = CuePointer(new Cue());
+ pLoadCue->setTrackId(m_record.getId());
pLoadCue->setType(mixxx::CueType::MainCue);
connect(pLoadCue.get(),
&Cue::updated,
@@ -705,7 +714,8 @@ void Track::slotCueUpdated() {
CuePointer Track::createAndAddCue() {
QMutexLocker lock(&m_qMutex);
- CuePointer pCue(new Cue(m_record.getId()));
+ CuePointer pCue(new Cue());
+ pCue->setTrackId(m_record.getId());
connect(pCue.get(), &Cue::updated, this, &Track::slotCueUpdated);
m_cuePoints.push_back(pCue);
markDirtyAndUnlock(&lock);
@@ -781,6 +791,8 @@ void Track::setCuePoints(const QList<CuePointer>& cuePoints) {
// connect new cue points
for (const auto& pCue: m_cuePoints) {
connect(pCue.get(), &Cue::updated, this, &Track::slotCueUpdated);
+ // Enure that the track IDs are correct
+ pCue->setTrackId(m_record.getId());
// update main cue point
if (pCue->getType() == mixxx::CueType::MainCue) {
m_record.setCuePoint(CuePosition(pCue->getPosition()));
@@ -801,7 +813,8 @@ void Track::importCuePoints(const QList<mixxx::CueInfo>& cueInfos) {
QList<CuePointer> cuePoints;
for (const mixxx::CueInfo& cueInfo : cueInfos) {
- CuePointer pCue(new Cue(trackId, sampleRate, cueInfo));
+ CuePointer pCue(new Cue(cueInfo, sampleRate));
+ pCue->setTrackId(trackId);
cuePoints.append(pCue);
}
setCuePoints(cuePoints);
@@ -949,6 +962,30 @@ void Track::setCoverInfo(const CoverInfoRelative& coverInfo) {
}
}
+bool Track::refreshCoverImageHash(
+ const QImage& loadedImage) {
+ QMutexLocker lock(&m_qMutex);
+ auto coverInfo = CoverInfo(
+ m_record.getCoverInfo(),
+ m_fileInfo.location());
+ if (!coverInfo.refreshImageHash(
+ loadedImage,
+ m_pSecurityToken)) {
+ return false;
+ }
+ if (!compareAndSet(
+ &m_record.refCoverInfo(),
+ static_cast<const CoverInfoRelative&>(coverInfo))) {
+ return false;
+ }
+ kLogger.info()
+ << "Refreshed cover image hash"
+ << m_fileInfo.location();
+ markDirtyAndUnlock(&lock);
+ emit coverArtUpdated();
+ return true;
+}
+
CoverInfoRelative Track::getCoverInfo() const {
QMutexLocker lock(&m_qMutex);
return m_record.getCoverInfo();
diff --git a/src/track/track.h b/src/track/track.h
index 266da4d00d..78965cf65d 100644
--- a/src/track/track.h
+++ b/src/track/track.h
@@ -279,13 +279,19 @@ class Track : public QObject {
void setCoverInfo(const CoverInfoRelative& coverInfo);
CoverInfoRelative getCoverInfo() const;
CoverInfo getCoverInfoWithLocation() const;
+ // Verify the cover image hash and update it if necessary.
+ // If the corresponding image has already been loaded it
+ // could be provided as a parameter to avoid reloading
+ // if actually needed.
+ bool refreshCoverImageHash(
+ const QImage& loadedImage = QImage());
quint16 getCoverHash() const;
// Set/get track metadata and cover art (optional) all at once.
void importMetadata(
mixxx::TrackMetadata importedMetadata,
- QDateTime metadataSynchronized);
+ QDateTime metadataSynchronized = QDateTime());
// Merge additional metadata that is not (yet) stored in the database
// and only available from file tags.
void mergeImportedMetadata(
diff --git a/src/track/trackinfo.cpp b/src/track/trackinfo.cpp
index 72ed440a81..4adcdee652 100644
--- a/src/track/trackinfo.cpp
+++ b/src/track/trackinfo.cpp
@@ -75,8 +75,7 @@ bool TrackInfo::compareEq(
#endif // __EXTRA_METADATA__
(getReplayGain() == trackInfo.getReplayGain()) &&
#if defined(__EXTRA_METADATA__)
- (getSeratoMarkers() == trackInfo.getSeratoMarkers()) &&
- (getSeratoMarkers2() == trackInfo.getSeratoMarkers2()) &&
+ (getSeratoTags() == trackInfo.getSeratoTags()) &&
(getSubtitle() == trackInfo.getSubtitle()) &&
#endif // __EXTRA_METADATA__
(getTitle() == trackInfo.getTitle()) &&
@@ -120,7 +119,7 @@ QDebug operator<<(QDebug dbg, const TrackInfo& arg) {
#endif // __EXTRA_METADATA__
arg.dbgReplayGain(dbg);
#if defined(__EXTRA_METADATA__)
- arg.dbgSeratoMarkers2(dbg);
+ arg.dbgSeratoTags(dbg);
arg.dbgSubtitle(dbg);
#endif // __EXTRA_METADATA__
arg.dbgTitle(dbg);
diff --git a/src/track/trackinfo.h b/src/track/trackinfo.h
index 478e87001c..367a4eb53d 100644
--- a/src/track/trackinfo.h
+++ b/src/track/trackinfo.h
@@ -6,8 +6,7 @@
#include "sources/audiosource.h"
#include "track/bpm.h"
#include "track/replaygain.h"
-#include "track/serato/markers.h"
-#include "track/serato/markers2.h"
+#include "track/serato/tags.h"
#include "util/duration.h"
#include "util/macros.h"
@@ -45,8 +44,7 @@ class TrackInfo final {
#endif // __EXTRA_METADATA__
PROPERTY_SET_BYVAL_GET_BYREF(ReplayGain, replayGain, ReplayGain)
#if defined(__EXTRA_METADATA__)
- PROPERTY_SET_BYVAL_GET