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
1=== modified file 'mixxx/build/depends.py'
2--- mixxx/build/depends.py 2011-02-09 18:34:37 +0000
3+++ mixxx/build/depends.py 2011-02-11 04:42:13 +0000
4@@ -529,6 +529,7 @@
5
6 "sampleutil.cpp",
7 "trackinfoobject.cpp",
8+ "trackbeats.cpp",
9 "baseplayer.cpp",
10 "basetrackplayer.cpp",
11 "deck.cpp",
12
13=== modified file 'mixxx/res/schema.xml'
14--- mixxx/res/schema.xml 2011-01-06 13:53:48 +0000
15+++ mixxx/res/schema.xml 2011-02-11 04:42:13 +0000
16@@ -228,4 +228,12 @@
17 );
18 </sql>
19 </revision>
20+ <revision version="10" min_compatible="3">
21+ <description>
22+ Add beats column to library table.
23+ </description>
24+ <sql>
25+ ALTER TABLE Library ADD COLUMN beats BLOB;
26+ </sql>
27+ </revision>
28 </schema>
29
30=== modified file 'mixxx/src/analyserbpm.cpp'
31--- mixxx/src/analyserbpm.cpp 2010-10-19 01:35:26 +0000
32+++ mixxx/src/analyserbpm.cpp 2011-02-11 04:42:13 +0000
33@@ -3,6 +3,7 @@
34
35 #include "BPMDetect.h"
36 #include "trackinfoobject.h"
37+#include "trackbeats.h"
38 #include "analyserbpm.h"
39
40
41@@ -29,6 +30,13 @@
42 // defaultrange ? MIN_BPM : m_iMinBpm,
43 // defaultrange ? MAX_BPM : m_iMaxBpm);
44 }
45+ else if ( tio->getBpm() != 0. )
46+ {
47+ // Make sure we have the track beats loaded or generate them.
48+ TrackBeatsPointer pTrackBeats = tio->getTrackBeats();
49+ if ( pTrackBeats == NULL )
50+ doBeats(tio);
51+ }
52 }
53
54
55@@ -58,6 +66,21 @@
56 return BPM;
57 }
58
59+void AnalyserBPM::doBeats(TrackPointer tio)
60+{
61+ TrackBeats *pTrackBeats = new TrackBeats(tio);
62+ float seconds;
63+ float duration = (float)tio->getDuration();
64+ float inc = 60. / tio->getBpm();
65+
66+ for (seconds = 0; seconds < duration; seconds += inc)
67+ {
68+ pTrackBeats->addBeatSeconds(seconds);
69+ }
70+
71+ tio->setTrackBeats(TrackBeatsPointer(pTrackBeats, &QObject::deleteLater), TRUE);
72+}
73+
74 void AnalyserBPM::finalise(TrackPointer tio) {
75 // Check if BPM detection is enabled
76 if(m_pDetector == NULL) {
77@@ -71,6 +94,8 @@
78
79 tio->setBpm(newbpm);
80 tio->setBpmConfirm();
81+
82+ doBeats(tio);
83 //if(pBpmReceiver) {
84 //pBpmReceiver->setComplete(tio, false, bpm);
85 //}
86
87=== modified file 'mixxx/src/analyserbpm.h'
88--- mixxx/src/analyserbpm.h 2010-07-15 19:07:16 +0000
89+++ mixxx/src/analyserbpm.h 2011-02-11 04:42:13 +0000
90@@ -17,6 +17,7 @@
91
92 private:
93 float correctBPM(float BPM, int min, int max, int aboveRange);
94+ void doBeats(TrackPointer tio);
95
96 ConfigObject<ConfigValue> *m_pConfig;
97 soundtouch::BPMDetect *m_pDetector;
98
99=== modified file 'mixxx/src/library/dao/trackdao.cpp'
100--- mixxx/src/library/dao/trackdao.cpp 2010-12-02 22:17:33 +0000
101+++ mixxx/src/library/dao/trackdao.cpp 2011-02-11 04:42:13 +0000
102@@ -3,6 +3,7 @@
103 #include <QtCore>
104 #include <QtSql>
105 #include "trackinfoobject.h"
106+#include "trackbeats.h"
107 #include "library/dao/trackdao.h"
108 #include "audiotagger.h"
109
110@@ -215,16 +216,16 @@
111
112 void TrackDAO::prepareLibraryInsert(QSqlQuery& query) {
113 query.prepare("INSERT INTO library (artist, title, album, year, genre, tracknumber, "
114- "filetype, location, comment, url, duration, rating, key, "
115+ "filetype, location, comment, url, duration, "
116 "bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, "
117 "timesplayed, "
118- "channels, mixxx_deleted, header_parsed) "
119+ "channels, mixxx_deleted, header_parsed, beats) "
120 "VALUES (:artist, "
121 ":title, :album, :year, :genre, :tracknumber, "
122- ":filetype, :location, :comment, :url, :duration, :rating, :key,"
123+ ":filetype, :location, :comment, :url, :duration, "
124 ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :wavesummaryhex, "
125 ":timesplayed, "
126- ":channels, :mixxx_deleted, :header_parsed)");
127+ ":channels, :mixxx_deleted, :header_parsed, :beats)");
128 }
129
130 void TrackDAO::bindTrackToLibraryInsert(QSqlQuery& query, TrackInfoObject* pTrack, int trackLocationId) {
131@@ -249,11 +250,23 @@
132 const QByteArray* pWaveSummary = pTrack->getWaveSummary();
133 if (pWaveSummary) //Avoid null pointer deref
134 query.bindValue(":wavesummaryhex", *pWaveSummary);
135+ else
136+ query.bindValue(":wavesummaryhex", QVariant(QVariant::ByteArray));
137 query.bindValue(":timesplayed", pTrack->getTimesPlayed());
138 //query.bindValue(":datetime_added", pTrack->getDateAdded());
139 query.bindValue(":channels", pTrack->getChannels());
140 query.bindValue(":mixxx_deleted", 0);
141 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0);
142+ const QByteArray *pBeatsBlob = NULL;
143+ TrackBeatsPointer pBeats = pTrack->getTrackBeats();
144+ if ( pBeats )
145+ pBeatsBlob = pBeats->serializeToBlob();
146+
147+ if ( pBeatsBlob )
148+ query.bindValue(":beats", *pBeatsBlob);
149+ else
150+ query.bindValue(":beats", QVariant(QVariant::ByteArray));
151+
152 }
153
154 void TrackDAO::addTracks(QList<TrackInfoObject*> tracksToAdd) {
155@@ -306,7 +319,8 @@
156 bindTrackToLibraryInsert(query, pTrack, pTrack->getId());
157
158 if (!query.exec()) {
159- qDebug() << "Failed to INSERT new track into library"
160+ qDebug() << "Failed to INSERT new track into library:"
161+ << pTrack->getFilename()
162 << __FILE__ << __LINE__ << query.lastError();
163 m_database.rollback();
164 return;
165@@ -550,6 +564,7 @@
166 bool header_parsed = query.value(query.record().indexOf("header_parsed")).toBool();
167
168 TrackInfoObject* track = new TrackInfoObject(location, false);
169+ TrackPointer pTrack = TrackPointer(track, this->deleteTrack);
170
171 // TIO already stats the file to see if it exists, what its length is,
172 // etc. So don't bother setting it.
173@@ -575,6 +590,19 @@
174 track->setReplayGain(replaygain.toFloat());
175 track->setWaveSummary(wavesummaryhex, false);
176 delete wavesummaryhex;
177+
178+ // TrackBeats needs the samplerate for it's constructor
179+ // so we do it all right here.
180+ QByteArray* beatsblob = new QByteArray(
181+ query.value(query.record().indexOf("beats")).toByteArray());
182+ if ( beatsblob->size() > 1 )
183+ {
184+ TrackBeats* pTrackBeats = new TrackBeats(pTrack);
185+ pTrackBeats->unserializeFromBlob(beatsblob);
186+ track->setTrackBeats(TrackBeatsPointer(pTrackBeats, &QObject::deleteLater), false);
187+ }
188+ delete beatsblob;
189+
190 track->setTimesPlayed(timesplayed);
191 track->setPlayed(played);
192 track->setChannels(channels);
193@@ -593,8 +621,6 @@
194 connect(track, SIGNAL(save()),
195 this, SLOT(slotTrackSave()));
196
197- TrackPointer pTrack = TrackPointer(track, this->deleteTrack);
198-
199 // Automatic conversion to a weak pointer
200 m_tracks[trackId] = pTrack;
201 m_trackCache.insert(trackId, new TrackPointer(pTrack));
202@@ -647,7 +673,17 @@
203 time.start();
204 QSqlQuery query(m_database);
205
206- 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));
207+ query.prepare(
208+ "SELECT library.id, artist, title, album, year, genre, tracknumber, "
209+ "filetype, rating, key, track_locations.location as location, "
210+ "track_locations.filesize as filesize, comment, url, duration, bitrate, "
211+ "samplerate, cuepoint, bpm, replaygain, wavesummaryhex, channels, "
212+ "header_parsed, timesplayed, played, beats "
213+ "FROM Library "
214+ "INNER JOIN track_locations "
215+ "ON library.location = track_locations.id "
216+ "WHERE library.id=" + QString("%1").arg(id)
217+ );
218
219 TrackPointer pTrack;
220
221@@ -686,7 +722,8 @@
222 "bitrate=:bitrate, samplerate=:samplerate, cuepoint=:cuepoint, "
223 "bpm=:bpm, replaygain=:replaygain, wavesummaryhex=:wavesummaryhex, "
224 "timesplayed=:timesplayed, played=:played, "
225- "channels=:channels, header_parsed=:header_parsed "
226+ "channels=:channels, header_parsed=:header_parsed, "
227+ "beats=:beats "
228 "WHERE id="+QString("%1").arg(trackId));
229 query.bindValue(":artist", pTrack->getArtist());
230 query.bindValue(":title", pTrack->getTitle());
231@@ -713,6 +750,13 @@
232 query.bindValue(":channels", pTrack->getChannels());
233 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0);
234 //query.bindValue(":location", pTrack->getLocation());
235+ TrackBeatsPointer pBeats = pTrack->getTrackBeats();
236+ if ( pBeats )
237+ {
238+ const QByteArray *pBeatsBlob = pBeats->serializeToBlob();
239+ if ( pBeatsBlob )
240+ query.bindValue(":beats", *pBeatsBlob);
241+ }
242
243 if (!query.exec()) {
244 qDebug() << query.lastError();
245
246=== modified file 'mixxx/src/library/trackcollection.cpp'
247--- mixxx/src/library/trackcollection.cpp 2010-12-22 21:23:09 +0000
248+++ mixxx/src/library/trackcollection.cpp 2011-02-11 04:42:13 +0000
249@@ -68,7 +68,7 @@
250 return false;
251 }
252
253- int requiredSchemaVersion = 9;
254+ int requiredSchemaVersion = 10;
255 if (!SchemaManager::upgradeToSchemaVersion(m_pConfig, m_db,
256 requiredSchemaVersion)) {
257 QMessageBox::warning(0, tr("Cannot upgrade database schema"),
258
259=== added file 'mixxx/src/trackbeats.cpp'
260--- mixxx/src/trackbeats.cpp 1970-01-01 00:00:00 +0000
261+++ mixxx/src/trackbeats.cpp 2011-02-11 04:42:13 +0000
262@@ -0,0 +1,349 @@
263+#include <QtDebug>
264+
265+#include "defs.h"
266+#include "trackinfoobject.h"
267+#include "trackbeats.h"
268+
269+#define TRACKBEATS_INDEX_RANGE 20
270+
271+
272+TrackBeats::TrackBeats(TrackPointer tio) : m_qMutex(QMutex::Recursive)
273+{
274+ m_iSampleRate = tio->getSampleRate();
275+}
276+
277+TrackBeats::~TrackBeats()
278+{
279+}
280+
281+/**
282+ * Private function that returns the sample index. This is an index into
283+ * m_beatIndex which holds the first sample offset that holds a beat for a
284+ * certain range of sample offsets.
285+ */
286+int TrackBeats::sampleIndex(int sample) const
287+{
288+ QMutexLocker lock(&m_qMutex);
289+ return (int) round(sample / (m_iSampleRate * TRACKBEATS_INDEX_RANGE));
290+}
291+
292+/**
293+ * Get the count of beats stored.
294+ */
295+int TrackBeats::getBeatCount() const
296+{
297+ QMutexLocker lock(&m_qMutex);
298+ return m_beats.size();
299+}
300+
301+/**
302+ * Add a beat at 'sample' sample.
303+ */
304+void TrackBeats::addBeatSample(int sample)
305+{
306+ QMutexLocker lock(&m_qMutex);
307+ int index = sampleIndex(sample);
308+ int i;
309+
310+
311+ if ((m_beatIndex.size()-1) < index )
312+ {
313+ for (i = m_beatIndex.size() - 1; i <= index; i++ )
314+ m_beatIndex.append(sample);
315+ }
316+ else
317+ {
318+ for (i = index; m_beatIndex.at(i) >= sample && i >= 0; i-- ) {
319+ m_beatIndex[index] = sample;
320+ }
321+ }
322+
323+ m_beats[sample] = sample;
324+}
325+
326+/**
327+ * Remove the beat with the sample offset specified.
328+ */
329+void TrackBeats::removeBeatSample(int sample)
330+{
331+ QMutexLocker lock(&m_qMutex);
332+ int index = sampleIndex(sample);
333+ int i;
334+ int newSample = -1;
335+
336+
337+ m_beats.remove(sample);
338+
339+ if ( ! m_beatIndex.contains(sample))
340+ return;
341+
342+ if ((index+1) < m_beatIndex.size()) {
343+ newSample = m_beatIndex.at(index+1);
344+
345+ for (i = index; i >= 0 && m_beatIndex.at(i) >= sample; i--) {
346+ if ( m_beatIndex.at(i) == sample )
347+ m_beatIndex[i] = newSample;
348+ }
349+ }
350+ else {
351+ for (i = index; i >= 0 && m_beatIndex.at(i) >= sample; i--) {
352+ if ( m_beatIndex.at(i) == sample )
353+ m_beatIndex.removeAt(i);
354+ }
355+ }
356+
357+ if ( m_beatIndex.contains(sample) )
358+ qDebug() << "Dangling Indexes:" << m_beatIndex.count(sample);
359+}
360+
361+/**
362+ * Add a beat at 'beat' seconds.
363+ */
364+void TrackBeats::addBeatSeconds(double beat)
365+{
366+ QMutexLocker lock(&m_qMutex);
367+ addBeatSample((int)round(beat * m_iSampleRate));
368+}
369+
370+
371+/**
372+ * Remove a beat at 'beat' seconds.
373+ */
374+void TrackBeats::removeBeatSeconds(double beat)
375+{
376+ QMutexLocker lock(&m_qMutex);
377+ removeBeatSample((int)round(beat * m_iSampleRate));
378+}
379+
380+/**
381+ * Find the Next beat starting from offset sample.
382+ */
383+int TrackBeats::findNextBeatSample(int sample) const
384+{
385+ QMutexLocker lock(&m_qMutex);
386+ QMapIterator<int, int> iter(m_beats);
387+ int index = sampleIndex(sample);
388+
389+
390+ if (m_beatIndex.size() > index)
391+ {
392+ iter.findNext(m_beatIndex.value(index));
393+ do {
394+ iter.next();
395+ } while((iter.hasNext()) && (iter.value() <= sample));
396+
397+ return iter.value();
398+ }
399+
400+ return -1;
401+}
402+
403+/**
404+ * Find the Previous beat starting from offset sample.
405+ */
406+int TrackBeats::findPrevBeatSample(int sample) const
407+{
408+ QMutexLocker lock(&m_qMutex);
409+ QMapIterator<int, int> iter(m_beats);
410+ int index = sampleIndex(sample);
411+
412+ if (m_beatIndex.size() > index)
413+ {
414+ iter.findNext(m_beatIndex.value(index));
415+ do {
416+ iter.previous();
417+ } while((iter.hasPrevious()) && (iter.value() >= sample));
418+
419+ return iter.value();
420+ }
421+
422+ return -1;
423+}
424+
425+/**
426+ * Find the Nth beat (offset) from sample .
427+ */
428+int TrackBeats::findBeatOffsetSamples(int sample, int offset) const
429+{
430+ QMutexLocker lock(&m_qMutex);
431+ QMapIterator<int, int> iter(m_beats);
432+ int index = sampleIndex(sample);
433+ int i;
434+
435+
436+ if (m_beatIndex.size() < index)
437+ return -1;
438+
439+
440+ iter.findNext(m_beatIndex.value(index));
441+ do {
442+ iter.next();
443+ } while((iter.hasNext()) && (iter.value() <= sample));
444+
445+ // Backup one just to be before the marker
446+ if ((iter.hasPrevious()) && (iter.value() > sample))
447+ iter.previous();
448+
449+ // Find the offset from the current beat
450+ if ( offset > 0 )
451+ {
452+ for (i = 0; i < offset && iter.hasNext(); i++)
453+ iter.next();
454+ }
455+ else if ( offset < 0 )
456+ {
457+ for (i = offset * -1; i > 0 && iter.hasPrevious(); i--)
458+ iter.previous();
459+ }
460+
461+ return iter.value();
462+}
463+
464+/**
465+ * Return TRUE if there is any beats between start and stop (in samples)
466+ */
467+bool TrackBeats::hasBeatsSamples(double start, double stop) const
468+{
469+ QMutexLocker lock(&m_qMutex);
470+ QMapIterator<int, int> iter(m_beats);
471+ int index = sampleIndex(start);
472+
473+
474+ if (iter.findNext(m_beatIndex.value(index)))
475+ {
476+ do {
477+ if ((iter.value() >= start) && (iter.value() <= stop))
478+ return true;
479+
480+ iter.next();
481+ } while((iter.hasNext()) && (iter.value() <= stop));
482+ }
483+
484+ return false;
485+}
486+
487+/**
488+ * Find and return a list of all beats between sample offset start and stop.
489+ */
490+QList<int>* TrackBeats::findBeatsSamples(int start, int stop) const
491+{
492+ QMutexLocker lock(&m_qMutex);
493+ QList<int> *ret = new QList<int>;
494+ QMapIterator<int, int> iter(m_beats);
495+ int index = sampleIndex(start);
496+
497+
498+ if (iter.findNext(m_beatIndex.value(index)))
499+ {
500+ do {
501+ if ((iter.value() >= start) && (iter.value() <= stop))
502+ ret->append(iter.value());
503+
504+ iter.next();
505+ } while((iter.hasNext()) && (iter.value() <= stop));
506+ }
507+
508+ return ret;
509+}
510+
511+/**
512+ * Find the Next beat starting from an offset in seconds.
513+ */
514+double TrackBeats::findNextBeatSeconds(double beat) const
515+{
516+ QMutexLocker lock(&m_qMutex);
517+ int sample = (int) round(beat * m_iSampleRate);
518+ return findNextBeatSample(sample) / m_iSampleRate;
519+}
520+
521+/**
522+ * Find the Previous beat starting from an offset in seconds.
523+ */
524+double TrackBeats::findPrevBeatSeconds(double beat) const
525+{
526+ QMutexLocker lock(&m_qMutex);
527+ int sample = (int) round(beat * m_iSampleRate);
528+ return findPrevBeatSample(sample) / m_iSampleRate;
529+}
530+
531+/**
532+ * Find the Nth beat (offset) from offset seconds in seconds.
533+ */
534+double TrackBeats::findBeatOffsetSeconds(double seconds, int offset) const
535+{
536+ QMutexLocker lock(&m_qMutex);
537+ int bgn = round(seconds * m_iSampleRate);
538+ return ((double)findBeatOffsetSamples(bgn, offset) / m_iSampleRate);
539+}
540+
541+/**
542+ * Return TRUE if there is any beats between start and stop (in seconds)
543+ */
544+bool TrackBeats::hasBeatsSeconds(double start, double stop) const
545+{
546+ QMutexLocker lock(&m_qMutex);
547+ int bgn = round(start * m_iSampleRate);
548+ int end = round(stop * m_iSampleRate);
549+
550+ return hasBeatsSamples(bgn, end);
551+}
552+
553+/**
554+ * Find and return a list of all beats between seconds start and stop.
555+ */
556+QList<double>* TrackBeats::findBeatsSeconds(double start, double stop) const
557+{
558+ QMutexLocker lock(&m_qMutex);
559+ QList<double> *ret = new QList<double>;
560+ QList<int>* samples;
561+ int begin = round(start * m_iSampleRate);
562+ int end = round(stop * m_iSampleRate);
563+ int i;
564+
565+
566+ samples = findBeatsSamples(begin, end);
567+ for (i = 0; i < samples->size(); i++)
568+ {
569+ ret->append((double)samples->at(i) / (double)m_iSampleRate);
570+ }
571+
572+ delete samples;
573+ return ret;
574+}
575+
576+QByteArray *TrackBeats::serializeToBlob()
577+{
578+ QMutexLocker lock(&m_qMutex);
579+ QByteArray *blob;
580+ int *buffer = new int[getBeatCount()];
581+ int *ptr = buffer;
582+ QMapIterator<int, int> iter(m_beats);
583+
584+
585+ iter.next();
586+
587+ while ( iter.hasNext())
588+ {
589+ *ptr++ = iter.value();
590+ iter.next();
591+ }
592+
593+ blob = new QByteArray((char *)buffer, getBeatCount() * sizeof(int));
594+ delete []buffer;
595+
596+ return blob;
597+}
598+
599+void TrackBeats::unserializeFromBlob(QByteArray *blob)
600+{
601+ QMutexLocker lock(&m_qMutex);
602+ int *ptr = (int *)blob->constData();
603+ int i;
604+
605+
606+ for (i = blob->size() / sizeof(int); --i; ptr++)
607+ {
608+ addBeatSample(*ptr);
609+ }
610+}
611+
612
613=== added file 'mixxx/src/trackbeats.h'
614--- mixxx/src/trackbeats.h 1970-01-01 00:00:00 +0000
615+++ mixxx/src/trackbeats.h 2011-02-11 04:42:13 +0000
616@@ -0,0 +1,59 @@
617+#include <QtDebug>
618+#include <QList>
619+#include <QSharedPointer>
620+#include "trackinfoobject.h"
621+
622+
623+#ifndef __TRACK_BEATS_H__
624+#define __TRACK_BEATS_H__
625+
626+class TrackBeats;
627+typedef QSharedPointer<TrackBeats> TrackBeatsPointer;
628+
629+class TrackBeats : public QObject
630+{
631+ Q_OBJECT;
632+public:
633+ TrackBeats(TrackPointer);
634+ virtual ~TrackBeats();
635+
636+
637+ int getBeatCount() const;
638+
639+ void addBeatSample(int);
640+ void addBeatSeconds(double);
641+ void removeBeatSample(int);
642+ void removeBeatSeconds(double);
643+ void nudgeSamples(int);
644+
645+ int findPrevBeatSample(int) const;
646+ int findNextBeatSample(int) const;
647+ int findBeatOffsetSamples(int, int) const;
648+ bool hasBeatsSamples(double, double) const;
649+ bool hasBeatsSeconds(double, double) const;
650+ double findNextBeatSeconds(double) const;
651+ double findPrevBeatSeconds(double) const;
652+ QList<int>* findBeatsSamples(int, int) const;
653+ double findBeatOffsetSeconds(double, int) const;
654+ QList<double>* findBeatsSeconds(double, double) const;
655+ QByteArray *serializeToBlob();
656+ void unserializeFromBlob(QByteArray *blob);
657+
658+private:
659+ /** Find the Samples Index */
660+ int sampleIndex(int) const;
661+ /** Sample Rate for this song */
662+ int m_iSampleRate;
663+ /** Duration of the entire song in seconds */
664+ double m_dDuration;
665+ /** 10 second index (in samples) */
666+ QList<int> m_beatIndex;
667+ /** Map of all the beats in samples */
668+ QMap<int, int> m_beats;
669+ /** Pointer to the related Track */
670+ TrackPointer m_track;
671+ /** Mutex protecting access to object */
672+ mutable QMutex m_qMutex;
673+};
674+
675+#endif
676
677=== modified file 'mixxx/src/trackinfoobject.cpp'
678--- mixxx/src/trackinfoobject.cpp 2010-11-24 15:20:09 +0000
679+++ mixxx/src/trackinfoobject.cpp 2011-02-11 04:42:13 +0000
680@@ -341,6 +341,22 @@
681 m_bBpmConfirm = confirm;
682 }
683
684+void TrackInfoObject::setTrackBeats(TrackBeatsPointer beats, bool isDirty)
685+{
686+ QMutexLocker lock(&m_qMutex);
687+ m_pTrackBeatsPointer = beats;
688+ if ( isDirty )
689+ setDirty(true);
690+
691+ emit(trackBeatsUpdated(1));
692+}
693+
694+TrackBeatsPointer TrackInfoObject::getTrackBeats() const
695+{
696+ QMutexLocker lock(&m_qMutex);
697+ return m_pTrackBeatsPointer;
698+}
699+
700 bool TrackInfoObject::getHeaderParsed() const
701 {
702 QMutexLocker lock(&m_qMutex);
703
704=== modified file 'mixxx/src/trackinfoobject.h'
705--- mixxx/src/trackinfoobject.h 2010-11-17 21:02:24 +0000
706+++ mixxx/src/trackinfoobject.h 2011-02-11 04:42:13 +0000
707@@ -40,9 +40,11 @@
708 class Cue;
709
710 class TrackInfoObject;
711+class TrackBeats;
712
713 typedef QSharedPointer<TrackInfoObject> TrackPointer;
714 typedef QWeakPointer<TrackInfoObject> TrackWeakPointer;
715+typedef QSharedPointer<TrackBeats> TrackBeatsPointer;
716
717 #include "segmentation.h"
718
719@@ -242,6 +244,12 @@
720 /** Set the track's full file path */
721 void setLocation(QString location);
722
723+ /** Get the Track's Beats list */
724+ TrackBeatsPointer getTrackBeats() const;
725+
726+ /** Set the Track's Beats */
727+ void setTrackBeats(TrackBeatsPointer beats, bool isDirty);
728+
729 const Segmentation<QString>* getChordData();
730 void setChordData(Segmentation<QString> cd);
731
732@@ -251,6 +259,7 @@
733 signals:
734 void wavesummaryUpdated(TrackInfoObject*);
735 void bpmUpdated(double bpm);
736+ void trackBeatsUpdated(int);
737 void ReplayGainUpdated(double replaygain);
738 void cuesUpdated();
739 void changed();
740@@ -370,6 +379,9 @@
741 double m_dVisualResampleRate;
742 Segmentation<QString> m_chordData;
743
744+ // Storage for the Track's detected beats
745+ TrackBeatsPointer m_pTrackBeatsPointer;
746+
747 friend class TrackDAO;
748 };
749
750
751=== modified file 'mixxx/src/waveform/waveformrenderbeat.cpp'
752--- mixxx/src/waveform/waveformrenderbeat.cpp 2010-11-25 01:06:46 +0000
753+++ mixxx/src/waveform/waveformrenderbeat.cpp 2011-02-11 04:42:13 +0000
754@@ -14,6 +14,8 @@
755 #include "widget/wskincolor.h"
756 #include "widget/wwidget.h"
757 #include "trackinfoobject.h"
758+#include "trackbeats.h"
759+
760
761 WaveformRenderBeat::WaveformRenderBeat(const char* group, WaveformRenderer *parent)
762 : m_pParent(parent),
763@@ -21,6 +23,7 @@
764 m_pBeatFirst(NULL),
765 m_pTrackSamples(NULL),
766 m_pTrack(),
767+ m_pTrackBeats(NULL),
768 m_iWidth(0),
769 m_iHeight(0),
770 m_dBpm(-1),
771@@ -38,6 +41,7 @@
772
773 m_pTrackSamples = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey(group,"track_samples")));
774 slotUpdateTrackSamples(m_pTrackSamples->get());
775+
776 connect(m_pTrackSamples, SIGNAL(valueChanged(double)),
777 this, SLOT(slotUpdateTrackSamples(double)));
778 }
779@@ -58,6 +62,15 @@
780 m_iNumSamples = (int)samples;
781 }
782
783+void WaveformRenderBeat::slotUpdateTrackBeats(int)
784+{
785+ m_pTrackBeats = m_pTrack->getTrackBeats();
786+ if ( m_pTrackBeats )
787+ qDebug() << "WaveformRenderBeat :: beats = " << m_pTrackBeats->getBeatCount();
788+ else
789+ qDebug() << "No WaveformRenderBeat beats list";
790+}
791+
792 void WaveformRenderBeat::resize(int w, int h) {
793 m_iWidth = w;
794 m_iHeight = h;
795@@ -91,7 +104,10 @@
796 m_dSamplesPerDownsample = n;
797 m_dSamplesPerPixel = double(f)/z;
798
799- //qDebug() << "WaveformRenderBeat sampleRate " << sampleRate << " samplesPerPixel " << m_dSamplesPerPixel;
800+ // Reset the tracker beats for each new song
801+ m_pTrackBeats = pTrack->getTrackBeats();
802+ connect(pTrack.data(), SIGNAL(trackBeatsUpdated(int)), this,
803+ SLOT(slotUpdateTrackBeats(int)));
804
805 }
806
807@@ -108,9 +124,18 @@
808 colorHighlight = WSkinColor::getCorrectColor(colorHighlight);
809 }
810
811-
812-void WaveformRenderBeat::draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double dPlayPos, double rateAdjust) {
813- if(m_dBpm == -1 || m_dBpm == 0)
814+void WaveformRenderBeat::draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double dPlayPos, double rateAdjust)
815+{
816+ drawTrackBeat(pPainter, event, buffer, dPlayPos, rateAdjust);
817+}
818+
819+void WaveformRenderBeat::drawTrackBeat(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double dPlayPos, double rateAdjust)
820+{
821+ QList<int>* beatSamples;
822+ int i;
823+
824+
825+ if ( m_pTrackBeats == NULL )
826 return;
827
828 slotUpdateTrackSamples(m_pTrackSamples->get());
829@@ -137,11 +162,6 @@
830 // Therefore, sample s is a beat if it satisfies s % 60f/b == 0.
831 // where s is a /mono/ sample
832
833- // beat length in samples
834- if(m_dBeatLength <= 0) {
835- m_dBeatLength = 60.0 * m_iSampleRate / m_dBpm;
836- }
837-
838 double subpixelsPerPixel = m_pParent->getSubpixelsPerPixel()*(1.0+rateAdjust);
839 const int oversample = (int)subpixelsPerPixel;
840
841@@ -163,10 +183,18 @@
842
843 // snap to the first beat
844 double curPos = ceilf(basePos/m_dBeatLength)*m_dBeatLength;
845-
846 bool reset = false;
847- for(;curPos <= endPos; curPos+=m_dBeatLength) {
848- if(curPos < 0)
849+ int curSample = (int)round(curPos);
850+ int endSample = (int)round(endPos);
851+
852+
853+ beatSamples = m_pTrackBeats->findBeatsSamples(curSample, endSample);
854+
855+ for(i = 0; i < beatSamples->size(); i++) {
856+ curPos = beatSamples->at(i);
857+
858+
859+ if (curPos < 0)
860 continue;
861
862 // i relative to the current play position in subpixels
863@@ -174,7 +202,7 @@
864
865 // If i is less than 20 subpixels from center, highlight it.
866 if(abs(i) < 20) {
867- pPainter->setPen(colorHighlight);
868+ pPainter->setPen(QColor(255,255,255));
869 reset = true;
870 }
871
872@@ -189,6 +217,7 @@
873 }
874 }
875
876+ delete beatSamples;
877 pPainter->restore();
878
879 }
880
881=== modified file 'mixxx/src/waveform/waveformrenderbeat.h'
882--- mixxx/src/waveform/waveformrenderbeat.h 2010-07-15 19:07:16 +0000
883+++ mixxx/src/waveform/waveformrenderbeat.h 2011-02-11 04:42:13 +0000
884@@ -7,6 +7,7 @@
885 #include <QVector>
886
887 #include "renderobject.h"
888+#include "trackbeats.h"
889
890 class QDomNode;
891 class QPainter;
892@@ -17,18 +18,21 @@
893 class WaveformRenderer;
894 class SoundSourceProxy;
895
896+
897 class WaveformRenderBeat : public RenderObject {
898 Q_OBJECT
899 public:
900 WaveformRenderBeat(const char *group, WaveformRenderer *parent);
901 void resize(int w, int h);
902 void setup(QDomNode node);
903+ void drawTrackBeat(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double playPos, double rateAdjust);
904 void draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double playPos, double rateAdjust);
905 void newTrack(TrackPointer pTrack);
906
907 public slots:
908 void slotUpdateBpm(double bpm);
909 void slotUpdateBeatFirst(double beatfirst);
910+ void slotUpdateTrackBeats(int);
911 void slotUpdateTrackSamples(double samples);
912 private:
913 WaveformRenderer *m_pParent;
914@@ -36,6 +40,7 @@
915 ControlObjectThreadMain *m_pBeatFirst;
916 ControlObjectThreadMain *m_pTrackSamples;
917 TrackPointer m_pTrack;
918+ TrackBeatsPointer m_pTrackBeats;
919 int m_iWidth, m_iHeight;
920 double m_dBpm;
921 double m_dBeatFirst;
922@@ -46,6 +51,7 @@
923 double m_dBeatLength;
924 int m_iNumSamples;
925 int m_iSampleRate;
926+
927 };
928
929 #endif

Subscribers

People subscribed via source and target branches