From 67bf9be3f97d9cc198e615f33ee8da8a4b9806ec Mon Sep 17 00:00:00 2001 From: Adam Szmigin Date: Fri, 23 Jun 2023 23:16:52 +0100 Subject: [PATCH 1/3] Update to libdjinterop 0.19 --- CMakeLists.txt | 5 +- src/library/export/dlglibraryexport.cpp | 52 +++++------- src/library/export/engineprimeexportjob.cpp | 83 +++++++++---------- src/library/export/engineprimeexportrequest.h | 2 +- 4 files changed, 62 insertions(+), 80 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d271a520f8a..c28a75590bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2065,12 +2065,13 @@ option(ENGINEPRIME "Support for library export to Denon Engine Prime" ON) if(ENGINEPRIME) # libdjinterop does not currently have a stable ABI, so we fetch sources for a specific tag, build here, and link # statically. This situation should be reviewed once libdjinterop hits version 1.x. - set(LIBDJINTEROP_VERSION 0.16.1) + set(LIBDJINTEROP_VERSION 0.19.1) # Look whether an existing installation of libdjinterop matches the required version. find_package(DjInterop ${LIBDJINTEROP_VERSION} EXACT CONFIG) if(NOT DjInterop_FOUND) find_package(DjInterop ${LIBDJINTEROP_VERSION} EXACT MODULE) endif() + if(DjInterop_FOUND) # An existing installation of djinterop is available. message(STATUS "STATIC link existing system installation of libdjinterop") @@ -2100,7 +2101,7 @@ if(ENGINEPRIME) URL "https://github.com/xsco/libdjinterop/archive/refs/tags/${LIBDJINTEROP_VERSION}.tar.gz" "https://launchpad.net/~xsco/+archive/ubuntu/djinterop/+sourcefiles/libdjinterop/${LIBDJINTEROP_VERSION}-0ubuntu1/libdjinterop_${LIBDJINTEROP_VERSION}.orig.tar.gz" - URL_HASH SHA256=25461f5cc3ea80850d8400872f4fef08ad3730d9f2051719cccf2460f5ac15ad + URL_HASH SHA256=fd03a7ed54bf4e58da0075b517a6e7382546227a1ac165769987d57e1cf5e36e DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}/downloads" DOWNLOAD_NAME "libdjinterop-${LIBDJINTEROP_VERSION}.tar.gz" INSTALL_DIR ${DJINTEROP_INSTALL_DIR} diff --git a/src/library/export/dlglibraryexport.cpp b/src/library/export/dlglibraryexport.cpp index 7b241af6942..8ee9dac5d2a 100644 --- a/src/library/export/dlglibraryexport.cpp +++ b/src/library/export/dlglibraryexport.cpp @@ -18,7 +18,7 @@ #include "library/trackset/crate/cratestorage.h" #include "moc_dlglibraryexport.cpp" -namespace el = djinterop::enginelibrary; +namespace el = djinterop::engine; namespace mixxx { @@ -199,8 +199,8 @@ void DlgLibraryExport::exportRequested() { // Work out what version was requested. // If there is an existing database, the version does not matter. int versionIndex = m_pVersionCombo->currentData().toInt(); - djinterop::semantic_version exportVersion = - versionIndex == -1 ? el::version_latest_firmware : el::all_versions[versionIndex]; + el::engine_version exportVersion = + versionIndex == -1 ? el::latest_os : el::all_versions[versionIndex]; // Construct a request to export the library/crates. auto pRequest = QSharedPointer::create(); @@ -232,46 +232,32 @@ void DlgLibraryExport::checkExistingDatabase() { m_pExistingDatabaseLabel->setText(""); m_pVersionCombo->clear(); m_pVersionCombo->setEnabled(true); - int versionIndex = 0; - for (const djinterop::semantic_version& version : el::all_versions) { + for (int versionIndex = 0; + versionIndex < (int)el::all_versions.size(); + ++versionIndex) { + el::engine_version version = el::all_versions[versionIndex]; m_pVersionCombo->insertItem(0, - QString::fromStdString(el::version_name(version)), + QString::fromStdString(version.name), QVariant{versionIndex}); - if (version == el::version_latest_firmware) { + if (version == el::latest_os) { // Latest firmware version is the default selection. m_pVersionCombo->setCurrentIndex(0); } - - ++versionIndex; } return; } - // Find out version of the existing database, and set the displayed - // version widget accordingly. Changing the schema version of existing - // databases is not currently supported. + // Load the existing database, and set the displayed version widget + // accordingly. Changing the schema version of existing databases is + // not currently supported. djinterop::database db = el::load_database(databaseDirectory.toStdString()); - const auto version = db.version(); - - const auto result = std::find(el::all_versions.begin(), el::all_versions.end(), version); - if (result == el::all_versions.end()) { - // Unknown database version. - m_pExistingDatabaseLabel->setText( - tr("A database already exists in the chosen directory, " - "but it is of an unsupported version. Export is not " - "guaranteed to succeed in this situation.")); - m_pVersionCombo->clear(); - m_pVersionCombo->setEnabled(false); - } else { - int versionIndex = std::distance(el::all_versions.begin(), result); - m_pExistingDatabaseLabel->setText( - tr("A database already exists in the chosen directory. " - "Exported tracks will be added into this database.")); - m_pVersionCombo->clear(); - m_pVersionCombo->insertItem( - 0, QString::fromStdString(el::version_name(version)), QVariant{versionIndex}); - m_pVersionCombo->setEnabled(false); - } + m_pExistingDatabaseLabel->setText( + tr("A database already exists in the chosen directory. " + "Exported tracks will be added into this database.")); + m_pVersionCombo->clear(); + m_pVersionCombo->insertItem( + 0, QString::fromStdString(db.version_name()), QVariant{-1}); + m_pVersionCombo->setEnabled(false); } catch (std::exception& e) { Q_UNUSED(e); diff --git a/src/library/export/engineprimeexportjob.cpp b/src/library/export/engineprimeexportjob.cpp index 9a1cbbe9392..b47a64d5f87 100644 --- a/src/library/export/engineprimeexportjob.cpp +++ b/src/library/export/engineprimeexportjob.cpp @@ -19,7 +19,7 @@ #include "util/thread_affinity.h" #include "waveform/waveformfactory.h" -namespace el = djinterop::enginelibrary; +namespace el = djinterop::engine; namespace mixxx { @@ -169,7 +169,9 @@ bool tryGetBeatgrid(BeatsPointer pBeats, return true; } -void exportMetadata(djinterop::database* pDatabase, +void exportMetadata( + djinterop::database* pDatabase, + const el::engine_version& dbVersion, QHash* pMixxxToEnginePrimeTrackIdMap, TrackPointer pTrack, const Waveform* pWaveform, @@ -183,12 +185,11 @@ void exportMetadata(djinterop::database* pDatabase, : djinterop::track_snapshot{}; snapshot.relative_path = relativePath.toStdString(); - // Note that the Engine Prime format has the scope for recording meta-data - // about whether track was imported from an external database. However, - // that meta-data only extends as far as other Engine Prime databases, - // which Mixxx is not. So we do not set any import information on the - // exported track. snapshot.track_number = pTrack->getTrackNumber().toInt(); + if (snapshot.track_number == 0) { + snapshot.track_number = djinterop::stdx::nullopt; + } + snapshot.duration = std::chrono::milliseconds{ static_cast(1000 * pTrack->getDuration())}; snapshot.bpm = pTrack->getBpm(); @@ -200,24 +201,14 @@ void exportMetadata(djinterop::database* pDatabase, snapshot.comment = pTrack->getComment().toStdString(); snapshot.composer = pTrack->getComposer().toStdString(); snapshot.key = toDjinteropKey(pTrack->getKey()); - int64_t lastModifiedMillisSinceEpoch = 0; - const QDateTime fileLastModified = pTrack->getFileInfo().lastModified(); - if (fileLastModified.isValid()) { - // Only defined if valid - lastModifiedMillisSinceEpoch = fileLastModified.toMSecsSinceEpoch(); - } - std::chrono::system_clock::time_point lastModifiedAt{ - std::chrono::milliseconds{lastModifiedMillisSinceEpoch}}; - snapshot.last_modified_at = lastModifiedAt; - snapshot.last_accessed_at = lastModifiedAt; snapshot.bitrate = pTrack->getBitrate(); snapshot.rating = pTrack->getRating() * 20; // note rating is in range 0-100 snapshot.file_bytes = pTrack->getFileInfo().sizeInBytes(); // Frames used interchangeably with "samples" here. const auto frameCount = static_cast(pTrack->getDuration() * pTrack->getSampleRate()); - snapshot.sampling = djinterop::sampling_info{ - static_cast(pTrack->getSampleRate()), frameCount}; + snapshot.sample_count = frameCount; + snapshot.sample_rate = pTrack->getSampleRate(); // Set track loudness. // Note that the djinterop API method for setting loudness may be revised @@ -230,16 +221,14 @@ void exportMetadata(djinterop::database* pDatabase, // Set main cue-point. mixxx::audio::FramePos cuePlayPos = pTrack->getMainCuePosition(); const auto cuePlayPosValue = cuePlayPos.isValid() ? cuePlayPos.value() : 0; - snapshot.default_main_cue = cuePlayPosValue; - snapshot.adjusted_main_cue = cuePlayPosValue; + snapshot.main_cue = cuePlayPosValue; // Fill in beat grid. BeatsPointer beats = pTrack->getBeats(); if (beats != nullptr) { std::vector beatgrid; if (tryGetBeatgrid(beats, cuePlayPos, frameCount, &beatgrid)) { - snapshot.default_beatgrid = beatgrid; - snapshot.adjusted_beatgrid = beatgrid; + snapshot.beatgrid = beatgrid; } else { qWarning() << "Beats data exists but is invalid for track" << pTrack->getId() << "(" @@ -253,7 +242,7 @@ void exportMetadata(djinterop::database* pDatabase, // Note that any existing hot cues on the track are kept in place, if Mixxx // does not have a hot cue at that location. const auto cues = pTrack->getCuePoints(); - snapshot.hot_cues.fill(djinterop::stdx::nullopt); + snapshot.hot_cues.resize(kMaxHotCues); for (const CuePointer& pCue : cues) { // We are only interested in hot cues. if (pCue->getType() != CueType::HotCue) { @@ -293,27 +282,19 @@ void exportMetadata(djinterop::database* pDatabase, snapshot.hot_cues[hotCueIndex] = hotCue; } - // Note that Mixxx does not support pre-calculated stored loops, but it will - // remember the position of a single ad-hoc loop between track loads. - // However, since this single ad-hoc loop is likely to be different in use - // from a set of stored loops (and is easily overwritten), we do not export - // it to the external database here. - // - // TODO: This comment above is wrong, it does support saved loops now. - // - // Note also that the loops on any existing track are not modified here. + // TODO (mr-smidge): Export saved loops. // Write waveform. - // Note that writing a single waveform will automatically calculate an - // overview waveform too. if (pWaveform) { - int64_t samplesPerEntry = - el::required_waveform_samples_per_entry(pTrack->getSampleRate()); - int64_t externalWaveformSize = (frameCount + samplesPerEntry - 1) / samplesPerEntry; + djinterop::waveform_extents extents = dbVersion.is_v2_schema() + ? el::calculate_overview_waveform_extents( + frameCount, pTrack->getSampleRate()) + : el::calculate_high_resolution_waveform_extents( + frameCount, pTrack->getSampleRate()); std::vector externalWaveform; - externalWaveform.reserve(externalWaveformSize); - for (int64_t i = 0; i < externalWaveformSize; ++i) { - int64_t j = pWaveform->getDataSize() * i / externalWaveformSize; + externalWaveform.reserve(extents.size); + for (uint64_t i = 0; i < extents.size; ++i) { + uint64_t j = pWaveform->getDataSize() * i / extents.size; externalWaveform.push_back({{pWaveform->getLow(j), kDefaultWaveformOpacity}, {pWaveform->getMid(j), kDefaultWaveformOpacity}, {pWaveform->getHigh(j), kDefaultWaveformOpacity}}); @@ -340,6 +321,7 @@ void exportMetadata(djinterop::database* pDatabase, void exportTrack( const QSharedPointer pRequest, djinterop::database* pDatabase, + const el::engine_version& dbVersion, QHash* pMixxxToEnginePrimeTrackIdMap, const TrackPointer pTrack, const Waveform* pWaveform) { @@ -356,6 +338,7 @@ void exportTrack( // Export meta-data. exportMetadata(pDatabase, + dbVersion, pMixxxToEnginePrimeTrackIdMap, pTrack, pWaveform, @@ -367,8 +350,13 @@ void exportCrate( const QHash& mixxxToEnginePrimeTrackIdMap, const Crate& crate, const QList& trackIds) { - // Create a new crate as a sub-crate of the top-level Mixxx crate. - auto extCrate = pExtRootCrate->create_sub_crate(crate.getName().toStdString()); + // Create a new crate as a sub-crate of the top-level Mixxx crate, if one + // does not already exist. + auto crateName = crate.getName().toStdString(); + const auto optionalExtCrate = pExtRootCrate->sub_crate_by_name(crateName); + auto extCrate = optionalExtCrate + ? *optionalExtCrate + : pExtRootCrate->create_sub_crate(crateName); // Loop through all track ids in this crate and add. for (const auto& trackId : trackIds) { @@ -521,12 +509,18 @@ void EnginePrimeExportJob::run() { // Ensure that the database exists, creating an empty one if not. std::unique_ptr pDb; + el::engine_version dbVersion; try { bool created; pDb = std::make_unique(el::create_or_load_database( m_pRequest->engineLibraryDbDir.path().toStdString(), m_pRequest->exportVersion, - created)); + created, + dbVersion)); + + if (!created) { + dbVersion = m_pRequest->exportVersion; + } } catch (std::exception& e) { qWarning() << "Failed to create/load database:" << e.what(); m_lastErrorMessage = e.what(); @@ -562,6 +556,7 @@ void EnginePrimeExportJob::run() { try { exportTrack(m_pRequest, pDb.get(), + dbVersion, &mixxxToEnginePrimeTrackIdMap, m_pLastLoadedTrack, m_pLastLoadedWaveform.get()); diff --git a/src/library/export/engineprimeexportrequest.h b/src/library/export/engineprimeexportrequest.h index efd840be837..1f31b2beea5 100644 --- a/src/library/export/engineprimeexportrequest.h +++ b/src/library/export/engineprimeexportrequest.h @@ -18,7 +18,7 @@ struct EnginePrimeExportRequest { QDir musicFilesDir; /// Version of Engine Prime database to use when exporting. - djinterop::semantic_version exportVersion; + djinterop::engine::engine_version exportVersion; /// Set of crates to export, if `exportSelectedCrates` is set to true. /// From fc9e45f7ce6faffc626671fd2bd5b1d1f24e686f Mon Sep 17 00:00:00 2001 From: Adam Szmigin Date: Sun, 9 Jul 2023 23:32:09 +0100 Subject: [PATCH 2/3] Change djinterop::engine namespace alias el -> e --- src/library/export/dlglibraryexport.cpp | 20 ++++++++++---------- src/library/export/engineprimeexportjob.cpp | 16 ++++++++-------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/library/export/dlglibraryexport.cpp b/src/library/export/dlglibraryexport.cpp index 8ee9dac5d2a..90f862e5d38 100644 --- a/src/library/export/dlglibraryexport.cpp +++ b/src/library/export/dlglibraryexport.cpp @@ -18,7 +18,7 @@ #include "library/trackset/crate/cratestorage.h" #include "moc_dlglibraryexport.cpp" -namespace el = djinterop::engine; +namespace e = djinterop::engine; namespace mixxx { @@ -193,14 +193,14 @@ void DlgLibraryExport::exportRequested() { QDir baseExportDirectory{m_pExportDirectoryTextField->text()}; const auto databaseDirectory = baseExportDirectory.filePath( - el::default_database_dir_name); + e::default_database_dir_name); const auto musicDirectory = baseExportDirectory.filePath(kDefaultMixxxExportDirName); // Work out what version was requested. // If there is an existing database, the version does not matter. int versionIndex = m_pVersionCombo->currentData().toInt(); - el::engine_version exportVersion = - versionIndex == -1 ? el::latest_os : el::all_versions[versionIndex]; + e::engine_version exportVersion = + versionIndex == -1 ? e::latest_os : e::all_versions[versionIndex]; // Construct a request to export the library/crates. auto pRequest = QSharedPointer::create(); @@ -222,24 +222,24 @@ void DlgLibraryExport::exportRequested() { void DlgLibraryExport::checkExistingDatabase() { QDir baseExportDirectory{m_pExportDirectoryTextField->text()}; const auto databaseDirectory = baseExportDirectory.filePath( - el::default_database_dir_name); + e::default_database_dir_name); try { // See if an EL DB exists in the chosen dir already. - bool exists = el::database_exists(databaseDirectory.toStdString()); + bool exists = e::database_exists(databaseDirectory.toStdString()); if (!exists) { // The user can freely choose a schema version for their new database. m_pExistingDatabaseLabel->setText(""); m_pVersionCombo->clear(); m_pVersionCombo->setEnabled(true); for (int versionIndex = 0; - versionIndex < (int)el::all_versions.size(); + versionIndex < (int)e::all_versions.size(); ++versionIndex) { - el::engine_version version = el::all_versions[versionIndex]; + e::engine_version version = e::all_versions[versionIndex]; m_pVersionCombo->insertItem(0, QString::fromStdString(version.name), QVariant{versionIndex}); - if (version == el::latest_os) { + if (version == e::latest_os) { // Latest firmware version is the default selection. m_pVersionCombo->setCurrentIndex(0); } @@ -250,7 +250,7 @@ void DlgLibraryExport::checkExistingDatabase() { // Load the existing database, and set the displayed version widget // accordingly. Changing the schema version of existing databases is // not currently supported. - djinterop::database db = el::load_database(databaseDirectory.toStdString()); + djinterop::database db = e::load_database(databaseDirectory.toStdString()); m_pExistingDatabaseLabel->setText( tr("A database already exists in the chosen directory. " "Exported tracks will be added into this database.")); diff --git a/src/library/export/engineprimeexportjob.cpp b/src/library/export/engineprimeexportjob.cpp index b47a64d5f87..cad200ea8d7 100644 --- a/src/library/export/engineprimeexportjob.cpp +++ b/src/library/export/engineprimeexportjob.cpp @@ -19,7 +19,7 @@ #include "util/thread_affinity.h" #include "waveform/waveformfactory.h" -namespace el = djinterop::engine; +namespace e = djinterop::engine; namespace mixxx { @@ -164,14 +164,14 @@ bool tryGetBeatgrid(BeatsPointer pBeats, std::vector beatgrid{ {0, firstBarAlignedBeatPlayPos.value()}, {numBeats, lastBeatPlayPos.value()}}; - beatgrid = el::normalize_beatgrid(std::move(beatgrid), frameCount); + beatgrid = e::normalize_beatgrid(std::move(beatgrid), frameCount); pBeatgrid->assign(std::begin(beatgrid), std::end(beatgrid)); return true; } void exportMetadata( djinterop::database* pDatabase, - const el::engine_version& dbVersion, + const e::engine_version& dbVersion, QHash* pMixxxToEnginePrimeTrackIdMap, TrackPointer pTrack, const Waveform* pWaveform, @@ -287,9 +287,9 @@ void exportMetadata( // Write waveform. if (pWaveform) { djinterop::waveform_extents extents = dbVersion.is_v2_schema() - ? el::calculate_overview_waveform_extents( + ? e::calculate_overview_waveform_extents( frameCount, pTrack->getSampleRate()) - : el::calculate_high_resolution_waveform_extents( + : e::calculate_high_resolution_waveform_extents( frameCount, pTrack->getSampleRate()); std::vector externalWaveform; externalWaveform.reserve(extents.size); @@ -321,7 +321,7 @@ void exportMetadata( void exportTrack( const QSharedPointer pRequest, djinterop::database* pDatabase, - const el::engine_version& dbVersion, + const e::engine_version& dbVersion, QHash* pMixxxToEnginePrimeTrackIdMap, const TrackPointer pTrack, const Waveform* pWaveform) { @@ -509,10 +509,10 @@ void EnginePrimeExportJob::run() { // Ensure that the database exists, creating an empty one if not. std::unique_ptr pDb; - el::engine_version dbVersion; + e::engine_version dbVersion; try { bool created; - pDb = std::make_unique(el::create_or_load_database( + pDb = std::make_unique(e::create_or_load_database( m_pRequest->engineLibraryDbDir.path().toStdString(), m_pRequest->exportVersion, created, From 5183ecfd5c3016255d67c1a1941646bdf8732125 Mon Sep 17 00:00:00 2001 From: Adam Szmigin Date: Mon, 10 Jul 2023 22:53:33 +0100 Subject: [PATCH 3/3] Explicit static cast for Engine version index --- src/library/export/dlglibraryexport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/export/dlglibraryexport.cpp b/src/library/export/dlglibraryexport.cpp index 90f862e5d38..5512faced63 100644 --- a/src/library/export/dlglibraryexport.cpp +++ b/src/library/export/dlglibraryexport.cpp @@ -233,7 +233,7 @@ void DlgLibraryExport::checkExistingDatabase() { m_pVersionCombo->clear(); m_pVersionCombo->setEnabled(true); for (int versionIndex = 0; - versionIndex < (int)e::all_versions.size(); + versionIndex < static_cast(e::all_versions.size()); ++versionIndex) { e::engine_version version = e::all_versions[versionIndex]; m_pVersionCombo->insertItem(0,