25#include <unordered_map>
26#include <unordered_set>
39 std::pair<int, int> { 1, 1 },
40 std::pair<int, int> { 2, 1 }, std::pair<int, int> { 3, 1 },
41 std::pair<int, int> { 4, 1 }, std::pair<int, int> { 6, 1 },
42 std::pair<int, int> { 1, 2 }, std::pair<int, int> { 1, 3 },
43 std::pair<int, int> { 1, 4 }, std::pair<int, int> { 1, 6 }
56 std::unordered_map<
int , std::vector<BarDivision>>;
63 constexpr auto minBarDuration = 1.;
64 constexpr auto maxBarDuration = 4.;
65 const int minNumBars =
66 std::max(
std::round(audioFileDuration / maxBarDuration), 1.);
67 const int maxNumBars =
std::round(audioFileDuration / minBarDuration);
70 IotaRange { minNumBars, maxNumBars + 1 }, [&](
int numBars) {
71 const auto barDuration = audioFileDuration / numBars;
72 const auto minBpb = std::clamp<int>(
75 const auto maxBpb = std::clamp<int>(
79 IotaRange { minBpb, maxBpb + 1 }, [&](
int beatsPerBar) {
82 [&](
const std::pair<int, int>& tatumsPerBeat) {
83 const auto [tatumsPerBeatNum, tatumsPerBeatDen] =
88 (beatsPerBar * tatumsPerBeatNum) % tatumsPerBeatDen !=
92 const int tatumsPerBar =
93 beatsPerBar * tatumsPerBeatNum / tatumsPerBeatDen;
94 const int numTatums = tatumsPerBar * numBars;
95 const auto tatumRate = 60. * numTatums / audioFileDuration;
97 minTatumsPerMinute < tatumRate &&
98 tatumRate < maxTatumsPerMinute)
99 possibleDivHierarchies[numTatums].push_back(
100 BarDivision { numBars, beatsPerBar });
104 return possibleDivHierarchies;
116 const auto pulseTrainPeriod = 1. * odf.size() / numTatums;
117 auto max = std::numeric_limits<float>::lowest();
123 const int j =
std::round(i * pulseTrainPeriod) + lag;
124 val += (j < odf.size() ? odf[j] : 0.f);
144 const std::vector<int>& peakIndices,
const std::vector<float>& peakValues,
145 size_t size,
int numDivisions,
int lag)
147 std::vector<double> peakDistances(peakIndices.size());
149 std::accumulate(peakValues.begin(), peakValues.end(), 0.);
150 const auto odfSamplesPerDiv = 1. *
size / numDivisions;
152 peakIndices.begin(), peakIndices.end(), peakDistances.begin(),
154 const auto shiftedIndex = peakIndex - lag;
155 const auto closestDiv = std::round(shiftedIndex / odfSamplesPerDiv);
157 const auto distance =
158 (shiftedIndex - closestDiv * odfSamplesPerDiv) / odfSamplesPerDiv;
160 return 2 * std::abs(distance);
164 const auto weightedAverage =
166 peakDistances.begin(), peakDistances.end(), peakValues.begin(), 0.) /
168 return weightedAverage;
172 const std::vector<float>& odf,
const std::vector<int>& peakIndices,
173 const std::vector<float>& peakValues,
174 const std::vector<int>& possibleNumTatums,
177 const auto quantizations = [&]() {
178 std::unordered_map<int, OnsetQuantization> quantizations;
180 possibleNumTatums.begin(), possibleNumTatums.end(),
181 std::inserter(quantizations, quantizations.end()), [&](
int numTatums) {
182 const auto lag = GetOnsetLag(odf, numTatums);
183 const auto distance = GetQuantizationDistance(
184 peakIndices, peakValues, odf.size(), numTatums, lag);
185 return std::make_pair(
186 numTatums, OnsetQuantization { distance, lag, numTatums });
188 return quantizations;
191 const auto bestFitIt = std::min_element(
192 quantizations.begin(), quantizations.end(),
193 [](
const std::pair<int, OnsetQuantization>& a,
194 const std::pair<int, OnsetQuantization>& b) {
195 return a.second.error < b.second.error;
198 const auto error = bestFitIt->second.error;
199 const auto mostLikelyNumTatums = bestFitIt->first;
200 const auto lag = bestFitIt->second.lag;
202 return { error, lag, mostLikelyNumTatums };
205std::optional<TimeSignature>
209 const auto tatumsPerBeat = 1. * numTatums / numBeats;
213 return (tatumsPerBeat == 3) ? TimeSignature::SixEight :
214 TimeSignature::TwoTwo;
216 return TimeSignature::ThreeFour;
218 return TimeSignature::FourFour;
237 static const std::unordered_map<TimeSignature, double> expectedBpms {
238 { TimeSignature::TwoTwo, 115. },
239 { TimeSignature::FourFour, 115. },
240 { TimeSignature::ThreeFour, 140. },
241 { TimeSignature::SixEight, 64. },
244 static const std::unordered_map<TimeSignature, double> bpmStdDevs {
245 { TimeSignature::TwoTwo, 25. },
246 { TimeSignature::FourFour, 25. },
247 { TimeSignature::ThreeFour, 25. },
248 { TimeSignature::SixEight, 15. },
252 static const std::unordered_map<TimeSignature, double> tsPrior {
253 { TimeSignature::TwoTwo, .1 },
254 { TimeSignature::FourFour, .45 },
255 { TimeSignature::ThreeFour, .2 },
256 { TimeSignature::SixEight, .25 },
259 const auto mu = expectedBpms.at(*ts);
260 const auto sigma = bpmStdDevs.at(*ts);
261 const auto tmp = (bpm - mu) / sigma;
262 const auto likelihood = std::exp(-.5 * tmp * tmp);
263 return likelihood * tsPrior.at(*ts);
270 double odfAutoCorrSampleRate,
double bpm,
271 const std::vector<float>& odfAutoCorr,
int odfAutocorrFullSize,
275 const auto lag = odfAutoCorrSampleRate * 60 / bpm;
280 auto periodIndex = 1;
285 auto j =
static_cast<int>(periodIndex++ * lag + .5);
286 if (j >= odfAutoCorr.size())
293 odfAutoCorr[i] <= odfAutoCorr[j] &&
294 odfAutoCorr[j] >= odfAutoCorr[k])
296 j = odfAutoCorr[i] > odfAutoCorr[k] ? i : k;
298 sum += odfAutoCorr[j];
303 return sum / numIndices;
307 const std::vector<BarDivision>& possibleBarDivisions,
308 double audioFileDuration,
int numTatums,
const std::vector<float>& odf,
315 const auto odfAutocorrFullSize = 2 * (odfAutoCorr.size() - 1);
317 const auto odfAutoCorrSampleRate = odfAutocorrFullSize / audioFileDuration;
319 std::vector<double> scores(possibleBarDivisions.size());
324 std::unordered_map<
int ,
double> autocorrScoreCache;
326 possibleBarDivisions.begin(), possibleBarDivisions.end(), scores.begin(),
328 const auto numBeats = barDivision.numBars * barDivision.beatsPerBar;
329 const auto timeSignature = GetTimeSignature(barDivision, numTatums);
330 const auto bpm = 1. * numBeats / audioFileDuration * 60;
331 const auto likelihood = GetTimeSignatureLikelihood(timeSignature, bpm);
332 if (!autocorrScoreCache.count(numBeats))
333 autocorrScoreCache[numBeats] = GetBeatSelfSimilarityScore(
334 odfAutoCorrSampleRate, bpm, odfAutoCorr, odfAutocorrFullSize,
336 const auto selfSimilarityScore = autocorrScoreCache.at(numBeats);
337 return likelihood * selfSimilarityScore;
340 return std::max_element(scores.begin(), scores.end()) - scores.begin();
344 const std::vector<float>& odf,
int numTatums,
345 std::vector<BarDivision> possibleBarDivisions,
double audioFileDuration,
348 std::vector<BarDivision> fourFourDivs;
352 GetTimeSignature(possibleBarDivisions[i], numTatums) ==
353 TimeSignature::FourFour)
354 fourFourDivs.push_back(possibleBarDivisions[i]);
361 if (!fourFourDivs.empty())
362 std::swap(possibleBarDivisions, fourFourDivs);
365 possibleBarDivisions, audioFileDuration, numTatums, odf, debugOutput);
367 const auto& barDivision = possibleBarDivisions[winnerIndex];
368 const auto numBeats = barDivision.numBars * barDivision.beatsPerBar;
370 const auto bpm = 60. * numBeats / audioFileDuration;
372 return { bpm, signature };
376 const std::vector<int>& peakIndices,
const std::vector<float>& peakValues)
382 std::accumulate(peakValues.begin(), peakValues.end(), 0.) /
384 const auto numPeaksAboveAvg =
385 std::count_if(peakValues.begin(), peakValues.end(), [&](
float v) {
388 return numPeaksAboveAvg <= 1;
394 const std::function<
void(
double)>& progressCallback,
400 1. *
audio.GetSampleRate() * odf.size() /
audio.GetNumSamples();
401 const auto audioFileDuration =
402 1. *
audio.GetNumSamples() /
audio.GetSampleRate();
408 debugOutput->
odfSr = odfSr;
412 const auto peakValues = ([&]() {
413 std::vector<float> peakValues(peakIndices.size());
415 peakIndices.begin(), peakIndices.end(), peakValues.begin(),
416 [&](
int i) { return odf[i]; });
424 if (possibleDivs.empty())
428 const auto possibleNumTatums = [&]() {
429 std::vector<int> possibleNumTatums(possibleDivs.size());
431 possibleDivs.begin(), possibleDivs.end(), possibleNumTatums.begin(),
432 [&](
const auto&
entry) { return entry.first; });
433 return possibleNumTatums;
437 odf, peakIndices, peakValues, possibleNumTatums, debugOutput);
440 odf, experiment.numDivisions, possibleDivs.at(experiment.numDivisions),
441 audioFileDuration, debugOutput);
443 const auto score = 1 - experiment.error;
448 debugOutput->
bpm = winnerMeter.bpm;
450 debugOutput->
odf = odf;
451 debugOutput->
odfSr = odfSr;
453 debugOutput->
score = score;
457 std::optional<MusicalMeter> {} :
static ProjectFileIORegistry::AttributeWriterEntry entry
void for_each_in_range(Range &&range, Function &&fn)
constexpr auto MapToPositiveHalfIndex(int index, int fullSize)
Useful when dealing with symmetric spectra reduced only to their positive half. See tests below for m...
bool IsSingleEvent(const std::vector< int > &peakIndices, const std::vector< float > &peakValues)
constexpr auto maxTatumsPerMinute
PossibleDivHierarchies GetPossibleDivHierarchies(double audioFileDuration)
std::optional< TimeSignature > GetTimeSignature(const BarDivision &barDivision, int numTatums)
constexpr auto minBeatsPerBar
size_t GetBestBarDivisionIndex(const std::vector< BarDivision > &possibleBarDivisions, double audioFileDuration, int numTatums, const std::vector< float > &odf, QuantizationFitDebugOutput *debugOutput)
int GetOnsetLag(const std::vector< float > &odf, int numTatums)
double GetQuantizationDistance(const std::vector< int > &peakIndices, const std::vector< float > &peakValues, size_t size, int numDivisions, int lag)
constexpr auto minTatumsPerMinute
double GetBeatSelfSimilarityScore(double odfAutoCorrSampleRate, double bpm, const std::vector< float > &odfAutoCorr, int odfAutocorrFullSize, QuantizationFitDebugOutput *debugOutput)
constexpr auto maxBeatsPerBar
std::unordered_map< int, std::vector< BarDivision > > PossibleDivHierarchies
double GetTimeSignatureLikelihood(const std::optional< TimeSignature > &ts, double bpm)
MusicalMeter GetMostLikelyMeterFromQuantizationExperiment(const std::vector< float > &odf, int numTatums, std::vector< BarDivision > possibleBarDivisions, double audioFileDuration, QuantizationFitDebugOutput *debugOutput)
constexpr std::array< std::pair< int, int >, 9 > possibleTatumsPerBeat
OnsetQuantization RunQuantizationExperiment(const std::vector< float > &odf, const std::vector< int > &peakIndices, const std::vector< float > &peakValues, const std::vector< int > &possibleNumTatums, QuantizationFitDebugOutput *debugOutput)
std::vector< float > GetOnsetDetectionFunction(const MirAudioReader &audio, const std::function< void(double)> &progressCallback, QuantizationFitDebugOutput *debugOutput)
std::vector< float > GetNormalizedCircularAutocorr(const std::vector< float > &ux)
Get the normalized, circular auto-correlation for a signal x whose length already is a power of two....
static const std::unordered_map< FalsePositiveTolerance, LoopClassifierSettings > loopClassifierSettings
std::optional< MusicalMeter > GetMeterUsingTatumQuantizationFit(const MirAudioReader &audio, FalsePositiveTolerance tolerance, const std::function< void(double)> &progressCallback, QuantizationFitDebugOutput *debugOutput)
Get the BPM of the given audio file, using the Tatum Quantization Fit method.
std::vector< int > GetPeakIndices(const std::vector< float > &x)
constexpr auto IsPowOfTwo(int x)
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
OnsetQuantization tatumQuantization
std::vector< float > odfAutoCorr
std::optional< TimeSignature > timeSignature
std::vector< int > odfPeakIndices
std::vector< int > odfAutoCorrPeakIndices