Skip to content

Commit

Permalink
Merge pull request #11720 from xsco/enh/djinterop-0.19
Browse files Browse the repository at this point in the history
Update to libdjinterop 0.19
  • Loading branch information
JoergAtGithub authored Jul 12, 2023
2 parents e5cd61e + 5183ecf commit f734136
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 86 deletions.
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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}
Expand Down
60 changes: 23 additions & 37 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 e = djinterop::engine;

namespace mixxx {

Expand Down Expand Up @@ -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();
djinterop::semantic_version exportVersion =
versionIndex == -1 ? el::version_latest_firmware : 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<EnginePrimeExportRequest>::create();
Expand All @@ -222,56 +222,42 @@ 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);
int versionIndex = 0;
for (const djinterop::semantic_version& version : el::all_versions) {
for (int versionIndex = 0;
versionIndex < static_cast<int>(e::all_versions.size());
++versionIndex) {
e::engine_version version = e::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 == e::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.
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);
}
// 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 = 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."));
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
87 changes: 41 additions & 46 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 e = djinterop::engine;

namespace mixxx {

Expand Down Expand Up @@ -164,12 +164,14 @@ bool tryGetBeatgrid(BeatsPointer pBeats,
std::vector<djinterop::beatgrid_marker> 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,
void exportMetadata(
djinterop::database* pDatabase,
const e::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()
? e::calculate_overview_waveform_extents(
frameCount, pTrack->getSampleRate())
: e::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 e::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;
e::engine_version dbVersion;
try {
bool created;
pDb = std::make_unique<djinterop::database>(el::create_or_load_database(
pDb = std::make_unique<djinterop::database>(e::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 f734136

Please sign in to comment.