summaryrefslogtreecommitdiffstats
path: root/src/track/bpm.h
blob: 93800ce2b2eeb5dd3edada83c2517be24355a407 (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
187
188
189
190
191
192
193
194
195
196
197
198
#pragma once

#include <QtDebug>

#include "util/fpclassify.h"
#include "util/math.h"

namespace mixxx {

// DTO for storing BPM information.
class Bpm final {
public:
    static constexpr double kValueUndefined = 0.0;
    static constexpr double kValueMin = 0.0; // lower bound (exclusive)
    static constexpr double kValueMax = 500.0; // higher bound (inclusive)

    constexpr Bpm()
            : Bpm(kValueUndefined) {
    }
    explicit constexpr Bpm(double value)
            : m_value(value) {
    }

    static double normalizeValue(double value);

    static QString displayValueText(double value);

    // Adjusts floating-point values to match their string representation
    // in file tags to account for rounding errors and false positives
    // when checking for modifications.
    // NOTE(2020-01-08, uklotzde): Since bpm values are stored with
    // integer precision in ID3 tags, bpm values are only considered
    // as modified if their rounded integer values differ. But even
    // then this pre-normalization step should not be skipped to prevent
    // fluttering values for other tag formats.
    void normalizeBeforeExport() {
        m_value = normalizeValue(m_value);
    }

    static bool isValidValue(double value) {
        return util_isfinite(value) && kValueMin < value;
    }

    bool isValid() const {
        return isValidValue(m_value);
    }
    double value() const {
        VERIFY_OR_DEBUG_ASSERT(isValid()) {
            return kValueUndefined;
        }
        return m_value;
    }
    void setValue(double value) {
        m_value = value;
    }
    void resetValue() {
        m_value = kValueUndefined;
    }

    static double valueFromString(const QString& str, bool* pValid = nullptr);
    static QString valueToString(double value);
    static int valueToInteger(double value) {
        return static_cast<int>(std::round(value));
    }

    enum class Comparison {
        Default, // full precision
        Integer, // rounded
        String, // stringified
    };

    bool compareEq(
            const Bpm& bpm,
            Comparison cmp = Comparison::Default) const {
        if (!isValid() && !bpm.isValid()) {
            // Both values are invalid and thus equal.
            return true;
        }

        if (isValid() != bpm.isValid()) {
            // One value is valid, one is not.
            return false;
        }

        // At this point both values are valid
        switch (cmp) {
        case Comparison::Integer:
            return Bpm::valueToInteger(value()) == Bpm::valueToInteger(bpm.value());
        case Comparison::String:
            return Bpm::valueToString(value()) == Bpm::valueToString(bpm.value());
        case Comparison::Default:
        default:
            return value() == bpm.value();
        }
    }

    QString displayText() const {
        return displayValueText(m_value);
    }

    Bpm& operator+=(double increment) {
        DEBUG_ASSERT(isValid());
        m_value += increment;
        return *this;
    }

    Bpm& operator-=(double decrement) {
        DEBUG_ASSERT(isValid());
        m_value -= decrement;
        return *this;
    }

    Bpm& operator*=(double multiple) {
        DEBUG_ASSERT(isValid());
        m_value *= multiple;
        return *this;
    }

    Bpm& operator/=(double divisor) {
        DEBUG_ASSERT(isValid());
        m_value /= divisor;
        return *this;
    }

private:
    double m_value;
};

/// Bpm can be added to a double
inline Bpm operator+(Bpm bpm, double bpmDiff) {
    return Bpm(bpm.value() + bpmDiff);
}

/// Bpm can be subtracted from a double
inline Bpm operator-(Bpm bpm, double bpmDiff) {
    return Bpm(bpm.value() - bpmDiff);
}

/// Two Bpm values can be subtracted to get a double
inline double operator-(Bpm bpm1, Bpm bpm2) {
    return bpm1.value() - bpm2.value();
}

// Adding two Bpm is not allowed, because it makes no sense semantically.

/// Bpm can be multiplied or divided by a double
inline Bpm operator*(Bpm bpm, double multiple) {
    return Bpm(bpm.value() * multiple);
}

inline Bpm operator/(Bpm bpm, double divisor) {
    return Bpm(bpm.value() / divisor);
}

/// Bpm can be divided by another Bpm to get a ratio (represented as a double).
inline double operator/(Bpm bpm, Bpm otherBpm) {
    return bpm.value() / otherBpm.value();
}

inline bool operator==(Bpm bpm1, Bpm bpm2) {
    if (!bpm1.isValid() && !bpm2.isValid()) {
        return true;
    }
    return bpm1.isValid() && bpm2.isValid() && bpm1.value() == bpm2.value();
}

inline bool operator!=(Bpm bpm1, Bpm bpm2) {
    return !(bpm1 == bpm2);
}

inline bool operator<(Bpm bpm1, Bpm bpm2) {
    return bpm1.value() < bpm2.value();
}

inline bool operator<=(Bpm bpm1, Bpm bpm2) {
    return (bpm1 == bpm2) || bpm1 < bpm2;
}

inline bool operator>(Bpm bpm1, Bpm bpm2) {
    return bpm2 < bpm1;
}

inline bool operator>=(Bpm bpm1, Bpm bpm2) {
    return bpm2 <= bpm1;
}

inline QDebug operator<<(QDebug dbg, Bpm arg) {
    if (arg.isValid()) {
        dbg.nospace() << "Bpm(" << arg.value() << ")";
    } else {
        dbg << "Bpm(Invalid)";
    }
    return dbg;
}
}

Q_DECLARE_TYPEINFO(mixxx::Bpm, Q_MOVABLE_TYPE);
Q_DECLARE_METATYPE(mixxx::Bpm)