diff --git a/src/library/rekordbox/rekordbox_anlz.cpp b/src/library/rekordbox/rekordbox_anlz.cpp index d46284a067c..0371d512f04 100644 --- a/src/library/rekordbox/rekordbox_anlz.cpp +++ b/src/library/rekordbox/rekordbox_anlz.cpp @@ -165,7 +165,7 @@ rekordbox_anlz_t::song_structure_tag_t::song_structure_tag_t(kaitai::kstream* p_ void rekordbox_anlz_t::song_structure_tag_t::_read() { m_len_entry_bytes = m__io->read_u4be(); m_len_entries = m__io->read_u2be(); - m_style = static_cast(m__io->read_u2be()); + m_style = m__io->read_u2be(); m__unnamed3 = m__io->read_bytes(6); m_end_beat = m__io->read_u2be(); m__unnamed5 = m__io->read_bytes(4); @@ -199,7 +199,8 @@ void rekordbox_anlz_t::cue_extended_entry_t::_read() { m__unnamed5 = m__io->read_bytes(3); m_time = m__io->read_u4be(); m_loop_time = m__io->read_u4be(); - m__unnamed8 = m__io->read_bytes(12); + m_color_id = m__io->read_u1(); + m__unnamed9 = m__io->read_bytes(11); n_len_comment = true; if (len_entry() > 43) { n_len_comment = false; @@ -230,10 +231,10 @@ void rekordbox_anlz_t::cue_extended_entry_t::_read() { n_color_blue = false; m_color_blue = m__io->read_u1(); } - n__unnamed15 = true; + n__unnamed16 = true; if ((len_entry() - len_comment()) > 48) { - n__unnamed15 = false; - m__unnamed15 = m__io->read_bytes(((len_entry() - 48) - len_comment())); + n__unnamed16 = false; + m__unnamed16 = m__io->read_bytes(((len_entry() - 48) - len_comment())); } } @@ -250,7 +251,7 @@ rekordbox_anlz_t::cue_extended_entry_t::~cue_extended_entry_t() { } if (!n_color_blue) { } - if (!n__unnamed15) { + if (!n__unnamed16) { } } @@ -284,11 +285,11 @@ void rekordbox_anlz_t::song_structure_entry_t::_read() { m_phrase_number = m__io->read_u2be(); m_beat_number = m__io->read_u2be(); switch (_parent()->style()) { - case PHRASE_STYLE_UP_DOWN: { + case 1: { m_phrase_id = new phrase_up_down_t(m__io, this, m__root); break; } - case PHRASE_STYLE_VERSE_BRIDGE: { + case 2: { m_phrase_id = new phrase_verse_bridge_t(m__io, this, m__root); break; } diff --git a/src/library/rekordbox/rekordbox_anlz.h b/src/library/rekordbox/rekordbox_anlz.h index 7b6f99a0d7b..93eab7ec72d 100644 --- a/src/library/rekordbox/rekordbox_anlz.h +++ b/src/library/rekordbox/rekordbox_anlz.h @@ -395,7 +395,7 @@ class rekordbox_anlz_t : public kaitai::kstruct { private: uint32_t m_len_entry_bytes; uint16_t m_len_entries; - phrase_style_t m_style; + uint16_t m_style; std::string m__unnamed3; uint16_t m_end_beat; std::string m__unnamed5; @@ -424,7 +424,7 @@ class rekordbox_anlz_t : public kaitai::kstruct { * bridge-verse style except verses 1-3 are labeled VERSE1 and verses * 4-6 are labeled VERSE2 in rekordbox. */ - phrase_style_t style() const { return m_style; } + uint16_t style() const { return m_style; } std::string _unnamed3() const { return m__unnamed3; } /** @@ -465,7 +465,8 @@ class rekordbox_anlz_t : public kaitai::kstruct { std::string m__unnamed5; uint32_t m_time; uint32_t m_loop_time; - std::string m__unnamed8; + uint8_t m_color_id; + std::string m__unnamed9; uint32_t m_len_comment; bool n_len_comment; @@ -508,11 +509,11 @@ class rekordbox_anlz_t : public kaitai::kstruct { bool _is_null_color_blue() { color_blue(); return n_color_blue; }; private: - std::string m__unnamed15; - bool n__unnamed15; + std::string m__unnamed16; + bool n__unnamed16; public: - bool _is_null__unnamed15() { _unnamed15(); return n__unnamed15; }; + bool _is_null__unnamed16() { _unnamed16(); return n__unnamed16; }; private: rekordbox_anlz_t* m__root; @@ -546,7 +547,13 @@ class rekordbox_anlz_t : public kaitai::kstruct { * back to the cue time if this is a loop. */ uint32_t loop_time() const { return m_loop_time; } - std::string _unnamed8() const { return m__unnamed8; } + + /** + * References a row in the colors table if the memory cue or loop + * has been assigned a color + */ + uint8_t color_id() const { return m_color_id; } + std::string _unnamed9() const { return m__unnamed9; } uint32_t len_comment() const { return m_len_comment; } /** @@ -573,7 +580,7 @@ class rekordbox_anlz_t : public kaitai::kstruct { * The blue component of the color to be displayed. */ uint8_t color_blue() const { return m_color_blue; } - std::string _unnamed15() const { return m__unnamed15; } + std::string _unnamed16() const { return m__unnamed16; } rekordbox_anlz_t* _root() const { return m__root; } rekordbox_anlz_t::cue_extended_tag_t* _parent() const { return m__parent; } }; diff --git a/src/library/rekordbox/rekordbox_anlz.ksy b/src/library/rekordbox/rekordbox_anlz.ksy index 922bedbd839..15fe3c2d803 100644 --- a/src/library/rekordbox/rekordbox_anlz.ksy +++ b/src/library/rekordbox/rekordbox_anlz.ksy @@ -244,7 +244,11 @@ types: doc: | The position, in milliseconds, at which the player loops back to the cue time if this is a loop. - - size: 12 # Loops seem to have some non-zero values in the last four bytes of this. + - id: color_id + type: u1 + doc: | + Color ID of memory cues and loops, same color IDs as track colors + - size: 11 # Loops seem to have some non-zero values in the last four bytes of this. - id: len_comment type: u4 if: len_entry > 43 diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 258ce031f55..2b7ff368e51 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -46,9 +46,8 @@ const QString kRekordboxPlaylistTracksTable = QStringLiteral("rekordbox_playlist const QString kPdbPath = QStringLiteral("PIONEER/rekordbox/export.pdb"); const QString kPLaylistPathDelimiter = QStringLiteral("-->"); -constexpr double kLongestPosition = 999999999.0; -enum class TrackColor : uint8_t { +enum class IDForColor : uint8_t { Pink = 1, Red, Orange, @@ -59,17 +58,19 @@ enum class TrackColor : uint8_t { Purple }; -constexpr mixxx::RgbColor kTrackColorPink(0xF870F8); -constexpr mixxx::RgbColor kTrackColorRed(0xF870900); -constexpr mixxx::RgbColor kTrackColorOrange(0xF8A030); -constexpr mixxx::RgbColor kTrackColorYellow(0xF8E331); -constexpr mixxx::RgbColor kTrackColorGreen(0x1EE000); -constexpr mixxx::RgbColor kTrackColorAqua(0x16C0F8); -constexpr mixxx::RgbColor kTrackColorBlue(0x0150F8); -constexpr mixxx::RgbColor kTrackColorPurple(0x9808F8); - -struct memory_cue_t { - double position; +constexpr mixxx::RgbColor kColorForIDPink(0xF870F8); +constexpr mixxx::RgbColor kColorForIDRed(0xF870900); +constexpr mixxx::RgbColor kColorForIDOrange(0xF8A030); +constexpr mixxx::RgbColor kColorForIDYellow(0xF8E331); +constexpr mixxx::RgbColor kColorForIDGreen(0x1EE000); +constexpr mixxx::RgbColor kColorForIDAqua(0x16C0F8); +constexpr mixxx::RgbColor kColorForIDBlue(0x0150F8); +constexpr mixxx::RgbColor kColorForIDPurple(0x9808F8); +constexpr mixxx::RgbColor kColorForIDNoColor(0x0); + +struct memory_cue_loop_t { + double startPosition; + double endPosition; QString comment; mixxx::RgbColor::optional_t color; }; @@ -271,15 +272,15 @@ QString getText(rekordbox_pdb_t::device_sql_string_t* deviceString) { if (instanceof (deviceString->body())) { rekordbox_pdb_t::device_sql_short_ascii_t* shortAsciiString = static_cast(deviceString->body()); - text = QString::fromStdString(shortAsciiString->text()); + text = QString::fromStdString(shortAsciiString->text()); } else if (instanceof (deviceString->body())) { rekordbox_pdb_t::device_sql_long_ascii_t* longAsciiString = static_cast(deviceString->body()); - text = QString::fromStdString(longAsciiString->text()); + text = QString::fromStdString(longAsciiString->text()); } else if (instanceof (deviceString->body())) { rekordbox_pdb_t::device_sql_long_utf16be_t* longUtf16beString = static_cast(deviceString->body()); - text = toUnicode(longUtf16beString->text()); + text = toUnicode(longUtf16beString->text()); } // Some strings read from Rekordbox *.PDB files contain random null characters @@ -321,6 +322,28 @@ int createDevicePlaylist(QSqlDatabase& database, QString devicePath) { return playlistID; } +mixxx::RgbColor colorFromID(int colorID) { + switch (static_cast(colorID)) { + case IDForColor::Pink: + return kColorForIDPink; + case IDForColor::Red: + return kColorForIDRed; + case IDForColor::Orange: + return kColorForIDOrange; + case IDForColor::Yellow: + return kColorForIDYellow; + case IDForColor::Green: + return kColorForIDGreen; + case IDForColor::Aqua: + return kColorForIDAqua; + case IDForColor::Blue: + return kColorForIDBlue; + case IDForColor::Purple: + return kColorForIDPurple; + } + return kColorForIDNoColor; +} + void insertTrack( QSqlDatabase& database, rekordbox_pdb_t::track_row_t* track, @@ -348,36 +371,6 @@ void insertTrack( QString comment = getText(track->comment()); QString tracknumber = QString::number(track->track_number()); QString anlzPath = devicePath + getText(track->analyze_path()); - mixxx::RgbColor::optional_t color = mixxx::RgbColor::nullopt(); - - switch (static_cast(track->color_id())) { - case TrackColor::Pink: - color = kTrackColorPink; - break; - case TrackColor::Red: - color = kTrackColorRed; - break; - case TrackColor::Orange: - color = kTrackColorOrange; - break; - case TrackColor::Yellow: - color = kTrackColorYellow; - break; - case TrackColor::Green: - color = kTrackColorGreen; - break; - case TrackColor::Aqua: - color = kTrackColorAqua; - break; - case TrackColor::Blue: - color = kTrackColorBlue; - break; - case TrackColor::Purple: - color = kTrackColorPurple; - break; - default: - break; - } query.bindValue(":rb_id", rbID); query.bindValue(":artist", artist); @@ -395,7 +388,7 @@ void insertTrack( query.bindValue(":bitrate", bitrate); query.bindValue(":analyze_path", anlzPath); query.bindValue(":device", device); - query.bindValue(":color", mixxx::RgbColor::toQVariant(color)); + query.bindValue(":color", mixxx::RgbColor::toQVariant(colorFromID(static_cast(track->color_id())))); if (!query.exec()) { LOG_FAILED_QUERY(query); @@ -794,7 +787,7 @@ void clearDeviceTables(QSqlDatabase& database, TreeItem* child) { transaction.commit(); } -void setHotCue(TrackPointer track, double position, int id, QString label, mixxx::RgbColor::optional_t color) { +void setHotCue(TrackPointer track, double startPosition, double endPosition, int id, QString label, mixxx::RgbColor::optional_t color) { CuePointer pCue; bool hotCueFound = false; @@ -810,8 +803,13 @@ void setHotCue(TrackPointer track, double position, int id, QString label, mixxx pCue = CuePointer(track->createAndAddCue()); } - pCue->setType(mixxx::CueType::HotCue); - pCue->setStartPosition(position); + pCue->setStartPosition(startPosition); + if (endPosition == Cue::kNoPosition) { + pCue->setType(mixxx::CueType::HotCue); + } else { + pCue->setType(mixxx::CueType::Loop); + pCue->setEndPosition(endPosition); + } pCue->setHotCue(id); pCue->setLabel(label); if (color) { @@ -819,7 +817,7 @@ void setHotCue(TrackPointer track, double position, int id, QString label, mixxx } } -void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool ignoreBeatsAndLegacyCues, QString anlzPath) { +void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool ignoreCues, QString anlzPath) { if (!QFile(anlzPath).exists()) { return; } @@ -834,15 +832,13 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i double sampleRateKhz = sampleRate / 1000.0; double samples = sampleRateKhz * mixxx::kEngineChannelCount; - QList memoryCues; + QList memoryCuesAndLoops; int lastHotCueIndex = 0; - double cueLoopStartPosition = kLongestPosition; - double cueLoopEndPosition = kLongestPosition; for (std::vector::iterator section = anlz.sections()->begin(); section != anlz.sections()->end(); ++section) { switch ((*section)->fourcc()) { case rekordbox_anlz_t::SECTION_TAGS_BEAT_GRID: { - if (ignoreBeatsAndLegacyCues) { + if (!ignoreCues) { break; } @@ -867,7 +863,7 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i track->setBeats(pBeats); } break; case rekordbox_anlz_t::SECTION_TAGS_CUES: { - if (ignoreBeatsAndLegacyCues) { + if (ignoreCues) { break; } @@ -885,22 +881,24 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i case rekordbox_anlz_t::CUE_LIST_TYPE_MEMORY_CUES: { switch ((*cueEntry)->type()) { case rekordbox_anlz_t::CUE_ENTRY_TYPE_MEMORY_CUE: { - memory_cue_t memoryCue; - memoryCue.position = position; + memory_cue_loop_t memoryCue; + memoryCue.startPosition = position; + memoryCue.endPosition = Cue::kNoPosition; memoryCue.color = mixxx::RgbColor::nullopt(); - memoryCues << memoryCue; + memoryCuesAndLoops << memoryCue; } break; case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: { - // As Mixxx can only have 1 saved loop, use the first occurrence of a memory loop relative to the start of the track - if (position < cueLoopStartPosition) { - cueLoopStartPosition = position; - int endTime = static_cast((*cueEntry)->loop_time()) - timingOffset; - // Ensure no offset times are less than 1 - if (endTime < 1) { - endTime = 1; - } - cueLoopEndPosition = samples * static_cast(endTime); + int endTime = static_cast((*cueEntry)->loop_time()) - timingOffset; + // Ensure no offset times are less than 1 + if (endTime < 1) { + endTime = 1; } + + memory_cue_loop_t loop; + loop.startPosition = position; + loop.endPosition = samples * static_cast(endTime); + loop.color = mixxx::RgbColor::nullopt(); + memoryCuesAndLoops << loop; } break; } } break; @@ -909,12 +907,16 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i if (hotCueIndex > lastHotCueIndex) { lastHotCueIndex = hotCueIndex; } - setHotCue(track, position, hotCueIndex, QString(), mixxx::RgbColor::nullopt()); + setHotCue(track, position, Cue::kNoPosition, hotCueIndex, QString(), mixxx::RgbColor::nullopt()); } break; } } } break; case rekordbox_anlz_t::SECTION_TAGS_CUES_2: { + if (ignoreCues) { + break; + } + rekordbox_anlz_t::cue_extended_tag_t* cuesExtendedTag = static_cast((*section)->body()); for (std::vector::iterator cueExtendedEntry = cuesExtendedTag->cues()->begin(); cueExtendedEntry != cuesExtendedTag->cues()->end(); ++cueExtendedEntry) { @@ -929,23 +931,26 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i case rekordbox_anlz_t::CUE_LIST_TYPE_MEMORY_CUES: { switch ((*cueExtendedEntry)->type()) { case rekordbox_anlz_t::CUE_ENTRY_TYPE_MEMORY_CUE: { - memory_cue_t memoryCue; - memoryCue.position = position; + memory_cue_loop_t memoryCue; + memoryCue.startPosition = position; + memoryCue.endPosition = Cue::kNoPosition; memoryCue.comment = toUnicode((*cueExtendedEntry)->comment()); - memoryCue.color = mixxx::RgbColor(qRgb(static_cast((*cueExtendedEntry)->color_red()), static_cast((*cueExtendedEntry)->color_green()), static_cast((*cueExtendedEntry)->color_blue()))); - memoryCues << memoryCue; + memoryCue.color = colorFromID(static_cast((*cueExtendedEntry)->color_id())); + memoryCuesAndLoops << memoryCue; } break; case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: { - // As Mixxx can only have 1 saved loop, use the first occurrence of a memory loop relative to the start of the track - if (position < cueLoopStartPosition) { - cueLoopStartPosition = position; - int endTime = static_cast((*cueExtendedEntry)->loop_time()) - timingOffset; - // Ensure no offset times are less than 1 - if (endTime < 1) { - endTime = 1; - } - cueLoopEndPosition = samples * static_cast(endTime); + int endTime = static_cast((*cueExtendedEntry)->loop_time()) - timingOffset; + // Ensure no offset times are less than 1 + if (endTime < 1) { + endTime = 1; } + + memory_cue_loop_t loop; + loop.startPosition = position; + loop.endPosition = samples * static_cast(endTime); + loop.comment = toUnicode((*cueExtendedEntry)->comment()); + loop.color = colorFromID(static_cast((*cueExtendedEntry)->color_id())); + memoryCuesAndLoops << loop; } break; } } break; @@ -954,7 +959,8 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i if (hotCueIndex > lastHotCueIndex) { lastHotCueIndex = hotCueIndex; } - setHotCue(track, position, hotCueIndex, toUnicode((*cueExtendedEntry)->comment()), mixxx::RgbColor(qRgb(static_cast((*cueExtendedEntry)->color_red()), static_cast((*cueExtendedEntry)->color_green()), static_cast((*cueExtendedEntry)->color_blue())))); + setHotCue( + track, position, Cue::kNoPosition, hotCueIndex, toUnicode((*cueExtendedEntry)->comment()), mixxx::RgbColor(qRgb(static_cast((*cueExtendedEntry)->color_red()), static_cast((*cueExtendedEntry)->color_green()), static_cast((*cueExtendedEntry)->color_blue())))); } break; } } @@ -964,30 +970,32 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i } } - if (memoryCues.size() > 0) { - std::sort(memoryCues.begin(), memoryCues.end(), [](const memory_cue_t& a, const memory_cue_t& b) -> bool { - return a.position < b.position; + if (memoryCuesAndLoops.size() > 0) { + std::sort(memoryCuesAndLoops.begin(), memoryCuesAndLoops.end(), [](const memory_cue_loop_t& a, const memory_cue_loop_t& b) -> bool { + return a.startPosition < b.startPosition; }); - // Set first chronological memory cue as Mixxx MainCue - memory_cue_t firstCue = memoryCues[0]; - track->setCuePoint(CuePosition(firstCue.position)); - CuePointer pLoadCue = track->findCueByType(mixxx::CueType::MainCue); - pLoadCue->setLabel(firstCue.comment); - - // Add remaining memory cues as hot cues (after actual found hotcues) as Mixxx can only have 1 cue - for (int memoryCueIndex = 1; memoryCueIndex < memoryCues.size(); memoryCueIndex++) { - memory_cue_t memoryCue = memoryCues[memoryCueIndex]; - setHotCue(track, memoryCue.position, lastHotCueIndex + memoryCueIndex, memoryCue.comment, memoryCue.color); + bool mainCueFound = false; + + // Add memory cues and loops + for (int memoryCueOrLoopIndex = 0; memoryCueOrLoopIndex < memoryCuesAndLoops.size(); memoryCueOrLoopIndex++) { + memory_cue_loop_t memoryCueOrLoop = memoryCuesAndLoops[memoryCueOrLoopIndex]; + + if (!mainCueFound && memoryCueOrLoop.endPosition == Cue::kNoPosition) { + // Set first chronological memory cue as Mixxx MainCue + track->setCuePoint(CuePosition(memoryCueOrLoop.startPosition)); + CuePointer pMainCue = track->findCueByType(mixxx::CueType::MainCue); + pMainCue->setLabel(memoryCueOrLoop.comment); + pMainCue->setColor(*memoryCueOrLoop.color); + mainCueFound = true; + } else { + // Mixxx v2.4 will feature multiple loops, so these saved here will be usable + // For 2.3, Mixxx treats them as hotcues and the first one will be loaded as the single loop Mixxx supports + lastHotCueIndex++; + setHotCue(track, memoryCueOrLoop.startPosition, memoryCueOrLoop.endPosition, lastHotCueIndex, memoryCueOrLoop.comment, memoryCueOrLoop.color); + } } } - - if (cueLoopStartPosition < kLongestPosition) { - CuePointer pCue(track->createAndAddCue()); - pCue->setStartPosition(cueLoopStartPosition); - pCue->setEndPosition(cueLoopEndPosition); - pCue->setType(mixxx::CueType::Loop); - } } } // anonymous namespace @@ -1089,12 +1097,24 @@ TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const { } } +#ifdef __COREAUDIO__ + if (location.toLower().endsWith(".m4a")) { + timingOffset = 48; + } +#endif + double sampleRate = static_cast(track->getSampleRate()); QString anlzPath = index.sibling(index.row(), fieldIndex("analyze_path")).data().toString(); - readAnalyze(track, sampleRate, timingOffset, false, anlzPath); QString anlzPathExt = anlzPath.left(anlzPath.length() - 3) + "EXT"; - readAnalyze(track, sampleRate, timingOffset, true, anlzPathExt); + + if (QFile(anlzPathExt).exists()) { + // Beatgrids appear to be only correct in legacy ANLZ file + readAnalyze(track, sampleRate, timingOffset, true, anlzPath); + readAnalyze(track, sampleRate, timingOffset, false, anlzPathExt); + } else { + readAnalyze(track, sampleRate, timingOffset, false, anlzPath); + } // Assume that the key of the file the has been analyzed in Recordbox is correct // and prevent the AnalyzerKey from re-analyzing.