1 #include "track/beatutils.h"
2 
3 #include <QList>
4 #include <QMap>
5 #include <QString>
6 #include <QtDebug>
7 #include <algorithm>
8 
9 #include "track/bpm.h"
10 #include "util/math.h"
11 
12 namespace {
13 
14 // When ironing the grid for long sequences of const tempo we use
15 // a 25 ms tolerance because this small of a difference is inaudible
16 // This is > 2 * 12 ms, the step width of the QM beat detector
17 constexpr double kMaxSecsPhaseError = 0.025;
18 // This is set to avoid to use a constant region during an offset shift.
19 // That happens for instance when the beat instrument changes.
20 constexpr double kMaxSecsPhaseErrorSum = 0.1;
21 constexpr int kMaxOutlierCount = 1;
22 constexpr int kMinRegionBeatCount = 16;
23 
24 } // namespace
25 
calculateAverageBpm(int numberOfBeats,mixxx::audio::SampleRate sampleRate,double lowerFrame,double upperFrame)26 double BeatUtils::calculateAverageBpm(int numberOfBeats,
27         mixxx::audio::SampleRate sampleRate,
28         double lowerFrame,
29         double upperFrame) {
30     double frames = upperFrame - lowerFrame;
31     DEBUG_ASSERT(frames > 0);
32     if (numberOfBeats < 1) {
33         return 0;
34     }
35     return 60.0 * numberOfBeats * sampleRate / frames;
36 }
37 
calculateBpm(const QVector<double> & beats,mixxx::audio::SampleRate sampleRate)38 double BeatUtils::calculateBpm(
39         const QVector<double>& beats,
40         mixxx::audio::SampleRate sampleRate) {
41     if (beats.size() < 2) {
42         return 0;
43     }
44 
45     // If we don't have enough beats for our regular approach, just divide the #
46     // of beats by the duration in minutes.
47     if (beats.size() < kMinRegionBeatCount) {
48         return calculateAverageBpm(beats.size() - 1, sampleRate, beats.first(), beats.last());
49     }
50 
51     QVector<BeatUtils::ConstRegion> constantRegions =
52             retrieveConstRegions(beats, sampleRate);
53     return makeConstBpm(constantRegions, sampleRate, nullptr);
54 }
55 
retrieveConstRegions(const QVector<double> & coarseBeats,mixxx::audio::SampleRate sampleRate)56 QVector<BeatUtils::ConstRegion> BeatUtils::retrieveConstRegions(
57         const QVector<double>& coarseBeats,
58         mixxx::audio::SampleRate sampleRate) {
59     // The QM Beat detector has a step size of 512 frames @ 44100 Hz. This means that
60     // Single beats have has a jitter of +- 12 ms around the actual position.
61     // Expressed in BPM it means we have for instance steps of these BPM value around 120 BPM
62     // 117.454 - 120.185 - 123.046 - 126.048
63     // A pure electronic 120.000 BPM track will have many 120,185 BPM beats and a few
64     // 117,454 BPM beats to adjust the collected offset.
65     // This function irons these adjustment beats by adjusting every beat to the average of
66     // a likely constant region.
67 
68     // Therefore we loop through the coarse beats and calculate the average beat
69     // length from the first beat.
70     // A inner loop checks for outliers using the momentary average as beat length.
71     // once we have found an average with only single outliers, we store the beats using the
72     // current average to adjust them by up to +-12 ms.
73     // Than we start with the region from the found beat to the end.
74 
75     QVector<ConstRegion> constantRegions;
76     if (!coarseBeats.size()) {
77         // no beats
78         return constantRegions;
79     }
80 
81     double maxPhaseError = kMaxSecsPhaseError * sampleRate;
82     double maxPhaseErrorSum = kMaxSecsPhaseErrorSum * sampleRate;
83     int leftIndex = 0;
84     int rightIndex = coarseBeats.size() - 1;
85 
86     while (leftIndex < coarseBeats.size() - 1) {
87         DEBUG_ASSERT(rightIndex > leftIndex);
88         double meanBeatLength =
89                 (coarseBeats[rightIndex] - coarseBeats[leftIndex]) /
90                 (rightIndex - leftIndex);
91         int outliersCount = 0;
92         double ironedBeat = coarseBeats[leftIndex];
93         double phaseErrorSum = 0;
94         int i = leftIndex + 1;
95         for (; i <= rightIndex; ++i) {
96             ironedBeat += meanBeatLength;
97             double phaseError = ironedBeat - coarseBeats[i];
98             phaseErrorSum += phaseError;
99             if (fabs(phaseError) > maxPhaseError) {
100                 outliersCount++;
101                 if (outliersCount > kMaxOutlierCount ||
102                         i == leftIndex + 1) { // the first beat must not be an outlier.
103                     // region is not const.
104                     break;
105                 }
106             }
107             if (fabs(phaseErrorSum) > maxPhaseErrorSum) {
108                 // we drift away in one direction, the meanBeatLength is not optimal.
109                 break;
110             }
111         }
112         if (i > rightIndex) {
113             // Verify that the first an the last beat are not correction beats in the same direction
114             // This would bend meanBeatLength unfavorably away from the optimum.
115             double regionBorderError = 0;
116             if (rightIndex > leftIndex + 2) {
117                 double firstBeatLength = coarseBeats[leftIndex + 1] - coarseBeats[leftIndex];
118                 double lastBeatLength = coarseBeats[rightIndex] - coarseBeats[rightIndex - 1];
119                 regionBorderError = fabs(firstBeatLength + lastBeatLength - (2 * meanBeatLength));
120             }
121             if (regionBorderError < maxPhaseError / 2) {
122                 // We have found a constant enough region.
123                 double firstBeat = coarseBeats[leftIndex];
124                 // store the regions for the later stages
125                 constantRegions.append({firstBeat, meanBeatLength});
126                 // continue with the next region.
127                 leftIndex = rightIndex;
128                 rightIndex = coarseBeats.size() - 1;
129                 continue;
130             }
131         }
132         // Try a by one beat smaller region
133         rightIndex--;
134     }
135 
136     // Add a final region with zero length to mark the end.
137     constantRegions.append({coarseBeats[coarseBeats.size() - 1], 0});
138     return constantRegions;
139 }
140 
141 // static
makeConstBpm(const QVector<BeatUtils::ConstRegion> & constantRegions,mixxx::audio::SampleRate sampleRate,double * pFirstBeat)142 double BeatUtils::makeConstBpm(
143         const QVector<BeatUtils::ConstRegion>& constantRegions,
144         mixxx::audio::SampleRate sampleRate,
145         double* pFirstBeat) {
146     // We assume here the track was recorded with an unhear-able static metronome.
147     // This metronome is likely at a full BPM.
148     // The track may has intros, outros and bridges without detectable beats.
149     // In these regions the detected beat might is floating around and is just wrong.
150     // The track may also has regions with different Rythm giving Instruments. They
151     // have a different shape of onsets and introduce a static beat offset.
152     // The track may also have break beats or other issues that makes the detector
153     // hook onto a beat that is by an integer fraction off the original metronome.
154 
155     // This code aims to find the static metronome and a phase offset.
156 
157     // Find the longest region somewhere in the middle of the track to start with.
158     // At least this region will be have finally correct annotated beats.
159 
160     // Note: This function is channel count independent. All sample based values in
161     // this functions are based on frames.
162 
163     int midRegionIndex = 0;
164     double longestRegionLength = 0;
165     double longestRegionBeatLength = 0;
166     for (int i = 0; i < constantRegions.size() - 1; ++i) {
167         double length = constantRegions[i + 1].firstBeat - constantRegions[i].firstBeat;
168         if (length > longestRegionLength) {
169             longestRegionLength = length;
170             longestRegionBeatLength = constantRegions[i].beatLength;
171             midRegionIndex = i;
172         }
173         //qDebug() << i << length << constantRegions[i].beatLength;
174     }
175 
176     if (longestRegionLength == 0) {
177         // no betas, we default to
178         return 128;
179     }
180 
181     int longestRegionNumberOfBeats = static_cast<int>(
182             (longestRegionLength / longestRegionBeatLength) + 0.5);
183     double longestRegionBeatLengthMin = longestRegionBeatLength -
184             ((kMaxSecsPhaseError * sampleRate) / longestRegionNumberOfBeats);
185     double longestRegionBeatLengthMax = longestRegionBeatLength +
186             ((kMaxSecsPhaseError * sampleRate) / longestRegionNumberOfBeats);
187 
188     int startRegionIndex = midRegionIndex;
189 
190     // Find a region at the beginning of the track with a similar tempo and phase
191     for (int i = 0; i < midRegionIndex; ++i) {
192         const double length = constantRegions[i + 1].firstBeat - constantRegions[i].firstBeat;
193         const int numberOfBeats = static_cast<int>((length / constantRegions[i].beatLength) + 0.5);
194         if (numberOfBeats < kMinRegionBeatCount) {
195             // Request short regions, too unstable.
196             continue;
197         }
198         const double thisRegionBeatLengthMin = constantRegions[i].beatLength -
199                 ((kMaxSecsPhaseError * sampleRate) / numberOfBeats);
200         const double thisRegionBeatLengthMax = constantRegions[i].beatLength +
201                 ((kMaxSecsPhaseError * sampleRate) / numberOfBeats);
202         // check if the tempo of the longest region is part of the rounding range of this region
203         if (longestRegionBeatLength > thisRegionBeatLengthMin &&
204                 longestRegionBeatLength < thisRegionBeatLengthMax) {
205             // Now check if both regions are at the same phase.
206             const double newLongestRegionLength = constantRegions[midRegionIndex + 1].firstBeat -
207                     constantRegions[i].firstBeat;
208 
209             double beatLengthMin = math_max(longestRegionBeatLengthMin, thisRegionBeatLengthMin);
210             double beatLengthMax = math_min(longestRegionBeatLengthMax, thisRegionBeatLengthMax);
211 
212             const int maxNumberOfBeats =
213                     static_cast<int>(round(newLongestRegionLength / beatLengthMin));
214             const int minNumberOfBeats =
215                     static_cast<int>(round(newLongestRegionLength / beatLengthMax));
216 
217             if (minNumberOfBeats != maxNumberOfBeats) {
218                 // Ambiguous number of beats, find a closer region.
219                 continue;
220             }
221             const int numberOfBeats = minNumberOfBeats;
222             const double newBeatLength = newLongestRegionLength / numberOfBeats;
223             if (newBeatLength > longestRegionBeatLengthMin &&
224                     newBeatLength < longestRegionBeatLengthMax) {
225                 longestRegionLength = newLongestRegionLength;
226                 longestRegionBeatLength = newBeatLength;
227                 longestRegionNumberOfBeats = numberOfBeats;
228                 longestRegionBeatLengthMin = longestRegionBeatLength -
229                         ((kMaxSecsPhaseError * sampleRate) / longestRegionNumberOfBeats);
230                 longestRegionBeatLengthMax = longestRegionBeatLength +
231                         ((kMaxSecsPhaseError * sampleRate) / longestRegionNumberOfBeats);
232                 startRegionIndex = i;
233                 break;
234             }
235         }
236     }
237 
238     // Find a region at the end of the track with similar tempo and phase
239     for (int i = constantRegions.size() - 2; i > midRegionIndex; --i) {
240         const double length = constantRegions[i + 1].firstBeat - constantRegions[i].firstBeat;
241         const int numberOfBeats = static_cast<int>((length / constantRegions[i].beatLength) + 0.5);
242         if (numberOfBeats < kMinRegionBeatCount) {
243             continue;
244         }
245         const double thisRegionBeatLengthMin = constantRegions[i].beatLength -
246                 ((kMaxSecsPhaseError * sampleRate) / numberOfBeats);
247         const double thisRegionBeatLengthMax = constantRegions[i].beatLength +
248                 ((kMaxSecsPhaseError * sampleRate) / numberOfBeats);
249         if (longestRegionBeatLength > thisRegionBeatLengthMin &&
250                 longestRegionBeatLength < thisRegionBeatLengthMax) {
251             // Now check if both regions are at the same phase.
252             const double newLongestRegionLength = constantRegions[i + 1].firstBeat -
253                     constantRegions[startRegionIndex].firstBeat;
254 
255             double minBeatLength = math_max(longestRegionBeatLengthMin, thisRegionBeatLengthMin);
256             double maxBeatLength = math_min(longestRegionBeatLengthMax, thisRegionBeatLengthMax);
257 
258             const int maxNumberOfBeats =
259                     static_cast<int>(round(newLongestRegionLength / minBeatLength));
260             const int minNumberOfBeats =
261                     static_cast<int>(round(newLongestRegionLength / maxBeatLength));
262 
263             if (minNumberOfBeats != maxNumberOfBeats) {
264                 // Ambiguous number of beats, find a closer region.
265                 continue;
266             }
267             const int numberOfBeats = minNumberOfBeats;
268             const double newBeatLength = newLongestRegionLength / numberOfBeats;
269             if (newBeatLength > longestRegionBeatLengthMin &&
270                     newBeatLength < longestRegionBeatLengthMax) {
271                 longestRegionLength = newLongestRegionLength;
272                 longestRegionBeatLength = newBeatLength;
273                 longestRegionNumberOfBeats = numberOfBeats;
274                 break;
275             }
276         }
277     }
278 
279     longestRegionBeatLengthMin = longestRegionBeatLength -
280             ((kMaxSecsPhaseError * sampleRate) / longestRegionNumberOfBeats);
281     longestRegionBeatLengthMax = longestRegionBeatLength +
282             ((kMaxSecsPhaseError * sampleRate) / longestRegionNumberOfBeats);
283 
284     // qDebug() << startRegion << midRegion << constantRegions.size()
285     //         << longestRegionLength << "<<<<<<<<<<<<<<<<<<<<<<<<<";
286 
287     //qDebug() << "First beat" << constantRegions[startRegion].firstBeat;
288     //qDebug() << longestRegionLength << longestRegionNumberOfBeats;
289 
290     // Create a const region region form the first beat of the first region to the last beat of the last region.
291 
292     const double minRoundBpm = 60.0 * sampleRate / longestRegionBeatLengthMax;
293     const double maxRoundBpm = 60.0 * sampleRate / longestRegionBeatLengthMin;
294     const double centerBpm = 60.0 * sampleRate / longestRegionBeatLength;
295 
296     //qDebug() << "minRoundBpm" << minRoundBpm;
297     //qDebug() << "maxRoundBpm" << maxRoundBpm;
298     const double roundBpm = roundBpmWithinRange(minRoundBpm, centerBpm, maxRoundBpm);
299 
300     if (pFirstBeat) {
301         // Move the first beat as close to the start of the track as we can. This is
302         // a constant beatgrid so "first beat" only affects the anchor point where
303         // bpm adjustments are made.
304         // This is a temporary fix, ideally the anchor point for the BPM grid should
305         // be the first proper downbeat, or perhaps the CUE point.
306         const double roundedBeatLength = 60.0 * sampleRate / roundBpm;
307         *pFirstBeat = fmod(constantRegions[startRegionIndex].firstBeat,
308                 roundedBeatLength);
309     }
310     return roundBpm;
311 }
312 
313 // static
roundBpmWithinRange(double minBpm,double centerBpm,double maxBpm)314 double BeatUtils::roundBpmWithinRange(double minBpm, double centerBpm, double maxBpm) {
315     // First try to snap to a full integer BPM
316     double snapBpm = round(centerBpm);
317     if (snapBpm > minBpm && snapBpm < maxBpm) {
318         // Success
319         return snapBpm;
320     }
321 
322     // Probe the reasonable multipliers for 0.5
323     const double roundBpmWidth = maxBpm - minBpm;
324     if (roundBpmWidth > 0.5) {
325         // 0.5 BPM are only reasonable if the double value is not insane
326         // or the 2/3 value is not too small.
327         if (centerBpm < 85.0) {
328             // this cane be actually up to 175 BPM
329             // allow halve BPM values
330             return round(centerBpm * 2) / 2;
331         } else if (centerBpm > 127.0) {
332             // optimize for 2/3 going down to 85
333             return round(centerBpm / 3 * 2) * 3 / 2;
334         }
335     }
336 
337     if (roundBpmWidth > 1.0 / 12) {
338         // this covers all sorts of 1/2 2/3 and 3/4 multiplier
339         return round(centerBpm * 12) / 12;
340     } else {
341         // We are here if we have more that ~75 beats and ~30 s
342         // try to snap to a 1/12 Bpm
343         snapBpm = round(centerBpm * 12) / 12;
344         if (snapBpm > minBpm && snapBpm < maxBpm) {
345             // Success
346             return snapBpm;
347         }
348         // else give up and use the original BPM value.
349     }
350 
351     return centerBpm;
352 }
353 
354 // static
getBeats(const QVector<BeatUtils::ConstRegion> & constantRegions)355 QVector<double> BeatUtils::getBeats(const QVector<BeatUtils::ConstRegion>& constantRegions) {
356     QVector<double> beats;
357     for (int i = 0; i < constantRegions.size() - 1; ++i) {
358         double beat = constantRegions[i].firstBeat;
359         constexpr double epsilon = 100; // Protection against tiny beats due rounding
360         while (beat < constantRegions[i + 1].firstBeat - epsilon) {
361             beats.append(beat);
362             beat += constantRegions[i].beatLength;
363         }
364     }
365     if (constantRegions.size() > 0) {
366         beats.append(constantRegions.last().firstBeat);
367     }
368     return beats;
369 }
370 
371 // static
adjustPhase(double firstBeat,double bpm,mixxx::audio::SampleRate sampleRate,const QVector<double> & beats)372 double BeatUtils::adjustPhase(
373         double firstBeat,
374         double bpm,
375         mixxx::audio::SampleRate sampleRate,
376         const QVector<double>& beats) {
377     const double beatLength = 60 * sampleRate / bpm;
378     const double startOffset = fmod(firstBeat, beatLength);
379     double offsetAdjust = 0;
380     double offsetAdjustCount = 0;
381     for (const auto& beat : beats) {
382         double offset = fmod(beat - startOffset, beatLength);
383         if (offset > beatLength / 2) {
384             offset -= beatLength;
385         }
386         if (abs(offset) < (kMaxSecsPhaseError * sampleRate)) {
387             offsetAdjust += offset;
388             offsetAdjustCount++;
389         }
390     }
391     offsetAdjust /= offsetAdjustCount;
392     qDebug() << "adjusting phase by" << offsetAdjust;
393     DEBUG_ASSERT(abs(offsetAdjust) < (kMaxSecsPhaseError * sampleRate));
394 
395     return firstBeat + offsetAdjust;
396 }
397