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