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: |
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 |
Related bugs: | |
Related blueprints: |
TrackBeats
(Essential)
|
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.
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.
- 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-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 |