Merge lp:~mixxxdevelopers/mixxx/features_trackbeats into lp:~mixxxdevelopers/mixxx/trunk

Proposed by Phillip Whelan
Status: Superseded
Proposed branch: lp:~mixxxdevelopers/mixxx/features_trackbeats
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 929 lines (+573/-23)
12 files modified
mixxx/build/depends.py (+1/-0)
mixxx/res/schema.xml (+8/-0)
mixxx/src/analyserbpm.cpp (+25/-0)
mixxx/src/analyserbpm.h (+1/-0)
mixxx/src/library/dao/trackdao.cpp (+53/-9)
mixxx/src/library/trackcollection.cpp (+1/-1)
mixxx/src/trackbeats.cpp (+349/-0)
mixxx/src/trackbeats.h (+59/-0)
mixxx/src/trackinfoobject.cpp (+16/-0)
mixxx/src/trackinfoobject.h (+12/-0)
mixxx/src/waveform/waveformrenderbeat.cpp (+42/-13)
mixxx/src/waveform/waveformrenderbeat.h (+6/-0)
To merge this branch: bzr merge lp:~mixxxdevelopers/mixxx/features_trackbeats
Reviewer Review Type Date Requested Status
Mixxx Development Team Pending
Review via email: mp+48583@code.launchpad.net

This proposal supersedes a proposal from 2011-02-04.

This proposal has been superseded by a proposal from 2011-02-11.

Description of the change

This branch adds the TrackBeats data type for tracking beats as sample offsets. This version uses SoundTouch to derive the beats. A future version will work with aubio, possibly only in the Analyse pane.

To post a comment you must log in.
2532. By Phillip Whelan

Cleaning up the order of TrackBeats and creating the few remaining Seconds equivalent functions.

2533. By Phillip Whelan

Commenting of functions and addition of the last seconds equivalent functions. Also adding removeBeatSample function and it's seconds equivalent.

2534. By Phillip Whelan

Removing unecessary white space.

2535. By Phillip Whelan

Tweaks to adding and removing samples.

2536. By RJ Skerry-Ryan

Add Beats abstract interface with a BeatGrid implementation.

2537. By RJ Skerry-Ryan

Tighten up the Beats/BeatGrid interface a little bit. Add a reimplementation of TrackBeats as BeatMatrix

2538. By RJ Skerry-Ryan

Remove TrackBeats. Change TIO, Analyser, and Waveform to use Beats generic interface instead. Add versioned serialization to TrackDAO. Move creation/destruction of the Beats variants into a BeatFactory class. Most awesome class name, ever.

2539. By RJ Skerry-Ryan

Fix lack of deletion of RenderObjects in WaveformRenderer

2540. By RJ Skerry-Ryan

Add destructor traces for TIO. Delete the WaveformRenderer.

2541. By RJ Skerry-Ryan

Tracks are no longer being deleted. Turning on deletion print in TrackDAO. Clear the track cache when TrackDAO is destroyed.

2542. By RJ Skerry-Ryan

Fix SQL statement that got munged by a merge.

2543. By RJ Skerry-Ryan

Rephrase :wavesummaryhex binding to match :beats

2544. By RJ Skerry-Ryan

Doh. Forgot to add BeatFactory files

2545. By Phillip Whelan

FIX: error in ternary syntax.

2546. By Phillip Whelan

FIX: generate BeatGrid if we do not have a beatgrid but we have detected the BPM of the song already.

2547. By Phillip Whelan

Various changes will bring the new Beats classes into feature parity with the
old trackbeats code.

  * beatfactory.cpp: include beatgrid.h and beatmatrix.h because forward
                     declaration proved to not be enough.
  * beats.h, beatgrid.h, beatmatrix.h: adding findNthBeat method.
  * beatgrid.cpp: many changes and fixes, including adding findNthBeat.
      - implement findNthBeat. calculate the beat using m_dFirstBeat.
      - reimplement findPrevBeat and findNextBeat using findNthBeat.
      - replace all other beat calculations with calls to findNextBeat.
  * beatmatrix.cpp:
      - implement findNthBeat.
      - reimplement findPrevBeat and findNextBeat using findNthBeat.

2548. By RJ Skerry-Ryan

Add language to beats.h that makes clear the behavior of findNextBeat and findPrevBeat when dSamples refers to the location of a beat.

2549. By RJ Skerry-Ryan

BeatMatrix::findNthBeat fixes for going in the negative direction. Changed variable from offset to n and updated comments to reflect that.

2550. By RJ Skerry-Ryan

Fixup BeatGrid::findNextBeat a little bit. It wasn't doing the previous direction correctly.

2551. By RJ Skerry-Ryan

Add some parameter checks to BeatGrid.

2552. By RJ Skerry-Ryan

Move BeatGrid legacy check from AnalyserBpm to TrackDAO so that any track loaded from DB will get a BeatGrid if it does not have one already and already has a BPM (or if the file metadata has a stored BPM)

2553. By Phillip Whelan

Merging from trunk.

2554. By RJ Skerry-Ryan

Move beat-related classes to track/

2555. By Phillip Whelan

Merging from Repository.

2556. By Phillip Whelan

Use a struct for (de)serializing the beat, for beatgrid, from the database.

2557. By Phillip Whelan

Undo application of SSE3 patch (not the time nor the place for it).

2558. By Phillip Whelan

Make the QByteArray argument to readByteArray and both Beat classes a const.

2559. By Albert Santoni

Rename struct to BeatGridData and fixed QByteArray pointer leak in TrackDAO.

2560. By Albert Santoni

Merge from trunk

2561. By Albert Santoni

Merge from trunk

2562. By Phillip Whelan

FIX: change track beats code to use sample offsets instead of frame offsets. this way the code fits in with the rest of the codebase.

2563. By Phillip Whelan

Comments for beatgrid units, contributed by Albert Santoni.

2564. By Phillip Whelan

Change beatgrid to serialize in frame offsets but use sample offsets at runtime.

2565. By Phillip Whelan

Cleaning up some cruft left over from earlier shift to using sample offsets at runtime.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'mixxx/build/depends.py'
--- mixxx/build/depends.py 2011-02-09 18:34:37 +0000
+++ mixxx/build/depends.py 2011-02-11 04:42:13 +0000
@@ -529,6 +529,7 @@
529529
530 "sampleutil.cpp",530 "sampleutil.cpp",
531 "trackinfoobject.cpp",531 "trackinfoobject.cpp",
532 "trackbeats.cpp",
532 "baseplayer.cpp",533 "baseplayer.cpp",
533 "basetrackplayer.cpp",534 "basetrackplayer.cpp",
534 "deck.cpp",535 "deck.cpp",
535536
=== modified file 'mixxx/res/schema.xml'
--- mixxx/res/schema.xml 2011-01-06 13:53:48 +0000
+++ mixxx/res/schema.xml 2011-02-11 04:42:13 +0000
@@ -228,4 +228,12 @@
228 );228 );
229 </sql>229 </sql>
230 </revision>230 </revision>
231 <revision version="10" min_compatible="3">
232 <description>
233 Add beats column to library table.
234 </description>
235 <sql>
236 ALTER TABLE Library ADD COLUMN beats BLOB;
237 </sql>
238 </revision>
231</schema>239</schema>
232240
=== modified file 'mixxx/src/analyserbpm.cpp'
--- mixxx/src/analyserbpm.cpp 2010-10-19 01:35:26 +0000
+++ mixxx/src/analyserbpm.cpp 2011-02-11 04:42:13 +0000
@@ -3,6 +3,7 @@
33
4#include "BPMDetect.h"4#include "BPMDetect.h"
5#include "trackinfoobject.h"5#include "trackinfoobject.h"
6#include "trackbeats.h"
6#include "analyserbpm.h"7#include "analyserbpm.h"
78
89
@@ -29,6 +30,13 @@
29 // defaultrange ? MIN_BPM : m_iMinBpm,30 // defaultrange ? MIN_BPM : m_iMinBpm,
30 // defaultrange ? MAX_BPM : m_iMaxBpm);31 // defaultrange ? MAX_BPM : m_iMaxBpm);
31 }32 }
33 else if ( tio->getBpm() != 0. )
34 {
35 // Make sure we have the track beats loaded or generate them.
36 TrackBeatsPointer pTrackBeats = tio->getTrackBeats();
37 if ( pTrackBeats == NULL )
38 doBeats(tio);
39 }
32}40}
3341
3442
@@ -58,6 +66,21 @@
58 return BPM;66 return BPM;
59}67}
6068
69void AnalyserBPM::doBeats(TrackPointer tio)
70{
71 TrackBeats *pTrackBeats = new TrackBeats(tio);
72 float seconds;
73 float duration = (float)tio->getDuration();
74 float inc = 60. / tio->getBpm();
75
76 for (seconds = 0; seconds < duration; seconds += inc)
77 {
78 pTrackBeats->addBeatSeconds(seconds);
79 }
80
81 tio->setTrackBeats(TrackBeatsPointer(pTrackBeats, &QObject::deleteLater), TRUE);
82}
83
61void AnalyserBPM::finalise(TrackPointer tio) {84void AnalyserBPM::finalise(TrackPointer tio) {
62 // Check if BPM detection is enabled85 // Check if BPM detection is enabled
63 if(m_pDetector == NULL) {86 if(m_pDetector == NULL) {
@@ -71,6 +94,8 @@
7194
72 tio->setBpm(newbpm);95 tio->setBpm(newbpm);
73 tio->setBpmConfirm();96 tio->setBpmConfirm();
97
98 doBeats(tio);
74 //if(pBpmReceiver) {99 //if(pBpmReceiver) {
75 //pBpmReceiver->setComplete(tio, false, bpm);100 //pBpmReceiver->setComplete(tio, false, bpm);
76 //}101 //}
77102
=== modified file 'mixxx/src/analyserbpm.h'
--- mixxx/src/analyserbpm.h 2010-07-15 19:07:16 +0000
+++ mixxx/src/analyserbpm.h 2011-02-11 04:42:13 +0000
@@ -17,6 +17,7 @@
1717
18 private:18 private:
19 float correctBPM(float BPM, int min, int max, int aboveRange);19 float correctBPM(float BPM, int min, int max, int aboveRange);
20 void doBeats(TrackPointer tio);
2021
21 ConfigObject<ConfigValue> *m_pConfig;22 ConfigObject<ConfigValue> *m_pConfig;
22 soundtouch::BPMDetect *m_pDetector;23 soundtouch::BPMDetect *m_pDetector;
2324
=== modified file 'mixxx/src/library/dao/trackdao.cpp'
--- mixxx/src/library/dao/trackdao.cpp 2010-12-02 22:17:33 +0000
+++ mixxx/src/library/dao/trackdao.cpp 2011-02-11 04:42:13 +0000
@@ -3,6 +3,7 @@
3#include <QtCore>3#include <QtCore>
4#include <QtSql>4#include <QtSql>
5#include "trackinfoobject.h"5#include "trackinfoobject.h"
6#include "trackbeats.h"
6#include "library/dao/trackdao.h"7#include "library/dao/trackdao.h"
7#include "audiotagger.h"8#include "audiotagger.h"
89
@@ -215,16 +216,16 @@
215216
216void TrackDAO::prepareLibraryInsert(QSqlQuery& query) {217void TrackDAO::prepareLibraryInsert(QSqlQuery& query) {
217 query.prepare("INSERT INTO library (artist, title, album, year, genre, tracknumber, "218 query.prepare("INSERT INTO library (artist, title, album, year, genre, tracknumber, "
218 "filetype, location, comment, url, duration, rating, key, "219 "filetype, location, comment, url, duration, "
219 "bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, "220 "bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, "
220 "timesplayed, "221 "timesplayed, "
221 "channels, mixxx_deleted, header_parsed) "222 "channels, mixxx_deleted, header_parsed, beats) "
222 "VALUES (:artist, "223 "VALUES (:artist, "
223 ":title, :album, :year, :genre, :tracknumber, "224 ":title, :album, :year, :genre, :tracknumber, "
224 ":filetype, :location, :comment, :url, :duration, :rating, :key,"225 ":filetype, :location, :comment, :url, :duration, "
225 ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :wavesummaryhex, "226 ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :wavesummaryhex, "
226 ":timesplayed, "227 ":timesplayed, "
227 ":channels, :mixxx_deleted, :header_parsed)");228 ":channels, :mixxx_deleted, :header_parsed, :beats)");
228}229}
229230
230void TrackDAO::bindTrackToLibraryInsert(QSqlQuery& query, TrackInfoObject* pTrack, int trackLocationId) {231void TrackDAO::bindTrackToLibraryInsert(QSqlQuery& query, TrackInfoObject* pTrack, int trackLocationId) {
@@ -249,11 +250,23 @@
249 const QByteArray* pWaveSummary = pTrack->getWaveSummary();250 const QByteArray* pWaveSummary = pTrack->getWaveSummary();
250 if (pWaveSummary) //Avoid null pointer deref251 if (pWaveSummary) //Avoid null pointer deref
251 query.bindValue(":wavesummaryhex", *pWaveSummary);252 query.bindValue(":wavesummaryhex", *pWaveSummary);
253 else
254 query.bindValue(":wavesummaryhex", QVariant(QVariant::ByteArray));
252 query.bindValue(":timesplayed", pTrack->getTimesPlayed());255 query.bindValue(":timesplayed", pTrack->getTimesPlayed());
253 //query.bindValue(":datetime_added", pTrack->getDateAdded());256 //query.bindValue(":datetime_added", pTrack->getDateAdded());
254 query.bindValue(":channels", pTrack->getChannels());257 query.bindValue(":channels", pTrack->getChannels());
255 query.bindValue(":mixxx_deleted", 0);258 query.bindValue(":mixxx_deleted", 0);
256 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0);259 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0);
260 const QByteArray *pBeatsBlob = NULL;
261 TrackBeatsPointer pBeats = pTrack->getTrackBeats();
262 if ( pBeats )
263 pBeatsBlob = pBeats->serializeToBlob();
264
265 if ( pBeatsBlob )
266 query.bindValue(":beats", *pBeatsBlob);
267 else
268 query.bindValue(":beats", QVariant(QVariant::ByteArray));
269
257}270}
258271
259void TrackDAO::addTracks(QList<TrackInfoObject*> tracksToAdd) {272void TrackDAO::addTracks(QList<TrackInfoObject*> tracksToAdd) {
@@ -306,7 +319,8 @@
306 bindTrackToLibraryInsert(query, pTrack, pTrack->getId());319 bindTrackToLibraryInsert(query, pTrack, pTrack->getId());
307320
308 if (!query.exec()) {321 if (!query.exec()) {
309 qDebug() << "Failed to INSERT new track into library"322 qDebug() << "Failed to INSERT new track into library:"
323 << pTrack->getFilename()
310 << __FILE__ << __LINE__ << query.lastError();324 << __FILE__ << __LINE__ << query.lastError();
311 m_database.rollback();325 m_database.rollback();
312 return;326 return;
@@ -550,6 +564,7 @@
550 bool header_parsed = query.value(query.record().indexOf("header_parsed")).toBool();564 bool header_parsed = query.value(query.record().indexOf("header_parsed")).toBool();
551565
552 TrackInfoObject* track = new TrackInfoObject(location, false);566 TrackInfoObject* track = new TrackInfoObject(location, false);
567 TrackPointer pTrack = TrackPointer(track, this->deleteTrack);
553568
554 // TIO already stats the file to see if it exists, what its length is,569 // TIO already stats the file to see if it exists, what its length is,
555 // etc. So don't bother setting it.570 // etc. So don't bother setting it.
@@ -575,6 +590,19 @@
575 track->setReplayGain(replaygain.toFloat());590 track->setReplayGain(replaygain.toFloat());
576 track->setWaveSummary(wavesummaryhex, false);591 track->setWaveSummary(wavesummaryhex, false);
577 delete wavesummaryhex;592 delete wavesummaryhex;
593
594 // TrackBeats needs the samplerate for it's constructor
595 // so we do it all right here.
596 QByteArray* beatsblob = new QByteArray(
597 query.value(query.record().indexOf("beats")).toByteArray());
598 if ( beatsblob->size() > 1 )
599 {
600 TrackBeats* pTrackBeats = new TrackBeats(pTrack);
601 pTrackBeats->unserializeFromBlob(beatsblob);
602 track->setTrackBeats(TrackBeatsPointer(pTrackBeats, &QObject::deleteLater), false);
603 }
604 delete beatsblob;
605
578 track->setTimesPlayed(timesplayed);606 track->setTimesPlayed(timesplayed);
579 track->setPlayed(played);607 track->setPlayed(played);
580 track->setChannels(channels);608 track->setChannels(channels);
@@ -593,8 +621,6 @@
593 connect(track, SIGNAL(save()),621 connect(track, SIGNAL(save()),
594 this, SLOT(slotTrackSave()));622 this, SLOT(slotTrackSave()));
595623
596 TrackPointer pTrack = TrackPointer(track, this->deleteTrack);
597
598 // Automatic conversion to a weak pointer624 // Automatic conversion to a weak pointer
599 m_tracks[trackId] = pTrack;625 m_tracks[trackId] = pTrack;
600 m_trackCache.insert(trackId, new TrackPointer(pTrack));626 m_trackCache.insert(trackId, new TrackPointer(pTrack));
@@ -647,7 +673,17 @@
647 time.start();673 time.start();
648 QSqlQuery query(m_database);674 QSqlQuery query(m_database);
649675
650 query.prepare("SELECT library.id, artist, title, album, year, genre, tracknumber, filetype, rating, key, track_locations.location as location, track_locations.filesize as filesize, comment, url, duration, bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, channels, header_parsed, timesplayed, played FROM Library INNER JOIN track_locations ON library.location = track_locations.id WHERE library.id=" + QString("%1").arg(id));676 query.prepare(
677 "SELECT library.id, artist, title, album, year, genre, tracknumber, "
678 "filetype, rating, key, track_locations.location as location, "
679 "track_locations.filesize as filesize, comment, url, duration, bitrate, "
680 "samplerate, cuepoint, bpm, replaygain, wavesummaryhex, channels, "
681 "header_parsed, timesplayed, played, beats "
682 "FROM Library "
683 "INNER JOIN track_locations "
684 "ON library.location = track_locations.id "
685 "WHERE library.id=" + QString("%1").arg(id)
686 );
651687
652 TrackPointer pTrack;688 TrackPointer pTrack;
653689
@@ -686,7 +722,8 @@
686 "bitrate=:bitrate, samplerate=:samplerate, cuepoint=:cuepoint, "722 "bitrate=:bitrate, samplerate=:samplerate, cuepoint=:cuepoint, "
687 "bpm=:bpm, replaygain=:replaygain, wavesummaryhex=:wavesummaryhex, "723 "bpm=:bpm, replaygain=:replaygain, wavesummaryhex=:wavesummaryhex, "
688 "timesplayed=:timesplayed, played=:played, "724 "timesplayed=:timesplayed, played=:played, "
689 "channels=:channels, header_parsed=:header_parsed "725 "channels=:channels, header_parsed=:header_parsed, "
726 "beats=:beats "
690 "WHERE id="+QString("%1").arg(trackId));727 "WHERE id="+QString("%1").arg(trackId));
691 query.bindValue(":artist", pTrack->getArtist());728 query.bindValue(":artist", pTrack->getArtist());
692 query.bindValue(":title", pTrack->getTitle());729 query.bindValue(":title", pTrack->getTitle());
@@ -713,6 +750,13 @@
713 query.bindValue(":channels", pTrack->getChannels());750 query.bindValue(":channels", pTrack->getChannels());
714 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0);751 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0);
715 //query.bindValue(":location", pTrack->getLocation());752 //query.bindValue(":location", pTrack->getLocation());
753 TrackBeatsPointer pBeats = pTrack->getTrackBeats();
754 if ( pBeats )
755 {
756 const QByteArray *pBeatsBlob = pBeats->serializeToBlob();
757 if ( pBeatsBlob )
758 query.bindValue(":beats", *pBeatsBlob);
759 }
716760
717 if (!query.exec()) {761 if (!query.exec()) {
718 qDebug() << query.lastError();762 qDebug() << query.lastError();
719763
=== modified file 'mixxx/src/library/trackcollection.cpp'
--- mixxx/src/library/trackcollection.cpp 2010-12-22 21:23:09 +0000
+++ mixxx/src/library/trackcollection.cpp 2011-02-11 04:42:13 +0000
@@ -68,7 +68,7 @@
68 return false;68 return false;
69 }69 }
7070
71 int requiredSchemaVersion = 9;71 int requiredSchemaVersion = 10;
72 if (!SchemaManager::upgradeToSchemaVersion(m_pConfig, m_db,72 if (!SchemaManager::upgradeToSchemaVersion(m_pConfig, m_db,
73 requiredSchemaVersion)) {73 requiredSchemaVersion)) {
74 QMessageBox::warning(0, tr("Cannot upgrade database schema"),74 QMessageBox::warning(0, tr("Cannot upgrade database schema"),
7575
=== added file 'mixxx/src/trackbeats.cpp'
--- mixxx/src/trackbeats.cpp 1970-01-01 00:00:00 +0000
+++ mixxx/src/trackbeats.cpp 2011-02-11 04:42:13 +0000
@@ -0,0 +1,349 @@
1#include <QtDebug>
2
3#include "defs.h"
4#include "trackinfoobject.h"
5#include "trackbeats.h"
6
7#define TRACKBEATS_INDEX_RANGE 20
8
9
10TrackBeats::TrackBeats(TrackPointer tio) : m_qMutex(QMutex::Recursive)
11{
12 m_iSampleRate = tio->getSampleRate();
13}
14
15TrackBeats::~TrackBeats()
16{
17}
18
19/**
20 * Private function that returns the sample index. This is an index into
21 * m_beatIndex which holds the first sample offset that holds a beat for a
22 * certain range of sample offsets.
23 */
24int TrackBeats::sampleIndex(int sample) const
25{
26 QMutexLocker lock(&m_qMutex);
27 return (int) round(sample / (m_iSampleRate * TRACKBEATS_INDEX_RANGE));
28}
29
30/**
31 * Get the count of beats stored.
32 */
33int TrackBeats::getBeatCount() const
34{
35 QMutexLocker lock(&m_qMutex);
36 return m_beats.size();
37}
38
39/**
40 * Add a beat at 'sample' sample.
41 */
42void TrackBeats::addBeatSample(int sample)
43{
44 QMutexLocker lock(&m_qMutex);
45 int index = sampleIndex(sample);
46 int i;
47
48
49 if ((m_beatIndex.size()-1) < index )
50 {
51 for (i = m_beatIndex.size() - 1; i <= index; i++ )
52 m_beatIndex.append(sample);
53 }
54 else
55 {
56 for (i = index; m_beatIndex.at(i) >= sample && i >= 0; i-- ) {
57 m_beatIndex[index] = sample;
58 }
59 }
60
61 m_beats[sample] = sample;
62}
63
64/**
65 * Remove the beat with the sample offset specified.
66 */
67void TrackBeats::removeBeatSample(int sample)
68{
69 QMutexLocker lock(&m_qMutex);
70 int index = sampleIndex(sample);
71 int i;
72 int newSample = -1;
73
74
75 m_beats.remove(sample);
76
77 if ( ! m_beatIndex.contains(sample))
78 return;
79
80 if ((index+1) < m_beatIndex.size()) {
81 newSample = m_beatIndex.at(index+1);
82
83 for (i = index; i >= 0 && m_beatIndex.at(i) >= sample; i--) {
84 if ( m_beatIndex.at(i) == sample )
85 m_beatIndex[i] = newSample;
86 }
87 }
88 else {
89 for (i = index; i >= 0 && m_beatIndex.at(i) >= sample; i--) {
90 if ( m_beatIndex.at(i) == sample )
91 m_beatIndex.removeAt(i);
92 }
93 }
94
95 if ( m_beatIndex.contains(sample) )
96 qDebug() << "Dangling Indexes:" << m_beatIndex.count(sample);
97}
98
99/**
100 * Add a beat at 'beat' seconds.
101 */
102void TrackBeats::addBeatSeconds(double beat)
103{
104 QMutexLocker lock(&m_qMutex);
105 addBeatSample((int)round(beat * m_iSampleRate));
106}
107
108
109/**
110 * Remove a beat at 'beat' seconds.
111 */
112void TrackBeats::removeBeatSeconds(double beat)
113{
114 QMutexLocker lock(&m_qMutex);
115 removeBeatSample((int)round(beat * m_iSampleRate));
116}
117
118/**
119 * Find the Next beat starting from offset sample.
120 */
121int TrackBeats::findNextBeatSample(int sample) const
122{
123 QMutexLocker lock(&m_qMutex);
124 QMapIterator<int, int> iter(m_beats);
125 int index = sampleIndex(sample);
126
127
128 if (m_beatIndex.size() > index)
129 {
130 iter.findNext(m_beatIndex.value(index));
131 do {
132 iter.next();
133 } while((iter.hasNext()) && (iter.value() <= sample));
134
135 return iter.value();
136 }
137
138 return -1;
139}
140
141/**
142 * Find the Previous beat starting from offset sample.
143 */
144int TrackBeats::findPrevBeatSample(int sample) const
145{
146 QMutexLocker lock(&m_qMutex);
147 QMapIterator<int, int> iter(m_beats);
148 int index = sampleIndex(sample);
149
150 if (m_beatIndex.size() > index)
151 {
152 iter.findNext(m_beatIndex.value(index));
153 do {
154 iter.previous();
155 } while((iter.hasPrevious()) && (iter.value() >= sample));
156
157 return iter.value();
158 }
159
160 return -1;
161}
162
163/**
164 * Find the Nth beat (offset) from sample .
165 */
166int TrackBeats::findBeatOffsetSamples(int sample, int offset) const
167{
168 QMutexLocker lock(&m_qMutex);
169 QMapIterator<int, int> iter(m_beats);
170 int index = sampleIndex(sample);
171 int i;
172
173
174 if (m_beatIndex.size() < index)
175 return -1;
176
177
178 iter.findNext(m_beatIndex.value(index));
179 do {
180 iter.next();
181 } while((iter.hasNext()) && (iter.value() <= sample));
182
183 // Backup one just to be before the marker
184 if ((iter.hasPrevious()) && (iter.value() > sample))
185 iter.previous();
186
187 // Find the offset from the current beat
188 if ( offset > 0 )
189 {
190 for (i = 0; i < offset && iter.hasNext(); i++)
191 iter.next();
192 }
193 else if ( offset < 0 )
194 {
195 for (i = offset * -1; i > 0 && iter.hasPrevious(); i--)
196 iter.previous();
197 }
198
199 return iter.value();
200}
201
202/**
203 * Return TRUE if there is any beats between start and stop (in samples)
204 */
205bool TrackBeats::hasBeatsSamples(double start, double stop) const
206{
207 QMutexLocker lock(&m_qMutex);
208 QMapIterator<int, int> iter(m_beats);
209 int index = sampleIndex(start);
210
211
212 if (iter.findNext(m_beatIndex.value(index)))
213 {
214 do {
215 if ((iter.value() >= start) && (iter.value() <= stop))
216 return true;
217
218 iter.next();
219 } while((iter.hasNext()) && (iter.value() <= stop));
220 }
221
222 return false;
223}
224
225/**
226 * Find and return a list of all beats between sample offset start and stop.
227 */
228QList<int>* TrackBeats::findBeatsSamples(int start, int stop) const
229{
230 QMutexLocker lock(&m_qMutex);
231 QList<int> *ret = new QList<int>;
232 QMapIterator<int, int> iter(m_beats);
233 int index = sampleIndex(start);
234
235
236 if (iter.findNext(m_beatIndex.value(index)))
237 {
238 do {
239 if ((iter.value() >= start) && (iter.value() <= stop))
240 ret->append(iter.value());
241
242 iter.next();
243 } while((iter.hasNext()) && (iter.value() <= stop));
244 }
245
246 return ret;
247}
248
249/**
250 * Find the Next beat starting from an offset in seconds.
251 */
252double TrackBeats::findNextBeatSeconds(double beat) const
253{
254 QMutexLocker lock(&m_qMutex);
255 int sample = (int) round(beat * m_iSampleRate);
256 return findNextBeatSample(sample) / m_iSampleRate;
257}
258
259/**
260 * Find the Previous beat starting from an offset in seconds.
261 */
262double TrackBeats::findPrevBeatSeconds(double beat) const
263{
264 QMutexLocker lock(&m_qMutex);
265 int sample = (int) round(beat * m_iSampleRate);
266 return findPrevBeatSample(sample) / m_iSampleRate;
267}
268
269/**
270 * Find the Nth beat (offset) from offset seconds in seconds.
271 */
272double TrackBeats::findBeatOffsetSeconds(double seconds, int offset) const
273{
274 QMutexLocker lock(&m_qMutex);
275 int bgn = round(seconds * m_iSampleRate);
276 return ((double)findBeatOffsetSamples(bgn, offset) / m_iSampleRate);
277}
278
279/**
280 * Return TRUE if there is any beats between start and stop (in seconds)
281 */
282bool TrackBeats::hasBeatsSeconds(double start, double stop) const
283{
284 QMutexLocker lock(&m_qMutex);
285 int bgn = round(start * m_iSampleRate);
286 int end = round(stop * m_iSampleRate);
287
288 return hasBeatsSamples(bgn, end);
289}
290
291/**
292 * Find and return a list of all beats between seconds start and stop.
293 */
294QList<double>* TrackBeats::findBeatsSeconds(double start, double stop) const
295{
296 QMutexLocker lock(&m_qMutex);
297 QList<double> *ret = new QList<double>;
298 QList<int>* samples;
299 int begin = round(start * m_iSampleRate);
300 int end = round(stop * m_iSampleRate);
301 int i;
302
303
304 samples = findBeatsSamples(begin, end);
305 for (i = 0; i < samples->size(); i++)
306 {
307 ret->append((double)samples->at(i) / (double)m_iSampleRate);
308 }
309
310 delete samples;
311 return ret;
312}
313
314QByteArray *TrackBeats::serializeToBlob()
315{
316 QMutexLocker lock(&m_qMutex);
317 QByteArray *blob;
318 int *buffer = new int[getBeatCount()];
319 int *ptr = buffer;
320 QMapIterator<int, int> iter(m_beats);
321
322
323 iter.next();
324
325 while ( iter.hasNext())
326 {
327 *ptr++ = iter.value();
328 iter.next();
329 }
330
331 blob = new QByteArray((char *)buffer, getBeatCount() * sizeof(int));
332 delete []buffer;
333
334 return blob;
335}
336
337void TrackBeats::unserializeFromBlob(QByteArray *blob)
338{
339 QMutexLocker lock(&m_qMutex);
340 int *ptr = (int *)blob->constData();
341 int i;
342
343
344 for (i = blob->size() / sizeof(int); --i; ptr++)
345 {
346 addBeatSample(*ptr);
347 }
348}
349
0350
=== added file 'mixxx/src/trackbeats.h'
--- mixxx/src/trackbeats.h 1970-01-01 00:00:00 +0000
+++ mixxx/src/trackbeats.h 2011-02-11 04:42:13 +0000
@@ -0,0 +1,59 @@
1#include <QtDebug>
2#include <QList>
3#include <QSharedPointer>
4#include "trackinfoobject.h"
5
6
7#ifndef __TRACK_BEATS_H__
8#define __TRACK_BEATS_H__
9
10class TrackBeats;
11typedef QSharedPointer<TrackBeats> TrackBeatsPointer;
12
13class TrackBeats : public QObject
14{
15 Q_OBJECT;
16public:
17 TrackBeats(TrackPointer);
18 virtual ~TrackBeats();
19
20
21 int getBeatCount() const;
22
23 void addBeatSample(int);
24 void addBeatSeconds(double);
25 void removeBeatSample(int);
26 void removeBeatSeconds(double);
27 void nudgeSamples(int);
28
29 int findPrevBeatSample(int) const;
30 int findNextBeatSample(int) const;
31 int findBeatOffsetSamples(int, int) const;
32 bool hasBeatsSamples(double, double) const;
33 bool hasBeatsSeconds(double, double) const;
34 double findNextBeatSeconds(double) const;
35 double findPrevBeatSeconds(double) const;
36 QList<int>* findBeatsSamples(int, int) const;
37 double findBeatOffsetSeconds(double, int) const;
38 QList<double>* findBeatsSeconds(double, double) const;
39 QByteArray *serializeToBlob();
40 void unserializeFromBlob(QByteArray *blob);
41
42private:
43 /** Find the Samples Index */
44 int sampleIndex(int) const;
45 /** Sample Rate for this song */
46 int m_iSampleRate;
47 /** Duration of the entire song in seconds */
48 double m_dDuration;
49 /** 10 second index (in samples) */
50 QList<int> m_beatIndex;
51 /** Map of all the beats in samples */
52 QMap<int, int> m_beats;
53 /** Pointer to the related Track */
54 TrackPointer m_track;
55 /** Mutex protecting access to object */
56 mutable QMutex m_qMutex;
57};
58
59#endif
060
=== modified file 'mixxx/src/trackinfoobject.cpp'
--- mixxx/src/trackinfoobject.cpp 2010-11-24 15:20:09 +0000
+++ mixxx/src/trackinfoobject.cpp 2011-02-11 04:42:13 +0000
@@ -341,6 +341,22 @@
341 m_bBpmConfirm = confirm;341 m_bBpmConfirm = confirm;
342}342}
343343
344void TrackInfoObject::setTrackBeats(TrackBeatsPointer beats, bool isDirty)
345{
346 QMutexLocker lock(&m_qMutex);
347 m_pTrackBeatsPointer = beats;
348 if ( isDirty )
349 setDirty(true);
350
351 emit(trackBeatsUpdated(1));
352}
353
354TrackBeatsPointer TrackInfoObject::getTrackBeats() const
355{
356 QMutexLocker lock(&m_qMutex);
357 return m_pTrackBeatsPointer;
358}
359
344bool TrackInfoObject::getHeaderParsed() const360bool TrackInfoObject::getHeaderParsed() const
345{361{
346 QMutexLocker lock(&m_qMutex);362 QMutexLocker lock(&m_qMutex);
347363
=== modified file 'mixxx/src/trackinfoobject.h'
--- mixxx/src/trackinfoobject.h 2010-11-17 21:02:24 +0000
+++ mixxx/src/trackinfoobject.h 2011-02-11 04:42:13 +0000
@@ -40,9 +40,11 @@
40class Cue;40class Cue;
4141
42class TrackInfoObject;42class TrackInfoObject;
43class TrackBeats;
4344
44typedef QSharedPointer<TrackInfoObject> TrackPointer;45typedef QSharedPointer<TrackInfoObject> TrackPointer;
45typedef QWeakPointer<TrackInfoObject> TrackWeakPointer;46typedef QWeakPointer<TrackInfoObject> TrackWeakPointer;
47typedef QSharedPointer<TrackBeats> TrackBeatsPointer;
4648
47#include "segmentation.h"49#include "segmentation.h"
4850
@@ -242,6 +244,12 @@
242 /** Set the track's full file path */244 /** Set the track's full file path */
243 void setLocation(QString location);245 void setLocation(QString location);
244246
247 /** Get the Track's Beats list */
248 TrackBeatsPointer getTrackBeats() const;
249
250 /** Set the Track's Beats */
251 void setTrackBeats(TrackBeatsPointer beats, bool isDirty);
252
245 const Segmentation<QString>* getChordData();253 const Segmentation<QString>* getChordData();
246 void setChordData(Segmentation<QString> cd);254 void setChordData(Segmentation<QString> cd);
247255
@@ -251,6 +259,7 @@
251 signals:259 signals:
252 void wavesummaryUpdated(TrackInfoObject*);260 void wavesummaryUpdated(TrackInfoObject*);
253 void bpmUpdated(double bpm);261 void bpmUpdated(double bpm);
262 void trackBeatsUpdated(int);
254 void ReplayGainUpdated(double replaygain);263 void ReplayGainUpdated(double replaygain);
255 void cuesUpdated();264 void cuesUpdated();
256 void changed();265 void changed();
@@ -370,6 +379,9 @@
370 double m_dVisualResampleRate;379 double m_dVisualResampleRate;
371 Segmentation<QString> m_chordData;380 Segmentation<QString> m_chordData;
372381
382 // Storage for the Track's detected beats
383 TrackBeatsPointer m_pTrackBeatsPointer;
384
373 friend class TrackDAO;385 friend class TrackDAO;
374};386};
375387
376388
=== modified file 'mixxx/src/waveform/waveformrenderbeat.cpp'
--- mixxx/src/waveform/waveformrenderbeat.cpp 2010-11-25 01:06:46 +0000
+++ mixxx/src/waveform/waveformrenderbeat.cpp 2011-02-11 04:42:13 +0000
@@ -14,6 +14,8 @@
14#include "widget/wskincolor.h"14#include "widget/wskincolor.h"
15#include "widget/wwidget.h"15#include "widget/wwidget.h"
16#include "trackinfoobject.h"16#include "trackinfoobject.h"
17#include "trackbeats.h"
18
1719
18WaveformRenderBeat::WaveformRenderBeat(const char* group, WaveformRenderer *parent)20WaveformRenderBeat::WaveformRenderBeat(const char* group, WaveformRenderer *parent)
19 : m_pParent(parent),21 : m_pParent(parent),
@@ -21,6 +23,7 @@
21 m_pBeatFirst(NULL),23 m_pBeatFirst(NULL),
22 m_pTrackSamples(NULL),24 m_pTrackSamples(NULL),
23 m_pTrack(),25 m_pTrack(),
26 m_pTrackBeats(NULL),
24 m_iWidth(0),27 m_iWidth(0),
25 m_iHeight(0),28 m_iHeight(0),
26 m_dBpm(-1),29 m_dBpm(-1),
@@ -38,6 +41,7 @@
3841
39 m_pTrackSamples = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey(group,"track_samples")));42 m_pTrackSamples = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey(group,"track_samples")));
40 slotUpdateTrackSamples(m_pTrackSamples->get());43 slotUpdateTrackSamples(m_pTrackSamples->get());
44
41 connect(m_pTrackSamples, SIGNAL(valueChanged(double)),45 connect(m_pTrackSamples, SIGNAL(valueChanged(double)),
42 this, SLOT(slotUpdateTrackSamples(double)));46 this, SLOT(slotUpdateTrackSamples(double)));
43}47}
@@ -58,6 +62,15 @@
58 m_iNumSamples = (int)samples;62 m_iNumSamples = (int)samples;
59}63}
6064
65void WaveformRenderBeat::slotUpdateTrackBeats(int)
66{
67 m_pTrackBeats = m_pTrack->getTrackBeats();
68 if ( m_pTrackBeats )
69 qDebug() << "WaveformRenderBeat :: beats = " << m_pTrackBeats->getBeatCount();
70 else
71 qDebug() << "No WaveformRenderBeat beats list";
72}
73
61void WaveformRenderBeat::resize(int w, int h) {74void WaveformRenderBeat::resize(int w, int h) {
62 m_iWidth = w;75 m_iWidth = w;
63 m_iHeight = h;76 m_iHeight = h;
@@ -91,7 +104,10 @@
91 m_dSamplesPerDownsample = n;104 m_dSamplesPerDownsample = n;
92 m_dSamplesPerPixel = double(f)/z;105 m_dSamplesPerPixel = double(f)/z;
93106
94 //qDebug() << "WaveformRenderBeat sampleRate " << sampleRate << " samplesPerPixel " << m_dSamplesPerPixel;107 // Reset the tracker beats for each new song
108 m_pTrackBeats = pTrack->getTrackBeats();
109 connect(pTrack.data(), SIGNAL(trackBeatsUpdated(int)), this,
110 SLOT(slotUpdateTrackBeats(int)));
95111
96}112}
97113
@@ -108,9 +124,18 @@
108 colorHighlight = WSkinColor::getCorrectColor(colorHighlight);124 colorHighlight = WSkinColor::getCorrectColor(colorHighlight);
109}125}
110126
111127void WaveformRenderBeat::draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double dPlayPos, double rateAdjust)
112void WaveformRenderBeat::draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double dPlayPos, double rateAdjust) {128{
113 if(m_dBpm == -1 || m_dBpm == 0)129 drawTrackBeat(pPainter, event, buffer, dPlayPos, rateAdjust);
130}
131
132void WaveformRenderBeat::drawTrackBeat(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double dPlayPos, double rateAdjust)
133{
134 QList<int>* beatSamples;
135 int i;
136
137
138 if ( m_pTrackBeats == NULL )
114 return;139 return;
115140
116 slotUpdateTrackSamples(m_pTrackSamples->get());141 slotUpdateTrackSamples(m_pTrackSamples->get());
@@ -137,11 +162,6 @@
137 // Therefore, sample s is a beat if it satisfies s % 60f/b == 0.162 // Therefore, sample s is a beat if it satisfies s % 60f/b == 0.
138 // where s is a /mono/ sample163 // where s is a /mono/ sample
139164
140 // beat length in samples
141 if(m_dBeatLength <= 0) {
142 m_dBeatLength = 60.0 * m_iSampleRate / m_dBpm;
143 }
144
145 double subpixelsPerPixel = m_pParent->getSubpixelsPerPixel()*(1.0+rateAdjust);165 double subpixelsPerPixel = m_pParent->getSubpixelsPerPixel()*(1.0+rateAdjust);
146 const int oversample = (int)subpixelsPerPixel;166 const int oversample = (int)subpixelsPerPixel;
147167
@@ -163,10 +183,18 @@
163183
164 // snap to the first beat184 // snap to the first beat
165 double curPos = ceilf(basePos/m_dBeatLength)*m_dBeatLength;185 double curPos = ceilf(basePos/m_dBeatLength)*m_dBeatLength;
166
167 bool reset = false;186 bool reset = false;
168 for(;curPos <= endPos; curPos+=m_dBeatLength) {187 int curSample = (int)round(curPos);
169 if(curPos < 0)188 int endSample = (int)round(endPos);
189
190
191 beatSamples = m_pTrackBeats->findBeatsSamples(curSample, endSample);
192
193 for(i = 0; i < beatSamples->size(); i++) {
194 curPos = beatSamples->at(i);
195
196
197 if (curPos < 0)
170 continue;198 continue;
171199
172 // i relative to the current play position in subpixels200 // i relative to the current play position in subpixels
@@ -174,7 +202,7 @@
174202
175 // If i is less than 20 subpixels from center, highlight it.203 // If i is less than 20 subpixels from center, highlight it.
176 if(abs(i) < 20) {204 if(abs(i) < 20) {
177 pPainter->setPen(colorHighlight);205 pPainter->setPen(QColor(255,255,255));
178 reset = true;206 reset = true;
179 }207 }
180208
@@ -189,6 +217,7 @@
189 }217 }
190 }218 }
191219
220 delete beatSamples;
192 pPainter->restore();221 pPainter->restore();
193222
194}223}
195224
=== modified file 'mixxx/src/waveform/waveformrenderbeat.h'
--- mixxx/src/waveform/waveformrenderbeat.h 2010-07-15 19:07:16 +0000
+++ mixxx/src/waveform/waveformrenderbeat.h 2011-02-11 04:42:13 +0000
@@ -7,6 +7,7 @@
7#include <QVector>7#include <QVector>
88
9#include "renderobject.h"9#include "renderobject.h"
10#include "trackbeats.h"
1011
11class QDomNode;12class QDomNode;
12class QPainter;13class QPainter;
@@ -17,18 +18,21 @@
17class WaveformRenderer;18class WaveformRenderer;
18class SoundSourceProxy;19class SoundSourceProxy;
1920
21
20class WaveformRenderBeat : public RenderObject {22class WaveformRenderBeat : public RenderObject {
21 Q_OBJECT23 Q_OBJECT
22public:24public:
23 WaveformRenderBeat(const char *group, WaveformRenderer *parent);25 WaveformRenderBeat(const char *group, WaveformRenderer *parent);
24 void resize(int w, int h);26 void resize(int w, int h);
25 void setup(QDomNode node);27 void setup(QDomNode node);
28 void drawTrackBeat(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double playPos, double rateAdjust);
26 void draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double playPos, double rateAdjust);29 void draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double playPos, double rateAdjust);
27 void newTrack(TrackPointer pTrack);30 void newTrack(TrackPointer pTrack);
2831
29public slots:32public slots:
30 void slotUpdateBpm(double bpm);33 void slotUpdateBpm(double bpm);
31 void slotUpdateBeatFirst(double beatfirst);34 void slotUpdateBeatFirst(double beatfirst);
35 void slotUpdateTrackBeats(int);
32 void slotUpdateTrackSamples(double samples);36 void slotUpdateTrackSamples(double samples);
33private:37private:
34 WaveformRenderer *m_pParent;38 WaveformRenderer *m_pParent;
@@ -36,6 +40,7 @@
36 ControlObjectThreadMain *m_pBeatFirst;40 ControlObjectThreadMain *m_pBeatFirst;
37 ControlObjectThreadMain *m_pTrackSamples;41 ControlObjectThreadMain *m_pTrackSamples;
38 TrackPointer m_pTrack;42 TrackPointer m_pTrack;
43 TrackBeatsPointer m_pTrackBeats;
39 int m_iWidth, m_iHeight;44 int m_iWidth, m_iHeight;
40 double m_dBpm;45 double m_dBpm;
41 double m_dBeatFirst;46 double m_dBeatFirst;
@@ -46,6 +51,7 @@
46 double m_dBeatLength;51 double m_dBeatLength;
47 int m_iNumSamples;52 int m_iNumSamples;
48 int m_iSampleRate;53 int m_iSampleRate;
54
49};55};
5056
51#endif57#endif

Subscribers

People subscribed via source and target branches