/* * beatutils.cpp * * Created on: 30/nov/2011 * Author: vittorio */ #include #include #include #include #include #include "track/beatutils.h" #include "util/math.h" // we are generous and assume the global_BPM to be at most 0.05 BPM far away // from the correct one #define BPM_ERROR 0.05 // the raw beatgrid is divided into blocks of size N from which the local bpm is // computed. Tweaked from 8 to 12 which improves the BPM accuracy for 'problem songs'. #define N 12 static bool sDebug = false; const double kCorrectBeatLocalBpmEpsilon = 0.05; //0.2; const int kHistogramDecimalPlaces = 2; const double kHistogramDecimalScale = pow(10.0, kHistogramDecimalPlaces); const double kBpmFilterTolerance = 1.0; void BeatUtils::printBeatStatistics(const QVector& beats, int SampleRate) { if (!sDebug) { return; } QMap frequency; for (int i = N; i < beats.size(); i += 1) { double beat_start = beats.at(i - N); double beat_end = beats.at(i); // Time needed to count a bar (N beats) const double time = (beat_end - beat_start) / SampleRate; if (time == 0) { continue; } double local_bpm = 60.0 * N / time; qDebug() << "Beat" << i << "local BPM:" << local_bpm; local_bpm = floor(local_bpm * kHistogramDecimalScale + 0.5) / kHistogramDecimalScale; frequency[local_bpm] += 1; } qDebug() << "Rounded local BPM histogram:"; QMapIterator it(frequency); while (it.hasNext()) { it.next(); qDebug() << it.key() << ":" << it.value(); } } // Given a sorted set of numbers, find the sample median. // http://en.wikipedia.org/wiki/Median#The_sample_median double BeatUtils::computeSampleMedian(const QList& sortedItems) { if (sortedItems.empty()) { return 0.0; } // When there are an even number of elements, the sample median is the mean // of the middle 2 elements. if (sortedItems.size() % 2 == 0) { int item_position = sortedItems.size() / 2; double item_value1 = sortedItems.at(item_position - 1); double item_value2 = sortedItems.at(item_position); return (item_value1 + item_value2) / 2.0; } // When there are an odd number of elements, find the {(n+1)/2}th item in // the sorted list. int item_position = (sortedItems.size() + 1) / 2; return sortedItems.at(item_position - 1); } QList BeatUtils::computeWindowedBpmsAndFrequencyHistogram( const QVector& beats, const int windowSize, const int windowStep, const int sampleRate, QMap* frequencyHistogram) { QList averageBpmList; for (int i = windowSize; i < beats.size(); i += windowStep) { //get start and end sample of the beats double start_sample = beats.at(i - windowSize); double end_sample = beats.at(i); // Time needed to count a bar (4 beats) double time = (end_sample - start_sample) / sampleRate; if (time == 0) { continue; } double localBpm = 60.0 * windowSize / time; // round BPM to have two decimal places double roundedBpm = floor(localBpm * kHistogramDecimalScale + 0.5) / kHistogramDecimalScale; // add to local BPM to list and increment frequency count averageBpmList << roundedBpm; (*frequencyHistogram)[roundedBpm] += 1; } return averageBpmList; } double BeatUtils::computeFilteredWeightedAverage( const QMap& frequencyTable, const double filterCenter, const double filterTolerance, QMap* filteredFrequencyTable) { double filterWeightedAverage = 0.0; int filterSum = 0; QMapIterator i(frequencyTable); while (i.hasNext()) { i.next(); const double value = i.key(); const int frequency = i.value(); if (fabs(value - filterCenter) <= filterTolerance) { // TODO(raffitea): Why > 1 ? if (i.value() > 1) { filterSum += frequency; filterWeightedAverage += value * frequency; filteredFrequencyTable->insert(i.key(), frequency); if (sDebug) { qDebug() << "Filtered Table:" << value << "Frequency:" << frequency; } } } } if (sDebug) { qDebug() << "Sum of filtered frequencies: " << filterSum; } if (filterSum == 0) { return filterCenter; } return filterWeightedAverage / static_cast(filterSum); } double BeatUtils::calculateBpm(const QVector& beats, int SampleRate, int min_bpm, int max_bpm) { /* * Let's compute the average local * BPM for N subsequent beats. * The average BPMs are * added to a list from which the statistical * median is computed * * N=12 seems to work great; We coincide with Traktor's * BPM value in many case but not worse than +-0.2 BPM */ /* * Just to demonstrate how you would count the beats manually * * Beat numbers: 1 2 3 4 5 6 7 8 9 * Beat positions: ? ? ? ? |? ? ? ? | ? * * Usually one measures the time of N beats. One stops the timer just before * the (N+1)th beat begins. The BPM is then computed by 60*N/