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