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: 833 lines (+477/-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 (+256/-0)
mixxx/src/trackbeats.h (+56/-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+48576@code.launchpad.net

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

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

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.
2531. By Phillip Whelan

FIX: change setTrackBeats call in AnalyserBPM.

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-01-31 23:27:53 +0000
3+++ mixxx/build/depends.py 2011-02-04 06:40:21 +0000
4@@ -520,6 +520,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-04 06:40:21 +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-04 06:40:21 +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-04 06:40:21 +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-04 06:40:21 +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-04 06:40:21 +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-04 06:40:21 +0000
262@@ -0,0 +1,256 @@
263+#include <QtDebug>
264+
265+#include "defs.h"
266+#include "trackbeats.h"
267+#include "trackinfoobject.h"
268+
269+
270+TrackBeats::TrackBeats(TrackPointer tio) : m_qMutex(QMutex::Recursive)
271+{
272+ m_iSampleRate = tio->getSampleRate();
273+}
274+
275+TrackBeats::~TrackBeats()
276+{
277+}
278+
279+void TrackBeats::addBeatSeconds(double beat)
280+{
281+ QMutexLocker lock(&m_qMutex);
282+ addBeatSample((int)round(beat * m_iSampleRate));
283+}
284+
285+bool TrackBeats::hasBeatsSamples(double start, double stop) const
286+{
287+ QMutexLocker lock(&m_qMutex);
288+ QMapIterator<int, int> iter(m_beats);
289+ int index = sampleIndex(start);
290+
291+
292+ if (iter.findNext(m_beatIndex.value(index)))
293+ {
294+ do {
295+ if ((iter.value() >= start) && (iter.value() <= stop))
296+ return true;
297+
298+ iter.next();
299+ } while((iter.hasNext()) && (iter.value() <= stop));
300+ }
301+
302+ return false;
303+}
304+
305+double TrackBeats::findNextBeatSeconds(double beat) const
306+{
307+ QMutexLocker lock(&m_qMutex);
308+ int sample = (int) round(beat * m_iSampleRate);
309+ return findNextBeatSample(sample) / m_iSampleRate;
310+}
311+
312+double TrackBeats::findPrevBeatSeconds(double beat) const
313+{
314+ QMutexLocker lock(&m_qMutex);
315+ int sample = (int) round(beat * m_iSampleRate);
316+ return findPrevBeatSample(sample) / m_iSampleRate;
317+}
318+
319+QList<double>* TrackBeats::findBeatsSeconds(double start, double stop) const
320+{
321+ QMutexLocker lock(&m_qMutex);
322+ QList<double> *ret = new QList<double>;
323+ QList<int>* samples;
324+ int begin = round(start * m_iSampleRate);
325+ int end = round(stop * m_iSampleRate);
326+ int i;
327+
328+
329+ samples = findBeatsSamples(begin, end);
330+ for (i = 0; i < samples->size(); i++)
331+ {
332+ //qDebug() << "Sample:" << samples.at(i) << "SampleRate:" << m_iSampleRate
333+ // << "Seconds:" << ((double)samples.at(i) / (double)m_iSampleRate);
334+
335+ ret->append((double)samples->at(i) / (double)m_iSampleRate);
336+ }
337+
338+ delete samples;
339+ return ret;
340+}
341+
342+int TrackBeats::getBeatCount() const
343+{
344+ QMutexLocker lock(&m_qMutex);
345+ return m_beats.size();
346+}
347+
348+void TrackBeats::dumpBeats()
349+{
350+ QMutexLocker lock(&m_qMutex);
351+ QMapIterator<int, int> iter(m_beats);
352+
353+ do {
354+ iter.next();
355+ qDebug() << "TrackBeat Sample:" << iter.value();
356+
357+ } while(iter.hasNext());
358+}
359+
360+int TrackBeats::sampleIndex(int sample) const
361+{
362+ QMutexLocker lock(&m_qMutex);
363+ return (int) round(sample / (m_iSampleRate * 10));
364+}
365+
366+void TrackBeats::addBeatSample(int sample)
367+{
368+ QMutexLocker lock(&m_qMutex);
369+ int index = sampleIndex(sample);
370+
371+
372+ if ((m_beatIndex.size()-1) < index )
373+ {
374+ int i;
375+
376+
377+ for (i = m_beatIndex.size() - 1; i < index; i++ )
378+ m_beatIndex.append(sample);
379+
380+ m_beatIndex.append(sample);
381+ }
382+
383+ m_beats[sample] = sample;
384+}
385+
386+int TrackBeats::findNextBeatSample(int sample) const
387+{
388+ QMutexLocker lock(&m_qMutex);
389+ QMapIterator<int, int> iter(m_beats);
390+ int index = sampleIndex(sample);
391+
392+
393+ if (m_beatIndex.size() > index)
394+ {
395+ // make sure we dont trip on unindex'ed areas (-1)..
396+ iter.findNext(m_beatIndex.value(index));
397+ do {
398+ iter.next();
399+ } while((iter.hasNext()) && (iter.value() <= sample));
400+
401+ return iter.value();
402+ }
403+
404+ return -1;
405+}
406+
407+int TrackBeats::findBeatOffsetSamples(int sample, int offset) const
408+{
409+ QMutexLocker lock(&m_qMutex);
410+ QMapIterator<int, int> iter(m_beats);
411+ int index = sampleIndex(sample);
412+ int i;
413+
414+
415+ if (m_beatIndex.size() < index)
416+ return -1;
417+
418+
419+ iter.findNext(m_beatIndex.value(index));
420+ do {
421+ iter.next();
422+ } while((iter.hasNext()) && (iter.value() <= sample));
423+
424+ // Backup one just to be before the marker
425+ if ((iter.hasPrevious()) && (iter.value() > sample))
426+ iter.previous();
427+
428+ // Find the offset from the current beat
429+ if ( offset > 0 )
430+ {
431+ for (i = 0; i < offset && iter.hasNext(); i++)
432+ iter.next();
433+ }
434+ else if ( offset < 0 )
435+ {
436+ for (i = offset * -1; i > 0 && iter.hasPrevious(); i--)
437+ iter.previous();
438+ }
439+
440+ return iter.value();
441+}
442+
443+int TrackBeats::findPrevBeatSample(int sample) const
444+{
445+ QMutexLocker lock(&m_qMutex);
446+ QMapIterator<int, int> iter(m_beats);
447+ int index = sampleIndex(sample);
448+
449+ if (m_beatIndex.size() > index)
450+ {
451+ iter.findNext(m_beatIndex.value(index));
452+ do {
453+ iter.previous();
454+ } while((iter.hasPrevious()) && (iter.value() >= sample));
455+
456+ return iter.value();
457+ }
458+
459+ return -1;
460+}
461+
462+QList<int>* TrackBeats::findBeatsSamples(int start, int stop) const
463+{
464+ QMutexLocker lock(&m_qMutex);
465+ QList<int> *ret = new QList<int>;
466+ QMapIterator<int, int> iter(m_beats);
467+ int index = sampleIndex(start);
468+
469+
470+ if (iter.findNext(m_beatIndex.value(index)))
471+ {
472+ do {
473+ if ((iter.value() >= start) && (iter.value() <= stop))
474+ ret->append(iter.value());
475+
476+ iter.next();
477+ } while((iter.hasNext()) && (iter.value() <= stop));
478+ }
479+
480+ return ret;
481+}
482+
483+QByteArray *TrackBeats::serializeToBlob()
484+{
485+ QMutexLocker lock(&m_qMutex);
486+ QByteArray *blob;
487+ int *buffer = new int[getBeatCount()];
488+ int *ptr = buffer;
489+ QMapIterator<int, int> iter(m_beats);
490+
491+
492+ iter.next();
493+
494+ while ( iter.hasNext())
495+ {
496+ *ptr++ = iter.value();
497+ iter.next();
498+ }
499+
500+ blob = new QByteArray((char *)buffer, getBeatCount() * sizeof(int));
501+ delete []buffer;
502+
503+ return blob;
504+}
505+
506+void TrackBeats::unserializeFromBlob(QByteArray *blob)
507+{
508+ QMutexLocker lock(&m_qMutex);
509+ int *ptr = (int *)blob->constData();
510+ int i;
511+
512+
513+ for (i = blob->size() / sizeof(int); --i; ptr++)
514+ {
515+ addBeatSample(*ptr);
516+ }
517+}
518+
519
520=== added file 'mixxx/src/trackbeats.h'
521--- mixxx/src/trackbeats.h 1970-01-01 00:00:00 +0000
522+++ mixxx/src/trackbeats.h 2011-02-04 06:40:21 +0000
523@@ -0,0 +1,56 @@
524+#include <QtDebug>
525+#include <QList>
526+#include <QSharedPointer>
527+#include "trackinfoobject.h"
528+
529+
530+#ifndef __TRACK_BEATS_H__
531+#define __TRACK_BEATS_H__
532+
533+class TrackBeats;
534+typedef QSharedPointer<TrackBeats> TrackBeatsPointer;
535+
536+class TrackBeats : public QObject
537+{
538+ Q_OBJECT;
539+public:
540+ TrackBeats(TrackPointer);
541+ virtual ~TrackBeats();
542+
543+ void addBeatSeconds(double);
544+
545+ double findNextBeatSeconds(double) const;
546+ double findPrevBeatSeconds(double) const;
547+ QList<double>* findBeatsSeconds(double, double) const;
548+
549+ int getBeatCount() const;
550+ void dumpBeats();
551+
552+ void addBeatSample(int);
553+ int findPrevBeatSample(int) const;
554+ int findNextBeatSample(int) const;
555+ int findBeatOffsetSamples(int sample, int offset) const;
556+ bool hasBeatsSamples(double bgnRange, double endRange) const;
557+ QList<int>* findBeatsSamples(int, int) const;
558+ QByteArray *serializeToBlob();
559+ void unserializeFromBlob(QByteArray *blob);
560+
561+private:
562+ /** Find the Samples Index */
563+ int sampleIndex(int) const;
564+
565+ /** Sample Rate for this song */
566+ int m_iSampleRate;
567+ /** Duration of the entire song in seconds */
568+ double m_dDuration;
569+ /** 10 second index (in samples) */
570+ QList<int> m_beatIndex;
571+ /** Map of all the beats in samples */
572+ QMap<int, int> m_beats;
573+ /** Pointer to the related Track */
574+ TrackPointer m_track;
575+ /** Mutex protecting access to object */
576+ mutable QMutex m_qMutex;
577+};
578+
579+#endif
580
581=== modified file 'mixxx/src/trackinfoobject.cpp'
582--- mixxx/src/trackinfoobject.cpp 2010-11-24 15:20:09 +0000
583+++ mixxx/src/trackinfoobject.cpp 2011-02-04 06:40:21 +0000
584@@ -341,6 +341,22 @@
585 m_bBpmConfirm = confirm;
586 }
587
588+void TrackInfoObject::setTrackBeats(TrackBeatsPointer beats, bool isDirty)
589+{
590+ QMutexLocker lock(&m_qMutex);
591+ m_pTrackBeatsPointer = beats;
592+ if ( isDirty )
593+ setDirty(true);
594+
595+ emit(trackBeatsUpdated(1));
596+}
597+
598+TrackBeatsPointer TrackInfoObject::getTrackBeats() const
599+{
600+ QMutexLocker lock(&m_qMutex);
601+ return m_pTrackBeatsPointer;
602+}
603+
604 bool TrackInfoObject::getHeaderParsed() const
605 {
606 QMutexLocker lock(&m_qMutex);
607
608=== modified file 'mixxx/src/trackinfoobject.h'
609--- mixxx/src/trackinfoobject.h 2010-11-17 21:02:24 +0000
610+++ mixxx/src/trackinfoobject.h 2011-02-04 06:40:21 +0000
611@@ -40,9 +40,11 @@
612 class Cue;
613
614 class TrackInfoObject;
615+class TrackBeats;
616
617 typedef QSharedPointer<TrackInfoObject> TrackPointer;
618 typedef QWeakPointer<TrackInfoObject> TrackWeakPointer;
619+typedef QSharedPointer<TrackBeats> TrackBeatsPointer;
620
621 #include "segmentation.h"
622
623@@ -242,6 +244,12 @@
624 /** Set the track's full file path */
625 void setLocation(QString location);
626
627+ /** Get the Track's Beats list */
628+ TrackBeatsPointer getTrackBeats() const;
629+
630+ /** Set the Track's Beats */
631+ void setTrackBeats(TrackBeatsPointer beats, bool isDirty);
632+
633 const Segmentation<QString>* getChordData();
634 void setChordData(Segmentation<QString> cd);
635
636@@ -251,6 +259,7 @@
637 signals:
638 void wavesummaryUpdated(TrackInfoObject*);
639 void bpmUpdated(double bpm);
640+ void trackBeatsUpdated(int);
641 void ReplayGainUpdated(double replaygain);
642 void cuesUpdated();
643 void changed();
644@@ -370,6 +379,9 @@
645 double m_dVisualResampleRate;
646 Segmentation<QString> m_chordData;
647
648+ // Storage for the Track's detected beats
649+ TrackBeatsPointer m_pTrackBeatsPointer;
650+
651 friend class TrackDAO;
652 };
653
654
655=== modified file 'mixxx/src/waveform/waveformrenderbeat.cpp'
656--- mixxx/src/waveform/waveformrenderbeat.cpp 2010-11-25 01:06:46 +0000
657+++ mixxx/src/waveform/waveformrenderbeat.cpp 2011-02-04 06:40:21 +0000
658@@ -14,6 +14,8 @@
659 #include "widget/wskincolor.h"
660 #include "widget/wwidget.h"
661 #include "trackinfoobject.h"
662+#include "trackbeats.h"
663+
664
665 WaveformRenderBeat::WaveformRenderBeat(const char* group, WaveformRenderer *parent)
666 : m_pParent(parent),
667@@ -21,6 +23,7 @@
668 m_pBeatFirst(NULL),
669 m_pTrackSamples(NULL),
670 m_pTrack(),
671+ m_pTrackBeats(NULL),
672 m_iWidth(0),
673 m_iHeight(0),
674 m_dBpm(-1),
675@@ -38,6 +41,7 @@
676
677 m_pTrackSamples = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey(group,"track_samples")));
678 slotUpdateTrackSamples(m_pTrackSamples->get());
679+
680 connect(m_pTrackSamples, SIGNAL(valueChanged(double)),
681 this, SLOT(slotUpdateTrackSamples(double)));
682 }
683@@ -58,6 +62,15 @@
684 m_iNumSamples = (int)samples;
685 }
686
687+void WaveformRenderBeat::slotUpdateTrackBeats(int)
688+{
689+ m_pTrackBeats = m_pTrack->getTrackBeats();
690+ if ( m_pTrackBeats )
691+ qDebug() << "WaveformRenderBeat :: beats = " << m_pTrackBeats->getBeatCount();
692+ else
693+ qDebug() << "No WaveformRenderBeat beats list";
694+}
695+
696 void WaveformRenderBeat::resize(int w, int h) {
697 m_iWidth = w;
698 m_iHeight = h;
699@@ -91,7 +104,10 @@
700 m_dSamplesPerDownsample = n;
701 m_dSamplesPerPixel = double(f)/z;
702
703- //qDebug() << "WaveformRenderBeat sampleRate " << sampleRate << " samplesPerPixel " << m_dSamplesPerPixel;
704+ // Reset the tracker beats for each new song
705+ m_pTrackBeats = pTrack->getTrackBeats();
706+ connect(pTrack.data(), SIGNAL(trackBeatsUpdated(int)), this,
707+ SLOT(slotUpdateTrackBeats(int)));
708
709 }
710
711@@ -108,9 +124,18 @@
712 colorHighlight = WSkinColor::getCorrectColor(colorHighlight);
713 }
714
715-
716-void WaveformRenderBeat::draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double dPlayPos, double rateAdjust) {
717- if(m_dBpm == -1 || m_dBpm == 0)
718+void WaveformRenderBeat::draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double dPlayPos, double rateAdjust)
719+{
720+ drawTrackBeat(pPainter, event, buffer, dPlayPos, rateAdjust);
721+}
722+
723+void WaveformRenderBeat::drawTrackBeat(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double dPlayPos, double rateAdjust)
724+{
725+ QList<int>* beatSamples;
726+ int i;
727+
728+
729+ if ( m_pTrackBeats == NULL )
730 return;
731
732 slotUpdateTrackSamples(m_pTrackSamples->get());
733@@ -137,11 +162,6 @@
734 // Therefore, sample s is a beat if it satisfies s % 60f/b == 0.
735 // where s is a /mono/ sample
736
737- // beat length in samples
738- if(m_dBeatLength <= 0) {
739- m_dBeatLength = 60.0 * m_iSampleRate / m_dBpm;
740- }
741-
742 double subpixelsPerPixel = m_pParent->getSubpixelsPerPixel()*(1.0+rateAdjust);
743 const int oversample = (int)subpixelsPerPixel;
744
745@@ -163,10 +183,18 @@
746
747 // snap to the first beat
748 double curPos = ceilf(basePos/m_dBeatLength)*m_dBeatLength;
749-
750 bool reset = false;
751- for(;curPos <= endPos; curPos+=m_dBeatLength) {
752- if(curPos < 0)
753+ int curSample = (int)round(curPos);
754+ int endSample = (int)round(endPos);
755+
756+
757+ beatSamples = m_pTrackBeats->findBeatsSamples(curSample, endSample);
758+
759+ for(i = 0; i < beatSamples->size(); i++) {
760+ curPos = beatSamples->at(i);
761+
762+
763+ if (curPos < 0)
764 continue;
765
766 // i relative to the current play position in subpixels
767@@ -174,7 +202,7 @@
768
769 // If i is less than 20 subpixels from center, highlight it.
770 if(abs(i) < 20) {
771- pPainter->setPen(colorHighlight);
772+ pPainter->setPen(QColor(255,255,255));
773 reset = true;
774 }
775
776@@ -189,6 +217,7 @@
777 }
778 }
779
780+ delete beatSamples;
781 pPainter->restore();
782
783 }
784
785=== modified file 'mixxx/src/waveform/waveformrenderbeat.h'
786--- mixxx/src/waveform/waveformrenderbeat.h 2010-07-15 19:07:16 +0000
787+++ mixxx/src/waveform/waveformrenderbeat.h 2011-02-04 06:40:21 +0000
788@@ -7,6 +7,7 @@
789 #include <QVector>
790
791 #include "renderobject.h"
792+#include "trackbeats.h"
793
794 class QDomNode;
795 class QPainter;
796@@ -17,18 +18,21 @@
797 class WaveformRenderer;
798 class SoundSourceProxy;
799
800+
801 class WaveformRenderBeat : public RenderObject {
802 Q_OBJECT
803 public:
804 WaveformRenderBeat(const char *group, WaveformRenderer *parent);
805 void resize(int w, int h);
806 void setup(QDomNode node);
807+ void drawTrackBeat(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double playPos, double rateAdjust);
808 void draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double playPos, double rateAdjust);
809 void newTrack(TrackPointer pTrack);
810
811 public slots:
812 void slotUpdateBpm(double bpm);
813 void slotUpdateBeatFirst(double beatfirst);
814+ void slotUpdateTrackBeats(int);
815 void slotUpdateTrackSamples(double samples);
816 private:
817 WaveformRenderer *m_pParent;
818@@ -36,6 +40,7 @@
819 ControlObjectThreadMain *m_pBeatFirst;
820 ControlObjectThreadMain *m_pTrackSamples;
821 TrackPointer m_pTrack;
822+ TrackBeatsPointer m_pTrackBeats;
823 int m_iWidth, m_iHeight;
824 double m_dBpm;
825 double m_dBeatFirst;
826@@ -46,6 +51,7 @@
827 double m_dBeatLength;
828 int m_iNumSamples;
829 int m_iSampleRate;
830+
831 };
832
833 #endif

Subscribers

People subscribed via source and target branches