Skip to content

Commit

Permalink
Merge pull request #2286 from goddisignz/quantized_loop_snap
Browse files Browse the repository at this point in the history
Beatloops snap to the closest (fractional) beat in quantized mode LP1752133
  • Loading branch information
daschuer authored Sep 27, 2019
2 parents 2000704 + a6d7a15 commit 04a1d47
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 19 deletions.
45 changes: 33 additions & 12 deletions src/engine/controls/loopingcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -682,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;
Expand Down Expand Up @@ -1060,16 +1064,19 @@ 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 beatLength = nextBeat - prevBeat;
double loopLength = beatLength * beats;

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):
Expand All @@ -1078,15 +1085,29 @@ 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<int>(floor((beat_pos / beat_len) *
loops_per_beat));
newloopSamples.start = prevBeat + beat_len / loops_per_beat * beat_frac;
double samplesSinceLastBeat = currentSample - prevBeat;

// 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 / loopLength) * loopLength;
double samplesSinceLastFractionBeat = currentSample - previousFractionBeat;

if (samplesSinceLastFractionBeat <= (loopLength / 2.0)) {
newloopSamples.start = previousFractionBeat;
} else {
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 = false;
if (m_pRateControl != NULL) {
reverse = m_pRateControl->isReverseButtonPressed();
}
if (reverse) {
newloopSamples.start -= loopLength;
}
} else {
newloopSamples.start = currentSample;
}
Expand Down
10 changes: 6 additions & 4 deletions src/engine/controls/loopingcontrol.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
#include <QObject>
#include <QStack>

#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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -129,6 +130,7 @@ class LoopingControl : public EngineControl {
ControlPushButton* m_pLoopHalveButton;
ControlPushButton* m_pLoopDoubleButton;
ControlObject* m_pSlipEnabled;
RateControl* m_pRateControl;
ControlObject* m_pPlayButton;

bool m_bLoopingEnabled;
Expand Down
7 changes: 7 additions & 0 deletions src/engine/controls/ratecontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
1 change: 1 addition & 0 deletions src/engine/controls/ratecontrol.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 9 additions & 3 deletions src/engine/enginebuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +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);
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);
Expand All @@ -198,9 +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);
Expand Down Expand Up @@ -532,6 +534,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()";
Expand Down
2 changes: 2 additions & 0 deletions src/engine/enginebuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ class EngineBuffer : public EngineObject {
bool getQueuedSeekPosition(double* pSeekPosition);
TrackPointer getLoadedTrack() const;

bool isReverse();

double getExactPlayPos();
double getVisualPlayPos();
double getTrackSamples();
Expand Down

0 comments on commit 04a1d47

Please sign in to comment.