changeset 75:f4fb0ac6120a

Interpolation for CQ. But this is wrong, it is interpolating between points in the nearest full-height columns even where there is a nearer point in the not-full-height ones
author Chris Cannam <c.cannam@qmul.ac.uk>
date Fri, 04 Apr 2014 11:49:53 +0100
parents c2e9c91ed3f7
children 5d3ab532d8af
files .hgignore Makefile.inc Makefile.linux cpp-qm-dsp/CQInterpolated.cpp cpp-qm-dsp/CQInterpolated.h cpp-qm-dsp/ConstantQ.cpp vamp/CQVamp.cpp vamp/CQVamp.h
diffstat 8 files changed, 354 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Mon Mar 24 16:30:40 2014 +0000
+++ b/.hgignore	Fri Apr 04 11:49:53 2014 +0100
@@ -3,3 +3,4 @@
 *.o
 *.so
 cpp-qm-dsp/test
+*.class
--- a/Makefile.inc	Mon Mar 24 16:30:40 2014 +0000
+++ b/Makefile.inc	Fri Apr 04 11:49:53 2014 +0100
@@ -20,8 +20,8 @@
 PLUGIN	:= cqvamp$(PLUGIN_EXT)
 TEST	:= $(LIB_DIR)/test
 
-LIB_HEADERS := $(LIB_DIR)/CQKernel.h $(LIB_DIR)/ConstantQ.h
-LIB_SOURCES := $(LIB_DIR)/CQKernel.cpp $(LIB_DIR)/ConstantQ.cpp
+LIB_HEADERS := $(LIB_DIR)/CQKernel.h $(LIB_DIR)/ConstantQ.h $(LIB_DIR)/CQInterpolated.h
+LIB_SOURCES := $(LIB_DIR)/CQKernel.cpp $(LIB_DIR)/ConstantQ.cpp $(LIB_DIR)/CQInterpolated.cpp
 
 VAMP_HEADERS := $(VAMP_DIR)/CQVamp.h
 VAMP_SOURCES := $(VAMP_DIR)/CQVamp.cpp $(VAMP_DIR)/libmain.cpp
@@ -56,6 +56,11 @@
 
 cpp-qm-dsp/CQKernel.o: cpp-qm-dsp/CQKernel.h
 cpp-qm-dsp/ConstantQ.o: cpp-qm-dsp/ConstantQ.h cpp-qm-dsp/CQKernel.h
-vamp/CQVamp.o: vamp/CQVamp.h cpp-qm-dsp/ConstantQ.h cpp-qm-dsp/CQKernel.h
-vamp/libmain.o: vamp/CQVamp.h
+vamp/CQVamp.o: vamp/CQVamp.h cpp-qm-dsp/CQInterpolated.h
+vamp/CQVamp.o: cpp-qm-dsp/ConstantQ.h cpp-qm-dsp/CQKernel.h
+vamp/libmain.o: vamp/CQVamp.h cpp-qm-dsp/CQInterpolated.h
+vamp/libmain.o: cpp-qm-dsp/ConstantQ.h cpp-qm-dsp/CQKernel.h
 cpp-qm-dsp/ConstantQ.o: cpp-qm-dsp/CQKernel.h
+cpp-qm-dsp/CQInterpolated.o: cpp-qm-dsp/ConstantQ.h cpp-qm-dsp/CQKernel.h
+vamp/CQVamp.o: cpp-qm-dsp/CQInterpolated.h cpp-qm-dsp/ConstantQ.h
+vamp/CQVamp.o: cpp-qm-dsp/CQKernel.h
--- a/Makefile.linux	Mon Mar 24 16:30:40 2014 +0000
+++ b/Makefile.linux	Fri Apr 04 11:49:53 2014 +0100
@@ -1,6 +1,6 @@
 
-CFLAGS := -Wall -O3 -fPIC -I../vamp-plugin-sdk/
-#CFLAGS := -g -fPIC -I../vamp-plugin-sdk
+#CFLAGS := -Wall -O3 -fPIC -I../vamp-plugin-sdk/
+CFLAGS := -g -fPIC -I../vamp-plugin-sdk
 
 CXXFLAGS := $(CFLAGS)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cpp-qm-dsp/CQInterpolated.cpp	Fri Apr 04 11:49:53 2014 +0100
@@ -0,0 +1,215 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    Constant-Q library
+    Copyright (c) 2013-2014 Queen Mary, University of London
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+    Except as contained in this notice, the names of the Centre for
+    Digital Music; Queen Mary, University of London; and Chris Cannam
+    shall not be used in advertising or otherwise to promote the sale,
+    use or other dealings in this Software without prior written
+    authorization.
+*/
+
+#include "CQInterpolated.h"
+
+#include <iostream>
+
+using std::vector;
+
+using std::cerr;
+using std::endl;
+
+CQInterpolated::CQInterpolated(double sampleRate,
+			       double minFreq, double maxFreq,
+			       int binsPerOctave,
+			       Interpolation interpolation) :
+    m_cq(sampleRate, minFreq, maxFreq, binsPerOctave),
+    m_interpolation(interpolation)
+{
+}
+
+CQInterpolated::~CQInterpolated()
+{
+}
+
+vector<vector<double> >
+CQInterpolated::process(const vector<double> &td)
+{
+    return postProcess(m_cq.process(td), false);
+}
+
+vector<vector<double> >
+CQInterpolated::getRemainingBlocks()
+{
+    return postProcess(m_cq.getRemainingBlocks(), true);
+}
+
+vector<vector<double> >
+CQInterpolated::postProcess(vector<vector<double> > cq, bool insist)
+{
+    if (m_interpolation == None) {
+	return cq;
+    }
+
+    int width = cq.size();
+
+    for (int i = 0; i < width; ++i) {
+	m_buffer.push_back(cq[i]);
+    }
+    
+    if (m_interpolation == Hold) {
+	return fetchHold(insist);
+    } else {
+	return fetchLinear(insist);
+    }
+}
+
+vector<vector<double> >
+CQInterpolated::fetchHold(bool)
+{
+    Grid out;
+    
+    int width = m_buffer.size();
+    int height = getTotalBins();
+
+    for (int i = 0; i < width; ++i) {
+	
+	vector<double> col = m_buffer[i];
+
+	int thisHeight = col.size();
+	int prevHeight = m_prevColumn.size();
+
+	for (int j = thisHeight; j < height; ++j) {
+	    if (j < prevHeight) {
+		col.push_back(m_prevColumn[j]);
+	    } else {
+		col.push_back(0.0);
+	    }
+	}
+
+	m_prevColumn = col;
+	out.push_back(col);
+    }
+
+    m_buffer.clear();
+
+    return out;
+}
+
+vector<vector<double> >
+CQInterpolated::fetchLinear(bool insist)
+{
+    Grid out;
+
+    //!!! This is surprisingly messy. I must be missing something.
+
+    // We can only return any data when we have at least one column
+    // that has the full height in the buffer, that is not the first
+    // column.
+    //
+    // If the first col has full height, and there is another one
+    // later that also does, then we can interpolate between those, up
+    // to but not including the second full height column.  Then we
+    // drop and return the columns we interpolated, leaving the second
+    // full-height col as the first col in the buffer. And repeat as
+    // long as enough columns are available.
+    //
+    // If the first col does not have full height, then (so long as
+    // we're following the logic above) we must simply have not yet
+    // reached the first full-height column in the CQ output, and we
+    // can interpolate nothing.
+    
+    int width = m_buffer.size();
+    int height = getTotalBins();
+
+    if (width == 0) return out;
+    
+    int firstFullHeight = -1;
+    int secondFullHeight = -1;
+
+    for (int i = 0; i < width; ++i) {
+	if (m_buffer[i].size() == height) {
+	    if (firstFullHeight == -1) {
+		firstFullHeight = i;
+	    } else if (secondFullHeight == -1) {
+		secondFullHeight = i;
+		break;
+	    }
+	}
+    }
+
+    if (firstFullHeight < 0) {
+	if (insist) {
+	    out = m_buffer;
+	    m_buffer.clear();
+	    return out;
+	} else {
+	    return out;
+	}
+    } else if (firstFullHeight > 0) {
+	// can interpolate nothing, stash up to first full height & recurse
+	out = Grid(m_buffer.begin(), m_buffer.begin() + firstFullHeight);
+	m_buffer = Grid(m_buffer.begin() + firstFullHeight, m_buffer.end());
+	Grid more = fetchLinear(insist);
+	out.insert(out.end(), more.begin(), more.end());
+	return out;
+    } else if (secondFullHeight < 0) {
+	// firstFullHeight == 0, but there is no second full height --
+	// wait for it unless insist flag is set
+	if (insist) {
+	    out = m_buffer;
+	    m_buffer.clear();
+	    return out;
+	} else {
+	    return out;
+	}
+    } else {
+	// firstFullHeight == 0 and secondFullHeight also valid. Can interpolate
+
+	out.push_back(m_buffer[0]);
+
+	for (int i = 1; i < secondFullHeight; ++i) {
+
+	    vector<double> col = m_buffer[i];
+	    int thisHeight = col.size();
+
+	    double proportion = double(i) / double(secondFullHeight);
+
+	    cerr << "secondFullHeight = " << secondFullHeight << " proportion = " << proportion << " ";
+
+	    for (int j = thisHeight; j < height; ++j) {
+		col.push_back((1.0 - proportion) * m_buffer[0][j]
+			      + proportion * m_buffer[secondFullHeight][j]);
+	    }
+
+	    out.push_back(col);
+	}
+
+	m_buffer = Grid(m_buffer.begin() + secondFullHeight, m_buffer.end());
+	Grid more = fetchLinear(insist);
+	out.insert(out.end(), more.begin(), more.end());
+	return out;
+    }
+}
+
+
+	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cpp-qm-dsp/CQInterpolated.h	Fri Apr 04 11:49:53 2014 +0100
@@ -0,0 +1,78 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    Constant-Q library
+    Copyright (c) 2013-2014 Queen Mary, University of London
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+    Except as contained in this notice, the names of the Centre for
+    Digital Music; Queen Mary, University of London; and Chris Cannam
+    shall not be used in advertising or otherwise to promote the sale,
+    use or other dealings in this Software without prior written
+    authorization.
+*/
+
+#ifndef CQ_INTERPOLATED_H
+#define CQ_INTERPOLATED_H
+
+#include <vector>
+#include "ConstantQ.h"
+
+class CQInterpolated
+{
+public:
+    enum Interpolation {
+	None, // leave empty cells empty
+	Hold, // repeat prior cell
+	Linear, // linear interpolation between consecutive time cells
+    };
+
+    CQInterpolated(double sampleRate,
+		   double minFreq, double maxFreq,
+		   int binsPerOctave,
+		   Interpolation interpolation);
+    ~CQInterpolated();
+
+    double getSampleRate() const { return m_cq.getSampleRate(); }
+    int getBinsPerOctave() const { return m_cq.getBinsPerOctave(); }
+    int getOctaves() const { return m_cq.getOctaves(); }
+    int getTotalBins() const { return m_cq.getTotalBins(); }
+    int getColumnHop() const { return m_cq.getColumnHop(); }
+    int getLatency() const { return m_cq.getLatency(); } 
+    double getMaxFrequency() const { return m_cq.getMaxFrequency(); }
+    double getMinFrequency() const { return m_cq.getMinFrequency(); }
+    double getBinFrequency(int bin) const { return m_cq.getBinFrequency(bin); }
+
+    std::vector<std::vector<double> > process(const std::vector<double> &);
+    std::vector<std::vector<double> > getRemainingBlocks();
+
+private:
+    ConstantQ m_cq;
+    Interpolation m_interpolation;
+
+    typedef std::vector<std::vector<double> > Grid;
+    Grid m_buffer;
+    Grid postProcess(Grid, bool insist);
+    Grid fetchHold(bool insist);
+    Grid fetchLinear(bool insist);
+    std::vector<double> m_prevColumn;
+};
+
+#endif
--- a/cpp-qm-dsp/ConstantQ.cpp	Mon Mar 24 16:30:40 2014 +0000
+++ b/cpp-qm-dsp/ConstantQ.cpp	Fri Apr 04 11:49:53 2014 +0100
@@ -247,7 +247,7 @@
         int base = out.size();
         int totalColumns = pow(2, m_octaves - 1) * m_p.atomsPerFrame;
         for (int i = 0; i < totalColumns; ++i) {
-            out.push_back(vector<double>(m_p.binsPerOctave * m_octaves, 0.0));
+            out.push_back(vector<double>());
         }
 
         for (int octave = 0; octave < m_octaves; ++octave) {
@@ -259,18 +259,20 @@
                 
                 for (int j = 0; j < m_p.atomsPerFrame; ++j) {
 
-		    for (int k = 0; k < pow(2, octave); ++k) {
-
-			int target = base + k +
+                    int target = base +
 			    (b * (totalColumns / blocksThisOctave) + 
 			     (j * ((totalColumns / blocksThisOctave) /
 				   m_p.atomsPerFrame)));
 
-			for (int i = 0; i < m_p.binsPerOctave; ++i) {
-			    out[target][m_p.binsPerOctave * octave + i] = 
-				block[j][m_p.binsPerOctave - i - 1];
-			}
-		    }
+                    while (out[target].size() < 
+                           m_p.binsPerOctave * (octave + 1)) {
+                        out[target].push_back(0.0);
+                    }
+                    
+                    for (int i = 0; i < m_p.binsPerOctave; ++i) {
+                        out[target][m_p.binsPerOctave * octave + i] = 
+                            block[j][m_p.binsPerOctave - i - 1];
+                    }
                 }
             }
         }
--- a/vamp/CQVamp.cpp	Mon Mar 24 16:30:40 2014 +0000
+++ b/vamp/CQVamp.cpp	Fri Apr 04 11:49:53 2014 +0100
@@ -31,8 +31,6 @@
 
 #include "CQVamp.h"
 
-#include "cpp-qm-dsp/ConstantQ.h"
-
 #include "base/Pitch.h"
 
 #include <algorithm>
@@ -49,6 +47,7 @@
     m_maxMIDIPitch(84),
     m_tuningFrequency(440),
     m_bpo(24),
+    m_interpolation(CQInterpolated::Linear),
     m_cq(0),
     m_maxFrequency(inputSampleRate/2),
     m_minFrequency(46),
@@ -147,6 +146,20 @@
     desc.quantizeStep = 1;
     list.push_back(desc);
 
+    desc.identifier = "interpolation";
+    desc.name = "Interpolation";
+    desc.unit = "";
+    desc.description = "Interpolation method used to fill empty cells in lower octaves";
+    desc.minValue = 0;
+    desc.maxValue = 2;
+    desc.defaultValue = 2;
+    desc.isQuantized = true;
+    desc.quantizeStep = 1;
+    desc.valueNames.push_back("None, leave empty");
+    desc.valueNames.push_back("None, repeat prior value");
+    desc.valueNames.push_back("Linear interpolation");
+    list.push_back(desc);
+
     return list;
 }
 
@@ -165,6 +178,9 @@
     if (param == "bpo") {
         return m_bpo;
     }
+    if (param == "interpolation") {
+        return (float)m_interpolation;
+    }
     std::cerr << "WARNING: CQVamp::getParameter: unknown parameter \""
               << param << "\"" << std::endl;
     return 0.0;
@@ -179,8 +195,10 @@
         m_maxMIDIPitch = lrintf(value);
     } else if (param == "tuning") {
         m_tuningFrequency = value;
-    } else  if (param == "bpo") {
+    } else if (param == "bpo") {
         m_bpo = lrintf(value);
+    } else if (param == "interpolation") {
+        m_interpolation = (CQInterpolated::Interpolation)lrintf(value);
     } else {
         std::cerr << "WARNING: CQVamp::setParameter: unknown parameter \""
                   << param << "\"" << std::endl;
@@ -192,7 +210,7 @@
 {
     if (m_cq) {
 	delete m_cq;
-	m_cq = 0;
+        m_cq = 0;
     }
 
     if (channels < getMinChannelCount() ||
@@ -206,8 +224,9 @@
     m_maxFrequency = Pitch::getFrequencyForPitch
         (m_maxMIDIPitch, 0, m_tuningFrequency);
 
-    m_cq = new ConstantQ
-	(m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo);
+    m_cq = new CQInterpolated
+	(m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo,
+         m_interpolation);
 
     return true;
 }
@@ -217,8 +236,9 @@
 {
     if (m_cq) {
 	delete m_cq;
-	m_cq = new ConstantQ
-	    (m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo);
+	m_cq = new CQInterpolated
+	    (m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo,
+             m_interpolation);
     }
     m_prevFeature.clear();
     m_haveStartTime = false;
@@ -303,18 +323,16 @@
 {
     FeatureSet returnFeatures;
 
-    for (int i = 0; i < (int)cqout.size(); ++i) {
+    int width = cqout.size();
+    int height = m_cq->getTotalBins();
 
-	vector<float> column(m_cq->getTotalBins(), 0.f);
+    for (int i = 0; i < width; ++i) {
 
-	for (int j = 0; j < (int)cqout[i].size(); ++j) {
+	vector<float> column(height, 0.f);
+        int thisHeight = cqout[i].size();
+	for (int j = 0; j < thisHeight; ++j) {
 	    column[j] = cqout[i][j];
 	}
-	for (int j = cqout[i].size(); j < m_cq->getTotalBins(); ++j) {
-	    if (j < (int)m_prevFeature.size()) {
-		column[j] = m_prevFeature[j];
-	    }
-	}
 
         // put low frequencies at the start
         std::reverse(column.begin(), column.end());
--- a/vamp/CQVamp.h	Mon Mar 24 16:30:40 2014 +0000
+++ b/vamp/CQVamp.h	Fri Apr 04 11:49:53 2014 +0100
@@ -34,6 +34,8 @@
 
 #include <vamp-sdk/Plugin.h>
 
+#include "cpp-qm-dsp/CQInterpolated.h"
+
 class ConstantQ;
 
 class CQVamp : public Vamp::Plugin
@@ -73,8 +75,9 @@
     int m_maxMIDIPitch;
     float m_tuningFrequency;
     int m_bpo;
+    CQInterpolated::Interpolation m_interpolation;
 
-    ConstantQ *m_cq;
+    CQInterpolated *m_cq;
     float m_maxFrequency;
     float m_minFrequency;
     int m_stepSize;