23f7f647f2c6 — Chris Cannam tip 6 days ago
Merge from branch higher-precision
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