* Add hasDuration to output descriptors
[vamp-aubio-plugins.git] / plugins / Notes.cpp
index 05f7189..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
@@ -22,8 +22,9 @@ 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_ibuf(0),
     m_fftgrain(0),
     m_onset(0),
@@ -36,8 +37,19 @@ Notes::Notes(float inputSampleRate) :
     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()
@@ -52,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)";
@@ -72,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
@@ -112,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;
 }
@@ -141,8 +163,8 @@ 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.defaultValue = (int)aubio_onset_complex;
     desc.minValue = 0;
     desc.maxValue = 6;
     desc.defaultValue = (int)aubio_onset_complex;
@@ -158,8 +180,8 @@ 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.defaultValue = (int)aubio_pitch_yinfft;
     desc.minValue = 0;
     desc.maxValue = 4;
     desc.defaultValue = (int)aubio_pitch_yinfft;
@@ -173,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;
@@ -182,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;
@@ -205,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;
     }
@@ -235,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);
     }
 }
 
     }
 }
 
@@ -244,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;
@@ -262,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) {
@@ -322,16 +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 -
-        Vamp::RealTime::frame2RealTime((4 + m_median) * m_stepSize,
-                                       m_inputSampleRate);
-    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);
 }