summaryrefslogtreecommitdiffstats
path: root/src/engine/controls/quantizecontrol.cpp
blob: 81c40d2f13a97a890cb737a42634c63bf727d545 (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
#include "engine/controls/quantizecontrol.h"

#include <QtDebug>

#include "control/controlobject.h"
#include "control/controlpushbutton.h"
#include "engine/controls/enginecontrol.h"
#include "moc_quantizecontrol.cpp"
#include "preferences/usersettings.h"
#include "track/track.h"

QuantizeControl::QuantizeControl(const QString& group,
        UserSettingsPointer pConfig)
        : EngineControl(group, pConfig) {
    // Turn quantize OFF by default. See Bug #898213
    m_pCOQuantizeEnabled = new ControlPushButton(ConfigKey(group, "quantize"), true);
    m_pCOQuantizeEnabled->setButtonMode(ControlPushButton::TOGGLE);
    m_pCONextBeat = new ControlObject(ConfigKey(group, "beat_next"));
    m_pCONextBeat->setKbdRepeatable(true);
    m_pCONextBeat->set(mixxx::audio::kInvalidFramePos.toEngineSamplePosMaybeInvalid());
    m_pCOPrevBeat = new ControlObject(ConfigKey(group, "beat_prev"));
    m_pCOPrevBeat->setKbdRepeatable(true);
    m_pCOPrevBeat->set(mixxx::audio::kInvalidFramePos.toEngineSamplePosMaybeInvalid());
    m_pCOClosestBeat = new ControlObject(ConfigKey(group, "beat_closest"));
    m_pCOClosestBeat->set(mixxx::audio::kInvalidFramePos.toEngineSamplePosMaybeInvalid());
}

QuantizeControl::~QuantizeControl() {
    delete m_pCOQuantizeEnabled;
    delete m_pCONextBeat;
    delete m_pCOPrevBeat;
    delete m_pCOClosestBeat;
}

void QuantizeControl::trackLoaded(TrackPointer pNewTrack) {
    if (pNewTrack) {
        m_pBeats = pNewTrack->getBeats();
        // Initialize prev and next beat as if current position was zero.
        // If there is a cue point, the value will be updated.
        lookupBeatPositions(mixxx::audio::kStartFramePos);
        updateClosestBeat(mixxx::audio::kStartFramePos);
    } else {
        m_pBeats.reset();
        m_pCOPrevBeat->set(mixxx::audio::kInvalidFramePos.toEngineSamplePosMaybeInvalid());
        m_pCONextBeat->set(mixxx::audio::kInvalidFramePos.toEngineSamplePosMaybeInvalid());
        m_pCOClosestBeat->set(mixxx::audio::kInvalidFramePos.toEngineSamplePosMaybeInvalid());
    }
}

void QuantizeControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) {
    m_pBeats = pBeats;
    const mixxx::audio::FramePos currentPosition = frameInfo().currentPosition;
    lookupBeatPositions(currentPosition);
    updateClosestBeat(currentPosition);
}

void QuantizeControl::setFrameInfo(mixxx::audio::FramePos currentPosition,
        mixxx::audio::FramePos trackEndPosition,
        mixxx::audio::SampleRate sampleRate) {
    FrameInfo frameInf = frameInfo();
    if (frameInf.currentPosition != currentPosition ||
            frameInf.trackEndPosition != trackEndPosition ||
            frameInf.sampleRate != sampleRate) {
        EngineControl::setFrameInfo(currentPosition, trackEndPosition, sampleRate);
        playPosChanged(currentPosition);
    }
}

void QuantizeControl::playPosChanged(mixxx::audio::FramePos position) {
    DEBUG_ASSERT(position.isValid());
    // We only need to update the prev or next if the current sample is
    // out of range of the existing beat positions or if we've been forced to
    // do so.
    // NOTE: This bypasses the epsilon calculation, but is there a way
    //       that could actually cause a problem?
    const auto prevBeatPosition =
            mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
                    m_pCOPrevBeat->get());
    const auto nextBeatPosition =
            mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
                    m_pCONextBeat->get());
    if (!prevBeatPosition.isValid() || position < prevBeatPosition ||
            !nextBeatPosition.isValid() || position > nextBeatPosition) {
        lookupBeatPositions(position);
    }
    updateClosestBeat(position);
}

void QuantizeControl::lookupBeatPositions(mixxx::audio::FramePos position) {
    DEBUG_ASSERT(position.isValid());
    mixxx::BeatsPointer pBeats = m_pBeats;
    if (pBeats) {
        mixxx::audio::FramePos prevBeatPosition;
        mixxx::audio::FramePos nextBeatPosition;
        pBeats->findPrevNextBeats(position, &prevBeatPosition, &nextBeatPosition, true);
        // FIXME: -1.0 is a valid frame position, should we set the COs to NaN?
        m_pCOPrevBeat->set(prevBeatPosition.toEngineSamplePosMaybeInvalid());
        m_pCONextBeat->set(nextBeatPosition.toEngineSamplePosMaybeInvalid());
    }
}

void QuantizeControl::updateClosestBeat(mixxx::audio::FramePos position) {
    DEBUG_ASSERT(position.isValid());
    if (!m_pBeats) {
        return;
    }
    const auto prevBeatPosition =
            mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
                    m_pCOPrevBeat->get());
    const auto nextBeatPosition =
            mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
                    m_pCONextBeat->get());

    // Calculate closest beat by hand since we want the beat locations themselves
    // and duplicating the work by calling the standard API would double
    // the number of mutex locks.
    if (!prevBeatPosition.isValid()) {
        if (nextBeatPosition.isValid()) {
            m_pCOClosestBeat->set(nextBeatPosition.toEngineSamplePos());
        } else {
            // Likely no beat information -- can't set closest beat value.
        }
    } else if (!nextBeatPosition.isValid()) {
        m_pCOClosestBeat->set(prevBeatPosition.toEngineSamplePos());
    } else {
        const mixxx::audio::FramePos currentClosestBeatPosition =
                (nextBeatPosition - position > position - prevBeatPosition)
                ? prevBeatPosition
                : nextBeatPosition;
        const auto closestBeatPosition =
                mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
                        m_pCOClosestBeat->get());
        if (closestBeatPosition != currentClosestBeatPosition) {
            m_pCOClosestBeat->set(currentClosestBeatPosition.toEngineSamplePos());
        }
    }
}