diff src/Silvet.cpp @ 319:c37da62ba4e5 livemode

Add onsets output (lower-latency than the notes output)
author Chris Cannam
date Wed, 29 Apr 2015 09:27:38 +0100
parents 92293058368a
children 7f9683c8de69
line wrap: on
line diff
--- a/src/Silvet.cpp	Tue Apr 28 16:54:12 2015 +0100
+++ b/src/Silvet.cpp	Wed Apr 29 09:27:38 2015 +0100
@@ -253,6 +253,22 @@
     m_notesOutputNo = list.size();
     list.push_back(d);
 
+    d.identifier = "onsets";
+    d.name = "Note onsets";
+    d.description = "Note onsets, without durations. These can be calculated sooner than complete notes as it isn't necessary to wait for the note to finish. Each event has time, estimated fundamental frequency in Hz, and a synthetic MIDI velocity (1-127) estimated from the strength of the pitch in the mixture.";
+    d.unit = "Hz";
+    d.hasFixedBinCount = true;
+    d.binCount = 2;
+    d.binNames.push_back("Frequency");
+    d.binNames.push_back("Velocity");
+    d.hasKnownExtents = false;
+    d.isQuantized = false;
+    d.sampleType = OutputDescriptor::VariableSampleRate;
+    d.sampleRate = processingSampleRate / (m_cq ? m_cq->getColumnHop() : 62);
+    d.hasDuration = false;
+    m_onsetsOutputNo = list.size();
+    list.push_back(d);
+
     d.identifier = "timefreq";
     d.name = "Time-frequency distribution";
     d.description = "Filtered constant-Q time-frequency distribution as used as input to the expectation-maximisation algorithm.";
@@ -716,13 +732,20 @@
             f.values[j % 12] += filtered[j] / inputGain;
         }
         fs[m_chromaOutputNo].push_back(f);
-        
-        FeatureList noteFeatures = noteTrack(shiftCount);
 
+        auto events = noteTrack(shiftCount);
+
+        FeatureList noteFeatures = events.first;
         for (FeatureList::const_iterator fi = noteFeatures.begin();
              fi != noteFeatures.end(); ++fi) {
             fs[m_notesOutputNo].push_back(*fi);
         }
+
+        FeatureList onsetFeatures = events.second;
+        for (FeatureList::const_iterator fi = onsetFeatures.begin();
+             fi != onsetFeatures.end(); ++fi) {
+            fs[m_onsetsOutputNo].push_back(*fi);
+        }
     }
 }
 
@@ -944,7 +967,7 @@
     return filtered;
 }
 
-Vamp::Plugin::FeatureList
+pair<Vamp::Plugin::FeatureList, Vamp::Plugin::FeatureList>
 Silvet::noteTrack(int shiftCount)
 {        
     // Minimum duration pruning, and conversion to notes. We can only
@@ -964,10 +987,10 @@
     int durationThreshold = floor(0.1 / columnDuration); // columns
     if (durationThreshold < 1) durationThreshold = 1;
 
-    FeatureList noteFeatures;
+    FeatureList noteFeatures, onsetFeatures;
 
     if (width < durationThreshold + 1) {
-        return noteFeatures;
+        return { noteFeatures, onsetFeatures };
     }
     
     //!!! try: repeated note detection? (look for change in first derivative of the pitch matrix)
@@ -976,13 +999,7 @@
          ni != m_pianoRoll[width-1].end(); ++ni) {
 
         int note = ni->first;
-        
-        if (active.find(note) != active.end()) {
-            // the note is still playing
-            continue;
-        }
 
-        // the note was playing but just ended
         int end = width;
         int start = end-1;
 
@@ -991,16 +1008,25 @@
         }
         ++start;
 
-        if ((end - start) < durationThreshold) {
+        int duration = end - start;
+            
+        if (duration < durationThreshold) {
             continue;
         }
 
-        emitNote(start, end, note, shiftCount, noteFeatures);
+        if (duration == durationThreshold) {
+            emitOnset(start, note, shiftCount, onsetFeatures);
+        }            
+        
+        if (active.find(note) == active.end()) {
+            // the note was playing but just ended
+            emitNote(start, end, note, shiftCount, noteFeatures);
+        }
     }
 
 //    cerr << "returning " << noteFeatures.size() << " complete note(s) " << endl;
 
-    return noteFeatures;
+    return { noteFeatures, onsetFeatures };
 }
 
 void
@@ -1065,6 +1091,40 @@
     }
 }
 
+void
+Silvet::emitOnset(int start, int note, int shiftCount,
+                  FeatureList &onsetFeatures)
+{
+    int len = int(m_pianoRoll.size());
+    int velocity = 0;
+
+    int shift = 0;
+    if (shiftCount > 1) {
+        shift = m_pianoRollShifts[start][note];
+    }
+    
+    for (int i = start; i < len; ++i) {
+        
+        double strength = m_pianoRoll[i][note];
+
+        int v;
+        if (m_mode == LiveMode) {
+            v = round(strength * 20);
+        } else {
+            v = round(strength * 2);
+        }
+        if (v > velocity) {
+            velocity = v;
+        }
+    }
+
+    onsetFeatures.push_back(makeOnsetFeature(start,
+                                             note,
+                                             shift,
+                                             shiftCount,
+                                             velocity));
+}
+
 RealTime
 Silvet::getColumnTimestamp(int column)
 {
@@ -1108,6 +1168,36 @@
     return f;
 }
 
+Silvet::Feature
+Silvet::makeOnsetFeature(int start,
+                         int note,
+                         int shift,
+                         int shiftCount,
+                         int velocity)
+{
+    Feature f;
+
+    f.hasTimestamp = true;
+    f.timestamp = getColumnTimestamp(start);
+
+    f.hasDuration = false;
+
+    f.values.clear();
+
+    f.values.push_back
+        (noteFrequency(note, shift, shiftCount));
+
+    float inputGain = getInputGainAt(f.timestamp);
+    velocity = round(velocity / inputGain);
+    if (velocity > 127) velocity = 127;
+    if (velocity < 1) velocity = 1;
+    f.values.push_back(velocity);
+
+    f.label = noteName(note, shift, shiftCount);
+
+    return f;
+}
+
 float
 Silvet::getInputGainAt(RealTime t)
 {