From a39c37d98eb0e1bacfa8aa2209ea87868c6f78fb Mon Sep 17 00:00:00 2001 From: Philip Gottschling Date: Tue, 17 Sep 2019 17:41:49 +0200 Subject: [PATCH 1/3] beat loops snap to the closest (fractional) beat in quantized mode --- src/engine/controls/loopingcontrol.cpp | 37 ++++++++++++++++++-------- src/engine/enginebuffer.cpp | 4 +++ src/engine/enginebuffer.h | 2 ++ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 02f9e10fb4c..2e7ae26ccba 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -1060,16 +1060,16 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable newloopSamples.start = currentSample; } } else { - // loop_in is set to the previous beat if quantize is on. The - // closest beat might be ahead of play position which would cause a seek. - // TODO: If in reverse, should probably choose nextBeat. + // loop_in is set to the closest beat if quantize is on and the loop size is >= 1 beat. + // The closest beat might be ahead of play position and will cause a catching loop. double prevBeat; double nextBeat; pBeats->findPrevNextBeats(currentSample, &prevBeat, &nextBeat); if (m_pQuantizeEnabled->toBool() && prevBeat != -1) { + double closestBeat = pBeats->findClosestBeat(currentSample); if (beats >= 1.0) { - newloopSamples.start = prevBeat; + newloopSamples.start = closestBeat; } else { // In case of beat length less then 1 beat: // (| - beats, ^ - current track's position): @@ -1078,13 +1078,28 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable // // If we press 1/2 beatloop we want loop from 50% to 100%, // If I press 1/4 beatloop, we want loop from 50% to 75% etc - double beat_len = nextBeat - prevBeat; - double loops_per_beat = 1.0 / beats; - double beat_pos = currentSample - prevBeat; - int beat_frac = - static_cast(floor((beat_pos / beat_len) * - loops_per_beat)); - newloopSamples.start = prevBeat + beat_len / loops_per_beat * beat_frac; + double beatLength = nextBeat - prevBeat; + double beatPos = currentSample - prevBeat; + + // find the two closest beat fractions and place the new loop start to the closer one + double fractionBeatLength = beatLength * beats; + double previousFractionBeat = prevBeat + (floor(beatPos / fractionBeatLength)) * fractionBeatLength; + double nextFractionBeat = prevBeat + (floor(beatPos / fractionBeatLength) + 1) * fractionBeatLength; + + if (abs(previousFractionBeat - currentSample) <= abs(nextFractionBeat - currentSample)) { + newloopSamples.start = previousFractionBeat; + } else { + newloopSamples.start = nextFractionBeat; + } + + // If running reverse, move the loop one fractional beat to the left. + // Thus, the loop end is (time-wise) in front of the current position. + // + // TODO: There must be a better way of checking if we are running reverse. Getting the "old_reverse" from the EngineBuffer somehow seems wrong. + // Furthermore, it does not work when we are not playing and the reverse button is held. + if (getEngineBuffer()->isReverse()) { + newloopSamples.start -= fractionBeatLength; + } } } else { diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 2c10c3bfe8a..8818a7ea30e 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -532,6 +532,10 @@ TrackPointer EngineBuffer::getLoadedTrack() const { return m_pCurrentTrack; } +bool EngineBuffer::isReverse() { + return m_reverse_old; +} + void EngineBuffer::ejectTrack() { // clear track values in any case, this may fix Bug #1450424 //qDebug() << "EngineBuffer::ejectTrack()"; diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h index 46735d16fa1..394794225fb 100644 --- a/src/engine/enginebuffer.h +++ b/src/engine/enginebuffer.h @@ -146,6 +146,8 @@ class EngineBuffer : public EngineObject { bool getQueuedSeekPosition(double* pSeekPosition); TrackPointer getLoadedTrack() const; + bool isReverse(); + double getExactPlayPos(); double getVisualPlayPos(); double getTrackSamples(); From 3bef0dffbcff75348e954ebe853f582da302728e Mon Sep 17 00:00:00 2001 From: Philip Gottschling Date: Sun, 22 Sep 2019 14:08:09 +0200 Subject: [PATCH 2/3] requested changes --- src/engine/controls/loopingcontrol.cpp | 35 +++++++++++++------------- src/engine/controls/loopingcontrol.h | 1 + src/engine/enginebuffer.cpp | 8 +++--- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 2e7ae26ccba..f050ed94354 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -119,6 +119,7 @@ LoopingControl::LoopingControl(QString group, m_pClosestBeat = ControlObject::getControl(ConfigKey(group, "beat_closest")); m_pTrackSamples = ControlObject::getControl(ConfigKey(group, "track_samples")); m_pSlipEnabled = ControlObject::getControl(ConfigKey(group, "slip_enabled")); + m_pCOReverse = ControlObject::getControl(group, "reverse"); // DEPRECATED: Use beatloop_size and beatloop_set instead. // Activates a beatloop of a specified number of beats. @@ -1067,6 +1068,9 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable pBeats->findPrevNextBeats(currentSample, &prevBeat, &nextBeat); if (m_pQuantizeEnabled->toBool() && prevBeat != -1) { + double beatLength = nextBeat - prevBeat; + double loopLength = beatLength * beats; + double closestBeat = pBeats->findClosestBeat(currentSample); if (beats >= 1.0) { newloopSamples.start = closestBeat; @@ -1078,30 +1082,25 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable // // If we press 1/2 beatloop we want loop from 50% to 100%, // If I press 1/4 beatloop, we want loop from 50% to 75% etc - double beatLength = nextBeat - prevBeat; - double beatPos = currentSample - prevBeat; + double samplesSinceLastBeat = currentSample - prevBeat; - // find the two closest beat fractions and place the new loop start to the closer one - double fractionBeatLength = beatLength * beats; - double previousFractionBeat = prevBeat + (floor(beatPos / fractionBeatLength)) * fractionBeatLength; - double nextFractionBeat = prevBeat + (floor(beatPos / fractionBeatLength) + 1) * fractionBeatLength; + // find the previous beat fraction and check if the current position is closer to this or the next one + // place the new loop start to the closer one + double previousFractionBeat = prevBeat + floor(samplesSinceLastBeat / beatLength) * beatLength; + double samplesSinceLastFractionBeat = currentSample - previousFractionBeat; - if (abs(previousFractionBeat - currentSample) <= abs(nextFractionBeat - currentSample)) { + if (samplesSinceLastFractionBeat <= (loopLength / 2.0)) { newloopSamples.start = previousFractionBeat; } else { - newloopSamples.start = nextFractionBeat; - } - - // If running reverse, move the loop one fractional beat to the left. - // Thus, the loop end is (time-wise) in front of the current position. - // - // TODO: There must be a better way of checking if we are running reverse. Getting the "old_reverse" from the EngineBuffer somehow seems wrong. - // Furthermore, it does not work when we are not playing and the reverse button is held. - if (getEngineBuffer()->isReverse()) { - newloopSamples.start -= fractionBeatLength; + newloopSamples.start = previousFractionBeat + loopLength; } } - + // If running reverse, move the loop one loop size to the left. + // Thus, the loops end will be closest to the current position + bool reverse = m_pCOReverse->toBool(); + if (reverse) { + newloopSamples.start -= loopLength; + } } else { newloopSamples.start = currentSample; } diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h index 55d4f0c15f1..26a8e134adf 100644 --- a/src/engine/controls/loopingcontrol.h +++ b/src/engine/controls/loopingcontrol.h @@ -129,6 +129,7 @@ class LoopingControl : public EngineControl { ControlPushButton* m_pLoopHalveButton; ControlPushButton* m_pLoopDoubleButton; ControlObject* m_pSlipEnabled; + ControlObject* m_pCOReverse; ControlObject* m_pPlayButton; bool m_bLoopingEnabled; diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 8818a7ea30e..cdb92315916 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -181,6 +181,11 @@ EngineBuffer::EngineBuffer(const QString& group, UserSettingsPointer pConfig, // beats. QuantizeControl* quantize_control = new QuantizeControl(group, pConfig); + // Rate Controller must be created before Looping Controller for the Reverse Button to be created + m_pRateControl = new RateControl(group, pConfig); + // Add the Rate Controller + addControl(m_pRateControl); + // Create the Loop Controller m_pLoopingControl = new LoopingControl(group, pConfig); addControl(m_pLoopingControl); @@ -198,9 +203,6 @@ EngineBuffer::EngineBuffer(const QString& group, UserSettingsPointer pConfig, addControl(m_pVinylControlControl); #endif - m_pRateControl = new RateControl(group, pConfig); - // Add the Rate Controller - addControl(m_pRateControl); // Create the BPM Controller m_pBpmControl = new BpmControl(group, pConfig); From a6d7a15974787c3d4100362b50c23318413b8d14 Mon Sep 17 00:00:00 2001 From: Philip Gottschling Date: Fri, 27 Sep 2019 16:44:42 +0200 Subject: [PATCH 3/3] fixed failing test --- src/engine/controls/loopingcontrol.cpp | 15 +++++++++++---- src/engine/controls/loopingcontrol.h | 11 ++++++----- src/engine/controls/ratecontrol.cpp | 7 +++++++ src/engine/controls/ratecontrol.h | 1 + src/engine/enginebuffer.cpp | 16 ++++++++-------- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index f050ed94354..245201df763 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -53,7 +53,7 @@ LoopingControl::LoopingControl(QString group, m_loopSamples.setValue(m_oldLoopSamples); m_currentSample.setValue(0.0); m_pActiveBeatLoop = NULL; - + m_pRateControl = NULL; //Create loop-in, loop-out, loop-exit, and reloop/exit ControlObjects m_pLoopInButton = new ControlPushButton(ConfigKey(group, "loop_in")); connect(m_pLoopInButton, &ControlObject::valueChanged, @@ -119,7 +119,6 @@ LoopingControl::LoopingControl(QString group, m_pClosestBeat = ControlObject::getControl(ConfigKey(group, "beat_closest")); m_pTrackSamples = ControlObject::getControl(ConfigKey(group, "track_samples")); m_pSlipEnabled = ControlObject::getControl(ConfigKey(group, "slip_enabled")); - m_pCOReverse = ControlObject::getControl(group, "reverse"); // DEPRECATED: Use beatloop_size and beatloop_set instead. // Activates a beatloop of a specified number of beats. @@ -683,6 +682,10 @@ void LoopingControl::setLoopOutToCurrentPosition() { m_loopSamples.setValue(loopSamples); } +void LoopingControl::setRateControl(RateControl* rateControl) { + m_pRateControl = rateControl; +} + void LoopingControl::slotLoopOut(double pressed) { if (m_pTrack == nullptr) { return; @@ -1086,7 +1089,7 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable // find the previous beat fraction and check if the current position is closer to this or the next one // place the new loop start to the closer one - double previousFractionBeat = prevBeat + floor(samplesSinceLastBeat / beatLength) * beatLength; + double previousFractionBeat = prevBeat + floor(samplesSinceLastBeat / loopLength) * loopLength; double samplesSinceLastFractionBeat = currentSample - previousFractionBeat; if (samplesSinceLastFractionBeat <= (loopLength / 2.0)) { @@ -1095,9 +1098,13 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable newloopSamples.start = previousFractionBeat + loopLength; } } + // If running reverse, move the loop one loop size to the left. // Thus, the loops end will be closest to the current position - bool reverse = m_pCOReverse->toBool(); + bool reverse = false; + if (m_pRateControl != NULL) { + reverse = m_pRateControl->isReverseButtonPressed(); + } if (reverse) { newloopSamples.start -= loopLength; } diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h index 26a8e134adf..6be5fdb78b0 100644 --- a/src/engine/controls/loopingcontrol.h +++ b/src/engine/controls/loopingcontrol.h @@ -8,11 +8,12 @@ #include #include -#include "preferences/usersettings.h" +#include "control/controlvalue.h" #include "engine/controls/enginecontrol.h" -#include "track/track.h" +#include "engine/controls/ratecontrol.h" +#include "preferences/usersettings.h" #include "track/beats.h" -#include "control/controlvalue.h" +#include "track/track.h" #define MINIMUM_AUDIBLE_LOOP_SIZE 300 // In samples @@ -50,7 +51,7 @@ class LoopingControl : public EngineControl { double getSyncPositionInsideLoop(double dRequestedPlaypos, double dSyncedPlayPos); void notifySeek(double dNewPlaypos) override; - + void setRateControl(RateControl* rateControl); bool isLoopingEnabled(); public slots: @@ -129,7 +130,7 @@ class LoopingControl : public EngineControl { ControlPushButton* m_pLoopHalveButton; ControlPushButton* m_pLoopDoubleButton; ControlObject* m_pSlipEnabled; - ControlObject* m_pCOReverse; + RateControl* m_pRateControl; ControlObject* m_pPlayButton; bool m_bLoopingEnabled; diff --git a/src/engine/controls/ratecontrol.cpp b/src/engine/controls/ratecontrol.cpp index 75c7327b10c..5ee5af58ffe 100644 --- a/src/engine/controls/ratecontrol.cpp +++ b/src/engine/controls/ratecontrol.cpp @@ -556,3 +556,10 @@ void RateControl::resetRateTemp(void) void RateControl::notifySeek(double playPos) { m_pScratchController->notifySeek(playPos); } + +bool RateControl::isReverseButtonPressed() { + if (m_pReverseButton) { + return m_pReverseButton->toBool(); + } + return false; +} diff --git a/src/engine/controls/ratecontrol.h b/src/engine/controls/ratecontrol.h index 635ae15b2e3..a53dfd383f1 100644 --- a/src/engine/controls/ratecontrol.h +++ b/src/engine/controls/ratecontrol.h @@ -78,6 +78,7 @@ class RateControl : public EngineControl { static void setRateRampSensitivity(int); static int getRateRampSensitivity(); void notifySeek(double dNewPlaypos) override; + bool isReverseButtonPressed(); public slots: void slotReverseRollActivate(double); diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index cdb92315916..5ba47c24a5c 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -180,19 +180,13 @@ EngineBuffer::EngineBuffer(const QString& group, UserSettingsPointer pConfig, // quantization (alignment) of loop in/out positions and (hot)cues with // beats. QuantizeControl* quantize_control = new QuantizeControl(group, pConfig); - - // Rate Controller must be created before Looping Controller for the Reverse Button to be created - m_pRateControl = new RateControl(group, pConfig); - // Add the Rate Controller - addControl(m_pRateControl); + addControl(quantize_control); + m_pQuantize = ControlObject::getControl(ConfigKey(group, "quantize")); // Create the Loop Controller m_pLoopingControl = new LoopingControl(group, pConfig); addControl(m_pLoopingControl); - addControl(quantize_control); - m_pQuantize = ControlObject::getControl(ConfigKey(group, "quantize")); - m_pEngineSync = pMixingEngine->getEngineSync(); m_pSyncControl = new SyncControl(group, pConfig, pChannel, m_pEngineSync); @@ -203,6 +197,12 @@ EngineBuffer::EngineBuffer(const QString& group, UserSettingsPointer pConfig, addControl(m_pVinylControlControl); #endif + // Create the Rate Controller + m_pRateControl = new RateControl(group, pConfig); + // Add the Rate Controller + addControl(m_pRateControl); + // Looping Control needs Rate Control for Reverse Button + m_pLoopingControl->setRateControl(m_pRateControl); // Create the BPM Controller m_pBpmControl = new BpmControl(group, pConfig);