summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Schürmann <daschuer@mixxx.org>2021-03-20 22:32:24 +0100
committerGitHub <noreply@github.com>2021-03-20 22:32:24 +0100
commit4ffba2a06f55b5e359d1a69762c9b705d91ebb66 (patch)
treed5ca54d9ee8e154eb4c79b13be4b73b59a3bf97f
parentaa3cc95c786742d2e2254ce9062a6c3f8f80dac7 (diff)
parent277ed269fc956d04ecbeecbe647ee6e68b8d38f4 (diff)
Merge pull request #3608 from JoergAtGithub/improve_beat_indicator
Reworked beat_active control:
-rw-r--r--src/engine/controls/bpmcontrol.cpp10
-rw-r--r--src/engine/controls/clockcontrol.cpp172
-rw-r--r--src/engine/controls/clockcontrol.h34
-rw-r--r--src/engine/controls/loopingcontrol.cpp2
-rw-r--r--src/engine/controls/quantizecontrol.cpp2
-rw-r--r--src/engine/enginebuffer.cpp6
-rw-r--r--src/test/beatgridtest.cpp28
-rw-r--r--src/test/beatmaptest.cpp32
-rw-r--r--src/track/beatgrid.cpp16
-rw-r--r--src/track/beatgrid.h5
-rw-r--r--src/track/beatmap.cpp13
-rw-r--r--src/track/beatmap.h5
-rw-r--r--src/track/beats.cpp2
-rw-r--r--src/track/beats.h5
14 files changed, 275 insertions, 57 deletions
diff --git a/src/engine/controls/bpmcontrol.cpp b/src/engine/controls/bpmcontrol.cpp
index 8336c9ddb8..f35990167b 100644
--- a/src/engine/controls/bpmcontrol.cpp
+++ b/src/engine/controls/bpmcontrol.cpp
@@ -570,7 +570,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;
}
@@ -605,14 +605,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;
diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp
index 6dc574ea4b..8b6c8fdd77 100644
--- a/src/engine/controls/clockcontrol.cpp
+++ b/src/engine/controls/clockcontrol.cpp
@@ -7,16 +7,31 @@
#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.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)
: 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_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() {
delete m_pCOBeatActive;
- delete m_pCOSampleRate;
}
// called from an engine worker thread
@@ -30,27 +45,154 @@ 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();
+}
- // TODO(XXX) should this be customizable, or latency dependent?
- const double blinkSeconds = 0.100;
+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:
+ * 0.0 --> No beat indication (ouside 20% area or play direction changed while indication was on)
+ * 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
+ */
- // 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;
+ // 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)) &&
+ (fabs(dRate) <= kSignificiantRateThreshold)) {
+ 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;
+ double nextIndicatorSamples;
const 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)) {
+ pBeats->findPrevNextBeats(currentSample,
+ &m_PrevBeatSamples,
+ &m_NextBeatSamples,
+ false); // Precise compare without tolerance needed
+ }
+ } else {
+ m_PrevBeatSamples = -1;
+ m_NextBeatSamples = -1;
+ }
+
+ // 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;
+ }
+
+ 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 {
+ 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);
+ }
+ } else {
+ // 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(2.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;
}
diff --git a/src/engine/controls/clockcontrol.h b/src/engine/controls/clockcontrol.h
index 87667db443..7c1c7c1cc8 100644
--- a/src/engine/controls/clockcontrol.h
+++ b/src/engine/controls/clockcontrol.h
@@ -19,12 +19,44 @@ class ClockControl: public EngineControl {
void process(const double dRate, const double currentSample,
const int iBufferSize) override;
+ void updateIndicators(const double dRate,
+ const double currentSample,
+ const double sampleRate);
+
void trackLoaded(TrackPointer pNewTrack) override;
void trackBeatsUpdated(mixxx::BeatsPointer pBeats) override;
private:
ControlObject* m_pCOBeatActive;
- ControlProxy* m_pCOSampleRate;
+
+ // ControlObjects that come from LoopingControl
+ ControlProxy* m_pLoopEnabled;
+ ControlProxy* m_pLoopStartPosition;
+ ControlProxy* m_pLoopEndPosition;
+
+ 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;
+
+ // 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/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp
index 755d4dfca3..c83b618ebd 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, 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 c059bca51c..fe84ae53be 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, true);
m_pCOPrevBeat->set(prevBeat);
m_pCONextBeat->set(nextBeat);
}
diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp
index b81f1ea291..3b1627b6c5 100644
--- a/src/engine/enginebuffer.cpp
+++ b/src/engine/enginebuffer.cpp
@@ -1356,6 +1356,12 @@ void EngineBuffer::updateIndicators(double speed, int iBufferSize) {
(double)iBufferSize / m_trackSamplesOld,
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());
}
void EngineBuffer::hintReader(const double dRate) {
diff --git a/src/test/beatgridtest.cpp b/src/test/beatgridtest.cpp
index 04f2c5ef34..a685a781d3 100644
--- a/src/test/beatgridtest.cpp
+++ b/src/test/beatgridtest.cpp
@@ -76,7 +76,12 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat) {
// Also test prev/next beat calculation.
double prevBeat, nextBeat;
- pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat);
+ pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true);
+ EXPECT_NEAR(position, prevBeat, kMaxBeatError);
+ EXPECT_NEAR(position + beatLength, nextBeat, kMaxBeatError);
+
+ // 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);
@@ -112,10 +117,15 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_BeforeEpsilon) {
// Also test prev/next beat calculation.
double prevBeat, nextBeat;
- pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat);
+ pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true);
EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError);
EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError);
+ // 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);
+
// Both previous and next beat should return the closest beat.
EXPECT_NEAR(kClosestBeat, pGrid->findNextBeat(position), kMaxBeatError);
EXPECT_NEAR(kClosestBeat, pGrid->findPrevBeat(position), kMaxBeatError);
@@ -148,7 +158,12 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_AfterEpsilon) {
// Also test prev/next beat calculation.
double prevBeat, nextBeat;
- pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat);
+ pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat, true);
+ EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError);
+ EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError);
+
+ // 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);
@@ -185,7 +200,12 @@ TEST(BeatGridTest, TestNthBeatWhenNotOnBeat) {
// Also test prev/next beat calculation
double foundPrevBeat, foundNextBeat;
- pGrid->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat);
+ pGrid->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, true);
+ EXPECT_NEAR(previousBeat, foundPrevBeat, kMaxBeatError);
+ EXPECT_NEAR(nextBeat, foundNextBeat, kMaxBeatError);
+
+ // 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 a6b7c22174..73253ff671 100644
--- a/src/test/beatmaptest.cpp
+++ b/src/test/beatmaptest.cpp
@@ -99,11 +99,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, true);
EXPECT_EQ(lastBeat, prevBeat);
EXPECT_EQ(-1, nextBeat);
- pMap->findPrevNextBeats(firstBeat, &prevBeat, &nextBeat);
+ pMap->findPrevNextBeats(firstBeat, &prevBeat, &nextBeat, true);
EXPECT_EQ(firstBeat, prevBeat);
EXPECT_EQ(firstBeat + beatLengthSamples, nextBeat);
}
@@ -136,7 +136,12 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat) {
// Also test prev/next beat calculation.
double prevBeat, nextBeat;
- pMap->findPrevNextBeats(position, &prevBeat, &nextBeat);
+ pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true);
+ EXPECT_EQ(position, prevBeat);
+ EXPECT_EQ(position + beatLengthSamples, nextBeat);
+
+ // Also test prev/next beat calculation without snaping tolerance
+ pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false);
EXPECT_EQ(position, prevBeat);
EXPECT_EQ(position + beatLengthSamples, nextBeat);
@@ -174,10 +179,15 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_BeforeEpsilon) {
// Also test prev/next beat calculation
double prevBeat, nextBeat;
- pMap->findPrevNextBeats(position, &prevBeat, &nextBeat);
+ pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true);
EXPECT_EQ(kClosestBeat, prevBeat);
EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat);
+ // Also test prev/next beat calculation without snaping tolerance
+ pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false);
+ 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));
@@ -215,7 +225,12 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_AfterEpsilon) {
// Also test prev/next beat calculation.
double prevBeat, nextBeat;
- pMap->findPrevNextBeats(position, &prevBeat, &nextBeat);
+ pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, true);
+ EXPECT_EQ(kClosestBeat, prevBeat);
+ EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat);
+
+ // Also test prev/next beat calculation without snapping tolerance
+ pMap->findPrevNextBeats(position, &prevBeat, &nextBeat, false);
EXPECT_EQ(kClosestBeat, prevBeat);
EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat);
@@ -255,7 +270,12 @@ TEST_F(BeatMapTest, TestNthBeatWhenNotOnBeat) {
// Also test prev/next beat calculation
double foundPrevBeat, foundNextBeat;
- pMap->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat);
+ pMap->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat, true);
+ EXPECT_EQ(previousBeat, foundPrevBeat);
+ EXPECT_EQ(nextBeat, foundNextBeat);
+
+ // 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 61b4887b8e..f43d3a50fd 100644
--- a/src/track/beatgrid.cpp
+++ b/src/track/beatgrid.cpp
@@ -151,7 +151,7 @@ double BeatGrid::findClosestBeat(double dSamples) const {
}
double prevBeat;
double nextBeat;
- findPrevNextBeats(dSamples, &prevBeat, &nextBeat);
+ findPrevNextBeats(dSamples, &prevBeat, &nextBeat, true);
if (prevBeat == -1) {
// If both values are -1, we correctly return -1.
return nextBeat;
@@ -206,8 +206,9 @@ double BeatGrid::findNthBeat(double dSamples, int n) const {
}
bool BeatGrid::findPrevNextBeats(double dSamples,
- double* dpPrevBeatSamples,
- double* dpNextBeatSamples) const {
+ double* dpPrevBeatSamples,
+ double* dpNextBeatSamples,
+ bool snapToNearBeats) const {
double dFirstBeatSample;
double dBeatLength;
if (!isValid()) {
@@ -222,11 +223,13 @@ 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 (fabs(nextBeat - beatFraction) < kEpsilon) {
+ 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;
// 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
@@ -240,7 +243,6 @@ bool BeatGrid::findPrevNextBeats(double dSamples,
return true;
}
-
std::unique_ptr<BeatIterator> BeatGrid::findBeats(double startSample, double stopSample) const {
if (!isValid() || startSample > stopSample) {
return std::unique_ptr<BeatIterator>();
diff --git a/src/track/beatgrid.h b/src/track/beatgrid.h
index 886a814171..8397fd7afd 100644
--- a/src/track/beatgrid.h
+++ b/src/track/beatgrid.h
@@ -46,8 +46,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) const override;
+ double* dpPrevBeatSamples,
+ double* dpNextBeatSamples,
+ bool snapToNearBeats) const override;
double findClosestBeat(double dSamples) const override;
double findNthBeat(double dSamples, int n) const override;
std::unique_ptr<BeatIterator> findBeats(double startSample, double stopSample) const override;
diff --git a/src/track/beatmap.cpp b/src/track/beatmap.cpp
index 1693155f88..f68af39bfc 100644
--- a/src/track/beatmap.cpp
+++ b/src/track/beatmap.cpp
@@ -287,7 +287,7 @@ double BeatMap::findClosestBeat(double dSamples) const {
}
double prevBeat;
double nextBeat;
- findPrevNextBeats(dSamples, &prevBeat, &nextBeat);
+ findPrevNextBeats(dSamples, &prevBeat, &nextBeat, true);
if (prevBeat == -1) {
// If both values are -1, we correctly return -1.
return nextBeat;
@@ -382,10 +382,10 @@ double BeatMap::findNthBeat(double dSamples, int n) const {
return -1;
}
-bool BeatMap::findPrevNextBeats(
- double dSamples,
+bool BeatMap::findPrevNextBeats(double dSamples,
double* dpPrevBeatSamples,
- double* dpNextBeatSamples) const {
+ double* dpNextBeatSamples,
+ bool snapToNearBeats) const {
if (!isValid()) {
*dpPrevBeatSamples = -1;
*dpNextBeatSamples = -1;
@@ -416,8 +416,9 @@ bool BeatMap::findPrevNextBeats(
for (; it != m_beats.end(); ++it) {
qint32 delta = it->frame_position() - beat.frame_position();
- // We are "on" this beat.
- if (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 d42e569722..a51e313609 100644
--- a/src/track/beatmap.h
+++ b/src/track/beatmap.h
@@ -49,8 +49,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 snapToNearBeats) const override;
double findClosestBeat(double dSamples) const override;
double findNthBeat(double dSamples, int n) const override;
std::unique_ptr<BeatIterator> findBeats(double startSample, double stopSample) const override;
diff --git a/src/track/beats.cpp b/src/track/beats.cpp
index c65266de7e..d8911f01a9 100644
--- a/src/track/beats.cpp
+++ b/src/track/beats.cpp
@@ -19,7 +19,7 @@ double Beats::findNBeatsFromSample(double fromSample, double beats) const {
double prevBeat;
double nextBeat;
- if (!findPrevNextBeats(fromSample, &prevBeat, &nextBeat)) {
+ 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 6f2540ab1d..73aaa4f72e 100644
--- a/src/track/beats.h
+++ b/src/track/beats.h
@@ -90,8 +90,9 @@ class Beats {
// 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 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