Merge lp:~mixxxdevelopers/mixxx/features_trackbeats into lp:~mixxxdevelopers/mixxx/trunk
- features_trackbeats
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 2654 |
Proposed branch: | lp:~mixxxdevelopers/mixxx/features_trackbeats |
Merge into: | lp:~mixxxdevelopers/mixxx/trunk |
Diff against target: |
1529 lines (+960/-92) 20 files modified
mixxx/build/depends.py (+4/-0) mixxx/res/schema.xml (+9/-0) mixxx/src/analyserbpm.cpp (+12/-7) mixxx/src/analyserbpm.h (+0/-2) mixxx/src/basetrackplayer.cpp (+2/-1) mixxx/src/library/dao/trackdao.cpp (+79/-16) mixxx/src/library/trackcollection.cpp (+1/-1) mixxx/src/track/beatfactory.cpp (+43/-0) mixxx/src/track/beatfactory.h (+19/-0) mixxx/src/track/beatgrid.cpp (+186/-0) mixxx/src/track/beatgrid.h (+75/-0) mixxx/src/track/beatmatrix.cpp (+271/-0) mixxx/src/track/beatmatrix.h (+67/-0) mixxx/src/track/beats.h (+106/-0) mixxx/src/trackinfoobject.cpp (+37/-0) mixxx/src/trackinfoobject.h (+14/-1) mixxx/src/waveform/waveformrenderbeat.cpp (+18/-45) mixxx/src/waveform/waveformrenderbeat.h (+7/-12) mixxx/src/waveform/waveformrenderer.cpp (+9/-6) mixxx/src/waveform/waveformrenderer.h (+1/-1) |
To merge this branch: | bzr merge lp:~mixxxdevelopers/mixxx/features_trackbeats |
Related bugs: | |
Related blueprints: |
TrackBeats
(Essential)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Albert Santoni | Approve | ||
RJ Skerry-Ryan | Approve | ||
Review via email: mp+49371@code.launchpad.net |
This proposal supersedes 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.
- 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.
RJ Skerry-Ryan (rryan) wrote : | # |
Oops, accidentally approved. Ignore that.
- 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/
Albert Santoni (gamegod) wrote : | # |
- Fix the commented pointer leak in TrackDAO:
- What was the final verdict on how adding these large blobs to the database will impact subsequent query performance? It would be good to document this since it likely won't be the last time we have to store blobs in the database. (eg. cover art)
- Probably wouldn't hurt to make those version strings in BeatFactory constants defined in the header.
- For the BeatGrid byte array, would it make sense to pack/unpack the blob as a C-style struct? You've got code like this:
+ pBuffer[0] = m_dBpm;
+ pBuffer[1] = m_dFirstBeat;
... and your schema for the format of this buffer is spread out all over the place. For example, BeatGrid:
+ QByteArray* pByteArray = new QByteArray(
and similarly, readByteArray() has an assert with the same hardcoded length. Using a C-style struct will probably make that code less fragile (eg. sizeof(
- void BeatGrid:
:)
- BeatGrid:
- The naming of these classes is very confusing. Matrix and grid are synonyms, so I'm barring you from using those words to describe different classes. Names that may be more clear could be "ExtrapolatedBe
Otherwise, code looks pretty good. I hope it works. :)
I'll do some testing next time I have a chance and take another pass at the code.
Thanks guys!
Albert
- 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).
Phillip Whelan (pwhelan) wrote : | # |
> - BeatGrid:
With the extrapolated beat grid (currently called 'BeatGrid') this will simply return a negative sample offset. Caller beware.
As for the custom beat grid (currently called 'BeatMatrix'), it should just return a simple 0.
- 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
Albert Santoni (gamegod) wrote : | # |
Phil and I fixed the remaining stuff from my review. Approve and merged into trunk tonight.
Preview Diff
1 | === modified file 'mixxx/build/depends.py' |
2 | --- mixxx/build/depends.py 2011-03-04 09:09:44 +0000 |
3 | +++ mixxx/build/depends.py 2011-03-07 05:53:37 +0000 |
4 | @@ -540,6 +540,10 @@ |
5 | |
6 | "sampleutil.cpp", |
7 | "trackinfoobject.cpp", |
8 | + "track/beatgrid.cpp", |
9 | + "track/beatmatrix.cpp", |
10 | + "track/beatfactory.cpp", |
11 | + |
12 | "baseplayer.cpp", |
13 | "basetrackplayer.cpp", |
14 | "deck.cpp", |
15 | |
16 | === modified file 'mixxx/res/schema.xml' |
17 | --- mixxx/res/schema.xml 2011-02-21 07:01:11 +0000 |
18 | +++ mixxx/res/schema.xml 2011-03-07 05:53:37 +0000 |
19 | @@ -266,4 +266,13 @@ |
20 | ); |
21 | </sql> |
22 | </revision> |
23 | + <revision version="12" min_compatible="3"> |
24 | + <description> |
25 | + Add beats column to library table. |
26 | + </description> |
27 | + <sql> |
28 | + ALTER TABLE Library ADD COLUMN beats BLOB; |
29 | + ALTER TABLE Library ADD COLUMN beats_version TEXT; |
30 | + </sql> |
31 | + </revision> |
32 | </schema> |
33 | |
34 | === modified file 'mixxx/src/analyserbpm.cpp' |
35 | --- mixxx/src/analyserbpm.cpp 2010-10-19 01:35:26 +0000 |
36 | +++ mixxx/src/analyserbpm.cpp 2011-03-07 05:53:37 +0000 |
37 | @@ -3,6 +3,8 @@ |
38 | |
39 | #include "BPMDetect.h" |
40 | #include "trackinfoobject.h" |
41 | +#include "track/beatgrid.h" |
42 | +#include "track/beatfactory.h" |
43 | #include "analyserbpm.h" |
44 | |
45 | |
46 | @@ -20,9 +22,9 @@ |
47 | // int defaultrange = m_pConfig->getValueString(ConfigKey("[BPM]","BPMAboveRangeEnabled")).toInt(); |
48 | bool bpmEnabled = (bool)m_pConfig->getValueString(ConfigKey("[BPM]","BPMDetectionEnabled")).toInt(); |
49 | |
50 | - // If BPM detection is not enabled, or the track already has BPM detection done. |
51 | - if(bpmEnabled && |
52 | - tio->getBpm() == 0.) { |
53 | + // If BPM detection is enabled and the track does not have a BPM already, |
54 | + // create a detector. |
55 | + if(bpmEnabled && tio->getBpm() == 0.) { |
56 | // All SoundSource's return stereo data, no matter the real file's type |
57 | m_pDetector = new soundtouch::BPMDetect(2, sampleRate); |
58 | //m_pDetector = new BPMDetect(tio->getChannels(), sampleRate); |
59 | @@ -31,10 +33,7 @@ |
60 | } |
61 | } |
62 | |
63 | - |
64 | - |
65 | void AnalyserBPM::process(const CSAMPLE *pIn, const int iLen) { |
66 | - |
67 | // Check if BPM detection is enabled |
68 | if(m_pDetector == NULL) { |
69 | return; |
70 | @@ -42,7 +41,6 @@ |
71 | //qDebug() << "AnalyserBPM::process() processing " << iLen << " samples"; |
72 | |
73 | m_pDetector->inputSamples(pIn, iLen/2); |
74 | - |
75 | } |
76 | |
77 | float AnalyserBPM::correctBPM( float BPM, int min, int max, int aboveRange) { |
78 | @@ -71,6 +69,13 @@ |
79 | |
80 | tio->setBpm(newbpm); |
81 | tio->setBpmConfirm(); |
82 | + |
83 | + // Currently, the BPM is only analyzed if the track has no BPM. This |
84 | + // means we don't have to worry that the track already has an existing |
85 | + // BeatGrid. |
86 | + BeatsPointer pBeats = BeatFactory::makeBeatGrid(tio, newbpm, 0.0f); |
87 | + tio->setBeats(pBeats); |
88 | + |
89 | //if(pBpmReceiver) { |
90 | //pBpmReceiver->setComplete(tio, false, bpm); |
91 | //} |
92 | |
93 | === modified file 'mixxx/src/analyserbpm.h' |
94 | --- mixxx/src/analyserbpm.h 2010-07-15 19:07:16 +0000 |
95 | +++ mixxx/src/analyserbpm.h 2011-03-07 05:53:37 +0000 |
96 | @@ -8,7 +8,6 @@ |
97 | |
98 | |
99 | class AnalyserBPM : public Analyser { |
100 | - |
101 | public: |
102 | AnalyserBPM(ConfigObject<ConfigValue> *_config); |
103 | void initialise(TrackPointer tio, int sampleRate, int totalSamples); |
104 | @@ -22,7 +21,6 @@ |
105 | soundtouch::BPMDetect *m_pDetector; |
106 | int m_iMinBpm, m_iMaxBpm; |
107 | bool m_bProcessEntireSong; |
108 | - |
109 | }; |
110 | |
111 | #endif |
112 | |
113 | === modified file 'mixxx/src/basetrackplayer.cpp' |
114 | --- mixxx/src/basetrackplayer.cpp 2010-11-17 21:02:24 +0000 |
115 | +++ mixxx/src/basetrackplayer.cpp 2011-03-07 05:53:37 +0000 |
116 | @@ -98,6 +98,7 @@ |
117 | delete m_pPlayPosition; |
118 | delete m_pBPM; |
119 | delete m_pReplayGain; |
120 | + delete m_pWaveformRenderer; |
121 | } |
122 | |
123 | void BaseTrackPlayer::slotLoadTrack(TrackPointer track, bool bStartFromEndPos) |
124 | @@ -184,7 +185,7 @@ |
125 | // Read the tags if required |
126 | if(!m_pLoadedTrack->getHeaderParsed()) |
127 | SoundSourceProxy::ParseHeader(m_pLoadedTrack.data()); |
128 | - |
129 | + |
130 | m_pLoadedTrack->incTimesPlayed(); |
131 | |
132 | // Generate waveform summary |
133 | |
134 | === modified file 'mixxx/src/library/dao/trackdao.cpp' |
135 | --- mixxx/src/library/dao/trackdao.cpp 2010-12-02 22:17:33 +0000 |
136 | +++ mixxx/src/library/dao/trackdao.cpp 2011-03-07 05:53:37 +0000 |
137 | @@ -2,7 +2,10 @@ |
138 | #include <QtDebug> |
139 | #include <QtCore> |
140 | #include <QtSql> |
141 | + |
142 | #include "trackinfoobject.h" |
143 | +#include "track/beats.h" |
144 | +#include "track/beatfactory.h" |
145 | #include "library/dao/trackdao.h" |
146 | #include "audiotagger.h" |
147 | |
148 | @@ -35,6 +38,8 @@ |
149 | |
150 | TrackDAO::~TrackDAO() |
151 | { |
152 | + qDebug() << "~TrackDAO()"; |
153 | + clearCache(); |
154 | } |
155 | |
156 | void TrackDAO::initialize() |
157 | @@ -218,13 +223,13 @@ |
158 | "filetype, location, comment, url, duration, rating, key, " |
159 | "bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, " |
160 | "timesplayed, " |
161 | - "channels, mixxx_deleted, header_parsed) " |
162 | + "channels, mixxx_deleted, header_parsed, beats_version, beats) " |
163 | "VALUES (:artist, " |
164 | ":title, :album, :year, :genre, :tracknumber, " |
165 | - ":filetype, :location, :comment, :url, :duration, :rating, :key," |
166 | + ":filetype, :location, :comment, :url, :duration, :rating, :key, " |
167 | ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :wavesummaryhex, " |
168 | ":timesplayed, " |
169 | - ":channels, :mixxx_deleted, :header_parsed)"); |
170 | + ":channels, :mixxx_deleted, :header_parsed, :beats_version, :beats)"); |
171 | } |
172 | |
173 | void TrackDAO::bindTrackToLibraryInsert(QSqlQuery& query, TrackInfoObject* pTrack, int trackLocationId) { |
174 | @@ -243,17 +248,33 @@ |
175 | query.bindValue(":bitrate", pTrack->getBitrate()); |
176 | query.bindValue(":samplerate", pTrack->getSampleRate()); |
177 | query.bindValue(":cuepoint", pTrack->getCuePoint()); |
178 | - query.bindValue(":bpm", pTrack->getBpm()); |
179 | + |
180 | query.bindValue(":replaygain", pTrack->getReplayGain()); |
181 | query.bindValue(":key", pTrack->getKey()); |
182 | const QByteArray* pWaveSummary = pTrack->getWaveSummary(); |
183 | - if (pWaveSummary) //Avoid null pointer deref |
184 | - query.bindValue(":wavesummaryhex", *pWaveSummary); |
185 | + query.bindValue(":wavesummaryhex", pWaveSummary ? *pWaveSummary : QVariant(QVariant::ByteArray)); |
186 | query.bindValue(":timesplayed", pTrack->getTimesPlayed()); |
187 | //query.bindValue(":datetime_added", pTrack->getDateAdded()); |
188 | query.bindValue(":channels", pTrack->getChannels()); |
189 | query.bindValue(":mixxx_deleted", 0); |
190 | query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0); |
191 | + |
192 | + const QByteArray* pBeatsBlob = NULL; |
193 | + QString blobVersion = ""; |
194 | + BeatsPointer pBeats = pTrack->getBeats(); |
195 | + // Fall back on cached BPM |
196 | + double dBpm = pTrack->getBpm(); |
197 | + |
198 | + if (pBeats) { |
199 | + pBeatsBlob = pBeats->toByteArray(); |
200 | + blobVersion = pBeats->getVersion(); |
201 | + dBpm = pBeats->getBpm(); |
202 | + } |
203 | + |
204 | + query.bindValue(":bpm", dBpm); |
205 | + query.bindValue(":beats_version", blobVersion); |
206 | + query.bindValue(":beats", pBeatsBlob ? *pBeatsBlob : QVariant(QVariant::ByteArray)); |
207 | + delete pBeatsBlob; |
208 | } |
209 | |
210 | void TrackDAO::addTracks(QList<TrackInfoObject*> tracksToAdd) { |
211 | @@ -306,7 +327,8 @@ |
212 | bindTrackToLibraryInsert(query, pTrack, pTrack->getId()); |
213 | |
214 | if (!query.exec()) { |
215 | - qDebug() << "Failed to INSERT new track into library" |
216 | + qDebug() << "Failed to INSERT new track into library:" |
217 | + << pTrack->getFilename() |
218 | << __FILE__ << __LINE__ << query.lastError(); |
219 | m_database.rollback(); |
220 | return; |
221 | @@ -495,7 +517,7 @@ |
222 | void TrackDAO::deleteTrack(TrackInfoObject* pTrack) { |
223 | Q_ASSERT(pTrack); |
224 | |
225 | - //qDebug() << "Got deletion call for track" << pTrack << "ID" << pTrack->getId() << pTrack->getInfo(); |
226 | + qDebug() << "Got deletion call for track" << pTrack << "ID" << pTrack->getId() << pTrack->getInfo(); |
227 | |
228 | // Save dirty tracks. |
229 | pTrack->save(); |
230 | @@ -521,8 +543,8 @@ |
231 | |
232 | int locationId = -1; |
233 | while (query.next()) { |
234 | + // Good god! Assign query.record() to a freaking variable! |
235 | int trackId = query.value(query.record().indexOf("id")).toInt(); |
236 | - |
237 | QString artist = query.value(query.record().indexOf("artist")).toString(); |
238 | QString title = query.value(query.record().indexOf("title")).toString(); |
239 | QString album = query.value(query.record().indexOf("album")).toString(); |
240 | @@ -550,6 +572,7 @@ |
241 | bool header_parsed = query.value(query.record().indexOf("header_parsed")).toBool(); |
242 | |
243 | TrackInfoObject* track = new TrackInfoObject(location, false); |
244 | + TrackPointer pTrack = TrackPointer(track, this->deleteTrack); |
245 | |
246 | // TIO already stats the file to see if it exists, what its length is, |
247 | // etc. So don't bother setting it. |
248 | @@ -575,6 +598,14 @@ |
249 | track->setReplayGain(replaygain.toFloat()); |
250 | track->setWaveSummary(wavesummaryhex, false); |
251 | delete wavesummaryhex; |
252 | + |
253 | + QString beatsVersion = query.value(query.record().indexOf("beats_version")).toString(); |
254 | + QByteArray beatsBlob = query.value(query.record().indexOf("beats")).toByteArray(); |
255 | + BeatsPointer pBeats = BeatFactory::loadBeatsFromByteArray(pTrack, beatsVersion, &beatsBlob); |
256 | + if (pBeats) { |
257 | + pTrack->setBeats(pBeats); |
258 | + } |
259 | + |
260 | track->setTimesPlayed(timesplayed); |
261 | track->setPlayed(played); |
262 | track->setChannels(channels); |
263 | @@ -593,8 +624,6 @@ |
264 | connect(track, SIGNAL(save()), |
265 | this, SLOT(slotTrackSave())); |
266 | |
267 | - TrackPointer pTrack = TrackPointer(track, this->deleteTrack); |
268 | - |
269 | // Automatic conversion to a weak pointer |
270 | m_tracks[trackId] = pTrack; |
271 | m_trackCache.insert(trackId, new TrackPointer(pTrack)); |
272 | @@ -606,6 +635,15 @@ |
273 | pTrack->parse(); |
274 | } |
275 | |
276 | + if (!pBeats && pTrack->getBpm() != 0.0f) { |
277 | + // The track has no stored beats but has a previously detected BPM |
278 | + // or a BPM loaded from metadata. Automatically create a beatgrid |
279 | + // for the track. This dirties the track so we have to do it after |
280 | + // all the signal connections, etc. are in place. |
281 | + BeatsPointer pBeats = BeatFactory::makeBeatGrid(pTrack, pTrack->getBpm(), 0.0f); |
282 | + pTrack->setBeats(pBeats); |
283 | + } |
284 | + |
285 | return pTrack; |
286 | } |
287 | //query.finish(); |
288 | @@ -647,7 +685,17 @@ |
289 | time.start(); |
290 | QSqlQuery query(m_database); |
291 | |
292 | - 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)); |
293 | + query.prepare( |
294 | + "SELECT library.id, artist, title, album, year, genre, tracknumber, " |
295 | + "filetype, rating, key, track_locations.location as location, " |
296 | + "track_locations.filesize as filesize, comment, url, duration, bitrate, " |
297 | + "samplerate, cuepoint, bpm, replaygain, wavesummaryhex, channels, " |
298 | + "header_parsed, timesplayed, played, beats_version, beats " |
299 | + "FROM Library " |
300 | + "INNER JOIN track_locations " |
301 | + "ON library.location = track_locations.id " |
302 | + "WHERE library.id=" + QString("%1").arg(id) |
303 | + ); |
304 | |
305 | TrackPointer pTrack; |
306 | |
307 | @@ -686,7 +734,8 @@ |
308 | "bitrate=:bitrate, samplerate=:samplerate, cuepoint=:cuepoint, " |
309 | "bpm=:bpm, replaygain=:replaygain, wavesummaryhex=:wavesummaryhex, " |
310 | "timesplayed=:timesplayed, played=:played, " |
311 | - "channels=:channels, header_parsed=:header_parsed " |
312 | + "channels=:channels, header_parsed=:header_parsed, " |
313 | + "beats_version=:beats_version, beats=:beats " |
314 | "WHERE id="+QString("%1").arg(trackId)); |
315 | query.bindValue(":artist", pTrack->getArtist()); |
316 | query.bindValue(":title", pTrack->getTitle()); |
317 | @@ -701,19 +750,33 @@ |
318 | query.bindValue(":bitrate", pTrack->getBitrate()); |
319 | query.bindValue(":samplerate", pTrack->getSampleRate()); |
320 | query.bindValue(":cuepoint", pTrack->getCuePoint()); |
321 | - query.bindValue(":bpm", pTrack->getBpm()); |
322 | + |
323 | query.bindValue(":replaygain", pTrack->getReplayGain()); |
324 | query.bindValue(":key", pTrack->getKey()); |
325 | query.bindValue(":rating", pTrack->getRating()); |
326 | const QByteArray* pWaveSummary = pTrack->getWaveSummary(); |
327 | - if (pWaveSummary) //Avoid null pointer deref |
328 | - query.bindValue(":wavesummaryhex", *pWaveSummary); |
329 | + query.bindValue(":wavesummaryhex", pWaveSummary ? *pWaveSummary : QVariant(QVariant::ByteArray)); |
330 | query.bindValue(":timesplayed", pTrack->getTimesPlayed()); |
331 | query.bindValue(":played", pTrack->getPlayed()); |
332 | query.bindValue(":channels", pTrack->getChannels()); |
333 | query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0); |
334 | //query.bindValue(":location", pTrack->getLocation()); |
335 | |
336 | + BeatsPointer pBeats = pTrack->getBeats(); |
337 | + QByteArray* pBeatsBlob = NULL; |
338 | + QString beatsVersion = ""; |
339 | + double dBpm = pTrack->getBpm(); |
340 | + |
341 | + if (pBeats) { |
342 | + pBeatsBlob = pBeats->toByteArray(); |
343 | + beatsVersion = pBeats->getVersion(); |
344 | + dBpm = pBeats->getBpm(); |
345 | + } |
346 | + |
347 | + query.bindValue(":beats", pBeatsBlob ? *pBeatsBlob : QVariant(QVariant::ByteArray)); |
348 | + query.bindValue(":beats_version", beatsVersion); |
349 | + query.bindValue(":bpm", dBpm); |
350 | + |
351 | if (!query.exec()) { |
352 | qDebug() << query.lastError(); |
353 | m_database.rollback(); |
354 | |
355 | === modified file 'mixxx/src/library/trackcollection.cpp' |
356 | --- mixxx/src/library/trackcollection.cpp 2011-02-21 07:01:11 +0000 |
357 | +++ mixxx/src/library/trackcollection.cpp 2011-03-07 05:53:37 +0000 |
358 | @@ -68,7 +68,7 @@ |
359 | return false; |
360 | } |
361 | |
362 | - int requiredSchemaVersion = 11; |
363 | + int requiredSchemaVersion = 12; |
364 | if (!SchemaManager::upgradeToSchemaVersion(m_pConfig, m_db, |
365 | requiredSchemaVersion)) { |
366 | QMessageBox::warning(0, tr("Cannot upgrade database schema"), |
367 | |
368 | === added directory 'mixxx/src/track' |
369 | === added file 'mixxx/src/track/beatfactory.cpp' |
370 | --- mixxx/src/track/beatfactory.cpp 1970-01-01 00:00:00 +0000 |
371 | +++ mixxx/src/track/beatfactory.cpp 2011-03-07 05:53:37 +0000 |
372 | @@ -0,0 +1,43 @@ |
373 | +#include <QtDebug> |
374 | + |
375 | +#include "track/beatgrid.h" |
376 | +#include "track/beatmatrix.h" |
377 | +#include "track/beatfactory.h" |
378 | + |
379 | +BeatsPointer BeatFactory::loadBeatsFromByteArray(TrackPointer pTrack, QString beatsVersion, |
380 | + QByteArray* beatsSerialized) { |
381 | + // TODO(XXX) make it so the version strings are not in multiple places |
382 | + if (beatsVersion == "BeatGrid-1.0") { |
383 | + BeatGrid* pGrid = new BeatGrid(pTrack, beatsSerialized); |
384 | + pGrid->moveToThread(pTrack->thread()); |
385 | + pGrid->setParent(pTrack.data()); |
386 | + qDebug() << "Successfully deserialized BeatGrid"; |
387 | + return BeatsPointer(pGrid, &BeatFactory::deleteBeats); |
388 | + } else if (beatsVersion == "BeatMatrix-1.0") { |
389 | + BeatMatrix* pMatrix = new BeatMatrix(pTrack, beatsSerialized); |
390 | + pMatrix->moveToThread(pTrack->thread()); |
391 | + pMatrix->setParent(pTrack.data()); |
392 | + qDebug() << "Successfully deserialized BeatMatrix"; |
393 | + return BeatsPointer(pMatrix, &BeatFactory::deleteBeats); |
394 | + } |
395 | + qDebug() << "BeatFactory::loadBeatsFromByteArray could not parse serialized beats."; |
396 | + return BeatsPointer(); |
397 | +} |
398 | + |
399 | +BeatsPointer BeatFactory::makeBeatGrid(TrackPointer pTrack, double dBpm, double dFirstBeatSample) { |
400 | + BeatGrid* pGrid = new BeatGrid(pTrack); |
401 | + pGrid->setGrid(dBpm, dFirstBeatSample); |
402 | + return BeatsPointer(pGrid, &BeatFactory::deleteBeats); |
403 | +} |
404 | + |
405 | +void BeatFactory::deleteBeats(Beats* pBeats) { |
406 | + // This assumes all Beats* variants multiply-inherit from QObject. Kind of ugly. Oh well. |
407 | + QObject* pObject = dynamic_cast<QObject*>(pBeats); |
408 | + |
409 | + if (pObject != NULL) { |
410 | + qDebug() << "Deleting QObject instance."; |
411 | + pObject->deleteLater(); |
412 | + } else { |
413 | + qDebug() << "Could not delete Beats instance, did not dynamic_cast to QObject"; |
414 | + } |
415 | +} |
416 | |
417 | === added file 'mixxx/src/track/beatfactory.h' |
418 | --- mixxx/src/track/beatfactory.h 1970-01-01 00:00:00 +0000 |
419 | +++ mixxx/src/track/beatfactory.h 2011-03-07 05:53:37 +0000 |
420 | @@ -0,0 +1,19 @@ |
421 | +#ifndef BEATFACTORY_H |
422 | +#define BEATFACTORY_H |
423 | + |
424 | +#include "track/beats.h" |
425 | +#include "track/beatgrid.h" |
426 | +#include "track/beatmatrix.h" |
427 | + |
428 | +class BeatFactory { |
429 | + public: |
430 | + static BeatsPointer loadBeatsFromByteArray(TrackPointer pTrack, |
431 | + QString beatsVersion, |
432 | + QByteArray* beatsSerialized); |
433 | + static BeatsPointer makeBeatGrid(TrackPointer pTrack, |
434 | + double dBpm, double dFirstBeatSample); |
435 | + private: |
436 | + static void deleteBeats(Beats* pBeats); |
437 | +}; |
438 | + |
439 | +#endif /* BEATFACTORY_H */ |
440 | |
441 | === added file 'mixxx/src/track/beatgrid.cpp' |
442 | --- mixxx/src/track/beatgrid.cpp 1970-01-01 00:00:00 +0000 |
443 | +++ mixxx/src/track/beatgrid.cpp 2011-03-07 05:53:37 +0000 |
444 | @@ -0,0 +1,186 @@ |
445 | +#include <QMutexLocker> |
446 | +#include <QDebug> |
447 | + |
448 | +#include "track/beatgrid.h" |
449 | + |
450 | + |
451 | +struct BeatGridData { |
452 | + double bpm; |
453 | + double firstBeat; |
454 | +}; |
455 | + |
456 | +BeatGrid::BeatGrid(TrackPointer pTrack, const QByteArray* pByteArray) |
457 | + : QObject(), |
458 | + m_mutex(QMutex::Recursive), |
459 | + m_iSampleRate(pTrack->getSampleRate()), |
460 | + m_dBpm(0.0f), |
461 | + m_dFirstBeat(0.0f), |
462 | + m_dBeatLength(0.0f) { |
463 | + connect(pTrack.data(), SIGNAL(bpmUpdated(double)), |
464 | + this, SLOT(slotTrackBpmUpdated(double))); |
465 | + |
466 | + qDebug() << "New BeatGrid"; |
467 | + if (pByteArray != NULL) { |
468 | + readByteArray(pByteArray); |
469 | + } |
470 | +} |
471 | + |
472 | +BeatGrid::~BeatGrid() { |
473 | + |
474 | +} |
475 | + |
476 | +void BeatGrid::setGrid(double dBpm, double dFirstBeatSample) { |
477 | + QMutexLocker lock(&m_mutex); |
478 | + m_dBpm = dBpm; |
479 | + m_dFirstBeat = dFirstBeatSample; |
480 | + m_dBeatLength = 60.0 * m_iSampleRate / m_dBpm; |
481 | +} |
482 | + |
483 | +QByteArray* BeatGrid::toByteArray() const { |
484 | + QMutexLocker locker(&m_mutex); |
485 | + BeatGridData blob = { m_dBpm, m_dFirstBeat }; |
486 | + QByteArray* pByteArray = new QByteArray((char *)&blob, sizeof(blob)); |
487 | + return pByteArray; |
488 | +} |
489 | + |
490 | +void BeatGrid::readByteArray(const QByteArray* pByteArray) { |
491 | + if ( pByteArray->size() != sizeof(BeatGridData)) |
492 | + return; |
493 | + BeatGridData *blob = (BeatGridData *)pByteArray->data(); |
494 | + setGrid(blob->bpm, blob->firstBeat); |
495 | +} |
496 | + |
497 | +QString BeatGrid::getVersion() const { |
498 | + QMutexLocker locker(&m_mutex); |
499 | + return "BeatGrid-1.0"; |
500 | +} |
501 | + |
502 | +// internal use only |
503 | +bool BeatGrid::isValid() const { |
504 | + return m_iSampleRate > 0 && m_dBpm > 0; |
505 | +} |
506 | + |
507 | +// This could be implemented in the Beats Class itself. |
508 | +// If necessary, the child class can redefine it. |
509 | +double BeatGrid::findNextBeat(double dSamples) const { |
510 | + return findNthBeat(dSamples, +1); |
511 | +} |
512 | + |
513 | +// This could be implemented in the Beats Class itself. |
514 | +// If necessary, the child class can redefine it. |
515 | +double BeatGrid::findPrevBeat(double dSamples) const { |
516 | + return findNthBeat(dSamples, -1); |
517 | +} |
518 | + |
519 | +// This is an internal call. This could be implemented in the Beats Class itself. |
520 | +double BeatGrid::findClosestBeat(double dSamples) const { |
521 | + QMutexLocker locker(&m_mutex); |
522 | + if (!isValid()) { |
523 | + return -1; |
524 | + } |
525 | + double nextBeat = findNextBeat(dSamples); |
526 | + double prevBeat = findPrevBeat(dSamples); |
527 | + return (nextBeat - dSamples > dSamples - prevBeat) ? prevBeat : nextBeat; |
528 | +} |
529 | + |
530 | +double BeatGrid::findNthBeat(double dSamples, int n) const { |
531 | + QMutexLocker locker(&m_mutex); |
532 | + if (!isValid() || n == 0) { |
533 | + return -1; |
534 | + } |
535 | + |
536 | + double dClosestBeat; |
537 | + if (n > 0) { |
538 | + // We're going forward, so use ceilf to round up to the next multiple of |
539 | + // m_dBeatLength |
540 | + dClosestBeat = ceilf(dSamples/m_dBeatLength) * m_dBeatLength + m_dFirstBeat; |
541 | + n = n - 1; |
542 | + } else { |
543 | + // We're going backward, so use floorf to round up to the next multiple |
544 | + // of m_dBeatLength |
545 | + dClosestBeat = floorf(dSamples/m_dBeatLength) * m_dBeatLength + m_dFirstBeat; |
546 | + n = n + 1; |
547 | + } |
548 | + |
549 | + return dClosestBeat + n * m_dBeatLength; |
550 | +} |
551 | + |
552 | +void BeatGrid::findBeats(double startSample, double stopSample, QList<double>* pBeatsList) const { |
553 | + QMutexLocker locker(&m_mutex); |
554 | + if (!isValid() || startSample > stopSample) { |
555 | + return; |
556 | + } |
557 | + double curBeat = findNextBeat(startSample); |
558 | + while (curBeat <= stopSample) { |
559 | + pBeatsList->append(curBeat); |
560 | + curBeat += m_dBeatLength; |
561 | + } |
562 | +} |
563 | + |
564 | +bool BeatGrid::hasBeatInRange(double startSample, double stopSample) const { |
565 | + QMutexLocker locker(&m_mutex); |
566 | + if (!isValid() || startSample > stopSample) { |
567 | + return false; |
568 | + } |
569 | + double curBeat = findNextBeat(startSample); |
570 | + if (curBeat <= stopSample) { |
571 | + return true; |
572 | + } |
573 | + return false; |
574 | +} |
575 | + |
576 | +double BeatGrid::getBpm() const { |
577 | + QMutexLocker locker(&m_mutex); |
578 | + if (!isValid()) { |
579 | + return -1; |
580 | + } |
581 | + return m_dBpm; |
582 | +} |
583 | + |
584 | +double BeatGrid::getBpmRange(double startSample, double stopSample) const { |
585 | + QMutexLocker locker(&m_mutex); |
586 | + if (!isValid() || startSample > stopSample) { |
587 | + return -1; |
588 | + } |
589 | + return m_dBpm; |
590 | +} |
591 | + |
592 | +void BeatGrid::addBeat(double dBeatSample) { |
593 | + //QMutexLocker locker(&m_mutex); |
594 | + return; |
595 | +} |
596 | + |
597 | +void BeatGrid::removeBeat(double dBeatSample) { |
598 | + //QMutexLocker locker(&m_mutex); |
599 | + return; |
600 | +} |
601 | + |
602 | +void BeatGrid::moveBeat(double dBeatSample, double dNewBeatSample) { |
603 | + //QMutexLocker locker(&m_mutex); |
604 | + return; |
605 | +} |
606 | + |
607 | +void BeatGrid::translate(double dNumSamples) { |
608 | + QMutexLocker locker(&m_mutex); |
609 | + if (!isValid()) { |
610 | + return; |
611 | + } |
612 | + m_dFirstBeat += dNumSamples; |
613 | + locker.unlock(); |
614 | + emit(updated()); |
615 | +} |
616 | + |
617 | +void BeatGrid::scale(double dScalePercentage) { |
618 | + QMutexLocker locker(&m_mutex); |
619 | + if (!isValid()) { |
620 | + return; |
621 | + } |
622 | + m_dBpm *= dScalePercentage; |
623 | + locker.unlock(); |
624 | + emit(updated()); |
625 | +} |
626 | + |
627 | +void BeatGrid::slotTrackBpmUpdated(double dBpm) { |
628 | + QMutexLocker locker(&m_mutex); |
629 | + m_dBpm = dBpm; |
630 | +} |
631 | |
632 | === added file 'mixxx/src/track/beatgrid.h' |
633 | --- mixxx/src/track/beatgrid.h 1970-01-01 00:00:00 +0000 |
634 | +++ mixxx/src/track/beatgrid.h 2011-03-07 05:53:37 +0000 |
635 | @@ -0,0 +1,75 @@ |
636 | +#ifndef BEATGRID_H |
637 | +#define BEATGRID_H |
638 | + |
639 | +#include <QMutex> |
640 | +#include <QObject> |
641 | + |
642 | +#include "trackinfoobject.h" |
643 | +#include "track/beats.h" |
644 | + |
645 | +// BeatGrid is an implementation of the Beats interface that implements an |
646 | +// infinite grid of beats, aligned to a song simply by a starting offset of the |
647 | +// first beat and the song's average beats-per-minute. |
648 | +class BeatGrid : public QObject, public virtual Beats { |
649 | + Q_OBJECT |
650 | + public: |
651 | + BeatGrid(TrackPointer pTrack, const QByteArray* pByteArray=NULL); |
652 | + virtual ~BeatGrid(); |
653 | + |
654 | + // Initializes the BeatGrid to have a BPM of dBpm and the first beat offset |
655 | + // of dFirstBeatSample. Does not generate an updated() signal, since it is |
656 | + // meant for initialization. |
657 | + void setGrid(double dBpm, double dFirstBeatSample); |
658 | + |
659 | + // The following are all methods from the Beats interface, see method |
660 | + // comments in beats.h |
661 | + |
662 | + virtual Beats::CapabilitiesFlags getCapabilities() const { |
663 | + return BEATSCAP_TRANSLATE | BEATSCAP_SCALE; |
664 | + } |
665 | + |
666 | + virtual QByteArray* toByteArray() const; |
667 | + virtual QString getVersion() const; |
668 | + |
669 | + //////////////////////////////////////////////////////////////////////////// |
670 | + // Beat calculations |
671 | + //////////////////////////////////////////////////////////////////////////// |
672 | + |
673 | + virtual double findNextBeat(double dSamples) const; |
674 | + virtual double findPrevBeat(double dSamples) const; |
675 | + virtual double findClosestBeat(double dSamples) const; |
676 | + virtual double findNthBeat(double dSamples, int n) const; |
677 | + virtual void findBeats(double startSample, double stopSample, BeatList* pBeatsList) const; |
678 | + virtual bool hasBeatInRange(double startSample, double stopSample) const; |
679 | + virtual double getBpm() const; |
680 | + virtual double getBpmRange(double startSample, double stopSample) const; |
681 | + |
682 | + //////////////////////////////////////////////////////////////////////////// |
683 | + // Beat mutations |
684 | + //////////////////////////////////////////////////////////////////////////// |
685 | + |
686 | + virtual void addBeat(double dBeatSample); |
687 | + virtual void removeBeat(double dBeatSample); |
688 | + virtual void moveBeat(double dBeatSample, double dNewBeatSample); |
689 | + virtual void translate(double dNumSamples); |
690 | + virtual void scale(double dScalePercentage); |
691 | + |
692 | + signals: |
693 | + void updated(); |
694 | + |
695 | + private slots: |
696 | + void slotTrackBpmUpdated(double bpm); |
697 | + |
698 | + private: |
699 | + void readByteArray(const QByteArray* pByteArray); |
700 | + // For internal use only. |
701 | + bool isValid() const; |
702 | + |
703 | + mutable QMutex m_mutex; |
704 | + int m_iSampleRate; |
705 | + double m_dBpm, m_dFirstBeat; |
706 | + double m_dBeatLength; |
707 | +}; |
708 | + |
709 | + |
710 | +#endif /* BEATGRID_H */ |
711 | |
712 | === added file 'mixxx/src/track/beatmatrix.cpp' |
713 | --- mixxx/src/track/beatmatrix.cpp 1970-01-01 00:00:00 +0000 |
714 | +++ mixxx/src/track/beatmatrix.cpp 2011-03-07 05:53:37 +0000 |
715 | @@ -0,0 +1,271 @@ |
716 | +#include <QtDebug> |
717 | +#include <QMutexLocker> |
718 | + |
719 | +#include "track/beatmatrix.h" |
720 | + |
721 | +BeatMatrix::BeatMatrix(TrackPointer pTrack, const QByteArray* pByteArray) |
722 | + : QObject(), |
723 | + m_mutex(QMutex::Recursive), |
724 | + m_iSampleRate(pTrack->getSampleRate()) { |
725 | + if (pByteArray != NULL) { |
726 | + readByteArray(pByteArray); |
727 | + } |
728 | +} |
729 | + |
730 | +BeatMatrix::~BeatMatrix() { |
731 | +} |
732 | + |
733 | +unsigned int BeatMatrix::numBeats() const { |
734 | + return m_beatList.size(); |
735 | +} |
736 | + |
737 | +QByteArray* BeatMatrix::toByteArray() const { |
738 | + QMutexLocker locker(&m_mutex); |
739 | + // No guarantees BeatLists are made of a data type which located adjacent |
740 | + // items in adjacent memory locations. |
741 | + double* pBuffer = new double[m_beatList.size()]; |
742 | + for (int i = 0; i < m_beatList.size(); ++i) { |
743 | + pBuffer[i] = m_beatList[i]; |
744 | + } |
745 | + QByteArray* pByteArray = new QByteArray((char*)pBuffer, sizeof(pBuffer[0]) * m_beatList.size()); |
746 | + delete [] pBuffer; |
747 | + return pByteArray; |
748 | +} |
749 | + |
750 | +void BeatMatrix::readByteArray(const QByteArray* pByteArray) { |
751 | + if (pByteArray->size() % sizeof(double) != 0) { |
752 | + qDebug() << "ERROR: Could not parse BeatMatrix from QByteArray of size" << pByteArray->size(); |
753 | + return; |
754 | + } |
755 | + |
756 | + int numBeats = pByteArray->size() / sizeof(double); |
757 | + double* pBuffer = (double*)pByteArray->data(); |
758 | + for (int i = 0; i < numBeats; ++i) { |
759 | + m_beatList.append(pBuffer[i]); |
760 | + } |
761 | +} |
762 | + |
763 | + |
764 | +QString BeatMatrix::getVersion() const { |
765 | + QMutexLocker locker(&m_mutex); |
766 | + return "BeatMatrix-1.0"; |
767 | +} |
768 | + |
769 | +// internal use only |
770 | +bool BeatMatrix::isValid() const { |
771 | + return m_iSampleRate > 0 && m_beatList.size() > 0; |
772 | +} |
773 | + |
774 | +double BeatMatrix::findNextBeat(double dSamples) const { |
775 | + return findNthBeat(dSamples, 1); |
776 | +} |
777 | + |
778 | +double BeatMatrix::findPrevBeat(double dSamples) const { |
779 | + return findNthBeat(dSamples, -1); |
780 | +} |
781 | + |
782 | +double BeatMatrix::findClosestBeat(double dSamples) const { |
783 | + QMutexLocker locker(&m_mutex); |
784 | + if (!isValid()) { |
785 | + return -1; |
786 | + } |
787 | + double nextBeat = findNextBeat(dSamples); |
788 | + double prevBeat = findPrevBeat(dSamples); |
789 | + return (nextBeat - dSamples > dSamples - prevBeat) ? prevBeat : nextBeat; |
790 | +} |
791 | + |
792 | +double BeatMatrix::findNthBeat(double dSamples, int n) const { |
793 | + QMutexLocker locker(&m_mutex); |
794 | + BeatList::const_iterator it; |
795 | + int i; |
796 | + |
797 | + if (!isValid() || n == 0) { |
798 | + return -1; |
799 | + } |
800 | + |
801 | + if (n > 0) { |
802 | + it = qLowerBound(m_beatList.begin(), m_beatList.end(), dSamples); |
803 | + |
804 | + // Count down until n=1 |
805 | + while (it != m_beatList.end()) { |
806 | + if (n == 1) { |
807 | + return *it; |
808 | + } |
809 | + it++; n--; |
810 | + } |
811 | + } |
812 | + else if (n < 0) { |
813 | + it = qUpperBound(m_beatList.begin(), m_beatList.end(), dSamples); |
814 | + |
815 | + // Count up until n=-1 |
816 | + while (it != m_beatList.begin()) { |
817 | + // qUpperBound starts us off at the position just-one-past the last |
818 | + // occurence of dSamples-or-smaller in the list. In order to get the |
819 | + // last instance of dSamples-or-smaller, we decrement it by 1 before |
820 | + // touching it. The guard of this while loop guarantees this does |
821 | + // not put us before the start of the loop. |
822 | + it--; |
823 | + if (n == -1) { |
824 | + return *it; |
825 | + } |
826 | + n++; |
827 | + } |
828 | + } |
829 | + |
830 | + return -1; |
831 | +} |
832 | + |
833 | +void BeatMatrix::findBeats(double startSample, double stopSample, QList<double>* pBeatsList) const { |
834 | + QMutexLocker locker(&m_mutex); |
835 | + if (!isValid() || startSample > stopSample) { |
836 | + return; |
837 | + } |
838 | + BeatList::const_iterator curBeat = qLowerBound(m_beatList.begin(), |
839 | + m_beatList.end(), |
840 | + startSample); |
841 | + BeatList::const_iterator stopBeat = qUpperBound(m_beatList.begin(), |
842 | + m_beatList.end(), |
843 | + stopSample); |
844 | + |
845 | + for (; curBeat != stopBeat; curBeat++) { |
846 | + pBeatsList->append(*curBeat); |
847 | + } |
848 | +} |
849 | + |
850 | +bool BeatMatrix::hasBeatInRange(double startSample, double stopSample) const { |
851 | + QMutexLocker locker(&m_mutex); |
852 | + if (!isValid() || startSample > stopSample) { |
853 | + return false; |
854 | + } |
855 | + BeatList::const_iterator startBeat = qLowerBound(m_beatList.begin(), |
856 | + m_beatList.end(), |
857 | + startSample); |
858 | + BeatList::const_iterator stopBeat = qUpperBound(m_beatList.begin(), |
859 | + m_beatList.end(), |
860 | + stopSample); |
861 | + |
862 | + if (startBeat != stopBeat) |
863 | + return true; |
864 | + return false; |
865 | +} |
866 | + |
867 | +double BeatMatrix::getBpm() const { |
868 | + QMutexLocker locker(&m_mutex); |
869 | + if (!isValid()) { |
870 | + return -1; |
871 | + } |
872 | + |
873 | + // TODO(XXX) not actually correct. We need the true song length. |
874 | + double startSample = *m_beatList.begin(); |
875 | + double stopSample = *(m_beatList.end()-1); |
876 | + double songDurationMinutes = |
877 | + (stopSample - startSample) / (60.0f * m_iSampleRate); |
878 | + return m_beatList.size() / songDurationMinutes; |
879 | +} |
880 | + |
881 | +double BeatMatrix::getBpmRange(double startSample, double stopSample) const { |
882 | + QMutexLocker locker(&m_mutex); |
883 | + if (!isValid() || startSample > stopSample) { |
884 | + return -1; |
885 | + } |
886 | + |
887 | + BeatList::const_iterator startBeat = qLowerBound(m_beatList.begin(), |
888 | + m_beatList.end(), |
889 | + startSample); |
890 | + BeatList::const_iterator stopBeat = qUpperBound(m_beatList.begin(), |
891 | + m_beatList.end(), |
892 | + stopSample); |
893 | + double rangeDurationMinutes = |
894 | + (stopSample - startSample) / (60.0f * m_iSampleRate); |
895 | + // Subtracting returns the number of beats between the samples referred to |
896 | + // by the start and end. |
897 | + double beatsInRange = stopBeat - startBeat; |
898 | + |
899 | + return beatsInRange / rangeDurationMinutes; |
900 | +} |
901 | + |
902 | +void BeatMatrix::addBeat(double dBeatSample) { |
903 | + QMutexLocker locker(&m_mutex); |
904 | + |
905 | + BeatList::iterator it = qLowerBound(m_beatList.begin(), |
906 | + m_beatList.end(), |
907 | + dBeatSample); |
908 | + // Don't insert a duplicate beat. TODO(XXX) determine what epsilon to |
909 | + // consider a beat identical to another. |
910 | + if (*it == dBeatSample) |
911 | + return; |
912 | + |
913 | + m_beatList.insert(it, dBeatSample); |
914 | + locker.unlock(); |
915 | + emit(updated()); |
916 | +} |
917 | + |
918 | +void BeatMatrix::removeBeat(double dBeatSample) { |
919 | + QMutexLocker locker(&m_mutex); |
920 | + BeatList::iterator it = qLowerBound(m_beatList.begin(), |
921 | + m_beatList.end(), dBeatSample); |
922 | + |
923 | + // In case there are duplicates, remove every instance of dBeatSample |
924 | + // TODO(XXX) add invariant checks against this |
925 | + // TODO(XXX) determine what epsilon to consider a beat identical to another |
926 | + while (*it == dBeatSample) { |
927 | + it = m_beatList.erase(it); |
928 | + } |
929 | + locker.unlock(); |
930 | + emit(updated()); |
931 | +} |
932 | + |
933 | +void BeatMatrix::moveBeat(double dBeatSample, double dNewBeatSample) { |
934 | + QMutexLocker locker(&m_mutex); |
935 | + |
936 | + BeatList::iterator it = qLowerBound(m_beatList.begin(), |
937 | + m_beatList.end(), dBeatSample); |
938 | + |
939 | + // Remove all beats from dBeatSample |
940 | + while (*it == dBeatSample) { |
941 | + it = m_beatList.erase(it); |
942 | + } |
943 | + |
944 | + // Now add a beat to dNewBeatSample |
945 | + it = qLowerBound(m_beatList.begin(), |
946 | + m_beatList.end(), dNewBeatSample); |
947 | + |
948 | + // TODO(XXX) beat epsilon |
949 | + if (*it != dNewBeatSample) { |
950 | + m_beatList.insert(it, dNewBeatSample); |
951 | + } |
952 | + locker.unlock(); |
953 | + emit(updated()); |
954 | +} |
955 | + |
956 | +void BeatMatrix::translate(double dNumSamples) { |
957 | + QMutexLocker locker(&m_mutex); |
958 | + if (!isValid()) { |
959 | + return; |
960 | + } |
961 | + |
962 | + for (BeatList::iterator it = m_beatList.begin(); |
963 | + it != m_beatList.end(); ++it) { |
964 | + *it += dNumSamples; |
965 | + } |
966 | + locker.unlock(); |
967 | + emit(updated()); |
968 | +} |
969 | + |
970 | +void BeatMatrix::scale(double dScalePercentage) { |
971 | + QMutexLocker locker(&m_mutex); |
972 | + if (!isValid()) { |
973 | + return; |
974 | + } |
975 | + for (BeatList::iterator it = m_beatList.begin(); |
976 | + it != m_beatList.end(); ++it) { |
977 | + *it *= dScalePercentage; |
978 | + } |
979 | + locker.unlock(); |
980 | + emit(updated()); |
981 | +} |
982 | + |
983 | +void BeatMatrix::slotTrackBpmUpdated(double dBpm) { |
984 | + //QMutexLocker locker(&m_mutex); |
985 | + // TODO(XXX) How do we handle this? |
986 | +} |
987 | |
988 | === added file 'mixxx/src/track/beatmatrix.h' |
989 | --- mixxx/src/track/beatmatrix.h 1970-01-01 00:00:00 +0000 |
990 | +++ mixxx/src/track/beatmatrix.h 2011-03-07 05:53:37 +0000 |
991 | @@ -0,0 +1,67 @@ |
992 | +#ifndef BEATMATRIX_H |
993 | +#define BEATMATRIX_H |
994 | + |
995 | +#include <QObject> |
996 | +#include <QMutex> |
997 | + |
998 | +#include "trackinfoobject.h" |
999 | +#include "track/beats.h" |
1000 | + |
1001 | +// BeatMatrix is an implementation of the Beats interface that implements a list |
1002 | +// of finite beats that have been extracted or otherwise annotated for a track. |
1003 | +class BeatMatrix : public QObject, public Beats { |
1004 | + Q_OBJECT |
1005 | + public: |
1006 | + BeatMatrix(TrackPointer pTrack, const QByteArray* pByteArray=NULL); |
1007 | + virtual ~BeatMatrix(); |
1008 | + |
1009 | + // See method comments in beats.h |
1010 | + |
1011 | + virtual Beats::CapabilitiesFlags getCapabilities() const { |
1012 | + return BEATSCAP_TRANSLATE | BEATSCAP_SCALE | BEATSCAP_ADDREMOVE | BEATSCAP_MOVEBEAT; |
1013 | + } |
1014 | + |
1015 | + virtual QByteArray* toByteArray() const; |
1016 | + virtual QString getVersion() const; |
1017 | + |
1018 | + //////////////////////////////////////////////////////////////////////////// |
1019 | + // Beat calculations |
1020 | + //////////////////////////////////////////////////////////////////////////// |
1021 | + |
1022 | + virtual double findNextBeat(double dSamples) const; |
1023 | + virtual double findPrevBeat(double dSamples) const; |
1024 | + virtual double findClosestBeat(double dSamples) const; |
1025 | + virtual double findNthBeat(double dSamples, int n) const; |
1026 | + virtual void findBeats(double startSample, double stopSample, QList<double>* pBeatsList) const; |
1027 | + virtual bool hasBeatInRange(double startSample, double stopSample) const; |
1028 | + virtual double getBpm() const; |
1029 | + virtual double getBpmRange(double startSample, double stopSample) const; |
1030 | + |
1031 | + //////////////////////////////////////////////////////////////////////////// |
1032 | + // Beat mutations |
1033 | + //////////////////////////////////////////////////////////////////////////// |
1034 | + |
1035 | + virtual void addBeat(double dBeatSample); |
1036 | + virtual void removeBeat(double dBeatSample); |
1037 | + virtual void moveBeat(double dBeatSample, double dNewBeatSample); |
1038 | + virtual void translate(double dNumSamples); |
1039 | + virtual void scale(double dScalePercentage); |
1040 | + |
1041 | + signals: |
1042 | + void updated(); |
1043 | + |
1044 | + private slots: |
1045 | + void slotTrackBpmUpdated(double bpm); |
1046 | + |
1047 | + private: |
1048 | + void readByteArray(const QByteArray* pByteArray); |
1049 | + // For internal use only. |
1050 | + bool isValid() const; |
1051 | + unsigned int numBeats() const; |
1052 | + |
1053 | + mutable QMutex m_mutex; |
1054 | + int m_iSampleRate; |
1055 | + BeatList m_beatList; |
1056 | +}; |
1057 | + |
1058 | +#endif /* BEATMATRIX_H */ |
1059 | |
1060 | === added file 'mixxx/src/track/beats.h' |
1061 | --- mixxx/src/track/beats.h 1970-01-01 00:00:00 +0000 |
1062 | +++ mixxx/src/track/beats.h 2011-03-07 05:53:37 +0000 |
1063 | @@ -0,0 +1,106 @@ |
1064 | +#ifndef BEATS_H |
1065 | +#define BEATS_H |
1066 | + |
1067 | +#include <QString> |
1068 | +#include <QList> |
1069 | +#include <QByteArray> |
1070 | +#include <QSharedPointer> |
1071 | + |
1072 | +class Beats; |
1073 | +typedef QSharedPointer<Beats> BeatsPointer; |
1074 | + |
1075 | +// QList's are attractive because they pre-allocate an internal buffer that is |
1076 | +// not free'd after a clear(). The downside is that they do not necessarily |
1077 | +// store adjecent items in adjacent memory locations. |
1078 | +typedef QList<double> BeatList; |
1079 | + |
1080 | +// Beats is a pure abstract base class for BPM and beat management classes. It |
1081 | +// provides a specification of all methods a beat-manager class must provide, as |
1082 | +// well as a capability model for representing optional features. |
1083 | +class Beats { |
1084 | + public: |
1085 | + Beats() { } |
1086 | + virtual ~Beats() { } |
1087 | + |
1088 | + enum Capabilities { |
1089 | + BEATSCAP_NONE = 0x0000, |
1090 | + BEATSCAP_ADDREMOVE = 0x0001, |
1091 | + BEATSCAP_TRANSLATE = 0x0002, |
1092 | + BEATSCAP_SCALE = 0x0004, |
1093 | + BEATSCAP_MOVEBEAT = 0x0008 |
1094 | + }; |
1095 | + typedef int CapabilitiesFlags; // Allows us to do ORing |
1096 | + |
1097 | + virtual Beats::CapabilitiesFlags getCapabilities() const = 0; |
1098 | + |
1099 | + // Serialization |
1100 | + virtual QByteArray* toByteArray() const = 0; |
1101 | + |
1102 | + // A string representing the version of the beat-processing code that |
1103 | + // produced this Beats instance. Used by BeatsFactory for associating a |
1104 | + // given serialization with the version that produced it. |
1105 | + virtual QString getVersion() const = 0; |
1106 | + |
1107 | + //////////////////////////////////////////////////////////////////////////// |
1108 | + // Beat calculations |
1109 | + //////////////////////////////////////////////////////////////////////////// |
1110 | + |
1111 | + // Starting from sample dSamples, return the sample of the next beat in the |
1112 | + // track, or -1 if none exists. If dSamples refers to the location of a |
1113 | + // beat, dSamples is returned. |
1114 | + virtual double findNextBeat(double dSamples) const = 0; |
1115 | + |
1116 | + // Starting from sample dSamples, return the sample of the previous beat in |
1117 | + // the track, or -1 if none exists. If dSamples refers to the location of |
1118 | + // beat, dSamples is returned. |
1119 | + virtual double findPrevBeat(double dSamples) const = 0; |
1120 | + |
1121 | + // Starting from sample dSamples, return the sample of the closest beat in |
1122 | + // the track, or -1 if none exists. |
1123 | + virtual double findClosestBeat(double dSamples) const = 0; |
1124 | + |
1125 | + // Find the Nth beat from sample dSamples. Works with both positive and |
1126 | + // negative values of n. Calling findNthBeat with n=0 is invalid. Calling |
1127 | + // findNthBeat with n=1 or n=-1 is equivalent to calling findNextBeat and |
1128 | + // findPrevBeat, respectively. If dSamples refers to the location of a beat, |
1129 | + // then dSamples is returned. If no beat can be found, returns -1.< |
1130 | + virtual double findNthBeat(double dSamples, int n) const = 0; |
1131 | + |
1132 | + // Adds to pBeatsList the position in samples of every beat occuring between |
1133 | + // startPosition and endPosition |
1134 | + virtual void findBeats(double startSample, double stopSample, BeatList* pBeatsList) const = 0; |
1135 | + |
1136 | + // Return whether or not a sample lies between startPosition and endPosition |
1137 | + virtual bool hasBeatInRange(double startSample, double stopSample) const = 0; |
1138 | + |
1139 | + // Return the average BPM over the entire track if the BPM is |
1140 | + // valid, otherwise returns -1 |
1141 | + virtual double getBpm() const = 0; |
1142 | + |
1143 | + // Return the average BPM over the range from startSample to endSample, |
1144 | + // specified in samples if the BPM is valid, otherwise returns -1 |
1145 | + virtual double getBpmRange(double startSample, double stopSample) const = 0; |
1146 | + |
1147 | + //////////////////////////////////////////////////////////////////////////// |
1148 | + // Beat mutations |
1149 | + //////////////////////////////////////////////////////////////////////////// |
1150 | + |
1151 | + // Add a beat at location dBeatSample. Beats instance must have the |
1152 | + // capability BEATSCAP_ADDREMOVE. |
1153 | + virtual void addBeat(double dBeatSample) = 0; |
1154 | + |
1155 | + // Remove a beat at location dBeatSample. Beats instance must have the |
1156 | + // capability BEATSCAP_ADDREMOVE. |
1157 | + virtual void removeBeat(double dBeatSample) = 0; |
1158 | + |
1159 | + // Translate all beats in the song by dNumSamples samples. Beats that lie |
1160 | + // before the start of the track or after the end of the track are not |
1161 | + // removed. Beats instance must have the capability BEATSCAP_TRANSLATE. |
1162 | + virtual void translate(double dNumSamples) = 0; |
1163 | + |
1164 | + // Scale the position of every beat in the song by dScalePercentage. Beats |
1165 | + // class must have the capability BEATSCAP_SCALE. |
1166 | + virtual void scale(double dScalePercentage) = 0; |
1167 | +}; |
1168 | + |
1169 | +#endif /* BEATS_H */ |
1170 | |
1171 | === modified file 'mixxx/src/trackinfoobject.cpp' |
1172 | --- mixxx/src/trackinfoobject.cpp 2010-11-24 15:20:09 +0000 |
1173 | +++ mixxx/src/trackinfoobject.cpp 2011-03-07 05:53:37 +0000 |
1174 | @@ -143,6 +143,7 @@ |
1175 | } |
1176 | |
1177 | TrackInfoObject::~TrackInfoObject() { |
1178 | + qDebug() << "~TrackInfoObject()"; |
1179 | } |
1180 | |
1181 | void TrackInfoObject::doSave() { |
1182 | @@ -341,6 +342,42 @@ |
1183 | m_bBpmConfirm = confirm; |
1184 | } |
1185 | |
1186 | +void TrackInfoObject::setBeats(BeatsPointer pBeats) { |
1187 | + QMutexLocker lock(&m_qMutex); |
1188 | + |
1189 | + // This whole method is not so great. The fact that Beats is an ABC is |
1190 | + // limiting with respect to QObject and signals/slots. |
1191 | + |
1192 | + QObject* pObject = NULL; |
1193 | + if (m_pBeats) { |
1194 | + pObject = dynamic_cast<QObject*>(m_pBeats.data()); |
1195 | + if (pObject) |
1196 | + pObject->disconnect(this, SIGNAL(updated())); |
1197 | + } |
1198 | + m_pBeats = pBeats; |
1199 | + pObject = dynamic_cast<QObject*>(m_pBeats.data()); |
1200 | + Q_ASSERT(pObject); |
1201 | + if (pObject) { |
1202 | + connect(pObject, SIGNAL(updated()), |
1203 | + this, SLOT(slotBeatsUpdated())); |
1204 | + } |
1205 | + setDirty(true); |
1206 | + lock.unlock(); |
1207 | + emit(beatsUpdated()); |
1208 | +} |
1209 | + |
1210 | +BeatsPointer TrackInfoObject::getBeats() const { |
1211 | + QMutexLocker lock(&m_qMutex); |
1212 | + return m_pBeats; |
1213 | +} |
1214 | + |
1215 | +void TrackInfoObject::slotBeatsUpdated() { |
1216 | + QMutexLocker lock(&m_qMutex); |
1217 | + setDirty(true); |
1218 | + lock.unlock(); |
1219 | + emit(beatsUpdated()); |
1220 | +} |
1221 | + |
1222 | bool TrackInfoObject::getHeaderParsed() const |
1223 | { |
1224 | QMutexLocker lock(&m_qMutex); |
1225 | |
1226 | === modified file 'mixxx/src/trackinfoobject.h' |
1227 | --- mixxx/src/trackinfoobject.h 2010-11-17 21:02:24 +0000 |
1228 | +++ mixxx/src/trackinfoobject.h 2011-03-07 05:53:37 +0000 |
1229 | @@ -28,7 +28,7 @@ |
1230 | #include <QWeakPointer> |
1231 | |
1232 | #include "defs.h" |
1233 | - |
1234 | +#include "track/beats.h" |
1235 | #include "library/dao/cue.h" |
1236 | |
1237 | class QString; |
1238 | @@ -242,6 +242,12 @@ |
1239 | /** Set the track's full file path */ |
1240 | void setLocation(QString location); |
1241 | |
1242 | + // Get the track's Beats list |
1243 | + BeatsPointer getBeats() const; |
1244 | + |
1245 | + // Set the track's Beats |
1246 | + void setBeats(BeatsPointer beats); |
1247 | + |
1248 | const Segmentation<QString>* getChordData(); |
1249 | void setChordData(Segmentation<QString> cd); |
1250 | |
1251 | @@ -251,6 +257,7 @@ |
1252 | signals: |
1253 | void wavesummaryUpdated(TrackInfoObject*); |
1254 | void bpmUpdated(double bpm); |
1255 | + void beatsUpdated(); |
1256 | void ReplayGainUpdated(double replaygain); |
1257 | void cuesUpdated(); |
1258 | void changed(); |
1259 | @@ -258,6 +265,9 @@ |
1260 | void clean(); |
1261 | void save(); |
1262 | |
1263 | + private slots: |
1264 | + void slotBeatsUpdated(); |
1265 | + |
1266 | private: |
1267 | |
1268 | // Common initialization function between all TIO constructors. |
1269 | @@ -370,6 +380,9 @@ |
1270 | double m_dVisualResampleRate; |
1271 | Segmentation<QString> m_chordData; |
1272 | |
1273 | + // Storage for the track's beats |
1274 | + BeatsPointer m_pBeats; |
1275 | + |
1276 | friend class TrackDAO; |
1277 | }; |
1278 | |
1279 | |
1280 | === modified file 'mixxx/src/waveform/waveformrenderbeat.cpp' |
1281 | --- mixxx/src/waveform/waveformrenderbeat.cpp 2010-11-25 01:06:46 +0000 |
1282 | +++ mixxx/src/waveform/waveformrenderbeat.cpp 2011-03-07 05:53:37 +0000 |
1283 | @@ -14,45 +14,26 @@ |
1284 | #include "widget/wskincolor.h" |
1285 | #include "widget/wwidget.h" |
1286 | #include "trackinfoobject.h" |
1287 | +#include "track/beats.h" |
1288 | + |
1289 | |
1290 | WaveformRenderBeat::WaveformRenderBeat(const char* group, WaveformRenderer *parent) |
1291 | : m_pParent(parent), |
1292 | - m_pBpm(NULL), |
1293 | - m_pBeatFirst(NULL), |
1294 | m_pTrackSamples(NULL), |
1295 | m_pTrack(), |
1296 | m_iWidth(0), |
1297 | m_iHeight(0), |
1298 | - m_dBpm(-1), |
1299 | - m_dBeatFirst(-1), |
1300 | m_dSamplesPerPixel(-1), |
1301 | m_dSamplesPerDownsample(-1), |
1302 | - m_dBeatLength(-1), |
1303 | m_iNumSamples(0), |
1304 | m_iSampleRate(-1) { |
1305 | - m_pBpm = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey(group, "file_bpm"))); |
1306 | - connect(m_pBpm, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateBpm(double))); |
1307 | - |
1308 | - //m_pBeatFirst = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey(group, "BeatFirst"))); |
1309 | - //connect(m_pBeatFirst, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateBeatFirst(double))); |
1310 | - |
1311 | - m_pTrackSamples = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey(group,"track_samples"))); |
1312 | + m_pTrackSamples = new ControlObjectThreadMain( |
1313 | + ControlObject::getControl(ConfigKey(group,"track_samples"))); |
1314 | slotUpdateTrackSamples(m_pTrackSamples->get()); |
1315 | connect(m_pTrackSamples, SIGNAL(valueChanged(double)), |
1316 | this, SLOT(slotUpdateTrackSamples(double))); |
1317 | } |
1318 | |
1319 | -void WaveformRenderBeat::slotUpdateBpm(double v) { |
1320 | - //qDebug() << "WaveformRenderBeat :: BPM = " << v; |
1321 | - m_dBpm = v; |
1322 | - m_dBeatLength = -1; |
1323 | -} |
1324 | - |
1325 | -void WaveformRenderBeat::slotUpdateBeatFirst(double v) { |
1326 | - //qDebug() << "WaveformRenderBeat :: beatFirst = " << v; |
1327 | - m_dBeatFirst = v; |
1328 | -} |
1329 | - |
1330 | void WaveformRenderBeat::slotUpdateTrackSamples(double samples) { |
1331 | //qDebug() << "WaveformRenderBeat :: samples = " << int(samples); |
1332 | m_iNumSamples = (int)samples; |
1333 | @@ -65,9 +46,7 @@ |
1334 | |
1335 | void WaveformRenderBeat::newTrack(TrackPointer pTrack) { |
1336 | m_pTrack = pTrack; |
1337 | - m_dBpm = -1; |
1338 | - m_dBeatFirst = -1; |
1339 | - m_dBeatLength = -1; |
1340 | + |
1341 | m_iNumSamples = 0; |
1342 | m_iSampleRate = 0; |
1343 | m_dSamplesPerDownsample = -1; |
1344 | @@ -90,13 +69,9 @@ |
1345 | |
1346 | m_dSamplesPerDownsample = n; |
1347 | m_dSamplesPerPixel = double(f)/z; |
1348 | - |
1349 | - //qDebug() << "WaveformRenderBeat sampleRate " << sampleRate << " samplesPerPixel " << m_dSamplesPerPixel; |
1350 | - |
1351 | } |
1352 | |
1353 | void WaveformRenderBeat::setup(QDomNode node) { |
1354 | - |
1355 | colorMarks.setNamedColor(WWidget::selectNodeQString(node, "BeatColor")); |
1356 | colorMarks = WSkinColor::getCorrectColor(colorMarks); |
1357 | |
1358 | @@ -108,10 +83,9 @@ |
1359 | colorHighlight = WSkinColor::getCorrectColor(colorHighlight); |
1360 | } |
1361 | |
1362 | - |
1363 | -void WaveformRenderBeat::draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double dPlayPos, double rateAdjust) { |
1364 | - if(m_dBpm == -1 || m_dBpm == 0) |
1365 | - return; |
1366 | +void WaveformRenderBeat::draw(QPainter *pPainter, QPaintEvent *event, |
1367 | + QVector<float> *buffer, double dPlayPos, double rateAdjust) { |
1368 | + int i; |
1369 | |
1370 | slotUpdateTrackSamples(m_pTrackSamples->get()); |
1371 | |
1372 | @@ -121,6 +95,10 @@ |
1373 | if(buffer == NULL) |
1374 | return; |
1375 | |
1376 | + BeatsPointer pBeats = m_pTrack->getBeats(); |
1377 | + if (!pBeats) |
1378 | + return; |
1379 | + |
1380 | int iCurPos = (int)(dPlayPos * m_iNumSamples); |
1381 | |
1382 | if(iCurPos % 2 != 0) |
1383 | @@ -137,11 +115,6 @@ |
1384 | // Therefore, sample s is a beat if it satisfies s % 60f/b == 0. |
1385 | // where s is a /mono/ sample |
1386 | |
1387 | - // beat length in samples |
1388 | - if(m_dBeatLength <= 0) { |
1389 | - m_dBeatLength = 60.0 * m_iSampleRate / m_dBpm; |
1390 | - } |
1391 | - |
1392 | double subpixelsPerPixel = m_pParent->getSubpixelsPerPixel()*(1.0+rateAdjust); |
1393 | const int oversample = (int)subpixelsPerPixel; |
1394 | |
1395 | @@ -161,12 +134,13 @@ |
1396 | double basePos = iCurPos - m_dSamplesPerPixel*halfw*(1.0+rateAdjust); |
1397 | double endPos = basePos + m_iWidth*m_dSamplesPerPixel*(1.0+rateAdjust); |
1398 | |
1399 | - // snap to the first beat |
1400 | - double curPos = ceilf(basePos/m_dBeatLength)*m_dBeatLength; |
1401 | + |
1402 | + m_beatList.clear(); |
1403 | + pBeats->findBeats(basePos, endPos, &m_beatList); |
1404 | |
1405 | bool reset = false; |
1406 | - for(;curPos <= endPos; curPos+=m_dBeatLength) { |
1407 | - if(curPos < 0) |
1408 | + foreach (double curPos, m_beatList) { |
1409 | + if (curPos < 0) |
1410 | continue; |
1411 | |
1412 | // i relative to the current play position in subpixels |
1413 | @@ -174,7 +148,7 @@ |
1414 | |
1415 | // If i is less than 20 subpixels from center, highlight it. |
1416 | if(abs(i) < 20) { |
1417 | - pPainter->setPen(colorHighlight); |
1418 | + pPainter->setPen(QColor(255,255,255)); |
1419 | reset = true; |
1420 | } |
1421 | |
1422 | @@ -190,5 +164,4 @@ |
1423 | } |
1424 | |
1425 | pPainter->restore(); |
1426 | - |
1427 | } |
1428 | |
1429 | === modified file 'mixxx/src/waveform/waveformrenderbeat.h' |
1430 | --- mixxx/src/waveform/waveformrenderbeat.h 2010-07-15 19:07:16 +0000 |
1431 | +++ mixxx/src/waveform/waveformrenderbeat.h 2011-03-07 05:53:37 +0000 |
1432 | @@ -12,40 +12,35 @@ |
1433 | class QPainter; |
1434 | class QPaintEvent; |
1435 | |
1436 | - |
1437 | class ControlObjectThreadMain; |
1438 | class WaveformRenderer; |
1439 | class SoundSourceProxy; |
1440 | |
1441 | class WaveformRenderBeat : public RenderObject { |
1442 | Q_OBJECT |
1443 | -public: |
1444 | + public: |
1445 | WaveformRenderBeat(const char *group, WaveformRenderer *parent); |
1446 | void resize(int w, int h); |
1447 | void setup(QDomNode node); |
1448 | - void draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double playPos, double rateAdjust); |
1449 | + void draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, |
1450 | + double playPos, double rateAdjust); |
1451 | void newTrack(TrackPointer pTrack); |
1452 | |
1453 | -public slots: |
1454 | - void slotUpdateBpm(double bpm); |
1455 | - void slotUpdateBeatFirst(double beatfirst); |
1456 | + public slots: |
1457 | void slotUpdateTrackSamples(double samples); |
1458 | -private: |
1459 | + private: |
1460 | WaveformRenderer *m_pParent; |
1461 | - ControlObjectThreadMain *m_pBpm; |
1462 | - ControlObjectThreadMain *m_pBeatFirst; |
1463 | ControlObjectThreadMain *m_pTrackSamples; |
1464 | TrackPointer m_pTrack; |
1465 | int m_iWidth, m_iHeight; |
1466 | - double m_dBpm; |
1467 | - double m_dBeatFirst; |
1468 | QColor colorMarks; |
1469 | QColor colorHighlight; |
1470 | double m_dSamplesPerPixel; |
1471 | double m_dSamplesPerDownsample; |
1472 | - double m_dBeatLength; |
1473 | int m_iNumSamples; |
1474 | int m_iSampleRate; |
1475 | + |
1476 | + BeatList m_beatList; |
1477 | }; |
1478 | |
1479 | #endif |
1480 | |
1481 | === modified file 'mixxx/src/waveform/waveformrenderer.cpp' |
1482 | --- mixxx/src/waveform/waveformrenderer.cpp 2010-08-27 20:10:20 +0000 |
1483 | +++ mixxx/src/waveform/waveformrenderer.cpp 2011-03-07 05:53:37 +0000 |
1484 | @@ -1,9 +1,3 @@ |
1485 | -/** |
1486 | - |
1487 | - A license and other info goes here! |
1488 | - |
1489 | - */ |
1490 | - |
1491 | #include <QDebug> |
1492 | #include <QDomNode> |
1493 | #include <QImage> |
1494 | @@ -126,6 +120,8 @@ |
1495 | |
1496 | |
1497 | WaveformRenderer::~WaveformRenderer() { |
1498 | + qDebug() << "~WaveformRenderer()"; |
1499 | + |
1500 | // Wait for the thread to quit |
1501 | m_bQuit = true; |
1502 | QThread::wait(); |
1503 | @@ -165,6 +161,13 @@ |
1504 | if(m_pRenderBeat) |
1505 | delete m_pRenderBeat; |
1506 | m_pRenderBeat = NULL; |
1507 | + |
1508 | + QMutableListIterator<RenderObject*> iter(m_renderObjects); |
1509 | + while (iter.hasNext()) { |
1510 | + RenderObject* ro = iter.next(); |
1511 | + iter.remove(); |
1512 | + delete ro; |
1513 | + } |
1514 | } |
1515 | |
1516 | void WaveformRenderer::slotUpdatePlayPos(double v) { |
1517 | |
1518 | === modified file 'mixxx/src/waveform/waveformrenderer.h' |
1519 | --- mixxx/src/waveform/waveformrenderer.h 2010-07-15 19:07:16 +0000 |
1520 | +++ mixxx/src/waveform/waveformrenderer.h 2011-03-07 05:53:37 +0000 |
1521 | @@ -28,7 +28,7 @@ |
1522 | Q_OBJECT |
1523 | public: |
1524 | WaveformRenderer(const char* group); |
1525 | - ~WaveformRenderer(); |
1526 | + virtual ~WaveformRenderer(); |
1527 | |
1528 | void resize(int w, int h); |
1529 | void draw(QPainter* pPainter, QPaintEvent *pEvent); |
Hey Phil,
Nice work. I went ahead and started hacking on it. Here's the rough outline of what I did:
1) Serialized beats had no version associated with them, so we have no way to deprecate a given way of generating them. I added a beats_version column in the schema to go along w/ the beats blob.
2) I wrote an abstract interface I called "Beats" which is the core of the methods behind TrackBeats, except without all the Seconds variants of the methods. I think it's best just to support one and make the caller deal w/ it. It makes the interface simpler.
3) I expanded the interface to cover measuring the average BPM, scaling the entire range, also finding the beat closest to a given sample.
4) I added a capabilities enum to the Beats so that a Beats implementation can express whether it supports mutation, translation, scaling, add/removing beats, and moving beats.
5) I made two Beats subclasses -- BeatGrid and BeatMatrix. A BeatGrid is a mathematically- backed beatgrid from an average BPM and a sample location of the first beat. A BeatMatrix is just TrackBeats renamed, it stores a BeatList of beat sample locations. I didn't really think it made sense to use a BeatMatrix for the transition, since it would waste a lot of database space from something that could be mathematically generated for free. A BeatGrid is serialized to the database as just those two doubles.
6) I updated the WaveformRenderBeat and AnalyserBpm to use the track's Beats instance.
7) I created a BeatFactory class to handle the creation and deletion of Beats instances.
8) Fixed some merge issues with trunk, some parameters in SQL statements were dropped.
I think you mentioned you saw the Beats were not getting destroyed. I see a bigger problem now which is that the Track's are not getting destroyed either. I think that this is a bug in Mixxx trunk right now too, so hopefully it gets fixed when it's fixed there. There are a lot of random parts of Mixxx that are not getting deleted correctly, so we just have to find all these.
Since I'm hacking on this too, I think Albert should be the branch reviewer.