summaryrefslogtreecommitdiffstats
path: root/src/engine/controls/clockcontrol.cpp
blob: 66226856891cb22c1a1616ccb2fb44b653a6d386 (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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#include "engine/controls/clockcontrol.h"

#include "control/controlobject.h"
#include "control/controlproxy.h"
#include "engine/controls/enginecontrol.h"
#include "moc_clockcontrol.cpp"
#include "preferences/usersettings.h"
#include "track/track.h"

ClockControl::ClockControl(const QString& group, UserSettingsPointer pConfig)
        : EngineControl(group, pConfig) {
    m_pCOBeatActive = new ControlObject(ConfigKey(group, "beat_active"));
    m_pCOBeatActive->setReadOnly();
    m_pCOBeatActive->forceSet(0.0);
    m_lastEvaluatedSample = 0;
    m_PrevBeatSamples = 0;
    m_InternalState = StateMachine::outsideIndicationArea;
    m_NextBeatSamples = 0;
    m_blinkIntervalSamples = 0;
    m_pLoopEnabled = new ControlProxy(group, "loop_enabled", this);
    m_pLoopStartPosition = new ControlProxy(group, "loop_start_position", this);
    m_pLoopEndPosition = new ControlProxy(group, "loop_end_position", this);
}

ClockControl::~ClockControl() {
    delete m_pCOBeatActive;
}

// called from an engine worker thread
void ClockControl::trackLoaded(TrackPointer pNewTrack) {
    mixxx::BeatsPointer pBeats;
    if (pNewTrack) {
        pBeats = pNewTrack->getBeats();
    }
    trackBeatsUpdated(pBeats);
}

void ClockControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) {
    // Clear on-beat control
    m_pCOBeatActive->forceSet(0.0);
    m_pBeats = pBeats;
}

void ClockControl::process(const double dRate,
        const double currentSample,
        const int iBuffersize) {
    Q_UNUSED(dRate);
    Q_UNUSED(currentSample);
    Q_UNUSED(iBuffersize);
}

void ClockControl::updateIndicators(const double dRate,
        const double currentSample,
        const double sampleRate) {
    /* This method sets the control beat_active is set to the following values:
    * +1.0 --> Forward playing, set at the beat and set back to 0.0 at 20% of beat distance
    *  0.0 --> No beat indication (ouside 20% area or play direction changed while indication was on)
    * -1.0 --> Reverse playing, set at the beat and set back to 0.0 at -20% of beat distance
    */

    // TODO(XXX) should this be customizable, or latency dependent?
    const double kBlinkInterval = 0.20; // LED is on 20% of the beat period
    const double kStandStillTolerance =
            0.0025; // (seconds) Minimum change, to he last evaluated position

    if (((currentSample <= (m_lastEvaluatedSample + kStandStillTolerance * sampleRate)) &&
                (currentSample >= (m_lastEvaluatedSample - kStandStillTolerance * sampleRate))) ||
            (dRate == 0.0)) {
        return; // No position change since last indicator update (e.g. deck stopped) -> No indicator update needed
    }

    double prevIndicatorSamples;
    double nextIndicatorSamples;

    mixxx::BeatsPointer pBeats = m_pBeats;
    if (pBeats) {
        if ((currentSample >= m_NextBeatSamples) ||
                (currentSample <= m_PrevBeatSamples)) {
            pBeats->findPrevNextBeats(currentSample,
                    &m_PrevBeatSamples,
                    &m_NextBeatSamples,
                    true); // Precise compare without tolerance needed
        }
    } else {
        m_PrevBeatSamples = -1;
        m_NextBeatSamples = -1;
    }

    // Loops need special handling
    if (m_pLoopEnabled->toBool()) {
        const double loop_start_position = m_pLoopStartPosition->get();
        const double loop_end_position = m_pLoopEndPosition->get();

        if ((m_PrevBeatSamples < loop_start_position) && (m_NextBeatSamples >= loop_end_position)) {
            // No beat inside loop -> show beat indication at loop_start_position
            prevIndicatorSamples = loop_start_position;
            nextIndicatorSamples = loop_end_position;
        } else {
            prevIndicatorSamples = m_PrevBeatSamples;
            nextIndicatorSamples = m_NextBeatSamples;
        }

        if ((m_PrevBeatSamples != -1) && (m_NextBeatSamples != -1)) {
            // Don't overwrite interval at begin/end of track
            if ((loop_end_position - loop_start_position) <
                    (m_NextBeatSamples - m_PrevBeatSamples)) {
                // Loops smaller than beat distance -> Set m_blinkIntervalSamples based on loop period
                m_blinkIntervalSamples =
                        (loop_end_position - loop_start_position) *
                        kBlinkInterval;
            } else {
                m_blinkIntervalSamples =
                        (nextIndicatorSamples - prevIndicatorSamples) *
                        kBlinkInterval;
            }
        }
    } else {
        prevIndicatorSamples = m_PrevBeatSamples;
        nextIndicatorSamples = m_NextBeatSamples;

        if ((prevIndicatorSamples != -1) && (nextIndicatorSamples != -1)) {
            // Don't overwrite interval at begin/end of track
            m_blinkIntervalSamples =
                    (nextIndicatorSamples - prevIndicatorSamples) *
                    kBlinkInterval;
        }
    }

    // The m_InternalState needs to be taken into account, to show a reliable beat indication for loops
    if (dRate > 0.0) {
        if (m_lastPlayDirection == true) {
            if ((currentSample > prevIndicatorSamples) &&
                    (currentSample <
                            prevIndicatorSamples + m_blinkIntervalSamples) &&
                    (m_InternalState != StateMachine::afterBeatActive) &&
                    (m_InternalState != StateMachine::afterBeatDirectionChanged)) {
                m_InternalState = StateMachine::afterBeatActive;
                m_pCOBeatActive->forceSet(1.0);
            } else if ((currentSample > prevIndicatorSamples +
                                       m_blinkIntervalSamples) &&
                    ((m_InternalState == StateMachine::afterBeatActive) ||
                            (m_InternalState == StateMachine::afterBeatDirectionChanged))) {
                m_InternalState = StateMachine::outsideIndicationArea;
                m_pCOBeatActive->forceSet(0.0);
            }
        } else {
            // Play direction changed while beat indicator was on and forward playing
            if ((currentSample < nextIndicatorSamples) &&
                    (currentSample >=
                            nextIndicatorSamples - m_blinkIntervalSamples) &&
                    (m_InternalState != StateMachine::beforeBeatDirectionChanged)) {
                m_InternalState = StateMachine::beforeBeatDirectionChanged;
                m_pCOBeatActive->forceSet(0.0);
            }
        }
        m_lastPlayDirection = true; // Forward
    } else if (dRate < 0.0) {
        if (m_lastPlayDirection == false) {
            if ((currentSample < nextIndicatorSamples) &&
                    (currentSample >
                            nextIndicatorSamples - m_blinkIntervalSamples) &&
                    (m_InternalState != StateMachine::beforeBeatActive) &&
                    (m_InternalState != StateMachine::beforeBeatDirectionChanged)) {
                m_InternalState = StateMachine::beforeBeatActive;
                m_pCOBeatActive->forceSet(-1.0);
            } else if ((currentSample < nextIndicatorSamples -
                                       m_blinkIntervalSamples) &&
                    ((m_InternalState == StateMachine::beforeBeatActive) ||
                            (m_InternalState == StateMachine::beforeBeatDirectionChanged))) {
                m_InternalState = StateMachine::outsideIndicationArea;
                m_pCOBeatActive->forceSet(0.0);
            }
        } else {
            // Play direction changed while beat indicator was on and reverse playing
            if ((currentSample > prevIndicatorSamples) &&
                    (currentSample <=
                            prevIndicatorSamples + m_blinkIntervalSamples) &&
                    (m_InternalState != StateMachine::afterBeatDirectionChanged)) {
                m_InternalState = StateMachine::afterBeatDirectionChanged;
                m_pCOBeatActive->forceSet(0.0);
            }
        }
        m_lastPlayDirection = false; // Reverse
    }
    m_lastEvaluatedSample = currentSample;
}