-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
needspeed
wants to merge
5
commits into
mixxxdj:main
Choose a base branch
from
needspeed:master
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
2ec07cc
Added Traktor Cue Support
needspeed c604809
Merge branch 'master' of https://github.com/needspeed/mixxx
needspeed 6f22022
Added (incomplete) TraktorCueType enum
needspeed 8f03e8f
Now Traktor cues are saved in their native format into traktor_cues.
needspeed f55b111
Added supported for all Traktor cue types
needspeed File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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), | ||
|
@@ -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(); | ||
|
@@ -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); | ||
|
@@ -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") { | ||
|
@@ -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; | ||
|
@@ -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 (); | ||
|
@@ -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()) { | ||
|
@@ -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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.