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