changeset 32:da54468cc452

Filter the constant Q spectrogram in a similar manner to the matlab version
author Chris Cannam
date Fri, 04 Apr 2014 13:29:33 +0100
parents c6d230c31713
children e08c330a761d
files .hgsubstate Makefile.inc src/Silvet.cpp src/Silvet.h
diffstat 4 files changed, 162 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsubstate	Thu Apr 03 17:38:45 2014 +0100
+++ b/.hgsubstate	Fri Apr 04 13:29:33 2014 +0100
@@ -1,1 +1,1 @@
-320aa9b3a2de3501a8e999319ad49862354416a1 constant-q-cpp
+ac0617538cf86fe83ffb8e352d7d04a2d7b9bb99 constant-q-cpp
--- a/Makefile.inc	Thu Apr 03 17:38:45 2014 +0100
+++ b/Makefile.inc	Fri Apr 04 13:29:33 2014 +0100
@@ -22,8 +22,8 @@
 VAMP_HEADERS := $(SRC_DIR)/Silvet.h
 VAMP_SOURCES := $(SRC_DIR)/Silvet.cpp $(SRC_DIR)/libmain.cpp
 
-CQ_HEADERS   := $(CQ_DIR)/CQKernel.h $(CQ_DIR)/ConstantQ.h
-CQ_SOURCES   := $(CQ_DIR)/CQKernel.cpp $(CQ_DIR)/ConstantQ.cpp
+CQ_HEADERS   := $(CQ_DIR)/CQKernel.h $(CQ_DIR)/ConstantQ.h $(CQ_DIR)/CQInterpolated.h
+CQ_SOURCES   := $(CQ_DIR)/CQKernel.cpp $(CQ_DIR)/ConstantQ.cpp $(CQ_DIR)/CQInterpolated.cpp
 
 HEADERS	     := $(VAMP_HEADERS) $(CQ_HEADERS)
 SOURCES	     := $(VAMP_SOURCES) $(CQ_SOURCES)
@@ -47,3 +47,23 @@
 
 # DO NOT DELETE
 
+src/Silvet.o: src/Silvet.h data/include/templates.h data/include/bassoon.h
+src/Silvet.o: data/include/cello.h data/include/clarinet.h
+src/Silvet.o: data/include/flute.h data/include/guitar.h data/include/horn.h
+src/Silvet.o: data/include/oboe.h data/include/tenorsax.h
+src/Silvet.o: data/include/violin.h data/include/piano-maps-SptkBGCl.h
+src/Silvet.o: data/include/piano1.h data/include/piano2.h
+src/Silvet.o: data/include/piano3.h
+src/Silvet.o: constant-q-cpp/cpp-qm-dsp/CQInterpolated.h
+src/Silvet.o: constant-q-cpp/cpp-qm-dsp/ConstantQ.h
+src/Silvet.o: constant-q-cpp/cpp-qm-dsp/CQKernel.h
+src/libmain.o: src/Silvet.h
+constant-q-cpp/cpp-qm-dsp/CQKernel.o: constant-q-cpp/cpp-qm-dsp/CQKernel.h
+constant-q-cpp/cpp-qm-dsp/ConstantQ.o: constant-q-cpp/cpp-qm-dsp/ConstantQ.h
+constant-q-cpp/cpp-qm-dsp/ConstantQ.o: constant-q-cpp/cpp-qm-dsp/CQKernel.h
+constant-q-cpp/cpp-qm-dsp/CQInterpolated.o: constant-q-cpp/cpp-qm-dsp/CQInterpolated.h
+constant-q-cpp/cpp-qm-dsp/CQInterpolated.o: constant-q-cpp/cpp-qm-dsp/ConstantQ.h
+constant-q-cpp/cpp-qm-dsp/CQInterpolated.o: constant-q-cpp/cpp-qm-dsp/CQKernel.h
+constant-q-cpp/cpp-qm-dsp/ConstantQ.o: constant-q-cpp/cpp-qm-dsp/CQKernel.h
+constant-q-cpp/cpp-qm-dsp/CQInterpolated.o: constant-q-cpp/cpp-qm-dsp/ConstantQ.h
+constant-q-cpp/cpp-qm-dsp/CQInterpolated.o: constant-q-cpp/cpp-qm-dsp/CQKernel.h
--- a/src/Silvet.cpp	Thu Apr 03 17:38:45 2014 +0100
+++ b/src/Silvet.cpp	Fri Apr 04 13:29:33 2014 +0100
@@ -17,19 +17,22 @@
 
 #include "data/include/templates.h"
 
+#include "maths/MedianFilter.h"
 #include "dsp/rateconversion/Resampler.h"
 
-#include "constant-q-cpp/cpp-qm-dsp/ConstantQ.h"
+#include "constant-q-cpp/cpp-qm-dsp/CQInterpolated.h"
 
 #include <vector>
 
+#include <cstdio>
+
 using std::vector;
 using std::cerr;
 using std::endl;
 
 static int processingSampleRate = 44100;
 static int processingBPO = 60;
-
+static int processingHeight = 545;
 
 Silvet::Silvet(float inputSampleRate) :
     Plugin(inputSampleRate),
@@ -42,6 +45,10 @@
 {
     delete m_resampler;
     delete m_cq;
+    for (int i = 0; i < (int)m_filterA.size(); ++i) {
+        delete m_filterA[i];
+        delete m_filterB[i];
+    }
 }
 
 string
@@ -169,8 +176,32 @@
     d.hasKnownExtents = false;
     d.isQuantized = false;
     d.sampleType = OutputDescriptor::VariableSampleRate;
-    d.sampleRate = 0;
+    d.sampleRate = m_inputSampleRate / (m_cq ? m_cq->getColumnHop() : 256);
     d.hasDuration = true;
+    m_notesOutputNo = list.size();
+    list.push_back(d);
+
+    d.identifier = "inputgrid";
+    d.name = "Filtered time-frequency grid";
+    d.description = "The pre-processed constant-Q time-frequency distribution used as input to the PLCA step";
+    d.unit = "";
+    d.hasFixedBinCount = true;
+    d.binCount = processingHeight;
+    d.binNames.clear();
+    if (m_cq) {
+        char name[20];
+        for (int i = 0; i < processingHeight; ++i) {
+            float freq = m_cq->getBinFrequency(i + 55);
+            sprintf(name, "%.1f Hz", freq);
+            d.binNames.push_back(name);
+        }
+    }
+    d.hasKnownExtents = false;
+    d.isQuantized = false;
+    d.sampleType = OutputDescriptor::FixedSampleRate;
+    d.sampleRate = 25;
+    d.hasDuration = false;
+    m_cqOutputNo = list.size();
     list.push_back(d);
 
     return list;
@@ -207,9 +238,22 @@
 	m_resampler = 0;
     }
 
-    m_cq = new ConstantQ
-	(processingSampleRate, 27.5, processingSampleRate / 3, processingBPO);
+    m_cq = new CQInterpolated
+	(processingSampleRate, 27.5, processingSampleRate / 3, processingBPO,
+         CQInterpolated::Linear);
 
+    for (int i = 0; i < (int)m_filterA.size(); ++i) {
+        delete m_filterA[i];
+        delete m_filterB[i];
+    }
+    m_filterA.clear();
+    m_filterB.clear();
+    for (int i = 0; i < processingHeight; ++i) {
+        m_filterA.push_back(new MedianFilter<double>(40));
+        m_filterB.push_back(new MedianFilter<double>(40));
+    }
+    m_columnCount = 0;
+    m_reducedColumnCount = 0;
 }
 
 Silvet::FeatureSet
@@ -222,9 +266,20 @@
 	data = m_resampler->process(data.data(), data.size());
     }
 
-    vector<vector<double> > cqout = m_cq->process(data);
+    Grid cqout = m_cq->process(data);
+    Grid filtered = preProcess(cqout);
 
-    return FeatureSet();
+    FeatureSet fs;
+
+    for (int i = 0; i < (int)filtered.size(); ++i) {
+        Feature f;
+        for (int j = 0; j < processingHeight; ++j) {
+            f.values.push_back(float(filtered[i][j]));
+        }
+        fs[m_cqOutputNo].push_back(f);
+    }
+
+    return fs;
 }
 
 Silvet::FeatureSet
@@ -234,3 +289,61 @@
     return FeatureSet();
 }
 
+Silvet::Grid
+Silvet::preProcess(const Grid &in)
+{
+    int width = in.size();
+
+    // reduce to 100 columns per second, or one column every 441 samples
+
+    int spacing = processingSampleRate / 100;
+
+    Grid out;
+
+    for (int i = 0; i < width; ++i) {
+
+        int prevSampleNo = (m_columnCount - 1) * m_cq->getColumnHop();
+        int sampleNo = m_columnCount * m_cq->getColumnHop();
+
+        bool select = (sampleNo / spacing != prevSampleNo / spacing);
+
+        if (select) {
+            vector<double> inCol = in[i];
+            vector<double> outCol(processingHeight);
+
+            // we reverse the column as we go (the CQ output is
+            // "upside-down", with high frequencies at the start of
+            // each column, and we want it the other way around) and
+            // then ignore the first 55 (lowest-frequency) bins,
+            // giving us 545 bins instead of 600
+
+            for (int j = 0; j < processingHeight; ++j) {
+
+                int ix = inCol.size() - j - 55;
+
+                double val = inCol[ix];
+                m_filterA[j]->push(val);
+
+                double a = m_filterA[j]->get();
+                m_filterB[j]->push(std::min(a, val));
+
+                double filtered = m_filterB[j]->get();
+                outCol[j] = filtered;
+            }
+
+            // then we only use every fourth filtered column, for 25
+            // columns per second in the eventual grid
+
+            if (m_reducedColumnCount % 4 == 0) {
+                out.push_back(outCol);
+            }
+
+            ++m_reducedColumnCount;
+        }
+
+        ++m_columnCount;
+    }
+
+    return out;
+}
+    
--- a/src/Silvet.h	Thu Apr 03 17:38:45 2014 +0100
+++ b/src/Silvet.h	Fri Apr 04 13:29:33 2014 +0100
@@ -18,10 +18,16 @@
 
 #include <vamp-sdk/Plugin.h>
 
+#include <vector>
+#include <string>
+
+#include "maths/MedianFilter.h"
+
 using std::string;
+using std::vector;
 
 class Resampler;
-class ConstantQ;
+class CQInterpolated;
 
 class Silvet : public Vamp::Plugin
 {
@@ -62,9 +68,20 @@
 
 protected:
     Resampler *m_resampler;
-    ConstantQ *m_cq;
+    CQInterpolated *m_cq;
+
+    typedef vector<vector<double> > Grid;
+
+    vector<MedianFilter<double> *> m_filterA;
+    vector<MedianFilter<double> *> m_filterB;
+    Grid preProcess(const Grid &);
 
     int m_blockSize;
+    int m_columnCount;
+    int m_reducedColumnCount;
+
+    mutable int m_notesOutputNo;
+    mutable int m_cqOutputNo;
 };
 
 #endif