Merge lp:~mixxxdevelopers/mixxx/features_trackbeats into lp:~mixxxdevelopers/mixxx/trunk

Proposed by Phillip Whelan
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
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.

Description of the change

This branch adds the TrackBeats data type for tracking beats as sample offsets. This version uses SoundTouch to derive the beats. A future version will work with aubio, possibly only in the Analyse pane.

To post a comment you must log in.
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.

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

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.

review: Approve
2542. By RJ Skerry-Ryan

Fix SQL statement that got munged by a merge.

Revision history for this message
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/

Revision history for this message
Albert Santoni (gamegod) wrote :

- Fix the commented pointer leak in TrackDAO::bindTrackToLibraryInsert()

- 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::toByteArray() has a hardcoded sizeof(double) * 2 here:

+ QByteArray* pByteArray = new QByteArray((char*)pBuffer, sizeof(double) * 2);

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(MyBeatsStruct)).

- void BeatGrid::readByteArray(QByteArray* pByteArray) ---> Did you mean a const QByteArray* ?
:)

- BeatGrid::findNthBeat(...) might lead to unintended behaviour if dSamples is negative. (???)

- 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 "ExtrapolatedBeatGrid" and "CustomBeatGrid".

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

review: Needs Fixing
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).

Revision history for this message
Phillip Whelan (pwhelan) wrote :

> - BeatGrid::findNthBeat(...) might lead to unintended behaviour if dSamples is negative. (???)

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

Revision history for this message
Albert Santoni (gamegod) wrote :

Phil and I fixed the remaining stuff from my review. Approve and merged into trunk tonight.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'mixxx/build/depends.py'
--- mixxx/build/depends.py 2011-03-04 09:09:44 +0000
+++ mixxx/build/depends.py 2011-03-07 05:53:37 +0000
@@ -540,6 +540,10 @@
540540
541 "sampleutil.cpp",541 "sampleutil.cpp",
542 "trackinfoobject.cpp",542 "trackinfoobject.cpp",
543 "track/beatgrid.cpp",
544 "track/beatmatrix.cpp",
545 "track/beatfactory.cpp",
546
543 "baseplayer.cpp",547 "baseplayer.cpp",
544 "basetrackplayer.cpp",548 "basetrackplayer.cpp",
545 "deck.cpp",549 "deck.cpp",
546550
=== modified file 'mixxx/res/schema.xml'
--- mixxx/res/schema.xml 2011-02-21 07:01:11 +0000
+++ mixxx/res/schema.xml 2011-03-07 05:53:37 +0000
@@ -266,4 +266,13 @@
266 );266 );
267 </sql>267 </sql>
268 </revision>268 </revision>
269 <revision version="12" min_compatible="3">
270 <description>
271 Add beats column to library table.
272 </description>
273 <sql>
274 ALTER TABLE Library ADD COLUMN beats BLOB;
275 ALTER TABLE Library ADD COLUMN beats_version TEXT;
276 </sql>
277 </revision>
269</schema>278</schema>
270279
=== modified file 'mixxx/src/analyserbpm.cpp'
--- mixxx/src/analyserbpm.cpp 2010-10-19 01:35:26 +0000
+++ mixxx/src/analyserbpm.cpp 2011-03-07 05:53:37 +0000
@@ -3,6 +3,8 @@
33
4#include "BPMDetect.h"4#include "BPMDetect.h"
5#include "trackinfoobject.h"5#include "trackinfoobject.h"
6#include "track/beatgrid.h"
7#include "track/beatfactory.h"
6#include "analyserbpm.h"8#include "analyserbpm.h"
79
810
@@ -20,9 +22,9 @@
20 // int defaultrange = m_pConfig->getValueString(ConfigKey("[BPM]","BPMAboveRangeEnabled")).toInt();22 // int defaultrange = m_pConfig->getValueString(ConfigKey("[BPM]","BPMAboveRangeEnabled")).toInt();
21 bool bpmEnabled = (bool)m_pConfig->getValueString(ConfigKey("[BPM]","BPMDetectionEnabled")).toInt();23 bool bpmEnabled = (bool)m_pConfig->getValueString(ConfigKey("[BPM]","BPMDetectionEnabled")).toInt();
2224
23 // If BPM detection is not enabled, or the track already has BPM detection done.25 // If BPM detection is enabled and the track does not have a BPM already,
24 if(bpmEnabled &&26 // create a detector.
25 tio->getBpm() == 0.) {27 if(bpmEnabled && tio->getBpm() == 0.) {
26 // All SoundSource's return stereo data, no matter the real file's type28 // All SoundSource's return stereo data, no matter the real file's type
27 m_pDetector = new soundtouch::BPMDetect(2, sampleRate);29 m_pDetector = new soundtouch::BPMDetect(2, sampleRate);
28 //m_pDetector = new BPMDetect(tio->getChannels(), sampleRate);30 //m_pDetector = new BPMDetect(tio->getChannels(), sampleRate);
@@ -31,10 +33,7 @@
31 }33 }
32}34}
3335
34
35
36void AnalyserBPM::process(const CSAMPLE *pIn, const int iLen) {36void AnalyserBPM::process(const CSAMPLE *pIn, const int iLen) {
37
38 // Check if BPM detection is enabled37 // Check if BPM detection is enabled
39 if(m_pDetector == NULL) {38 if(m_pDetector == NULL) {
40 return;39 return;
@@ -42,7 +41,6 @@
42 //qDebug() << "AnalyserBPM::process() processing " << iLen << " samples";41 //qDebug() << "AnalyserBPM::process() processing " << iLen << " samples";
4342
44 m_pDetector->inputSamples(pIn, iLen/2);43 m_pDetector->inputSamples(pIn, iLen/2);
45
46}44}
4745
48float AnalyserBPM::correctBPM( float BPM, int min, int max, int aboveRange) {46float AnalyserBPM::correctBPM( float BPM, int min, int max, int aboveRange) {
@@ -71,6 +69,13 @@
7169
72 tio->setBpm(newbpm);70 tio->setBpm(newbpm);
73 tio->setBpmConfirm();71 tio->setBpmConfirm();
72
73 // Currently, the BPM is only analyzed if the track has no BPM. This
74 // means we don't have to worry that the track already has an existing
75 // BeatGrid.
76 BeatsPointer pBeats = BeatFactory::makeBeatGrid(tio, newbpm, 0.0f);
77 tio->setBeats(pBeats);
78
74 //if(pBpmReceiver) {79 //if(pBpmReceiver) {
75 //pBpmReceiver->setComplete(tio, false, bpm);80 //pBpmReceiver->setComplete(tio, false, bpm);
76 //}81 //}
7782
=== modified file 'mixxx/src/analyserbpm.h'
--- mixxx/src/analyserbpm.h 2010-07-15 19:07:16 +0000
+++ mixxx/src/analyserbpm.h 2011-03-07 05:53:37 +0000
@@ -8,7 +8,6 @@
88
99
10class AnalyserBPM : public Analyser {10class AnalyserBPM : public Analyser {
11
12 public:11 public:
13 AnalyserBPM(ConfigObject<ConfigValue> *_config);12 AnalyserBPM(ConfigObject<ConfigValue> *_config);
14 void initialise(TrackPointer tio, int sampleRate, int totalSamples);13 void initialise(TrackPointer tio, int sampleRate, int totalSamples);
@@ -22,7 +21,6 @@
22 soundtouch::BPMDetect *m_pDetector;21 soundtouch::BPMDetect *m_pDetector;
23 int m_iMinBpm, m_iMaxBpm;22 int m_iMinBpm, m_iMaxBpm;
24 bool m_bProcessEntireSong;23 bool m_bProcessEntireSong;
25
26};24};
2725
28#endif26#endif
2927
=== modified file 'mixxx/src/basetrackplayer.cpp'
--- mixxx/src/basetrackplayer.cpp 2010-11-17 21:02:24 +0000
+++ mixxx/src/basetrackplayer.cpp 2011-03-07 05:53:37 +0000
@@ -98,6 +98,7 @@
98 delete m_pPlayPosition;98 delete m_pPlayPosition;
99 delete m_pBPM;99 delete m_pBPM;
100 delete m_pReplayGain;100 delete m_pReplayGain;
101 delete m_pWaveformRenderer;
101}102}
102103
103void BaseTrackPlayer::slotLoadTrack(TrackPointer track, bool bStartFromEndPos)104void BaseTrackPlayer::slotLoadTrack(TrackPointer track, bool bStartFromEndPos)
@@ -184,7 +185,7 @@
184 // Read the tags if required185 // Read the tags if required
185 if(!m_pLoadedTrack->getHeaderParsed())186 if(!m_pLoadedTrack->getHeaderParsed())
186 SoundSourceProxy::ParseHeader(m_pLoadedTrack.data());187 SoundSourceProxy::ParseHeader(m_pLoadedTrack.data());
187 188
188 m_pLoadedTrack->incTimesPlayed();189 m_pLoadedTrack->incTimesPlayed();
189190
190 // Generate waveform summary191 // Generate waveform summary
191192
=== modified file 'mixxx/src/library/dao/trackdao.cpp'
--- mixxx/src/library/dao/trackdao.cpp 2010-12-02 22:17:33 +0000
+++ mixxx/src/library/dao/trackdao.cpp 2011-03-07 05:53:37 +0000
@@ -2,7 +2,10 @@
2#include <QtDebug>2#include <QtDebug>
3#include <QtCore>3#include <QtCore>
4#include <QtSql>4#include <QtSql>
5
5#include "trackinfoobject.h"6#include "trackinfoobject.h"
7#include "track/beats.h"
8#include "track/beatfactory.h"
6#include "library/dao/trackdao.h"9#include "library/dao/trackdao.h"
7#include "audiotagger.h"10#include "audiotagger.h"
811
@@ -35,6 +38,8 @@
3538
36TrackDAO::~TrackDAO()39TrackDAO::~TrackDAO()
37{40{
41 qDebug() << "~TrackDAO()";
42 clearCache();
38}43}
3944
40void TrackDAO::initialize()45void TrackDAO::initialize()
@@ -218,13 +223,13 @@
218 "filetype, location, comment, url, duration, rating, key, "223 "filetype, location, comment, url, duration, rating, key, "
219 "bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, "224 "bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, "
220 "timesplayed, "225 "timesplayed, "
221 "channels, mixxx_deleted, header_parsed) "226 "channels, mixxx_deleted, header_parsed, beats_version, beats) "
222 "VALUES (:artist, "227 "VALUES (:artist, "
223 ":title, :album, :year, :genre, :tracknumber, "228 ":title, :album, :year, :genre, :tracknumber, "
224 ":filetype, :location, :comment, :url, :duration, :rating, :key,"229 ":filetype, :location, :comment, :url, :duration, :rating, :key, "
225 ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :wavesummaryhex, "230 ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :wavesummaryhex, "
226 ":timesplayed, "231 ":timesplayed, "
227 ":channels, :mixxx_deleted, :header_parsed)");232 ":channels, :mixxx_deleted, :header_parsed, :beats_version, :beats)");
228}233}
229234
230void TrackDAO::bindTrackToLibraryInsert(QSqlQuery& query, TrackInfoObject* pTrack, int trackLocationId) {235void TrackDAO::bindTrackToLibraryInsert(QSqlQuery& query, TrackInfoObject* pTrack, int trackLocationId) {
@@ -243,17 +248,33 @@
243 query.bindValue(":bitrate", pTrack->getBitrate());248 query.bindValue(":bitrate", pTrack->getBitrate());
244 query.bindValue(":samplerate", pTrack->getSampleRate());249 query.bindValue(":samplerate", pTrack->getSampleRate());
245 query.bindValue(":cuepoint", pTrack->getCuePoint());250 query.bindValue(":cuepoint", pTrack->getCuePoint());
246 query.bindValue(":bpm", pTrack->getBpm());251
247 query.bindValue(":replaygain", pTrack->getReplayGain());252 query.bindValue(":replaygain", pTrack->getReplayGain());
248 query.bindValue(":key", pTrack->getKey());253 query.bindValue(":key", pTrack->getKey());
249 const QByteArray* pWaveSummary = pTrack->getWaveSummary();254 const QByteArray* pWaveSummary = pTrack->getWaveSummary();
250 if (pWaveSummary) //Avoid null pointer deref255 query.bindValue(":wavesummaryhex", pWaveSummary ? *pWaveSummary : QVariant(QVariant::ByteArray));
251 query.bindValue(":wavesummaryhex", *pWaveSummary);
252 query.bindValue(":timesplayed", pTrack->getTimesPlayed());256 query.bindValue(":timesplayed", pTrack->getTimesPlayed());
253 //query.bindValue(":datetime_added", pTrack->getDateAdded());257 //query.bindValue(":datetime_added", pTrack->getDateAdded());
254 query.bindValue(":channels", pTrack->getChannels());258 query.bindValue(":channels", pTrack->getChannels());
255 query.bindValue(":mixxx_deleted", 0);259 query.bindValue(":mixxx_deleted", 0);
256 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0);260 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0);
261
262 const QByteArray* pBeatsBlob = NULL;
263 QString blobVersion = "";
264 BeatsPointer pBeats = pTrack->getBeats();
265 // Fall back on cached BPM
266 double dBpm = pTrack->getBpm();
267
268 if (pBeats) {
269 pBeatsBlob = pBeats->toByteArray();
270 blobVersion = pBeats->getVersion();
271 dBpm = pBeats->getBpm();
272 }
273
274 query.bindValue(":bpm", dBpm);
275 query.bindValue(":beats_version", blobVersion);
276 query.bindValue(":beats", pBeatsBlob ? *pBeatsBlob : QVariant(QVariant::ByteArray));
277 delete pBeatsBlob;
257}278}
258279
259void TrackDAO::addTracks(QList<TrackInfoObject*> tracksToAdd) {280void TrackDAO::addTracks(QList<TrackInfoObject*> tracksToAdd) {
@@ -306,7 +327,8 @@
306 bindTrackToLibraryInsert(query, pTrack, pTrack->getId());327 bindTrackToLibraryInsert(query, pTrack, pTrack->getId());
307328
308 if (!query.exec()) {329 if (!query.exec()) {
309 qDebug() << "Failed to INSERT new track into library"330 qDebug() << "Failed to INSERT new track into library:"
331 << pTrack->getFilename()
310 << __FILE__ << __LINE__ << query.lastError();332 << __FILE__ << __LINE__ << query.lastError();
311 m_database.rollback();333 m_database.rollback();
312 return;334 return;
@@ -495,7 +517,7 @@
495void TrackDAO::deleteTrack(TrackInfoObject* pTrack) {517void TrackDAO::deleteTrack(TrackInfoObject* pTrack) {
496 Q_ASSERT(pTrack);518 Q_ASSERT(pTrack);
497519
498 //qDebug() << "Got deletion call for track" << pTrack << "ID" << pTrack->getId() << pTrack->getInfo();520 qDebug() << "Got deletion call for track" << pTrack << "ID" << pTrack->getId() << pTrack->getInfo();
499521
500 // Save dirty tracks.522 // Save dirty tracks.
501 pTrack->save();523 pTrack->save();
@@ -521,8 +543,8 @@
521543
522 int locationId = -1;544 int locationId = -1;
523 while (query.next()) {545 while (query.next()) {
546 // Good god! Assign query.record() to a freaking variable!
524 int trackId = query.value(query.record().indexOf("id")).toInt();547 int trackId = query.value(query.record().indexOf("id")).toInt();
525
526 QString artist = query.value(query.record().indexOf("artist")).toString();548 QString artist = query.value(query.record().indexOf("artist")).toString();
527 QString title = query.value(query.record().indexOf("title")).toString();549 QString title = query.value(query.record().indexOf("title")).toString();
528 QString album = query.value(query.record().indexOf("album")).toString();550 QString album = query.value(query.record().indexOf("album")).toString();
@@ -550,6 +572,7 @@
550 bool header_parsed = query.value(query.record().indexOf("header_parsed")).toBool();572 bool header_parsed = query.value(query.record().indexOf("header_parsed")).toBool();
551573
552 TrackInfoObject* track = new TrackInfoObject(location, false);574 TrackInfoObject* track = new TrackInfoObject(location, false);
575 TrackPointer pTrack = TrackPointer(track, this->deleteTrack);
553576
554 // TIO already stats the file to see if it exists, what its length is,577 // TIO already stats the file to see if it exists, what its length is,
555 // etc. So don't bother setting it.578 // etc. So don't bother setting it.
@@ -575,6 +598,14 @@
575 track->setReplayGain(replaygain.toFloat());598 track->setReplayGain(replaygain.toFloat());
576 track->setWaveSummary(wavesummaryhex, false);599 track->setWaveSummary(wavesummaryhex, false);
577 delete wavesummaryhex;600 delete wavesummaryhex;
601
602 QString beatsVersion = query.value(query.record().indexOf("beats_version")).toString();
603 QByteArray beatsBlob = query.value(query.record().indexOf("beats")).toByteArray();
604 BeatsPointer pBeats = BeatFactory::loadBeatsFromByteArray(pTrack, beatsVersion, &beatsBlob);
605 if (pBeats) {
606 pTrack->setBeats(pBeats);
607 }
608
578 track->setTimesPlayed(timesplayed);609 track->setTimesPlayed(timesplayed);
579 track->setPlayed(played);610 track->setPlayed(played);
580 track->setChannels(channels);611 track->setChannels(channels);
@@ -593,8 +624,6 @@
593 connect(track, SIGNAL(save()),624 connect(track, SIGNAL(save()),
594 this, SLOT(slotTrackSave()));625 this, SLOT(slotTrackSave()));
595626
596 TrackPointer pTrack = TrackPointer(track, this->deleteTrack);
597
598 // Automatic conversion to a weak pointer627 // Automatic conversion to a weak pointer
599 m_tracks[trackId] = pTrack;628 m_tracks[trackId] = pTrack;
600 m_trackCache.insert(trackId, new TrackPointer(pTrack));629 m_trackCache.insert(trackId, new TrackPointer(pTrack));
@@ -606,6 +635,15 @@
606 pTrack->parse();635 pTrack->parse();
607 }636 }
608637
638 if (!pBeats && pTrack->getBpm() != 0.0f) {
639 // The track has no stored beats but has a previously detected BPM
640 // or a BPM loaded from metadata. Automatically create a beatgrid
641 // for the track. This dirties the track so we have to do it after
642 // all the signal connections, etc. are in place.
643 BeatsPointer pBeats = BeatFactory::makeBeatGrid(pTrack, pTrack->getBpm(), 0.0f);
644 pTrack->setBeats(pBeats);
645 }
646
609 return pTrack;647 return pTrack;
610 }648 }
611 //query.finish();649 //query.finish();
@@ -647,7 +685,17 @@
647 time.start();685 time.start();
648 QSqlQuery query(m_database);686 QSqlQuery query(m_database);
649687
650 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));688 query.prepare(
689 "SELECT library.id, artist, title, album, year, genre, tracknumber, "
690 "filetype, rating, key, track_locations.location as location, "
691 "track_locations.filesize as filesize, comment, url, duration, bitrate, "
692 "samplerate, cuepoint, bpm, replaygain, wavesummaryhex, channels, "
693 "header_parsed, timesplayed, played, beats_version, beats "
694 "FROM Library "
695 "INNER JOIN track_locations "
696 "ON library.location = track_locations.id "
697 "WHERE library.id=" + QString("%1").arg(id)
698 );
651699
652 TrackPointer pTrack;700 TrackPointer pTrack;
653701
@@ -686,7 +734,8 @@
686 "bitrate=:bitrate, samplerate=:samplerate, cuepoint=:cuepoint, "734 "bitrate=:bitrate, samplerate=:samplerate, cuepoint=:cuepoint, "
687 "bpm=:bpm, replaygain=:replaygain, wavesummaryhex=:wavesummaryhex, "735 "bpm=:bpm, replaygain=:replaygain, wavesummaryhex=:wavesummaryhex, "
688 "timesplayed=:timesplayed, played=:played, "736 "timesplayed=:timesplayed, played=:played, "
689 "channels=:channels, header_parsed=:header_parsed "737 "channels=:channels, header_parsed=:header_parsed, "
738 "beats_version=:beats_version, beats=:beats "
690 "WHERE id="+QString("%1").arg(trackId));739 "WHERE id="+QString("%1").arg(trackId));
691 query.bindValue(":artist", pTrack->getArtist());740 query.bindValue(":artist", pTrack->getArtist());
692 query.bindValue(":title", pTrack->getTitle());741 query.bindValue(":title", pTrack->getTitle());
@@ -701,19 +750,33 @@
701 query.bindValue(":bitrate", pTrack->getBitrate());750 query.bindValue(":bitrate", pTrack->getBitrate());
702 query.bindValue(":samplerate", pTrack->getSampleRate());751 query.bindValue(":samplerate", pTrack->getSampleRate());
703 query.bindValue(":cuepoint", pTrack->getCuePoint());752 query.bindValue(":cuepoint", pTrack->getCuePoint());
704 query.bindValue(":bpm", pTrack->getBpm());753
705 query.bindValue(":replaygain", pTrack->getReplayGain());754 query.bindValue(":replaygain", pTrack->getReplayGain());
706 query.bindValue(":key", pTrack->getKey());755 query.bindValue(":key", pTrack->getKey());
707 query.bindValue(":rating", pTrack->getRating());756 query.bindValue(":rating", pTrack->getRating());
708 const QByteArray* pWaveSummary = pTrack->getWaveSummary();757 const QByteArray* pWaveSummary = pTrack->getWaveSummary();
709 if (pWaveSummary) //Avoid null pointer deref758 query.bindValue(":wavesummaryhex", pWaveSummary ? *pWaveSummary : QVariant(QVariant::ByteArray));
710 query.bindValue(":wavesummaryhex", *pWaveSummary);
711 query.bindValue(":timesplayed", pTrack->getTimesPlayed());759 query.bindValue(":timesplayed", pTrack->getTimesPlayed());
712 query.bindValue(":played", pTrack->getPlayed());760 query.bindValue(":played", pTrack->getPlayed());
713 query.bindValue(":channels", pTrack->getChannels());761 query.bindValue(":channels", pTrack->getChannels());
714 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0);762 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0);
715 //query.bindValue(":location", pTrack->getLocation());763 //query.bindValue(":location", pTrack->getLocation());
716764
765 BeatsPointer pBeats = pTrack->getBeats();
766 QByteArray* pBeatsBlob = NULL;
767 QString beatsVersion = "";
768 double dBpm = pTrack->getBpm();
769
770 if (pBeats) {
771 pBeatsBlob = pBeats->toByteArray();
772 beatsVersion = pBeats->getVersion();
773 dBpm = pBeats->getBpm();
774 }
775
776 query.bindValue(":beats", pBeatsBlob ? *pBeatsBlob : QVariant(QVariant::ByteArray));
777 query.bindValue(":beats_version", beatsVersion);
778 query.bindValue(":bpm", dBpm);
779
717 if (!query.exec()) {780 if (!query.exec()) {
718 qDebug() << query.lastError();781 qDebug() << query.lastError();
719 m_database.rollback();782 m_database.rollback();
720783
=== modified file 'mixxx/src/library/trackcollection.cpp'
--- mixxx/src/library/trackcollection.cpp 2011-02-21 07:01:11 +0000
+++ mixxx/src/library/trackcollection.cpp 2011-03-07 05:53:37 +0000
@@ -68,7 +68,7 @@
68 return false;68 return false;
69 }69 }
7070
71 int requiredSchemaVersion = 11;71 int requiredSchemaVersion = 12;
72 if (!SchemaManager::upgradeToSchemaVersion(m_pConfig, m_db,72 if (!SchemaManager::upgradeToSchemaVersion(m_pConfig, m_db,
73 requiredSchemaVersion)) {73 requiredSchemaVersion)) {
74 QMessageBox::warning(0, tr("Cannot upgrade database schema"),74 QMessageBox::warning(0, tr("Cannot upgrade database schema"),
7575
=== added directory 'mixxx/src/track'
=== added file 'mixxx/src/track/beatfactory.cpp'
--- mixxx/src/track/beatfactory.cpp 1970-01-01 00:00:00 +0000
+++ mixxx/src/track/beatfactory.cpp 2011-03-07 05:53:37 +0000
@@ -0,0 +1,43 @@
1#include <QtDebug>
2
3#include "track/beatgrid.h"
4#include "track/beatmatrix.h"
5#include "track/beatfactory.h"
6
7BeatsPointer BeatFactory::loadBeatsFromByteArray(TrackPointer pTrack, QString beatsVersion,
8 QByteArray* beatsSerialized) {
9 // TODO(XXX) make it so the version strings are not in multiple places
10 if (beatsVersion == "BeatGrid-1.0") {
11 BeatGrid* pGrid = new BeatGrid(pTrack, beatsSerialized);
12 pGrid->moveToThread(pTrack->thread());
13 pGrid->setParent(pTrack.data());
14 qDebug() << "Successfully deserialized BeatGrid";
15 return BeatsPointer(pGrid, &BeatFactory::deleteBeats);
16 } else if (beatsVersion == "BeatMatrix-1.0") {
17 BeatMatrix* pMatrix = new BeatMatrix(pTrack, beatsSerialized);
18 pMatrix->moveToThread(pTrack->thread());
19 pMatrix->setParent(pTrack.data());
20 qDebug() << "Successfully deserialized BeatMatrix";
21 return BeatsPointer(pMatrix, &BeatFactory::deleteBeats);
22 }
23 qDebug() << "BeatFactory::loadBeatsFromByteArray could not parse serialized beats.";
24 return BeatsPointer();
25}
26
27BeatsPointer BeatFactory::makeBeatGrid(TrackPointer pTrack, double dBpm, double dFirstBeatSample) {
28 BeatGrid* pGrid = new BeatGrid(pTrack);
29 pGrid->setGrid(dBpm, dFirstBeatSample);
30 return BeatsPointer(pGrid, &BeatFactory::deleteBeats);
31}
32
33void BeatFactory::deleteBeats(Beats* pBeats) {
34 // This assumes all Beats* variants multiply-inherit from QObject. Kind of ugly. Oh well.
35 QObject* pObject = dynamic_cast<QObject*>(pBeats);
36
37 if (pObject != NULL) {
38 qDebug() << "Deleting QObject instance.";
39 pObject->deleteLater();
40 } else {
41 qDebug() << "Could not delete Beats instance, did not dynamic_cast to QObject";
42 }
43}
044
=== added file 'mixxx/src/track/beatfactory.h'
--- mixxx/src/track/beatfactory.h 1970-01-01 00:00:00 +0000
+++ mixxx/src/track/beatfactory.h 2011-03-07 05:53:37 +0000
@@ -0,0 +1,19 @@
1#ifndef BEATFACTORY_H
2#define BEATFACTORY_H
3
4#include "track/beats.h"
5#include "track/beatgrid.h"
6#include "track/beatmatrix.h"
7
8class BeatFactory {
9 public:
10 static BeatsPointer loadBeatsFromByteArray(TrackPointer pTrack,
11 QString beatsVersion,
12 QByteArray* beatsSerialized);
13 static BeatsPointer makeBeatGrid(TrackPointer pTrack,
14 double dBpm, double dFirstBeatSample);
15 private:
16 static void deleteBeats(Beats* pBeats);
17};
18
19#endif /* BEATFACTORY_H */
020
=== added file 'mixxx/src/track/beatgrid.cpp'
--- mixxx/src/track/beatgrid.cpp 1970-01-01 00:00:00 +0000
+++ mixxx/src/track/beatgrid.cpp 2011-03-07 05:53:37 +0000
@@ -0,0 +1,186 @@
1#include <QMutexLocker>
2#include <QDebug>
3
4#include "track/beatgrid.h"
5
6
7struct BeatGridData {
8 double bpm;
9 double firstBeat;
10};
11
12BeatGrid::BeatGrid(TrackPointer pTrack, const QByteArray* pByteArray)
13 : QObject(),
14 m_mutex(QMutex::Recursive),
15 m_iSampleRate(pTrack->getSampleRate()),
16 m_dBpm(0.0f),
17 m_dFirstBeat(0.0f),
18 m_dBeatLength(0.0f) {
19 connect(pTrack.data(), SIGNAL(bpmUpdated(double)),
20 this, SLOT(slotTrackBpmUpdated(double)));
21
22 qDebug() << "New BeatGrid";
23 if (pByteArray != NULL) {
24 readByteArray(pByteArray);
25 }
26}
27
28BeatGrid::~BeatGrid() {
29
30}
31
32void BeatGrid::setGrid(double dBpm, double dFirstBeatSample) {
33 QMutexLocker lock(&m_mutex);
34 m_dBpm = dBpm;
35 m_dFirstBeat = dFirstBeatSample;
36 m_dBeatLength = 60.0 * m_iSampleRate / m_dBpm;
37}
38
39QByteArray* BeatGrid::toByteArray() const {
40 QMutexLocker locker(&m_mutex);
41 BeatGridData blob = { m_dBpm, m_dFirstBeat };
42 QByteArray* pByteArray = new QByteArray((char *)&blob, sizeof(blob));
43 return pByteArray;
44}
45
46void BeatGrid::readByteArray(const QByteArray* pByteArray) {
47 if ( pByteArray->size() != sizeof(BeatGridData))
48 return;
49 BeatGridData *blob = (BeatGridData *)pByteArray->data();
50 setGrid(blob->bpm, blob->firstBeat);
51}
52
53QString BeatGrid::getVersion() const {
54 QMutexLocker locker(&m_mutex);
55 return "BeatGrid-1.0";
56}
57
58// internal use only
59bool BeatGrid::isValid() const {
60 return m_iSampleRate > 0 && m_dBpm > 0;
61}
62
63// This could be implemented in the Beats Class itself.
64// If necessary, the child class can redefine it.
65double BeatGrid::findNextBeat(double dSamples) const {
66 return findNthBeat(dSamples, +1);
67}
68
69// This could be implemented in the Beats Class itself.
70// If necessary, the child class can redefine it.
71double BeatGrid::findPrevBeat(double dSamples) const {
72 return findNthBeat(dSamples, -1);
73}
74
75// This is an internal call. This could be implemented in the Beats Class itself.
76double BeatGrid::findClosestBeat(double dSamples) const {
77 QMutexLocker locker(&m_mutex);
78 if (!isValid()) {
79 return -1;
80 }
81 double nextBeat = findNextBeat(dSamples);
82 double prevBeat = findPrevBeat(dSamples);
83 return (nextBeat - dSamples > dSamples - prevBeat) ? prevBeat : nextBeat;
84}
85
86double BeatGrid::findNthBeat(double dSamples, int n) const {
87 QMutexLocker locker(&m_mutex);
88 if (!isValid() || n == 0) {
89 return -1;
90 }
91
92 double dClosestBeat;
93 if (n > 0) {
94 // We're going forward, so use ceilf to round up to the next multiple of
95 // m_dBeatLength
96 dClosestBeat = ceilf(dSamples/m_dBeatLength) * m_dBeatLength + m_dFirstBeat;
97 n = n - 1;
98 } else {
99 // We're going backward, so use floorf to round up to the next multiple
100 // of m_dBeatLength
101 dClosestBeat = floorf(dSamples/m_dBeatLength) * m_dBeatLength + m_dFirstBeat;
102 n = n + 1;
103 }
104
105 return dClosestBeat + n * m_dBeatLength;
106}
107
108void BeatGrid::findBeats(double startSample, double stopSample, QList<double>* pBeatsList) const {
109 QMutexLocker locker(&m_mutex);
110 if (!isValid() || startSample > stopSample) {
111 return;
112 }
113 double curBeat = findNextBeat(startSample);
114 while (curBeat <= stopSample) {
115 pBeatsList->append(curBeat);
116 curBeat += m_dBeatLength;
117 }
118}
119
120bool BeatGrid::hasBeatInRange(double startSample, double stopSample) const {
121 QMutexLocker locker(&m_mutex);
122 if (!isValid() || startSample > stopSample) {
123 return false;
124 }
125 double curBeat = findNextBeat(startSample);
126 if (curBeat <= stopSample) {
127 return true;
128 }
129 return false;
130}
131
132double BeatGrid::getBpm() const {
133 QMutexLocker locker(&m_mutex);
134 if (!isValid()) {
135 return -1;
136 }
137 return m_dBpm;
138}
139
140double BeatGrid::getBpmRange(double startSample, double stopSample) const {
141 QMutexLocker locker(&m_mutex);
142 if (!isValid() || startSample > stopSample) {
143 return -1;
144 }
145 return m_dBpm;
146}
147
148void BeatGrid::addBeat(double dBeatSample) {
149 //QMutexLocker locker(&m_mutex);
150 return;
151}
152
153void BeatGrid::removeBeat(double dBeatSample) {
154 //QMutexLocker locker(&m_mutex);
155 return;
156}
157
158void BeatGrid::moveBeat(double dBeatSample, double dNewBeatSample) {
159 //QMutexLocker locker(&m_mutex);
160 return;
161}
162
163void BeatGrid::translate(double dNumSamples) {
164 QMutexLocker locker(&m_mutex);
165 if (!isValid()) {
166 return;
167 }
168 m_dFirstBeat += dNumSamples;
169 locker.unlock();
170 emit(updated());
171}
172
173void BeatGrid::scale(double dScalePercentage) {
174 QMutexLocker locker(&m_mutex);
175 if (!isValid()) {
176 return;
177 }
178 m_dBpm *= dScalePercentage;
179 locker.unlock();
180 emit(updated());
181}
182
183void BeatGrid::slotTrackBpmUpdated(double dBpm) {
184 QMutexLocker locker(&m_mutex);
185 m_dBpm = dBpm;
186}
0187
=== added file 'mixxx/src/track/beatgrid.h'
--- mixxx/src/track/beatgrid.h 1970-01-01 00:00:00 +0000
+++ mixxx/src/track/beatgrid.h 2011-03-07 05:53:37 +0000
@@ -0,0 +1,75 @@
1#ifndef BEATGRID_H
2#define BEATGRID_H
3
4#include <QMutex>
5#include <QObject>
6
7#include "trackinfoobject.h"
8#include "track/beats.h"
9
10// BeatGrid is an implementation of the Beats interface that implements an
11// infinite grid of beats, aligned to a song simply by a starting offset of the
12// first beat and the song's average beats-per-minute.
13class BeatGrid : public QObject, public virtual Beats {
14 Q_OBJECT
15 public:
16 BeatGrid(TrackPointer pTrack, const QByteArray* pByteArray=NULL);
17 virtual ~BeatGrid();
18
19 // Initializes the BeatGrid to have a BPM of dBpm and the first beat offset
20 // of dFirstBeatSample. Does not generate an updated() signal, since it is
21 // meant for initialization.
22 void setGrid(double dBpm, double dFirstBeatSample);
23
24 // The following are all methods from the Beats interface, see method
25 // comments in beats.h
26
27 virtual Beats::CapabilitiesFlags getCapabilities() const {
28 return BEATSCAP_TRANSLATE | BEATSCAP_SCALE;
29 }
30
31 virtual QByteArray* toByteArray() const;
32 virtual QString getVersion() const;
33
34 ////////////////////////////////////////////////////////////////////////////
35 // Beat calculations
36 ////////////////////////////////////////////////////////////////////////////
37
38 virtual double findNextBeat(double dSamples) const;
39 virtual double findPrevBeat(double dSamples) const;
40 virtual double findClosestBeat(double dSamples) const;
41 virtual double findNthBeat(double dSamples, int n) const;
42 virtual void findBeats(double startSample, double stopSample, BeatList* pBeatsList) const;
43 virtual bool hasBeatInRange(double startSample, double stopSample) const;
44 virtual double getBpm() const;
45 virtual double getBpmRange(double startSample, double stopSample) const;
46
47 ////////////////////////////////////////////////////////////////////////////
48 // Beat mutations
49 ////////////////////////////////////////////////////////////////////////////
50
51 virtual void addBeat(double dBeatSample);
52 virtual void removeBeat(double dBeatSample);
53 virtual void moveBeat(double dBeatSample, double dNewBeatSample);
54 virtual void translate(double dNumSamples);
55 virtual void scale(double dScalePercentage);
56
57 signals:
58 void updated();
59
60 private slots:
61 void slotTrackBpmUpdated(double bpm);
62
63 private:
64 void readByteArray(const QByteArray* pByteArray);
65 // For internal use only.
66 bool isValid() const;
67
68 mutable QMutex m_mutex;
69 int m_iSampleRate;
70 double m_dBpm, m_dFirstBeat;
71 double m_dBeatLength;
72};
73
74
75#endif /* BEATGRID_H */
076
=== added file 'mixxx/src/track/beatmatrix.cpp'
--- mixxx/src/track/beatmatrix.cpp 1970-01-01 00:00:00 +0000
+++ mixxx/src/track/beatmatrix.cpp 2011-03-07 05:53:37 +0000
@@ -0,0 +1,271 @@
1#include <QtDebug>
2#include <QMutexLocker>
3
4#include "track/beatmatrix.h"
5
6BeatMatrix::BeatMatrix(TrackPointer pTrack, const QByteArray* pByteArray)
7 : QObject(),
8 m_mutex(QMutex::Recursive),
9 m_iSampleRate(pTrack->getSampleRate()) {
10 if (pByteArray != NULL) {
11 readByteArray(pByteArray);
12 }
13}
14
15BeatMatrix::~BeatMatrix() {
16}
17
18unsigned int BeatMatrix::numBeats() const {
19 return m_beatList.size();
20}
21
22QByteArray* BeatMatrix::toByteArray() const {
23 QMutexLocker locker(&m_mutex);
24 // No guarantees BeatLists are made of a data type which located adjacent
25 // items in adjacent memory locations.
26 double* pBuffer = new double[m_beatList.size()];
27 for (int i = 0; i < m_beatList.size(); ++i) {
28 pBuffer[i] = m_beatList[i];
29 }
30 QByteArray* pByteArray = new QByteArray((char*)pBuffer, sizeof(pBuffer[0]) * m_beatList.size());
31 delete [] pBuffer;
32 return pByteArray;
33}
34
35void BeatMatrix::readByteArray(const QByteArray* pByteArray) {
36 if (pByteArray->size() % sizeof(double) != 0) {
37 qDebug() << "ERROR: Could not parse BeatMatrix from QByteArray of size" << pByteArray->size();
38 return;
39 }
40
41 int numBeats = pByteArray->size() / sizeof(double);
42 double* pBuffer = (double*)pByteArray->data();
43 for (int i = 0; i < numBeats; ++i) {
44 m_beatList.append(pBuffer[i]);
45 }
46}
47
48
49QString BeatMatrix::getVersion() const {
50 QMutexLocker locker(&m_mutex);
51 return "BeatMatrix-1.0";
52}
53
54// internal use only
55bool BeatMatrix::isValid() const {
56 return m_iSampleRate > 0 && m_beatList.size() > 0;
57}
58
59double BeatMatrix::findNextBeat(double dSamples) const {
60 return findNthBeat(dSamples, 1);
61}
62
63double BeatMatrix::findPrevBeat(double dSamples) const {
64 return findNthBeat(dSamples, -1);
65}
66
67double BeatMatrix::findClosestBeat(double dSamples) const {
68 QMutexLocker locker(&m_mutex);
69 if (!isValid()) {
70 return -1;
71 }
72 double nextBeat = findNextBeat(dSamples);
73 double prevBeat = findPrevBeat(dSamples);
74 return (nextBeat - dSamples > dSamples - prevBeat) ? prevBeat : nextBeat;
75}
76
77double BeatMatrix::findNthBeat(double dSamples, int n) const {
78 QMutexLocker locker(&m_mutex);
79 BeatList::const_iterator it;
80 int i;
81
82 if (!isValid() || n == 0) {
83 return -1;
84 }
85
86 if (n > 0) {
87 it = qLowerBound(m_beatList.begin(), m_beatList.end(), dSamples);
88
89 // Count down until n=1
90 while (it != m_beatList.end()) {
91 if (n == 1) {
92 return *it;
93 }
94 it++; n--;
95 }
96 }
97 else if (n < 0) {
98 it = qUpperBound(m_beatList.begin(), m_beatList.end(), dSamples);
99
100 // Count up until n=-1
101 while (it != m_beatList.begin()) {
102 // qUpperBound starts us off at the position just-one-past the last
103 // occurence of dSamples-or-smaller in the list. In order to get the
104 // last instance of dSamples-or-smaller, we decrement it by 1 before
105 // touching it. The guard of this while loop guarantees this does
106 // not put us before the start of the loop.
107 it--;
108 if (n == -1) {
109 return *it;
110 }
111 n++;
112 }
113 }
114
115 return -1;
116}
117
118void BeatMatrix::findBeats(double startSample, double stopSample, QList<double>* pBeatsList) const {
119 QMutexLocker locker(&m_mutex);
120 if (!isValid() || startSample > stopSample) {
121 return;
122 }
123 BeatList::const_iterator curBeat = qLowerBound(m_beatList.begin(),
124 m_beatList.end(),
125 startSample);
126 BeatList::const_iterator stopBeat = qUpperBound(m_beatList.begin(),
127 m_beatList.end(),
128 stopSample);
129
130 for (; curBeat != stopBeat; curBeat++) {
131 pBeatsList->append(*curBeat);
132 }
133}
134
135bool BeatMatrix::hasBeatInRange(double startSample, double stopSample) const {
136 QMutexLocker locker(&m_mutex);
137 if (!isValid() || startSample > stopSample) {
138 return false;
139 }
140 BeatList::const_iterator startBeat = qLowerBound(m_beatList.begin(),
141 m_beatList.end(),
142 startSample);
143 BeatList::const_iterator stopBeat = qUpperBound(m_beatList.begin(),
144 m_beatList.end(),
145 stopSample);
146
147 if (startBeat != stopBeat)
148 return true;
149 return false;
150}
151
152double BeatMatrix::getBpm() const {
153 QMutexLocker locker(&m_mutex);
154 if (!isValid()) {
155 return -1;
156 }
157
158 // TODO(XXX) not actually correct. We need the true song length.
159 double startSample = *m_beatList.begin();
160 double stopSample = *(m_beatList.end()-1);
161 double songDurationMinutes =
162 (stopSample - startSample) / (60.0f * m_iSampleRate);
163 return m_beatList.size() / songDurationMinutes;
164}
165
166double BeatMatrix::getBpmRange(double startSample, double stopSample) const {
167 QMutexLocker locker(&m_mutex);
168 if (!isValid() || startSample > stopSample) {
169 return -1;
170 }
171
172 BeatList::const_iterator startBeat = qLowerBound(m_beatList.begin(),
173 m_beatList.end(),
174 startSample);
175 BeatList::const_iterator stopBeat = qUpperBound(m_beatList.begin(),
176 m_beatList.end(),
177 stopSample);
178 double rangeDurationMinutes =
179 (stopSample - startSample) / (60.0f * m_iSampleRate);
180 // Subtracting returns the number of beats between the samples referred to
181 // by the start and end.
182 double beatsInRange = stopBeat - startBeat;
183
184 return beatsInRange / rangeDurationMinutes;
185}
186
187void BeatMatrix::addBeat(double dBeatSample) {
188 QMutexLocker locker(&m_mutex);
189
190 BeatList::iterator it = qLowerBound(m_beatList.begin(),
191 m_beatList.end(),
192 dBeatSample);
193 // Don't insert a duplicate beat. TODO(XXX) determine what epsilon to
194 // consider a beat identical to another.
195 if (*it == dBeatSample)
196 return;
197
198 m_beatList.insert(it, dBeatSample);
199 locker.unlock();
200 emit(updated());
201}
202
203void BeatMatrix::removeBeat(double dBeatSample) {
204 QMutexLocker locker(&m_mutex);
205 BeatList::iterator it = qLowerBound(m_beatList.begin(),
206 m_beatList.end(), dBeatSample);
207
208 // In case there are duplicates, remove every instance of dBeatSample
209 // TODO(XXX) add invariant checks against this
210 // TODO(XXX) determine what epsilon to consider a beat identical to another
211 while (*it == dBeatSample) {
212 it = m_beatList.erase(it);
213 }
214 locker.unlock();
215 emit(updated());
216}
217
218void BeatMatrix::moveBeat(double dBeatSample, double dNewBeatSample) {
219 QMutexLocker locker(&m_mutex);
220
221 BeatList::iterator it = qLowerBound(m_beatList.begin(),
222 m_beatList.end(), dBeatSample);
223
224 // Remove all beats from dBeatSample
225 while (*it == dBeatSample) {
226 it = m_beatList.erase(it);
227 }
228
229 // Now add a beat to dNewBeatSample
230 it = qLowerBound(m_beatList.begin(),
231 m_beatList.end(), dNewBeatSample);
232
233 // TODO(XXX) beat epsilon
234 if (*it != dNewBeatSample) {
235 m_beatList.insert(it, dNewBeatSample);
236 }
237 locker.unlock();
238 emit(updated());
239}
240
241void BeatMatrix::translate(double dNumSamples) {
242 QMutexLocker locker(&m_mutex);
243 if (!isValid()) {
244 return;
245 }
246
247 for (BeatList::iterator it = m_beatList.begin();
248 it != m_beatList.end(); ++it) {
249 *it += dNumSamples;
250 }
251 locker.unlock();
252 emit(updated());
253}
254
255void BeatMatrix::scale(double dScalePercentage) {
256 QMutexLocker locker(&m_mutex);
257 if (!isValid()) {
258 return;
259 }
260 for (BeatList::iterator it = m_beatList.begin();
261 it != m_beatList.end(); ++it) {
262 *it *= dScalePercentage;
263 }
264 locker.unlock();
265 emit(updated());
266}
267
268void BeatMatrix::slotTrackBpmUpdated(double dBpm) {
269 //QMutexLocker locker(&m_mutex);
270 // TODO(XXX) How do we handle this?
271}
0272
=== added file 'mixxx/src/track/beatmatrix.h'
--- mixxx/src/track/beatmatrix.h 1970-01-01 00:00:00 +0000
+++ mixxx/src/track/beatmatrix.h 2011-03-07 05:53:37 +0000
@@ -0,0 +1,67 @@
1#ifndef BEATMATRIX_H
2#define BEATMATRIX_H
3
4#include <QObject>
5#include <QMutex>
6
7#include "trackinfoobject.h"
8#include "track/beats.h"
9
10// BeatMatrix is an implementation of the Beats interface that implements a list
11// of finite beats that have been extracted or otherwise annotated for a track.
12class BeatMatrix : public QObject, public Beats {
13 Q_OBJECT
14 public:
15 BeatMatrix(TrackPointer pTrack, const QByteArray* pByteArray=NULL);
16 virtual ~BeatMatrix();
17
18 // See method comments in beats.h
19
20 virtual Beats::CapabilitiesFlags getCapabilities() const {
21 return BEATSCAP_TRANSLATE | BEATSCAP_SCALE | BEATSCAP_ADDREMOVE | BEATSCAP_MOVEBEAT;
22 }
23
24 virtual QByteArray* toByteArray() const;
25 virtual QString getVersion() const;
26
27 ////////////////////////////////////////////////////////////////////////////
28 // Beat calculations
29 ////////////////////////////////////////////////////////////////////////////
30
31 virtual double findNextBeat(double dSamples) const;
32 virtual double findPrevBeat(double dSamples) const;
33 virtual double findClosestBeat(double dSamples) const;
34 virtual double findNthBeat(double dSamples, int n) const;
35 virtual void findBeats(double startSample, double stopSample, QList<double>* pBeatsList) const;
36 virtual bool hasBeatInRange(double startSample, double stopSample) const;
37 virtual double getBpm() const;
38 virtual double getBpmRange(double startSample, double stopSample) const;
39
40 ////////////////////////////////////////////////////////////////////////////
41 // Beat mutations
42 ////////////////////////////////////////////////////////////////////////////
43
44 virtual void addBeat(double dBeatSample);
45 virtual void removeBeat(double dBeatSample);
46 virtual void moveBeat(double dBeatSample, double dNewBeatSample);
47 virtual void translate(double dNumSamples);
48 virtual void scale(double dScalePercentage);
49
50 signals:
51 void updated();
52
53 private slots:
54 void slotTrackBpmUpdated(double bpm);
55
56 private:
57 void readByteArray(const QByteArray* pByteArray);
58 // For internal use only.
59 bool isValid() const;
60 unsigned int numBeats() const;
61
62 mutable QMutex m_mutex;
63 int m_iSampleRate;
64 BeatList m_beatList;
65};
66
67#endif /* BEATMATRIX_H */
068
=== added file 'mixxx/src/track/beats.h'
--- mixxx/src/track/beats.h 1970-01-01 00:00:00 +0000
+++ mixxx/src/track/beats.h 2011-03-07 05:53:37 +0000
@@ -0,0 +1,106 @@
1#ifndef BEATS_H
2#define BEATS_H
3
4#include <QString>
5#include <QList>
6#include <QByteArray>
7#include <QSharedPointer>
8
9class Beats;
10typedef QSharedPointer<Beats> BeatsPointer;
11
12// QList's are attractive because they pre-allocate an internal buffer that is
13// not free'd after a clear(). The downside is that they do not necessarily
14// store adjecent items in adjacent memory locations.
15typedef QList<double> BeatList;
16
17// Beats is a pure abstract base class for BPM and beat management classes. It
18// provides a specification of all methods a beat-manager class must provide, as
19// well as a capability model for representing optional features.
20class Beats {
21 public:
22 Beats() { }
23 virtual ~Beats() { }
24
25 enum Capabilities {
26 BEATSCAP_NONE = 0x0000,
27 BEATSCAP_ADDREMOVE = 0x0001,
28 BEATSCAP_TRANSLATE = 0x0002,
29 BEATSCAP_SCALE = 0x0004,
30 BEATSCAP_MOVEBEAT = 0x0008
31 };
32 typedef int CapabilitiesFlags; // Allows us to do ORing
33
34 virtual Beats::CapabilitiesFlags getCapabilities() const = 0;
35
36 // Serialization
37 virtual QByteArray* toByteArray() const = 0;
38
39 // A string representing the version of the beat-processing code that
40 // produced this Beats instance. Used by BeatsFactory for associating a
41 // given serialization with the version that produced it.
42 virtual QString getVersion() const = 0;
43
44 ////////////////////////////////////////////////////////////////////////////
45 // Beat calculations
46 ////////////////////////////////////////////////////////////////////////////
47
48 // Starting from sample dSamples, return the sample of the next beat in the
49 // track, or -1 if none exists. If dSamples refers to the location of a
50 // beat, dSamples is returned.
51 virtual double findNextBeat(double dSamples) const = 0;
52
53 // Starting from sample dSamples, return the sample of the previous beat in
54 // the track, or -1 if none exists. If dSamples refers to the location of
55 // beat, dSamples is returned.
56 virtual double findPrevBeat(double dSamples) const = 0;
57
58 // Starting from sample dSamples, return the sample of the closest beat in
59 // the track, or -1 if none exists.
60 virtual double findClosestBeat(double dSamples) const = 0;
61
62 // Find the Nth beat from sample dSamples. Works with both positive and
63 // negative values of n. Calling findNthBeat with n=0 is invalid. Calling
64 // findNthBeat with n=1 or n=-1 is equivalent to calling findNextBeat and
65 // findPrevBeat, respectively. If dSamples refers to the location of a beat,
66 // then dSamples is returned. If no beat can be found, returns -1.<
67 virtual double findNthBeat(double dSamples, int n) const = 0;
68
69 // Adds to pBeatsList the position in samples of every beat occuring between
70 // startPosition and endPosition
71 virtual void findBeats(double startSample, double stopSample, BeatList* pBeatsList) const = 0;
72
73 // Return whether or not a sample lies between startPosition and endPosition
74 virtual bool hasBeatInRange(double startSample, double stopSample) const = 0;
75
76 // Return the average BPM over the entire track if the BPM is
77 // valid, otherwise returns -1
78 virtual double getBpm() const = 0;
79
80 // Return the average BPM over the range from startSample to endSample,
81 // specified in samples if the BPM is valid, otherwise returns -1
82 virtual double getBpmRange(double startSample, double stopSample) const = 0;
83
84 ////////////////////////////////////////////////////////////////////////////
85 // Beat mutations
86 ////////////////////////////////////////////////////////////////////////////
87
88 // Add a beat at location dBeatSample. Beats instance must have the
89 // capability BEATSCAP_ADDREMOVE.
90 virtual void addBeat(double dBeatSample) = 0;
91
92 // Remove a beat at location dBeatSample. Beats instance must have the
93 // capability BEATSCAP_ADDREMOVE.
94 virtual void removeBeat(double dBeatSample) = 0;
95
96 // Translate all beats in the song by dNumSamples samples. Beats that lie
97 // before the start of the track or after the end of the track are not
98 // removed. Beats instance must have the capability BEATSCAP_TRANSLATE.
99 virtual void translate(double dNumSamples) = 0;
100
101 // Scale the position of every beat in the song by dScalePercentage. Beats
102 // class must have the capability BEATSCAP_SCALE.
103 virtual void scale(double dScalePercentage) = 0;
104};
105
106#endif /* BEATS_H */
0107
=== modified file 'mixxx/src/trackinfoobject.cpp'
--- mixxx/src/trackinfoobject.cpp 2010-11-24 15:20:09 +0000
+++ mixxx/src/trackinfoobject.cpp 2011-03-07 05:53:37 +0000
@@ -143,6 +143,7 @@
143}143}
144144
145TrackInfoObject::~TrackInfoObject() {145TrackInfoObject::~TrackInfoObject() {
146 qDebug() << "~TrackInfoObject()";
146}147}
147148
148void TrackInfoObject::doSave() {149void TrackInfoObject::doSave() {
@@ -341,6 +342,42 @@
341 m_bBpmConfirm = confirm;342 m_bBpmConfirm = confirm;
342}343}
343344
345void TrackInfoObject::setBeats(BeatsPointer pBeats) {
346 QMutexLocker lock(&m_qMutex);
347
348 // This whole method is not so great. The fact that Beats is an ABC is
349 // limiting with respect to QObject and signals/slots.
350
351 QObject* pObject = NULL;
352 if (m_pBeats) {
353 pObject = dynamic_cast<QObject*>(m_pBeats.data());
354 if (pObject)
355 pObject->disconnect(this, SIGNAL(updated()));
356 }
357 m_pBeats = pBeats;
358 pObject = dynamic_cast<QObject*>(m_pBeats.data());
359 Q_ASSERT(pObject);
360 if (pObject) {
361 connect(pObject, SIGNAL(updated()),
362 this, SLOT(slotBeatsUpdated()));
363 }
364 setDirty(true);
365 lock.unlock();
366 emit(beatsUpdated());
367}
368
369BeatsPointer TrackInfoObject::getBeats() const {
370 QMutexLocker lock(&m_qMutex);
371 return m_pBeats;
372}
373
374void TrackInfoObject::slotBeatsUpdated() {
375 QMutexLocker lock(&m_qMutex);
376 setDirty(true);
377 lock.unlock();
378 emit(beatsUpdated());
379}
380
344bool TrackInfoObject::getHeaderParsed() const381bool TrackInfoObject::getHeaderParsed() const
345{382{
346 QMutexLocker lock(&m_qMutex);383 QMutexLocker lock(&m_qMutex);
347384
=== modified file 'mixxx/src/trackinfoobject.h'
--- mixxx/src/trackinfoobject.h 2010-11-17 21:02:24 +0000
+++ mixxx/src/trackinfoobject.h 2011-03-07 05:53:37 +0000
@@ -28,7 +28,7 @@
28#include <QWeakPointer>28#include <QWeakPointer>
2929
30#include "defs.h"30#include "defs.h"
3131#include "track/beats.h"
32#include "library/dao/cue.h"32#include "library/dao/cue.h"
3333
34class QString;34class QString;
@@ -242,6 +242,12 @@
242 /** Set the track's full file path */242 /** Set the track's full file path */
243 void setLocation(QString location);243 void setLocation(QString location);
244244
245 // Get the track's Beats list
246 BeatsPointer getBeats() const;
247
248 // Set the track's Beats
249 void setBeats(BeatsPointer beats);
250
245 const Segmentation<QString>* getChordData();251 const Segmentation<QString>* getChordData();
246 void setChordData(Segmentation<QString> cd);252 void setChordData(Segmentation<QString> cd);
247253
@@ -251,6 +257,7 @@
251 signals:257 signals:
252 void wavesummaryUpdated(TrackInfoObject*);258 void wavesummaryUpdated(TrackInfoObject*);
253 void bpmUpdated(double bpm);259 void bpmUpdated(double bpm);
260 void beatsUpdated();
254 void ReplayGainUpdated(double replaygain);261 void ReplayGainUpdated(double replaygain);
255 void cuesUpdated();262 void cuesUpdated();
256 void changed();263 void changed();
@@ -258,6 +265,9 @@
258 void clean();265 void clean();
259 void save();266 void save();
260267
268 private slots:
269 void slotBeatsUpdated();
270
261 private:271 private:
262272
263 // Common initialization function between all TIO constructors.273 // Common initialization function between all TIO constructors.
@@ -370,6 +380,9 @@
370 double m_dVisualResampleRate;380 double m_dVisualResampleRate;
371 Segmentation<QString> m_chordData;381 Segmentation<QString> m_chordData;
372382
383 // Storage for the track's beats
384 BeatsPointer m_pBeats;
385
373 friend class TrackDAO;386 friend class TrackDAO;
374};387};
375388
376389
=== modified file 'mixxx/src/waveform/waveformrenderbeat.cpp'
--- mixxx/src/waveform/waveformrenderbeat.cpp 2010-11-25 01:06:46 +0000
+++ mixxx/src/waveform/waveformrenderbeat.cpp 2011-03-07 05:53:37 +0000
@@ -14,45 +14,26 @@
14#include "widget/wskincolor.h"14#include "widget/wskincolor.h"
15#include "widget/wwidget.h"15#include "widget/wwidget.h"
16#include "trackinfoobject.h"16#include "trackinfoobject.h"
17#include "track/beats.h"
18
1719
18WaveformRenderBeat::WaveformRenderBeat(const char* group, WaveformRenderer *parent)20WaveformRenderBeat::WaveformRenderBeat(const char* group, WaveformRenderer *parent)
19 : m_pParent(parent),21 : m_pParent(parent),
20 m_pBpm(NULL),
21 m_pBeatFirst(NULL),
22 m_pTrackSamples(NULL),22 m_pTrackSamples(NULL),
23 m_pTrack(),23 m_pTrack(),
24 m_iWidth(0),24 m_iWidth(0),
25 m_iHeight(0),25 m_iHeight(0),
26 m_dBpm(-1),
27 m_dBeatFirst(-1),
28 m_dSamplesPerPixel(-1),26 m_dSamplesPerPixel(-1),
29 m_dSamplesPerDownsample(-1),27 m_dSamplesPerDownsample(-1),
30 m_dBeatLength(-1),
31 m_iNumSamples(0),28 m_iNumSamples(0),
32 m_iSampleRate(-1) {29 m_iSampleRate(-1) {
33 m_pBpm = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey(group, "file_bpm")));30 m_pTrackSamples = new ControlObjectThreadMain(
34 connect(m_pBpm, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateBpm(double)));31 ControlObject::getControl(ConfigKey(group,"track_samples")));
35
36 //m_pBeatFirst = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey(group, "BeatFirst")));
37 //connect(m_pBeatFirst, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateBeatFirst(double)));
38
39 m_pTrackSamples = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey(group,"track_samples")));
40 slotUpdateTrackSamples(m_pTrackSamples->get());32 slotUpdateTrackSamples(m_pTrackSamples->get());
41 connect(m_pTrackSamples, SIGNAL(valueChanged(double)),33 connect(m_pTrackSamples, SIGNAL(valueChanged(double)),
42 this, SLOT(slotUpdateTrackSamples(double)));34 this, SLOT(slotUpdateTrackSamples(double)));
43}35}
4436
45void WaveformRenderBeat::slotUpdateBpm(double v) {
46 //qDebug() << "WaveformRenderBeat :: BPM = " << v;
47 m_dBpm = v;
48 m_dBeatLength = -1;
49}
50
51void WaveformRenderBeat::slotUpdateBeatFirst(double v) {
52 //qDebug() << "WaveformRenderBeat :: beatFirst = " << v;
53 m_dBeatFirst = v;
54}
55
56void WaveformRenderBeat::slotUpdateTrackSamples(double samples) {37void WaveformRenderBeat::slotUpdateTrackSamples(double samples) {
57 //qDebug() << "WaveformRenderBeat :: samples = " << int(samples);38 //qDebug() << "WaveformRenderBeat :: samples = " << int(samples);
58 m_iNumSamples = (int)samples;39 m_iNumSamples = (int)samples;
@@ -65,9 +46,7 @@
6546
66void WaveformRenderBeat::newTrack(TrackPointer pTrack) {47void WaveformRenderBeat::newTrack(TrackPointer pTrack) {
67 m_pTrack = pTrack;48 m_pTrack = pTrack;
68 m_dBpm = -1;49
69 m_dBeatFirst = -1;
70 m_dBeatLength = -1;
71 m_iNumSamples = 0;50 m_iNumSamples = 0;
72 m_iSampleRate = 0;51 m_iSampleRate = 0;
73 m_dSamplesPerDownsample = -1;52 m_dSamplesPerDownsample = -1;
@@ -90,13 +69,9 @@
9069
91 m_dSamplesPerDownsample = n;70 m_dSamplesPerDownsample = n;
92 m_dSamplesPerPixel = double(f)/z;71 m_dSamplesPerPixel = double(f)/z;
93
94 //qDebug() << "WaveformRenderBeat sampleRate " << sampleRate << " samplesPerPixel " << m_dSamplesPerPixel;
95
96}72}
9773
98void WaveformRenderBeat::setup(QDomNode node) {74void WaveformRenderBeat::setup(QDomNode node) {
99
100 colorMarks.setNamedColor(WWidget::selectNodeQString(node, "BeatColor"));75 colorMarks.setNamedColor(WWidget::selectNodeQString(node, "BeatColor"));
101 colorMarks = WSkinColor::getCorrectColor(colorMarks);76 colorMarks = WSkinColor::getCorrectColor(colorMarks);
10277
@@ -108,10 +83,9 @@
108 colorHighlight = WSkinColor::getCorrectColor(colorHighlight);83 colorHighlight = WSkinColor::getCorrectColor(colorHighlight);
109}84}
11085
11186void WaveformRenderBeat::draw(QPainter *pPainter, QPaintEvent *event,
112void WaveformRenderBeat::draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double dPlayPos, double rateAdjust) {87 QVector<float> *buffer, double dPlayPos, double rateAdjust) {
113 if(m_dBpm == -1 || m_dBpm == 0)88 int i;
114 return;
11589
116 slotUpdateTrackSamples(m_pTrackSamples->get());90 slotUpdateTrackSamples(m_pTrackSamples->get());
11791
@@ -121,6 +95,10 @@
121 if(buffer == NULL)95 if(buffer == NULL)
122 return;96 return;
12397
98 BeatsPointer pBeats = m_pTrack->getBeats();
99 if (!pBeats)
100 return;
101
124 int iCurPos = (int)(dPlayPos * m_iNumSamples);102 int iCurPos = (int)(dPlayPos * m_iNumSamples);
125103
126 if(iCurPos % 2 != 0)104 if(iCurPos % 2 != 0)
@@ -137,11 +115,6 @@
137 // Therefore, sample s is a beat if it satisfies s % 60f/b == 0.115 // Therefore, sample s is a beat if it satisfies s % 60f/b == 0.
138 // where s is a /mono/ sample116 // where s is a /mono/ sample
139117
140 // beat length in samples
141 if(m_dBeatLength <= 0) {
142 m_dBeatLength = 60.0 * m_iSampleRate / m_dBpm;
143 }
144
145 double subpixelsPerPixel = m_pParent->getSubpixelsPerPixel()*(1.0+rateAdjust);118 double subpixelsPerPixel = m_pParent->getSubpixelsPerPixel()*(1.0+rateAdjust);
146 const int oversample = (int)subpixelsPerPixel;119 const int oversample = (int)subpixelsPerPixel;
147120
@@ -161,12 +134,13 @@
161 double basePos = iCurPos - m_dSamplesPerPixel*halfw*(1.0+rateAdjust);134 double basePos = iCurPos - m_dSamplesPerPixel*halfw*(1.0+rateAdjust);
162 double endPos = basePos + m_iWidth*m_dSamplesPerPixel*(1.0+rateAdjust);135 double endPos = basePos + m_iWidth*m_dSamplesPerPixel*(1.0+rateAdjust);
163136
164 // snap to the first beat137
165 double curPos = ceilf(basePos/m_dBeatLength)*m_dBeatLength;138 m_beatList.clear();
139 pBeats->findBeats(basePos, endPos, &m_beatList);
166140
167 bool reset = false;141 bool reset = false;
168 for(;curPos <= endPos; curPos+=m_dBeatLength) {142 foreach (double curPos, m_beatList) {
169 if(curPos < 0)143 if (curPos < 0)
170 continue;144 continue;
171145
172 // i relative to the current play position in subpixels146 // i relative to the current play position in subpixels
@@ -174,7 +148,7 @@
174148
175 // If i is less than 20 subpixels from center, highlight it.149 // If i is less than 20 subpixels from center, highlight it.
176 if(abs(i) < 20) {150 if(abs(i) < 20) {
177 pPainter->setPen(colorHighlight);151 pPainter->setPen(QColor(255,255,255));
178 reset = true;152 reset = true;
179 }153 }
180154
@@ -190,5 +164,4 @@
190 }164 }
191165
192 pPainter->restore();166 pPainter->restore();
193
194}167}
195168
=== modified file 'mixxx/src/waveform/waveformrenderbeat.h'
--- mixxx/src/waveform/waveformrenderbeat.h 2010-07-15 19:07:16 +0000
+++ mixxx/src/waveform/waveformrenderbeat.h 2011-03-07 05:53:37 +0000
@@ -12,40 +12,35 @@
12class QPainter;12class QPainter;
13class QPaintEvent;13class QPaintEvent;
1414
15
16class ControlObjectThreadMain;15class ControlObjectThreadMain;
17class WaveformRenderer;16class WaveformRenderer;
18class SoundSourceProxy;17class SoundSourceProxy;
1918
20class WaveformRenderBeat : public RenderObject {19class WaveformRenderBeat : public RenderObject {
21 Q_OBJECT20 Q_OBJECT
22public:21 public:
23 WaveformRenderBeat(const char *group, WaveformRenderer *parent);22 WaveformRenderBeat(const char *group, WaveformRenderer *parent);
24 void resize(int w, int h);23 void resize(int w, int h);
25 void setup(QDomNode node);24 void setup(QDomNode node);
26 void draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer, double playPos, double rateAdjust);25 void draw(QPainter *pPainter, QPaintEvent *event, QVector<float> *buffer,
26 double playPos, double rateAdjust);
27 void newTrack(TrackPointer pTrack);27 void newTrack(TrackPointer pTrack);
2828
29public slots:29 public slots:
30 void slotUpdateBpm(double bpm);
31 void slotUpdateBeatFirst(double beatfirst);
32 void slotUpdateTrackSamples(double samples);30 void slotUpdateTrackSamples(double samples);
33private:31 private:
34 WaveformRenderer *m_pParent;32 WaveformRenderer *m_pParent;
35 ControlObjectThreadMain *m_pBpm;
36 ControlObjectThreadMain *m_pBeatFirst;
37 ControlObjectThreadMain *m_pTrackSamples;33 ControlObjectThreadMain *m_pTrackSamples;
38 TrackPointer m_pTrack;34 TrackPointer m_pTrack;
39 int m_iWidth, m_iHeight;35 int m_iWidth, m_iHeight;
40 double m_dBpm;
41 double m_dBeatFirst;
42 QColor colorMarks;36 QColor colorMarks;
43 QColor colorHighlight;37 QColor colorHighlight;
44 double m_dSamplesPerPixel;38 double m_dSamplesPerPixel;
45 double m_dSamplesPerDownsample;39 double m_dSamplesPerDownsample;
46 double m_dBeatLength;
47 int m_iNumSamples;40 int m_iNumSamples;
48 int m_iSampleRate;41 int m_iSampleRate;
42
43 BeatList m_beatList;
49};44};
5045
51#endif46#endif
5247
=== modified file 'mixxx/src/waveform/waveformrenderer.cpp'
--- mixxx/src/waveform/waveformrenderer.cpp 2010-08-27 20:10:20 +0000
+++ mixxx/src/waveform/waveformrenderer.cpp 2011-03-07 05:53:37 +0000
@@ -1,9 +1,3 @@
1/**
2
3 A license and other info goes here!
4
5 */
6
7#include <QDebug>1#include <QDebug>
8#include <QDomNode>2#include <QDomNode>
9#include <QImage>3#include <QImage>
@@ -126,6 +120,8 @@
126120
127121
128WaveformRenderer::~WaveformRenderer() {122WaveformRenderer::~WaveformRenderer() {
123 qDebug() << "~WaveformRenderer()";
124
129 // Wait for the thread to quit125 // Wait for the thread to quit
130 m_bQuit = true;126 m_bQuit = true;
131 QThread::wait();127 QThread::wait();
@@ -165,6 +161,13 @@
165 if(m_pRenderBeat)161 if(m_pRenderBeat)
166 delete m_pRenderBeat;162 delete m_pRenderBeat;
167 m_pRenderBeat = NULL;163 m_pRenderBeat = NULL;
164
165 QMutableListIterator<RenderObject*> iter(m_renderObjects);
166 while (iter.hasNext()) {
167 RenderObject* ro = iter.next();
168 iter.remove();
169 delete ro;
170 }
168}171}
169172
170void WaveformRenderer::slotUpdatePlayPos(double v) {173void WaveformRenderer::slotUpdatePlayPos(double v) {
171174
=== modified file 'mixxx/src/waveform/waveformrenderer.h'
--- mixxx/src/waveform/waveformrenderer.h 2010-07-15 19:07:16 +0000
+++ mixxx/src/waveform/waveformrenderer.h 2011-03-07 05:53:37 +0000
@@ -28,7 +28,7 @@
28 Q_OBJECT28 Q_OBJECT
29public:29public:
30 WaveformRenderer(const char* group);30 WaveformRenderer(const char* group);
31 ~WaveformRenderer();31 virtual ~WaveformRenderer();
3232
33 void resize(int w, int h);33 void resize(int w, int h);
34 void draw(QPainter* pPainter, QPaintEvent *pEvent);34 void draw(QPainter* pPainter, QPaintEvent *pEvent);

Subscribers

People subscribed via source and target branches