Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to libdjinterop 0.19 #11720

Merged
merged 3 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use std::size_t in the first place and remove the nerrowing cast here?

Copy link
Member

@Swiftb0y Swiftb0y Jul 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because we add -1 further down... Though the solution would be to just start at 1 in this loop instead of 0...
https://github.com/mixxxdj/mixxx/pull/11720/files#diff-d9d73ff88def8fe08f4f69d92acf7c11db20ab58ba2712c92fb0addf6036932bR258-R259

++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