annotate constant-q-cpp/vamp/CQVamp.cpp @ 372:af71cbdab621 tip

Update bqvec code
author Chris Cannam
date Tue, 19 Nov 2019 10:13:32 +0000
parents 5d0a2ebb4d17
children
rev   line source
Chris@366 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@366 2 /*
Chris@366 3 Constant-Q library
Chris@366 4 Copyright (c) 2013-2014 Queen Mary, University of London
Chris@366 5
Chris@366 6 Permission is hereby granted, free of charge, to any person
Chris@366 7 obtaining a copy of this software and associated documentation
Chris@366 8 files (the "Software"), to deal in the Software without
Chris@366 9 restriction, including without limitation the rights to use, copy,
Chris@366 10 modify, merge, publish, distribute, sublicense, and/or sell copies
Chris@366 11 of the Software, and to permit persons to whom the Software is
Chris@366 12 furnished to do so, subject to the following conditions:
Chris@366 13
Chris@366 14 The above copyright notice and this permission notice shall be
Chris@366 15 included in all copies or substantial portions of the Software.
Chris@366 16
Chris@366 17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
Chris@366 18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
Chris@366 19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
Chris@366 20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
Chris@366 21 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
Chris@366 22 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
Chris@366 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Chris@366 24
Chris@366 25 Except as contained in this notice, the names of the Centre for
Chris@366 26 Digital Music; Queen Mary, University of London; and Chris Cannam
Chris@366 27 shall not be used in advertising or otherwise to promote the sale,
Chris@366 28 use or other dealings in this Software without prior written
Chris@366 29 authorization.
Chris@366 30 */
Chris@366 31
Chris@366 32 #include "CQVamp.h"
Chris@366 33
Chris@366 34 #include "Pitch.h"
Chris@366 35
Chris@366 36 #include <algorithm>
Chris@366 37 #include <cstdio>
Chris@366 38
Chris@366 39 using std::string;
Chris@366 40 using std::vector;
Chris@366 41 using std::cerr;
Chris@366 42 using std::endl;
Chris@366 43
Chris@366 44 // The plugin offers either MIDI pitch or frequency range parameters,
Chris@366 45 // depending on the midiPitchParameters option given to the
Chris@366 46 // constructor. It never offers both. So they can have different
Chris@366 47 // defaults; if we're using MIDI pitch, the min and max frequencies
Chris@366 48 // will come from those rather than from the m_minFrequency and
Chris@366 49 // m_maxFrequency members.
Chris@366 50 static const int defaultMinMIDIPitch = 36;
Chris@366 51 static const int defaultMaxMIDIPitch = 96;
Chris@366 52 static const int defaultBPO = 36;
Chris@366 53 static const float defaultMinFrequency = 110;
Chris@366 54 static const float defaultMaxFrequency = 14700;
Chris@366 55 static const float defaultTuningFrequency = 440.f;
Chris@366 56
Chris@366 57 CQVamp::CQVamp(float inputSampleRate, bool midiPitchParameters) :
Chris@366 58 Vamp::Plugin(inputSampleRate),
Chris@366 59 m_midiPitchParameters(midiPitchParameters),
Chris@366 60 m_minMIDIPitch(defaultMinMIDIPitch),
Chris@366 61 m_maxMIDIPitch(defaultMaxMIDIPitch),
Chris@366 62 m_tuningFrequency(defaultTuningFrequency),
Chris@366 63 m_bpo(defaultBPO),
Chris@366 64 m_interpolation(CQSpectrogram::InterpolateLinear),
Chris@366 65 m_cq(0),
Chris@366 66 m_maxFrequency(defaultMaxFrequency),
Chris@366 67 m_minFrequency(defaultMinFrequency),
Chris@366 68 m_haveStartTime(false),
Chris@366 69 m_columnCount(0)
Chris@366 70 {
Chris@366 71 }
Chris@366 72
Chris@366 73 CQVamp::~CQVamp()
Chris@366 74 {
Chris@366 75 delete m_cq;
Chris@366 76 }
Chris@366 77
Chris@366 78 string
Chris@366 79 CQVamp::getIdentifier() const
Chris@366 80 {
Chris@366 81 if (m_midiPitchParameters) {
Chris@366 82 return "cqvampmidi";
Chris@366 83 } else {
Chris@366 84 return "cqvamp";
Chris@366 85 }
Chris@366 86 }
Chris@366 87
Chris@366 88 string
Chris@366 89 CQVamp::getName() const
Chris@366 90 {
Chris@366 91 if (m_midiPitchParameters) {
Chris@366 92 return "CQ Constant-Q Spectrogram (MIDI pitch range)";
Chris@366 93 } else {
Chris@366 94 return "CQ Constant-Q Spectrogram (Hz range)";
Chris@366 95 }
Chris@366 96 }
Chris@366 97
Chris@366 98 string
Chris@366 99 CQVamp::getDescription() const
Chris@366 100 {
Chris@366 101 if (m_midiPitchParameters) {
Chris@366 102 return "Extract a spectrogram with constant ratio of centre frequency to resolution from the input audio, specifying the frequency range in MIDI pitch units.";
Chris@366 103 } else {
Chris@366 104 return "Extract a spectrogram with constant ratio of centre frequency to resolution from the input audio, specifying the frequency range in Hz.";
Chris@366 105 }
Chris@366 106 }
Chris@366 107
Chris@366 108 string
Chris@366 109 CQVamp::getMaker() const
Chris@366 110 {
Chris@366 111 return "Queen Mary, University of London";
Chris@366 112 }
Chris@366 113
Chris@366 114 int
Chris@366 115 CQVamp::getPluginVersion() const
Chris@366 116 {
Chris@366 117 return 2;
Chris@366 118 }
Chris@366 119
Chris@366 120 string
Chris@366 121 CQVamp::getCopyright() const
Chris@366 122 {
Chris@366 123 return "Plugin by Chris Cannam. Method by Christian Schörkhuber and Anssi Klapuri. Copyright (c) 2015 QMUL. BSD/MIT licence.";
Chris@366 124 }
Chris@366 125
Chris@366 126 CQVamp::ParameterList
Chris@366 127 CQVamp::getParameterDescriptors() const
Chris@366 128 {
Chris@366 129 ParameterList list;
Chris@366 130
Chris@366 131 ParameterDescriptor desc;
Chris@366 132
Chris@366 133 if (m_midiPitchParameters) {
Chris@366 134
Chris@366 135 desc.identifier = "minpitch";
Chris@366 136 desc.name = "Minimum Pitch";
Chris@366 137 desc.unit = "MIDI units";
Chris@366 138 desc.description = "MIDI pitch corresponding to the lowest frequency to be included in the constant-Q transform. (The actual minimum frequency may be lower, as the range always covers an integral number of octaves below the highest frequency.)";
Chris@366 139 desc.minValue = 0;
Chris@366 140 desc.maxValue = 127;
Chris@366 141 desc.defaultValue = defaultMinMIDIPitch;
Chris@366 142 desc.isQuantized = true;
Chris@366 143 desc.quantizeStep = 1;
Chris@366 144 list.push_back(desc);
Chris@366 145
Chris@366 146 desc.identifier = "maxpitch";
Chris@366 147 desc.name = "Maximum Pitch";
Chris@366 148 desc.unit = "MIDI units";
Chris@366 149 desc.description = "MIDI pitch corresponding to the highest frequency to be included in the constant-Q transform";
Chris@366 150 desc.minValue = 0;
Chris@366 151 desc.maxValue = 127;
Chris@366 152 desc.defaultValue = defaultMaxMIDIPitch;
Chris@366 153 desc.isQuantized = true;
Chris@366 154 desc.quantizeStep = 1;
Chris@366 155 list.push_back(desc);
Chris@366 156
Chris@366 157 desc.identifier = "tuning";
Chris@366 158 desc.name = "Tuning Frequency";
Chris@366 159 desc.unit = "Hz";
Chris@366 160 desc.description = "Frequency of concert A";
Chris@366 161 desc.minValue = 360;
Chris@366 162 desc.maxValue = 500;
Chris@366 163 desc.defaultValue = defaultTuningFrequency;
Chris@366 164 desc.isQuantized = false;
Chris@366 165 list.push_back(desc);
Chris@366 166
Chris@366 167 } else {
Chris@366 168
Chris@366 169 desc.identifier = "minfreq";
Chris@366 170 desc.name = "Minimum Frequency";
Chris@366 171 desc.unit = "Hz";
Chris@366 172 desc.description = "Lowest frequency to be included in the constant-Q transform. (The actual minimum frequency may be lower, as the range always covers an integral number of octaves below the highest frequency.)";
Chris@366 173 desc.minValue = 1;
Chris@366 174 desc.maxValue = 22050;
Chris@366 175 desc.defaultValue = defaultMinFrequency;
Chris@366 176 desc.isQuantized = false;
Chris@366 177 list.push_back(desc);
Chris@366 178
Chris@366 179 desc.identifier = "maxfreq";
Chris@366 180 desc.name = "Maximum Frequency";
Chris@366 181 desc.unit = "Hz";
Chris@366 182 desc.description = "MIDI pitch corresponding to the highest frequency to be included in the constant-Q transform";
Chris@366 183 desc.minValue = 1;
Chris@366 184 desc.maxValue = 22050;
Chris@366 185 desc.defaultValue = defaultMaxFrequency;
Chris@366 186 desc.isQuantized = false;
Chris@366 187 list.push_back(desc);
Chris@366 188 }
Chris@366 189
Chris@366 190 desc.identifier = "bpo";
Chris@366 191 desc.name = "Bins per Octave";
Chris@366 192 desc.unit = "bins";
Chris@366 193 desc.description = "Number of constant-Q transform bins per octave";
Chris@366 194 desc.minValue = 2;
Chris@366 195 desc.maxValue = 480;
Chris@366 196 desc.defaultValue = defaultBPO;
Chris@366 197 desc.isQuantized = true;
Chris@366 198 desc.quantizeStep = 1;
Chris@366 199 list.push_back(desc);
Chris@366 200
Chris@366 201 desc.identifier = "interpolation";
Chris@366 202 desc.name = "Interpolation";
Chris@366 203 desc.unit = "";
Chris@366 204 desc.description = "Interpolation method used to fill empty cells in lower octaves";
Chris@366 205 desc.minValue = 0;
Chris@366 206 desc.maxValue = 2;
Chris@366 207 desc.defaultValue = 2;
Chris@366 208 desc.isQuantized = true;
Chris@366 209 desc.quantizeStep = 1;
Chris@366 210 desc.valueNames.push_back("None, leave as zero");
Chris@366 211 desc.valueNames.push_back("None, repeat prior value");
Chris@366 212 desc.valueNames.push_back("Linear interpolation");
Chris@366 213 list.push_back(desc);
Chris@366 214
Chris@366 215 return list;
Chris@366 216 }
Chris@366 217
Chris@366 218 float
Chris@366 219 CQVamp::getParameter(std::string param) const
Chris@366 220 {
Chris@366 221 if (param == "minpitch" && m_midiPitchParameters) {
Chris@366 222 return m_minMIDIPitch;
Chris@366 223 }
Chris@366 224 if (param == "maxpitch" && m_midiPitchParameters) {
Chris@366 225 return m_maxMIDIPitch;
Chris@366 226 }
Chris@366 227 if (param == "tuning" && m_midiPitchParameters) {
Chris@366 228 return m_tuningFrequency;
Chris@366 229 }
Chris@366 230 if (param == "bpo") {
Chris@366 231 return m_bpo;
Chris@366 232 }
Chris@366 233 if (param == "interpolation") {
Chris@366 234 return (float)m_interpolation;
Chris@366 235 }
Chris@366 236 if (param == "minfreq" && !m_midiPitchParameters) {
Chris@366 237 return m_minFrequency;
Chris@366 238 }
Chris@366 239 if (param == "maxfreq" && !m_midiPitchParameters) {
Chris@366 240 return m_maxFrequency;
Chris@366 241 }
Chris@366 242 std::cerr << "WARNING: CQVamp::getParameter: unknown parameter \""
Chris@366 243 << param << "\"" << std::endl;
Chris@366 244 return 0.0;
Chris@366 245 }
Chris@366 246
Chris@366 247 void
Chris@366 248 CQVamp::setParameter(std::string param, float value)
Chris@366 249 {
Chris@366 250 if (param == "minpitch" && m_midiPitchParameters) {
Chris@366 251 m_minMIDIPitch = int(value + 0.5f);
Chris@366 252 } else if (param == "maxpitch" && m_midiPitchParameters) {
Chris@366 253 m_maxMIDIPitch = int(value + 0.5f);
Chris@366 254 } else if (param == "tuning" && m_midiPitchParameters) {
Chris@366 255 m_tuningFrequency = value;
Chris@366 256 } else if (param == "bpo") {
Chris@366 257 m_bpo = int(value + 0.5f);
Chris@366 258 } else if (param == "interpolation") {
Chris@366 259 m_interpolation = (CQSpectrogram::Interpolation)int(value + 0.5f);
Chris@366 260 } else if (param == "minfreq" && !m_midiPitchParameters) {
Chris@366 261 m_minFrequency = value;
Chris@366 262 } else if (param == "maxfreq" && !m_midiPitchParameters) {
Chris@366 263 m_maxFrequency = value;
Chris@366 264 } else {
Chris@366 265 std::cerr << "WARNING: CQVamp::setParameter: unknown parameter \""
Chris@366 266 << param << "\"" << std::endl;
Chris@366 267 }
Chris@366 268 }
Chris@366 269
Chris@366 270 bool
Chris@366 271 CQVamp::initialise(size_t channels, size_t stepSize, size_t blockSize)
Chris@366 272 {
Chris@366 273 if (m_cq) {
Chris@366 274 delete m_cq;
Chris@366 275 m_cq = 0;
Chris@366 276 }
Chris@366 277
Chris@366 278 if (channels < getMinChannelCount() ||
Chris@366 279 channels > getMaxChannelCount()) return false;
Chris@366 280
Chris@366 281 m_stepSize = stepSize;
Chris@366 282 m_blockSize = blockSize;
Chris@366 283
Chris@366 284 if (m_midiPitchParameters) {
Chris@366 285 m_minFrequency = Pitch::getFrequencyForPitch
Chris@366 286 (m_minMIDIPitch, 0, m_tuningFrequency);
Chris@366 287 m_maxFrequency = Pitch::getFrequencyForPitch
Chris@366 288 (m_maxMIDIPitch, 0, m_tuningFrequency);
Chris@366 289 }
Chris@366 290
Chris@366 291 reset();
Chris@366 292
Chris@366 293 if (!m_cq || !m_cq->isValid()) {
Chris@366 294 cerr << "CQVamp::initialise: Constant-Q parameters not valid! Not initialising" << endl;
Chris@366 295 return false;
Chris@366 296 }
Chris@366 297
Chris@366 298 return true;
Chris@366 299 }
Chris@366 300
Chris@366 301 void
Chris@366 302 CQVamp::reset()
Chris@366 303 {
Chris@366 304 delete m_cq;
Chris@366 305 CQParameters p(m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo);
Chris@366 306 m_cq = new CQSpectrogram(p, m_interpolation);
Chris@366 307 m_haveStartTime = false;
Chris@366 308 m_columnCount = 0;
Chris@366 309 }
Chris@366 310
Chris@366 311 size_t
Chris@366 312 CQVamp::getPreferredStepSize() const
Chris@366 313 {
Chris@366 314 return 0;
Chris@366 315 }
Chris@366 316
Chris@366 317 size_t
Chris@366 318 CQVamp::getPreferredBlockSize() const
Chris@366 319 {
Chris@366 320 return 0;
Chris@366 321 }
Chris@366 322
Chris@366 323 std::string
Chris@366 324 CQVamp::noteName(int i) const
Chris@366 325 {
Chris@366 326 static const char *names[] = {
Chris@366 327 "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
Chris@366 328 };
Chris@366 329
Chris@366 330 const char *n = names[i % 12];
Chris@366 331 int oct = i / 12 - 1;
Chris@366 332 char buf[20];
Chris@366 333 sprintf(buf, "%d %s%d", i, n, oct);
Chris@366 334
Chris@366 335 return buf;
Chris@366 336 }
Chris@366 337
Chris@366 338 CQVamp::OutputList
Chris@366 339 CQVamp::getOutputDescriptors() const
Chris@366 340 {
Chris@366 341 OutputList list;
Chris@366 342
Chris@366 343 OutputDescriptor d;
Chris@366 344 d.identifier = "constantq";
Chris@366 345 d.name = "Constant-Q Spectrogram";
Chris@366 346 d.unit = "";
Chris@366 347 d.description = "Output of constant-Q transform, as a single vector per process block";
Chris@366 348 d.hasFixedBinCount = true;
Chris@366 349 d.binCount = (m_cq ? m_cq->getTotalBins() : (9 * 24));
Chris@366 350
Chris@366 351 if (m_cq) {
Chris@366 352 char name[20];
Chris@366 353 for (int i = 0; i < (int)d.binCount; ++i) {
Chris@366 354 float freq = m_cq->getBinFrequency(d.binCount - i - 1);
Chris@366 355 sprintf(name, "%.1f Hz", freq);
Chris@366 356 int note = Pitch::getPitchForFrequency(freq, 0, m_tuningFrequency);
Chris@366 357 float nearestFreq =
Chris@366 358 Pitch::getFrequencyForPitch(note, 0, m_tuningFrequency);
Chris@366 359 if (fabs(freq - nearestFreq) < 0.01) {
Chris@366 360 d.binNames.push_back(name + std::string(" ") + noteName(note));
Chris@366 361 } else {
Chris@366 362 d.binNames.push_back(name);
Chris@366 363 }
Chris@366 364 }
Chris@366 365 }
Chris@366 366
Chris@366 367 d.hasKnownExtents = false;
Chris@366 368 d.isQuantized = false;
Chris@366 369 d.sampleType = OutputDescriptor::FixedSampleRate;
Chris@366 370 d.sampleRate = m_inputSampleRate / (m_cq ? m_cq->getColumnHop() : 256);
Chris@366 371 list.push_back(d);
Chris@366 372
Chris@366 373 return list;
Chris@366 374 }
Chris@366 375
Chris@366 376 CQVamp::FeatureSet
Chris@366 377 CQVamp::process(const float *const *inputBuffers,
Chris@366 378 Vamp::RealTime timestamp)
Chris@366 379 {
Chris@366 380 if (!m_cq) {
Chris@366 381 cerr << "ERROR: CQVamp::process: "
Chris@366 382 << "Plugin has not been initialised"
Chris@366 383 << endl;
Chris@366 384 return FeatureSet();
Chris@366 385 }
Chris@366 386
Chris@366 387 if (!m_haveStartTime) {
Chris@366 388 m_startTime = timestamp;
Chris@366 389 m_haveStartTime = true;
Chris@366 390 }
Chris@366 391
Chris@366 392 vector<double> data;
Chris@366 393 for (int i = 0; i < m_blockSize; ++i) data.push_back(inputBuffers[0][i]);
Chris@366 394
Chris@366 395 vector<vector<double> > cqout = m_cq->process(data);
Chris@366 396 return convertToFeatures(cqout);
Chris@366 397 }
Chris@366 398
Chris@366 399 CQVamp::FeatureSet
Chris@366 400 CQVamp::getRemainingFeatures()
Chris@366 401 {
Chris@366 402 vector<vector<double> > cqout = m_cq->getRemainingOutput();
Chris@366 403 return convertToFeatures(cqout);
Chris@366 404 }
Chris@366 405
Chris@366 406 CQVamp::FeatureSet
Chris@366 407 CQVamp::convertToFeatures(const vector<vector<double> > &cqout)
Chris@366 408 {
Chris@366 409 FeatureSet returnFeatures;
Chris@366 410
Chris@366 411 int width = cqout.size();
Chris@366 412 int height = m_cq->getTotalBins();
Chris@366 413
Chris@366 414 for (int i = 0; i < width; ++i) {
Chris@366 415
Chris@366 416 vector<float> column(height, 0.f);
Chris@366 417 int thisHeight = cqout[i].size();
Chris@366 418 for (int j = 0; j < thisHeight; ++j) {
Chris@366 419 column[j] = cqout[i][j];
Chris@366 420 }
Chris@366 421
Chris@366 422 // put low frequencies at the start
Chris@366 423 std::reverse(column.begin(), column.end());
Chris@366 424
Chris@366 425 Feature feature;
Chris@366 426 feature.hasTimestamp = true;
Chris@366 427 feature.timestamp = m_startTime + Vamp::RealTime::frame2RealTime
Chris@366 428 (m_columnCount * m_cq->getColumnHop() - m_cq->getLatency(),
Chris@366 429 m_inputSampleRate);
Chris@366 430 feature.values = column;
Chris@366 431 feature.label = "";
Chris@366 432
Chris@366 433 // cerr << "timestamp = " << feature.timestamp << " (start time = " << m_startTime << ", column count = " << m_columnCount << ", latency = " << m_cq->getLatency() << ", sample rate " << m_inputSampleRate << ")" << endl;
Chris@366 434
Chris@366 435 if (feature.timestamp >= m_startTime) {
Chris@366 436 returnFeatures[0].push_back(feature);
Chris@366 437 }
Chris@366 438
Chris@366 439 ++m_columnCount;
Chris@366 440 }
Chris@366 441
Chris@366 442 return returnFeatures;
Chris@366 443 }
Chris@366 444