changeset 26:166cc6d39390

* Add note onset output * Add broadband energy rise detection function
author Chris Cannam <c.cannam@qmul.ac.uk>
date Fri, 18 May 2007 16:43:59 +0000
parents aa588f3b46a2
children 3256bfa04ed8
files plugins/BeatDetect.cpp plugins/BeatDetect.h
diffstat 2 files changed, 129 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/plugins/BeatDetect.cpp	Thu May 17 13:29:02 2007 +0000
+++ b/plugins/BeatDetect.cpp	Fri May 18 16:43:59 2007 +0000
@@ -10,6 +10,7 @@
 #include "BeatDetect.h"
 
 #include <dsp/onsets/DetectionFunction.h>
+#include <dsp/onsets/PeakPicking.h>
 #include <dsp/tempotracking/TempoTrack.h>
 
 using std::string;
@@ -43,7 +44,8 @@
 BeatDetector::BeatDetector(float inputSampleRate) :
     Vamp::Plugin(inputSampleRate),
     m_d(0),
-    m_dfType(DF_COMPLEXSD)
+    m_dfType(DF_COMPLEXSD),
+    m_sensitivity(50)
 {
 }
 
@@ -61,14 +63,13 @@
 string
 BeatDetector::getName() const
 {
-    return "Beat Tracker";
+    return "Note Onset and Beat Tracker";
 }
 
 string
 BeatDetector::getDescription() const
 {
-    //!!!
-    return "";
+    return "Estimate tempo, metrical beat locations, and individual note onset positions";
 }
 
 string
@@ -97,7 +98,7 @@
     ParameterDescriptor desc;
     desc.identifier = "dftype";
     desc.name = "Onset Detection Function Type";
-    desc.description = "";
+    desc.description = "Method used to calculate the onset detection function";
     desc.minValue = 0;
     desc.maxValue = 3;
     desc.defaultValue = 3;
@@ -107,6 +108,19 @@
     desc.valueNames.push_back("Spectral Difference");
     desc.valueNames.push_back("Phase Deviation");
     desc.valueNames.push_back("Complex Domain");
+    desc.valueNames.push_back("Broadband Energy Rise");
+    list.push_back(desc);
+
+    desc.identifier = "sensitivity";
+    desc.name = "Onset Detector Sensitivity";
+    desc.description = "Sensitivity of peak-picker for onset detection";
+    desc.minValue = 0;
+    desc.maxValue = 100;
+    desc.defaultValue = 50;
+    desc.isQuantized = true;
+    desc.quantizeStep = 1;
+    desc.unit = "%";
+    desc.valueNames.clear();
     list.push_back(desc);
 
     return list;
@@ -121,7 +135,10 @@
         case DF_SPECDIFF: return 1;
         case DF_PHASEDEV: return 2;
         default: case DF_COMPLEXSD: return 3;
+        case DF_BROADBAND: return 4;
         }
+    } else if (name == "sensitivity") {
+        return m_sensitivity;
     }
     return 0.0;
 }
@@ -135,7 +152,10 @@
         case 1: m_dfType = DF_SPECDIFF; break;
         case 2: m_dfType = DF_PHASEDEV; break;
         default: case 3: m_dfType = DF_COMPLEXSD; break;
+        case 4: m_dfType = DF_BROADBAND; break;
         }
+    } else if (name == "sensitivity") {
+        m_sensitivity = value;
     }
 }
 
@@ -171,6 +191,7 @@
     dfConfig.stepSecs = float(stepSize) / m_inputSampleRate;
     dfConfig.stepSize = stepSize;
     dfConfig.frameLength = blockSize;
+    dfConfig.dbRise = 6.0 - m_sensitivity / 20.0;
     
     m_d = new BeatDetectorData(dfConfig);
     return true;
@@ -203,7 +224,7 @@
 
     OutputDescriptor beat;
     beat.identifier = "beats";
-    beat.name = "Detected Beats";
+    beat.name = "Beats";
     beat.unit = "";
     beat.hasFixedBinCount = true;
     beat.binCount = 0;
@@ -212,7 +233,7 @@
 
     OutputDescriptor df;
     df.identifier = "detection_fn";
-    df.name = "Beat Detection Function";
+    df.name = "Onset Detection Function";
     df.unit = "";
     df.hasFixedBinCount = true;
     df.binCount = 1;
@@ -229,9 +250,35 @@
     tempo.sampleType = OutputDescriptor::VariableSampleRate;
     tempo.sampleRate = 1.0 / m_stepSecs;
 
+    OutputDescriptor onsets;
+    onsets.identifier = "onsets";
+    onsets.name = "Note Onsets";
+    onsets.unit = "";
+    onsets.hasFixedBinCount = true;
+    onsets.binCount = 0;
+    onsets.sampleType = OutputDescriptor::VariableSampleRate;
+    onsets.sampleRate = 1.0 / m_stepSecs;
+
+    OutputDescriptor sdf;
+    sdf.identifier = "smoothed_df";
+    sdf.name = "Smoothed Detection Function";
+    sdf.unit = "";
+    sdf.hasFixedBinCount = true;
+    sdf.binCount = 1;
+    sdf.hasKnownExtents = false;
+    sdf.isQuantized = false;
+
+    sdf.sampleType = OutputDescriptor::VariableSampleRate;
+
+//!!! SV doesn't seem to handle these correctly in getRemainingFeatures
+//    sdf.sampleType = OutputDescriptor::FixedSampleRate;
+    sdf.sampleRate = 1.0 / m_stepSecs;
+
     list.push_back(beat);
     list.push_back(df);
     list.push_back(tempo);
+    list.push_back(onsets);
+    list.push_back(sdf);
 
     return list;
 }
@@ -303,11 +350,14 @@
     ttParams.WinT.pre = 7;
 
     TempoTrack tempoTracker(ttParams);
+
     vector<double> tempos;
     vector<int> beats = tempoTracker.process(m_d->dfOutput, &tempos);
 
     FeatureSet returnFeatures;
 
+    char label[100];
+
     for (size_t i = 0; i < beats.size(); ++i) {
 
 	size_t frame = beats[i] * m_d->dfConfig.stepSize;
@@ -331,7 +381,6 @@
 	    if (frameIncrement > 0) {
 		bpm = (60.0 * m_inputSampleRate) / frameIncrement;
 		bpm = int(bpm * 100.0 + 0.5) / 100.0;
-                static char label[100];
                 sprintf(label, "%.2f bpm", bpm);
                 feature.label = label;
 	    }
@@ -348,16 +397,86 @@
 
 //        std::cerr << "unit " << i << ", step size " << m_d->dfConfig.stepSize << ", hop " << ttParams.lagLength << ", frame = " << frame << std::endl;
         
-        if (tempos[i] > 1 && tempos[i] != prevTempo) {
+        if (tempos[i] > 1 && int(tempos[i] * 100) != int(prevTempo * 100)) {
             Feature feature;
             feature.hasTimestamp = true;
             feature.timestamp = Vamp::RealTime::frame2RealTime
                 (frame, lrintf(m_inputSampleRate));
             feature.values.push_back(tempos[i]);
+            sprintf(label, "%.2f bpm", tempos[i]);
+            feature.label = label;
             returnFeatures[2].push_back(feature); // tempo is output 2
         }
     }
 
+    // Now a separate pass for onsets and smoothed detection function
+
+    PPickParams ppParams;
+    ppParams.length = m_d->dfOutput.size();
+    // tau and cutoff appear to be unused in PeakPicking, but I've
+    // inserted some moderately plausible values rather than leave
+    // them unset.  The QuadThresh values come from trial and error.
+    // The rest of these are copied from ttParams: I don't claim to
+    // know whether they're good or not --cc
+    ppParams.tau = m_d->dfConfig.stepSize / m_inputSampleRate;
+    ppParams.alpha = 9;
+    ppParams.cutoff = m_inputSampleRate/4;
+    ppParams.LPOrd = 2;
+    ppParams.LPACoeffs = aCoeffs;
+    ppParams.LPBCoeffs = bCoeffs;
+    ppParams.WinT.post = 8;
+    ppParams.WinT.pre = 7;
+    ppParams.QuadThresh.a = (100 - m_sensitivity) / 1000.0;
+    ppParams.QuadThresh.b = 0;
+    ppParams.QuadThresh.c = (100 - m_sensitivity) / 1500.0;
+
+    PeakPicking peakPicker(ppParams);
+
+    double *ppSrc = new double[ppParams.length];
+    for (int i = 0; i < ppParams.length; ++i) {
+        ppSrc[i] = m_d->dfOutput[i];
+    }
+
+    vector<int> onsets;
+    peakPicker.process(ppSrc, ppParams.length, onsets);
+
+    for (size_t i = 0; i < onsets.size(); ++i) {
+
+        size_t index = onsets[i];
+
+        if (m_dfType != DF_BROADBAND) {
+            double prevDiff = 0.0;
+            while (index > 1) {
+                double diff = ppSrc[index] - ppSrc[index-1];
+                if (diff < prevDiff * 0.75) break;
+                prevDiff = diff;
+                --index;
+            }
+        }
+
+	size_t frame = index * m_d->dfConfig.stepSize;
+
+	Feature feature;
+	feature.hasTimestamp = true;
+	feature.timestamp = Vamp::RealTime::frame2RealTime
+	    (frame, lrintf(m_inputSampleRate));
+
+	returnFeatures[3].push_back(feature); // onsets are output 3
+    }
+
+    for (int i = 0; i < ppParams.length; ++i) {
+        
+        Feature feature;
+//        feature.hasTimestamp = false;
+        feature.hasTimestamp = true;
+	size_t frame = i * m_d->dfConfig.stepSize;
+	feature.timestamp = Vamp::RealTime::frame2RealTime
+	    (frame, lrintf(m_inputSampleRate));
+
+        feature.values.push_back(ppSrc[i]);
+        returnFeatures[4].push_back(feature); // smoothed df is output 4
+    }
+
     return returnFeatures;
 }
 
--- a/plugins/BeatDetect.h	Thu May 17 13:29:02 2007 +0000
+++ b/plugins/BeatDetect.h	Fri May 18 16:43:59 2007 +0000
@@ -49,6 +49,7 @@
 protected:
     BeatDetectorData *m_d;
     int m_dfType;
+    float m_sensitivity;
     static float m_stepSecs;
 };