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