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

Add support for Traktor cues #1411

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,7 @@ def sources(self, build):

"library/itunes/itunesfeature.cpp",
"library/traktor/traktorfeature.cpp",
"library/traktor/traktor_cue.cpp",

"library/sidebarmodel.cpp",
"library/library.cpp",
Expand Down
23 changes: 23 additions & 0 deletions res/schema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -426,4 +426,27 @@ METADATA
ALTER TABLE cues ADD COLUMN color INTEGER DEFAULT 4294901760 NOT NULL;
</sql>
</revision>
<revision version="28" min_compatible="3">
<description>
Add traktor cue support.
Table represents traktor cues in their native format.
Traktor cues are pushed into it when the traktor library is parsed.
The traktor cues are read from this table, when a track is added to
the library.
See library/traktor/traktorfeature.cpp and library/traktor/traktor_cue.cpp
</description>
<sql>
CREATE TABLE IF NOT EXISTS traktor_cues (
Copy link
Member

Choose a reason for hiding this comment

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

Please add a comment to the code, how the table will be used.

Copy link
Author

Choose a reason for hiding this comment

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

I am descriptive in the file now.
Don't know if this is enough.

id integer PRIMARY KEY AUTOINCREMENT,
track_id integer NOT NULL REFERENCES traktor_library(id),
displ_order integer DEFAULT 0 NOT NULL,
hotcue integer DEFAULT -1 NOT NULL,
len integer DEFAULT 0 NOT NULL,
name text DEFAULT '' NOT NULL,
repeats integer DEFAULT -1 NOT NULL,
start double DEFAULT -1 NOT NULL,
type integer DEFAULT 0 NOT NULL
);
</sql>
</revision>
</schema>
2 changes: 1 addition & 1 deletion src/database/mixxxdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
const QString MixxxDb::kDefaultSchemaFile(":/schema.xml");

//static
const int MixxxDb::kRequiredSchemaVersion = 27;
const int MixxxDb::kRequiredSchemaVersion = 28;

namespace {

Expand Down
4 changes: 2 additions & 2 deletions src/library/dao/cue.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class Cue : public QObject {
JUMP = 5,
};

Cue(int id, TrackId trackId, CueType type, double position, double length,
int hotCue, QString label, QColor color);
~Cue() override;

bool isDirty() const;
Expand Down Expand Up @@ -52,8 +54,6 @@ class Cue : public QObject {

private:
explicit Cue(TrackId trackId);
Cue(int id, TrackId trackId, CueType type, double position, double length,
int hotCue, QString label, QColor color);
void setDirty(bool dirty);
void setId(int id);
void setTrackId(TrackId trackId);
Expand Down
3 changes: 3 additions & 0 deletions src/library/trackcollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ class TrackCollection : public QObject,
AnalysisDao& getAnalysisDAO() {
return m_analysisDao;
}
CueDAO& getCueDAO() {
return m_cueDao;
}

QSharedPointer<BaseTrackCache> getTrackSource() const {
return m_pTrackSource;
Expand Down
81 changes: 81 additions & 0 deletions src/library/traktor/traktor_cue.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "library/traktor/traktor_cue.h"
#include <QSqlRecord>

TraktorCue::TraktorCue(int displ_order, int hotcue, double len, QString name,
int repeats, double start, TraktorCueType type) :
displ_order(displ_order), hotcue(hotcue), len(len), name(name), repeats(repeats),
start(start), type(type) {
}

TraktorCue::TraktorCue(const QXmlStreamAttributes &cue_attributes) :
TraktorCue(
cue_attributes.value("DISPL_ORDER").toString().toInt(),
cue_attributes.value("HOTCUE").toString().toInt(),
cue_attributes.value("LEN").toString().toDouble(),
cue_attributes.value("NAME").toString(),
cue_attributes.value("REPEATS").toString().toInt(),
cue_attributes.value("START").toString().toDouble(),
(TraktorCueType)cue_attributes.value("TYPE").toString().toInt()
) {
}

TraktorCue::TraktorCue(const QSqlRecord &record, const QSqlQuery &query) :
TraktorCue(
query.value(record.indexOf("displ_order")).toInt(),
query.value(record.indexOf("hotcue")).toInt(),
query.value(record.indexOf("len")).toDouble(),
query.value(record.indexOf("name")).toString(),
query.value(record.indexOf("repeats")).toInt(),
query.value(record.indexOf("start")).toDouble(),
(TraktorCueType)query.value(record.indexOf("type")).toInt()
) {
track_id = query.value(record.indexOf("track_id")).toInt();
}

void TraktorCue::fillQuery(int track_id, QSqlQuery &query) {
query.bindValue(":track_id", track_id);
query.bindValue(":displ_order", displ_order);
query.bindValue(":hotcue", hotcue);
query.bindValue(":len", len);
query.bindValue(":name", name);
query.bindValue(":repeats", repeats);
query.bindValue(":start", start);
query.bindValue(":type", type);
}

CuePointer TraktorCue::toCue(int samplerate) {
//Incomplete
int hotcue = this->hotcue;
Cue::CueType type;
switch (this->type) {
case HOTCUE:
type = Cue::CueType::CUE;
break;
case LOAD:
type = Cue::CueType::LOAD;
break;
case LOOP:
type = Cue::CueType::LOOP;
break;
case AUTOGRID:
type = Cue::CueType::BEAT;
break;
case FADEIN:
case FADEOUT:
qDebug() << "Fade cue points are not yet supported in mixxx";
return CuePointer(NULL);
default:
qDebug() << "Unknown traktor cue type: " << this->type;
return CuePointer(NULL);
}
TrackId track_id(this->track_id);
QColor color("#FF0000");
//start is a a float denoting milliseconds
//convert to stereoSamplePointer
int position = int(start/1000.0 * double(samplerate) * 2.0);
int length = int(len/1000.0 * double(samplerate) * 2.0);

return CuePointer(new Cue(-1, track_id, type, position, length, hotcue,
name, color));
}

42 changes: 42 additions & 0 deletions src/library/traktor/traktor_cue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#ifndef TRAKTOR_CUE_H
#define TRAKTOR_CUE_H

#include <QString>
#include <QXmlStreamAttributes>
#include <QSqlQuery>
#include "library/dao/cue.h"

#define TRAKTOR_CUE_QUERY "INSERT INTO traktor_cues (track_id, displ_order," \
"hotcue, len, name, repeats, start, type)" \
" VALUES (:track_id, :displ_order, :hotcue, :len," \
":name, :repeats, :start, :type)"
enum TraktorCueType {
HOTCUE, //Normal Cue
FADEIN, //Triggered by fadeout in playing deck
FADEOUT, //Triggers fadein cue in other deck
LOAD, //Position to jump to when track is loaded into deck
AUTOGRID, //First beat of track
LOOP //Loop Cue
};

class TraktorCue {
public:
TraktorCue(const QXmlStreamAttributes &cue_attributes);
TraktorCue(const QSqlRecord &record, const QSqlQuery &query);
CuePointer toCue(int samplerate);
void fillQuery(int track_id, QSqlQuery &query);
void setTrackId(int track_id);

private:
TraktorCue(int displ_order, int hotcue, double len, QString name, int repeats, double start, TraktorCueType type);
int track_id;
int displ_order;
int hotcue;
double len;
QString name;
int repeats;
double start;
TraktorCueType type;
};

#endif // TRAKTOR_CUE_H
76 changes: 73 additions & 3 deletions src/library/traktor/traktorfeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <QDesktopServices>

#include "library/traktor/traktorfeature.h"
#include "library/traktor/traktor_cue.h"

#include "library/librarytablemodel.h"
#include "library/missingtablemodel.h"
Expand Down Expand Up @@ -50,6 +51,52 @@ bool TraktorPlaylistModel::isColumnHiddenByDefault(int column) {
return BaseSqlTableModel::isColumnHiddenByDefault(column);
}

void addCuesToTrack(TrackPointer pTrack, QString traktorTrackId, const QSqlDatabase &m_database) {
QList<CuePointer> cues;
QSqlQuery query(m_database);
query.prepare("SELECT * FROM traktor_cues WHERE track_id = :track_id");
query.bindValue(":track_id", traktorTrackId);
if (query.exec()) {
while (query.next()) {
TraktorCue cue(query.record(), query);
CuePointer pCue = cue.toCue(pTrack->getSampleRate());
if (pCue) {
cues.push_back(pCue);
}
}
qDebug() << "Found " << cues.count() << " cues";
pTrack->setCuePoints(cues);
} else {
LOG_FAILED_QUERY(query);
}
}

TrackPointer TraktorTrackModel::getTrack(const QModelIndex& index) const {
QString id = index.sibling(index.row(), fieldIndex("id")).data().toString();
QString location = index.sibling(index.row(), fieldIndex("location")).data().toString();
bool track_already_in_library = m_pTrackCollection->getTrackDAO().trackExistsInDatabase(location);
TrackPointer pTrack = BaseExternalTrackModel::getTrack(index);

if (pTrack && !track_already_in_library) {
Copy link
Author

Choose a reason for hiding this comment

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

This is the check that has to be removed (besides other minor changes) to be able to import changed Traktor cues.

addCuesToTrack(pTrack, id, m_database);
}

return pTrack;
}

TrackPointer TraktorPlaylistModel::getTrack(const QModelIndex& index) const {
QString id = index.sibling(index.row(), fieldIndex("track_id")).data().toString();
QString location = index.sibling(index.row(), fieldIndex("location")).data().toString();
bool track_already_in_library = m_pTrackCollection->getTrackDAO().trackExistsInDatabase(location);
TrackPointer pTrack = BaseExternalPlaylistModel::getTrack(index);

if (pTrack && !track_already_in_library) {
addCuesToTrack(pTrack, id, m_database);
}

return pTrack;
}

TraktorFeature::TraktorFeature(QObject* parent, TrackCollection* pTrackCollection)
: BaseExternalLibraryFeature(parent, pTrackCollection),
m_pTrackCollection(pTrackCollection),
Expand Down Expand Up @@ -187,6 +234,7 @@ TreeItem* TraktorFeature::importLibrary(QString file) {
clearTable("traktor_playlist_tracks");
clearTable("traktor_library");
clearTable("traktor_playlists");
clearTable("traktor_cues");
transaction.commit();

transaction.transaction();
Expand All @@ -196,6 +244,8 @@ TreeItem* TraktorFeature::importLibrary(QString file) {
"rating,key) VALUES (:artist, :title, :album, :year,:genre,"
":comment, :tracknumber,:bpm, :bitrate,:duration, :location,"
":rating,:key)");
QSqlQuery cue_query(m_database);
cue_query.prepare(TRAKTOR_CUE_QUERY);

//Parse Trakor XML file using SAX (for performance)
QFile traktor_file(file);
Expand All @@ -218,7 +268,7 @@ TreeItem* TraktorFeature::importLibrary(QString file) {
// Each "ENTRY" tag in <COLLECTION> represents a track
if (inCollectionTag && xml.name() == "ENTRY") {
//parse track
parseTrack(xml, query);
parseTrack(xml, query, cue_query);
++nAudioFiles; //increment number of files in the music collection
}
if (xml.name() == "PLAYLISTS") {
Expand Down Expand Up @@ -259,7 +309,7 @@ TreeItem* TraktorFeature::importLibrary(QString file) {
return root;
}

void TraktorFeature::parseTrack(QXmlStreamReader &xml, QSqlQuery &query) {
void TraktorFeature::parseTrack(QXmlStreamReader &xml, QSqlQuery &query, QSqlQuery &cue_query) {
QString title;
QString artist;
QString album;
Expand All @@ -278,6 +328,7 @@ void TraktorFeature::parseTrack(QXmlStreamReader &xml, QSqlQuery &query) {
int rating = 0;
QString comment;
QString tracknumber;
QList<TraktorCue> cues;

//get XML attributes of starting ENTRY tag
QXmlStreamAttributes attr = xml.attributes ();
Expand Down Expand Up @@ -337,6 +388,10 @@ void TraktorFeature::parseTrack(QXmlStreamReader &xml, QSqlQuery &query) {
bpm = attr.value("BPM").toString().toFloat();
continue;
}
if (xml.name() == "CUE_V2") {
cues.push_back(TraktorCue(xml.attributes()));
continue;
}
}
//We leave the infinte loop, if twe have the closing tag "ENTRY"
if (xml.name() == "ENTRY" && xml.isEndElement()) {
Expand All @@ -360,12 +415,27 @@ void TraktorFeature::parseTrack(QXmlStreamReader &xml, QSqlQuery &query) {
query.bindValue(":bpm", bpm);
query.bindValue(":bitrate", bitrate);


bool success = query.exec();
if (!success) {
qDebug() << "SQL Error in TraktorTableModel.cpp: line"
qDebug() << "SQL Error in TraktorFeature.cpp: line"
<< __LINE__ << " " << query.lastError();
return;
}
//Insert Cues to traktor_cue table
QVariant id = query.lastInsertId();
if (id.isValid()) {
int id_ = id.toInt();
for (TraktorCue &cue : cues) {
cue.fillQuery(id_, cue_query);
bool success = cue_query.exec();
if (!success) {
qDebug() << "SQL Error in TraktorFeature.cpp: line"
<< __LINE__ << cue_query.executedQuery() << " " << cue_query.lastError();
return;
}
}
}
}

// Purpose: Parsing all the folder and playlists of Traktor
Expand Down
4 changes: 3 additions & 1 deletion src/library/traktor/traktorfeature.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class TraktorTrackModel : public BaseExternalTrackModel {
TrackCollection* pTrackCollection,
QSharedPointer<BaseTrackCache> trackSource);
virtual bool isColumnHiddenByDefault(int column);
TrackPointer getTrack(const QModelIndex& index) const override;
};

class TraktorPlaylistModel : public BaseExternalPlaylistModel {
Expand All @@ -33,6 +34,7 @@ class TraktorPlaylistModel : public BaseExternalPlaylistModel {
TrackCollection* pTrackCollection,
QSharedPointer<BaseTrackCache> trackSource);
virtual bool isColumnHiddenByDefault(int column);
TrackPointer getTrack(const QModelIndex& index) const override;
};

class TraktorFeature : public BaseExternalLibraryFeature {
Expand All @@ -57,7 +59,7 @@ class TraktorFeature : public BaseExternalLibraryFeature {
virtual BaseSqlTableModel* getPlaylistModelForPlaylist(QString playlist);
TreeItem* importLibrary(QString file);
// parses a track in the music collection
void parseTrack(QXmlStreamReader &xml, QSqlQuery &query);
void parseTrack(QXmlStreamReader &xml, QSqlQuery &query, QSqlQuery &cue_query);
// Iterates over all playliost and folders and constructs the childmodel
TreeItem* parsePlaylists(QXmlStreamReader &xml);
// processes a particular playlist
Expand Down