M .hgignore +2 -0
@@ 3,4 3,6 @@ syntax: glob
re:^doc/html/
re:^doc/latex/
*.so
+*.o
+*.a
re:^tests/test-minibpm$
M README.md +1 -1
@@ 6,7 6,7 @@ implemented in C++ with few external dep
Written by Chris Cannam, chris.cannam@breakfastquay.com.
Published by Particular Programs Ltd t/a Breakfast Quay.
-Copyright 2007-2021 Particular Programs Ltd.
+Copyright 2007-2025 Particular Programs Ltd.
* About MiniBPM: https://breakfastquay.com/minibpm/
* Code repository: https://hg.sr.ht/~breakfastquay/minibpm
M com/breakfastquay/minibpm/MiniBpm.java +1 -1
@@ 3,7 3,7 @@
/*
MiniBPM
A fixed-tempo BPM estimator for music audio
- Copyright 2012-2021 Particular Programs Ltd.
+ Copyright 2012-2025 Particular Programs Ltd.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
M src/MiniBpm.cpp +35 -19
@@ 3,7 3,7 @@
/*
MiniBPM
A fixed-tempo BPM estimator for music audio
- Copyright 2012-2021 Particular Programs Ltd.
+ Copyright 2012-2025 Particular Programs Ltd.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
@@ 63,6 63,7 @@
#include <vector>
#include <map>
+#include <set>
#include <utility>
#include <cmath>
#include <algorithm>
@@ 225,6 226,7 @@ public:
// 0 1 2 4 4 4 8 8 8 8 8 8 16 16 16 16 16 16 16 ...
base = (lag * multiple) - (multiple / 4);
count = (multiple / 4) + (multiple / 2);
+ if (base < 0) base = 0;
}
}
@@ 269,40 271,48 @@ public:
int multiple = 1;
double interpolated = lag;
+ double overallPeak = 0.0;
- double total = 0.0;
- int n = 0;
+ while (multiple <= 16) {
- while (1) {
-
int base, count;
getContributingRange(lag, multiple, base, count);
-
if (base + count > acfLength) break;
double peak = 0.0;
- int peakidx = 0;
- for (int j = base; j < base + count; ++j) {
+ int pix = 0;
+ for (int j = base; j < acfLength; ++j) {
if (acf[j] > peak) {
peak = acf[j];
- peakidx = j;
+ pix = j;
+ } else if (j >= base + count) {
+ break;
}
}
- if (peak > 0.0) {
- double scaled = double(peakidx) / multiple;
- total += scaled;
- ++n;
+ if (peak > overallPeak * 0.9) {
+ double loc = pix;
+ if (pix > 0 && pix + 1 < acfLength) {
+ double a = acf[pix-1];
+ double b = acf[pix];
+ double c = acf[pix+1];
+ if (b > a && b > c) {
+ double d = a - 2*b + c;
+ if (d != 0.0) {
+ loc += ((a - c) / d) / 2.0;
+ }
+ }
+ }
+ interpolated = loc / multiple;
+ if (peak > overallPeak) {
+ overallPeak = peak;
+ }
}
if (multiple == 1) multiple = m_beatsPerBar;
else multiple = multiple * 2;
}
- if (n > 0) {
- interpolated = total / n;
- }
-
double bpm = Autocorrelation::lagToBpm(interpolated, m_hopsPerSec);
return bpm;
}
@@ 381,7 391,7 @@ public:
m_input = new double[m_blockSize];
m_partial = new double[m_stepSize];
- int frameSize = std::max(lfsize, hfsize);
+ int frameSize = std::max(lfsize, hfsize);
m_frame = new double[frameSize];
zero(m_input, m_blockSize);
@@ 563,12 573,18 @@ public:
return 0.0;
}
+ std::set<int> grossCandidatesSeen;
+
std::multimap<double, int>::const_iterator ci(candidateMap.end());
while (ci != candidateMap.begin()) {
--ci;
int lag = ci->second + minlag;
double bpm = filter.refine(lag, acf, acfLength);
- m_candidates.push_back(bpm);
+ int gross = int(round(bpm * 2.0)); // treat within 0.5 as duplicate
+ if (grossCandidatesSeen.find(gross) == grossCandidatesSeen.end()) {
+ m_candidates.push_back(bpm);
+ grossCandidatesSeen.insert(gross);
+ }
}
delete[] cf;
M src/MiniBpm.h +1 -1
@@ 3,7 3,7 @@
/*
MiniBPM
A fixed-tempo BPM estimator for music audio
- Copyright 2012-2021 Particular Programs Ltd.
+ Copyright 2012-2025 Particular Programs Ltd.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
M src/jni/MiniBpmJNI.cpp +1 -1
@@ 3,7 3,7 @@
/*
MiniBPM
A fixed-tempo BPM estimator for music audio
- Copyright 2012-2021 Particular Programs Ltd.
+ Copyright 2012-2025 Particular Programs Ltd.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
M tests/TestMiniBpm.cpp +2 -3
@@ 3,7 3,7 @@
/*
MiniBPM
A fixed-tempo BPM estimator for music audio
- Copyright 2012-2021 Particular Programs Ltd.
+ Copyright 2012-2025 Particular Programs Ltd.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
@@ 304,8 304,7 @@ BOOST_AUTO_TEST_CASE(refineAdjusting)
{
double acf[] = { 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0 };
double bpm = REFINER(1).refine(1, acf, 8);
- BOOST_CHECK_LT(bpm, 60);
- BOOST_CHECK_GT(bpm, 50);
+ BOOST_CHECK_EQUAL(bpm, 48);
}
BOOST_AUTO_TEST_SUITE_END()
M vamp/Makefile +1 -1
@@ 7,7 7,7 @@ VAMP_SOURCES := libmain.cpp MiniBpmVamp.
MINIBPM_SOURCES := ../src/MiniBpm.cpp
MINIBPM_HEADERS := ../src/MiniBpm.h
-CXXFLAGS := -I../src -fPIC
+CXXFLAGS := -std=c++98 -I../src -fPIC
VAMP_LDFLAGS := $(LDFLAGS) $(PLUGIN_LDFLAGS) -lvamp-sdk
M vamp/MiniBpmVamp.cpp +31 -3
@@ 3,7 3,7 @@
/*
MiniBPM
A fixed-tempo BPM estimator for music audio
- Copyright 2012-2021 Particular Programs Ltd.
+ Copyright 2012-2025 Particular Programs Ltd.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
@@ 71,7 71,7 @@ MiniBpmVamp::getPluginVersion() const
{
// Increment this each time you release a version that behaves
// differently from the previous one
- return 1;
+ return 2;
}
string
@@ 189,6 189,18 @@ MiniBpmVamp::getOutputDescriptors() cons
d.hasDuration = true;
outputs.push_back(d);
+ d = OutputDescriptor();
+ d.identifier = "candidates";
+ d.name = "Tempo Candidates";
+ d.description = "A multi-valued feature containing all tempo candidates starting with the most likely";
+ d.hasFixedBinCount = false;
+ d.hasKnownExtents = false;
+ d.isQuantized = false;
+ d.sampleType = OutputDescriptor::FixedSampleRate;
+ d.sampleRate = m_inputSampleRate;
+ d.hasDuration = true;
+ outputs.push_back(d);
+
return outputs;
}
@@ 210,12 222,14 @@ MiniBpmVamp::reset()
{
m_mbpm->reset();
m_mbpm->setBeatsPerBar(m_beatsPerBar);
+ m_lastTimestamp = Vamp::RealTime::zeroTime;
}
MiniBpmVamp::FeatureSet
MiniBpmVamp::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
{
m_mbpm->process(inputBuffers[0], m_blockSize);
+ m_lastTimestamp = timestamp;
return FeatureSet();
}
@@ 230,10 244,24 @@ MiniBpmVamp::getRemainingFeatures()
f.hasTimestamp = true;
f.timestamp = Vamp::RealTime::zeroTime;
f.hasDuration = true;
- f.duration = Vamp::RealTime::fromSeconds(1.0);
+ f.duration = m_lastTimestamp +
+ Vamp::RealTime::frame2RealTime(m_blockSize, m_inputSampleRate);
f.values.push_back(bpm);
fs[0].push_back(f);
+ std::vector<double> candidates = m_mbpm->getTempoCandidates();
+
+ f = Feature();
+ f.hasTimestamp = true;
+ f.timestamp = Vamp::RealTime::zeroTime;
+ f.hasDuration = true;
+ f.duration = m_lastTimestamp +
+ Vamp::RealTime::frame2RealTime(m_blockSize, m_inputSampleRate);
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ f.values.push_back(candidates[i]);
+ }
+ fs[1].push_back(f);
+
return fs;
}
M vamp/MiniBpmVamp.h +2 -1
@@ 3,7 3,7 @@
/*
MiniBPM
A fixed-tempo BPM estimator for music audio
- Copyright 2012-2021 Particular Programs Ltd.
+ Copyright 2012-2025 Particular Programs Ltd.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
@@ 72,6 72,7 @@ public:
protected:
breakfastquay::MiniBPM *m_mbpm;
+ Vamp::RealTime m_lastTimestamp;
size_t m_blockSize;
int m_beatsPerBar;
};
M vamp/libmain.cpp +1 -1
@@ 3,7 3,7 @@
/*
MiniBPM
A fixed-tempo BPM estimator for music audio
- Copyright 2012-2021 Particular Programs Ltd.
+ Copyright 2012-2025 Particular Programs Ltd.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as