# HG changeset patch # User Chris Cannam # Date 1400072674 -3600 # Node ID fdd32f995b0d029d1f6c253c5d52b5ceb72dbc58 # Parent f5325762ff6dee0223473a212887956b464c4666 First cut at a chromagram plugin as well diff -r f5325762ff6d -r fdd32f995b0d Makefile.inc --- a/Makefile.inc Wed May 14 12:49:10 2014 +0100 +++ b/Makefile.inc Wed May 14 14:04:34 2014 +0100 @@ -25,8 +25,8 @@ 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 +VAMP_HEADERS := $(VAMP_DIR)/CQVamp.h $(VAMP_DIR)/CQChromaVamp.h +VAMP_SOURCES := $(VAMP_DIR)/CQVamp.cpp $(VAMP_DIR)/CQChromaVamp.cpp $(VAMP_DIR)/libmain.cpp HEADERS := $(LIB_HEADERS) $(VAMP_HEADERS) SOURCES := $(LIB_SOURCES) $(VAMP_SOURCES) @@ -72,6 +72,9 @@ 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/CQChromaVamp.o: vamp/CQChromaVamp.h cpp-qm-dsp/CQSpectrogram.h +vamp/CQChromaVamp.o: cpp-qm-dsp/ConstantQ.h cpp-qm-dsp/CQBase.h +vamp/CQChromaVamp.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 diff -r f5325762ff6d -r fdd32f995b0d cqvamp.cat --- a/cqvamp.cat Wed May 14 12:49:10 2014 +0100 +++ b/cqvamp.cat Wed May 14 14:04:34 2014 +0100 @@ -1,1 +1,3 @@ vamp:cqvamp:cqvamp::Visualisation +vamp:cqvamp:cqvampmidi::Visualisation +vamp:cqvamp:cqchromavamp::Visualisation diff -r f5325762ff6d -r fdd32f995b0d vamp/CQChromaVamp.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vamp/CQChromaVamp.cpp Wed May 14 14:04:34 2014 +0100 @@ -0,0 +1,362 @@ +/* -*- 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 "CQChromaVamp.h" + +#include "cpp-qm-dsp/CQSpectrogram.h" + +#include "base/Pitch.h" + +#include +#include + +using std::string; +using std::vector; +using std::cerr; +using std::endl; + +static const int defaultLowestOctave = 0; +static const int defaultOctaveCount = 10; +static const int defaultBPO = 36; +static const float defaultTuningFrequency = 440.f; + +CQChromaVamp::CQChromaVamp(float inputSampleRate) : + Vamp::Plugin(inputSampleRate), + m_lowestOctave(defaultLowestOctave), + m_octaveCount(defaultOctaveCount), + m_tuningFrequency(defaultTuningFrequency), + m_bpo(defaultBPO), + m_cq(0), + m_maxFrequency(0), + m_minFrequency(0), + m_haveStartTime(false), + m_columnCount(0) +{ +} + +CQChromaVamp::~CQChromaVamp() +{ + delete m_cq; +} + +string +CQChromaVamp::getIdentifier() const +{ + return "cqchromavamp"; +} + +string +CQChromaVamp::getName() const +{ + return "Chromagram"; +} + +string +CQChromaVamp::getDescription() const +{ + return "Extract a Constant-Q spectrogram with constant ratio of centre frequency to resolution from the audio, then wrapping it around into a single-octave chromagram."; +} + +string +CQChromaVamp::getMaker() const +{ + return "Queen Mary, University of London"; +} + +int +CQChromaVamp::getPluginVersion() const +{ + return 1; +} + +string +CQChromaVamp::getCopyright() const +{ + return "Plugin by Chris Cannam. Method by Christian Schörkhuber and Anssi Klapuri. Copyright (c) 2013 QMUL"; +} + +CQChromaVamp::ParameterList +CQChromaVamp::getParameterDescriptors() const +{ + ParameterList list; + + ParameterDescriptor desc; + + desc.identifier = "lowestoct"; + desc.name = "Lowest Contributing Octave"; + desc.unit = ""; + desc.description = "Octave number of the lowest octave to include in the chromagram. Octave numbering is ASA standard, with -1 as the first octave in the MIDI range and middle-C being C4. The octave starts at C."; + desc.minValue = -1; + desc.maxValue = 12; + desc.defaultValue = defaultLowestOctave; + desc.isQuantized = true; + desc.quantizeStep = 1; + list.push_back(desc); + + desc.identifier = "octaves"; + desc.name = "Contributing Octave Count"; + desc.unit = "octaves"; + desc.description = "Number of octaves to use when generating the Constant-Q transform. All octaves are wrapped around and summed to produce a single octave chromagram as output."; + desc.minValue = 1; + desc.maxValue = 12; + desc.defaultValue = defaultOctaveCount; + desc.isQuantized = true; + desc.quantizeStep = 1; + list.push_back(desc); + + desc.identifier = "tuning"; + desc.name = "Tuning Frequency"; + desc.unit = "Hz"; + desc.description = "Frequency of concert A"; + desc.minValue = 360; + desc.maxValue = 500; + desc.defaultValue = 440; + desc.isQuantized = false; + list.push_back(desc); + + desc.identifier = "bpo"; + desc.name = "Bins per Octave"; + desc.unit = "bins"; + desc.description = "Number of constant-Q transform bins per octave"; + desc.minValue = 2; + desc.maxValue = 480; + desc.defaultValue = defaultBPO; + desc.isQuantized = true; + desc.quantizeStep = 1; + list.push_back(desc); + + return list; +} + +float +CQChromaVamp::getParameter(std::string param) const +{ + if (param == "lowestoct") { + return m_lowestOctave; + } + if (param == "octaves") { + return m_octaveCount; + } + if (param == "tuning") { + return m_tuningFrequency; + } + if (param == "bpo") { + return m_bpo; + } + std::cerr << "WARNING: CQChromaVamp::getParameter: unknown parameter \"" + << param << "\"" << std::endl; + return 0.0; +} + +void +CQChromaVamp::setParameter(std::string param, float value) +{ + if (param == "lowestoct") { + m_lowestOctave = lrintf(value); + } else if (param == "octaves") { + m_octaveCount = lrintf(value); + } else if (param == "tuning") { + m_tuningFrequency = value; + } else if (param == "bpo") { + m_bpo = lrintf(value); + } else { + std::cerr << "WARNING: CQChromaVamp::setParameter: unknown parameter \"" + << param << "\"" << std::endl; + } +} + +bool +CQChromaVamp::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (m_cq) { + delete m_cq; + m_cq = 0; + } + + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_stepSize = stepSize; + m_blockSize = blockSize; + + int highestOctave = m_lowestOctave + m_octaveCount - 1; + int highestMIDIPitch = (1 + highestOctave) * 12 + 11; + + m_maxFrequency = Pitch::getFrequencyForPitch + (highestMIDIPitch, 0, m_tuningFrequency); + m_minFrequency = m_maxFrequency / pow(2, m_octaveCount + 1) * + pow(2, 1.0 / m_bpo); + + cerr << "lowest octave: " << m_lowestOctave << ", highest octave: " + << highestOctave << ", highest midi pitch: " << highestMIDIPitch + << ", min freq " << m_minFrequency << ", max freq " << m_maxFrequency + << endl; + + m_cq = new CQSpectrogram + (m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo, + CQSpectrogram::InterpolateLinear); + + return true; +} + +void +CQChromaVamp::reset() +{ + if (m_cq) { + delete m_cq; + m_cq = new CQSpectrogram + (m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo, + CQSpectrogram::InterpolateLinear); + } + m_haveStartTime = false; + m_columnCount = 0; +} + +size_t +CQChromaVamp::getPreferredStepSize() const +{ + return 0; +} + +size_t +CQChromaVamp::getPreferredBlockSize() const +{ + return 0; +} + +CQChromaVamp::OutputList +CQChromaVamp::getOutputDescriptors() const +{ + static const char *names[] = { + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" + }; + + OutputList list; + + OutputDescriptor d; + d.identifier = "chromagram"; + d.name = "Chromagram"; + d.unit = ""; + d.description = "Chromagram obtained from output of constant-Q transform, folding over each process block into a single-octave vector"; + d.hasFixedBinCount = true; + d.binCount = m_bpo; + + if (m_cq) { + char name[20]; + for (int i = 0; i < (int)d.binCount; ++i) { + float freq = m_cq->getBinFrequency(i); + int note = Pitch::getPitchForFrequency(freq, 0, m_tuningFrequency); + float nearestFreq = + Pitch::getFrequencyForPitch(note, 0, m_tuningFrequency); + sprintf(name, "%d", i); + if (fabs(freq - nearestFreq) < 0.01) { + d.binNames.push_back(name + std::string(" ") + names[note % 12]); + } else { + d.binNames.push_back(name); + } + } + } + + d.hasKnownExtents = false; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = m_inputSampleRate / (m_cq ? m_cq->getColumnHop() : 256); + list.push_back(d); + + return list; +} + +CQChromaVamp::FeatureSet +CQChromaVamp::process(const float *const *inputBuffers, + Vamp::RealTime timestamp) +{ + if (!m_cq) { + cerr << "ERROR: CQChromaVamp::process: " + << "Plugin has not been initialised" + << endl; + return FeatureSet(); + } + + if (!m_haveStartTime) { + m_startTime = timestamp; + m_haveStartTime = true; + } + + vector data; + for (int i = 0; i < m_blockSize; ++i) data.push_back(inputBuffers[0][i]); + + vector > cqout = m_cq->process(data); + return convertToFeatures(cqout); +} + +CQChromaVamp::FeatureSet +CQChromaVamp::getRemainingFeatures() +{ + vector > cqout = m_cq->getRemainingOutput(); + return convertToFeatures(cqout); +} + +CQChromaVamp::FeatureSet +CQChromaVamp::convertToFeatures(const vector > &cqout) +{ + FeatureSet returnFeatures; + + int width = cqout.size(); + + for (int i = 0; i < width; ++i) { + + vector column(m_bpo, 0.f); + + // fold and invert to put low frequencies at the start + + int thisHeight = cqout[i].size(); + for (int j = 0; j < thisHeight; ++j) { + column[m_bpo - (j % m_bpo) - 1] += cqout[i][j]; + } + + Feature feature; + feature.hasTimestamp = true; + feature.timestamp = m_startTime + Vamp::RealTime::frame2RealTime + (m_columnCount * m_cq->getColumnHop() - m_cq->getLatency(), + m_inputSampleRate); + feature.values = column; + feature.label = ""; + + if (feature.timestamp >= m_startTime) { + returnFeatures[0].push_back(feature); + } + + ++m_columnCount; + } + + return returnFeatures; +} + diff -r f5325762ff6d -r fdd32f995b0d vamp/CQChromaVamp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vamp/CQChromaVamp.h Wed May 14 14:04:34 2014 +0100 @@ -0,0 +1,91 @@ +/* -*- 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 CQCHROMAVAMP_H +#define CQCHROMAVAMP_H + +#include + +class CQSpectrogram; + +class CQChromaVamp : public Vamp::Plugin +{ +public: + CQChromaVamp(float inputSampleRate); + virtual ~CQChromaVamp(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const { return TimeDomain; } + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + ParameterList getParameterDescriptors() const; + float getParameter(std::string) const; + void setParameter(std::string, float); + + size_t getPreferredStepSize() const; + size_t getPreferredBlockSize() const; + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + int m_lowestOctave; + int m_octaveCount; + float m_tuningFrequency; + int m_bpo; + + CQSpectrogram *m_cq; + float m_maxFrequency; + float m_minFrequency; + int m_stepSize; + int m_blockSize; + + Vamp::RealTime m_startTime; + bool m_haveStartTime; + int m_columnCount; + + FeatureSet convertToFeatures(const std::vector > &); +}; + + +#endif diff -r f5325762ff6d -r fdd32f995b0d vamp/CQVamp.cpp --- a/vamp/CQVamp.cpp Wed May 14 12:49:10 2014 +0100 +++ b/vamp/CQVamp.cpp Wed May 14 14:04:34 2014 +0100 @@ -191,7 +191,7 @@ desc.description = "Number of constant-Q transform bins per octave"; desc.minValue = 2; desc.maxValue = 480; - desc.defaultValue = 60; + desc.defaultValue = defaultBPO; desc.isQuantized = true; desc.quantizeStep = 1; list.push_back(desc); @@ -302,7 +302,6 @@ (m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo, m_interpolation); } - m_prevFeature.clear(); m_haveStartTime = false; m_columnCount = 0; } @@ -329,23 +328,11 @@ const char *n = names[i % 12]; int oct = i / 12 - 1; char buf[20]; - sprintf(buf, "%s%d", n, oct); + sprintf(buf, "%d %s%d", i, n, oct); return buf; } -float -CQVamp::noteFrequency(int note) const -{ - return m_tuningFrequency * pow(2.0, (note - 69) / 12.0); -} - -int -CQVamp::noteNumber(float freq) const -{ - return int(round(57.0 + (12.0 * log(freq / (m_tuningFrequency / 2.0)) / log(2.0)))); -} - CQVamp::OutputList CQVamp::getOutputDescriptors() const { @@ -364,9 +351,11 @@ for (int i = 0; i < (int)d.binCount; ++i) { float freq = m_cq->getBinFrequency(i); sprintf(name, "%.1f Hz", freq); - if (fabs(noteFrequency(noteNumber(freq)) - freq) < 0.01) { - d.binNames.push_back(name + std::string(": ") + - noteName(noteNumber(freq))); + int note = Pitch::getPitchForFrequency(freq, 0, m_tuningFrequency); + float nearestFreq = + Pitch::getFrequencyForPitch(note, 0, m_tuningFrequency); + if (fabs(freq - nearestFreq) < 0.01) { + d.binNames.push_back(name + std::string(" ") + noteName(note)); } else { d.binNames.push_back(name); } @@ -431,8 +420,6 @@ // put low frequencies at the start std::reverse(column.begin(), column.end()); - m_prevFeature = column; - Feature feature; feature.hasTimestamp = true; feature.timestamp = m_startTime + Vamp::RealTime::frame2RealTime diff -r f5325762ff6d -r fdd32f995b0d vamp/CQVamp.h --- a/vamp/CQVamp.h Wed May 14 12:49:10 2014 +0100 +++ b/vamp/CQVamp.h Wed May 14 14:04:34 2014 +0100 @@ -89,8 +89,6 @@ int m_columnCount; std::string noteName(int i) const; - float noteFrequency(int i) const; - int noteNumber(float freq) const; std::vector m_prevFeature; FeatureSet convertToFeatures(const std::vector > &); diff -r f5325762ff6d -r fdd32f995b0d vamp/libmain.cpp --- a/vamp/libmain.cpp Wed May 14 12:49:10 2014 +0100 +++ b/vamp/libmain.cpp Wed May 14 14:04:34 2014 +0100 @@ -33,6 +33,7 @@ #include #include "CQVamp.h" +#include "CQChromaVamp.h" class CQVampPluginAdapter : public Vamp::PluginAdapterBase { @@ -54,6 +55,7 @@ static CQVampPluginAdapter midiAdapter(true); static CQVampPluginAdapter hzAdapter(false); +static Vamp::PluginAdapter chromaAdapter; const VampPluginDescriptor * vampGetPluginDescriptor(unsigned int version, unsigned int index) @@ -63,6 +65,7 @@ switch (index) { case 0: return hzAdapter.getDescriptor(); case 1: return midiAdapter.getDescriptor(); + case 2: return chromaAdapter.getDescriptor(); default: return 0; } }