changeset 119:a38d6940f8fb

Bring in dsp dependencies
author Chris Cannam <c.cannam@qmul.ac.uk>
date Thu, 15 May 2014 12:24:11 +0100
parents 36bfbc606642
children 3fa7e938e7d4
files .hgsub .hgsubstate COPYING src/dsp/KaiserWindow.cpp src/dsp/KaiserWindow.h src/dsp/MathUtilities.cpp src/dsp/MathUtilities.h src/dsp/Resampler.cpp src/dsp/Resampler.h src/dsp/SincWindow.cpp src/dsp/SincWindow.h src/processfile.cpp src/test.cpp test/processfile.cpp test/test.cpp
diffstat 15 files changed, 1879 insertions(+), 377 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsub	Thu May 15 12:06:31 2014 +0100
+++ b/.hgsub	Thu May 15 12:24:11 2014 +0100
@@ -1,1 +1,2 @@
+src/ext/kissfft = http://hg.code.sf.net/p/kissfft/code
 misc/matlab = https://code.soundsoftware.ac.uk/hg/constant-q-toolbox
--- a/.hgsubstate	Thu May 15 12:06:31 2014 +0100
+++ b/.hgsubstate	Thu May 15 12:24:11 2014 +0100
@@ -1,2 +1,3 @@
 2b03ca77abcc91140dbb37827f06034769c53547 matlab
 2b03ca77abcc91140dbb37827f06034769c53547 misc/matlab
+fbe1bb0bc7b94ec252842b8b7e3f3347ec75d92f src/ext/kissfft
--- a/COPYING	Thu May 15 12:06:31 2014 +0100
+++ b/COPYING	Thu May 15 12:24:11 2014 +0100
@@ -1,12 +1,6 @@
 
-The code in this library is provided under an MIT-style licence as
-follows. However, note that other code which it depends on may be
-licensed differently (for example, qm-dsp is under the GPL). This
-licence applies only to the source files actually contained in this
-repository, and not to any of its subrepositories. See individual
-files for more details.
+This library is provided to you under the following licence:
 
-    Constant-Q library
     Copyright (c) 2013-2014 Queen Mary, University of London
 
     Permission is hereby granted, free of charge, to any person
@@ -34,4 +28,38 @@
     use or other dealings in this Software without prior written
     authorization.
 
+The code in src/ext/kissfft is under the following licence:
 
+    Copyright (c) 2003-2010 Mark Borgerding
+
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the author nor the names of any contributors may be used
+      to endorse or promote products derived from this software
+      without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+    CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+    BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+    TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dsp/KaiserWindow.cpp	Thu May 15 12:24:11 2014 +0100
@@ -0,0 +1,83 @@
+/* -*- 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 "KaiserWindow.h"
+
+#include "maths/MathUtilities.h"
+
+KaiserWindow::Parameters
+KaiserWindow::parametersForTransitionWidth(double attenuation,
+					   double transition)
+{
+    Parameters p;
+    p.length = 1 + (attenuation > 21.0 ?
+		    ceil((attenuation - 7.95) / (2.285 * transition)) :
+		    ceil(5.79 / transition));
+    p.beta = (attenuation > 50.0 ? 
+	      0.1102 * (attenuation - 8.7) :
+	      attenuation > 21.0 ? 
+	      0.5842 * pow(attenuation - 21.0, 0.4) + 0.07886 * (attenuation - 21.0) :
+	      0);
+    return p;
+}
+
+static double besselTerm(double x, int i)
+{
+    if (i == 0) {
+	return 1;
+    } else {
+	double f = MathUtilities::factorial(i);
+	return pow(x/2, i*2) / (f*f);
+    }
+}
+
+static double bessel0(double x)
+{
+    double b = 0.0;
+    for (int i = 0; i < 20; ++i) {
+	b += besselTerm(x, i);
+    }
+    return b;
+}
+
+void
+KaiserWindow::init()
+{
+    double denominator = bessel0(m_beta);
+    bool even = (m_length % 2 == 0);
+    for (int i = 0; i < (even ? m_length/2 : (m_length+1)/2); ++i) {
+	double k = double(2*i) / double(m_length-1) - 1.0;
+	m_window.push_back(bessel0(m_beta * sqrt(1.0 - k*k)) / denominator);
+    }
+    for (int i = 0; i < (even ? m_length/2 : (m_length-1)/2); ++i) {
+        m_window.push_back(m_window[int(m_length/2) - i - 1]);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dsp/KaiserWindow.h	Thu May 15 12:24:11 2014 +0100
@@ -0,0 +1,123 @@
+/* -*- 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 KAISER_WINDOW_H
+#define KAISER_WINDOW_H
+
+#include <vector>
+#include <cmath>
+
+/**
+ * Kaiser window: A windower whose bandwidth and sidelobe height
+ * (signal-noise ratio) can be specified. These parameters are traded
+ * off against the window length.
+ */
+class KaiserWindow
+{
+public:
+    struct Parameters {
+	int length;
+	double beta;
+    };
+
+    /**
+     * Construct a Kaiser windower with the given length and beta
+     * parameter.
+     */
+    KaiserWindow(Parameters p) : m_length(p.length), m_beta(p.beta) { init(); }
+
+    /**
+     * Construct a Kaiser windower with the given attenuation in dB
+     * and transition width in samples.
+     */
+    static KaiserWindow byTransitionWidth(double attenuation,
+					  double transition) {
+	return KaiserWindow
+	    (parametersForTransitionWidth(attenuation, transition));
+    }
+
+    /**
+     * Construct a Kaiser windower with the given attenuation in dB
+     * and transition bandwidth in Hz for the given samplerate.
+     */
+    static KaiserWindow byBandwidth(double attenuation,
+				    double bandwidth,
+				    double samplerate) {
+	return KaiserWindow
+	    (parametersForBandwidth(attenuation, bandwidth, samplerate));
+    }
+
+    /**
+     * Obtain the parameters necessary for a Kaiser window of the
+     * given attenuation in dB and transition width in samples.
+     */
+    static Parameters parametersForTransitionWidth(double attenuation,
+						   double transition);
+
+    /**
+     * Obtain the parameters necessary for a Kaiser window of the
+     * given attenuation in dB and transition bandwidth in Hz for the
+     * given samplerate.
+     */
+    static Parameters parametersForBandwidth(double attenuation,
+					     double bandwidth,
+					     double samplerate) {
+	return parametersForTransitionWidth
+	    (attenuation, (bandwidth * 2 * M_PI) / samplerate);
+    } 
+
+    int getLength() const {
+	return m_length;
+    }
+
+    const double *getWindow() const { 
+	return m_window.data();
+    }
+
+    void cut(double *src) const { 
+	cut(src, src); 
+    }
+
+    void cut(const double *src, double *dst) const {
+	for (int i = 0; i < m_length; ++i) {
+	    dst[i] = src[i] * m_window[i];
+	}
+    }
+
+private:
+    int m_length;
+    double m_beta;
+    std::vector<double> m_window;
+
+    void init();
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dsp/MathUtilities.cpp	Thu May 15 12:24:11 2014 +0100
@@ -0,0 +1,419 @@
+/* -*- 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 "MathUtilities.h"
+
+#include <iostream>
+#include <algorithm>
+#include <vector>
+#include <cmath>
+
+
+double MathUtilities::mod(double x, double y)
+{
+    double a = floor( x / y );
+
+    double b = x - ( y * a );
+    return b;
+}
+
+double MathUtilities::princarg(double ang)
+{
+    double ValOut;
+
+    ValOut = mod( ang + M_PI, -2 * M_PI ) + M_PI;
+
+    return ValOut;
+}
+
+void MathUtilities::getAlphaNorm(const double *data, unsigned int len, unsigned int alpha, double* ANorm)
+{
+    unsigned int i;
+    double temp = 0.0;
+    double a=0.0;
+	
+    for( i = 0; i < len; i++)
+    {
+	temp = data[ i ];
+		
+	a  += ::pow( fabs(temp), double(alpha) );
+    }
+    a /= ( double )len;
+    a = ::pow( a, ( 1.0 / (double) alpha ) );
+
+    *ANorm = a;
+}
+
+double MathUtilities::getAlphaNorm( const std::vector <double> &data, unsigned int alpha )
+{
+    unsigned int i;
+    unsigned int len = data.size();
+    double temp = 0.0;
+    double a=0.0;
+	
+    for( i = 0; i < len; i++)
+    {
+	temp = data[ i ];
+		
+	a  += ::pow( fabs(temp), double(alpha) );
+    }
+    a /= ( double )len;
+    a = ::pow( a, ( 1.0 / (double) alpha ) );
+
+    return a;
+}
+
+double MathUtilities::round(double x)
+{
+    if (x < 0) {
+        return -floor(-x + 0.5);
+    } else {
+        return floor(x + 0.5);
+    }
+}
+
+double MathUtilities::median(const double *src, unsigned int len)
+{
+    if (len == 0) return 0;
+    
+    std::vector<double> scratch;
+    for (int i = 0; i < len; ++i) scratch.push_back(src[i]);
+    std::sort(scratch.begin(), scratch.end());
+
+    int middle = len/2;
+    if (len % 2 == 0) {
+        return (scratch[middle] + scratch[middle - 1]) / 2;
+    } else {
+        return scratch[middle];
+    }
+}
+
+double MathUtilities::sum(const double *src, unsigned int len)
+{
+    unsigned int i ;
+    double retVal =0.0;
+
+    for(  i = 0; i < len; i++)
+    {
+	retVal += src[ i ];
+    }
+
+    return retVal;
+}
+
+double MathUtilities::mean(const double *src, unsigned int len)
+{
+    double retVal =0.0;
+
+    if (len == 0) return 0;
+
+    double s = sum( src, len );
+	
+    retVal =  s  / (double)len;
+
+    return retVal;
+}
+
+double MathUtilities::mean(const std::vector<double> &src,
+                           unsigned int start,
+                           unsigned int count)
+{
+    double sum = 0.;
+	
+    if (count == 0) return 0;
+    
+    for (int i = 0; i < (int)count; ++i)
+    {
+        sum += src[start + i];
+    }
+
+    return sum / count;
+}
+
+void MathUtilities::getFrameMinMax(const double *data, unsigned int len, double *min, double *max)
+{
+    unsigned int i;
+    double temp = 0.0;
+
+    if (len == 0) {
+        *min = *max = 0;
+        return;
+    }
+	
+    *min = data[0];
+    *max = data[0];
+
+    for( i = 0; i < len; i++)
+    {
+	temp = data[ i ];
+
+	if( temp < *min )
+	{
+	    *min =  temp ;
+	}
+	if( temp > *max )
+	{
+	    *max =  temp ;
+	}
+		
+    }
+}
+
+int MathUtilities::getMax( double* pData, unsigned int Length, double* pMax )
+{
+	unsigned int index = 0;
+	unsigned int i;
+	double temp = 0.0;
+	
+	double max = pData[0];
+
+	for( i = 0; i < Length; i++)
+	{
+		temp = pData[ i ];
+
+		if( temp > max )
+		{
+			max =  temp ;
+			index = i;
+		}
+		
+   	}
+
+	if (pMax) *pMax = max;
+
+
+	return index;
+}
+
+int MathUtilities::getMax( const std::vector<double> & data, double* pMax )
+{
+	unsigned int index = 0;
+	unsigned int i;
+	double temp = 0.0;
+	
+	double max = data[0];
+
+	for( i = 0; i < data.size(); i++)
+	{
+		temp = data[ i ];
+
+		if( temp > max )
+		{
+			max =  temp ;
+			index = i;
+		}
+		
+   	}
+
+	if (pMax) *pMax = max;
+
+
+	return index;
+}
+
+void MathUtilities::circShift( double* pData, int length, int shift)
+{
+	shift = shift % length;
+	double temp;
+	int i,n;
+
+	for( i = 0; i < shift; i++)
+	{
+		temp=*(pData + length - 1);
+
+		for( n = length-2; n >= 0; n--)
+		{
+			*(pData+n+1)=*(pData+n);
+		}
+
+        *pData = temp;
+    }
+}
+
+int MathUtilities::compareInt (const void * a, const void * b)
+{
+  return ( *(int*)a - *(int*)b );
+}
+
+void MathUtilities::normalise(double *data, int length, NormaliseType type)
+{
+    switch (type) {
+
+    case NormaliseNone: return;
+
+    case NormaliseUnitSum:
+    {
+        double sum = 0.0;
+        for (int i = 0; i < length; ++i) {
+            sum += data[i];
+        }
+        if (sum != 0.0) {
+            for (int i = 0; i < length; ++i) {
+                data[i] /= sum;
+            }
+        }
+    }
+    break;
+
+    case NormaliseUnitMax:
+    {
+        double max = 0.0;
+        for (int i = 0; i < length; ++i) {
+            if (fabs(data[i]) > max) {
+                max = fabs(data[i]);
+            }
+        }
+        if (max != 0.0) {
+            for (int i = 0; i < length; ++i) {
+                data[i] /= max;
+            }
+        }
+    }
+    break;
+
+    }
+}
+
+void MathUtilities::normalise(std::vector<double> &data, NormaliseType type)
+{
+    switch (type) {
+
+    case NormaliseNone: return;
+
+    case NormaliseUnitSum:
+    {
+        double sum = 0.0;
+        for (int i = 0; i < (int)data.size(); ++i) sum += data[i];
+        if (sum != 0.0) {
+            for (int i = 0; i < (int)data.size(); ++i) data[i] /= sum;
+        }
+    }
+    break;
+
+    case NormaliseUnitMax:
+    {
+        double max = 0.0;
+        for (int i = 0; i < (int)data.size(); ++i) {
+            if (fabs(data[i]) > max) max = fabs(data[i]);
+        }
+        if (max != 0.0) {
+            for (int i = 0; i < (int)data.size(); ++i) data[i] /= max;
+        }
+    }
+    break;
+
+    }
+}
+
+void MathUtilities::adaptiveThreshold(std::vector<double> &data)
+{
+    int sz = int(data.size());
+    if (sz == 0) return;
+
+    std::vector<double> smoothed(sz);
+	
+    int p_pre = 8;
+    int p_post = 7;
+
+    for (int i = 0; i < sz; ++i) {
+
+        int first = std::max(0,      i - p_pre);
+        int last  = std::min(sz - 1, i + p_post);
+
+        smoothed[i] = mean(data, first, last - first + 1);
+    }
+
+    for (int i = 0; i < sz; i++) {
+        data[i] -= smoothed[i];
+        if (data[i] < 0.0) data[i] = 0.0;
+    }
+}
+
+bool
+MathUtilities::isPowerOfTwo(int x)
+{
+    if (x < 1) return false;
+    if (x & (x-1)) return false;
+    return true;
+}
+
+int
+MathUtilities::nextPowerOfTwo(int x)
+{
+    if (isPowerOfTwo(x)) return x;
+    if (x < 1) return 1;
+    int n = 1;
+    while (x) { x >>= 1; n <<= 1; }
+    return n;
+}
+
+int
+MathUtilities::previousPowerOfTwo(int x)
+{
+    if (isPowerOfTwo(x)) return x;
+    if (x < 1) return 1;
+    int n = 1;
+    x >>= 1;
+    while (x) { x >>= 1; n <<= 1; }
+    return n;
+}
+
+int
+MathUtilities::nearestPowerOfTwo(int x)
+{
+    if (isPowerOfTwo(x)) return x;
+    int n0 = previousPowerOfTwo(x), n1 = nextPowerOfTwo(x);
+    if (x - n0 < n1 - x) return n0;
+    else return n1;
+}
+
+double
+MathUtilities::factorial(int x)
+{
+    if (x < 0) return 0;
+    double f = 1;
+    for (int i = 1; i <= x; ++i) {
+	f = f * i;
+    }
+    return f;
+}
+
+int
+MathUtilities::gcd(int a, int b)
+{
+    int c = a % b;
+    if (c == 0) {
+        return b;
+    } else {
+        return gcd(b, c);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dsp/MathUtilities.h	Thu May 15 12:24:11 2014 +0100
@@ -0,0 +1,153 @@
+/* -*- 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 MATHUTILITIES_H
+#define MATHUTILITIES_H
+
+#include <vector>
+
+#include "nan-inf.h"
+
+/**
+ * Static helper functions for simple mathematical calculations.
+ */
+class MathUtilities  
+{
+public:	
+    /**
+     * Round x to the nearest integer.
+     */
+    static double round( double x );
+
+    /**
+     * Return through min and max pointers the highest and lowest
+     * values in the given array of the given length.
+     */
+    static void	  getFrameMinMax( const double* data, unsigned int len,  double* min, double* max );
+
+    /**
+     * Return the mean of the given array of the given length.
+     */
+    static double mean( const double* src, unsigned int len );
+
+    /**
+     * Return the mean of the subset of the given vector identified by
+     * start and count.
+     */
+    static double mean( const std::vector<double> &data,
+                        unsigned int start, unsigned int count );
+    
+    /**
+     * Return the sum of the values in the given array of the given
+     * length.
+     */
+    static double sum( const double* src, unsigned int len );
+
+    /**
+     * Return the median of the values in the given array of the given
+     * length. If the array is even in length, the returned value will
+     * be half-way between the two values adjacent to median.
+     */
+    static double median( const double* src, unsigned int len );
+
+    /**
+     * The principle argument function. Map the phase angle ang into
+     * the range [-pi,pi).
+     */
+    static double princarg( double ang );
+
+    /**
+     * Floating-point division modulus: return x % y.
+     */
+    static double mod( double x, double y);
+
+    static void	  getAlphaNorm(const double *data, unsigned int len, unsigned int alpha, double* ANorm);
+    static double getAlphaNorm(const std::vector <double> &data, unsigned int alpha );
+
+    static void   circShift( double* data, int length, int shift);
+
+    static int	  getMax( double* data, unsigned int length, double* max = 0 );
+    static int	  getMax( const std::vector<double> &data, double* max = 0 );
+    static int    compareInt(const void * a, const void * b);
+
+    enum NormaliseType {
+        NormaliseNone,
+        NormaliseUnitSum,
+        NormaliseUnitMax
+    };
+
+    static void normalise(double *data, int length,
+                          NormaliseType n = NormaliseUnitMax);
+
+    static void normalise(std::vector<double> &data,
+                          NormaliseType n = NormaliseUnitMax);
+
+    /**
+     * Threshold the input/output vector data against a moving-mean
+     * average filter.
+     */
+    static void adaptiveThreshold(std::vector<double> &data);
+
+    /** 
+     * Return true if x is 2^n for some integer n >= 0.
+     */
+    static bool isPowerOfTwo(int x);
+
+    /**
+     * Return the next higher integer power of two from x, e.g. 1300
+     * -> 2048, 2048 -> 2048.
+     */
+    static int nextPowerOfTwo(int x);
+
+    /**
+     * Return the next lower integer power of two from x, e.g. 1300 ->
+     * 1024, 2048 -> 2048.
+     */
+    static int previousPowerOfTwo(int x);
+
+    /**
+     * Return the nearest integer power of two to x, e.g. 1300 -> 1024,
+     * 12 -> 16 (not 8; if two are equidistant, the higher is returned).
+     */
+    static int nearestPowerOfTwo(int x);
+
+    /**
+     * Return x!
+     */
+    static double factorial(int x); // returns double in case it is large
+
+    /**
+     * Return the greatest common divisor of natural numbers a and b.
+     */
+    static int gcd(int a, int b);
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dsp/Resampler.cpp	Thu May 15 12:24:11 2014 +0100
@@ -0,0 +1,433 @@
+/* -*- 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 "Resampler.h"
+
+#include "maths/MathUtilities.h"
+#include "base/KaiserWindow.h"
+#include "base/SincWindow.h"
+#include "thread/Thread.h"
+
+#include <iostream>
+#include <vector>
+#include <map>
+#include <cassert>
+
+using std::vector;
+using std::map;
+using std::cerr;
+using std::endl;
+
+//#define DEBUG_RESAMPLER 1
+//#define DEBUG_RESAMPLER_VERBOSE 1
+
+Resampler::Resampler(int sourceRate, int targetRate) :
+    m_sourceRate(sourceRate),
+    m_targetRate(targetRate)
+{
+    initialise(100, 0.02);
+}
+
+Resampler::Resampler(int sourceRate, int targetRate, 
+                     double snr, double bandwidth) :
+    m_sourceRate(sourceRate),
+    m_targetRate(targetRate)
+{
+    initialise(snr, bandwidth);
+}
+
+Resampler::~Resampler()
+{
+    delete[] m_phaseData;
+}
+
+// peakToPole -> length -> beta -> window
+static map<double, map<int, map<double, vector<double> > > >
+knownFilters;
+
+static Mutex
+knownFilterMutex;
+
+void
+Resampler::initialise(double snr, double bandwidth)
+{
+    int higher = std::max(m_sourceRate, m_targetRate);
+    int lower = std::min(m_sourceRate, m_targetRate);
+
+    m_gcd = MathUtilities::gcd(lower, higher);
+    m_peakToPole = higher / m_gcd;
+
+    if (m_targetRate < m_sourceRate) {
+        // antialiasing filter, should be slightly below nyquist
+        m_peakToPole = m_peakToPole / (1.0 - bandwidth/2.0);
+    }
+
+    KaiserWindow::Parameters params =
+	KaiserWindow::parametersForBandwidth(snr, bandwidth, higher / m_gcd);
+
+    params.length =
+	(params.length % 2 == 0 ? params.length + 1 : params.length);
+    
+    params.length =
+        (params.length > 200001 ? 200001 : params.length);
+
+    m_filterLength = params.length;
+
+    vector<double> filter;
+    knownFilterMutex.lock();
+
+    if (knownFilters[m_peakToPole][m_filterLength].find(params.beta) ==
+	knownFilters[m_peakToPole][m_filterLength].end()) {
+
+	KaiserWindow kw(params);
+	SincWindow sw(m_filterLength, m_peakToPole * 2);
+
+	filter = vector<double>(m_filterLength, 0.0);
+	for (int i = 0; i < m_filterLength; ++i) filter[i] = 1.0;
+	sw.cut(filter.data());
+	kw.cut(filter.data());
+
+	knownFilters[m_peakToPole][m_filterLength][params.beta] = filter;
+    }
+
+    filter = knownFilters[m_peakToPole][m_filterLength][params.beta];
+    knownFilterMutex.unlock();
+
+    int inputSpacing = m_targetRate / m_gcd;
+    int outputSpacing = m_sourceRate / m_gcd;
+
+#ifdef DEBUG_RESAMPLER
+    cerr << "resample " << m_sourceRate << " -> " << m_targetRate
+         << ": inputSpacing " << inputSpacing << ", outputSpacing "
+         << outputSpacing << ": filter length " << m_filterLength
+         << endl;
+#endif
+
+    // Now we have a filter of (odd) length flen in which the lower
+    // sample rate corresponds to every n'th point and the higher rate
+    // to every m'th where n and m are higher and lower rates divided
+    // by their gcd respectively. So if x coordinates are on the same
+    // scale as our filter resolution, then source sample i is at i *
+    // (targetRate / gcd) and target sample j is at j * (sourceRate /
+    // gcd).
+
+    // To reconstruct a single target sample, we want a buffer (real
+    // or virtual) of flen values formed of source samples spaced at
+    // intervals of (targetRate / gcd), in our example case 3.  This
+    // is initially formed with the first sample at the filter peak.
+    //
+    // 0  0  0  0  a  0  0  b  0
+    //
+    // and of course we have our filter
+    //
+    // f1 f2 f3 f4 f5 f6 f7 f8 f9
+    //
+    // We take the sum of products of non-zero values from this buffer
+    // with corresponding values in the filter
+    //
+    // a * f5 + b * f8
+    //
+    // Then we drop (sourceRate / gcd) values, in our example case 4,
+    // from the start of the buffer and fill until it has flen values
+    // again
+    //
+    // a  0  0  b  0  0  c  0  0
+    //
+    // repeat to reconstruct the next target sample
+    //
+    // a * f1 + b * f4 + c * f7
+    //
+    // and so on.
+    //
+    // Above I said the buffer could be "real or virtual" -- ours is
+    // virtual. We don't actually store all the zero spacing values,
+    // except for padding at the start; normally we store only the
+    // values that actually came from the source stream, along with a
+    // phase value that tells us how many virtual zeroes there are at
+    // the start of the virtual buffer.  So the two examples above are
+    //
+    // 0 a b  [ with phase 1 ]
+    // a b c  [ with phase 0 ]
+    //
+    // Having thus broken down the buffer so that only the elements we
+    // need to multiply are present, we can also unzip the filter into
+    // every-nth-element subsets at each phase, allowing us to do the
+    // filter multiplication as a simply vector multiply. That is, rather
+    // than store
+    //
+    // f1 f2 f3 f4 f5 f6 f7 f8 f9
+    // 
+    // we store separately
+    //
+    // f1 f4 f7
+    // f2 f5 f8
+    // f3 f6 f9
+    //
+    // Each time we complete a multiply-and-sum, we need to work out
+    // how many (real) samples to drop from the start of our buffer,
+    // and how many to add at the end of it for the next multiply.  We
+    // know we want to drop enough real samples to move along by one
+    // computed output sample, which is our outputSpacing number of
+    // virtual buffer samples. Depending on the relationship between
+    // input and output spacings, this may mean dropping several real
+    // samples, one real sample, or none at all (and simply moving to
+    // a different "phase").
+
+    m_phaseData = new Phase[inputSpacing];
+
+    for (int phase = 0; phase < inputSpacing; ++phase) {
+
+	Phase p;
+
+	p.nextPhase = phase - outputSpacing;
+	while (p.nextPhase < 0) p.nextPhase += inputSpacing;
+	p.nextPhase %= inputSpacing;
+	
+	p.drop = int(ceil(std::max(0.0, double(outputSpacing - phase))
+			  / inputSpacing));
+
+	int filtZipLength = int(ceil(double(m_filterLength - phase)
+				     / inputSpacing));
+
+	for (int i = 0; i < filtZipLength; ++i) {
+	    p.filter.push_back(filter[i * inputSpacing + phase]);
+	}
+
+	m_phaseData[phase] = p;
+    }
+
+#ifdef DEBUG_RESAMPLER
+    int cp = 0;
+    int totDrop = 0;
+    for (int i = 0; i < inputSpacing; ++i) {
+        cerr << "phase = " << cp << ", drop = " << m_phaseData[cp].drop
+             << ", filter length = " << m_phaseData[cp].filter.size()
+             << ", next phase = " << m_phaseData[cp].nextPhase << endl;
+        totDrop += m_phaseData[cp].drop;
+        cp = m_phaseData[cp].nextPhase;
+    }
+    cerr << "total drop = " << totDrop << endl;
+#endif
+
+    // The May implementation of this uses a pull model -- we ask the
+    // resampler for a certain number of output samples, and it asks
+    // its source stream for as many as it needs to calculate
+    // those. This means (among other things) that the source stream
+    // can be asked for enough samples up-front to fill the buffer
+    // before the first output sample is generated.
+    // 
+    // In this implementation we're using a push model in which a
+    // certain number of source samples is provided and we're asked
+    // for as many output samples as that makes available. But we
+    // can't return any samples from the beginning until half the
+    // filter length has been provided as input. This means we must
+    // either return a very variable number of samples (none at all
+    // until the filter fills, then half the filter length at once) or
+    // else have a lengthy declared latency on the output. We do the
+    // latter. (What do other implementations do?)
+    //
+    // We want to make sure the first "real" sample will eventually be
+    // aligned with the centre sample in the filter (it's tidier, and
+    // easier to do diagnostic calculations that way). So we need to
+    // pick the initial phase and buffer fill accordingly.
+    // 
+    // Example: if the inputSpacing is 2, outputSpacing is 3, and
+    // filter length is 7,
+    // 
+    //    x     x     x     x     a     b     c ... input samples
+    // 0  1  2  3  4  5  6  7  8  9 10 11 12 13 ... 
+    //          i        j        k        l    ... output samples
+    // [--------|--------] <- filter with centre mark
+    //
+    // Let h be the index of the centre mark, here 3 (generally
+    // int(filterLength/2) for odd-length filters).
+    //
+    // The smallest n such that h + n * outputSpacing > filterLength
+    // is 2 (that is, ceil((filterLength - h) / outputSpacing)), and
+    // (h + 2 * outputSpacing) % inputSpacing == 1, so the initial
+    // phase is 1.
+    //
+    // To achieve our n, we need to pre-fill the "virtual" buffer with
+    // 4 zero samples: the x's above. This is int((h + n *
+    // outputSpacing) / inputSpacing). It's the phase that makes this
+    // buffer get dealt with in such a way as to give us an effective
+    // index for sample a of 9 rather than 8 or 10 or whatever.
+    //
+    // This gives us output latency of 2 (== n), i.e. output samples i
+    // and j will appear before the one in which input sample a is at
+    // the centre of the filter.
+
+    int h = int(m_filterLength / 2);
+    int n = ceil(double(m_filterLength - h) / outputSpacing);
+    
+    m_phase = (h + n * outputSpacing) % inputSpacing;
+
+    int fill = (h + n * outputSpacing) / inputSpacing;
+    
+    m_latency = n;
+
+    m_buffer = vector<double>(fill, 0);
+    m_bufferOrigin = 0;
+
+#ifdef DEBUG_RESAMPLER
+    cerr << "initial phase " << m_phase << " (as " << (m_filterLength/2) << " % " << inputSpacing << ")"
+	      << ", latency " << m_latency << endl;
+#endif
+}
+
+double
+Resampler::reconstructOne()
+{
+    Phase &pd = m_phaseData[m_phase];
+    double v = 0.0;
+    int n = pd.filter.size();
+
+    assert(n + m_bufferOrigin <= (int)m_buffer.size());
+
+    const double *const __restrict__ buf = m_buffer.data() + m_bufferOrigin;
+    const double *const __restrict__ filt = pd.filter.data();
+
+    for (int i = 0; i < n; ++i) {
+	// NB gcc can only vectorize this with -ffast-math
+	v += buf[i] * filt[i];
+    }
+
+    m_bufferOrigin += pd.drop;
+    m_phase = pd.nextPhase;
+    return v;
+}
+
+int
+Resampler::process(const double *src, double *dst, int n)
+{
+    for (int i = 0; i < n; ++i) {
+	m_buffer.push_back(src[i]);
+    }
+
+    int maxout = int(ceil(double(n) * m_targetRate / m_sourceRate));
+    int outidx = 0;
+
+#ifdef DEBUG_RESAMPLER
+    cerr << "process: buf siz " << m_buffer.size() << " filt siz for phase " << m_phase << " " << m_phaseData[m_phase].filter.size() << endl;
+#endif
+
+    double scaleFactor = (double(m_targetRate) / m_gcd) / m_peakToPole;
+
+    while (outidx < maxout &&
+	   m_buffer.size() >= m_phaseData[m_phase].filter.size() + m_bufferOrigin) {
+	dst[outidx] = scaleFactor * reconstructOne();
+	outidx++;
+    }
+
+    m_buffer = vector<double>(m_buffer.begin() + m_bufferOrigin, m_buffer.end());
+    m_bufferOrigin = 0;
+    
+    return outidx;
+}
+    
+vector<double>
+Resampler::process(const double *src, int n)
+{
+    int maxout = int(ceil(double(n) * m_targetRate / m_sourceRate));
+    vector<double> out(maxout, 0.0);
+    int got = process(src, out.data(), n);
+    assert(got <= maxout);
+    if (got < maxout) out.resize(got);
+    return out;
+}
+
+vector<double>
+Resampler::resample(int sourceRate, int targetRate, const double *data, int n)
+{
+    Resampler r(sourceRate, targetRate);
+
+    int latency = r.getLatency();
+
+    // latency is the output latency. We need to provide enough
+    // padding input samples at the end of input to guarantee at
+    // *least* the latency's worth of output samples. that is,
+
+    int inputPad = int(ceil((double(latency) * sourceRate) / targetRate));
+
+    // that means we are providing this much input in total:
+
+    int n1 = n + inputPad;
+
+    // and obtaining this much output in total:
+
+    int m1 = int(ceil((double(n1) * targetRate) / sourceRate));
+
+    // in order to return this much output to the user:
+
+    int m = int(ceil((double(n) * targetRate) / sourceRate));
+    
+#ifdef DEBUG_RESAMPLER
+    cerr << "n = " << n << ", sourceRate = " << sourceRate << ", targetRate = " << targetRate << ", m = " << m << ", latency = " << latency << ", inputPad = " << inputPad << ", m1 = " << m1 << ", n1 = " << n1 << ", n1 - n = " << n1 - n << endl;
+#endif
+
+    vector<double> pad(n1 - n, 0.0);
+    vector<double> out(m1 + 1, 0.0);
+
+    int gotData = r.process(data, out.data(), n);
+    int gotPad = r.process(pad.data(), out.data() + gotData, pad.size());
+    int got = gotData + gotPad;
+    
+#ifdef DEBUG_RESAMPLER
+    cerr << "resample: " << n << " in, " << pad.size() << " padding, " << got << " out (" << gotData << " data, " << gotPad << " padding, latency = " << latency << ")" << endl;
+#endif
+#ifdef DEBUG_RESAMPLER_VERBOSE
+    int printN = 50;
+    cerr << "first " << printN << " in:" << endl;
+    for (int i = 0; i < printN && i < n; ++i) {
+	if (i % 5 == 0) cerr << endl << i << "... ";
+        cerr << data[i] << " ";
+    }
+    cerr << endl;
+#endif
+
+    int toReturn = got - latency;
+    if (toReturn > m) toReturn = m;
+
+    vector<double> sliced(out.begin() + latency, 
+			  out.begin() + latency + toReturn);
+
+#ifdef DEBUG_RESAMPLER_VERBOSE
+    cerr << "first " << printN << " out (after latency compensation), length " << sliced.size() << ":";
+    for (int i = 0; i < printN && i < sliced.size(); ++i) {
+	if (i % 5 == 0) cerr << endl << i << "... ";
+	cerr << sliced[i] << " ";
+    }
+    cerr << endl;
+#endif
+
+    return sliced;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dsp/Resampler.h	Thu May 15 12:24:11 2014 +0100
@@ -0,0 +1,119 @@
+/* -*- 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 RESAMPLER_H
+#define RESAMPLER_H
+
+#include <vector>
+
+/**
+ * Resampler resamples a stream from one integer sample rate to
+ * another (arbitrary) rate, using a kaiser-windowed sinc filter.  The
+ * results and performance are pretty similar to libraries such as
+ * libsamplerate, though this implementation does not support
+ * time-varying ratios (the ratio is fixed on construction).
+ *
+ * See also Decimator, which is faster and rougher but supports only
+ * power-of-two downsampling factors.
+ */
+class Resampler
+{
+public:
+    /**
+     * Construct a Resampler to resample from sourceRate to
+     * targetRate.
+     */
+    Resampler(int sourceRate, int targetRate);
+
+    /**
+     * Construct a Resampler to resample from sourceRate to
+     * targetRate, using the given filter parameters.
+     */
+    Resampler(int sourceRate, int targetRate,
+              double snr, double bandwidth);
+
+    virtual ~Resampler();
+
+    /**
+     * Read n input samples from src and write resampled data to
+     * dst. The return value is the number of samples written, which
+     * will be no more than ceil((n * targetRate) / sourceRate). The
+     * caller must ensure the dst buffer has enough space for the
+     * samples returned.
+     */
+    int process(const double *src, double *dst, int n);
+
+    /**
+     * Read n input samples from src and return resampled data by
+     * value.
+     */
+    std::vector<double> process(const double *src, int n);
+
+    /**
+     * Return the number of samples of latency at the output due by
+     * the filter. (That is, the output will be delayed by this number
+     * of samples relative to the input.)
+     */
+    int getLatency() const { return m_latency; }
+
+    /**
+     * Carry out a one-off resample of a single block of n
+     * samples. The output is latency-compensated.
+     */
+    static std::vector<double> resample
+    (int sourceRate, int targetRate, const double *data, int n);
+
+private:
+    int m_sourceRate;
+    int m_targetRate;
+    int m_gcd;
+    int m_filterLength;
+    int m_bufferLength;
+    int m_latency;
+    double m_peakToPole;
+    
+    struct Phase {
+        int nextPhase;
+        std::vector<double> filter;
+        int drop;
+    };
+
+    Phase *m_phaseData;
+    int m_phase;
+    std::vector<double> m_buffer;
+    int m_bufferOrigin;
+
+    void initialise(double, double);
+    double reconstructOne();
+};
+
+#endif
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dsp/SincWindow.cpp	Thu May 15 12:24:11 2014 +0100
@@ -0,0 +1,63 @@
+/* -*- 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 "SincWindow.h"
+
+#include <cmath>
+
+void
+SincWindow::init()
+{
+    if (m_length < 1) {
+	return;
+    } else if (m_length < 2) {
+	m_window.push_back(1);
+	return;
+    } else {
+
+	int n0 = (m_length % 2 == 0 ? m_length/2 : (m_length - 1)/2);
+	int n1 = (m_length % 2 == 0 ? m_length/2 : (m_length + 1)/2);
+	double m = 2 * M_PI / m_p;
+
+	for (int i = 0; i < n0; ++i) {
+	    double x = ((m_length / 2) - i) * m;
+	    m_window.push_back(sin(x) / x);
+	}
+
+	m_window.push_back(1.0);
+
+	for (int i = 1; i < n1; ++i) {
+	    double x = i * m;
+	    m_window.push_back(sin(x) / x);
+	}
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dsp/SincWindow.h	Thu May 15 12:24:11 2014 +0100
@@ -0,0 +1,79 @@
+/* -*- 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 SINC_WINDOW_H
+#define SINC_WINDOW_H
+
+#include <vector>
+
+/**
+ * A window containing values of the sinc function, i.e. sin(x)/x with
+ * sinc(0) == 1, with x == 0 at the centre.
+ */
+class SincWindow
+{
+public:
+    /**
+     * Construct a windower of the given length, containing the values
+     * of sinc(x) with x=0 in the middle, i.e. at sample (length-1)/2
+     * for odd or (length/2)+1 for even length, such that the distance
+     * from -pi to pi (the nearest zero crossings either side of the
+     * peak) is p samples.
+     */
+    SincWindow(int length, double p) : m_length(length), m_p(p) { init(); }
+
+    int getLength() const {
+	return m_length;
+    }
+
+    const double *getWindow() const { 
+	return m_window.data();
+    }
+
+    void cut(double *src) const { 
+	cut(src, src); 
+    }
+
+    void cut(const double *src, double *dst) const {
+	for (int i = 0; i < m_length; ++i) {
+	    dst[i] = src[i] * m_window[i];
+	}
+    }
+
+private:
+    int m_length;
+    double m_p;
+    std::vector<double> m_window;
+
+    void init();
+};
+
+#endif
--- a/src/processfile.cpp	Thu May 15 12:06:31 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,294 +0,0 @@
-
-#include "ConstantQ.h"
-#include "CQInverse.h"
-
-#include <sndfile.h>
-
-#include <iostream>
-
-using std::vector;
-using std::cerr;
-using std::endl;
-
-#include <cstring>
-
-#include <getopt.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include <cstdlib>
-
-int main(int argc, char **argv)
-{
-    double maxFreq = 0;
-    double minFreq = 0;
-    int bpo = 0;
-    bool help = false;
-    
-    int c;
-
-    while (1) {
-	int optionIndex = 0;
-	
-	static struct option longOpts[] = {
-	    { "help", 0, 0, 'h', },
-	    { "maxfreq", 1, 0, 'x', },
-	    { "minfreq", 1, 0, 'n', },
-	    { "bpo", 1, 0, 'b' },
-	    { 0, 0, 0, 0 },
-	};
-
-	c = getopt_long(argc, argv,
-			"hx:n:b:",
-			longOpts, &optionIndex);
-	if (c == -1) break;
-
-	switch (c) {
-	case 'h': help = true; break;
-	case 'x': maxFreq = atof(optarg); break;
-	case 'n': minFreq = atof(optarg); break;
-	case 'b': bpo = atoi(optarg); break;
-	default: help = true; break;
-	}
-    }
-
-    if (help || (optind + 2 != argc && optind + 3 != argc)) {
-	cerr << endl;
-	cerr << "Usage: " << argv[0] << " [options] infile.wav outfile.wav [differencefile.wav]" << endl;
-	cerr << endl;
-	cerr << "Options:" << endl;
-	cerr << "  -x<X>, --maxfreq <X>  Maximum frequency (default = sample rate / 3)" << endl;
-	cerr << "  -n<X>, --minfreq <X>  Minimum frequency (default = 100, actual min may vary)" << endl;
-	cerr << "  -b<X>, --bpo <X>      Bins per octave   (default = 60)" << endl;
-	cerr << "  -h, --help            Print this help" << endl;
-	cerr << endl;
-	cerr << "This rather useless program simply performs a forward Constant-Q transform with" << endl;
-	cerr << "the requested parameters, followed by its inverse, and writes the result to the" << endl;
-	cerr << "output file. If a diff file name is provided, the difference between input and" << endl;
-	cerr << "output signals is also written to that. All this accomplishes is to produce a" << endl;
-	cerr << "signal that approximates the input: it's intended for test purposes only." << endl;
-	cerr << endl;
-	cerr << "(Want to calculate and obtain a Constant-Q spectrogram? Use the CQVamp plugin" << endl;
-	cerr << "in a Vamp plugin host.)" << endl;
-	cerr << endl;
-	return 2;
-    }
-
-    char *fileName = strdup(argv[optind++]);
-    char *fileNameOut = strdup(argv[optind++]);
-    char *diffFileName = (optind < argc ? strdup(argv[optind++]) : 0);
-    bool doDiff = (diffFileName != 0);
-
-    SNDFILE *sndfile;
-    SNDFILE *sndfileOut;
-    SNDFILE *sndDiffFile = 0;
-    SF_INFO sfinfo;
-    SF_INFO sfinfoOut;
-    SF_INFO sfinfoDiff;
-    memset(&sfinfo, 0, sizeof(SF_INFO));
-
-    sndfile = sf_open(fileName, SFM_READ, &sfinfo);
-    if (!sndfile) {
-	cerr << "ERROR: Failed to open input file \"" << fileName << "\": "
-	     << sf_strerror(sndfile) << endl;
-	return 1;
-    }
-
-    sfinfoOut.channels = 1;
-    sfinfoOut.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
-    sfinfoOut.frames = sfinfo.frames;
-    sfinfoOut.samplerate = sfinfo.samplerate;
-    sfinfoOut.sections = sfinfo.sections;
-    sfinfoOut.seekable = sfinfo.seekable;
-
-    sndfileOut = sf_open(fileNameOut, SFM_WRITE, &sfinfoOut) ;
-    if (!sndfileOut) {
-	cerr << "ERROR: Failed to open output file \"" << fileNameOut << "\" for writing: "
-	     << sf_strerror(sndfileOut) << endl;
-	return 1;
-    }
-
-    if (doDiff) {
-	sfinfoDiff = sfinfoOut;
-	sndDiffFile = sf_open(diffFileName, SFM_WRITE, &sfinfoDiff);
-	if (!sndDiffFile) {
-	    cerr << "ERROR: Failed to open diff output file \"" << diffFileName << "\" for writing: "
-		 << sf_strerror(sndDiffFile) << endl;
-	    return 1;
-	}
-    }
-
-    int ibs = 1024;
-    int channels = sfinfo.channels;
-    float *fbuf = new float[channels * ibs];
-
-    if (maxFreq == 0.0) maxFreq = sfinfo.samplerate / 3;
-    if (minFreq == 0.0) minFreq = 100;
-    if (bpo == 0) bpo = 60;
-
-    ConstantQ cq(sfinfo.samplerate, minFreq, maxFreq, bpo);
-    CQInverse cqi(sfinfo.samplerate, minFreq, maxFreq, bpo);
-
-    cerr << "max freq = " << cq.getMaxFrequency() << ", min freq = "
-	 << cq.getMinFrequency() << ", octaves = " << cq.getOctaves() << endl;
-
-    cerr << "octave boundaries: ";
-    for (int i = 0; i < cq.getOctaves(); ++i) {
-	cerr << cq.getMaxFrequency() / pow(2, i) << " ";
-    }
-    cerr << endl;
-
-    int inframe = 0;
-    int outframe = 0;
-    int latency = cq.getLatency() + cqi.getLatency();
-
-    vector<double> buffer;
-
-    double maxdiff = 0.0;
-    int maxdiffidx = 0;
-
-    cerr << "forward latency = " << cq.getLatency() << ", inverse latency = " 
-	 << cqi.getLatency() << ", total = " << latency << endl;
-
-    timeval tv;
-    (void)gettimeofday(&tv, 0);
-
-    while (inframe < sfinfo.frames) {
-
-        int count = -1;
-	
-	if ((count = sf_readf_float(sndfile, fbuf, ibs)) < 0) {
-	    break;
-	}
-
-	vector<double> cqin;
-	for (int i = 0; i < count; ++i) {
-	    double v = fbuf[i * channels];
-	    if (channels > 1) {
-		for (int c = 1; c < channels; ++c) {
-		    v += fbuf[i * channels + c];
-		}
-		v /= channels;
-	    }
-	    cqin.push_back(v);
-	}
-
-	if (doDiff) {
-	    buffer.insert(buffer.end(), cqin.begin(), cqin.end());
-	}
-	
-	vector<double> cqout = cqi.process(cq.process(cqin));
-
-	for (int i = 0; i < int(cqout.size()); ++i) {
-	    if (cqout[i] > 1.0) cqout[i] = 1.0;
-	    if (cqout[i] < -1.0) cqout[i] = -1.0;
-	}
-
-	if (outframe >= latency) {
-
-	    sf_writef_double(sndfileOut, 
-			     cqout.data(), 
-			     cqout.size());
-
-	} else if (outframe + (int)cqout.size() >= latency) {
-
-	    int offset = latency - outframe;
-	    sf_writef_double(sndfileOut, 
-			     cqout.data() + offset,
-			     cqout.size() - offset);
-	}
-
-	if (doDiff) {
-	    for (int i = 0; i < (int)cqout.size(); ++i) {
-		if (outframe + i >= latency) {
-		    int dframe = outframe + i - latency;
-		    if (dframe >= (int)buffer.size()) cqout[i] = 0;
-		    else cqout[i] -= buffer[dframe];
-		    if (fabs(cqout[i]) > maxdiff &&
-			dframe > sfinfo.samplerate && // ignore first/last sec
-			dframe + sfinfo.samplerate < sfinfo.frames) {
-			maxdiff = fabs(cqout[i]);
-			maxdiffidx = dframe;
-		    }
-		}
-	    }
-	    
-	    if (outframe >= latency) {
-
-		sf_writef_double(sndDiffFile, 
-				 cqout.data(), 
-				 cqout.size());
-
-	    } else if (outframe + (int)cqout.size() >= latency) {
-
-		int offset = latency - outframe;
-		sf_writef_double(sndDiffFile, 
-				 cqout.data() + offset,
-				 cqout.size() - offset);
-	    }
-	}
-
-	inframe += count;
-	outframe += cqout.size();
-    }
-
-    vector<double> r = cqi.process(cq.getRemainingOutput());
-    vector<double> r2 = cqi.getRemainingOutput();
-
-    r.insert(r.end(), r2.begin(), r2.end());
-
-    for (int i = 0; i < int(r.size()); ++i) {
-	if (r[i] > 1.0) r[i] = 1.0;
-	if (r[i] < -1.0) r[i] = -1.0;
-    }
-
-    sf_writef_double(sndfileOut, r.data(), r.size());
-    if (doDiff) {
-	for (int i = 0; i < (int)r.size(); ++i) {
-	    if (outframe + i >= latency) {
-		int dframe = outframe + i - latency;
-		if (dframe >= (int)buffer.size()) r[i] = 0;
-		else r[i] -= buffer[dframe];
-		if (fabs(r[i]) > maxdiff &&
-		    dframe > sfinfo.samplerate && // ignore first/last sec
-		    dframe + sfinfo.samplerate < sfinfo.frames) {
-		    maxdiff = fabs(r[i]);
-		    maxdiffidx = dframe;
-		}
-	    }
-	}
-	sf_writef_double(sndDiffFile, r.data(), r.size());
-    }
-    outframe += r.size();
-
-    sf_close(sndfile);
-    sf_close(sndfileOut);
-
-    if (doDiff) {
-	sf_close(sndDiffFile);
-    }
-
-    cerr << "in: " << inframe << ", out: " << outframe - latency << endl;
-
-    if (doDiff) {
-	double db = 10 * log10(maxdiff);
-	cerr << "max diff [excluding first and last second of audio] is "
-	     << maxdiff << " (" << db << " dBFS)"
-	     << " at sample index " << maxdiffidx << endl;
-    }
-    
-    timeval etv;
-    (void)gettimeofday(&etv, 0);
-        
-    etv.tv_sec -= tv.tv_sec;
-    if (etv.tv_usec < tv.tv_usec) {
-	etv.tv_usec += 1000000;
-	etv.tv_sec -= 1;
-    }
-    etv.tv_usec -= tv.tv_usec;
-        
-    double sec = double(etv.tv_sec) + (double(etv.tv_usec) / 1000000.0);
-    cerr << "elapsed time (not counting init): " << sec << " sec, frames/sec at input: " << inframe/sec << endl;
-
-    return 0;
-}
-
--- a/src/test.cpp	Thu May 15 12:06:31 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/*
-    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 <vector>
-
-using std::vector;
-using std::cerr;
-using std::cout;
-using std::endl;
-
-#include <cstdio>
-
-int main(int argc, char **argv)
-{
-    vector<double> in;
-
-    for (int i = 0; i < 64; ++i) {
-//	if (i == 0) in.push_back(1);
-//	else in.push_back(0);
-	in.push_back(sin(i * M_PI / 2.0));
-    }
-
-    CQSpectrogram k(8, 1, 4, 4, CQSpectrogram::InterpolateZeros);
-
-    vector<vector<double> > out = k.process(in);
-    vector<vector<double> > rest = k.getRemainingOutput();
-
-    out.insert(out.end(), rest.begin(), rest.end());
-
-    cerr << "got " << out.size() << " back (" << out[0].size() << " in each?)" << endl;
-
-    for (int b = 0; b < (int)out.size() / 8; ++b) {
-	printf("\nColumns %d to %d:\n\n", b * 8, b * 8 + 7);
-	for (int j = int(out[0].size()) - 1; j >= 0; --j) {
-	    for (int i = 0; i < 8; ++i) {
-		if (i + b * 8 < (int)out.size()) {
-		    double v = out[i + b * 8][j];
-		    if (v < 0.0001) printf("  0      ");
-		    else printf("  %.4f ", out[i + b * 8][j]);
-		}
-	    }
-	    printf("\n");
-	}
-    }
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/processfile.cpp	Thu May 15 12:24:11 2014 +0100
@@ -0,0 +1,294 @@
+
+#include "ConstantQ.h"
+#include "CQInverse.h"
+
+#include <sndfile.h>
+
+#include <iostream>
+
+using std::vector;
+using std::cerr;
+using std::endl;
+
+#include <cstring>
+
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <cstdlib>
+
+int main(int argc, char **argv)
+{
+    double maxFreq = 0;
+    double minFreq = 0;
+    int bpo = 0;
+    bool help = false;
+    
+    int c;
+
+    while (1) {
+	int optionIndex = 0;
+	
+	static struct option longOpts[] = {
+	    { "help", 0, 0, 'h', },
+	    { "maxfreq", 1, 0, 'x', },
+	    { "minfreq", 1, 0, 'n', },
+	    { "bpo", 1, 0, 'b' },
+	    { 0, 0, 0, 0 },
+	};
+
+	c = getopt_long(argc, argv,
+			"hx:n:b:",
+			longOpts, &optionIndex);
+	if (c == -1) break;
+
+	switch (c) {
+	case 'h': help = true; break;
+	case 'x': maxFreq = atof(optarg); break;
+	case 'n': minFreq = atof(optarg); break;
+	case 'b': bpo = atoi(optarg); break;
+	default: help = true; break;
+	}
+    }
+
+    if (help || (optind + 2 != argc && optind + 3 != argc)) {
+	cerr << endl;
+	cerr << "Usage: " << argv[0] << " [options] infile.wav outfile.wav [differencefile.wav]" << endl;
+	cerr << endl;
+	cerr << "Options:" << endl;
+	cerr << "  -x<X>, --maxfreq <X>  Maximum frequency (default = sample rate / 3)" << endl;
+	cerr << "  -n<X>, --minfreq <X>  Minimum frequency (default = 100, actual min may vary)" << endl;
+	cerr << "  -b<X>, --bpo <X>      Bins per octave   (default = 60)" << endl;
+	cerr << "  -h, --help            Print this help" << endl;
+	cerr << endl;
+	cerr << "This rather useless program simply performs a forward Constant-Q transform with" << endl;
+	cerr << "the requested parameters, followed by its inverse, and writes the result to the" << endl;
+	cerr << "output file. If a diff file name is provided, the difference between input and" << endl;
+	cerr << "output signals is also written to that. All this accomplishes is to produce a" << endl;
+	cerr << "signal that approximates the input: it's intended for test purposes only." << endl;
+	cerr << endl;
+	cerr << "(Want to calculate and obtain a Constant-Q spectrogram? Use the CQVamp plugin" << endl;
+	cerr << "in a Vamp plugin host.)" << endl;
+	cerr << endl;
+	return 2;
+    }
+
+    char *fileName = strdup(argv[optind++]);
+    char *fileNameOut = strdup(argv[optind++]);
+    char *diffFileName = (optind < argc ? strdup(argv[optind++]) : 0);
+    bool doDiff = (diffFileName != 0);
+
+    SNDFILE *sndfile;
+    SNDFILE *sndfileOut;
+    SNDFILE *sndDiffFile = 0;
+    SF_INFO sfinfo;
+    SF_INFO sfinfoOut;
+    SF_INFO sfinfoDiff;
+    memset(&sfinfo, 0, sizeof(SF_INFO));
+
+    sndfile = sf_open(fileName, SFM_READ, &sfinfo);
+    if (!sndfile) {
+	cerr << "ERROR: Failed to open input file \"" << fileName << "\": "
+	     << sf_strerror(sndfile) << endl;
+	return 1;
+    }
+
+    sfinfoOut.channels = 1;
+    sfinfoOut.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
+    sfinfoOut.frames = sfinfo.frames;
+    sfinfoOut.samplerate = sfinfo.samplerate;
+    sfinfoOut.sections = sfinfo.sections;
+    sfinfoOut.seekable = sfinfo.seekable;
+
+    sndfileOut = sf_open(fileNameOut, SFM_WRITE, &sfinfoOut) ;
+    if (!sndfileOut) {
+	cerr << "ERROR: Failed to open output file \"" << fileNameOut << "\" for writing: "
+	     << sf_strerror(sndfileOut) << endl;
+	return 1;
+    }
+
+    if (doDiff) {
+	sfinfoDiff = sfinfoOut;
+	sndDiffFile = sf_open(diffFileName, SFM_WRITE, &sfinfoDiff);
+	if (!sndDiffFile) {
+	    cerr << "ERROR: Failed to open diff output file \"" << diffFileName << "\" for writing: "
+		 << sf_strerror(sndDiffFile) << endl;
+	    return 1;
+	}
+    }
+
+    int ibs = 1024;
+    int channels = sfinfo.channels;
+    float *fbuf = new float[channels * ibs];
+
+    if (maxFreq == 0.0) maxFreq = sfinfo.samplerate / 3;
+    if (minFreq == 0.0) minFreq = 100;
+    if (bpo == 0) bpo = 60;
+
+    ConstantQ cq(sfinfo.samplerate, minFreq, maxFreq, bpo);
+    CQInverse cqi(sfinfo.samplerate, minFreq, maxFreq, bpo);
+
+    cerr << "max freq = " << cq.getMaxFrequency() << ", min freq = "
+	 << cq.getMinFrequency() << ", octaves = " << cq.getOctaves() << endl;
+
+    cerr << "octave boundaries: ";
+    for (int i = 0; i < cq.getOctaves(); ++i) {
+	cerr << cq.getMaxFrequency() / pow(2, i) << " ";
+    }
+    cerr << endl;
+
+    int inframe = 0;
+    int outframe = 0;
+    int latency = cq.getLatency() + cqi.getLatency();
+
+    vector<double> buffer;
+
+    double maxdiff = 0.0;
+    int maxdiffidx = 0;
+
+    cerr << "forward latency = " << cq.getLatency() << ", inverse latency = " 
+	 << cqi.getLatency() << ", total = " << latency << endl;
+
+    timeval tv;
+    (void)gettimeofday(&tv, 0);
+
+    while (inframe < sfinfo.frames) {
+
+        int count = -1;
+	
+	if ((count = sf_readf_float(sndfile, fbuf, ibs)) < 0) {
+	    break;
+	}
+
+	vector<double> cqin;
+	for (int i = 0; i < count; ++i) {
+	    double v = fbuf[i * channels];
+	    if (channels > 1) {
+		for (int c = 1; c < channels; ++c) {
+		    v += fbuf[i * channels + c];
+		}
+		v /= channels;
+	    }
+	    cqin.push_back(v);
+	}
+
+	if (doDiff) {
+	    buffer.insert(buffer.end(), cqin.begin(), cqin.end());
+	}
+	
+	vector<double> cqout = cqi.process(cq.process(cqin));
+
+	for (int i = 0; i < int(cqout.size()); ++i) {
+	    if (cqout[i] > 1.0) cqout[i] = 1.0;
+	    if (cqout[i] < -1.0) cqout[i] = -1.0;
+	}
+
+	if (outframe >= latency) {
+
+	    sf_writef_double(sndfileOut, 
+			     cqout.data(), 
+			     cqout.size());
+
+	} else if (outframe + (int)cqout.size() >= latency) {
+
+	    int offset = latency - outframe;
+	    sf_writef_double(sndfileOut, 
+			     cqout.data() + offset,
+			     cqout.size() - offset);
+	}
+
+	if (doDiff) {
+	    for (int i = 0; i < (int)cqout.size(); ++i) {
+		if (outframe + i >= latency) {
+		    int dframe = outframe + i - latency;
+		    if (dframe >= (int)buffer.size()) cqout[i] = 0;
+		    else cqout[i] -= buffer[dframe];
+		    if (fabs(cqout[i]) > maxdiff &&
+			dframe > sfinfo.samplerate && // ignore first/last sec
+			dframe + sfinfo.samplerate < sfinfo.frames) {
+			maxdiff = fabs(cqout[i]);
+			maxdiffidx = dframe;
+		    }
+		}
+	    }
+	    
+	    if (outframe >= latency) {
+
+		sf_writef_double(sndDiffFile, 
+				 cqout.data(), 
+				 cqout.size());
+
+	    } else if (outframe + (int)cqout.size() >= latency) {
+
+		int offset = latency - outframe;
+		sf_writef_double(sndDiffFile, 
+				 cqout.data() + offset,
+				 cqout.size() - offset);
+	    }
+	}
+
+	inframe += count;
+	outframe += cqout.size();
+    }
+
+    vector<double> r = cqi.process(cq.getRemainingOutput());
+    vector<double> r2 = cqi.getRemainingOutput();
+
+    r.insert(r.end(), r2.begin(), r2.end());
+
+    for (int i = 0; i < int(r.size()); ++i) {
+	if (r[i] > 1.0) r[i] = 1.0;
+	if (r[i] < -1.0) r[i] = -1.0;
+    }
+
+    sf_writef_double(sndfileOut, r.data(), r.size());
+    if (doDiff) {
+	for (int i = 0; i < (int)r.size(); ++i) {
+	    if (outframe + i >= latency) {
+		int dframe = outframe + i - latency;
+		if (dframe >= (int)buffer.size()) r[i] = 0;
+		else r[i] -= buffer[dframe];
+		if (fabs(r[i]) > maxdiff &&
+		    dframe > sfinfo.samplerate && // ignore first/last sec
+		    dframe + sfinfo.samplerate < sfinfo.frames) {
+		    maxdiff = fabs(r[i]);
+		    maxdiffidx = dframe;
+		}
+	    }
+	}
+	sf_writef_double(sndDiffFile, r.data(), r.size());
+    }
+    outframe += r.size();
+
+    sf_close(sndfile);
+    sf_close(sndfileOut);
+
+    if (doDiff) {
+	sf_close(sndDiffFile);
+    }
+
+    cerr << "in: " << inframe << ", out: " << outframe - latency << endl;
+
+    if (doDiff) {
+	double db = 10 * log10(maxdiff);
+	cerr << "max diff [excluding first and last second of audio] is "
+	     << maxdiff << " (" << db << " dBFS)"
+	     << " at sample index " << maxdiffidx << endl;
+    }
+    
+    timeval etv;
+    (void)gettimeofday(&etv, 0);
+        
+    etv.tv_sec -= tv.tv_sec;
+    if (etv.tv_usec < tv.tv_usec) {
+	etv.tv_usec += 1000000;
+	etv.tv_sec -= 1;
+    }
+    etv.tv_usec -= tv.tv_usec;
+        
+    double sec = double(etv.tv_sec) + (double(etv.tv_usec) / 1000000.0);
+    cerr << "elapsed time (not counting init): " << sec << " sec, frames/sec at input: " << inframe/sec << endl;
+
+    return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test.cpp	Thu May 15 12:24:11 2014 +0100
@@ -0,0 +1,76 @@
+/*
+    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 <vector>
+
+using std::vector;
+using std::cerr;
+using std::cout;
+using std::endl;
+
+#include <cstdio>
+
+int main(int argc, char **argv)
+{
+    vector<double> in;
+
+    for (int i = 0; i < 64; ++i) {
+//	if (i == 0) in.push_back(1);
+//	else in.push_back(0);
+	in.push_back(sin(i * M_PI / 2.0));
+    }
+
+    CQSpectrogram k(8, 1, 4, 4, CQSpectrogram::InterpolateZeros);
+
+    vector<vector<double> > out = k.process(in);
+    vector<vector<double> > rest = k.getRemainingOutput();
+
+    out.insert(out.end(), rest.begin(), rest.end());
+
+    cerr << "got " << out.size() << " back (" << out[0].size() << " in each?)" << endl;
+
+    for (int b = 0; b < (int)out.size() / 8; ++b) {
+	printf("\nColumns %d to %d:\n\n", b * 8, b * 8 + 7);
+	for (int j = int(out[0].size()) - 1; j >= 0; --j) {
+	    for (int i = 0; i < 8; ++i) {
+		if (i + b * 8 < (int)out.size()) {
+		    double v = out[i + b * 8][j];
+		    if (v < 0.0001) printf("  0      ");
+		    else printf("  %.4f ", out[i + b * 8][j]);
+		}
+	    }
+	    printf("\n");
+	}
+    }
+}
+