Skip to content

Commit

Permalink
feat: add engine.softTakeoverIsIgnoring Javascript API
Browse files Browse the repository at this point in the history
This is for mappings to determine whether setting a value would
result in actual changes to it or get it ignored because of
SoftTakeover and provide visual (LED) feedback for it.
  • Loading branch information
Swiftb0y committed Aug 12, 2024
1 parent cdbaf37 commit b531f61
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,17 @@ void ControllerScriptInterfaceLegacy::softTakeoverIgnoreNextValue(
m_st.ignoreNext(pControl);
}

bool ControllerScriptInterfaceLegacy::softTakeoverWillIgnore(
const QString& group, const QString& name, double parameter) {
ControlObject* pControl = ControlObject::getControl(
ConfigKey(group, name));
if (!pControl) {
return false;
}

return m_st.willIgnore(pControl, parameter);
}

double ControllerScriptInterfaceLegacy::getDeckRate(const QString& group) {
double rate = 0.0;
ControlObjectScript* pRateRatio =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class ControllerScriptInterfaceLegacy : public QObject {
Q_INVOKABLE bool isScratching(int deck);
Q_INVOKABLE void softTakeover(const QString& group, const QString& name, bool set);
Q_INVOKABLE void softTakeoverIgnoreNextValue(const QString& group, const QString& name);
Q_INVOKABLE bool softTakeoverWillIgnore(
const QString& group, const QString& name, double parameter);
Q_INVOKABLE void brake(const int deck,
bool activate,
double factor = 1.0,
Expand Down
46 changes: 30 additions & 16 deletions src/controllers/softtakeover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ void SoftTakeoverCtrl::enable(gsl::not_null<ControlPotmeter*> pControl) {
m_softTakeoverHash.try_emplace(static_cast<ControlObject*>(pControl.get()));
}

bool SoftTakeover::ignore(const ControlObject& control, double newParameter) {
bool ignore = false;
bool SoftTakeover::willIgnore(const ControlObject& control,
double newParameter,
ClockT::time_point currentTime) const {
/*
* We only want to ignore the controller when:
* - its new value is far away from the current value of the ControlObject
Expand All @@ -38,27 +39,40 @@ bool SoftTakeover::ignore(const ControlObject& control, double newParameter) {
* Don't ignore in every other case.
*/

if (m_time == kFirstValueTime) {
return true;
}
// don't ignore value if a previous one was not ignored in time
if (currentTime < m_time + kSubsequentValueOverrideTime) {
return false;
}
const double currentParameter = control.getParameter();
const double difference = currentParameter - newParameter;
const double prevDiff = currentParameter - m_prevParameter;
// Don't ignore if opposite side of the current parameter value
if (std::signbit(prevDiff) != std::signbit(difference)) {
return false;
}
// On same side of the current parameter value
if (fabs(difference) <= m_dThreshold || fabs(prevDiff) <= m_dThreshold) {
// differences are below threshold
return false;
}
return true;
}

bool SoftTakeover::ignore(const ControlObject& control, double newParameter) {
bool ignore = false;

auto currentTime = ClockT::now();
// We will get a sudden jump if we don't ignore the first value.
if (m_time == kFirstValueTime) {
ignore = true;
// Change the stored time (but keep it far away from the current time)
// so this block doesn't run again.
m_time = ClockT::time_point(kFirstValueTime + 1ms);
} else if (currentTime >= m_time + kSubsequentValueOverrideTime) {
// don't ignore value if a previous one was not ignored in time
const double currentParameter = control.getParameter();
const double difference = currentParameter - newParameter;
const double prevDiff = currentParameter - m_prevParameter;
if (std::signbit(prevDiff) == std::signbit(difference)) {
// On same side of the current parameter value
if (fabs(difference) > m_dThreshold && fabs(prevDiff) > m_dThreshold) {
// differences are above threshold
ignore = true;
// qDebug() << "SoftTakeover::ignore: ignoring, not near"
// << newParameter << m_prevParameter << currentParameter;
}
}
} else {
ignore = willIgnore(control, newParameter, currentTime);
}
if (!ignore) {
// Update the time only if the value is not ignored. Replaces any
Expand Down
23 changes: 19 additions & 4 deletions src/controllers/softtakeover.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ControlPotmeter;

class SoftTakeover {
public:
using ClockT = mixxx::Time;
// 3/128 units away from the current is enough to catch fast non-sequential moves
// but not cause an audibly noticeable jump, determined experimentally with
// slow-refresh controllers.
Expand All @@ -20,6 +21,9 @@ class SoftTakeover {

SoftTakeover() = default;
bool ignore(const ControlObject& control, double newParameter);
bool willIgnore(const ControlObject& control,
double newParameter,
ClockT::time_point currentTime) const;
void ignoreNext() {
m_time = kFirstValueTime;
}
Expand All @@ -30,11 +34,11 @@ class SoftTakeover {
// TODO (XXX): find a better testing solution than this TestAccess
// front-door coupled to `mixxx::Time`.
struct TestAccess {
static constexpr mixxx::Time::duration getTimeThreshold() {
static constexpr ClockT::duration getTimeThreshold() {
return kSubsequentValueOverrideTime;
}
template<class Rep = mixxx::Time::rep,
class Period = mixxx::Time::period>
template<class Rep = ClockT::rep,
class Period = ClockT::period>
static void advanceTimePastThreshold(
std::chrono::duration<Rep, Period> offset =
std::chrono::nanoseconds(0)) {
Expand All @@ -43,7 +47,6 @@ class SoftTakeover {
};

private:
using ClockT = mixxx::Time;
// If a new value is received within this amount of time, jump to it
// regardless. This allows quickly whipping controls to work while retaining
// the benefits of soft-takeover for slower movements. Setting this too
Expand Down Expand Up @@ -79,6 +82,18 @@ class SoftTakeoverCtrl {
auto& [coKey, refSoftTakeover] = *it;
return refSoftTakeover.ignore(*pControl, newParameter);
}
bool willIgnore(ControlObject* pControl, double newParameter) {
auto it = m_softTakeoverHash.find(pControl);
if (it == m_softTakeoverHash.end()) {
return false;
}
VERIFY_OR_DEBUG_ASSERT(pControl) {
return false;
}
auto currentTime = SoftTakeover::ClockT::now();
auto& [coKey, refSoftTakeover] = *it;
return refSoftTakeover.willIgnore(*pControl, newParameter, currentTime);
}
// Ignore the next supplied parameter
void ignoreNext(ControlObject* pControl) {
auto it = m_softTakeoverHash.find(pControl);
Expand Down
11 changes: 11 additions & 0 deletions src/test/softtakeover_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,17 @@ TEST_F(SoftTakeoverTest, CatchOutOfBounds) {
EXPECT_FALSE(st_control.ignore(&co, co.getParameterForValue(250)));
}

TEST_F(SoftTakeoverTestWithValue, willIgnore) {
// First is always ignored.
EXPECT_TRUE(st_control.ignore(&co, co.getParameterForValue(120)));
SoftTakeover::TestAccess::advanceTimePastThreshold();
EXPECT_TRUE(st_control.willIgnore(&co, co.getParameterForValue(80)));
// do not ignore a value in range;
EXPECT_FALSE(st_control.willIgnore(&co, co.getParameterForValue(51)));
// but still ignore a value outside range because willIgnore just tests
EXPECT_TRUE(st_control.ignore(&co, co.getParameterForValue(80)));
}

// For the ignore cases, check that they work correctly with various signed values
// TODO

Expand Down

0 comments on commit b531f61

Please sign in to comment.