Mercurial > hg > constant-q-cpp
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;