Skip to content

Commit

Permalink
Update to libdjinterop 0.19
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-smidge committed Jul 7, 2023
1 parent 7f6b341 commit 8be5301
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 80 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2065,7 +2065,7 @@ 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)

# On MacOS, Mixxx does not use system SQLite, so we will use libdjinterop's
# embedded SQLite in such a case.
Expand All @@ -2089,7 +2089,7 @@ if(ENGINEPRIME)
# the configuration.
ExternalProject_Add(libdjinterop
URL "https://github.com/xsco/libdjinterop/archive/refs/tags/${LIBDJINTEROP_VERSION}.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}
Expand Down
52 changes: 19 additions & 33 deletions src/library/export/dlglibraryexport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#include "library/trackset/crate/cratestorage.h"
#include "moc_dlglibraryexport.cpp"

namespace el = djinterop::enginelibrary;
namespace el = djinterop::engine;

namespace mixxx {

Expand Down Expand Up @@ -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<EnginePrimeExportRequest>::create();
Expand Down Expand Up @@ -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);
Expand Down
83 changes: 39 additions & 44 deletions src/library/export/engineprimeexportjob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#include "util/thread_affinity.h"
#include "waveform/waveformfactory.h"

namespace el = djinterop::enginelibrary;
namespace el = djinterop::engine;

namespace mixxx {

Expand Down Expand Up @@ -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<TrackId, int64_t>* pMixxxToEnginePrimeTrackIdMap,
TrackPointer pTrack,
const Waveform* pWaveform,
Expand All @@ -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<int64_t>(1000 * pTrack->getDuration())};
snapshot.bpm = pTrack->getBpm();
Expand All @@ -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<int64_t>(pTrack->getDuration() * pTrack->getSampleRate());
snapshot.sampling = djinterop::sampling_info{
static_cast<double>(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
Expand All @@ -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<djinterop::beatgrid_marker> 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() << "("
Expand All @@ -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) {
Expand Down Expand Up @@ -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<djinterop::waveform_entry> 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}});
Expand All @@ -340,6 +321,7 @@ void exportMetadata(djinterop::database* pDatabase,
void exportTrack(
const QSharedPointer<EnginePrimeExportRequest> pRequest,
djinterop::database* pDatabase,
const el::engine_version& dbVersion,
QHash<TrackId, int64_t>* pMixxxToEnginePrimeTrackIdMap,
const TrackPointer pTrack,
const Waveform* pWaveform) {
Expand All @@ -356,6 +338,7 @@ void exportTrack(

// Export meta-data.
exportMetadata(pDatabase,
dbVersion,
pMixxxToEnginePrimeTrackIdMap,
pTrack,
pWaveform,
Expand All @@ -367,8 +350,13 @@ void exportCrate(
const QHash<TrackId, int64_t>& mixxxToEnginePrimeTrackIdMap,
const Crate& crate,
const QList<TrackId>& 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) {
Expand Down Expand Up @@ -521,12 +509,18 @@ void EnginePrimeExportJob::run() {

// Ensure that the database exists, creating an empty one if not.
std::unique_ptr<djinterop::database> pDb;
el::engine_version dbVersion;
try {
bool created;
pDb = std::make_unique<djinterop::database>(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();
Expand Down Expand Up @@ -562,6 +556,7 @@ void EnginePrimeExportJob::run() {
try {
exportTrack(m_pRequest,
pDb.get(),
dbVersion,
&mixxxToEnginePrimeTrackIdMap,
m_pLastLoadedTrack,
m_pLastLoadedWaveform.get());
Expand Down
2 changes: 1 addition & 1 deletion src/library/export/engineprimeexportrequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down

0 comments on commit 8be5301

Please sign in to comment.