changeset 90:bfc7cf71f2ef

Rearrange classes so the basic ConstantQ produces a complex output, and CQSpectrogram (formerly CQInterpolated) is required if you want a real output
author Chris Cannam <c.cannam@qmul.ac.uk>
date Fri, 09 May 2014 10:05:16 +0100
parents 25947630486b
children 51f5f0deef2f
files Makefile.inc cpp-qm-dsp/CQBase.h cpp-qm-dsp/CQInterpolated.cpp cpp-qm-dsp/CQInterpolated.h cpp-qm-dsp/CQSpectrogram.cpp cpp-qm-dsp/CQSpectrogram.h cpp-qm-dsp/ConstantQ.cpp cpp-qm-dsp/ConstantQ.h cpp-qm-dsp/test.cpp vamp/CQVamp.cpp vamp/CQVamp.h
diffstat 11 files changed, 468 insertions(+), 382 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.inc	Fri May 09 08:25:24 2014 +0100
+++ b/Makefile.inc	Fri May 09 10:05:16 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_DIR)/CQInterpolated.h $(LIB_DIR)/CQInverse.h
-LIB_SOURCES := $(LIB_DIR)/CQKernel.cpp $(LIB_DIR)/ConstantQ.cpp $(LIB_DIR)/CQInterpolated.cpp $(LIB_DIR)/CQInverse.cpp
+LIB_HEADERS := $(LIB_DIR)/CQBase.h $(LIB_DIR)/CQKernel.h $(LIB_DIR)/ConstantQ.h $(LIB_DIR)/CQSpectrogram.h $(LIB_DIR)/CQInverse.h
+LIB_SOURCES := $(LIB_DIR)/CQKernel.cpp $(LIB_DIR)/ConstantQ.cpp $(LIB_DIR)/CQSpectrogram.cpp $(LIB_DIR)/CQInverse.cpp
 
 VAMP_HEADERS := $(VAMP_DIR)/CQVamp.h
 VAMP_SOURCES := $(VAMP_DIR)/CQVamp.cpp $(VAMP_DIR)/libmain.cpp
@@ -55,12 +55,20 @@
 # DO NOT DELETE
 
 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/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/ConstantQ.h cpp-qm-dsp/CQBase.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
+cpp-qm-dsp/CQSpectrogram.o: cpp-qm-dsp/CQSpectrogram.h cpp-qm-dsp/ConstantQ.h
+cpp-qm-dsp/CQSpectrogram.o: cpp-qm-dsp/CQBase.h cpp-qm-dsp/CQKernel.h
+cpp-qm-dsp/CQInverse.o: cpp-qm-dsp/CQInverse.h cpp-qm-dsp/CQKernel.h
+vamp/CQVamp.o: vamp/CQVamp.h cpp-qm-dsp/CQSpectrogram.h
+vamp/CQVamp.o: cpp-qm-dsp/ConstantQ.h cpp-qm-dsp/CQBase.h
 vamp/CQVamp.o: cpp-qm-dsp/CQKernel.h
+vamp/libmain.o: vamp/CQVamp.h cpp-qm-dsp/CQSpectrogram.h
+vamp/libmain.o: cpp-qm-dsp/ConstantQ.h cpp-qm-dsp/CQBase.h
+vamp/libmain.o: cpp-qm-dsp/CQKernel.h
+cpp-qm-dsp/ConstantQ.o: cpp-qm-dsp/CQBase.h cpp-qm-dsp/CQKernel.h
+cpp-qm-dsp/CQSpectrogram.o: cpp-qm-dsp/ConstantQ.h cpp-qm-dsp/CQBase.h
+cpp-qm-dsp/CQSpectrogram.o: cpp-qm-dsp/CQKernel.h
+cpp-qm-dsp/CQInverse.o: cpp-qm-dsp/CQKernel.h
+vamp/CQVamp.o: cpp-qm-dsp/CQSpectrogram.h cpp-qm-dsp/ConstantQ.h
+vamp/CQVamp.o: cpp-qm-dsp/CQBase.h cpp-qm-dsp/CQKernel.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cpp-qm-dsp/CQBase.h	Fri May 09 10:05:16 2014 +0100
@@ -0,0 +1,60 @@
+/* -*- 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 CQBASE_H
+#define CQBASE_H
+
+#include <vector>
+#include <complex>
+
+class CQBase // interface class
+{
+public:
+    typedef std::complex<double> Complex;
+    typedef std::vector<double> RealSequence;
+    typedef std::vector<double> RealColumn;
+    typedef std::vector<Complex> ComplexSequence;
+    typedef std::vector<Complex> ComplexColumn;
+    typedef std::vector<RealColumn> RealBlock;
+    typedef std::vector<ComplexColumn> ComplexBlock;
+
+    virtual double getSampleRate() const = 0;
+    virtual int getBinsPerOctave() const = 0;
+    virtual int getOctaves() const = 0; 
+    virtual int getTotalBins() const = 0;
+    virtual int getColumnHop() const = 0;
+    virtual int getLatency() const = 0;
+    virtual double getMaxFrequency() const = 0;
+    virtual double getMinFrequency() const = 0; // actual min, not that passed to ctor
+    virtual double getBinFrequency(int bin) const = 0;
+};
+
+#endif
--- a/cpp-qm-dsp/CQInterpolated.cpp	Fri May 09 08:25:24 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,247 +0,0 @@
-/* -*- 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>
-#include <stdexcept>
-
-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(const 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 ((int)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 = linearInterpolated(m_buffer, 0, secondFullHeight);
-	m_buffer = Grid(m_buffer.begin() + secondFullHeight, m_buffer.end());
-	Grid more = fetchLinear(insist);
-	out.insert(out.end(), more.begin(), more.end());
-	return out;
-    }
-}
-
-vector<vector<double> >
-CQInterpolated::linearInterpolated(const Grid &g, int x0, int x1)
-{
-    // g must be a grid with full-height columns at x0 and x1
-
-    if (x0 >= x1) {
-	throw std::logic_error("x0 >= x1");
-    }
-    if (x1 >= (int)g.size()) {
-	throw std::logic_error("x1 >= g.size()");
-    }
-    if (g[x0].size() != g[x1].size()) {
-	throw std::logic_error("x0 and x1 are not the same height");
-    }
-
-    int height = g[x0].size();
-    int width = x1 - x0;
-
-    Grid out(g.begin() + x0, g.begin() + x1);
-
-    for (int y = 0; y < height; ++y) {
-
-	int spacing = width;
-	for (int i = 1; i < width; ++i) {
-	    int thisHeight = g[x0 + i].size();
-	    if (thisHeight > height) {
-		throw std::logic_error("First column not full-height");
-	    }
-	    if (thisHeight > y) {
-		spacing = i;
-		break;
-	    }
-	}
-
-	if (spacing < 2) continue;
-
-	for (int i = 0; i + spacing <= width; i += spacing) {
-	    for (int j = 1; j < spacing; ++j) {
-		double proportion = double(j)/double(spacing);
-		double interpolated = 
-		    g[x0 + i][y] * (1.0 - proportion) +
-		    g[x0 + i + spacing][y] * proportion;
-		out[i + j].push_back(interpolated);
-	    }
-	}
-    }
-
-    return out;
-}
-
-
-	
--- a/cpp-qm-dsp/CQInterpolated.h	Fri May 09 08:25:24 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-/* -*- 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(const Grid &, bool insist);
-    Grid fetchHold(bool insist);
-    Grid fetchLinear(bool insist);
-    Grid linearInterpolated(const Grid &, int, int);
-    std::vector<double> m_prevColumn;
-};
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cpp-qm-dsp/CQSpectrogram.cpp	Fri May 09 10:05:16 2014 +0100
@@ -0,0 +1,263 @@
+/* -*- 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 "CQSpectrogram.h"
+
+#include <iostream>
+#include <stdexcept>
+
+using std::cerr;
+using std::endl;
+
+CQSpectrogram::CQSpectrogram(double sampleRate,
+			       double minFreq, double maxFreq,
+			       int binsPerOctave,
+			       Interpolation interpolation) :
+    m_cq(sampleRate, minFreq, maxFreq, binsPerOctave),
+    m_interpolation(interpolation)
+{
+}
+
+CQSpectrogram::~CQSpectrogram()
+{
+}
+
+CQSpectrogram::RealBlock
+CQSpectrogram::process(const RealSequence &td)
+{
+    return postProcess(m_cq.process(td), false);
+}
+
+CQSpectrogram::RealBlock
+CQSpectrogram::getRemainingOutput()
+{
+    return postProcess(m_cq.getRemainingOutput(), true);
+}
+
+CQSpectrogram::RealBlock
+CQSpectrogram::postProcess(const ComplexBlock &cq, bool insist)
+{
+    int width = cq.size();
+
+    // convert to magnitudes
+    RealBlock spec;
+    for (int i = 0; i < width; ++i) {
+        int height = cq[i].size();
+        RealColumn col(height, 0);
+        for (int j = 0; j < height; ++j) {
+            col[j] = abs(cq[i][j]);
+        }
+        spec.push_back(col);
+    }
+
+    if (m_interpolation == InterpolateZeros) {
+        for (int i = 0; i < width; ++i) {
+            int sh = spec[i].size();
+            int fh = getTotalBins();
+            for (int j = sh; j < fh; ++j) {
+                spec[i].push_back(0);
+            }
+        }
+	return spec;
+    }
+
+    for (int i = 0; i < width; ++i) {
+	m_buffer.push_back(spec[i]);
+    }
+    
+    if (m_interpolation == InterpolateHold) {
+	return fetchHold(insist);
+    } else {
+	return fetchLinear(insist);
+    }
+}
+
+CQSpectrogram::RealBlock
+CQSpectrogram::fetchHold(bool)
+{
+    RealBlock out;
+    
+    int width = m_buffer.size();
+    int height = getTotalBins();
+
+    for (int i = 0; i < width; ++i) {
+	
+	RealColumn 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;
+}
+
+CQSpectrogram::RealBlock
+CQSpectrogram::fetchLinear(bool insist)
+{
+    RealBlock 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 ((int)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 = RealBlock(m_buffer.begin(), m_buffer.begin() + firstFullHeight);
+	m_buffer = RealBlock(m_buffer.begin() + firstFullHeight, m_buffer.end());
+	RealBlock 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 = linearInterpolated(m_buffer, 0, secondFullHeight);
+	m_buffer = RealBlock(m_buffer.begin() + secondFullHeight, m_buffer.end());
+	RealBlock more = fetchLinear(insist);
+	out.insert(out.end(), more.begin(), more.end());
+	return out;
+    }
+}
+
+CQSpectrogram::RealBlock
+CQSpectrogram::linearInterpolated(const RealBlock &g, int x0, int x1)
+{
+    // g must be a grid with full-height columns at x0 and x1
+
+    if (x0 >= x1) {
+	throw std::logic_error("x0 >= x1");
+    }
+    if (x1 >= (int)g.size()) {
+	throw std::logic_error("x1 >= g.size()");
+    }
+    if (g[x0].size() != g[x1].size()) {
+	throw std::logic_error("x0 and x1 are not the same height");
+    }
+
+    int height = g[x0].size();
+    int width = x1 - x0;
+
+    RealBlock out(g.begin() + x0, g.begin() + x1);
+
+    for (int y = 0; y < height; ++y) {
+
+	int spacing = width;
+	for (int i = 1; i < width; ++i) {
+	    int thisHeight = g[x0 + i].size();
+	    if (thisHeight > height) {
+		throw std::logic_error("First column not full-height");
+	    }
+	    if (thisHeight > y) {
+		spacing = i;
+		break;
+	    }
+	}
+
+	if (spacing < 2) continue;
+
+	for (int i = 0; i + spacing <= width; i += spacing) {
+	    for (int j = 1; j < spacing; ++j) {
+		double proportion = double(j)/double(spacing);
+		double interpolated = 
+		    g[x0 + i][y] * (1.0 - proportion) +
+		    g[x0 + i + spacing][y] * proportion;
+		out[i + j].push_back(interpolated);
+	    }
+	}
+    }
+
+    return out;
+}
+
+
+	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cpp-qm-dsp/CQSpectrogram.h	Fri May 09 10:05:16 2014 +0100
@@ -0,0 +1,77 @@
+/* -*- 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 CQSPECTROGRAM_H
+#define CQSPECTROGRAM_H
+
+#include "ConstantQ.h"
+
+class CQSpectrogram : public CQBase
+{
+public:
+    enum Interpolation {
+	InterpolateZeros, // leave empty cells as zero
+	InterpolateHold, // repeat prior cell
+	InterpolateLinear, // linear interpolation between consecutive time cells
+    };
+
+    CQSpectrogram(double sampleRate,
+                  double minFreq, double maxFreq,
+                  int binsPerOctave,
+                  Interpolation interpolation);
+    ~CQSpectrogram();
+
+    virtual double getSampleRate() const { return m_cq.getSampleRate(); }
+    virtual int getBinsPerOctave() const { return m_cq.getBinsPerOctave(); }
+    virtual int getOctaves() const { return m_cq.getOctaves(); }
+    virtual int getTotalBins() const { return m_cq.getTotalBins(); }
+    virtual int getColumnHop() const { return m_cq.getColumnHop(); }
+    virtual int getLatency() const { return m_cq.getLatency(); } 
+    virtual double getMaxFrequency() const { return m_cq.getMaxFrequency(); }
+    virtual double getMinFrequency() const { return m_cq.getMinFrequency(); }
+    virtual double getBinFrequency(int bin) const { return m_cq.getBinFrequency(bin); }
+
+    RealBlock process(const RealSequence &);
+    RealBlock getRemainingOutput();
+
+private:
+    ConstantQ m_cq;
+    Interpolation m_interpolation;
+
+    RealBlock m_buffer;
+    RealBlock postProcess(const ComplexBlock &, bool insist);
+    RealBlock fetchHold(bool insist);
+    RealBlock fetchLinear(bool insist);
+    RealBlock linearInterpolated(const RealBlock &, int, int);
+    RealColumn m_prevColumn;
+};
+
+#endif
--- a/cpp-qm-dsp/ConstantQ.cpp	Fri May 09 08:25:24 2014 +0100
+++ b/cpp-qm-dsp/ConstantQ.cpp	Fri May 09 10:05:16 2014 +0100
@@ -47,8 +47,6 @@
 using std::cerr;
 using std::endl;
 
-typedef std::complex<double> C;
-
 ConstantQ::ConstantQ(double sampleRate,
                      double minFreq,
                      double maxFreq,
@@ -215,8 +213,8 @@
     m_fft = new FFTReal(m_p.fftSize);
 }
 
-vector<vector<double> > 
-ConstantQ::process(const vector<double> &td)
+ConstantQ::ComplexBlock
+ConstantQ::process(const RealSequence &td)
 {
     m_buffers[0].insert(m_buffers[0].end(), td.begin(), td.end());
 
@@ -225,7 +223,7 @@
         m_buffers[i].insert(m_buffers[i].end(), dec.begin(), dec.end());
     }
 
-    vector<vector<double> > out;
+    ComplexBlock out;
 
     while (true) {
 
@@ -247,7 +245,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>());
+            out.push_back(ComplexColumn());
         }
 
         for (int octave = 0; octave < m_octaves; ++octave) {
@@ -255,7 +253,7 @@
             int blocksThisOctave = pow(2, (m_octaves - octave - 1));
 
             for (int b = 0; b < blocksThisOctave; ++b) {
-                vector<vector<double> > block = processOctaveBlock(octave);
+                ComplexBlock block = processOctaveBlock(octave);
                 
                 for (int j = 0; j < m_p.atomsPerFrame; ++j) {
 
@@ -266,7 +264,7 @@
 
                     while (int(out[target].size()) < 
                            m_p.binsPerOctave * (octave + 1)) {
-                        out[target].push_back(0.0);
+                        out[target].push_back(Complex());
                     }
                     
                     for (int i = 0; i < m_p.binsPerOctave; ++i) {
@@ -281,8 +279,8 @@
     return out;
 }
 
-vector<vector<double> >
-ConstantQ::getRemainingBlocks()
+ConstantQ::ComplexBlock
+ConstantQ::getRemainingOutput()
 {
     // Same as padding added at start, though rounded up
     int pad = ceil(double(m_outputLatency) / m_bigBlockSize) * m_bigBlockSize;
@@ -290,7 +288,7 @@
     return process(zeros);
 }
 
-vector<vector<double> >
+ConstantQ::ComplexBlock
 ConstantQ::processOctaveBlock(int octave)
 {
     vector<double> ro(m_p.fftSize, 0.0);
@@ -304,19 +302,19 @@
                    m_buffers[octave].end());
     m_buffers[octave] = shifted;
 
-    vector<C> cv;
+    ComplexSequence cv;
     for (int i = 0; i < m_p.fftSize; ++i) {
-        cv.push_back(C(ro[i], io[i]));
+        cv.push_back(Complex(ro[i], io[i]));
     }
 
-    vector<C> cqrowvec = m_kernel->process(cv);
+    ComplexSequence cqrowvec = m_kernel->process(cv);
 
-    // Reform into a column matrix and use only the magnitude
-    vector<vector<double> > cqblock;
+    // Reform into a column matrix
+    ComplexBlock cqblock;
     for (int j = 0; j < m_p.atomsPerFrame; ++j) {
-        cqblock.push_back(vector<double>());
+        cqblock.push_back(ComplexColumn());
         for (int i = 0; i < m_p.binsPerOctave; ++i) {
-            cqblock[j].push_back(abs(cqrowvec[i * m_p.atomsPerFrame + j]));
+            cqblock[j].push_back(cqrowvec[i * m_p.atomsPerFrame + j]);
         }
     }
 
--- a/cpp-qm-dsp/ConstantQ.h	Fri May 09 08:25:24 2014 +0100
+++ b/cpp-qm-dsp/ConstantQ.h	Fri May 09 10:05:16 2014 +0100
@@ -32,30 +32,36 @@
 #ifndef CONSTANTQ_H
 #define CONSTANTQ_H
 
+#include "CQBase.h"
 #include "CQKernel.h"
 
-#include <vector>
-
 class Resampler;
 class FFTReal;
 
-class ConstantQ
+/**
+ * Calculate a complex sparse constant-Q representation from
+ * time-domain input.
+ *
+ * For a real (magnitude-only) or interpolated representation, see
+ * CQSpectrogram.
+ */
+class ConstantQ : public CQBase
 {
 public:
     ConstantQ(double sampleRate, 
 	      double minFreq, double maxFreq, 
 	      int binsPerOctave);
-    ~ConstantQ();
+    virtual ~ConstantQ();
 
-    double getSampleRate() const { return m_sampleRate; }
-    int getBinsPerOctave() const { return m_binsPerOctave; }
-    int getOctaves() const { return m_octaves; }
-    int getTotalBins() const { return m_octaves * m_binsPerOctave; }
-    int getColumnHop() const { return m_p.fftHop / m_p.atomsPerFrame; }
-    int getLatency() const { return m_outputLatency; } 
-    double getMaxFrequency() const { return m_p.maxFrequency; }
-    double getMinFrequency() const; // actual min, not that passed to ctor
-    double getBinFrequency(int bin) const;
+    virtual double getSampleRate() const { return m_sampleRate; }
+    virtual int getBinsPerOctave() const { return m_binsPerOctave; }
+    virtual int getOctaves() const { return m_octaves; }
+    virtual int getTotalBins() const { return m_octaves * m_binsPerOctave; }
+    virtual int getColumnHop() const { return m_p.fftHop / m_p.atomsPerFrame; }
+    virtual int getLatency() const { return m_outputLatency; } 
+    virtual double getMaxFrequency() const { return m_p.maxFrequency; }
+    virtual double getMinFrequency() const;
+    virtual double getBinFrequency(int bin) const;
 
     /**
      * Given a series of time-domain samples, return a series of
@@ -76,14 +82,14 @@
      * and every bin contains a value, use CQInterpolated instead of
      * ConstantQ.
      */
-    std::vector<std::vector<double> > process(const std::vector<double> &);
+    ComplexBlock process(const RealSequence &);
 
     /**
      * Return the remaining constant-Q columns following the end of
      * processing. Any buffered input is padded so as to ensure that
      * all input provided to process() will have been returned.
      */
-    std::vector<std::vector<double> > getRemainingBlocks();
+    ComplexBlock getRemainingOutput();
 
 private:
     double m_sampleRate;
@@ -97,14 +103,14 @@
     int m_bigBlockSize;
 
     std::vector<Resampler *> m_decimators;
-    std::vector<std::vector<double> > m_buffers;
+    RealBlock m_buffers;
 
     int m_outputLatency;
 
     FFTReal *m_fft;
 
     void initialise();
-    std::vector<std::vector<double> > processOctaveBlock(int octave);
+    ComplexBlock processOctaveBlock(int octave);
 };
 
 #endif
--- a/cpp-qm-dsp/test.cpp	Fri May 09 08:25:24 2014 +0100
+++ b/cpp-qm-dsp/test.cpp	Fri May 09 10:05:16 2014 +0100
@@ -28,7 +28,7 @@
     authorization.
 */
 
-#include "ConstantQ.h"
+#include "CQSpectrogram.h"
 
 #include <iostream>
 #include <vector>
@@ -50,10 +50,10 @@
 	in.push_back(sin(i * M_PI / 2.0));
     }
 
-    ConstantQ k(8, 1, 4, 4);
+    CQSpectrogram k(8, 1, 4, 4, CQSpectrogram::None);
 
     vector<vector<double> > out = k.process(in);
-    vector<vector<double> > rest = k.getRemainingBlocks();
+    vector<vector<double> > rest = k.getRemainingOutput();
 
     out.insert(out.end(), rest.begin(), rest.end());
 
--- a/vamp/CQVamp.cpp	Fri May 09 08:25:24 2014 +0100
+++ b/vamp/CQVamp.cpp	Fri May 09 10:05:16 2014 +0100
@@ -47,7 +47,7 @@
     m_maxMIDIPitch(84),
     m_tuningFrequency(440),
     m_bpo(24),
-    m_interpolation(CQInterpolated::Linear),
+    m_interpolation(CQSpectrogram::InterpolateLinear),
     m_cq(0),
     m_maxFrequency(inputSampleRate/2),
     m_minFrequency(46),
@@ -155,7 +155,7 @@
     desc.defaultValue = 2;
     desc.isQuantized = true;
     desc.quantizeStep = 1;
-    desc.valueNames.push_back("None, leave empty");
+    desc.valueNames.push_back("None, leave as zero");
     desc.valueNames.push_back("None, repeat prior value");
     desc.valueNames.push_back("Linear interpolation");
     list.push_back(desc);
@@ -198,7 +198,7 @@
     } else if (param == "bpo") {
         m_bpo = lrintf(value);
     } else if (param == "interpolation") {
-        m_interpolation = (CQInterpolated::Interpolation)lrintf(value);
+        m_interpolation = (CQSpectrogram::Interpolation)lrintf(value);
     } else {
         std::cerr << "WARNING: CQVamp::setParameter: unknown parameter \""
                   << param << "\"" << std::endl;
@@ -224,7 +224,7 @@
     m_maxFrequency = Pitch::getFrequencyForPitch
         (m_maxMIDIPitch, 0, m_tuningFrequency);
 
-    m_cq = new CQInterpolated
+    m_cq = new CQSpectrogram
 	(m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo,
          m_interpolation);
 
@@ -236,7 +236,7 @@
 {
     if (m_cq) {
 	delete m_cq;
-	m_cq = new CQInterpolated
+	m_cq = new CQSpectrogram
 	    (m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo,
              m_interpolation);
     }
@@ -314,7 +314,7 @@
 CQVamp::FeatureSet
 CQVamp::getRemainingFeatures()
 {
-    vector<vector<double> > cqout = m_cq->getRemainingBlocks();
+    vector<vector<double> > cqout = m_cq->getRemainingOutput();
     return convertToFeatures(cqout);
 }
 
--- a/vamp/CQVamp.h	Fri May 09 08:25:24 2014 +0100
+++ b/vamp/CQVamp.h	Fri May 09 10:05:16 2014 +0100
@@ -34,7 +34,7 @@
 
 #include <vamp-sdk/Plugin.h>
 
-#include "cpp-qm-dsp/CQInterpolated.h"
+#include "cpp-qm-dsp/CQSpectrogram.h"
 
 class ConstantQ;
 
@@ -75,9 +75,9 @@
     int m_maxMIDIPitch;
     float m_tuningFrequency;
     int m_bpo;
-    CQInterpolated::Interpolation m_interpolation;
+    CQSpectrogram::Interpolation m_interpolation;
 
-    CQInterpolated *m_cq;
+    CQSpectrogram *m_cq;
     float m_maxFrequency;
     float m_minFrequency;
     int m_stepSize;