diff --git a/.gitignore b/.gitignore
index ba9739c6688..90c11a51c67 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+.cproject
+.project
*.diff
*.log
*.o
diff --git a/res/skins/Deere/deck_visual_row.xml b/res/skins/Deere/deck_visual_row.xml
index 82ffa49ade6..851240323ea 100644
--- a/res/skins/Deere/deck_visual_row.xml
+++ b/res/skins/Deere/deck_visual_row.xml
@@ -28,6 +28,8 @@
#ffffff
+ #ccffff
+ #00ff00
#00FF00
#EA0000
diff --git a/res/skins/LateNight/waveform.xml b/res/skins/LateNight/waveform.xml
index 9022f7b3cbc..af8362a3bbb 100644
--- a/res/skins/LateNight/waveform.xml
+++ b/res/skins/LateNight/waveform.xml
@@ -14,6 +14,8 @@
#ffffff
+ #ccffff
+ #00ff00
#00FF00
#EA0000
diff --git a/res/skins/Shade/waveform.xml b/res/skins/Shade/waveform.xml
index 6364afbc833..8bc61eeddcc 100644
--- a/res/skins/Shade/waveform.xml
+++ b/res/skins/Shade/waveform.xml
@@ -11,6 +11,8 @@
#191F24
#FFFFFF
+ #CCFFFFF
+ #00FF00
#00FF00
#EA0000
#00FF00
diff --git a/res/skins/Tango/waveform.xml b/res/skins/Tango/waveform.xml
index 7708e49063a..5edd4f2532b 100644
--- a/res/skins/Tango/waveform.xml
+++ b/res/skins/Tango/waveform.xml
@@ -29,6 +29,8 @@ Variables:
#ffffff
+ #00630e
+ #00ff00
#FF4300
diff --git a/src/engine/controls/bpmcontrol.cpp b/src/engine/controls/bpmcontrol.cpp
index 301a2fd8fdf..70309110ea6 100644
--- a/src/engine/controls/bpmcontrol.cpp
+++ b/src/engine/controls/bpmcontrol.cpp
@@ -1,4 +1,5 @@
#include
+#include
#include "control/controlobject.h"
#include "control/controlpushbutton.h"
@@ -129,6 +130,9 @@ BpmControl::BpmControl(QString group,
this, &BpmControl::slotBeatsTranslate,
Qt::DirectConnection);
+ m_pSetPraseMark = new ControlObject(ConfigKey(group, "phrasemark_set"));
+ connect(m_pSetPraseMark, &ControlObject::valueChanged, this, &BpmControl::slotSetPhraseMark, Qt::DirectConnection);
+
m_pBeatsTranslateMatchAlignment = new ControlPushButton(ConfigKey(group, "beats_translate_match_alignment"));
connect(m_pBeatsTranslateMatchAlignment, &ControlObject::valueChanged,
this, &BpmControl::slotBeatsTranslateMatchAlignment,
@@ -811,6 +815,16 @@ void BpmControl::slotBeatsTranslate(double v) {
}
}
+void BpmControl::slotSetPhraseMark(double v) {
+ if (v > 0) {
+ TrackPointer pTrack = m_pTrack;
+ if (pTrack) {
+ double currentSample = getSampleOfTrack().current;
+ pTrack->setPhraseBegin(currentSample);
+ }
+ }
+}
+
void BpmControl::slotBeatsTranslateMatchAlignment(double v) {
BeatsPointer pBeats = m_pBeats;
if (v > 0 && pBeats && (pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) {
diff --git a/src/engine/controls/bpmcontrol.h b/src/engine/controls/bpmcontrol.h
index 57e09161d04..9f005176938 100644
--- a/src/engine/controls/bpmcontrol.h
+++ b/src/engine/controls/bpmcontrol.h
@@ -88,6 +88,7 @@ class BpmControl : public EngineControl {
void slotUpdatedTrackBeats();
void slotBeatsTranslate(double);
void slotBeatsTranslateMatchAlignment(double);
+ void slotSetPhraseMark(double);
private:
SyncMode getSyncMode() const {
@@ -144,6 +145,10 @@ class BpmControl : public EngineControl {
// Button that translates the beats so the nearest beat is on the current
// playposition.
ControlPushButton* m_pTranslateBeats;
+
+ // Control that sets a phrase mark to the closest beat.
+ ControlObject* m_pSetPraseMark;
+
// Button that translates beats to match another playing deck
ControlPushButton* m_pBeatsTranslateMatchAlignment;
diff --git a/src/preferences/dialog/dlgprefwaveform.cpp b/src/preferences/dialog/dlgprefwaveform.cpp
index 41c7e4a149c..3762d0cfd5a 100644
--- a/src/preferences/dialog/dlgprefwaveform.cpp
+++ b/src/preferences/dialog/dlgprefwaveform.cpp
@@ -1,3 +1,5 @@
+#include
+
#include "preferences/dialog/dlgprefwaveform.h"
#include "mixxx.h"
@@ -73,6 +75,7 @@ DlgPrefWaveform::DlgPrefWaveform(QWidget* pParent, MixxxMainWindow* pMixxx,
this, SLOT(slotSetVisualGainHigh(double)));
connect(normalizeOverviewCheckBox, SIGNAL(toggled(bool)),
this, SLOT(slotSetNormalizeOverview(bool)));
+ connect(showBarAndPhraseMarksCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotSetBarAndPhrase(bool)));
connect(factory, SIGNAL(waveformMeasured(float,int)),
this, SLOT(slotWaveformMeasured(float,int)));
connect(waveformOverviewComboBox, SIGNAL(currentIndexChanged(int)),
@@ -111,6 +114,7 @@ void DlgPrefWaveform::slotUpdate() {
midVisualGain->setValue(factory->getVisualGain(WaveformWidgetFactory::Mid));
highVisualGain->setValue(factory->getVisualGain(WaveformWidgetFactory::High));
normalizeOverviewCheckBox->setChecked(factory->isOverviewNormalized());
+ showBarAndPhraseMarksCheckBox->setChecked(factory->getShowBarAndPhrase());
// Round zoom to int to get a default zoom index.
defaultZoomComboBox->setCurrentIndex(static_cast(factory->getDefaultZoom()) - 1);
playMarkerPositionSlider->setValue(factory->getPlayMarkerPosition() * 100);
@@ -231,6 +235,10 @@ void DlgPrefWaveform::slotSetNormalizeOverview(bool normalize) {
WaveformWidgetFactory::instance()->setOverviewNormalized(normalize);
}
+void DlgPrefWaveform::slotSetBarAndPhrase(bool showBarAndPhrase) {
+ WaveformWidgetFactory::instance()->setShowBarAndPhrase(showBarAndPhrase);
+}
+
void DlgPrefWaveform::slotWaveformMeasured(float frameRate, int droppedFrames) {
frameRateAverage->setText(
QString::number((double)frameRate, 'f', 2) + " : " +
diff --git a/src/preferences/dialog/dlgprefwaveform.h b/src/preferences/dialog/dlgprefwaveform.h
index d1cb7ebbd03..ba4e99fa6ab 100644
--- a/src/preferences/dialog/dlgprefwaveform.h
+++ b/src/preferences/dialog/dlgprefwaveform.h
@@ -34,6 +34,7 @@ class DlgPrefWaveform : public DlgPreferencePage, public Ui::DlgPrefWaveformDlg
void slotSetVisualGainMid(double gain);
void slotSetVisualGainHigh(double gain);
void slotSetNormalizeOverview(bool normalize);
+ void slotSetBarAndPhrase(bool barAndPhrase);
void slotWaveformMeasured(float frameRate, int droppedFrames);
void slotClearCachedWaveforms();
void slotSetBeatGridAlpha(int alpha);
diff --git a/src/preferences/dialog/dlgprefwaveformdlg.ui b/src/preferences/dialog/dlgprefwaveformdlg.ui
index 8c5480ab486..443e2ffe6c9 100644
--- a/src/preferences/dialog/dlgprefwaveformdlg.ui
+++ b/src/preferences/dialog/dlgprefwaveformdlg.ui
@@ -16,7 +16,7 @@
-
-
-
+
-
Visual gain
@@ -64,7 +64,14 @@
- -
+
-
+
+
+ Show bar and phrase marks
+
+
+
+ -
Displays which OpenGL version is supported by the current platform.
@@ -93,7 +100,7 @@
- -
+
-
@@ -123,14 +130,14 @@
- -
+
-
Qt::Horizontal
- -
+
-
@@ -165,7 +172,7 @@
- -
+
-
-
@@ -323,7 +330,7 @@ Select from different types of displays for the waveform, which differ primarily
- -
+
-
Displays the actual frame rate.
diff --git a/src/proto/beats.proto b/src/proto/beats.proto
index 567b7f4e59d..85d3747028c 100644
--- a/src/proto/beats.proto
+++ b/src/proto/beats.proto
@@ -28,4 +28,5 @@ message BeatMap {
message BeatGrid {
optional Bpm bpm = 1;
optional Beat first_beat = 2;
+ optional Beat first_phrase = 3;
}
diff --git a/src/test/beatgridtest.cpp b/src/test/beatgridtest.cpp
index 801546b6162..e7f944a71a6 100644
--- a/src/test/beatgridtest.cpp
+++ b/src/test/beatgridtest.cpp
@@ -41,7 +41,7 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat) {
TrackPointer pTrack(Track::newTemporary());
int sampleRate = 44100;
- double bpm = 60.0;
+ double bpm = 60.1;
const int kFrameSize = 2;
pTrack->setBpm(bpm);
pTrack->setSampleRate(sampleRate);
@@ -58,26 +58,26 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat) {
// findNthBeat should return exactly the current beat if we ask for 1 or
// -1. For all other values, it should return n times the beat length.
for (int i = 1; i < 20; ++i) {
- EXPECT_EQ(position + beatLength*(i-1), pGrid->findNthBeat(position, i));
- EXPECT_EQ(position + beatLength*(-i+1), pGrid->findNthBeat(position, -i));
+ EXPECT_DOUBLE_EQ(position + beatLength * (i - 1), pGrid->findNthBeat(position, i));
+ EXPECT_DOUBLE_EQ(position + beatLength * (-i + 1), pGrid->findNthBeat(position, -i));
}
// Also test prev/next beat calculation.
double prevBeat, nextBeat;
pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat);
- EXPECT_EQ(position, prevBeat);
- EXPECT_EQ(position + beatLength, nextBeat);
+ EXPECT_DOUBLE_EQ(position, prevBeat);
+ EXPECT_DOUBLE_EQ(position + beatLength, nextBeat);
// Both previous and next beat should return the current position.
- EXPECT_EQ(position, pGrid->findNextBeat(position));
- EXPECT_EQ(position, pGrid->findPrevBeat(position));
+ EXPECT_DOUBLE_EQ(position, pGrid->findNextBeat(position));
+ EXPECT_DOUBLE_EQ(position, pGrid->findPrevBeat(position));
}
TEST(BeatGridTest, TestNthBeatWhenOnBeat_BeforeEpsilon) {
TrackPointer pTrack(Track::newTemporary());
int sampleRate = 44100;
- double bpm = 60.0;
+ double bpm = 60.1;
const int kFrameSize = 2;
pTrack->setBpm(bpm);
pTrack->setSampleRate(sampleRate);
@@ -96,26 +96,26 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_BeforeEpsilon) {
// findNthBeat should return exactly the current beat if we ask for 1 or
// -1. For all other values, it should return n times the beat length.
for (int i = 1; i < 20; ++i) {
- EXPECT_EQ(kClosestBeat + beatLength*(i-1), pGrid->findNthBeat(position, i));
- EXPECT_EQ(kClosestBeat + beatLength*(-i+1), pGrid->findNthBeat(position, -i));
+ EXPECT_DOUBLE_EQ(kClosestBeat + beatLength * (i - 1), pGrid->findNthBeat(position, i));
+ EXPECT_DOUBLE_EQ(kClosestBeat + beatLength * (-i + 1), pGrid->findNthBeat(position, -i));
}
// Also test prev/next beat calculation.
double prevBeat, nextBeat;
pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat);
- EXPECT_EQ(kClosestBeat, prevBeat);
- EXPECT_EQ(kClosestBeat + beatLength, nextBeat);
+ EXPECT_DOUBLE_EQ(kClosestBeat, prevBeat);
+ EXPECT_DOUBLE_EQ(kClosestBeat + beatLength, nextBeat);
// Both previous and next beat should return the closest beat.
- EXPECT_EQ(kClosestBeat, pGrid->findNextBeat(position));
- EXPECT_EQ(kClosestBeat, pGrid->findPrevBeat(position));
+ EXPECT_DOUBLE_EQ(kClosestBeat, pGrid->findNextBeat(position));
+ EXPECT_DOUBLE_EQ(kClosestBeat, pGrid->findPrevBeat(position));
}
TEST(BeatGridTest, TestNthBeatWhenOnBeat_AfterEpsilon) {
TrackPointer pTrack(Track::newTemporary());
int sampleRate = 44100;
- double bpm = 60.0;
+ double bpm = 60.1;
const int kFrameSize = 2;
pTrack->setBpm(bpm);
pTrack->setSampleRate(sampleRate);
@@ -134,25 +134,25 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_AfterEpsilon) {
// findNthBeat should return exactly the current beat if we ask for 1 or
// -1. For all other values, it should return n times the beat length.
for (int i = 1; i < 20; ++i) {
- EXPECT_EQ(kClosestBeat + beatLength*(i-1), pGrid->findNthBeat(position, i));
- EXPECT_EQ(kClosestBeat + beatLength*(-i+1), pGrid->findNthBeat(position, -i));
+ EXPECT_DOUBLE_EQ(kClosestBeat + beatLength * (i - 1), pGrid->findNthBeat(position, i));
+ EXPECT_DOUBLE_EQ(kClosestBeat + beatLength * (-i + 1), pGrid->findNthBeat(position, -i));
}
// Also test prev/next beat calculation.
double prevBeat, nextBeat;
pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat);
- EXPECT_EQ(kClosestBeat, prevBeat);
- EXPECT_EQ(kClosestBeat + beatLength, nextBeat);
+ EXPECT_DOUBLE_EQ(kClosestBeat, prevBeat);
+ EXPECT_DOUBLE_EQ(kClosestBeat + beatLength, nextBeat);
// Both previous and next beat should return the closest beat.
- EXPECT_EQ(kClosestBeat, pGrid->findNextBeat(position));
- EXPECT_EQ(kClosestBeat, pGrid->findPrevBeat(position));
+ EXPECT_DOUBLE_EQ(kClosestBeat, pGrid->findNextBeat(position));
+ EXPECT_DOUBLE_EQ(kClosestBeat, pGrid->findPrevBeat(position));
}
TEST(BeatGridTest, TestNthBeatWhenNotOnBeat) {
TrackPointer pTrack(Track::newTemporary());
int sampleRate = 44100;
- double bpm = 60.0;
+ double bpm = 60.1;
const int kFrameSize = 2;
pTrack->setBpm(bpm);
pTrack->setSampleRate(sampleRate);
diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp
index b796a811a38..7fb96b14adb 100644
--- a/src/track/beatgrid.cpp
+++ b/src/track/beatgrid.cpp
@@ -1,30 +1,60 @@
#include
#include
+#include
#include "track/beatgrid.h"
#include "util/math.h"
static const int kFrameSize = 2;
+namespace {
+constexpr int kBeatsPerBar = 4;
+constexpr int kBarsPerPhrase = 4;
+} // anonymous namespace
+
struct BeatGridData {
double bpm;
double firstBeat;
+ double firstPhrase;
};
class BeatGridIterator : public BeatIterator {
public:
- BeatGridIterator(double dBeatLength, double dFirstBeat, double dEndSample)
+ BeatGridIterator(double dBeatLength, double dFirstBeat, double dEndSample, double dFirstPhraseBegin, int beatsPerBar, int barsPerPhrase)
: m_dBeatLength(dBeatLength),
m_dCurrentSample(dFirstBeat),
- m_dEndSample(dEndSample) {
+ m_dEndSample(dEndSample),
+ m_dPhraseBeginSample(dFirstPhraseBegin),
+ m_beatsPerBar(beatsPerBar),
+ m_barsPerPhrase(barsPerPhrase),
+ m_beatCounter(0) {
}
virtual bool hasNext() const {
return m_dBeatLength > 0 && m_dCurrentSample <= m_dEndSample;
}
- virtual double next() {
- double beat = m_dCurrentSample;
+ virtual BeatData next() {
+ BeatData beat;
+ beat.sample = m_dCurrentSample;
+
+ if (m_dCurrentSample < m_dPhraseBeginSample || m_dCurrentSample > m_dEndSample) {
+ beat.phraseNumber = -1;
+ beat.beatNumber = -1;
+ beat.barNumber = -1;
+ } else {
+ beat.beatNumber = static_cast(round((m_dCurrentSample - m_dPhraseBeginSample) / m_dBeatLength));
+
+ // If we are at a bar start, set bar number and check for phrase.
+ if (std::remainder(beat.beatNumber, m_beatsPerBar) == 0) {
+ beat.barNumber = beat.beatNumber / m_beatsPerBar;
+ beat.phraseNumber = (std::remainder(beat.barNumber, m_barsPerPhrase) == 0 ? beat.barNumber / m_barsPerPhrase : -1);
+ } else {
+ beat.barNumber = -1;
+ beat.phraseNumber = -1;
+ }
+ }
+
m_dCurrentSample += m_dBeatLength;
return beat;
}
@@ -33,6 +63,10 @@ class BeatGridIterator : public BeatIterator {
double m_dBeatLength;
double m_dCurrentSample;
double m_dEndSample;
+ double m_dPhraseBeginSample;
+ int m_beatsPerBar;
+ int m_barsPerPhrase;
+ int m_beatCounter;
};
BeatGrid::BeatGrid(
@@ -64,6 +98,10 @@ BeatGrid::BeatGrid(const BeatGrid& other)
}
void BeatGrid::setGrid(double dBpm, double dFirstBeatSample) {
+ setGrid(dBpm, dFirstBeatSample, dFirstBeatSample);
+}
+
+void BeatGrid::setGrid(double dBpm, double dFirstBeatSample, double dFirstPhraseBegin) {
if (dBpm < 0) {
dBpm = 0.0;
}
@@ -71,6 +109,7 @@ void BeatGrid::setGrid(double dBpm, double dFirstBeatSample) {
QMutexLocker lock(&m_mutex);
m_grid.mutable_bpm()->set_bpm(dBpm);
m_grid.mutable_first_beat()->set_frame_position(dFirstBeatSample / kFrameSize);
+ m_grid.mutable_first_phrase()->set_frame_position(dFirstPhraseBegin / kFrameSize);
// Calculate beat length as sample offsets
m_dBeatLength = (60.0 * m_iSampleRate / dBpm) * kFrameSize;
}
@@ -103,7 +142,11 @@ void BeatGrid::readByteArray(const QByteArray& byteArray) {
const BeatGridData* blob = reinterpret_cast(byteArray.constData());
// We serialize into frame offsets but use sample offsets at runtime
- setGrid(blob->bpm, blob->firstBeat * kFrameSize);
+ setGrid(blob->bpm, blob->firstBeat * kFrameSize, blob->firstPhrase * kFrameSize);
+}
+
+double BeatGrid::firstPhraseSample() const {
+ return m_grid.first_phrase().frame_position() * kFrameSize;
}
double BeatGrid::firstBeatSample() const {
@@ -178,14 +221,12 @@ double BeatGrid::findNthBeat(double dSamples, int n) const {
const double kEpsilon = .01;
if (fabs(nextBeat - beatFraction) < kEpsilon) {
- beatFraction = nextBeat;
// If we are going to pretend we were actually on nextBeat then prevBeat
// needs to be re-calculated. Since it is floor(beatFraction), that's
// the same as nextBeat. We only use prevBeat so no need to increment
// nextBeat.
prevBeat = nextBeat;
} else if (fabs(prevBeat - beatFraction) < kEpsilon) {
- beatFraction = prevBeat;
// If we are going to pretend we were actually on prevBeat then nextBeat
// needs to be re-calculated. Since it is ceil(beatFraction), that's
// the same as prevBeat. We will only use nextBeat so no need to
@@ -195,21 +236,16 @@ double BeatGrid::findNthBeat(double dSamples, int n) const {
double dClosestBeat;
if (n > 0) {
- // We're going forward, so use ceil to round up to the next multiple of
- // m_dBeatLength
+ // We're going forward
dClosestBeat = nextBeat * m_dBeatLength + firstBeatSample();
n = n - 1;
} else {
- // We're going backward, so use floor to round down to the next multiple
- // of m_dBeatLength
+ // We're going backward
dClosestBeat = prevBeat * m_dBeatLength + firstBeatSample();
n = n + 1;
}
- double dResult = floor(dClosestBeat + n * m_dBeatLength);
- if (!even(static_cast(dResult))) {
- dResult--;
- }
+ double dResult = dClosestBeat + n * m_dBeatLength;
return dResult;
}
@@ -246,14 +282,8 @@ bool BeatGrid::findPrevNextBeats(double dSamples,
// And nextBeat needs to be incremented.
++nextBeat;
}
- *dpPrevBeatSamples = floor(prevBeat * dBeatLength + dFirstBeatSample);
- *dpNextBeatSamples = floor(nextBeat * dBeatLength + dFirstBeatSample);
- if (!even(static_cast(*dpPrevBeatSamples))) {
- --*dpPrevBeatSamples;
- }
- if (!even(static_cast(*dpNextBeatSamples))) {
- --*dpNextBeatSamples;
- }
+ *dpPrevBeatSamples = prevBeat * dBeatLength + dFirstBeatSample;
+ *dpNextBeatSamples = nextBeat * dBeatLength + dFirstBeatSample;
return true;
}
@@ -269,7 +299,9 @@ std::unique_ptr BeatGrid::findBeats(double startSample, double sto
if (curBeat == -1.0) {
return std::unique_ptr();
}
- return std::make_unique(m_dBeatLength, curBeat, stopSample);
+ double m_firstPhraseBegin = firstPhraseSample();
+ return std::make_unique(
+ m_dBeatLength, curBeat, stopSample, m_firstPhraseBegin, kBeatsPerBar, kBarsPerPhrase);
}
bool BeatGrid::hasBeatInRange(double startSample, double stopSample) const {
@@ -311,6 +343,11 @@ double BeatGrid::getBpmAroundPosition(double curSample, int n) const {
return bpm();
}
+double BeatGrid::calculateFirstPhraseSample(double quantizedPhraseBegin) const {
+ double samplesPerPhrase = kBeatsPerBar * kBarsPerPhrase * m_dBeatLength;
+ return std::fmod(quantizedPhraseBegin, samplesPerPhrase);
+}
+
void BeatGrid::addBeat(double dBeatSample) {
Q_UNUSED(dBeatSample);
//QMutexLocker locker(&m_mutex);
@@ -370,6 +407,12 @@ void BeatGrid::scale(enum BPMScale scale) {
setBpm(bpm);
}
+void BeatGrid::setFirstPhraseBegin(double firstPhraseBegin) {
+ double newFirstPhraseBegin = firstPhraseBegin / kFrameSize;
+ m_grid.mutable_first_phrase()->set_frame_position(newFirstPhraseBegin);
+ emit(updated());
+}
+
void BeatGrid::setBpm(double dBpm) {
QMutexLocker locker(&m_mutex);
if (dBpm > getMaxBpm()) {
diff --git a/src/track/beatgrid.h b/src/track/beatgrid.h
index 90ad5ca0c85..2c395026e80 100644
--- a/src/track/beatgrid.h
+++ b/src/track/beatgrid.h
@@ -29,6 +29,7 @@ class BeatGrid final : public Beats {
// of dFirstBeatSample. Does not generate an updated() signal, since it is
// meant for initialization.
void setGrid(double dBpm, double dFirstBeatSample);
+ void setGrid(double dBpm, double dFirstBeatSample, double dFirstPhraseBegin);
// The following are all methods from the Beats interface, see method
// comments in beats.h
@@ -59,6 +60,7 @@ class BeatGrid final : public Beats {
virtual double getBpm() const;
virtual double getBpmRange(double startSample, double stopSample) const;
virtual double getBpmAroundPosition(double curSample, int n) const;
+ virtual double calculateFirstPhraseSample(double quantizedPhraseBegin) const;
////////////////////////////////////////////////////////////////////////////
// Beat mutations
@@ -70,10 +72,12 @@ class BeatGrid final : public Beats {
virtual void translate(double dNumSamples);
virtual void scale(enum BPMScale scale);
virtual void setBpm(double dBpm);
+ virtual void setFirstPhraseBegin(double firstPhraseBegin);
private:
BeatGrid(const BeatGrid& other);
double firstBeatSample() const;
+ double firstPhraseSample() const;
double bpm() const;
void readByteArray(const QByteArray& byteArray);
diff --git a/src/track/beatmap.cpp b/src/track/beatmap.cpp
index b6460a527c9..8640755f584 100644
--- a/src/track/beatmap.cpp
+++ b/src/track/beatmap.cpp
@@ -44,8 +44,9 @@ class BeatMapIterator : public BeatIterator {
return m_currentBeat != m_endBeat;
}
- virtual double next() {
- double beat = framesToSamples(m_currentBeat->frame_position());
+ virtual BeatData next() {
+ BeatData beat;
+ beat.sample = framesToSamples(m_currentBeat->frame_position());
++m_currentBeat;
while (m_currentBeat != m_endBeat && !m_currentBeat->enabled()) {
++m_currentBeat;
@@ -422,6 +423,12 @@ double BeatMap::getBpmRange(double startSample, double stopSample) const {
return calculateBpm(startBeat, stopBeat);
}
+double BeatMap::getSamplesSincePhraseStart(double phraseSample) const {
+ Q_UNUSED(phraseSample);
+ DEBUG_ASSERT(!"BeatMap::getSamplesSincePhraseStart() not implemented");
+ return 0.0;
+}
+
double BeatMap::getBpmAroundPosition(double curSample, int n) const {
QMutexLocker locker(&m_mutex);
if (!isValid())
@@ -684,6 +691,12 @@ void BeatMap::scaleFourth() {
}
}
+void BeatMap::setFirstPhraseBegin(double firstPhraseBegin) {
+ Q_UNUSED(firstPhraseBegin);
+ DEBUG_ASSERT(!"BeatMap::setFirstPhraseBegin() not implemented");
+ return;
+}
+
void BeatMap::setBpm(double dBpm) {
Q_UNUSED(dBpm);
DEBUG_ASSERT(!"BeatMap::setBpm() not implemented");
diff --git a/src/track/beatmap.h b/src/track/beatmap.h
index 184e28d35ca..56712653928 100644
--- a/src/track/beatmap.h
+++ b/src/track/beatmap.h
@@ -68,6 +68,7 @@ class BeatMap final : public Beats {
virtual double getBpm() const;
virtual double getBpmRange(double startSample, double stopSample) const;
virtual double getBpmAroundPosition(double curSample, int n) const;
+ virtual double getSamplesSincePhraseStart(double phraseSample) const;
////////////////////////////////////////////////////////////////////////////
// Beat mutations
@@ -79,6 +80,7 @@ class BeatMap final : public Beats {
virtual void translate(double dNumSamples);
virtual void scale(enum BPMScale scale);
virtual void setBpm(double dBpm);
+ virtual void setFirstPhraseBegin(double firstPhraseBegin);
private:
BeatMap(const BeatMap& other);
diff --git a/src/track/beats.h b/src/track/beats.h
index c0f1a5a8a4d..5b0c9cde8b1 100644
--- a/src/track/beats.h
+++ b/src/track/beats.h
@@ -16,11 +16,18 @@ namespace {
class Beats;
typedef QSharedPointer BeatsPointer;
+struct BeatData {
+ double sample;
+ int beatNumber;
+ int barNumber;
+ int phraseNumber;
+};
+
class BeatIterator {
public:
virtual ~BeatIterator() {}
virtual bool hasNext() const = 0;
- virtual double next() = 0;
+ virtual BeatData next() = 0;
};
// Beats is a pure abstract base class for BPM and beat management classes. It
@@ -135,6 +142,8 @@ class Beats : public QObject {
// BPM returns -1.
virtual double getBpmAroundPosition(double curSample, int n) const = 0;
+ virtual double getSamplesSincePhraseStart(double phraseSample) const = 0;
+
virtual double getMaxBpm() const {
return kMaxBpm;
}
@@ -164,6 +173,8 @@ class Beats : public QObject {
// have the capability BEATSCAP_SET.
virtual void setBpm(double dBpm) = 0;
+ virtual void setFirstPhraseBegin(double firstPhraseBegin) = 0;
+
signals:
void updated();
};
diff --git a/src/track/track.cpp b/src/track/track.cpp
index c0e04e0c06d..8aafc6591b1 100644
--- a/src/track/track.cpp
+++ b/src/track/track.cpp
@@ -1,5 +1,6 @@
#include
#include
+#include
#include
@@ -124,7 +125,7 @@ void Track::relocate(
void Track::setTrackMetadata(
mixxx::TrackMetadata trackMetadata,
QDateTime metadataSynchronized) {
- // Safe some values that are needed after move assignment and unlocking(see below)
+ // Save some values that are needed after move assignment and unlocking(see below)
const auto newBpm = trackMetadata.getTrackInfo().getBpm();
const auto newKey = trackMetadata.getTrackInfo().getKey();
const auto newReplayGain = trackMetadata.getTrackInfo().getReplayGain();
@@ -561,6 +562,13 @@ int Track::getSampleRate() const {
return m_record.getMetadata().getSampleRate();
}
+void Track::setPhraseBegin(double dPhraseBeginSample) {
+ double dQuantizedPhraseBegin = m_pBeats->findClosestBeat(dPhraseBeginSample);
+ double dFirstQuantizedPhraseBegin =
+ m_pBeats->getSamplesSincePhraseStart(dQuantizedPhraseBegin);
+ m_pBeats->setFirstPhraseBegin(dFirstQuantizedPhraseBegin);
+}
+
void Track::setChannels(int iChannels) {
QMutexLocker lock(&m_qMutex);
if (compareAndSet(&m_record.refMetadata().refChannels(), mixxx::AudioSignal::ChannelCount(iChannels))) {
diff --git a/src/track/track.h b/src/track/track.h
index 2596a4599e0..7d3c117b61f 100644
--- a/src/track/track.h
+++ b/src/track/track.h
@@ -102,6 +102,9 @@ class Track : public QObject {
void setSampleRate(int iSampleRate);
// Get sample rate
int getSampleRate() const;
+ // Get first phrase mark within this track
+ double getFirstPhraseBegin() const;
+ void setPhraseBegin(double dPhraseBeginSample);
// Sets the bitrate
void setBitrate(int);
diff --git a/src/waveform/renderers/waveformrenderbeat.cpp b/src/waveform/renderers/waveformrenderbeat.cpp
index a4816c2e336..d1674199823 100644
--- a/src/waveform/renderers/waveformrenderbeat.cpp
+++ b/src/waveform/renderers/waveformrenderbeat.cpp
@@ -1,6 +1,7 @@
#include
#include
#include
+#include
#include "waveform/renderers/waveformrenderbeat.h"
@@ -8,6 +9,7 @@
#include "track/beats.h"
#include "track/track.h"
#include "waveform/renderers/waveformwidgetrenderer.h"
+#include "waveform/waveformwidgetfactory.h"
#include "widget/wskincolor.h"
#include "widget/wwidget.h"
@@ -22,9 +24,14 @@ WaveformRenderBeat::~WaveformRenderBeat() {
void WaveformRenderBeat::setup(const QDomNode& node, const SkinContext& context) {
m_beatColor.setNamedColor(context.selectString(node, "BeatColor"));
m_beatColor = WSkinColor::getCorrectColor(m_beatColor).toRgb();
+ m_barColor.setNamedColor(context.selectString(node, "BarColor"));
+ m_barColor = WSkinColor::getCorrectColor(m_barColor).toRgb();
+ m_phraseColor.setNamedColor(context.selectString(node, "PhraseColor"));
+ m_phraseColor = WSkinColor::getCorrectColor(m_phraseColor).toRgb();
}
void WaveformRenderBeat::draw(QPainter* painter, QPaintEvent* /*event*/) {
+ m_showBarAndPhrase = WaveformWidgetFactory::instance()->getShowBarAndPhrase();
TrackPointer trackInfo = m_waveformRenderer->getTrackInfo();
if (!trackInfo)
@@ -38,8 +45,10 @@ void WaveformRenderBeat::draw(QPainter* painter, QPaintEvent* /*event*/) {
if (alpha == 0)
return;
m_beatColor.setAlphaF(alpha/100.0);
+ m_barColor.setAlphaF(alpha / 100.0);
+ m_phraseColor.setAlphaF(alpha / 100.0);
- const int trackSamples = m_waveformRenderer->getTrackSamples();
+ const int trackSamples = m_waveformRenderer->getNumberOfSamples();
if (trackSamples <= 0) {
return;
}
@@ -67,35 +76,79 @@ void WaveformRenderBeat::draw(QPainter* painter, QPaintEvent* /*event*/) {
QPen beatPen(m_beatColor);
beatPen.setWidthF(std::max(1.0, scaleFactor()));
- painter->setPen(beatPen);
+
+ QPen barPen(m_barColor);
+ barPen.setWidthF(std::max(1.0, scaleFactor() * 2));
+
+ QPen phrasePen(m_phraseColor);
+ phrasePen.setWidthF(std::max(1.0, scaleFactor() * 2));
const Qt::Orientation orientation = m_waveformRenderer->getOrientation();
const float rendererWidth = m_waveformRenderer->getWidth();
const float rendererHeight = m_waveformRenderer->getHeight();
- int beatCount = 0;
-
while (it->hasNext()) {
- double beatPosition = it->next();
+ BeatData beat = it->next();
double xBeatPoint =
- m_waveformRenderer->transformSamplePositionInRendererWorld(beatPosition);
+ m_waveformRenderer->transformSamplePositionInRendererWorld(beat.sample);
xBeatPoint = qRound(xBeatPoint);
- // If we don't have enough space, double the size.
- if (beatCount >= m_beats.size()) {
- m_beats.resize(m_beats.size() * 2);
+ // Selects the right pen, if we are in phrase also paints the phrase tag
+ if (beat.phraseNumber != -1 && m_showBarAndPhrase) {
+ // Selects the font
+ QFont font; // Uses the application default
+ font.setPointSizeF(10 * scaleFactor());
+ font.setStretch(100);
+ font.setWeight(75);
+
+ QFontMetrics metrics(font);
+
+ // Calculates the size of the box
+ QString label(QString::number(beat.phraseNumber));
+ QRect wordRect = metrics.tightBoundingRect(label);
+ const int marginX = 1 * scaleFactor();
+ const int marginY = 1 * scaleFactor();
+ wordRect.moveTop(marginX + 1);
+ wordRect.moveLeft(marginY + 1);
+ wordRect.setHeight(wordRect.height() + (wordRect.height() % 2));
+ wordRect.setWidth(wordRect.width() + (wordRect.width()) % 2);
+ //even wordrect to have an even Image >> draw the line in the middle !
+
+ int labelRectWidth = wordRect.width() + 2 * marginX + 4;
+ int labelRectHeight = wordRect.height() + 2 * marginY + 4 * scaleFactor();
+
+ QRectF labelRect(xBeatPoint - labelRectWidth,
+ rendererHeight - labelRectHeight,
+ (float)labelRectWidth,
+ (float)labelRectHeight);
+
+ // Draw the label rect
+ QColor rectColor = m_phraseColor;
+ rectColor.setAlpha(200);
+ painter->setPen(m_phraseColor);
+ painter->setBrush(QBrush(rectColor));
+ painter->drawRoundedRect(labelRect, 2 * scaleFactor(), 2 * scaleFactor());
+ // Draw the text
+ painter->setBrush(QBrush(QColor(0, 0, 0, 0)));
+ painter->setFont(font);
+ painter->setPen(Qt::black);
+ painter->drawText(labelRect, Qt::AlignCenter, label);
+
+ painter->setPen(phrasePen);
+ } else if (beat.barNumber != -1 && m_showBarAndPhrase) {
+ painter->setPen(barPen);
+ } else {
+ painter->setPen(beatPen);
}
+ // Paints the beat line
if (orientation == Qt::Horizontal) {
- m_beats[beatCount++].setLine(xBeatPoint, 0.0f, xBeatPoint, rendererHeight);
+ painter->drawLine(xBeatPoint, 0.0f, xBeatPoint, rendererHeight);
} else {
- m_beats[beatCount++].setLine(0.0f, xBeatPoint, rendererWidth, xBeatPoint);
+ painter->drawLine(0.0f, xBeatPoint, rendererWidth, xBeatPoint);
}
}
- // Make sure to use constData to prevent detaches!
- painter->drawLines(m_beats.constData(), beatCount);
-
painter->restore();
}
diff --git a/src/waveform/renderers/waveformrenderbeat.h b/src/waveform/renderers/waveformrenderbeat.h
index 444afba7b25..d074ae2ab96 100644
--- a/src/waveform/renderers/waveformrenderbeat.h
+++ b/src/waveform/renderers/waveformrenderbeat.h
@@ -3,6 +3,8 @@
#include
+#include "control/controlproxy.h"
+#include "preferences/usersettings.h"
#include "skin/skincontext.h"
#include "util/class.h"
#include "waveform/renderers/waveformrendererabstract.h"
@@ -17,7 +19,10 @@ class WaveformRenderBeat : public WaveformRendererAbstract {
private:
QColor m_beatColor;
+ QColor m_barColor;
+ QColor m_phraseColor;
QVector m_beats;
+ bool m_showBarAndPhrase;
DISALLOW_COPY_AND_ASSIGN(WaveformRenderBeat);
};
diff --git a/src/waveform/renderers/waveformwidgetrenderer.h b/src/waveform/renderers/waveformwidgetrenderer.h
index 62001a17027..6501e2f8c92 100644
--- a/src/waveform/renderers/waveformwidgetrenderer.h
+++ b/src/waveform/renderers/waveformwidgetrenderer.h
@@ -73,7 +73,9 @@ class WaveformWidgetRenderer {
double getZoomFactor() const { return m_zoomFactor;}
double getRateAdjust() const { return m_rateAdjust;}
double getGain() const { return m_gain;}
- int getTrackSamples() const { return m_trackSamples;}
+ int getNumberOfSamples() const {
+ return m_trackSamples;
+ }
int beatGridAlpha() const { return m_alphaBeatGrid; }
diff --git a/src/waveform/waveformwidgetfactory.cpp b/src/waveform/waveformwidgetfactory.cpp
index c7818c4a3ac..8f73493e3d8 100644
--- a/src/waveform/waveformwidgetfactory.cpp
+++ b/src/waveform/waveformwidgetfactory.cpp
@@ -103,6 +103,7 @@ WaveformWidgetFactory::WaveformWidgetFactory()
m_defaultZoom(WaveformWidgetRenderer::s_waveformDefaultZoom),
m_zoomSync(false),
m_overviewNormalized(false),
+ m_showBarAndPhrase(false),
m_openGlAvailable(false),
m_openGlesAvailable(false),
m_openGLShaderAvailable(false),
@@ -334,6 +335,13 @@ bool WaveformWidgetFactory::setConfig(UserSettingsPointer config) {
m_config->set(ConfigKey("[Waveform]","OverviewNormalized"), ConfigValue(m_overviewNormalized));
}
+ int showBarAndPhrase = m_config->getValueString(ConfigKey("[Waveform]", "BarAndPhrase")).toInt(&ok);
+ if (ok) {
+ setShowBarAndPhrase(static_cast(showBarAndPhrase));
+ } else {
+ m_config->set(ConfigKey("[Waveform]", "BarAndPhrase"), ConfigValue(m_showBarAndPhrase));
+ }
+
m_playMarkerPosition = m_config->getValue(ConfigKey("[Waveform]","PlayMarkerPosition"),
WaveformWidgetRenderer::s_defaultPlayMarkerPosition);
setPlayMarkerPosition(m_playMarkerPosition);
@@ -559,6 +567,13 @@ void WaveformWidgetFactory::setOverviewNormalized(bool normalize) {
}
}
+void WaveformWidgetFactory::setShowBarAndPhrase(bool showBarAndPhrase) {
+ m_showBarAndPhrase = showBarAndPhrase;
+ if (m_config) {
+ m_config->set(ConfigKey("[Waveform]", "BarAndPhrase"), ConfigValue(m_showBarAndPhrase));
+ }
+}
+
void WaveformWidgetFactory::setPlayMarkerPosition(double position) {
//qDebug() << "setPlayMarkerPosition, position=" << position;
m_playMarkerPosition = position;
diff --git a/src/waveform/waveformwidgetfactory.h b/src/waveform/waveformwidgetfactory.h
index e2ffef8f9bc..fdb529db550 100644
--- a/src/waveform/waveformwidgetfactory.h
+++ b/src/waveform/waveformwidgetfactory.h
@@ -96,6 +96,11 @@ class WaveformWidgetFactory : public QObject, public Singleton getAvailableTypes() const { return m_waveformWidgetHandles;}
void getAvailableVSyncTypes(QList >* list);
void destroyWidgets();
@@ -152,6 +157,7 @@ class WaveformWidgetFactory : public QObject, public Singleton