changeset 314:f98ba4f47e49 livemode

Merge from default branch
author Chris Cannam
date Tue, 28 Apr 2015 11:24:23 +0100
parents 26be240475b5 (diff) fa2ffbb786df (current diff)
children dec47312ed40
files .hgsubstate Makefile.inc Makefile.linux src/EM.cpp src/Silvet.cpp src/Silvet.h
diffstat 9 files changed, 327 insertions(+), 71 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.inc	Tue Apr 28 11:09:31 2015 +0100
+++ b/Makefile.inc	Tue Apr 28 11:24:23 2015 +0100
@@ -20,8 +20,8 @@
 
 PLUGIN	:= silvet$(PLUGIN_EXT)
 
-PLUGIN_HEADERS := $(SRC_DIR)/Silvet.h $(SRC_DIR)/EM.h $(SRC_DIR)/Instruments.h
-PLUGIN_SOURCES := $(SRC_DIR)/Silvet.cpp $(SRC_DIR)/EM.cpp $(SRC_DIR)/Instruments.cpp $(SRC_DIR)/libmain.cpp
+PLUGIN_HEADERS := $(SRC_DIR)/Silvet.h $(SRC_DIR)/EM.h $(SRC_DIR)/Instruments.h $(SRC_DIR)/LiveInstruments.h
+PLUGIN_SOURCES := $(SRC_DIR)/Silvet.cpp $(SRC_DIR)/EM.cpp $(SRC_DIR)/Instruments.cpp $(SRC_DIR)/LiveInstruments.cpp $(SRC_DIR)/libmain.cpp
 
 BQVEC_HEADERS	:= $(BQVEC_DIR)/Allocators.h $(BQVEC_DIR)/Restrict.h $(BQVEC_DIR)/VectorOps.h
 BQVEC_SOURCES	:= $(BQVEC_DIR)/src/Allocators.cpp
@@ -58,7 +58,7 @@
 # DO NOT DELETE
 
 src/Silvet.o: src/Silvet.h src/MedianFilter.h src/Instruments.h src/EM.h
-src/Silvet.o: constant-q-cpp/src/dsp/Resampler.h
+src/Silvet.o: constant-q-cpp/src/dsp/Resampler.h src/LiveInstruments.h
 src/EM.o: src/EM.h src/Instruments.h
 src/Instruments.o: src/Instruments.h data/include/templates.h
 src/Instruments.o: data/include/bassoon.h data/include/cello.h
@@ -68,6 +68,16 @@
 src/Instruments.o: data/include/violin.h data/include/viola.h
 src/Instruments.o: data/include/piano1.h data/include/piano2.h
 src/Instruments.o: data/include/piano3.h data/include/pianorwc.h
+src/LiveInstruments.o: src/LiveInstruments.h src/Instruments.h
+src/LiveInstruments.o: data/include/templates.h data/include/bassoon.h
+src/LiveInstruments.o: data/include/cello.h data/include/clarinet.h
+src/LiveInstruments.o: data/include/flute.h data/include/guitar.h
+src/LiveInstruments.o: data/include/horn.h data/include/oboe.h
+src/LiveInstruments.o: data/include/tenorsax.h data/include/violin.h
+src/LiveInstruments.o: data/include/viola.h data/include/piano1.h
+src/LiveInstruments.o: data/include/piano2.h data/include/piano3.h
+src/LiveInstruments.o: data/include/pianorwc.h
 src/libmain.o: src/Silvet.h src/MedianFilter.h src/Instruments.h
 flattendynamics/flattendynamics-ladspa.o: flattendynamics/flattendynamics-ladspa.h
 src/Silvet.o: src/MedianFilter.h src/Instruments.h
+src/LiveInstruments.o: src/Instruments.h
--- a/data/include/templates.h	Tue Apr 28 11:09:31 2015 +0100
+++ b/data/include/templates.h	Tue Apr 28 11:24:23 2015 +0100
@@ -6,7 +6,7 @@
 /* note: intended to parse as both C and C++ */
 
 #define SILVET_TEMPLATE_COUNT      14   /* Number of instruments */
-#define SILVET_TEMPLATE_NOTE_COUNT 88   /* Number of notes per instrument */ 
+#define SILVET_TEMPLATE_NOTE_COUNT 88   /* Number of notes per instrument */
 #define SILVET_TEMPLATE_HEIGHT     545  /* Frequency bins per template */
 #define SILVET_TEMPLATE_MAX_SHIFT  2    /* Zeros at either end of template */ 
 #define SILVET_TEMPLATE_SIZE       549  /* Height + 2 * max shift space */ 
--- a/src/Instruments.cpp	Tue Apr 28 11:09:31 2015 +0100
+++ b/src/Instruments.cpp	Tue Apr 28 11:24:23 2015 +0100
@@ -19,16 +19,28 @@
 
 #include <iostream>
 
-const int InstrumentPack::templateNoteCount = SILVET_TEMPLATE_NOTE_COUNT;
-const int InstrumentPack::templateHeight = SILVET_TEMPLATE_HEIGHT;
-const int InstrumentPack::templateMaxShift = SILVET_TEMPLATE_MAX_SHIFT;
-const int InstrumentPack::templateSize = SILVET_TEMPLATE_SIZE;
-
 using std::string;
 using std::vector;
 using std::cerr;
 using std::endl;
 
+InstrumentPack::InstrumentPack(int lowest, int highest,
+                               std::string n, std::vector<Templates> tt) :
+    templateNoteCount(SILVET_TEMPLATE_NOTE_COUNT),
+    templateHeight(SILVET_TEMPLATE_HEIGHT),
+    templateMaxShift(SILVET_TEMPLATE_MAX_SHIFT),
+    templateSize(SILVET_TEMPLATE_SIZE),
+    lowestNote(lowest),
+    highestNote(highest),
+    maxPolyphony(5),
+    pitchSparsity(1.1),
+    sourceSparsity(1.2),
+    levelThreshold(5),
+    name(n),
+    templates(tt)
+{
+}
+
 const char *simpleInstruments[] = {
     // Each instrument has two consecutive slots, one for the pack
     // name and one for the template to look up
--- a/src/Instruments.h	Tue Apr 28 11:09:31 2015 +0100
+++ b/src/Instruments.h	Tue Apr 28 11:24:23 2015 +0100
@@ -32,10 +32,10 @@
 class InstrumentPack
 {
 public:
-    static const int templateNoteCount;
-    static const int templateHeight;
-    static const int templateMaxShift;
-    static const int templateSize;
+    int templateNoteCount;
+    int templateHeight;
+    int templateMaxShift;
+    int templateSize;
 
     int lowestNote;
     int highestNote;
@@ -60,15 +60,9 @@
 
 private:
     InstrumentPack(int lowest, int highest, std::string n,
-		   std::vector<Templates> tt) :
-	lowestNote(lowest),
-	highestNote(highest),
-        maxPolyphony(5),
-        pitchSparsity(1.1),
-        sourceSparsity(1.2),
-        levelThreshold(5),
-	name(n),
-	templates(tt) { }
+		   std::vector<Templates> tt);
+    
+    friend class LiveAdapter;
 };
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/LiveInstruments.cpp	Tue Apr 28 11:24:23 2015 +0100
@@ -0,0 +1,94 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+  Silvet
+
+  A Vamp plugin for note transcription.
+  Centre for Digital Music, Queen Mary University of London.
+    
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 2 of the
+  License, or (at your option) any later version.  See the file
+  COPYING included with this distribution for more information.
+*/
+
+#include "LiveInstruments.h"
+
+#include "data/include/templates.h"
+
+#include <iostream>
+
+using namespace std;
+
+InstrumentPack
+LiveAdapter::adapt(const InstrumentPack &original)
+{
+    vector<InstrumentPack::Templates> templates;
+
+//            cerr << "LiveAdapter: reduced template height is " << SILVET_TEMPLATE_HEIGHT/5 << endl;
+            
+    for (vector<InstrumentPack::Templates>::const_iterator i =
+	     original.templates.begin();
+	 i != original.templates.end(); ++i) {
+
+	InstrumentPack::Templates t;
+	t.lowestNote = i->lowestNote;
+	t.highestNote = i->highestNote;
+	t.data.resize(i->data.size());
+
+	for (int j = 0; j < int(i->data.size()); ++j) {
+
+	    t.data[j].resize(SILVET_TEMPLATE_HEIGHT/5);
+
+            float sum = 0.f;
+
+	    for (int k = 0; k < SILVET_TEMPLATE_HEIGHT/5; ++k) {
+
+                t.data[j][k] = 0.f;
+
+                for (int m = 0; m < 5; ++m) {
+                    t.data[j][k] += i->data[j][k * 5 + m + 2];
+                }
+                
+                sum += t.data[j][k];
+	    }
+            
+	    // re-normalise
+            if (sum > 0.f) {
+                for (int k = 0; k < (int)t.data[j].size(); ++k) {
+                    t.data[j][k] *= 1.f / sum;
+                }
+            }
+	}
+
+	templates.push_back(t);
+    }
+    
+    InstrumentPack live(original.lowestNote,
+			original.highestNote,
+			original.name,
+			templates);
+
+    live.templateHeight = SILVET_TEMPLATE_HEIGHT/5;
+    live.templateMaxShift = 0;
+    live.templateSize = live.templateHeight;
+    
+    live.maxPolyphony = original.maxPolyphony;
+    live.pitchSparsity = original.pitchSparsity;
+    live.sourceSparsity = original.sourceSparsity;
+    live.levelThreshold = original.levelThreshold / 20;
+
+    return live;
+}
+
+vector<InstrumentPack>
+LiveAdapter::adaptAll(const vector<InstrumentPack> &v)
+{
+    vector<InstrumentPack> out;
+    for (int i = 0; i < (int)v.size(); ++i) {
+        InstrumentPack p(LiveAdapter::adapt(v[i]));
+        out.push_back(p);
+    }
+    return out;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/LiveInstruments.h	Tue Apr 28 11:24:23 2015 +0100
@@ -0,0 +1,32 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+  Silvet
+
+  A Vamp plugin for note transcription.
+  Centre for Digital Music, Queen Mary University of London.
+    
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 2 of the
+  License, or (at your option) any later version.  See the file
+  COPYING included with this distribution for more information.
+*/
+
+#ifndef SILVET_LIVE_INSTRUMENTS_H
+#define SILVET_LIVE_INSTRUMENTS_H
+
+#include "Instruments.h"
+
+/**
+ * Adapt an instrument pack into a "live" version, with fewer bins per
+ * octave and so lower CQ latency.
+ */
+class LiveAdapter
+{
+public:
+    static InstrumentPack adapt(const InstrumentPack &original);
+    static std::vector<InstrumentPack> adaptAll(const std::vector<InstrumentPack> &);
+};
+
+#endif
--- a/src/Silvet.cpp	Tue Apr 28 11:09:31 2015 +0100
+++ b/src/Silvet.cpp	Tue Apr 28 11:24:23 2015 +0100
@@ -21,6 +21,7 @@
 #include "MedianFilter.h"
 #include "constant-q-cpp/src/dsp/Resampler.h"
 #include "flattendynamics-ladspa.h"
+#include "LiveInstruments.h"
 
 #include <vector>
 #include <future>
@@ -37,7 +38,9 @@
 using Vamp::RealTime;
 
 static int processingSampleRate = 44100;
-static int processingBPO = 60;
+
+static int binsPerSemitoneLive = 1;
+static int binsPerSemitoneNormal = 5;
 
 static int minInputSampleRate = 100;
 static int maxInputSampleRate = 192000;
@@ -45,10 +48,11 @@
 Silvet::Silvet(float inputSampleRate) :
     Plugin(inputSampleRate),
     m_instruments(InstrumentPack::listInstrumentPacks()),
+    m_liveInstruments(LiveAdapter::adaptAll(m_instruments)),
     m_resampler(0),
     m_flattener(0),
     m_cq(0),
-    m_hqMode(true),
+    m_mode(HighQualityMode),
     m_fineTuning(false),
     m_instrument(0),
     m_colsPerSec(50),
@@ -141,14 +145,15 @@
     desc.identifier = "mode";
     desc.name = "Processing mode";
     desc.unit = "";
-    desc.description = "Sets the tradeoff of processing speed against transcription quality. Draft mode modifies a number of internal parameters in favour of speed. Intensive mode (the default) will almost always produce better results.";
+    desc.description = "Sets the tradeoff of processing speed against transcription quality. Draft mode is tuned in favour of overall speed; Live mode is tuned in favour of lower latency; while Intensive mode (the default) will almost always produce the best results.";
     desc.minValue = 0;
-    desc.maxValue = 1;
+    desc.maxValue = 2;
     desc.defaultValue = 1;
     desc.isQuantized = true;
     desc.quantizeStep = 1;
     desc.valueNames.push_back("Draft (faster)"); 
     desc.valueNames.push_back("Intensive (higher quality)");
+    desc.valueNames.push_back("Live (lower latency)");
     list.push_back(desc);
 
     desc.identifier = "instrument";
@@ -185,7 +190,7 @@
 Silvet::getParameter(string identifier) const
 {
     if (identifier == "mode") {
-        return m_hqMode ? 1.f : 0.f;
+        return (float)(int)m_mode;
     } else if (identifier == "finetune") {
         return m_fineTuning ? 1.f : 0.f;
     } else if (identifier == "instrument") {
@@ -198,7 +203,7 @@
 Silvet::setParameter(string identifier, float value) 
 {
     if (identifier == "mode") {
-        m_hqMode = (value > 0.5);
+        m_mode = (ProcessingMode)(int)(value + 0.5);
     } else if (identifier == "finetune") {
         m_fineTuning = (value > 0.5);
     } else if (identifier == "instrument") {
@@ -251,18 +256,18 @@
     d.description = "Filtered constant-Q time-frequency distribution as used as input to the expectation-maximisation algorithm.";
     d.unit = "";
     d.hasFixedBinCount = true;
-    d.binCount = m_instruments[0].templateHeight;
+    d.binCount = getPack(0).templateHeight;
     d.binNames.clear();
     if (m_cq) {
         char name[50];
-        for (int i = 0; i < m_instruments[0].templateHeight; ++i) {
+        for (int i = 0; i < getPack(0).templateHeight; ++i) {
             // We have a 600-bin (10 oct 60-bin CQ) of which the
             // lowest-frequency 55 bins have been dropped, for a
             // 545-bin template. The native CQ bins go high->low
             // frequency though, so these are still the first 545 bins
             // as reported by getBinFrequency, though in reverse order
             float freq = m_cq->getBinFrequency
-                (m_instruments[0].templateHeight - i - 1);
+                (getPack(0).templateHeight - i - 1);
             sprintf(name, "%.1f Hz", freq);
             d.binNames.push_back(name);
         }
@@ -280,10 +285,10 @@
     d.description = "Pitch activation distribution resulting from expectation-maximisation algorithm, prior to note extraction.";
     d.unit = "";
     d.hasFixedBinCount = true;
-    d.binCount = m_instruments[0].templateNoteCount;
+    d.binCount = getPack(0).templateNoteCount;
     d.binNames.clear();
     if (m_cq) {
-        for (int i = 0; i < m_instruments[0].templateNoteCount; ++i) {
+        for (int i = 0; i < getPack(0).templateNoteCount; ++i) {
             d.binNames.push_back(noteName(i, 0, 1));
         }
     }
@@ -315,6 +320,35 @@
     m_chromaOutputNo = list.size();
     list.push_back(d);
 
+    d.identifier = "templates";
+    d.name = "Templates";
+    d.description = "Constant-Q spectral templates for the selected instrument pack.";
+    d.unit = "";
+    d.hasFixedBinCount = true;
+    d.binCount = getPack(0).templateHeight;
+    d.binNames.clear();
+    if (m_cq) {
+        char name[50];
+        for (int i = 0; i < getPack(0).templateHeight; ++i) {
+            // We have a 600-bin (10 oct 60-bin CQ) of which the
+            // lowest-frequency 55 bins have been dropped, for a
+            // 545-bin template. The native CQ bins go high->low
+            // frequency though, so these are still the first 545 bins
+            // as reported by getBinFrequency, though in reverse order
+            float freq = m_cq->getBinFrequency
+                (getPack(0).templateHeight - i - 1);
+            sprintf(name, "%.1f Hz", freq);
+            d.binNames.push_back(name);
+        }
+    }
+    d.hasKnownExtents = false;
+    d.isQuantized = false;
+    d.sampleType = OutputDescriptor::FixedSampleRate;
+    d.sampleRate = m_colsPerSec;
+    d.hasDuration = false;
+    m_templateOutputNo = list.size();
+    list.push_back(d);
+
     return list;
 }
 
@@ -377,7 +411,12 @@
             float((shiftCount - shift) - int(shiftCount / 2) - 1) / shiftCount;
     }
 
-    return float(27.5 * pow(2.0, (note + pshift) / 12.0));
+    float freq = float(27.5 * pow(2.0, (note + pshift) / 12.0));
+
+//    cerr << "note = " << note << ", shift = " << shift << ", shiftCount = "
+//         << shiftCount << ", obtained freq = " << freq << endl;
+    
+    return freq;
 }
 
 bool
@@ -428,18 +467,32 @@
     m_flattener = new FlattenDynamics(m_inputSampleRate); // before resampling
     m_flattener->reset();
 
+    // this happens to be processingSampleRate / 3, and is the top
+    // freq used for the EM templates:
+    double maxFreq = 14700;
+
+    if (m_mode == LiveMode) {
+        // We only have 12 bpo rather than 60, so we need the top bin
+        // to be the middle one of the top 5, i.e. 2/5 of a semitone
+        // lower than 14700
+        maxFreq *= powf(2.0, -1.0 / 30.0);
+    }
+    
     double minFreq = 27.5;
 
-    if (!m_hqMode) {
+    if (m_mode != HighQualityMode) {
         // We don't actually return any notes from the bottom octave,
         // so we can just pad with zeros
         minFreq *= 2;
     }
 
+    int bpo = 12 *
+        (m_mode == LiveMode ? binsPerSemitoneLive : binsPerSemitoneNormal);
+
     CQParameters params(processingSampleRate,
                         minFreq, 
-                        processingSampleRate / 3,
-                        processingBPO);
+                        maxFreq,
+                        bpo);
 
     params.q = 0.95; // MIREX code uses 0.8, but it seems 0.9 or lower
                      // drops the FFT size to 512 from 1024 and alters
@@ -452,14 +505,18 @@
 
     m_cq = new CQSpectrogram(params, CQSpectrogram::InterpolateLinear);
 
-    m_colsPerSec = m_hqMode ? 50 : 25;
+//    cerr << "CQ bins = " << m_cq->getTotalBins() << endl;
+//    cerr << "CQ min freq = " << m_cq->getMinFrequency() << " (and for confirmation, freq of bin 0 = " << m_cq->getBinFrequency(0) << ")" << endl;
+    
+    m_colsPerSec = (m_mode == DraftMode ? 25 : 50);
 
     for (int i = 0; i < (int)m_postFilter.size(); ++i) {
         delete m_postFilter[i];
     }
     m_postFilter.clear();
-    for (int i = 0; i < m_instruments[0].templateNoteCount; ++i) {
-        m_postFilter.push_back(new MedianFilter<double>(3));
+    int postFilterLength = 3;
+    for (int i = 0; i < getPack(0).templateNoteCount; ++i) {
+        m_postFilter.push_back(new MedianFilter<double>(postFilterLength));
     }
     m_pianoRoll.clear();
     m_inputGains.clear();
@@ -472,9 +529,14 @@
 Silvet::FeatureSet
 Silvet::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
 {
+    FeatureSet fs;
+    
     if (!m_haveStartTime) {
+
         m_startTime = timestamp;
         m_haveStartTime = true;
+
+        insertTemplateFeatures(fs);
     }
 
     vector<float> flattened(m_blockSize);
@@ -507,7 +569,7 @@
         if (hadCount < resamplerLatency) {
             int stillToDrop = resamplerLatency - hadCount;
             if (stillToDrop >= int(data.size())) {
-                return FeatureSet();
+                return fs;
             } else {
                 data = vector<double>(data.begin() + stillToDrop, data.end());
             }
@@ -515,7 +577,7 @@
     }
 
     Grid cqout = m_cq->process(data);
-    FeatureSet fs = transcribe(cqout);
+    transcribe(cqout, fs);
     return fs;
 }
 
@@ -523,20 +585,42 @@
 Silvet::getRemainingFeatures()
 {
     Grid cqout = m_cq->getRemainingOutput();
-    FeatureSet fs = transcribe(cqout);
+    FeatureSet fs;
+    if (m_columnCount == 0) {
+        // process() was never called, but we still want these
+        insertTemplateFeatures(fs);
+    } else {
+        transcribe(cqout, fs);
+    }
     return fs;
 }
 
-Silvet::FeatureSet
-Silvet::transcribe(const Grid &cqout)
+void
+Silvet::insertTemplateFeatures(FeatureSet &fs)
+{
+    const InstrumentPack &pack = getPack(m_instrument);
+    for (int i = 0; i < int(pack.templates.size()) * pack.templateNoteCount; ++i) {
+        RealTime timestamp = RealTime::fromSeconds(double(i) / m_colsPerSec);
+        Feature f;
+        char buffer[50];
+        sprintf(buffer, "Note %d", i + 1);
+        f.label = buffer;
+        f.hasTimestamp = true;
+        f.timestamp = timestamp;
+        f.values = pack.templates[i / pack.templateNoteCount]
+            .data[i % pack.templateNoteCount];
+        fs[m_templateOutputNo].push_back(f);
+    }
+}        
+
+void
+Silvet::transcribe(const Grid &cqout, Silvet::FeatureSet &fs)
 {
     Grid filtered = preProcess(cqout);
 
-    FeatureSet fs;
-
-    if (filtered.empty()) return fs;
+    if (filtered.empty()) return;
     
-    const InstrumentPack &pack = m_instruments[m_instrument];
+    const InstrumentPack &pack(getPack(m_instrument));
 
     for (int i = 0; i < (int)filtered.size(); ++i) {
         Feature f;
@@ -550,7 +634,7 @@
 
     Grid localPitches(width);
 
-    bool wantShifts = m_hqMode && m_fineTuning;
+    bool wantShifts = (m_mode == HighQualityMode) && m_fineTuning;
     int shiftCount = 1;
     if (wantShifts) {
         shiftCount = pack.templateMaxShift * 2 + 1;
@@ -590,7 +674,7 @@
         if (wantShifts) localBestShifts[i] = out.second;
     }
 #endif
-    
+        
     for (int i = 0; i < width; ++i) {
 
         // This returns a filtered column, and pushes the
@@ -623,8 +707,6 @@
             fs[m_notesOutputNo].push_back(*fi);
         }
     }
-
-    return fs;
 }
 
 pair<vector<double>, vector<int> >
@@ -634,6 +716,10 @@
 {
     double columnThreshold = 1e-5;
     
+    if (m_mode == LiveMode) {
+        columnThreshold /= 20;
+    }
+    
     vector<double> pitches(pack.templateNoteCount, 0.0);
     vector<int> bestShifts;
     
@@ -643,12 +729,12 @@
     }
     if (sum < columnThreshold) return { pitches, bestShifts };
 
-    EM em(&pack, m_hqMode);
+    EM em(&pack, m_mode == HighQualityMode);
 
     em.setPitchSparsity(pack.pitchSparsity);
     em.setSourceSparsity(pack.sourceSparsity);
 
-    int iterations = m_hqMode ? 20 : 10;
+    int iterations = (m_mode == HighQualityMode ? 20 : 10);
 
     for (int j = 0; j < iterations; ++j) {
         em.iterate(column.data());
@@ -702,7 +788,7 @@
     // size we reduce to in a moment
     int latentColumns = m_cq->getLatency() / m_cq->getColumnHop();
 
-    const InstrumentPack &pack = m_instruments[m_instrument];
+    const InstrumentPack &pack(getPack(m_instrument));
 
     for (int i = 0; i < width; ++i) {
 
@@ -721,39 +807,42 @@
             vector<double> outCol(pack.templateHeight);
 
             // In HQ mode, the CQ returns 600 bins and we ignore the
-            // lowest 55 of them.
+            // lowest 55 of them (assuming binsPerSemitone == 5).
             // 
-            // In draft mode the CQ is an octave shorter, returning
-            // 540 bins, so we instead pad them with an additional 5
-            // zeros.
+            // In draft and live mode the CQ is an octave shorter,
+            // returning 540 bins or equivalent, so we instead pad
+            // them with an additional 5 or equivalent zeros.
             // 
             // We also need to reverse the column as we go, since the
             // raw CQ has the high frequencies first and we need it
             // the other way around.
 
-            if (m_hqMode) {
+            int bps = (m_mode == LiveMode ?
+                       binsPerSemitoneLive : binsPerSemitoneNormal);
+            
+            if (m_mode == HighQualityMode) {
                 for (int j = 0; j < pack.templateHeight; ++j) {
-                    int ix = inCol.size() - j - 55;
+                    int ix = inCol.size() - j - (11 * bps);
                     outCol[j] = inCol[ix];
                 }
             } else {
-                for (int j = 0; j < 5; ++j) {
+                for (int j = 0; j < bps; ++j) {
                     outCol[j] = 0.0;
                 }
-                for (int j = 5; j < pack.templateHeight; ++j) {
-                    int ix = inCol.size() - j + 4;
+                for (int j = bps; j < pack.templateHeight; ++j) {
+                    int ix = inCol.size() - j + (bps-1);
                     outCol[j] = inCol[ix];
                 }
             }
 
             vector<double> noiseLevel1 = 
-                MedianFilter<double>::filter(40, outCol);
+                MedianFilter<double>::filter(8 * bps, outCol);
             for (int j = 0; j < pack.templateHeight; ++j) {
                 noiseLevel1[j] = std::min(outCol[j], noiseLevel1[j]);
             }
 
             vector<double> noiseLevel2 = 
-                MedianFilter<double>::filter(40, noiseLevel1);
+                MedianFilter<double>::filter(8 * bps, noiseLevel1);
             for (int j = 0; j < pack.templateHeight; ++j) {
                 outCol[j] = std::max(outCol[j] - noiseLevel2[j], 0.0);
             }
@@ -772,7 +861,7 @@
                     const vector<int> &bestShifts,
                     bool wantShifts)
 {
-    const InstrumentPack &pack = m_instruments[m_instrument];
+    const InstrumentPack &pack(getPack(m_instrument));
 
     vector<double> filtered;
 
@@ -921,7 +1010,12 @@
             }
         }
 
-        int v = round(strength * 2);
+        int v;
+        if (m_mode == LiveMode) {
+            v = round(strength * 30);
+        } else {
+            v = round(strength * 2);
+        }
         if (v > partVelocity) {
             partVelocity = v;
         }
--- a/src/Silvet.h	Tue Apr 28 11:09:31 2015 +0100
+++ b/src/Silvet.h	Tue Apr 28 11:24:23 2015 +0100
@@ -73,12 +73,27 @@
 
 protected:
     const std::vector<InstrumentPack> m_instruments;
+    const std::vector<InstrumentPack> m_liveInstruments;
 
+    const InstrumentPack &getPack(int instrument) const {
+        if (m_mode == LiveMode) {
+            return m_liveInstruments[instrument];
+        } else {
+            return m_instruments[instrument];
+        }
+    }
+    
     Resampler *m_resampler;
     FlattenDynamics *m_flattener;
     CQSpectrogram *m_cq;
 
-    bool m_hqMode;
+    enum ProcessingMode { // ordered so draft==0 and hq==1 as in prior releases
+        DraftMode = 0,
+        HighQualityMode = 1,
+        LiveMode = 2,
+    };
+    ProcessingMode m_mode;
+    
     bool m_fineTuning;
     int m_instrument;
     int m_colsPerSec;
@@ -112,7 +127,9 @@
 
     float getInputGainAt(Vamp::RealTime t);
 
-    FeatureSet transcribe(const Grid &);
+    void insertTemplateFeatures(FeatureSet &);
+    
+    void transcribe(const Grid &, FeatureSet &);
 
     string chromaName(int n) const;
     string noteName(int n, int shift, int shiftCount) const;
@@ -127,6 +144,7 @@
     mutable int m_notesOutputNo;
     mutable int m_fcqOutputNo;
     mutable int m_pitchOutputNo;
+    mutable int m_templateOutputNo;
     mutable int m_chromaOutputNo;
 };
 
--- a/testdata/evaluation/run.sh	Tue Apr 28 11:09:31 2015 +0100
+++ b/testdata/evaluation/run.sh	Tue Apr 28 11:24:23 2015 +0100
@@ -79,6 +79,8 @@
     
     piece=`basename \`dirname "$infile" \``
     arrangement=`basename "$infile" .wav`
+
+    # Change this to the processing mode you want to test (e.g. 0 for draft)
     mode=1
 
     echo