Merge lp:~mixxxdevelopers/mixxx/features_trackbeats into lp:~mixxxdevelopers/mixxx/trunk
- features_trackbeats
- Merge into trunk
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 |
Related bugs: | |
Related blueprints: |
TrackBeats
(Essential)
|
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.
Commit message
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.
- 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
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 |