Skip to content

Commit

Permalink
Merge pull request #2671 from Holzhaus/serato-markers-position-fix
Browse files Browse the repository at this point in the history
track/serato/markers: Fix parsing of position values
  • Loading branch information
Be-ing authored Apr 17, 2020
2 parents f2d701f + b14e546 commit 6e8fcb2
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 75 deletions.
6 changes: 3 additions & 3 deletions src/test/seratomarkerstest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ TEST_F(SeratoMarkersTest, ParseEntry) {
QByteArray("\x00\x00\x0d\x2a\x58\x7f\x7f\x7f\x7f\x7f\x00\x7f\x7f\x7f\x7f\x7f\x06\x32\x10\x00\x01\x00", 22),
true,
true,
862808,
218456,
false,
0x7f7f7f7f,
mixxx::RgbColor(0xcc8800),
Expand All @@ -87,7 +87,7 @@ TEST_F(SeratoMarkersTest, ParseEntry) {
QByteArray("\x00\x00\x03\x54\x64\x7f\x7f\x7f\x7f\x7f\x00\x7f\x7f\x7f\x7f\x7f\x00\x00\x01\x4c\x01\x00", 22),
true,
true,
218212,
60004,
false,
0x7f7f7f7f,
mixxx::RgbColor(0x0000cc),
Expand All @@ -107,7 +107,7 @@ TEST_F(SeratoMarkersTest, ParseEntry) {
QByteArray("\x00\x00\x00\x07\x77\x7f\x7f\x7f\x7f\x7f\x00\x7f\x7f\x7f\x7f\x7f\x00\x03\x18\x00\x01\x00", 22),
true,
true,
1911,
1015,
false,
0x7f7f7f7f,
mixxx::RgbColor(0x00cc00),
Expand Down
158 changes: 86 additions & 72 deletions src/track/serato/markers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,58 @@ const int kLoopEntryStartIndex = 5;
const int kEntrySize = 22;
const quint16 kVersion = 0x0205;

// These functions conversion between the 4-byte "Serato Markers_" color format
// and RgbColor (3-Byte RGB, transparency disabled).
//
// Serato's custom color format that is used here also represents RGB colors,
// but inserts a single null bit after every 7 payload bits, starting from the
// rightmost bit.
// These functions convert between a custom 4-byte format (that we'll call
// "serato32" for brevity) and 3-byte plaintext (both quint32).
// Serato's custom format inserts a single null bit after every 7 payload
// bits, starting from the rightmost bit.
//
// Here's an example:
//
// | Hex Binary
// ------------- | ----------- --------------------------------
// 3-byte RGB | 00 00 cc 000 0000000 0000001 1001100
// Serato format | 00 00 01 4c 00000000000000000000000101001100
// |
// 3-byte RGB | cc 88 00 110 0110010 0010000 0000000
// Serato format | 06 32 10 00 00000110001100100001000000000000
// | Hex Binary
// ---------------- | ----------- --------------------------------
// 3-byte plaintext | 00 00 cc 000 0000000 0000001 1001100
// serato32 value | 00 00 01 4c 00000000000000000000000101001100
// |
// 3-byte plaintext | cc 88 00 110 0110010 0010000 0000000
// serato32 value | 06 32 10 00 00000110001100100001000000000000
//
// See this for details:
// https://github.com/Holzhaus/serato-tags/blob/master/docs/serato_markers_.md#color-format

mixxx::RgbColor seratoColorToRgb(quint8 w, quint8 x, quint8 y, quint8 z) {
quint8 b = (z & 0x7F) | ((y & 0x01) << 7);
quint8 g = ((y & 0x7F) >> 1) | ((x & 0x03) << 6);
quint8 r = ((x & 0x7F) >> 2) | ((w & 0x07) << 5);
return mixxx::RgbColor((r << 16) | (g << 8) | b);
// https://github.com/Holzhaus/serato-tags/blob/master/docs/serato_markers_.md#custom-serato32-binary-format

/// Decode value from Serato's 32-bit custom format to 24-bit plaintext.
quint32 serato32toUint24(quint8 w, quint8 x, quint8 y, quint8 z) {
quint8 c = (z & 0x7F) | ((y & 0x01) << 7);
quint8 b = ((y & 0x7F) >> 1) | ((x & 0x03) << 6);
quint8 a = ((x & 0x7F) >> 2) | ((w & 0x07) << 5);
return ((static_cast<quint32>(a) << 16) | (static_cast<quint32>(b) << 8) |
static_cast<quint32>(c));
}

mixxx::RgbColor seratoColorToRgb(quint32 color) {
return seratoColorToRgb(
(color >> 24) & 0xFF,
(color >> 16) & 0xFF,
(color >> 8) & 0xFF,
color & 0xFF);
/// Decode value from Serato's 32-bit custom format to 24-bit plaintext.
quint32 serato32toUint24(quint32 value) {
return serato32toUint24((value >> 24) & 0xFF,
(value >> 16) & 0xFF,
(value >> 8) & 0xFF,
value & 0xFF);
}

quint32 seratoColorFromRgb(quint8 r, quint8 g, quint8 b) {
quint8 z = b & 0x7F;
quint8 y = ((b >> 7) | (g << 1)) & 0x7F;
quint8 x = ((g >> 6) | (r << 2)) & 0x7F;
quint8 w = (r >> 5);
return (static_cast<quint32>(w) << 24) |
(static_cast<quint32>(x) << 16) |
(static_cast<quint32>(y) << 8) |
static_cast<quint32>(z);
/// Encode a 24-bit plaintext value into Serato's 32-bit custom format.
quint32 serato32fromUint24(quint8 a, quint8 b, quint8 c) {
quint8 z = c & 0x7F;
quint8 y = ((c >> 7) | (b << 1)) & 0x7F;
quint8 x = ((b >> 6) | (a << 2)) & 0x7F;
quint8 w = (a >> 5);
return (static_cast<quint32>(w) << 24) | (static_cast<quint32>(x) << 16) |
(static_cast<quint32>(y) << 8) | static_cast<quint32>(z);
}

quint32 seratoColorFromRgb(mixxx::RgbColor rgb) {
return seratoColorFromRgb(
(rgb >> 16) & 0xFF,
(rgb >> 8) & 0xFF,
rgb & 0xFF);
}
/// Encode a 24-bit plaintext value into Serato's 32-bit custom format. The 8
/// most significant bits of the quint32 will be ignored.
quint32 serato32fromUint24(quint32 value) {
return serato32fromUint24(
(value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF);
}
} // namespace

namespace mixxx {

Expand All @@ -75,13 +74,16 @@ QByteArray SeratoMarkersEntry::dump() const {
stream.setVersion(QDataStream::Qt_5_0);
stream.setByteOrder(QDataStream::BigEndian);
stream << static_cast<quint8>((m_hasStartPosition ? 0x00 : 0x7F))
<< static_cast<quint32>((m_hasStartPosition ? m_startPosition : 0x7F7F7F7F))
<< static_cast<quint32>(
(m_hasStartPosition ? serato32fromUint24(m_startPosition)
: 0x7F7F7F7F))
<< static_cast<quint8>((m_hasEndPosition ? 0x00 : 0x7F))
<< static_cast<quint32>((m_hasEndPosition ? m_endPosition : 0x7F7F7F7F));
<< static_cast<quint32>(
(m_hasEndPosition ? serato32fromUint24(m_endPosition)
: 0x7F7F7F7F));
stream.writeRawData("\x00\x7F\x7F\x7F\x7F\x7F", 6);
stream << static_cast<quint32>(seratoColorFromRgb(m_color))
<< static_cast<quint8>(m_type)
<< static_cast<quint8>(m_isLocked);
stream << serato32fromUint24(static_cast<quint32>(m_color))
<< static_cast<quint8>(m_type) << static_cast<quint8>(m_isLocked);
return data;
}

Expand All @@ -95,49 +97,56 @@ SeratoMarkersEntryPointer SeratoMarkersEntry::parse(const QByteArray& data) {
quint8 type;
quint8 startPositionStatus;
quint8 endPositionStatus;
quint32 startPosition;
quint32 endPosition;
quint32 colorRaw;
quint32 startPositionSerato32;
quint32 endPositionSerato32;
quint32 colorSerato32;
bool isLocked;
char buffer[6];

QDataStream stream(data);
stream.setVersion(QDataStream::Qt_5_0);
stream.setByteOrder(QDataStream::BigEndian);
stream >> startPositionStatus >> startPosition >> endPositionStatus >> endPosition;
stream >> startPositionStatus >> startPositionSerato32 >>
endPositionStatus >> endPositionSerato32;

if (stream.readRawData(buffer, sizeof(buffer)) != sizeof(buffer)) {
qWarning() << "Parsing SeratoMarkersEntry failed:"
<< "unable to read bytes 10..16";
return nullptr;
}

stream >> colorRaw >> type >> isLocked;
stream >> colorSerato32 >> type >> isLocked;

const RgbColor color = seratoColorToRgb(colorRaw);
const RgbColor color = RgbColor(serato32toUint24(colorSerato32));

// Parse Start Position
bool hasStartPosition = (startPositionStatus != 0x7F);
quint32 startPosition = 0x7F7F7F7F;
if (!hasStartPosition) {
// Start position not set
if (startPosition != 0x7F7F7F7F) {
if (startPositionSerato32 != 0x7F7F7F7F) {
qWarning() << "Parsing SeratoMarkersEntry failed:"
<< "startPosition != 0x7F7F7F7F";

return nullptr;
}
} else {
startPosition = serato32toUint24(startPositionSerato32);
}

// Parse End Position
bool hasEndPosition = (endPositionStatus != 0x7F);
quint32 endPosition = 0x7F7F7F7F;
if (!hasEndPosition) {
// End position not set
if (endPosition != 0x7F7F7F7F) {
if (endPositionSerato32 != 0x7F7F7F7F) {
qWarning() << "Parsing SeratoMarkersEntry failed:"
<< "endPosition != 0x7F7F7F7F";

return nullptr;
}
} else {
endPosition = serato32toUint24(endPositionSerato32);
}

// Make sure that the unknown (and probably unused) bytes have the expected value
Expand All @@ -159,19 +168,20 @@ SeratoMarkersEntryPointer SeratoMarkersEntry::parse(const QByteArray& data) {
return nullptr;
}

SeratoMarkersEntryPointer pEntry = SeratoMarkersEntryPointer(new SeratoMarkersEntry(
hasStartPosition,
startPosition,
hasEndPosition,
endPosition,
color,
type,
isLocked));
SeratoMarkersEntryPointer pEntry =
SeratoMarkersEntryPointer(new SeratoMarkersEntry(hasStartPosition,
startPosition,
hasEndPosition,
endPosition,
color,
type,
isLocked));
qDebug() << "SeratoMarkersEntry" << *pEntry;
return pEntry;
}

bool SeratoMarkers::parse(SeratoMarkers* seratoMarkers, const QByteArray& data) {
bool SeratoMarkers::parse(
SeratoMarkers* seratoMarkers, const QByteArray& data) {
QDataStream stream(data);
stream.setVersion(QDataStream::Qt_5_0);
stream.setByteOrder(QDataStream::BigEndian);
Expand All @@ -189,7 +199,8 @@ bool SeratoMarkers::parse(SeratoMarkers* seratoMarkers, const QByteArray& data)

if (numEntries != kNumEntries) {
qWarning() << "Parsing SeratoMarkers_ failed:"
<< "Expected" << kNumEntries << "entries but found" << numEntries;
<< "Expected" << kNumEntries << "entries but found"
<< numEntries;
return false;
}

Expand All @@ -203,8 +214,8 @@ bool SeratoMarkers::parse(SeratoMarkers* seratoMarkers, const QByteArray& data)
}

QByteArray entryData = QByteArray(buffer, kEntrySize);
SeratoMarkersEntryPointer pEntry = SeratoMarkersEntryPointer(
SeratoMarkersEntry::parse(entryData));
SeratoMarkersEntryPointer pEntry =
SeratoMarkersEntryPointer(SeratoMarkersEntry::parse(entryData));
if (!pEntry) {
qWarning() << "Parsing SeratoMarkers_ failed:"
<< "Unable to parse entry!";
Expand All @@ -221,16 +232,17 @@ bool SeratoMarkers::parse(SeratoMarkers* seratoMarkers, const QByteArray& data)
if (i >= kLoopEntryStartIndex &&
pEntry->typeId() != SeratoMarkersEntry::TypeId::Loop) {
qWarning() << "Parsing SeratoMarkers_ failed:"
<< "Expected loop entry but found type" << pEntry->type();
<< "Expected loop entry but found type"
<< pEntry->type();
return false;
}

entries.append(pEntry);
}

quint32 trackColorRaw;
stream >> trackColorRaw;
RgbColor trackColor = seratoColorToRgb(trackColorRaw);
quint32 trackColorSerato32;
stream >> trackColorSerato32;
RgbColor trackColor = RgbColor(serato32toUint24(trackColorSerato32));

if (stream.status() != QDataStream::Status::Ok) {
qWarning() << "Parsing SeratoMarkers_ failed:"
Expand All @@ -256,7 +268,8 @@ QByteArray SeratoMarkers::dump() const {
return data;
}

data.resize(sizeof(quint16) + 2 * sizeof(quint32) + kEntrySize * m_entries.size());
data.resize(sizeof(quint16) + 2 * sizeof(quint32) +
kEntrySize * m_entries.size());

QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_0);
Expand All @@ -266,7 +279,8 @@ QByteArray SeratoMarkers::dump() const {
SeratoMarkersEntryPointer pEntry = m_entries.at(i);
stream.writeRawData(pEntry->dump(), kEntrySize);
}
stream << seratoColorFromRgb(m_trackColor.value_or(SeratoTags::kDefaultTrackColor));
stream << serato32fromUint24(static_cast<quint32>(
m_trackColor.value_or(SeratoTags::kDefaultTrackColor)));
return data;
}

Expand Down

0 comments on commit 6e8fcb2

Please sign in to comment.