summaryrefslogtreecommitdiffstats
path: root/src/controllers/softtakeover.cpp
blob: fcd4e9dee37ceeb3a7e2993a707a09210846b594 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include "controllers/softtakeover.h"
#include "control/controlpotmeter.h"
#include "util/math.h"
#include "util/time.h"

// 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.
const double SoftTakeover::kDefaultTakeoverThreshold = 3.0 / 128;

const mixxx::Duration SoftTakeover::kSubsequentValueOverrideTime =
        mixxx::Duration::fromMillis(50);

SoftTakeoverCtrl::SoftTakeoverCtrl() {

}

SoftTakeoverCtrl::~SoftTakeoverCtrl() {
    QHashIterator<ControlObject*, SoftTakeover*> i(m_softTakeoverHash);
    while (i.hasNext()) {
        i.next();
        delete i.value();
    }
}

void SoftTakeoverCtrl::enable(ControlObject* control) {
    ControlPotmeter* cpo = qobject_cast<ControlPotmeter*>(control);
    if (cpo == nullptr) {
        // softtakecover works only for continuous ControlPotmeter based COs
        return;
    }

    // Initialize times
    if (!m_softTakeoverHash.contains(control)) {
        m_softTakeoverHash.insert(control, new SoftTakeover());
    }
}

void SoftTakeoverCtrl::disable(ControlObject* control) {
    if (control == nullptr) {
        return;
    }
    SoftTakeover* pSt = m_softTakeoverHash.take(control);
    if (pSt) {
        delete pSt;
    }
}

bool SoftTakeoverCtrl::ignore(ControlObject* control, double newParameter) {
    if (control == nullptr) {
        return false;
    }
    bool ignore = false;
    SoftTakeover* pSt = m_softTakeoverHash.value(control);
    if (pSt) {
        ignore = pSt->ignore(control, newParameter);
    }
    return ignore;
}

void SoftTakeoverCtrl::ignoreNext(ControlObject* control) {
    if (control == nullptr) {
        return;
    }

    SoftTakeover* pSt = m_softTakeoverHash.value(control);
    if (pSt == nullptr) {
        return;
    }

    pSt->ignoreNext();
}

SoftTakeover::SoftTakeover()
    : m_prevParameter(0),
      m_dThreshold(kDefaultTakeoverThreshold) {
}

void SoftTakeover::setThreshold(double threshold) {
    m_dThreshold = threshold;
}

bool SoftTakeover::ignore(ControlObject* control, double newParameter) {
    bool ignore = false;
    /*
     * We only want to ignore the controller when:
     * - its new value is far away from the current value of the ControlObject
     * AND either of the following:
     *  - its new and previous values are on the opposite side of the current
     *      value of the ControlObject AND the new one arrives awhile after the
     *      previous one (regardless of what the previous value was)
     *  - new and previous values are on the same side of the current value of
     *      the ControlObject AND either:
     *      - the previous value is (also) far from the current CO value
     *          (regardless of the new value's arrival time)
     *      - the new value arrives awhile after the previous one
     *          (regardless of what the previous value was)
     *
     * Sheesh, this is much easier to show in a truth table!
     *
     * Sides    prev distance   new distance    new value arrives   Ignore
     * opposite close           far             later               TRUE
     * opposite far             far             later               TRUE
     * same     close           far             later               TRUE
     * same     far             far             soon                TRUE
     * same     far             far             later               TRUE
     *
     *      Don't ignore in every other case.
     */

    mixxx::Duration currentTime = mixxx::Time::elapsed();
    // We will get a sudden jump if we don't ignore the first value.
    if (m_time == mixxx::Duration::fromMillis(0)) {
        ignore = true;
        // Change the stored time (but keep it far away from the current time)
        //  so this block doesn't run again.
        m_time = mixxx::Duration::fromMillis(1);
//         qDebug() << "SoftTakeover::ignore: ignoring the first value"
//                  << newParameter;
    } 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 ((prevDiff < 0 && difference < 0) ||
                (prevDiff > 0 && difference > 0)) {
            // 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;
            }
        }
    }
    if (!ignore) {
        // Update the time only if the value is not ignored. Replaces any
        // previous value for this control
        m_time = currentTime;
    }
    // Update the previous value every time
    m_prevParameter = newParameter;

    return ignore;
}

void SoftTakeover::ignoreNext() {
    m_time = mixxx::Duration::fromMillis(0);
}