summaryrefslogtreecommitdiffstats
path: root/src/track/serato/beatsimporter.cpp
blob: 12e8826c7c8eebc9c66c2daa93362cda014ac410 (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
#include "track/serato/beatsimporter.h"

#include "track/serato/tags.h"

namespace mixxx {

SeratoBeatsImporter::SeratoBeatsImporter()
        : BeatsImporter(),
          m_pTerminalMarker(nullptr) {
}

SeratoBeatsImporter::SeratoBeatsImporter(
        const QList<SeratoBeatGridNonTerminalMarkerPointer>& nonTerminalMarkers,
        SeratoBeatGridTerminalMarkerPointer pTerminalMarker)
        : BeatsImporter(),
          m_nonTerminalMarkers(nonTerminalMarkers),
          m_pTerminalMarker(pTerminalMarker) {
    DEBUG_ASSERT(pTerminalMarker);
}

bool SeratoBeatsImporter::isEmpty() const {
    return !m_pTerminalMarker;
};

QVector<mixxx::audio::FramePos> SeratoBeatsImporter::importBeatsAndApplyTimingOffset(
        const QString& filePath, const audio::StreamInfo& streamInfo) {
    const audio::SignalInfo& signalInfo = streamInfo.getSignalInfo();
    const double timingOffsetMillis = SeratoTags::guessTimingOffsetMillis(
            filePath, signalInfo);

    return importBeatsAndApplyTimingOffset(timingOffsetMillis, signalInfo);
}

QVector<mixxx::audio::FramePos> SeratoBeatsImporter::importBeatsAndApplyTimingOffset(
        double timingOffsetMillis, const audio::SignalInfo& signalInfo) {
    VERIFY_OR_DEBUG_ASSERT(!isEmpty()) {
        return {};
    }

    QVector<mixxx::audio::FramePos> beats;
    double beatPositionMillis = 0;
    // Calculate beat positions for non-terminal markers
    for (int i = 0; i < m_nonTerminalMarkers.size(); ++i) {
        SeratoBeatGridNonTerminalMarkerPointer pMarker = m_nonTerminalMarkers.at(i);
        beatPositionMillis = static_cast<double>(pMarker->positionSecs()) * 1000;
        VERIFY_OR_DEBUG_ASSERT(pMarker->positionSecs() >= 0 &&
                pMarker->beatsTillNextMarker() > 0) {
            return {};
        }
        const double nextBeatPositionMillis =
                static_cast<double>(i == (m_nonTerminalMarkers.size() - 1)
                                ? m_pTerminalMarker->positionSecs()
                                : m_nonTerminalMarkers.at(i + 1)
                                          ->positionSecs()) *
                1000;
        VERIFY_OR_DEBUG_ASSERT(nextBeatPositionMillis > beatPositionMillis) {
            return {};
        }
        const double beatLengthMillis =
                (nextBeatPositionMillis - beatPositionMillis) /
                pMarker->beatsTillNextMarker();

        beats.reserve(beats.size() + pMarker->beatsTillNextMarker());
        for (quint32 j = 0; j < pMarker->beatsTillNextMarker(); ++j) {
            const auto beatPosition = mixxx::audio::FramePos(signalInfo.millis2frames(
                    beatPositionMillis + timingOffsetMillis));
            beats.append(beatPosition.toNearestFrameBoundary());
            beatPositionMillis += beatLengthMillis;
        }
    }

    // Calculate remaining beat positions between the last non-terminal marker
    // beat (or the start of the track if none exist) and the terminal marker,
    // using the given BPM.
    //
    //                beatPositionMillis          Terminal Marker
    //                     v                              v
    //     | | | | | | | | |[###### Remaining range ######]
    const double beatLengthMillis = 60000.0 / static_cast<double>(m_pTerminalMarker->bpm());
    VERIFY_OR_DEBUG_ASSERT(m_pTerminalMarker->positionSecs() >= 0 && beatLengthMillis > 0) {
        return {};
    }

    const double rangeEndBeatPositionMillis =
            static_cast<double>(m_pTerminalMarker->positionSecs() * 1000);

    if (beats.isEmpty()) {
        DEBUG_ASSERT(beatPositionMillis == 0);
        // If there are no beats yet (because there were no non-terminal
        // markers), beatPositionMillis will be 0. In that case we have to find
        // the offset backwards from the terminal marker position.
        // Because we're working with doubles, we can't use modulo and need to
        // implement it ourselves.
        const double divisor = std::floor(rangeEndBeatPositionMillis / beatLengthMillis);
        beatPositionMillis = rangeEndBeatPositionMillis - (divisor * beatLengthMillis);
    } else if (beatPositionMillis > rangeEndBeatPositionMillis) {
        beatPositionMillis = rangeEndBeatPositionMillis;
    }

    // Now fill the range with beats until the end is reached. Add a half beat
    // length, to make sure that the last beat is actually included.
    while (beatPositionMillis <= (rangeEndBeatPositionMillis + beatLengthMillis / 2)) {
        const auto beatPosition = mixxx::audio::FramePos(signalInfo.millis2frames(
                beatPositionMillis + timingOffsetMillis));
        beats.append(beatPosition.toNearestFrameBoundary());
        beatPositionMillis += beatLengthMillis;
    }

    m_nonTerminalMarkers.clear();
    m_pTerminalMarker.reset();

    return beats;
}

} // namespace mixxx