From 4719d5e9529e39b64e46e3cf46fe06a6106e0e66 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Sat, 30 Jan 2021 19:03:20 +0100 Subject: [PATCH 01/14] Reworked beat_active control: -Made CO beat_active read-only -Update from engine::updateIndicators instead of process, which isn't updated enough for a reliable beat indicator -Fixed accuracy issue in beatgrid.cpp (compare to 1/100 of a beat period can be severalsamples off) -Don't calculate if deck is not playing -Set beat_active at the beat not +-50ms before/after -More states -Period of beat_active on is no longer hard coded 200ms, it's no 20% of the peat period --- src/engine/controls/clockcontrol.cpp | 96 ++++++++++++++++++++++++---- src/engine/controls/clockcontrol.h | 13 +++- src/engine/enginebuffer.cpp | 2 + src/track/beatgrid.cpp | 7 +- 4 files changed, 100 insertions(+), 18 deletions(-) diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index 22cd4707bc9..ed1d27e7a28 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -10,13 +10,16 @@ ClockControl::ClockControl(const QString& group, UserSettingsPointer pConfig) : EngineControl(group, pConfig) { m_pCOBeatActive = new ControlObject(ConfigKey(group, "beat_active")); - m_pCOBeatActive->set(0.0); - m_pCOSampleRate = new ControlProxy("[Master]","samplerate"); + m_pCOBeatActive->setReadOnly(); + m_pCOBeatActive->forceSet(0.0); + m_lastEvaluatedSample = 0; + m_PrevBeatSamples = 0; + m_PrevBeatSamples = 0; + m_blinkIntervalSamples = 0; } ClockControl::~ClockControl() { delete m_pCOBeatActive; - delete m_pCOSampleRate; } // called from an engine worker thread @@ -30,27 +33,92 @@ void ClockControl::trackLoaded(TrackPointer pNewTrack) { void ClockControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) { // Clear on-beat control - m_pCOBeatActive->set(0.0); + m_pCOBeatActive->forceSet(0.0); m_pBeats = pBeats; } void ClockControl::process(const double dRate, - const double currentSample, - const int iBuffersize) { + const double currentSample, + const int iBuffersize) { + Q_UNUSED(dRate); + Q_UNUSED(currentSample); Q_UNUSED(iBuffersize); - double samplerate = m_pCOSampleRate->get(); +} + +void ClockControl::updateIndicators(const double dRate, + const double currentSample) { + /* This method sets the control beat_active is set to the following values: + * +1.0 --> Forward playing, set at the beat and set back to 0.0 at 20% of beat distance + * +0.5 --> Direction changed to reverse playing while forward playing indication was on + * 0.0 --> No beat indication + * -0.5 --> Direction changed to forward playing while reverse playing indication was on + * -1.0 --> Reverse playing, set at the beat and set back to 0.0 at -20% of beat distance + */ // TODO(XXX) should this be customizable, or latency dependent? - const double blinkSeconds = 0.100; + const double kBlinkInterval = 0.20; // LED is on 20% of the beat period - // Multiply by two to get samples from frames. Interval is scaled linearly - // by the rate. - const double blinkIntervalSamples = 2.0 * samplerate * (1.0 * dRate) * blinkSeconds; + if ((currentSample == m_lastEvaluatedSample) || + (dRate == 0.0)) { + return; // No position change (e.g. deck stopped) -> No indicator update needed + } mixxx::BeatsPointer pBeats = m_pBeats; if (pBeats) { - double closestBeat = pBeats->findClosestBeat(currentSample); - double distanceToClosestBeat = fabs(currentSample - closestBeat); - m_pCOBeatActive->set(distanceToClosestBeat < blinkIntervalSamples / 2.0); + if ((currentSample >= m_NextBeatSamples) || + (currentSample <= m_PrevBeatSamples)) { + //qDebug() << "### findPrevNextBeats ### " << " currentSample: " << currentSample << " m_lastEvaluatedSample: " << m_lastEvaluatedSample << " m_PrevBeatSamples: " << m_PrevBeatSamples << " m_NextBeatSamples: " << m_NextBeatSamples; + + pBeats->findPrevNextBeats( + currentSample, &m_PrevBeatSamples, &m_NextBeatSamples); + + m_blinkIntervalSamples = (m_NextBeatSamples - m_PrevBeatSamples) * kBlinkInterval; + } + //qDebug() << "dRate:" << dRate << " m_lastPlayDirection:" << m_lastPlayDirection << " m_pCOBeatActive->get(): " << m_pCOBeatActive->get() << " currentSample: " << currentSample << " m_lastEvaluatedSample: " << m_lastEvaluatedSample << " m_PrevBeatSamples: " << m_PrevBeatSamples << " m_NextBeatSamples: " << m_NextBeatSamples << " m_blinkIntervalSamples: " << m_blinkIntervalSamples; + + if (dRate >= 0.0) { + if (m_lastPlayDirection == true) { + if ((currentSample > m_PrevBeatSamples) && + (currentSample < + m_PrevBeatSamples + m_blinkIntervalSamples) && + (m_pCOBeatActive->get() < 0.5)) { + m_pCOBeatActive->forceSet(1.0); + } else if ((currentSample > m_PrevBeatSamples + + m_blinkIntervalSamples) && + (m_pCOBeatActive->get() > 0.0)) { + m_pCOBeatActive->forceSet(0.0); + } + } else { + // Play direction changed while beat indicator was on and forward playing + if ((currentSample < m_NextBeatSamples) && + (currentSample >= + m_NextBeatSamples - m_blinkIntervalSamples)) { + m_pCOBeatActive->forceSet(-0.5); + } + } + m_lastPlayDirection = true; // Forward + } else { + if (m_lastPlayDirection == false) { + if ((currentSample < m_NextBeatSamples) && + (currentSample > + m_NextBeatSamples - m_blinkIntervalSamples) && + (m_pCOBeatActive->get() > -0.5)) { + m_pCOBeatActive->forceSet(-1.0); + } else if ((currentSample < m_NextBeatSamples - + m_blinkIntervalSamples) && + (m_pCOBeatActive->get() < 0.0)) { + m_pCOBeatActive->forceSet(0.0); + } + } else { + // Play direction changed while beat indicator was on and reverse playing + if ((currentSample > m_PrevBeatSamples) && + (currentSample <= + m_PrevBeatSamples + m_blinkIntervalSamples)) { + m_pCOBeatActive->forceSet(0.5); + } + } + m_lastPlayDirection = false; // Reverse + } + m_lastEvaluatedSample = currentSample; } } diff --git a/src/engine/controls/clockcontrol.h b/src/engine/controls/clockcontrol.h index 87667db443a..a3600f5f2ec 100644 --- a/src/engine/controls/clockcontrol.h +++ b/src/engine/controls/clockcontrol.h @@ -19,12 +19,23 @@ class ClockControl: public EngineControl { void process(const double dRate, const double currentSample, const int iBufferSize) override; + void updateIndicators(const double dRate, + const double currentSample); + void trackLoaded(TrackPointer pNewTrack) override; void trackBeatsUpdated(mixxx::BeatsPointer pBeats) override; private: ControlObject* m_pCOBeatActive; - ControlProxy* m_pCOSampleRate; + + double m_lastEvaluatedSample; + + double m_PrevBeatSamples; + double m_NextBeatSamples; + double m_blinkIntervalSamples; + + // True is forward direction, False is reverse + bool m_lastPlayDirection; // m_pBeats is written from an engine worker thread mixxx::BeatsPointer m_pBeats; diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index da5cea027b5..0f3cc43a65c 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -1350,6 +1350,8 @@ void EngineBuffer::updateIndicators(double speed, int iBufferSize) { (double)iBufferSize / m_trackSamplesOld, fractionalPlayposFromAbsolute(m_dSlipPosition), tempoTrackSeconds); + + m_pClockControl->updateIndicators(speed * m_baserate_old, m_filepos_play); } void EngineBuffer::hintReader(const double dRate) { diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp index 375c7f6dd3c..36bfad9863d 100644 --- a/src/track/beatgrid.cpp +++ b/src/track/beatgrid.cpp @@ -233,9 +233,10 @@ bool BeatGrid::findPrevNextBeats(double dSamples, double prevBeat = floor(beatFraction); double nextBeat = ceil(beatFraction); - // If the position is within 1/100th of the next or previous beat, treat it - // as if it is that beat. - const double kEpsilon = .01; + // If the position is within 1/1000,000,000th of the next or previous beat, treat it + // as if it is that beat. This value ensures safe float comparisation and that the + // accuracy is always better one sample. + const double kEpsilon = 1e-09; if (fabs(nextBeat - beatFraction) < kEpsilon) { beatFraction = nextBeat; From 143f2556c0c88a57e204a2ac8b2d4a653864f4d9 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Sun, 21 Feb 2021 18:11:22 +0100 Subject: [PATCH 02/14] Added switch, to select if findPrevNextBeats should compare with or without tolerance epsilon --- src/engine/controls/bpmcontrol.cpp | 2 +- src/engine/controls/clockcontrol.cpp | 6 ++++-- src/engine/controls/loopingcontrol.cpp | 2 +- src/engine/controls/quantizecontrol.cpp | 2 +- src/test/beatgridtest.cpp | 8 ++++---- src/test/beatmaptest.cpp | 12 ++++++------ src/track/beatgrid.cpp | 21 +++++++++++++-------- src/track/beatgrid.h | 6 ++++-- src/track/beatmap.cpp | 19 +++++++++++++------ src/track/beatmap.h | 5 +++-- src/track/beats.cpp | 2 +- src/track/beats.h | 5 +++-- 12 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/engine/controls/bpmcontrol.cpp b/src/engine/controls/bpmcontrol.cpp index 9374bf49507..05143a483f6 100644 --- a/src/engine/controls/bpmcontrol.cpp +++ b/src/engine/controls/bpmcontrol.cpp @@ -539,7 +539,7 @@ bool BpmControl::getBeatContext(const mixxx::BeatsPointer& pBeats, double dPrevBeat; double dNextBeat; - if (!pBeats->findPrevNextBeats(dPosition, &dPrevBeat, &dNextBeat)) { + if (!pBeats->findPrevNextBeats(dPosition, &dPrevBeat, &dNextBeat, false)) { return false; } diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index ed1d27e7a28..a7dd22c0f74 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -69,8 +69,10 @@ void ClockControl::updateIndicators(const double dRate, (currentSample <= m_PrevBeatSamples)) { //qDebug() << "### findPrevNextBeats ### " << " currentSample: " << currentSample << " m_lastEvaluatedSample: " << m_lastEvaluatedSample << " m_PrevBeatSamples: " << m_PrevBeatSamples << " m_NextBeatSamples: " << m_NextBeatSamples; - pBeats->findPrevNextBeats( - currentSample, &m_PrevBeatSamples, &m_NextBeatSamples); + pBeats->findPrevNextBeats(currentSample, + &m_PrevBeatSamples, + &m_NextBeatSamples, + true); // Precise compare without tolerance needed m_blinkIntervalSamples = (m_NextBeatSamples - m_PrevBeatSamples) * kBlinkInterval; } diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 44653ca86a2..37482602560 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -1194,7 +1194,7 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable // The closest beat might be ahead of play position and will cause a catching loop. double prevBeat; double nextBeat; - pBeats->findPrevNextBeats(currentSample, &prevBeat, &nextBeat); + pBeats->findPrevNextBeats(currentSample, &prevBeat, &nextBeat, false); if (m_pQuantizeEnabled->toBool() && prevBeat != -1) { double beatLength = nextBeat - prevBeat; diff --git a/src/engine/controls/quantizecontrol.cpp b/src/engine/controls/quantizecontrol.cpp index c059bca51c1..6edbe1d7724 100644 --- a/src/engine/controls/quantizecontrol.cpp +++ b/src/engine/controls/quantizecontrol.cpp @@ -80,7 +80,7 @@ void QuantizeControl::lookupBeatPositions(double dCurrentSample) { mixxx::BeatsPointer pBeats = m_pBeats; if (pBeats) { double prevBeat, nextBeat; - pBeats->findPrevNextBeats(dCurrentSample, &prevBeat, &nextBeat); + pBeats->findPrevNextBeats(dCurrentSample, &prevBeat, &nextBeat, false); m_pCOPrevBeat->set(prevBeat); m_pCONextBeat->set(nextBeat); } diff --git a/src/test/beatgridtest.cpp b/src/test/beatgridtest.cpp index 780aeb11b9c..96c908a9f6b 100644 --- a/src/test/beatgridtest.cpp +++ b/src/test/beatgridtest.cpp @@ -78,7 +78,7 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat) { // Also test prev/next beat calculation. double prevBeat, nextBeat; - pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat); + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_NEAR(position, prevBeat, kMaxBeatError); EXPECT_NEAR(position + beatLength, nextBeat, kMaxBeatError); @@ -115,7 +115,7 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { // Also test prev/next beat calculation. double prevBeat, nextBeat; - pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat); + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError); EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError); @@ -152,7 +152,7 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_AfterEpsilon) { // Also test prev/next beat calculation. double prevBeat, nextBeat; - pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat); + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError); EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError); @@ -190,7 +190,7 @@ TEST(BeatGridTest, TestNthBeatWhenNotOnBeat) { // Also test prev/next beat calculation double foundPrevBeat, foundNextBeat; - pGrid->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat); + pGrid->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, false); EXPECT_NEAR(previousBeat, foundPrevBeat, kMaxBeatError); EXPECT_NEAR(nextBeat, foundNextBeat, kMaxBeatError); } diff --git a/src/test/beatmaptest.cpp b/src/test/beatmaptest.cpp index 5cbafa71d35..cf3780e0e6f 100644 --- a/src/test/beatmaptest.cpp +++ b/src/test/beatmaptest.cpp @@ -100,11 +100,11 @@ TEST_F(BeatMapTest, TestNthBeat) { EXPECT_EQ(-1, pMap->findNthBeat(firstBeat, -2)); double prevBeat, nextBeat; - pMap->findPrevNextBeats(lastBeat, &prevBeat, &nextBeat); + pMap->findPrevNextBeats(lastBeat, &prevBeat, &nextBeat, false); EXPECT_EQ(lastBeat, prevBeat); EXPECT_EQ(-1, nextBeat); - pMap->findPrevNextBeats(firstBeat, &prevBeat, &nextBeat); + pMap->findPrevNextBeats(firstBeat, &prevBeat, &nextBeat, false); EXPECT_EQ(firstBeat, prevBeat); EXPECT_EQ(firstBeat + beatLengthSamples, nextBeat); } @@ -137,7 +137,7 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat) { // Also test prev/next beat calculation. double prevBeat, nextBeat; - pMap->findPrevNextBeats(position, &prevBeat, &nextBeat); + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_EQ(position, prevBeat); EXPECT_EQ(position + beatLengthSamples, nextBeat); @@ -175,7 +175,7 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { // Also test prev/next beat calculation double prevBeat, nextBeat; - pMap->findPrevNextBeats(position, &prevBeat, &nextBeat); + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_EQ(kClosestBeat, prevBeat); EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat); @@ -216,7 +216,7 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_AfterEpsilon) { // Also test prev/next beat calculation. double prevBeat, nextBeat; - pMap->findPrevNextBeats(position, &prevBeat, &nextBeat); + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_EQ(kClosestBeat, prevBeat); EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat); @@ -256,7 +256,7 @@ TEST_F(BeatMapTest, TestNthBeatWhenNotOnBeat) { // Also test prev/next beat calculation double foundPrevBeat, foundNextBeat; - pMap->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat); + pMap->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, false); EXPECT_EQ(previousBeat, foundPrevBeat); EXPECT_EQ(nextBeat, foundNextBeat); } diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp index 36bfad9863d..2268d13000e 100644 --- a/src/track/beatgrid.cpp +++ b/src/track/beatgrid.cpp @@ -158,7 +158,7 @@ double BeatGrid::findClosestBeat(double dSamples) const { } double prevBeat; double nextBeat; - findPrevNextBeats(dSamples, &prevBeat, &nextBeat); + findPrevNextBeats(dSamples, &prevBeat, &nextBeat, false); if (prevBeat == -1) { // If both values are -1, we correctly return -1. return nextBeat; @@ -214,8 +214,10 @@ double BeatGrid::findNthBeat(double dSamples, int n) const { } bool BeatGrid::findPrevNextBeats(double dSamples, - double* dpPrevBeatSamples, - double* dpNextBeatSamples) const { + + double* dpPrevBeatSamples, + double* dpNextBeatSamples, + bool NoTolerance) const { double dFirstBeatSample; double dBeatLength; { @@ -232,11 +234,15 @@ bool BeatGrid::findPrevNextBeats(double dSamples, double beatFraction = (dSamples - dFirstBeatSample) / dBeatLength; double prevBeat = floor(beatFraction); double nextBeat = ceil(beatFraction); + double kEpsilon; - // If the position is within 1/1000,000,000th of the next or previous beat, treat it - // as if it is that beat. This value ensures safe float comparisation and that the - // accuracy is always better one sample. - const double kEpsilon = 1e-09; + if (NoTolerance) { + kEpsilon = 0.0f; + } else { + // If the position is within 1/100th of the next or previous beat, treat it + // as if it is that beat. + kEpsilon = .01; + } if (fabs(nextBeat - beatFraction) < kEpsilon) { beatFraction = nextBeat; @@ -252,7 +258,6 @@ bool BeatGrid::findPrevNextBeats(double dSamples, return true; } - std::unique_ptr BeatGrid::findBeats(double startSample, double stopSample) const { QMutexLocker locker(&m_mutex); if (!isValid() || startSample > stopSample) { diff --git a/src/track/beatgrid.h b/src/track/beatgrid.h index bd4747cc799..b1b0612b721 100644 --- a/src/track/beatgrid.h +++ b/src/track/beatgrid.h @@ -52,8 +52,10 @@ class BeatGrid final : public Beats { double findNextBeat(double dSamples) const override; double findPrevBeat(double dSamples) const override; bool findPrevNextBeats(double dSamples, - double* dpPrevBeatSamples, - double* dpNextBeatSamples) const override; + + double* dpPrevBeatSamples, + double* dpNextBeatSamples, + bool NoTolerance) const override; double findClosestBeat(double dSamples) const override; double findNthBeat(double dSamples, int n) const override; std::unique_ptr findBeats(double startSample, double stopSample) const override; diff --git a/src/track/beatmap.cpp b/src/track/beatmap.cpp index 9cb0e2b3fd6..cbc808f9126 100644 --- a/src/track/beatmap.cpp +++ b/src/track/beatmap.cpp @@ -188,7 +188,7 @@ double BeatMap::findClosestBeat(double dSamples) const { } double prevBeat; double nextBeat; - findPrevNextBeats(dSamples, &prevBeat, &nextBeat); + findPrevNextBeats(dSamples, &prevBeat, &nextBeat, false); if (prevBeat == -1) { // If both values are -1, we correctly return -1. return nextBeat; @@ -286,8 +286,9 @@ double BeatMap::findNthBeat(double dSamples, int n) const { } bool BeatMap::findPrevNextBeats(double dSamples, - double* dpPrevBeatSamples, - double* dpNextBeatSamples) const { + double* dpPrevBeatSamples, + double* dpNextBeatSamples, + bool NoTolerance) const { QMutexLocker locker(&m_mutex); if (!isValid()) { @@ -304,9 +305,15 @@ bool BeatMap::findPrevNextBeats(double dSamples, BeatList::const_iterator it = std::lower_bound(m_beats.constBegin(), m_beats.constEnd(), beat, BeatLessThan); - // If the position is within 1/10th of a second of the next or previous - // beat, pretend we are on that beat. - const double kFrameEpsilon = 0.1 * m_iSampleRate; + double kFrameEpsilon; + + if (NoTolerance) { + kFrameEpsilon = 0.0f; + } else { + // If the position is within 1/10th of a second of the next or previous + // beat, pretend we are on that beat. + kFrameEpsilon = 0.1 * m_iSampleRate; + } // Back-up by one. if (it != m_beats.begin()) { diff --git a/src/track/beatmap.h b/src/track/beatmap.h index d3f05c25911..cd231d74963 100644 --- a/src/track/beatmap.h +++ b/src/track/beatmap.h @@ -60,8 +60,9 @@ class BeatMap final : public Beats { double findNextBeat(double dSamples) const override; double findPrevBeat(double dSamples) const override; bool findPrevNextBeats(double dSamples, - double* dpPrevBeatSamples, - double* dpNextBeatSamples) const override; + double* dpPrevBeatSamples, + double* dpNextBeatSamples, + bool NoTolerance) const override; double findClosestBeat(double dSamples) const override; double findNthBeat(double dSamples, int n) const override; std::unique_ptr findBeats(double startSample, double stopSample) const override; diff --git a/src/track/beats.cpp b/src/track/beats.cpp index 58d90a96599..992c295d616 100644 --- a/src/track/beats.cpp +++ b/src/track/beats.cpp @@ -21,7 +21,7 @@ double Beats::findNBeatsFromSample(double fromSample, double beats) const { double prevBeat; double nextBeat; - if (!findPrevNextBeats(fromSample, &prevBeat, &nextBeat)) { + if (!findPrevNextBeats(fromSample, &prevBeat, &nextBeat, false)) { return fromSample; } double fromFractionBeats = (fromSample - prevBeat) / (nextBeat - prevBeat); diff --git a/src/track/beats.h b/src/track/beats.h index c3ae7e568e5..19814f58eeb 100644 --- a/src/track/beats.h +++ b/src/track/beats.h @@ -93,8 +93,9 @@ class Beats : public QObject { // even. Returns false if *at least one* sample is -1. (Can return false // with one beat successfully filled) virtual bool findPrevNextBeats(double dSamples, - double* dpPrevBeatSamples, - double* dpNextBeatSamples) const = 0; + double* dpPrevBeatSamples, + double* dpNextBeatSamples, + bool NoTolerance) const = 0; // Starting from sample dSamples, return the sample of the closest beat in // the track, or -1 if none exists. Non- -1 values are guaranteed to be From 1e6d0669b207981893f3d9565472be14705953f6 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Sun, 21 Feb 2021 21:18:53 +0100 Subject: [PATCH 03/14] Added testcases for NoTolerance mode of findPrevNextBeats --- src/test/beatgridtest.cpp | 20 ++++++++++++++++++++ src/test/beatmaptest.cpp | 20 ++++++++++++++++++++ src/track/beatgrid.cpp | 2 +- src/track/beatmap.cpp | 2 +- 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/test/beatgridtest.cpp b/src/test/beatgridtest.cpp index 96c908a9f6b..1607b8fdde1 100644 --- a/src/test/beatgridtest.cpp +++ b/src/test/beatgridtest.cpp @@ -82,6 +82,11 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat) { EXPECT_NEAR(position, prevBeat, kMaxBeatError); EXPECT_NEAR(position + beatLength, nextBeat, kMaxBeatError); + // Also test prev/next beat calculation without tolerance + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + EXPECT_NEAR(position, prevBeat, kMaxBeatError); + EXPECT_NEAR(position + beatLength, nextBeat, kMaxBeatError); + // Both previous and next beat should return the current position. EXPECT_NEAR(position, pGrid->findNextBeat(position), kMaxBeatError); EXPECT_NEAR(position, pGrid->findPrevBeat(position), kMaxBeatError); @@ -119,6 +124,11 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError); EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError); + // Also test prev/next beat calculation without tolerance + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + EXPECT_NEAR(kClosestBeat - beatLength, prevBeat, kMaxBeatError); + EXPECT_NEAR(kClosestBeat, nextBeat, kMaxBeatError); + // Both previous and next beat should return the closest beat. EXPECT_NEAR(kClosestBeat, pGrid->findNextBeat(position), kMaxBeatError); EXPECT_NEAR(kClosestBeat, pGrid->findPrevBeat(position), kMaxBeatError); @@ -156,6 +166,11 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_AfterEpsilon) { EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError); EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError); + // Also test prev/next beat calculation without tolerance + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError); + EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError); + // Both previous and next beat should return the closest beat. EXPECT_NEAR(kClosestBeat, pGrid->findNextBeat(position), kMaxBeatError); EXPECT_NEAR(kClosestBeat, pGrid->findPrevBeat(position), kMaxBeatError); @@ -193,6 +208,11 @@ TEST(BeatGridTest, TestNthBeatWhenNotOnBeat) { pGrid->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, false); EXPECT_NEAR(previousBeat, foundPrevBeat, kMaxBeatError); EXPECT_NEAR(nextBeat, foundNextBeat, kMaxBeatError); + + // Also test prev/next beat calculation without tolerance + pGrid->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, true); + EXPECT_NEAR(previousBeat, foundPrevBeat, kMaxBeatError); + EXPECT_NEAR(nextBeat, foundNextBeat, kMaxBeatError); } } // namespace diff --git a/src/test/beatmaptest.cpp b/src/test/beatmaptest.cpp index cf3780e0e6f..994f202a5da 100644 --- a/src/test/beatmaptest.cpp +++ b/src/test/beatmaptest.cpp @@ -141,6 +141,11 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat) { EXPECT_EQ(position, prevBeat); EXPECT_EQ(position + beatLengthSamples, nextBeat); + // Also test prev/next beat calculation without tolerance + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + EXPECT_EQ(position, prevBeat); + EXPECT_EQ(position + beatLengthSamples, nextBeat); + // Both previous and next beat should return the current position. EXPECT_EQ(position, pMap->findNextBeat(position)); EXPECT_EQ(position, pMap->findPrevBeat(position)); @@ -179,6 +184,11 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { EXPECT_EQ(kClosestBeat, prevBeat); EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat); + // Also test prev/next beat calculation without tolerance + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + EXPECT_EQ(kClosestBeat - beatLengthSamples, prevBeat); + EXPECT_EQ(kClosestBeat, nextBeat); + // Both previous and next beat should return the closest beat. EXPECT_EQ(kClosestBeat, pMap->findNextBeat(position)); EXPECT_EQ(kClosestBeat, pMap->findPrevBeat(position)); @@ -220,6 +230,11 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_AfterEpsilon) { EXPECT_EQ(kClosestBeat, prevBeat); EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat); + // Also test prev/next beat calculation without tolerance + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + EXPECT_EQ(kClosestBeat, prevBeat); + EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat); + // Both previous and next beat should return the closest beat. EXPECT_EQ(kClosestBeat, pMap->findNextBeat(position)); EXPECT_EQ(kClosestBeat, pMap->findPrevBeat(position)); @@ -259,6 +274,11 @@ TEST_F(BeatMapTest, TestNthBeatWhenNotOnBeat) { pMap->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, false); EXPECT_EQ(previousBeat, foundPrevBeat); EXPECT_EQ(nextBeat, foundNextBeat); + + // Also test prev/next beat calculation without tolerance + pMap->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, true); + EXPECT_EQ(previousBeat, foundPrevBeat); + EXPECT_EQ(nextBeat, foundNextBeat); } TEST_F(BeatMapTest, TestBpmAround) { diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp index 2268d13000e..3df12ba093f 100644 --- a/src/track/beatgrid.cpp +++ b/src/track/beatgrid.cpp @@ -244,7 +244,7 @@ bool BeatGrid::findPrevNextBeats(double dSamples, kEpsilon = .01; } - if (fabs(nextBeat - beatFraction) < kEpsilon) { + if (fabs(nextBeat - beatFraction) <= kEpsilon) { beatFraction = nextBeat; // If we are going to pretend we were actually on nextBeat then prevBeatFraction // needs to be re-calculated. Since it is floor(beatFraction), that's diff --git a/src/track/beatmap.cpp b/src/track/beatmap.cpp index cbc808f9126..7bc5f4b68e2 100644 --- a/src/track/beatmap.cpp +++ b/src/track/beatmap.cpp @@ -328,7 +328,7 @@ bool BeatMap::findPrevNextBeats(double dSamples, qint32 delta = it->frame_position() - beat.frame_position(); // We are "on" this beat. - if (abs(delta) < kFrameEpsilon) { + if (abs(delta) <= kFrameEpsilon) { on_beat = it; break; } From 1a8f2c271cbe067069bcf4e74413b902d1ce946e Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Sun, 21 Feb 2021 22:09:34 +0100 Subject: [PATCH 04/14] Improved if cases for NoTolerance code in findPrevNextBeats --- src/track/beatgrid.cpp | 15 ++++++--------- src/track/beatmap.cpp | 17 ++++++----------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp index 3df12ba093f..26ce54297a3 100644 --- a/src/track/beatgrid.cpp +++ b/src/track/beatgrid.cpp @@ -234,17 +234,14 @@ bool BeatGrid::findPrevNextBeats(double dSamples, double beatFraction = (dSamples - dFirstBeatSample) / dBeatLength; double prevBeat = floor(beatFraction); double nextBeat = ceil(beatFraction); - double kEpsilon; - if (NoTolerance) { - kEpsilon = 0.0f; - } else { - // If the position is within 1/100th of the next or previous beat, treat it - // as if it is that beat. - kEpsilon = .01; - } + const double kEpsilon = .01; + + if ((NoTolerance && ((nextBeat - beatFraction) == 0.0)) || + (!NoTolerance && (fabs(nextBeat - beatFraction) < kEpsilon))) { + // In tolerance mode: If the position is within 1/100th of the next or previous beat, + // treat it as if it is that beat. - if (fabs(nextBeat - beatFraction) <= kEpsilon) { beatFraction = nextBeat; // If we are going to pretend we were actually on nextBeat then prevBeatFraction // needs to be re-calculated. Since it is floor(beatFraction), that's diff --git a/src/track/beatmap.cpp b/src/track/beatmap.cpp index 7bc5f4b68e2..76b9c68f90b 100644 --- a/src/track/beatmap.cpp +++ b/src/track/beatmap.cpp @@ -305,15 +305,9 @@ bool BeatMap::findPrevNextBeats(double dSamples, BeatList::const_iterator it = std::lower_bound(m_beats.constBegin(), m_beats.constEnd(), beat, BeatLessThan); - double kFrameEpsilon; - - if (NoTolerance) { - kFrameEpsilon = 0.0f; - } else { - // If the position is within 1/10th of a second of the next or previous - // beat, pretend we are on that beat. - kFrameEpsilon = 0.1 * m_iSampleRate; - } + // If the position is within 1/10th of a second of the next or previous + // beat, pretend we are on that beat. + const double kFrameEpsilon = 0.1 * m_iSampleRate; // Back-up by one. if (it != m_beats.begin()) { @@ -327,8 +321,9 @@ bool BeatMap::findPrevNextBeats(double dSamples, for (; it != m_beats.end(); ++it) { qint32 delta = it->frame_position() - beat.frame_position(); - // We are "on" this beat. - if (abs(delta) <= kFrameEpsilon) { + if ((NoTolerance && (delta == 0)) || + (!NoTolerance && (abs(delta) < kFrameEpsilon))) { + // We are "on" this beat. on_beat = it; break; } From 5cb6696d2bf75d436e5f114196fdcfd83fb47482 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Sat, 27 Feb 2021 11:07:51 +0100 Subject: [PATCH 05/14] Moved the 5 states into an internal state machine The CO returns now 0 for the cases of play direction changes while beat indication is on --- src/engine/controls/clockcontrol.cpp | 51 ++++++++++++++++++++-------- src/engine/controls/clockcontrol.h | 15 ++++++++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index a7dd22c0f74..10218cec940 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -14,7 +14,8 @@ ClockControl::ClockControl(const QString& group, UserSettingsPointer pConfig) m_pCOBeatActive->forceSet(0.0); m_lastEvaluatedSample = 0; m_PrevBeatSamples = 0; - m_PrevBeatSamples = 0; + m_InternalState = StateMachine::outsideIndicationArea; + m_NextBeatSamples = 0; m_blinkIntervalSamples = 0; } @@ -49,9 +50,7 @@ void ClockControl::updateIndicators(const double dRate, const double currentSample) { /* This method sets the control beat_active is set to the following values: * +1.0 --> Forward playing, set at the beat and set back to 0.0 at 20% of beat distance - * +0.5 --> Direction changed to reverse playing while forward playing indication was on - * 0.0 --> No beat indication - * -0.5 --> Direction changed to forward playing while reverse playing indication was on + * 0.0 --> No beat indication (ouside 20% area or play direction changed while indication was on) * -1.0 --> Reverse playing, set at the beat and set back to 0.0 at -20% of beat distance */ @@ -67,7 +66,11 @@ void ClockControl::updateIndicators(const double dRate, if (pBeats) { if ((currentSample >= m_NextBeatSamples) || (currentSample <= m_PrevBeatSamples)) { - //qDebug() << "### findPrevNextBeats ### " << " currentSample: " << currentSample << " m_lastEvaluatedSample: " << m_lastEvaluatedSample << " m_PrevBeatSamples: " << m_PrevBeatSamples << " m_NextBeatSamples: " << m_NextBeatSamples; + /*qDebug() << "### findPrevNextBeats ### " << + " currentSample: " << currentSample << + " m_lastEvaluatedSample: " << m_lastEvaluatedSample + << " m_PrevBeatSamples: " << m_PrevBeatSamples + << " m_NextBeatSamples: " << m_NextBeatSamples;*/ pBeats->findPrevNextBeats(currentSample, &m_PrevBeatSamples, @@ -76,26 +79,40 @@ void ClockControl::updateIndicators(const double dRate, m_blinkIntervalSamples = (m_NextBeatSamples - m_PrevBeatSamples) * kBlinkInterval; } - //qDebug() << "dRate:" << dRate << " m_lastPlayDirection:" << m_lastPlayDirection << " m_pCOBeatActive->get(): " << m_pCOBeatActive->get() << " currentSample: " << currentSample << " m_lastEvaluatedSample: " << m_lastEvaluatedSample << " m_PrevBeatSamples: " << m_PrevBeatSamples << " m_NextBeatSamples: " << m_NextBeatSamples << " m_blinkIntervalSamples: " << m_blinkIntervalSamples; + /*qDebug() << "dRate:" << dRate << + " m_lastPlayDirection:" << m_lastPlayDirection << + " m_pCOBeatActive->get(): " << m_pCOBeatActive->get() << + " currentSample: " << currentSample << + " m_lastEvaluatedSample: " << m_lastEvaluatedSample << + " m_PrevBeatSamples: " << m_PrevBeatSamples << + " m_NextBeatSamples: " << m_NextBeatSamples << + " m_blinkIntervalSamples: " << m_blinkIntervalSamples;*/ + // The m_InternalState needs to be taken into account, to show a reliable beat indication for loops if (dRate >= 0.0) { if (m_lastPlayDirection == true) { if ((currentSample > m_PrevBeatSamples) && (currentSample < m_PrevBeatSamples + m_blinkIntervalSamples) && - (m_pCOBeatActive->get() < 0.5)) { + (m_InternalState != StateMachine::afterBeatActive) && + (m_InternalState != StateMachine::afterBeatDirectionChanged)) { + m_InternalState = StateMachine::afterBeatActive; m_pCOBeatActive->forceSet(1.0); } else if ((currentSample > m_PrevBeatSamples + m_blinkIntervalSamples) && - (m_pCOBeatActive->get() > 0.0)) { + ((m_InternalState == StateMachine::afterBeatActive) || + (m_InternalState == StateMachine::afterBeatDirectionChanged))) { + m_InternalState = StateMachine::outsideIndicationArea; m_pCOBeatActive->forceSet(0.0); } } else { // Play direction changed while beat indicator was on and forward playing if ((currentSample < m_NextBeatSamples) && (currentSample >= - m_NextBeatSamples - m_blinkIntervalSamples)) { - m_pCOBeatActive->forceSet(-0.5); + m_NextBeatSamples - m_blinkIntervalSamples) && + (m_InternalState != StateMachine::beforeBeatDirectionChanged)) { + m_InternalState = StateMachine::beforeBeatDirectionChanged; + m_pCOBeatActive->forceSet(0.0); } } m_lastPlayDirection = true; // Forward @@ -104,19 +121,25 @@ void ClockControl::updateIndicators(const double dRate, if ((currentSample < m_NextBeatSamples) && (currentSample > m_NextBeatSamples - m_blinkIntervalSamples) && - (m_pCOBeatActive->get() > -0.5)) { + (m_InternalState != StateMachine::beforeBeatActive) && + (m_InternalState != StateMachine::beforeBeatDirectionChanged)) { + m_InternalState = StateMachine::beforeBeatActive; m_pCOBeatActive->forceSet(-1.0); } else if ((currentSample < m_NextBeatSamples - m_blinkIntervalSamples) && - (m_pCOBeatActive->get() < 0.0)) { + ((m_InternalState == StateMachine::beforeBeatActive) || + (m_InternalState == StateMachine::beforeBeatDirectionChanged))) { + m_InternalState = StateMachine::outsideIndicationArea; m_pCOBeatActive->forceSet(0.0); } } else { // Play direction changed while beat indicator was on and reverse playing if ((currentSample > m_PrevBeatSamples) && (currentSample <= - m_PrevBeatSamples + m_blinkIntervalSamples)) { - m_pCOBeatActive->forceSet(0.5); + m_PrevBeatSamples + m_blinkIntervalSamples) && + (m_InternalState != StateMachine::afterBeatDirectionChanged)) { + m_InternalState = StateMachine::afterBeatDirectionChanged; + m_pCOBeatActive->forceSet(0.0); } } m_lastPlayDirection = false; // Reverse diff --git a/src/engine/controls/clockcontrol.h b/src/engine/controls/clockcontrol.h index a3600f5f2ec..dccbe1b1824 100644 --- a/src/engine/controls/clockcontrol.h +++ b/src/engine/controls/clockcontrol.h @@ -30,6 +30,21 @@ class ClockControl: public EngineControl { double m_lastEvaluatedSample; + enum class StateMachine : int { + afterBeatDirectionChanged = + 2, /// Direction changed to reverse playing while forward playing indication was on + afterBeatActive = + 1, /// Forward playing, set at the beat and set back to 0.0 at 20% of beat distance + outsideIndicationArea = + 0, /// Outside -20% ... +20% of the beat distance + beforeBeatActive = + -1, /// Reverse playing, set at the beat and set back to 0.0 at -20% of beat distance + beforeBeatDirectionChanged = + -2 /// Direction changed to forward playing while reverse playing indication was on + }; + + StateMachine m_InternalState; + double m_PrevBeatSamples; double m_NextBeatSamples; double m_blinkIntervalSamples; From dd1b2b174db90739915f42dcc55aeb58b5bd08a9 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Sat, 27 Feb 2021 17:23:09 +0100 Subject: [PATCH 06/14] Added the loop cases: -quantized loop on beat -loop smaller than beat distance -loop without a beat inside --- src/engine/controls/clockcontrol.cpp | 179 ++++++++++++++++----------- src/engine/controls/clockcontrol.h | 8 +- src/engine/enginebuffer.cpp | 2 +- 3 files changed, 116 insertions(+), 73 deletions(-) diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index 10218cec940..66226856891 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -17,6 +17,9 @@ ClockControl::ClockControl(const QString& group, UserSettingsPointer pConfig) m_InternalState = StateMachine::outsideIndicationArea; m_NextBeatSamples = 0; m_blinkIntervalSamples = 0; + m_pLoopEnabled = new ControlProxy(group, "loop_enabled", this); + m_pLoopStartPosition = new ControlProxy(group, "loop_start_position", this); + m_pLoopEndPosition = new ControlProxy(group, "loop_end_position", this); } ClockControl::~ClockControl() { @@ -47,7 +50,8 @@ void ClockControl::process(const double dRate, } void ClockControl::updateIndicators(const double dRate, - const double currentSample) { + const double currentSample, + const double sampleRate) { /* This method sets the control beat_active is set to the following values: * +1.0 --> Forward playing, set at the beat and set back to 0.0 at 20% of beat distance * 0.0 --> No beat indication (ouside 20% area or play direction changed while indication was on) @@ -56,94 +60,127 @@ void ClockControl::updateIndicators(const double dRate, // TODO(XXX) should this be customizable, or latency dependent? const double kBlinkInterval = 0.20; // LED is on 20% of the beat period + const double kStandStillTolerance = + 0.0025; // (seconds) Minimum change, to he last evaluated position - if ((currentSample == m_lastEvaluatedSample) || + if (((currentSample <= (m_lastEvaluatedSample + kStandStillTolerance * sampleRate)) && + (currentSample >= (m_lastEvaluatedSample - kStandStillTolerance * sampleRate))) || (dRate == 0.0)) { - return; // No position change (e.g. deck stopped) -> No indicator update needed + return; // No position change since last indicator update (e.g. deck stopped) -> No indicator update needed } + double prevIndicatorSamples; + double nextIndicatorSamples; + mixxx::BeatsPointer pBeats = m_pBeats; if (pBeats) { if ((currentSample >= m_NextBeatSamples) || (currentSample <= m_PrevBeatSamples)) { - /*qDebug() << "### findPrevNextBeats ### " << - " currentSample: " << currentSample << - " m_lastEvaluatedSample: " << m_lastEvaluatedSample - << " m_PrevBeatSamples: " << m_PrevBeatSamples - << " m_NextBeatSamples: " << m_NextBeatSamples;*/ - pBeats->findPrevNextBeats(currentSample, &m_PrevBeatSamples, &m_NextBeatSamples, true); // Precise compare without tolerance needed + } + } else { + m_PrevBeatSamples = -1; + m_NextBeatSamples = -1; + } - m_blinkIntervalSamples = (m_NextBeatSamples - m_PrevBeatSamples) * kBlinkInterval; + // Loops need special handling + if (m_pLoopEnabled->toBool()) { + const double loop_start_position = m_pLoopStartPosition->get(); + const double loop_end_position = m_pLoopEndPosition->get(); + + if ((m_PrevBeatSamples < loop_start_position) && (m_NextBeatSamples >= loop_end_position)) { + // No beat inside loop -> show beat indication at loop_start_position + prevIndicatorSamples = loop_start_position; + nextIndicatorSamples = loop_end_position; + } else { + prevIndicatorSamples = m_PrevBeatSamples; + nextIndicatorSamples = m_NextBeatSamples; } - /*qDebug() << "dRate:" << dRate << - " m_lastPlayDirection:" << m_lastPlayDirection << - " m_pCOBeatActive->get(): " << m_pCOBeatActive->get() << - " currentSample: " << currentSample << - " m_lastEvaluatedSample: " << m_lastEvaluatedSample << - " m_PrevBeatSamples: " << m_PrevBeatSamples << - " m_NextBeatSamples: " << m_NextBeatSamples << - " m_blinkIntervalSamples: " << m_blinkIntervalSamples;*/ - - // The m_InternalState needs to be taken into account, to show a reliable beat indication for loops - if (dRate >= 0.0) { - if (m_lastPlayDirection == true) { - if ((currentSample > m_PrevBeatSamples) && - (currentSample < - m_PrevBeatSamples + m_blinkIntervalSamples) && - (m_InternalState != StateMachine::afterBeatActive) && - (m_InternalState != StateMachine::afterBeatDirectionChanged)) { - m_InternalState = StateMachine::afterBeatActive; - m_pCOBeatActive->forceSet(1.0); - } else if ((currentSample > m_PrevBeatSamples + - m_blinkIntervalSamples) && - ((m_InternalState == StateMachine::afterBeatActive) || - (m_InternalState == StateMachine::afterBeatDirectionChanged))) { - m_InternalState = StateMachine::outsideIndicationArea; - m_pCOBeatActive->forceSet(0.0); - } + + if ((m_PrevBeatSamples != -1) && (m_NextBeatSamples != -1)) { + // Don't overwrite interval at begin/end of track + if ((loop_end_position - loop_start_position) < + (m_NextBeatSamples - m_PrevBeatSamples)) { + // Loops smaller than beat distance -> Set m_blinkIntervalSamples based on loop period + m_blinkIntervalSamples = + (loop_end_position - loop_start_position) * + kBlinkInterval; } else { - // Play direction changed while beat indicator was on and forward playing - if ((currentSample < m_NextBeatSamples) && - (currentSample >= - m_NextBeatSamples - m_blinkIntervalSamples) && - (m_InternalState != StateMachine::beforeBeatDirectionChanged)) { - m_InternalState = StateMachine::beforeBeatDirectionChanged; - m_pCOBeatActive->forceSet(0.0); - } + m_blinkIntervalSamples = + (nextIndicatorSamples - prevIndicatorSamples) * + kBlinkInterval; + } + } + } else { + prevIndicatorSamples = m_PrevBeatSamples; + nextIndicatorSamples = m_NextBeatSamples; + + if ((prevIndicatorSamples != -1) && (nextIndicatorSamples != -1)) { + // Don't overwrite interval at begin/end of track + m_blinkIntervalSamples = + (nextIndicatorSamples - prevIndicatorSamples) * + kBlinkInterval; + } + } + + // The m_InternalState needs to be taken into account, to show a reliable beat indication for loops + if (dRate > 0.0) { + if (m_lastPlayDirection == true) { + if ((currentSample > prevIndicatorSamples) && + (currentSample < + prevIndicatorSamples + m_blinkIntervalSamples) && + (m_InternalState != StateMachine::afterBeatActive) && + (m_InternalState != StateMachine::afterBeatDirectionChanged)) { + m_InternalState = StateMachine::afterBeatActive; + m_pCOBeatActive->forceSet(1.0); + } else if ((currentSample > prevIndicatorSamples + + m_blinkIntervalSamples) && + ((m_InternalState == StateMachine::afterBeatActive) || + (m_InternalState == StateMachine::afterBeatDirectionChanged))) { + m_InternalState = StateMachine::outsideIndicationArea; + m_pCOBeatActive->forceSet(0.0); } - m_lastPlayDirection = true; // Forward } else { - if (m_lastPlayDirection == false) { - if ((currentSample < m_NextBeatSamples) && - (currentSample > - m_NextBeatSamples - m_blinkIntervalSamples) && - (m_InternalState != StateMachine::beforeBeatActive) && - (m_InternalState != StateMachine::beforeBeatDirectionChanged)) { - m_InternalState = StateMachine::beforeBeatActive; - m_pCOBeatActive->forceSet(-1.0); - } else if ((currentSample < m_NextBeatSamples - - m_blinkIntervalSamples) && - ((m_InternalState == StateMachine::beforeBeatActive) || - (m_InternalState == StateMachine::beforeBeatDirectionChanged))) { - m_InternalState = StateMachine::outsideIndicationArea; - m_pCOBeatActive->forceSet(0.0); - } - } else { - // Play direction changed while beat indicator was on and reverse playing - if ((currentSample > m_PrevBeatSamples) && - (currentSample <= - m_PrevBeatSamples + m_blinkIntervalSamples) && - (m_InternalState != StateMachine::afterBeatDirectionChanged)) { - m_InternalState = StateMachine::afterBeatDirectionChanged; - m_pCOBeatActive->forceSet(0.0); - } + // Play direction changed while beat indicator was on and forward playing + if ((currentSample < nextIndicatorSamples) && + (currentSample >= + nextIndicatorSamples - m_blinkIntervalSamples) && + (m_InternalState != StateMachine::beforeBeatDirectionChanged)) { + m_InternalState = StateMachine::beforeBeatDirectionChanged; + m_pCOBeatActive->forceSet(0.0); + } + } + m_lastPlayDirection = true; // Forward + } else if (dRate < 0.0) { + if (m_lastPlayDirection == false) { + if ((currentSample < nextIndicatorSamples) && + (currentSample > + nextIndicatorSamples - m_blinkIntervalSamples) && + (m_InternalState != StateMachine::beforeBeatActive) && + (m_InternalState != StateMachine::beforeBeatDirectionChanged)) { + m_InternalState = StateMachine::beforeBeatActive; + m_pCOBeatActive->forceSet(-1.0); + } else if ((currentSample < nextIndicatorSamples - + m_blinkIntervalSamples) && + ((m_InternalState == StateMachine::beforeBeatActive) || + (m_InternalState == StateMachine::beforeBeatDirectionChanged))) { + m_InternalState = StateMachine::outsideIndicationArea; + m_pCOBeatActive->forceSet(0.0); + } + } else { + // Play direction changed while beat indicator was on and reverse playing + if ((currentSample > prevIndicatorSamples) && + (currentSample <= + prevIndicatorSamples + m_blinkIntervalSamples) && + (m_InternalState != StateMachine::afterBeatDirectionChanged)) { + m_InternalState = StateMachine::afterBeatDirectionChanged; + m_pCOBeatActive->forceSet(0.0); } - m_lastPlayDirection = false; // Reverse } - m_lastEvaluatedSample = currentSample; + m_lastPlayDirection = false; // Reverse } + m_lastEvaluatedSample = currentSample; } diff --git a/src/engine/controls/clockcontrol.h b/src/engine/controls/clockcontrol.h index dccbe1b1824..7c1c7c1cc84 100644 --- a/src/engine/controls/clockcontrol.h +++ b/src/engine/controls/clockcontrol.h @@ -20,7 +20,8 @@ class ClockControl: public EngineControl { const int iBufferSize) override; void updateIndicators(const double dRate, - const double currentSample); + const double currentSample, + const double sampleRate); void trackLoaded(TrackPointer pNewTrack) override; void trackBeatsUpdated(mixxx::BeatsPointer pBeats) override; @@ -28,6 +29,11 @@ class ClockControl: public EngineControl { private: ControlObject* m_pCOBeatActive; + // ControlObjects that come from LoopingControl + ControlProxy* m_pLoopEnabled; + ControlProxy* m_pLoopStartPosition; + ControlProxy* m_pLoopEndPosition; + double m_lastEvaluatedSample; enum class StateMachine : int { diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 0f3cc43a65c..cae33bd1b30 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -1351,7 +1351,7 @@ void EngineBuffer::updateIndicators(double speed, int iBufferSize) { fractionalPlayposFromAbsolute(m_dSlipPosition), tempoTrackSeconds); - m_pClockControl->updateIndicators(speed * m_baserate_old, m_filepos_play); + m_pClockControl->updateIndicators(speed * m_baserate_old, m_filepos_play, m_pSampleRate->get()); } void EngineBuffer::hintReader(const double dRate) { From 6adb30cef5664a37f98af6ed2b34762f50020015 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Mon, 1 Mar 2021 19:08:31 +0100 Subject: [PATCH 07/14] Removed getBeatContextNoLookup() by using findPrevNextBeats without tolerance/snap behavior --- src/engine/controls/bpmcontrol.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/engine/controls/bpmcontrol.cpp b/src/engine/controls/bpmcontrol.cpp index 05143a483f6..76be8884773 100644 --- a/src/engine/controls/bpmcontrol.cpp +++ b/src/engine/controls/bpmcontrol.cpp @@ -539,7 +539,7 @@ bool BpmControl::getBeatContext(const mixxx::BeatsPointer& pBeats, double dPrevBeat; double dNextBeat; - if (!pBeats->findPrevNextBeats(dPosition, &dPrevBeat, &dNextBeat, false)) { + if (!pBeats->findPrevNextBeats(dPosition, &dPrevBeat, &dNextBeat, true)) { return false; } @@ -574,14 +574,6 @@ bool BpmControl::getBeatContextNoLookup( if (dpBeatPercentage != nullptr) { *dpBeatPercentage = dBeatLength == 0.0 ? 0.0 : (dPosition - dPrevBeat) / dBeatLength; - // Because findNext and findPrev have an epsilon built in, sometimes - // the beat percentage is out of range. Fix it. - if (*dpBeatPercentage < 0) { - ++*dpBeatPercentage; - } - if (*dpBeatPercentage > 1) { - --*dpBeatPercentage; - } } return true; From 8b889743aca04ed851a7c9b572735090e559ba2a Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Mon, 1 Mar 2021 20:22:06 +0100 Subject: [PATCH 08/14] Renamed NoTolerance to snapToNearBeats and inverted the bool, to match the new term --- src/engine/controls/bpmcontrol.cpp | 2 +- src/engine/controls/clockcontrol.cpp | 2 +- src/engine/controls/loopingcontrol.cpp | 2 +- src/engine/controls/quantizecontrol.cpp | 2 +- src/test/beatgridtest.cpp | 24 ++++++++++----------- src/test/beatmaptest.cpp | 28 ++++++++++++------------- src/track/beatgrid.cpp | 11 +++++----- src/track/beatgrid.h | 3 +-- src/track/beatmap.cpp | 8 +++---- src/track/beatmap.h | 2 +- src/track/beats.cpp | 2 +- src/track/beats.h | 2 +- 12 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/engine/controls/bpmcontrol.cpp b/src/engine/controls/bpmcontrol.cpp index 76be8884773..38135a461ef 100644 --- a/src/engine/controls/bpmcontrol.cpp +++ b/src/engine/controls/bpmcontrol.cpp @@ -539,7 +539,7 @@ bool BpmControl::getBeatContext(const mixxx::BeatsPointer& pBeats, double dPrevBeat; double dNextBeat; - if (!pBeats->findPrevNextBeats(dPosition, &dPrevBeat, &dNextBeat, true)) { + if (!pBeats->findPrevNextBeats(dPosition, &dPrevBeat, &dNextBeat, false)) { return false; } diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index 66226856891..26f6c5b8e66 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -79,7 +79,7 @@ void ClockControl::updateIndicators(const double dRate, pBeats->findPrevNextBeats(currentSample, &m_PrevBeatSamples, &m_NextBeatSamples, - true); // Precise compare without tolerance needed + false); // Precise compare without tolerance needed } } else { m_PrevBeatSamples = -1; diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 37482602560..deb039a804c 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -1194,7 +1194,7 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable // The closest beat might be ahead of play position and will cause a catching loop. double prevBeat; double nextBeat; - pBeats->findPrevNextBeats(currentSample, &prevBeat, &nextBeat, false); + pBeats->findPrevNextBeats(currentSample, &prevBeat, &nextBeat, true); if (m_pQuantizeEnabled->toBool() && prevBeat != -1) { double beatLength = nextBeat - prevBeat; diff --git a/src/engine/controls/quantizecontrol.cpp b/src/engine/controls/quantizecontrol.cpp index 6edbe1d7724..fe84ae53bea 100644 --- a/src/engine/controls/quantizecontrol.cpp +++ b/src/engine/controls/quantizecontrol.cpp @@ -80,7 +80,7 @@ void QuantizeControl::lookupBeatPositions(double dCurrentSample) { mixxx::BeatsPointer pBeats = m_pBeats; if (pBeats) { double prevBeat, nextBeat; - pBeats->findPrevNextBeats(dCurrentSample, &prevBeat, &nextBeat, false); + pBeats->findPrevNextBeats(dCurrentSample, &prevBeat, &nextBeat, true); m_pCOPrevBeat->set(prevBeat); m_pCONextBeat->set(nextBeat); } diff --git a/src/test/beatgridtest.cpp b/src/test/beatgridtest.cpp index 1607b8fdde1..46496c2a7b3 100644 --- a/src/test/beatgridtest.cpp +++ b/src/test/beatgridtest.cpp @@ -78,12 +78,12 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat) { // Also test prev/next beat calculation. double prevBeat, nextBeat; - pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, false); + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true); EXPECT_NEAR(position, prevBeat, kMaxBeatError); EXPECT_NEAR(position + beatLength, nextBeat, kMaxBeatError); - // Also test prev/next beat calculation without tolerance - pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + // Also test prev/next beat calculation without snaping tolerance + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_NEAR(position, prevBeat, kMaxBeatError); EXPECT_NEAR(position + beatLength, nextBeat, kMaxBeatError); @@ -120,12 +120,12 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { // Also test prev/next beat calculation. double prevBeat, nextBeat; - pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, false); + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true); EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError); EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError); - // Also test prev/next beat calculation without tolerance - pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + // Also test prev/next beat calculation without snaping tolerance + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_NEAR(kClosestBeat - beatLength, prevBeat, kMaxBeatError); EXPECT_NEAR(kClosestBeat, nextBeat, kMaxBeatError); @@ -162,12 +162,12 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_AfterEpsilon) { // Also test prev/next beat calculation. double prevBeat, nextBeat; - pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, false); + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true); EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError); EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError); - // Also test prev/next beat calculation without tolerance - pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + // Also test prev/next beat calculation without snaping tolerance + pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError); EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError); @@ -205,12 +205,12 @@ TEST(BeatGridTest, TestNthBeatWhenNotOnBeat) { // Also test prev/next beat calculation double foundPrevBeat, foundNextBeat; - pGrid->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, false); + pGrid->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, true); EXPECT_NEAR(previousBeat, foundPrevBeat, kMaxBeatError); EXPECT_NEAR(nextBeat, foundNextBeat, kMaxBeatError); - // Also test prev/next beat calculation without tolerance - pGrid->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, true); + // Also test prev/next beat calculation without snaping tolerance + pGrid->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, false); EXPECT_NEAR(previousBeat, foundPrevBeat, kMaxBeatError); EXPECT_NEAR(nextBeat, foundNextBeat, kMaxBeatError); } diff --git a/src/test/beatmaptest.cpp b/src/test/beatmaptest.cpp index 994f202a5da..91f2d3da15c 100644 --- a/src/test/beatmaptest.cpp +++ b/src/test/beatmaptest.cpp @@ -100,11 +100,11 @@ TEST_F(BeatMapTest, TestNthBeat) { EXPECT_EQ(-1, pMap->findNthBeat(firstBeat, -2)); double prevBeat, nextBeat; - pMap->findPrevNextBeats(lastBeat, &prevBeat, &nextBeat, false); + pMap->findPrevNextBeats(lastBeat, &prevBeat, &nextBeat, true); EXPECT_EQ(lastBeat, prevBeat); EXPECT_EQ(-1, nextBeat); - pMap->findPrevNextBeats(firstBeat, &prevBeat, &nextBeat, false); + pMap->findPrevNextBeats(firstBeat, &prevBeat, &nextBeat, true); EXPECT_EQ(firstBeat, prevBeat); EXPECT_EQ(firstBeat + beatLengthSamples, nextBeat); } @@ -137,12 +137,12 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat) { // Also test prev/next beat calculation. double prevBeat, nextBeat; - pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false); + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true); EXPECT_EQ(position, prevBeat); EXPECT_EQ(position + beatLengthSamples, nextBeat); - // Also test prev/next beat calculation without tolerance - pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + // Also test prev/next beat calculation without snaping tolerance + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_EQ(position, prevBeat); EXPECT_EQ(position + beatLengthSamples, nextBeat); @@ -180,12 +180,12 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { // Also test prev/next beat calculation double prevBeat, nextBeat; - pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false); + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true); EXPECT_EQ(kClosestBeat, prevBeat); EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat); - // Also test prev/next beat calculation without tolerance - pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + // Also test prev/next beat calculation without snaping tolerance + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_EQ(kClosestBeat - beatLengthSamples, prevBeat); EXPECT_EQ(kClosestBeat, nextBeat); @@ -226,12 +226,12 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_AfterEpsilon) { // Also test prev/next beat calculation. double prevBeat, nextBeat; - pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false); + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true); EXPECT_EQ(kClosestBeat, prevBeat); EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat); - // Also test prev/next beat calculation without tolerance - pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true); + // Also test prev/next beat calculation without snapping tolerance + pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false); EXPECT_EQ(kClosestBeat, prevBeat); EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat); @@ -271,12 +271,12 @@ TEST_F(BeatMapTest, TestNthBeatWhenNotOnBeat) { // Also test prev/next beat calculation double foundPrevBeat, foundNextBeat; - pMap->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, false); + pMap->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, true); EXPECT_EQ(previousBeat, foundPrevBeat); EXPECT_EQ(nextBeat, foundNextBeat); - // Also test prev/next beat calculation without tolerance - pMap->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, true); + // Also test prev/next beat calculation without snaping tolerance + pMap->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, false); EXPECT_EQ(previousBeat, foundPrevBeat); EXPECT_EQ(nextBeat, foundNextBeat); } diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp index 26ce54297a3..aa0ea8d582d 100644 --- a/src/track/beatgrid.cpp +++ b/src/track/beatgrid.cpp @@ -158,7 +158,7 @@ double BeatGrid::findClosestBeat(double dSamples) const { } double prevBeat; double nextBeat; - findPrevNextBeats(dSamples, &prevBeat, &nextBeat, false); + findPrevNextBeats(dSamples, &prevBeat, &nextBeat, true); if (prevBeat == -1) { // If both values are -1, we correctly return -1. return nextBeat; @@ -214,10 +214,9 @@ double BeatGrid::findNthBeat(double dSamples, int n) const { } bool BeatGrid::findPrevNextBeats(double dSamples, - double* dpPrevBeatSamples, double* dpNextBeatSamples, - bool NoTolerance) const { + bool snapToNearBeats) const { double dFirstBeatSample; double dBeatLength; { @@ -237,9 +236,9 @@ bool BeatGrid::findPrevNextBeats(double dSamples, const double kEpsilon = .01; - if ((NoTolerance && ((nextBeat - beatFraction) == 0.0)) || - (!NoTolerance && (fabs(nextBeat - beatFraction) < kEpsilon))) { - // In tolerance mode: If the position is within 1/100th of the next or previous beat, + if ((!snapToNearBeats && ((nextBeat - beatFraction) == 0.0)) || + (snapToNearBeats && (fabs(nextBeat - beatFraction) < kEpsilon))) { + // In snapToNearBeats mode: If the position is within 1/100th of the next or previous beat, // treat it as if it is that beat. beatFraction = nextBeat; diff --git a/src/track/beatgrid.h b/src/track/beatgrid.h index b1b0612b721..1f260b16717 100644 --- a/src/track/beatgrid.h +++ b/src/track/beatgrid.h @@ -52,10 +52,9 @@ class BeatGrid final : public Beats { double findNextBeat(double dSamples) const override; double findPrevBeat(double dSamples) const override; bool findPrevNextBeats(double dSamples, - double* dpPrevBeatSamples, double* dpNextBeatSamples, - bool NoTolerance) const override; + bool snapToNearBeats) const override; double findClosestBeat(double dSamples) const override; double findNthBeat(double dSamples, int n) const override; std::unique_ptr findBeats(double startSample, double stopSample) const override; diff --git a/src/track/beatmap.cpp b/src/track/beatmap.cpp index 76b9c68f90b..505c88b62ec 100644 --- a/src/track/beatmap.cpp +++ b/src/track/beatmap.cpp @@ -188,7 +188,7 @@ double BeatMap::findClosestBeat(double dSamples) const { } double prevBeat; double nextBeat; - findPrevNextBeats(dSamples, &prevBeat, &nextBeat, false); + findPrevNextBeats(dSamples, &prevBeat, &nextBeat, true); if (prevBeat == -1) { // If both values are -1, we correctly return -1. return nextBeat; @@ -288,7 +288,7 @@ double BeatMap::findNthBeat(double dSamples, int n) const { bool BeatMap::findPrevNextBeats(double dSamples, double* dpPrevBeatSamples, double* dpNextBeatSamples, - bool NoTolerance) const { + bool snapToNearBeats) const { QMutexLocker locker(&m_mutex); if (!isValid()) { @@ -321,8 +321,8 @@ bool BeatMap::findPrevNextBeats(double dSamples, for (; it != m_beats.end(); ++it) { qint32 delta = it->frame_position() - beat.frame_position(); - if ((NoTolerance && (delta == 0)) || - (!NoTolerance && (abs(delta) < kFrameEpsilon))) { + if ((!snapToNearBeats && (delta == 0)) || + (snapToNearBeats && (abs(delta) < kFrameEpsilon))) { // We are "on" this beat. on_beat = it; break; diff --git a/src/track/beatmap.h b/src/track/beatmap.h index cd231d74963..e3881778b0b 100644 --- a/src/track/beatmap.h +++ b/src/track/beatmap.h @@ -62,7 +62,7 @@ class BeatMap final : public Beats { bool findPrevNextBeats(double dSamples, double* dpPrevBeatSamples, double* dpNextBeatSamples, - bool NoTolerance) const override; + bool snapToNearBeats) const override; double findClosestBeat(double dSamples) const override; double findNthBeat(double dSamples, int n) const override; std::unique_ptr findBeats(double startSample, double stopSample) const override; diff --git a/src/track/beats.cpp b/src/track/beats.cpp index 992c295d616..56eb37b1772 100644 --- a/src/track/beats.cpp +++ b/src/track/beats.cpp @@ -21,7 +21,7 @@ double Beats::findNBeatsFromSample(double fromSample, double beats) const { double prevBeat; double nextBeat; - if (!findPrevNextBeats(fromSample, &prevBeat, &nextBeat, false)) { + if (!findPrevNextBeats(fromSample, &prevBeat, &nextBeat, true)) { return fromSample; } double fromFractionBeats = (fromSample - prevBeat) / (nextBeat - prevBeat); diff --git a/src/track/beats.h b/src/track/beats.h index 19814f58eeb..3c3ee7471a4 100644 --- a/src/track/beats.h +++ b/src/track/beats.h @@ -95,7 +95,7 @@ class Beats : public QObject { virtual bool findPrevNextBeats(double dSamples, double* dpPrevBeatSamples, double* dpNextBeatSamples, - bool NoTolerance) const = 0; + bool snapToNearBeats) const = 0; // Starting from sample dSamples, return the sample of the closest beat in // the track, or -1 if none exists. Non- -1 values are guaranteed to be From 93feabe67c2e3452941446ae17edb345bbcc78b4 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Tue, 2 Mar 2021 19:51:57 +0100 Subject: [PATCH 09/14] const -> constexpr --- src/engine/controls/clockcontrol.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index 26f6c5b8e66..608f00bdae7 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -7,6 +7,12 @@ #include "preferences/usersettings.h" #include "track/track.h" +namespace { +constexpr double kBlinkInterval = 0.20; // LED is on 20% of the beat period +constexpr double kStandStillTolerance = + 0.0025; // (seconds) Minimum change, to he last evaluated position +} // namespace + ClockControl::ClockControl(const QString& group, UserSettingsPointer pConfig) : EngineControl(group, pConfig) { m_pCOBeatActive = new ControlObject(ConfigKey(group, "beat_active")); @@ -58,11 +64,6 @@ void ClockControl::updateIndicators(const double dRate, * -1.0 --> Reverse playing, set at the beat and set back to 0.0 at -20% of beat distance */ - // TODO(XXX) should this be customizable, or latency dependent? - const double kBlinkInterval = 0.20; // LED is on 20% of the beat period - const double kStandStillTolerance = - 0.0025; // (seconds) Minimum change, to he last evaluated position - if (((currentSample <= (m_lastEvaluatedSample + kStandStillTolerance * sampleRate)) && (currentSample >= (m_lastEvaluatedSample - kStandStillTolerance * sampleRate))) || (dRate == 0.0)) { From fd644116d3cb2151e4b5e5a391c0c84344f00e2e Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Tue, 2 Mar 2021 20:10:22 +0100 Subject: [PATCH 10/14] Added ToDo comment about moving the updateIndicators to waveform update loop. --- src/engine/enginebuffer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index cae33bd1b30..9593905856a 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -1351,6 +1351,10 @@ void EngineBuffer::updateIndicators(double speed, int iBufferSize) { fractionalPlayposFromAbsolute(m_dSlipPosition), tempoTrackSeconds); + // TODO: Especially with long audio buffers, jitter is visible. This can be fixed by moving the + // ClockControl::updateIndicators into the waveform update loop which is synced with the display refresh rate. + // Via the visual play position it's possible to access to the sample that is currently played, + // and not the one that have been processed as in the current solution. m_pClockControl->updateIndicators(speed * m_baserate_old, m_filepos_play, m_pSampleRate->get()); } From c3493eae15688ffae9bf8299ccac3fd91c354e1e Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Fri, 5 Mar 2021 20:17:40 +0100 Subject: [PATCH 11/14] Handled the cue point case. Don't show beat indication after (hot) cue button press. --- src/engine/controls/clockcontrol.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index 608f00bdae7..d731f1f7a71 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -10,7 +10,7 @@ namespace { constexpr double kBlinkInterval = 0.20; // LED is on 20% of the beat period constexpr double kStandStillTolerance = - 0.0025; // (seconds) Minimum change, to he last evaluated position + 0.005; // (seconds) Minimum change, to he last evaluated position } // namespace ClockControl::ClockControl(const QString& group, UserSettingsPointer pConfig) @@ -64,10 +64,18 @@ void ClockControl::updateIndicators(const double dRate, * -1.0 --> Reverse playing, set at the beat and set back to 0.0 at -20% of beat distance */ - if (((currentSample <= (m_lastEvaluatedSample + kStandStillTolerance * sampleRate)) && - (currentSample >= (m_lastEvaluatedSample - kStandStillTolerance * sampleRate))) || - (dRate == 0.0)) { - return; // No position change since last indicator update (e.g. deck stopped) -> No indicator update needed + // No position change since last indicator update (e.g. deck stopped) -> No indicator update needed + if ((currentSample <= (m_lastEvaluatedSample + kStandStillTolerance * sampleRate)) && + (currentSample >= (m_lastEvaluatedSample - kStandStillTolerance * sampleRate))) { + return; + } + + // Position change more significiantly, but rate is zero. Occurs when pressing a cue point + // The m_InternalState needs to be taken into account here to prevent uneccessary events (state 0 -> state 0) + if ((dRate == 0.0) + && (m_InternalState != StateMachine::outsideIndicationArea)) { + m_InternalState = StateMachine::outsideIndicationArea; + m_pCOBeatActive->forceSet(0.0); } double prevIndicatorSamples; From d7ab0d09ef1d775a6ba902404fa20505b0e9fe45 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Fri, 5 Mar 2021 20:28:06 +0100 Subject: [PATCH 12/14] Changed reverse beat indication state from -1 to +2 --- src/engine/controls/clockcontrol.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index d731f1f7a71..eebd1b7d207 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -59,9 +59,9 @@ void ClockControl::updateIndicators(const double dRate, const double currentSample, const double sampleRate) { /* This method sets the control beat_active is set to the following values: - * +1.0 --> Forward playing, set at the beat and set back to 0.0 at 20% of beat distance * 0.0 --> No beat indication (ouside 20% area or play direction changed while indication was on) - * -1.0 --> Reverse playing, set at the beat and set back to 0.0 at -20% of beat distance + * 1.0 --> Forward playing, set at the beat and set back to 0.0 at 20% of beat distance + * 2.0 --> Reverse playing, set at the beat and set back to 0.0 at -20% of beat distance */ // No position change since last indicator update (e.g. deck stopped) -> No indicator update needed @@ -171,7 +171,7 @@ void ClockControl::updateIndicators(const double dRate, (m_InternalState != StateMachine::beforeBeatActive) && (m_InternalState != StateMachine::beforeBeatDirectionChanged)) { m_InternalState = StateMachine::beforeBeatActive; - m_pCOBeatActive->forceSet(-1.0); + m_pCOBeatActive->forceSet(2.0); } else if ((currentSample < nextIndicatorSamples - m_blinkIntervalSamples) && ((m_InternalState == StateMachine::beforeBeatActive) || From f04c35a802ce5e0573c96d9049a5518b3adf29b6 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Fri, 5 Mar 2021 20:43:34 +0100 Subject: [PATCH 13/14] pre-commit --- src/engine/controls/clockcontrol.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index 5f0e36c70eb..4ae4fdbbb6d 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -66,14 +66,13 @@ void ClockControl::updateIndicators(const double dRate, // No position change since last indicator update (e.g. deck stopped) -> No indicator update needed if ((currentSample <= (m_lastEvaluatedSample + kStandStillTolerance * sampleRate)) && - (currentSample >= (m_lastEvaluatedSample - kStandStillTolerance * sampleRate))) { + (currentSample >= (m_lastEvaluatedSample - kStandStillTolerance * sampleRate))) { return; } // Position change more significiantly, but rate is zero. Occurs when pressing a cue point // The m_InternalState needs to be taken into account here to prevent uneccessary events (state 0 -> state 0) - if ((dRate == 0.0) - && (m_InternalState != StateMachine::outsideIndicationArea)) { + if ((dRate == 0.0) && (m_InternalState != StateMachine::outsideIndicationArea)) { m_InternalState = StateMachine::outsideIndicationArea; m_pCOBeatActive->forceSet(0.0); } From 277ed269fc956d04ecbeecbe647ee6e68b8d38f4 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Sat, 6 Mar 2021 12:39:28 +0100 Subject: [PATCH 14/14] Improved responsiveness when pressing play/cue button at while track is hold on a beat --- src/engine/controls/clockcontrol.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index 4ae4fdbbb6d..8b6c8fdd777 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -11,6 +11,8 @@ namespace { constexpr double kBlinkInterval = 0.20; // LED is on 20% of the beat period constexpr double kStandStillTolerance = 0.005; // (seconds) Minimum change, to he last evaluated position +constexpr double kSignificiantRateThreshold = + 0.1; // If rate is significiant, update indicator also inside standstill tolerance } // namespace ClockControl::ClockControl(const QString& group, UserSettingsPointer pConfig) @@ -65,8 +67,10 @@ void ClockControl::updateIndicators(const double dRate, */ // No position change since last indicator update (e.g. deck stopped) -> No indicator update needed + // The kSignificiantRateThreshold condition ensures an immidiate indicator update, when the play/cue button is pressed if ((currentSample <= (m_lastEvaluatedSample + kStandStillTolerance * sampleRate)) && - (currentSample >= (m_lastEvaluatedSample - kStandStillTolerance * sampleRate))) { + (currentSample >= (m_lastEvaluatedSample - kStandStillTolerance * sampleRate)) && + (fabs(dRate) <= kSignificiantRateThreshold)) { return; }