Skip to content

Commit

Permalink
PositionScratchCon: fix explicit seeking while scratching, eg. jump t…
Browse files Browse the repository at this point in the history
…o hotcue

adopt the seek position explicitly in order to avoid crazy seek speeds
  • Loading branch information
ronso0 committed Dec 30, 2024
1 parent 94192da commit 99d15da
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 24 deletions.
8 changes: 6 additions & 2 deletions src/controllers/keyboard/keyboardeventfilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,15 @@ QKeySequence KeyboardEventFilter::getKeySeq(QKeyEvent* e) {
const QKeySequence k = QKeySequence(modseq + keyseq);

if (CmdlineArgs::Instance().getDeveloper()) {
qDebug() << " .";
qDebug() << " .";
if (e->type() == QEvent::KeyPress) {
qDebug() << "keyboard press: " << k.toString();
qDebug() << " keyboard press: " << k.toString();
} else if (e->type() == QEvent::KeyRelease) {
qDebug() << "keyboard release: " << k.toString();
qDebug() << " keyboard release: " << k.toString();
}
qDebug() << " .";
qDebug() << " .";
}

return k;
Expand Down
7 changes: 7 additions & 0 deletions src/engine/enginebuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,13 @@ void EngineBuffer::setNewPlaypos(mixxx::audio::FramePos position) {

// Must hold the engineLock while using m_engineControls
for (const auto& pControl : std::as_const(m_engineControls)) {
if (pControl == m_pRateControl) {
// qWarning() << "---------------------------------";
// qWarning() << "--------------------EB setNewPlaypos, notify all "
// "engine controls"
// << m_playPos;
// qWarning() << "---------------------------------";
}
pControl->notifySeek(m_playPos);
}

Expand Down
108 changes: 101 additions & 7 deletions src/engine/positionscratchcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ constexpr double kThrowThreshold = 2.5;
// Max velocity we would like to stop in a given time period.
constexpr double kMaxVelocity = 100;
// Seconds to stop a throw at the max velocity.
// TODO make configurable, eg. to customize spinbacks with controllers
constexpr double kTimeToStop = 1.0;
constexpr double kSeekPosTolerance = 50;

Check failure on line 81 in src/engine/positionscratchcontroller.cpp

View workflow job for this annotation

GitHub Actions / clazy

unused variable 'kSeekPosTolerance' [-Werror,-Wunused-const-variable]

Check failure on line 81 in src/engine/positionscratchcontroller.cpp

View workflow job for this annotation

GitHub Actions / clang-tidy

unused variable 'kSeekPosTolerance' [clang-diagnostic-unused-const-variable]

Check failure on line 81 in src/engine/positionscratchcontroller.cpp

View workflow job for this annotation

GitHub Actions / macOS 13 x64

unused variable 'kSeekPosTolerance' [-Werror,-Wunused-const-variable]

Check failure on line 81 in src/engine/positionscratchcontroller.cpp

View workflow job for this annotation

GitHub Actions / macOS 13 arm64

unused variable 'kSeekPosTolerance' [-Werror,-Wunused-const-variable]
constexpr int kMaxSeekWaitCount = 1;

} // anonymous namespace

Expand All @@ -93,6 +96,7 @@ PositionScratchController::PositionScratchController(const QString& group)
m_isScratching(false),
m_inertiaEnabled(false),
m_prevSamplePos(0),
m_seekSamplePos(std::numeric_limits<double>::quiet_NaN()),
m_samplePosDeltaSum(0),
m_scratchTargetDelta(0),
m_scratchStartPos(0),
Expand All @@ -104,7 +108,8 @@ PositionScratchController::PositionScratchController(const QString& group)
m_callsPerDt(1),
m_p(1),
m_d(1),
m_f(0.4) {
m_f(0.4),
m_seekWaitCounter(-1) {
m_pMainSampleRate->connectValueChanged(this,
&PositionScratchController::slotSampleRateChanged);
}
Expand Down Expand Up @@ -152,6 +157,38 @@ void PositionScratchController::process(double currentSamplePos,
return;
}

double queuedSeekPos = m_seekSamplePos;
bool adoptSeekPos = false;
if (!util_isnan(queuedSeekPos)) {
if (m_seekWaitCounter >= 0 &&
m_seekWaitCounter <= kMaxSeekWaitCount) { //&&
// fabs(currentSamplePos - queuedSeekPos) < kSeekPosTolerance) {
double posDiff = queuedSeekPos - currentSamplePos;
const QString filler = QString(posDiff >= 0 ? " " : "");
qWarning() << "-------------------------------------------------------- "
"match queued seek pos after" << m_seekWaitCounter << "calls "
"at" << qSetRealNumberPrecision(18) << currentSamplePos;
qWarning().noquote()
<< "--------------------------------------------------------"
" "
<< "diff:" << filler << qSetRealNumberPrecision(18) << posDiff;
resetSeekPos();
adoptSeekPos = true;

// Additionally:
// when seeking while scratching we may get new position updates
// after seeking. Try to ignore those until has stopped?
} else if (m_seekWaitCounter >= 0 && m_seekWaitCounter < kMaxSeekWaitCount) {
qWarning() << "-------------------------------------------------------- "
"seek queued" << m_seekWaitCounter << "calls ago";
m_seekWaitCounter++;
} else {
qWarning() << "-------------------------------------------------------- "
"reset queued seek after" << m_seekWaitCounter << "calls";
resetSeekPos();
}
}

if (bufferSize != m_bufferSize) {
m_bufferSize = bufferSize;
slotSampleRateChanged(m_pMainSampleRate->get());
Expand Down Expand Up @@ -202,7 +239,11 @@ void PositionScratchController::process(double currentSamplePos,
m_inertiaEnabled = false;

double sampleDelta = 0.0;
// Seeks occur if we wrap around due to loop/repeat and when we've
// seeked explicitly, for example when jumping to cue/hotcue.
// We consider it only in the explicit case.
if (wrappedAround > 0) {
// qWarning() << "XXX----wrappedAround" << wrappedAround;
// If we wrapped around calculate the virtual position like if
// we are not looping, i.e. sum up diffs from loop start/end and
// loop length for each wrap-aound (necessary if the buffer is
Expand All @@ -221,13 +262,26 @@ void PositionScratchController::process(double currentSamplePos,
(triggerPos - m_prevSamplePos) +
(currentSamplePos - targetPos);
} else {
// Check if we need to adopt a new position after seek.
if (adoptSeekPos) {
m_prevSamplePos = queuedSeekPos;
// qWarning() << " ----------------------adopt seek pos";
}
sampleDelta = currentSamplePos - m_prevSamplePos;
if (adoptSeekPos) {
qWarning() << "-------------------------------------------------------- "
" sampleDelta:" << sampleDelta;
}
}

// Measure the total distance traveled since last call, add it to the
// running total and normalize to one buffer.
// This is required to scratch within loop boundaries.
m_samplePosDeltaSum += (sampleDelta) / (bufferSize * baseSampleRate);
if (adoptSeekPos) {
qWarning() << "-------------------------------------------------------- "
"m_samplePosDeltaSum:" << m_samplePosDeltaSum;
}

// If we may have a new position, calculate scratch parameters and
// eventually the new rate.
Expand All @@ -237,13 +291,21 @@ void PositionScratchController::process(double currentSamplePos,
// and normalize to one buffer
double scratchTargetDelta = (scratchPosition - m_scratchStartPos) /
(bufferSize * baseSampleRate);
if (adoptSeekPos) {
qWarning() << "-------------------------------------------------------- "
"scratchTargetDelta: " << scratchTargetDelta;
}

bool calcRate = true;

if (m_scratchTargetDelta == scratchTargetDelta) {
// We get here if the next mouse position is delayed, the mouse
// is stopped or moves very slowly. Since we don't know the case
// we assume delayed mouse updates for 40 ms.
if (adoptSeekPos) {
qWarning() << "-------------------------------------------------------- "
"scratchTargetDelta == m_scratchTargetDelta";
}
// We get here if the next mouse position is delayed, the
// mouse is stopped or moves very slowly. Since we don't
// know the case we assume delayed mouse updates for 40 ms.
// TODO Make threshold configurable for controller use?
m_moveDelay += m_dt * m_callsPerDt;
if (m_moveDelay < kMoveDelayMax) {
Expand All @@ -263,6 +325,12 @@ void PositionScratchController::process(double currentSamplePos,
}
}
} else {
if (adoptSeekPos) {
qWarning() << "-------------------------------------------------------- "
"scratchTargetDelta != m_scratchTargetDelta";
qWarning() << "-------------------------------------------------------- "
"m_moveDelay = 0, adopt scratchTargetDelta";
}
m_moveDelay = 0;
m_scratchTargetDelta = scratchTargetDelta;
}
Expand All @@ -272,12 +340,14 @@ void PositionScratchController::process(double currentSamplePos,
scratchTargetDelta - m_samplePosDeltaSum);
m_rate = m_pVelocityController->observation(ctrlError);
m_rate /= ceil(kDefaultSampleInterval / m_dt);
// ??
// Note: The following SoundTouch changes the also rate by a ramp
// This looks like average of the new and the old rate independent
// from m_dt. Ramping is disabled when direction changes or rate = 0;
// (determined experimentally)
if (fabs(m_rate) < MIN_SEEK_SPEED) {
// we cannot get closer
// TODO ramp to new rate
m_rate = 0;
}
}
Expand Down Expand Up @@ -310,12 +380,36 @@ void PositionScratchController::process(double currentSamplePos,
m_scratchStartPos = scratchPosition;
// qDebug() << "scratchEnable()" << currentSamplePos;
}

m_prevSamplePos = currentSamplePos;
}

void PositionScratchController::notifySeek(mixxx::audio::FramePos position) {
DEBUG_ASSERT(position.isValid());
// scratching continues after seek due to calculating the relative distance traveled
// in m_samplePosDeltaSum
m_prevSamplePos = position.toEngineSamplePos();
double newPos = position.toEngineSamplePos();
if (m_prevSamplePos == newPos) {
qWarning() << "---------------------------------------------------- "
"notifySeek: equal --> ignore";
return;
} else if (fabs(m_prevSamplePos - newPos) < 100) {
qWarning() << "---------------------------------------------------- "
"notifySeek: very close --> ignore";
return;
} else if (!isEnabled()) {
qWarning() << "---------------------------------------------------- "
"notifySeek: not scratching --> ignore";
return;
}
// ?? Scratching continues after seek due to calculating the relative
// ?? distance traveled in m_samplePosDeltaSum
// Note: it can happen that RateControl is not yet aware of the seek position,
// hence it's possible that we get the new position only in thesecond next
// process() call.
// That's what the wait counter is for: keep the seek position for next to calls,
// then reset it (we might come across the seek position just by playing or scratching
// and then we must process it regularly.
qWarning() << "---------------------------------------------------- "
"notifySeek: accept";
m_seekSamplePos = newPos;
m_seekWaitCounter = 0;
}
8 changes: 8 additions & 0 deletions src/engine/positionscratchcontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class PositionScratchController : public QObject {
void slotSampleRateChanged(double sampleRate);

private:
void resetSeekPos() {
m_seekSamplePos = std::numeric_limits<double>::quiet_NaN();
m_seekWaitCounter = -1;
}

const QString m_group;
std::unique_ptr<ControlObject> m_pScratchEnable;
std::unique_ptr<ControlObject> m_pScratchPos;
Expand All @@ -47,6 +52,7 @@ class PositionScratchController : public QObject {
bool m_isScratching;
bool m_inertiaEnabled;
double m_prevSamplePos;
double m_seekSamplePos;
double m_samplePosDeltaSum;
double m_scratchTargetDelta;
double m_scratchStartPos;
Expand All @@ -62,4 +68,6 @@ class PositionScratchController : public QObject {
double m_p;
double m_d;
double m_f;

int m_seekWaitCounter;
};
15 changes: 0 additions & 15 deletions src/engine/readaheadmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,22 +283,8 @@ double ReadAheadManager::getFilePlaypositionFromLog(
}

double filePlayposition = 0;
bool shouldNotifySeek = false;
while (m_readAheadLog.size() > 0 && numConsumedSamples > 0) {
ReadLogEntry& entry = m_readAheadLog.front();

// Notify EngineControls that we have taken a seek.
// Every new entry start with a seek
// (Not looping control)
if (shouldNotifySeek) {
if (m_pRateControl) {
const auto seekPosition =
mixxx::audio::FramePos::fromEngineSamplePos(
entry.virtualPlaypositionStart);
m_pRateControl->notifySeek(seekPosition);
}
}

// Advance our idea of the current virtual playposition to this
// ReadLogEntry's start position.
filePlayposition = entry.advancePlayposition(&numConsumedSamples);
Expand All @@ -307,7 +293,6 @@ double ReadAheadManager::getFilePlaypositionFromLog(
// This entry is empty now.
m_readAheadLog.pop_front();
}
shouldNotifySeek = true;
}

return filePlayposition;
Expand Down

0 comments on commit 99d15da

Please sign in to comment.