* Add hasDuration to output descriptors
[vamp-aubio-plugins.git] / plugins / Notes.cpp
index 0a64dce..11a73c2 100644 (file)
@@ -4,7 +4,7 @@
     Vamp feature extraction plugins using Paul Brossier's Aubio library.
 
     Centre for Digital Music, Queen Mary, University of London.
     Vamp feature extraction plugins using Paul Brossier's Aubio library.
 
     Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam.
+    This file copyright 2006-2008 Chris Cannam and QMUL.
     
     This program is free software; you can redistribute it and/or
     modify it under the terms of the GNU General Public License as
     
     This program is free software; you can redistribute it and/or
     modify it under the terms of the GNU General Public License as
@@ -14,6 +14,7 @@
 
 */
 
 
 */
 
+#include <math.h>
 #include "Notes.h"
 
 using std::string;
 #include "Notes.h"
 
 using std::string;
@@ -21,22 +22,34 @@ using std::vector;
 using std::cerr;
 using std::endl;
 
 using std::cerr;
 using std::endl;
 
-Notes::Notes(float inputSampleRate) :
+Notes::Notes(float inputSampleRate, unsigned int apiVersion) :
     Plugin(inputSampleRate),
     Plugin(inputSampleRate),
+    m_apiVersion(apiVersion),
     m_ibuf(0),
     m_fftgrain(0),
     m_onset(0),
     m_pv(0),
     m_peakpick(0),
     m_onsetdet(0),
     m_ibuf(0),
     m_fftgrain(0),
     m_onset(0),
     m_pv(0),
     m_peakpick(0),
     m_onsetdet(0),
-    m_onsettype(aubio_onset_mkl),
+    m_onsettype(aubio_onset_complex),
     m_pitchdet(0),
     m_pitchdet(0),
-    m_pitchtype(aubio_pitch_fcomb),
+    m_pitchtype(aubio_pitch_yinfft),
     m_pitchmode(aubio_pitchm_freq),
     m_threshold(0.3),
     m_silence(-90),
     m_pitchmode(aubio_pitchm_freq),
     m_threshold(0.3),
     m_silence(-90),
-    m_median(6)
+    m_median(6),
+    m_minpitch(27),
+    m_maxpitch(95),
+    m_wrapRange(false),
+    m_avoidLeaps(false),
+    m_prevPitch(-1)
 {
 {
+    if (apiVersion == 1) {
+        cerr << "vamp-aubio: WARNING: using compatibility version 1 of the Vamp API for note\n"
+             << "tracker plugin: upgrade your host to v2 for proper duration support" << endl;
+    } else {
+        cerr << "vamp-aubio: NOTE: using v2 API for true durations" << endl;
+    }
 }
 
 Notes::~Notes()
 }
 
 Notes::~Notes()
@@ -51,18 +64,24 @@ Notes::~Notes()
 }
 
 string
 }
 
 string
-Notes::getName() const
+Notes::getIdentifier() const
 {
     return "aubionotes";
 }
 
 string
 {
     return "aubionotes";
 }
 
 string
-Notes::getDescription() const
+Notes::getName() const
 {
     return "Aubio Note Tracker";
 }
 
 string
 {
     return "Aubio Note Tracker";
 }
 
 string
+Notes::getDescription() const
+{
+    return "Estimate note onset positions, pitches and durations";
+}
+
+string
 Notes::getMaker() const
 {
     return "Paul Brossier (plugin by Chris Cannam)";
 Notes::getMaker() const
 {
     return "Paul Brossier (plugin by Chris Cannam)";
@@ -71,7 +90,8 @@ Notes::getMaker() const
 int
 Notes::getPluginVersion() const
 {
 int
 Notes::getPluginVersion() const
 {
-    return 1;
+    if (m_apiVersion == 1) return 2;
+    return 3;
 }
 
 string
 }
 
 string
@@ -111,8 +131,11 @@ Notes::initialise(size_t channels, size_t stepSize, size_t blockSize)
                                           m_pitchmode);
 
     m_count = 0;
                                           m_pitchmode);
 
     m_count = 0;
+    m_delay = Vamp::RealTime::frame2RealTime((4 + m_median) * m_stepSize,
+                                       lrintf(m_inputSampleRate));
     m_currentOnset = Vamp::RealTime::zeroTime;
     m_haveCurrent = false;
     m_currentOnset = Vamp::RealTime::zeroTime;
     m_haveCurrent = false;
+    m_prevPitch = -1;
 
     return true;
 }
 
     return true;
 }
@@ -125,18 +148,13 @@ Notes::reset()
 size_t
 Notes::getPreferredStepSize() const
 {
 size_t
 Notes::getPreferredStepSize() const
 {
-    if (m_onsettype == aubio_onset_energy ||
-        m_onsettype == aubio_onset_hfc) {
-        return 512;
-    } else {
-        return 128;
-    }
+    return 512;
 }
 
 size_t
 Notes::getPreferredBlockSize() const
 {
 }
 
 size_t
 Notes::getPreferredBlockSize() const
 {
-    return getPreferredStepSize();
+    return 4 * getPreferredStepSize();
 }
 
 Notes::ParameterList
 }
 
 Notes::ParameterList
@@ -145,11 +163,11 @@ Notes::getParameterDescriptors() const
     ParameterList list;
     
     ParameterDescriptor desc;
     ParameterList list;
     
     ParameterDescriptor desc;
-    desc.name = "onsettype";
-    desc.description = "Onset Detection Function Type";
+    desc.identifier = "onsettype";
+    desc.name = "Onset Detection Function Type";
     desc.minValue = 0;
     desc.maxValue = 6;
     desc.minValue = 0;
     desc.maxValue = 6;
-    desc.defaultValue = (int)aubio_onset_mkl;
+    desc.defaultValue = (int)aubio_onset_complex;
     desc.isQuantized = true;
     desc.quantizeStep = 1;
     desc.valueNames.push_back("Energy Based");
     desc.isQuantized = true;
     desc.quantizeStep = 1;
     desc.valueNames.push_back("Energy Based");
@@ -162,11 +180,11 @@ Notes::getParameterDescriptors() const
     list.push_back(desc);
 
     desc = ParameterDescriptor();
     list.push_back(desc);
 
     desc = ParameterDescriptor();
-    desc.name = "pitchtype";
-    desc.description = "Pitch Detection Function Type";
+    desc.identifier = "pitchtype";
+    desc.name = "Pitch Detection Function Type";
     desc.minValue = 0;
     desc.maxValue = 4;
     desc.minValue = 0;
     desc.maxValue = 4;
-    desc.defaultValue = (int)aubio_pitch_fcomb;
+    desc.defaultValue = (int)aubio_pitch_yinfft;
     desc.isQuantized = true;
     desc.quantizeStep = 1;
     desc.valueNames.push_back("YIN Frequency Estimator");
     desc.isQuantized = true;
     desc.quantizeStep = 1;
     desc.valueNames.push_back("YIN Frequency Estimator");
@@ -177,8 +195,50 @@ Notes::getParameterDescriptors() const
     list.push_back(desc);
 
     desc = ParameterDescriptor();
     list.push_back(desc);
 
     desc = ParameterDescriptor();
-    desc.name = "peakpickthreshold";
-    desc.description = "Peak Picker Threshold";
+    desc.identifier = "minpitch";
+    desc.name = "Minimum Pitch";
+    desc.minValue = 0;
+    desc.maxValue = 127;
+    desc.defaultValue = 32;
+    desc.unit = "MIDI units";
+    desc.isQuantized = true;
+    desc.quantizeStep = 1;
+    list.push_back(desc);
+
+    desc = ParameterDescriptor();
+    desc.identifier = "maxpitch";
+    desc.name = "Maximum Pitch";
+    desc.minValue = 0;
+    desc.maxValue = 127;
+    desc.defaultValue = 95;
+    desc.unit = "MIDI units";
+    desc.isQuantized = true;
+    desc.quantizeStep = 1;
+    list.push_back(desc);
+
+    desc = ParameterDescriptor();
+    desc.identifier = "wraprange";
+    desc.name = "Fold Higher or Lower Notes into Range";
+    desc.minValue = 0;
+    desc.maxValue = 1;
+    desc.defaultValue = 0;
+    desc.isQuantized = true;
+    desc.quantizeStep = 1;
+    list.push_back(desc);
+
+    desc = ParameterDescriptor();
+    desc.identifier = "avoidleaps";
+    desc.name = "Avoid Multi-Octave Jumps";
+    desc.minValue = 0;
+    desc.maxValue = 1;
+    desc.defaultValue = 0;
+    desc.isQuantized = true;
+    desc.quantizeStep = 1;
+    list.push_back(desc);
+
+    desc = ParameterDescriptor();
+    desc.identifier = "peakpickthreshold";
+    desc.name = "Peak Picker Threshold";
     desc.minValue = 0;
     desc.maxValue = 1;
     desc.defaultValue = 0.3;
     desc.minValue = 0;
     desc.maxValue = 1;
     desc.defaultValue = 0.3;
@@ -186,8 +246,8 @@ Notes::getParameterDescriptors() const
     list.push_back(desc);
 
     desc = ParameterDescriptor();
     list.push_back(desc);
 
     desc = ParameterDescriptor();
-    desc.name = "silencethreshold";
-    desc.description = "Silence Threshold";
+    desc.identifier = "silencethreshold";
+    desc.name = "Silence Threshold";
     desc.minValue = -120;
     desc.maxValue = 0;
     desc.defaultValue = -90;
     desc.minValue = -120;
     desc.maxValue = 0;
     desc.defaultValue = -90;
@@ -209,6 +269,14 @@ Notes::getParameter(std::string param) const
         return m_threshold;
     } else if (param == "silencethreshold") {
         return m_silence;
         return m_threshold;
     } else if (param == "silencethreshold") {
         return m_silence;
+    } else if (param == "minpitch") {
+        return m_minpitch;
+    } else if (param == "maxpitch") {
+        return m_maxpitch;
+    } else if (param == "wraprange") {
+        return m_wrapRange ? 1.0 : 0.0;
+    } else if (param == "avoidleaps") {
+        return m_avoidLeaps ? 1.0 : 0.0;
     } else {
         return 0.0;
     }
     } else {
         return 0.0;
     }
@@ -239,6 +307,14 @@ Notes::setParameter(std::string param, float value)
         m_threshold = value;
     } else if (param == "silencethreshold") {
         m_silence = value;
         m_threshold = value;
     } else if (param == "silencethreshold") {
         m_silence = value;
+    } else if (param == "minpitch") {
+        m_minpitch = lrintf(value);
+    } else if (param == "maxpitch") {
+        m_maxpitch = lrintf(value);
+    } else if (param == "wraprange") {
+        m_wrapRange = (value > 0.5);
+    } else if (param == "avoidleaps") {
+        m_avoidLeaps = (value > 0.5);
     }
 }
 
     }
 }
 
@@ -248,14 +324,23 @@ Notes::getOutputDescriptors() const
     OutputList list;
 
     OutputDescriptor d;
     OutputList list;
 
     OutputDescriptor d;
-    d.name = "notes";
+    d.identifier = "notes";
+    d.name = "Notes";
     d.unit = "Hz";
     d.unit = "Hz";
-    d.description = "Notes";
     d.hasFixedBinCount = true;
     d.hasFixedBinCount = true;
-    d.binCount = 2;
-    d.binNames.push_back("Frequency");
-    d.binNames.push_back("Duration");
-    d.binNames.push_back("Velocity");
+
+    if (m_apiVersion == 1) {
+        d.binCount = 3;
+        d.binNames.push_back("Frequency");
+        d.binNames.push_back("Duration");
+        d.binNames.push_back("Velocity");
+    } else {
+        d.binCount = 2;
+        d.binNames.push_back("Frequency");
+        d.binNames.push_back("Velocity");
+        d.hasDuration = true;
+    }
+
     d.hasKnownExtents = false;
     d.isQuantized = false;
     d.sampleType = OutputDescriptor::VariableSampleRate;
     d.hasKnownExtents = false;
     d.isQuantized = false;
     d.sampleType = OutputDescriptor::VariableSampleRate;
@@ -266,7 +351,7 @@ Notes::getOutputDescriptors() const
 }
 
 Notes::FeatureSet
 }
 
 Notes::FeatureSet
-Notes::process(float **inputBuffers, Vamp::RealTime timestamp)
+Notes::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
 {
     for (size_t i = 0; i < m_stepSize; ++i) {
         for (size_t j = 0; j < m_channelCount; ++j) {
 {
     for (size_t i = 0; i < m_stepSize; ++i) {
         for (size_t j = 0; j < m_channelCount; ++j) {
@@ -326,14 +411,54 @@ Notes::pushNote(FeatureSet &fs, const Vamp::RealTime &offTime)
     float median = toSort[toSort.size()/2];
     if (median < 45.0) return;
 
     float median = toSort[toSort.size()/2];
     if (median < 45.0) return;
 
+    float freq = median;
+    int midiPitch = (int)floor(aubio_freqtomidi(freq) + 0.5);
+    
+    if (m_avoidLeaps) {
+        if (m_prevPitch >= 0) {
+            while (midiPitch < m_prevPitch - 12) {
+                midiPitch += 12;
+                freq *= 2;
+            }
+            while (midiPitch > m_prevPitch + 12) {
+                midiPitch -= 12;
+                freq /= 2;
+            }
+        }
+    }
+
+    while (midiPitch < m_minpitch) {
+        if (!m_wrapRange) return;
+        midiPitch += 12;
+        freq *= 2;
+    }
+
+    while (midiPitch > m_maxpitch) {
+        if (!m_wrapRange) return;
+        midiPitch -= 12;
+        freq /= 2;
+    }
+
+    m_prevPitch = midiPitch;
+
     Feature feature;
     feature.hasTimestamp = true;
     Feature feature;
     feature.hasTimestamp = true;
-    feature.timestamp = m_currentOnset;
-    feature.values.push_back(median);
-//    feature.values.push_back(FLOOR(aubio_freqtomidi(median) + 0.5));
-    feature.values.push_back
-        (Vamp::RealTime::realTime2Frame(offTime, lrintf(m_inputSampleRate)) -
-         Vamp::RealTime::realTime2Frame(m_currentOnset, lrintf(m_inputSampleRate)));
+    if (m_currentOnset < m_delay) m_currentOnset = m_delay;
+    feature.timestamp = m_currentOnset - m_delay;
+    feature.values.push_back(freq);
+
+    if (m_apiVersion == 1) {
+        feature.values.push_back
+            (Vamp::RealTime::realTime2Frame
+             (offTime, lrintf(m_inputSampleRate)) -
+             Vamp::RealTime::realTime2Frame
+             (m_currentOnset, lrintf(m_inputSampleRate)));
+        feature.hasDuration = false;
+    } else {
+        feature.hasDuration = true;
+        feature.duration = offTime - m_currentOnset;
+    }
+
     feature.values.push_back(m_currentLevel);
     fs[0].push_back(feature);
 }
     feature.values.push_back(m_currentLevel);
     fs[0].push_back(feature);
 }