changeset 7:4a777e3b515e

Details of output sample rate / step size calculation, plus copyright etc
author Chris Cannam
date Wed, 12 Mar 2014 11:50:40 +0000
parents e502cf649389
children c037af3977e1
files COPYING LowFreq.cpp LowFreq.h README plugins.cpp
diffstat 5 files changed, 213 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING	Wed Mar 12 11:50:40 2014 +0000
@@ -0,0 +1,31 @@
+
+    Low-frequency spectrogram
+    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.
+
+
+
--- a/LowFreq.cpp	Tue Mar 11 18:36:24 2014 +0000
+++ b/LowFreq.cpp	Wed Mar 12 11:50:40 2014 +0000
@@ -1,3 +1,33 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    Low-frequency spectrogram
+    Copyright (c) 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 "LowFreq.h"
 
@@ -21,12 +51,12 @@
 static float defaultFmax = 10;
 
 static int minN = 1;
-static int maxN = 16384;
+static int maxN = 2048;
 static int defaultN = 64;
 
 static float minOverlap = 0.0;
-static float maxOverlap = 87.5;
-static float overlapStep = 12.5;
+static float maxOverlap = 90.0;
+static float overlapStep = 5;
 static float defaultOverlap = 50.0;
 
 LowFreq::LowFreq(float inputSampleRate) :
@@ -40,6 +70,14 @@
     m_fft(0),
     m_window(0)
 {
+    m_nonIntegralInputRate = false;
+    if (fabs(double(m_inputSampleRate) - round(m_inputSampleRate)) > 1e-6) {
+	m_nonIntegralInputRate = true;
+    }
+
+    if (inputSampleRate / 4 < maxFmax) {
+        maxFmax = inputSampleRate / 4;
+    }
 }
 
 LowFreq::~LowFreq()
@@ -155,7 +193,7 @@
 
     d.identifier = "overlap";
     d.name = "Frame overlap";
-    d.description = "Amount of overlap between one Hann-windowed frame and the next. Should be at least 50% to avoid gaps due to windowing.";
+    d.description = "Amount of overlap between one Hann-windowed frame and the next. Should be at least 50% to avoid gaps due to windowing. This may be rounded, sometimes drastically, to ensure that output columns fall on integral sample boundaries in the input signal.";
     d.unit = "%";
     d.minValue = minOverlap;
     d.maxValue = maxOverlap;
@@ -230,11 +268,11 @@
     d.hasKnownExtents = false;
     d.isQuantized = false;
     d.sampleType = OutputDescriptor::FixedSampleRate;
+    d.sampleRate = getOutputSampleRate();
 
-    d.sampleRate = float(getTargetSampleRate()) / float(getTargetStepSize());
-
-//!!! We need to make sure the input sample rate / d.sampleRate is an
-//!!! integer (otherwise e.g. SV won't display correctly)
+    cerr << "output descriptor effective sample rate = " << d.sampleRate << endl;
+    cerr << "input sample rate = " << m_inputSampleRate << endl;
+    cerr << "input rate / output rate = " << m_inputSampleRate / d.sampleRate << endl;
     
     char namebuf[50];
     for (int i = 0; i < m_n; ++i) {
@@ -242,8 +280,6 @@
 	d.binNames.push_back(namebuf);
     }
 
-    cerr << "output descriptor effective sample rate = " << d.sampleRate << endl;
-
     d.hasDuration = false;
     list.push_back(d);
 
@@ -297,7 +333,7 @@
 	return false;
     }
 
-    if (fabs(m_inputSampleRate - round(m_inputSampleRate)) > 1e-6) {
+    if (m_nonIntegralInputRate) {
 	cerr << "LowFreq::initialise: WARNING: input sample rate " 
 	     << m_inputSampleRate << " is non-integral, output frequencies "
 	     << "will be skewed by rounding it to nearest integer" << endl;
@@ -347,8 +383,6 @@
 	 << ", resampler latency " << m_resampler->getLatency()
 	 << ", fft size " << getFFTSize() << endl;
 
-    //!!! want to make sure our output sample rate is 
-
     //!!! not handling resampler latency
 }
 
@@ -361,11 +395,66 @@
     return tfs;
 }
 
+float
+LowFreq::getOutputSampleRate() const
+{
+    return float(getTargetSampleRate()) / float(getTargetStepSize());
+}
+
+static int gcd(int a, int b)
+{
+    int c = a % b;
+    if (c == 0) {
+        return b;
+    } else {
+        return gcd(b, c);
+    }
+}
+
 int
 LowFreq::getTargetStepSize() const
 {
+    // We need to make sure that m_inputSampleRate / output sample
+    // rate is an integer, otherwise hosts like SV will end up with
+    // rounding error when counting columns with non-integral width in
+    // terms of the input file sample rate.
+    //
+    // Since the output sample rate (see getOutputSampleRate above) is
+    // target rate / target step size, and we can't mess with the
+    // target rate, we should adjust the target step size instead.
+    //
+    // So we want m_inputSampleRate / (targetRate / step) to be
+    // integral, that is, step * m_inputSampleRate == n * targetRate
+    // (where n is a positive integer) and step = n * targetRate /
+    // m_inputSampleRate. Of course, step must also be integral. We
+    // can achieve this by rounding step to the nearest multiple of
+    // targetRate / g, where g is the gcd of m_inputSampleRate and
+    // targetRate.
+
+    //!!! though actually it might be better not to offer overlap as
+    //!!! an option at all. Instead the target step size could always
+    //!!! just be the minimum that makes for an integral column width
+    //!!! in terms of the input sample rate.
+
     int step = int(round(getFFTSize() * (1.0 - (m_overlap / 100.0))));
-    cerr << "LowFreq::getTargetStepSize: step is " << step << endl;
+    cerr << "LowFreq::getTargetStepSize: preferred step is " << step << endl;
+
+    // Note: if the input sample rate turns out not to be integral
+    // anyway, then we don't need to care about the details -- the
+    // host can evidently cope
+    if (m_nonIntegralInputRate) return step;
+
+    int g = gcd(int(round(m_inputSampleRate)), getTargetSampleRate());
+    int m = getTargetSampleRate() / g;
+    cerr << "m = " << m << endl;
+    
+    int rounded = (step / m) * m;
+    if (rounded == 0 || ((step - rounded) > (rounded + m - step))) {
+        rounded = rounded + m;
+    }
+
+    cerr << "rounded step to " << rounded << " giving ri / ro = " << (m_inputSampleRate * rounded) / getTargetSampleRate() << endl;
+    
     return step;
 }    
 
@@ -402,8 +491,6 @@
 	data[i] = inputBuffers[0][i];
     }
 
-    cout << "timestamp: " << timestamp << endl;
-
     vector<double> resampled = m_resampler->process(data, m_blockSize);
     m_buffer.insert(m_buffer.end(), resampled.begin(), resampled.end());
 
@@ -451,23 +538,14 @@
     double *windowed = new double[sz];
     m_window->cut(m_buffer.data(), windowed);	
 
-    cout << "in: ";
-    for (int i = 0; i < sz; ++i) {
-	cout << windowed[i] << " ";
-    }
-    cout << endl;
-
     m_fft->process(false, windowed, 0, realOut, imagOut);
 
-    cout << "out: ";
     int base = getFirstOutputBin();
     for (int i = 0; i < m_n; ++i) {
 	int ix = base + i;
 	float mag = (realOut[ix] * realOut[ix] + imagOut[ix] * imagOut[ix]);
 	f.values.push_back(mag);
-	cout << mag << " ";
     }
-    cout << endl;
 
     return f;
 }
@@ -475,8 +553,6 @@
 void
 LowFreq::advance()
 {
-    cerr << "step = " << getTargetStepSize() << endl;
-    
     std::vector<double> advanced(m_buffer.data() + getTargetStepSize(),
 				 m_buffer.data() + m_buffer.size());
     
--- a/LowFreq.h	Tue Mar 11 18:36:24 2014 +0000
+++ b/LowFreq.h	Wed Mar 12 11:50:40 2014 +0000
@@ -1,3 +1,34 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    Low-frequency spectrogram
+    Copyright (c) 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 _LOWFREQ_H_
 #define _LOWFREQ_H_
 
@@ -56,6 +87,7 @@
     int getFFTSize() const;
     int getFirstOutputBin() const;
     float getOutputBinFrequency(int i) const;
+    float getOutputSampleRate() const;
 
     float m_fmin;
     float m_fmax;
@@ -64,6 +96,8 @@
 
     int m_blockSize;
 
+    bool m_nonIntegralInputRate;
+
     Resampler *m_resampler;
     FFT *m_fft;
     Window<double> *m_window;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Wed Mar 12 11:50:40 2014 +0000
@@ -0,0 +1,15 @@
+
+Low-frequency spectrogram
+=========================
+
+This is a Vamp plugin that produces a spectrogram from only the
+low-frequency content of a signal.
+
+You supply a frequency range and a number of bins, and it returns a
+magnitude spectrum containing the requested number of bins, spanning
+the frequency range you asked for. It does this by first downsampling
+the signal to an integer multiple of the maximum requested frequency,
+then calculating a Hann-windowed FFT of an appropriate size and
+returning a subset of its magnitudes.
+
+Chris Cannam, March 2014
--- a/plugins.cpp	Tue Mar 11 18:36:24 2014 +0000
+++ b/plugins.cpp	Wed Mar 12 11:50:40 2014 +0000
@@ -1,3 +1,33 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    Low-frequency spectrogram
+    Copyright (c) 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 <vamp/vamp.h>
 #include <vamp-sdk/PluginAdapter.h>