annotate plugins/KeyDetect.cpp @ 247:e6abd6e99051

This is not actually a tonic-strength output - it's a key-strength output in which the relative major and minor (which have different tonics but the same key signatures) have been summed into a single bin
author Chris Cannam <cannam@all-day-breakfast.com>
date Thu, 26 Sep 2019 15:44:47 +0100
parents a3612b821a0b
children 73c9922fb649
rev   line source
c@21 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
c@21 2
c@21 3 /*
c@38 4 QM Vamp Plugin Set
c@21 5
c@21 6 Centre for Digital Music, Queen Mary, University of London.
c@135 7
c@135 8 This program is free software; you can redistribute it and/or
c@135 9 modify it under the terms of the GNU General Public License as
c@135 10 published by the Free Software Foundation; either version 2 of the
c@135 11 License, or (at your option) any later version. See the file
c@135 12 COPYING included with this distribution for more information.
c@21 13 */
c@21 14
c@21 15 #include "KeyDetect.h"
c@21 16
c@21 17 using std::string;
c@21 18 using std::vector;
c@21 19
c@21 20 #include <cmath>
c@21 21
c@21 22
c@60 23 // Order for circle-of-5ths plotting
c@60 24 static int conversion[24] =
c@60 25 { 7, 12, 5, 10, 3, 8, 1, 6, 11, 4, 9, 2,
c@60 26 16, 21, 14, 19, 24, 17, 22, 15, 20, 13, 18, 23 };
c@60 27
c@60 28
c@21 29 KeyDetector::KeyDetector(float inputSampleRate) :
c@21 30 Plugin(inputSampleRate),
c@21 31 m_stepSize(0),
c@21 32 m_blockSize(0),
c@21 33 m_tuningFrequency(440),
c@21 34 m_length(10),
cannam@242 35 m_rapid(true),
c@21 36 m_getKeyMode(0),
c@21 37 m_inputFrame(0),
c@21 38 m_prevKey(-1)
c@21 39 {
c@21 40 }
c@21 41
c@21 42 KeyDetector::~KeyDetector()
c@21 43 {
c@21 44 delete m_getKeyMode;
c@21 45 if ( m_inputFrame ) {
c@21 46 delete [] m_inputFrame;
c@21 47 }
c@21 48 }
c@21 49
c@21 50 string
c@22 51 KeyDetector::getIdentifier() const
c@21 52 {
c@21 53 return "qm-keydetector";
c@21 54 }
c@21 55
c@21 56 string
c@22 57 KeyDetector::getName() const
c@22 58 {
c@22 59 return "Key Detector";
c@22 60 }
c@22 61
c@22 62 string
c@21 63 KeyDetector::getDescription() const
c@21 64 {
c@50 65 return "Estimate the key of the music";
c@21 66 }
c@21 67
c@21 68 string
c@21 69 KeyDetector::getMaker() const
c@21 70 {
c@50 71 return "Queen Mary, University of London";
c@21 72 }
c@21 73
c@21 74 int
c@21 75 KeyDetector::getPluginVersion() const
c@21 76 {
cannam@244 77 return 6;
c@21 78 }
c@21 79
c@21 80 string
c@21 81 KeyDetector::getCopyright() const
c@21 82 {
cannam@242 83 return "Plugin by Katy Noland and Christian Landone. Copyright (c) 2006-2019 QMUL - All Rights Reserved";
c@21 84 }
c@21 85
c@21 86 KeyDetector::ParameterList
c@21 87 KeyDetector::getParameterDescriptors() const
c@21 88 {
c@21 89 ParameterList list;
c@21 90
c@21 91 ParameterDescriptor desc;
c@22 92 desc.identifier = "tuning";
c@22 93 desc.name = "Tuning Frequency";
c@52 94 desc.description = "Frequency of concert A";
c@21 95 desc.unit = "Hz";
c@21 96 desc.minValue = 420;
c@21 97 desc.maxValue = 460;
c@21 98 desc.defaultValue = 440;
c@21 99 desc.isQuantized = false;
c@21 100 list.push_back(desc);
c@21 101
c@22 102 desc.identifier = "length";
c@22 103 desc.name = "Window Length";
c@21 104 desc.unit = "chroma frames";
c@52 105 desc.description = "Number of chroma analysis frames per key estimation";
c@21 106 desc.minValue = 1;
c@21 107 desc.maxValue = 30;
c@21 108 desc.defaultValue = 10;
c@21 109 desc.isQuantized = true;
c@21 110 desc.quantizeStep = 1;
c@21 111 list.push_back(desc);
c@21 112
cannam@242 113 desc.identifier = "rapid";
cannam@242 114 desc.name = "Rapid";
cannam@242 115 desc.unit = "";
cannam@242 116 desc.description = "Sample intervals without overlap, for speed";
cannam@242 117 desc.minValue = 0;
cannam@242 118 desc.maxValue = 1;
cannam@242 119 desc.defaultValue = 1;
cannam@242 120 desc.isQuantized = true;
cannam@242 121 desc.quantizeStep = 1;
cannam@242 122 list.push_back(desc);
cannam@242 123
c@21 124 return list;
c@21 125 }
c@21 126
c@21 127 float
c@21 128 KeyDetector::getParameter(std::string param) const
c@21 129 {
c@21 130 if (param == "tuning") {
c@21 131 return m_tuningFrequency;
c@21 132 }
c@21 133 if (param == "length") {
cannam@244 134 return float(m_length);
c@21 135 }
cannam@242 136 if (param == "rapid") {
cannam@242 137 return m_rapid ? 1.f : 0.f;
cannam@242 138 }
c@52 139 std::cerr << "WARNING: KeyDetector::getParameter: unknown parameter \""
c@21 140 << param << "\"" << std::endl;
c@21 141 return 0.0;
c@21 142 }
c@21 143
c@21 144 void
c@21 145 KeyDetector::setParameter(std::string param, float value)
c@21 146 {
c@21 147 if (param == "tuning") {
c@21 148 m_tuningFrequency = value;
c@21 149 } else if (param == "length") {
c@21 150 m_length = int(value + 0.1);
cannam@242 151 } else if (param == "rapid") {
cannam@242 152 m_rapid = (value > 0.5);
c@21 153 } else {
c@52 154 std::cerr << "WARNING: KeyDetector::setParameter: unknown parameter \""
c@21 155 << param << "\"" << std::endl;
c@21 156 }
cannam@242 157
cannam@242 158 // force recalculate:
cannam@242 159 m_stepSize = 0;
cannam@242 160 m_blockSize = 0;
cannam@242 161 }
cannam@242 162
cannam@242 163 GetKeyMode::Config
cannam@242 164 KeyDetector::getConfig() const
cannam@242 165 {
cannam@242 166 GetKeyMode::Config config(m_inputSampleRate, m_tuningFrequency);
cannam@242 167 config.hpcpAverage = m_length;
cannam@242 168 config.medianAverage = m_length;
cannam@242 169 config.frameOverlapFactor = (m_rapid ? 1 : 8);
cannam@242 170 config.decimationFactor = 8;
cannam@242 171 return config;
c@21 172 }
c@21 173
c@21 174 bool
c@21 175 KeyDetector::initialise(size_t channels, size_t stepSize, size_t blockSize)
c@21 176 {
c@21 177 if (m_getKeyMode) {
c@21 178 delete m_getKeyMode;
c@21 179 m_getKeyMode = 0;
c@21 180 }
c@21 181
c@21 182 if (channels < getMinChannelCount() ||
c@21 183 channels > getMaxChannelCount()) return false;
c@21 184
cannam@242 185 m_getKeyMode = new GetKeyMode(getConfig());
c@21 186
c@21 187 m_stepSize = m_getKeyMode->getHopSize();
c@21 188 m_blockSize = m_getKeyMode->getBlockSize();
c@21 189
c@21 190 if (stepSize != m_stepSize || blockSize != m_blockSize) {
c@52 191 std::cerr << "KeyDetector::initialise: ERROR: step/block sizes "
c@21 192 << stepSize << "/" << blockSize << " differ from required "
c@21 193 << m_stepSize << "/" << m_blockSize << std::endl;
c@21 194 delete m_getKeyMode;
c@21 195 m_getKeyMode = 0;
c@21 196 return false;
c@21 197 }
c@21 198
c@21 199 m_inputFrame = new double[m_blockSize];
c@21 200
c@21 201 m_prevKey = -1;
c@95 202 m_first = true;
c@95 203
c@21 204 return true;
c@21 205 }
c@21 206
c@21 207 void
c@21 208 KeyDetector::reset()
c@21 209 {
c@21 210 if (m_getKeyMode) {
c@21 211 delete m_getKeyMode;
cannam@242 212 m_getKeyMode = new GetKeyMode(getConfig());
c@21 213 }
c@21 214
c@21 215 if (m_inputFrame) {
c@21 216 for( unsigned int i = 0; i < m_blockSize; i++ ) {
c@21 217 m_inputFrame[ i ] = 0.0;
c@21 218 }
c@21 219 }
c@21 220
c@21 221 m_prevKey = -1;
c@95 222 m_first = true;
c@21 223 }
c@21 224
c@21 225
c@21 226 KeyDetector::OutputList
c@21 227 KeyDetector::getOutputDescriptors() const
c@21 228 {
c@21 229 OutputList list;
c@21 230
c@72 231 float osr = 0.0f;
c@72 232 if (m_stepSize == 0) (void)getPreferredStepSize();
cannam@244 233 osr = m_inputSampleRate / float(m_stepSize);
c@72 234
c@21 235 OutputDescriptor d;
c@22 236 d.identifier = "tonic";
c@22 237 d.name = "Tonic Pitch";
c@21 238 d.unit = "";
c@52 239 d.description = "Tonic of the estimated key (from C = 1 to B = 12)";
c@21 240 d.hasFixedBinCount = true;
c@21 241 d.binCount = 1;
c@21 242 d.hasKnownExtents = true;
c@21 243 d.isQuantized = true;
c@48 244 d.minValue = 1;
c@48 245 d.maxValue = 12;
c@21 246 d.quantizeStep = 1;
c@72 247 d.sampleRate = osr;
c@72 248 d.sampleType = OutputDescriptor::VariableSampleRate;
c@21 249 list.push_back(d);
c@21 250
c@22 251 d.identifier = "mode";
c@22 252 d.name = "Key Mode";
c@21 253 d.unit = "";
c@52 254 d.description = "Major or minor mode of the estimated key (major = 0, minor = 1)";
c@21 255 d.hasFixedBinCount = true;
c@21 256 d.binCount = 1;
c@21 257 d.hasKnownExtents = true;
c@21 258 d.isQuantized = true;
c@21 259 d.minValue = 0;
c@21 260 d.maxValue = 1;
c@21 261 d.quantizeStep = 1;
c@72 262 d.sampleRate = osr;
c@72 263 d.sampleType = OutputDescriptor::VariableSampleRate;
c@21 264 list.push_back(d);
c@21 265
c@22 266 d.identifier = "key";
c@22 267 d.name = "Key";
c@21 268 d.unit = "";
c@52 269 d.description = "Estimated key (from C major = 1 to B major = 12 and C minor = 13 to B minor = 24)";
c@21 270 d.hasFixedBinCount = true;
c@21 271 d.binCount = 1;
c@21 272 d.hasKnownExtents = true;
c@21 273 d.isQuantized = true;
c@48 274 d.minValue = 1;
c@48 275 d.maxValue = 24;
c@21 276 d.quantizeStep = 1;
c@72 277 d.sampleRate = osr;
c@72 278 d.sampleType = OutputDescriptor::VariableSampleRate;
c@21 279 list.push_back(d);
c@21 280
c@60 281 d.identifier = "keystrength";
c@60 282 d.name = "Key Strength Plot";
c@60 283 d.unit = "";
c@60 284 d.description = "Correlation of the chroma vector with stored key profile for each major and minor key";
c@60 285 d.hasFixedBinCount = true;
c@60 286 d.binCount = 25;
c@60 287 d.hasKnownExtents = false;
c@60 288 d.isQuantized = false;
c@73 289 d.sampleType = OutputDescriptor::OneSamplePerStep;
cannam@245 290 d.binNames.clear();
c@60 291 for (int i = 0; i < 24; ++i) {
c@60 292 if (i == 12) d.binNames.push_back(" ");
c@60 293 int idx = conversion[i];
c@63 294 std::string label = getKeyName(idx > 12 ? idx-12 : idx,
c@63 295 i >= 12,
c@63 296 true);
c@60 297 d.binNames.push_back(label);
c@60 298 }
c@60 299 list.push_back(d);
c@60 300
cannam@247 301 d.identifier = "mergedkeystrength";
cannam@247 302 d.name = "Merged Key Strength Plot";
cannam@244 303 d.unit = "";
cannam@247 304 d.description = "Correlation of the chroma vector with stored key profile for each key, with major and minor alternatives merged";
cannam@244 305 d.hasFixedBinCount = true;
cannam@244 306 d.binCount = 12;
cannam@244 307 d.hasKnownExtents = false;
cannam@244 308 d.isQuantized = false;
cannam@244 309 d.sampleType = OutputDescriptor::OneSamplePerStep;
cannam@245 310 d.binNames.clear();
cannam@244 311 for (int i = 0; i < 12; ++i) {
cannam@244 312 int idx = conversion[i];
cannam@247 313 std::string label = getBothKeyNames(idx > 12 ? idx-12 : idx);
cannam@244 314 d.binNames.push_back(label);
cannam@244 315 }
cannam@244 316 list.push_back(d);
cannam@244 317
c@21 318 return list;
c@21 319 }
c@21 320
c@21 321 KeyDetector::FeatureSet
c@21 322 KeyDetector::process(const float *const *inputBuffers,
c@21 323 Vamp::RealTime now)
c@21 324 {
c@21 325 if (m_stepSize == 0) {
c@21 326 return FeatureSet();
c@21 327 }
c@21 328
c@21 329 FeatureSet returnFeatures;
c@21 330
c@21 331 for ( unsigned int i = 0 ; i < m_blockSize; i++ ) {
c@21 332 m_inputFrame[i] = (double)inputBuffers[0][i];
c@21 333 }
c@21 334
c@21 335 int key = m_getKeyMode->process(m_inputFrame);
cannam@242 336
c@21 337 int tonic = key;
c@21 338 if (tonic > 12) tonic -= 12;
c@21 339
c@21 340 int prevTonic = m_prevKey;
c@21 341 if (prevTonic > 12) prevTonic -= 12;
c@21 342
cannam@242 343 bool minor = (key > 12);
cannam@242 344 bool prevMinor = (m_prevKey > 12);
cannam@242 345
c@95 346 if (m_first || (tonic != prevTonic)) {
c@21 347 Feature feature;
c@72 348 feature.hasTimestamp = true;
c@72 349 feature.timestamp = now;
c@21 350 feature.values.push_back((float)tonic);
c@63 351 feature.label = getKeyName(tonic, minor, false);
c@21 352 returnFeatures[0].push_back(feature); // tonic
c@21 353 }
c@21 354
cannam@242 355 if (m_first || (minor != prevMinor)) {
c@21 356 Feature feature;
c@72 357 feature.hasTimestamp = true;
c@72 358 feature.timestamp = now;
c@63 359 feature.values.push_back(minor ? 1.f : 0.f);
c@21 360 feature.label = (minor ? "Minor" : "Major");
c@21 361 returnFeatures[1].push_back(feature); // mode
c@21 362 }
c@21 363
c@95 364 if (m_first || (key != m_prevKey)) {
c@21 365 Feature feature;
c@72 366 feature.hasTimestamp = true;
c@72 367 feature.timestamp = now;
c@21 368 feature.values.push_back((float)key);
c@63 369 feature.label = getKeyName(tonic, minor, true);
c@21 370 returnFeatures[2].push_back(feature); // key
c@21 371 }
c@21 372
c@21 373 m_prevKey = key;
c@95 374 m_first = false;
c@21 375
c@60 376 Feature ksf;
cannam@244 377 ksf.hasTimestamp = false;
c@60 378 ksf.values.reserve(25);
cannam@244 379
cannam@244 380 Feature tsf;
cannam@244 381 tsf.hasTimestamp = false;
cannam@244 382 tsf.values.reserve(12);
cannam@244 383
c@60 384 double *keystrengths = m_getKeyMode->getKeyStrengths();
cannam@244 385
c@60 386 for (int i = 0; i < 24; ++i) {
cannam@244 387
c@60 388 if (i == 12) ksf.values.push_back(-1);
cannam@244 389 ksf.values.push_back(float(keystrengths[conversion[i]-1]));
cannam@244 390
cannam@244 391 if (i < 12) {
cannam@244 392 tsf.values.push_back(float(keystrengths[conversion[i]-1]));
cannam@244 393 } else {
cannam@244 394 tsf.values[i-12] += float(keystrengths[conversion[i]-1]);
cannam@244 395 }
c@60 396 }
cannam@244 397
c@60 398 returnFeatures[3].push_back(ksf);
cannam@244 399 returnFeatures[4].push_back(tsf);
c@60 400
c@21 401 return returnFeatures;
c@21 402 }
c@21 403
c@21 404 KeyDetector::FeatureSet
c@21 405 KeyDetector::getRemainingFeatures()
c@21 406 {
c@21 407 return FeatureSet();
c@21 408 }
c@21 409
c@21 410 size_t
c@21 411 KeyDetector::getPreferredStepSize() const
c@21 412 {
c@21 413 if (!m_stepSize) {
cannam@242 414 GetKeyMode gkm(getConfig());
c@21 415 m_stepSize = gkm.getHopSize();
c@21 416 m_blockSize = gkm.getBlockSize();
c@21 417 }
c@21 418 return m_stepSize;
c@21 419 }
c@21 420
c@21 421 size_t
c@21 422 KeyDetector::getPreferredBlockSize() const
c@21 423 {
c@21 424 if (!m_blockSize) {
cannam@242 425 GetKeyMode gkm(getConfig());
c@21 426 m_stepSize = gkm.getHopSize();
c@21 427 m_blockSize = gkm.getBlockSize();
c@21 428 }
c@21 429 return m_blockSize;
c@21 430 }
c@21 431
c@63 432 std::string
c@63 433 KeyDetector::getKeyName(int index, bool minor, bool includeMajMin) const
c@21 434 {
c@48 435 // Keys are numbered with 1 => C, 12 => B
c@48 436 // This is based on chromagram base set to a C in qm-dsp's GetKeyMode.cpp
c@63 437
c@63 438 static const char *namesMajor[] = {
c@63 439 "C", "Db", "D", "Eb",
c@21 440 "E", "F", "F# / Gb", "G",
c@63 441 "Ab", "A", "Bb", "B"
c@21 442 };
c@63 443
c@63 444 static const char *namesMinor[] = {
c@65 445 "C", "C#", "D", "Eb / D#",
c@65 446 "E", "F", "F#", "G",
c@65 447 "G#", "A", "Bb", "B"
c@63 448 };
c@63 449
c@21 450 if (index < 1 || index > 12) {
c@21 451 return "(unknown)";
c@21 452 }
c@63 453
c@63 454 std::string base;
c@63 455
c@63 456 if (minor) base = namesMinor[index - 1];
c@63 457 else base = namesMajor[index - 1];
c@63 458
c@63 459 if (!includeMajMin) return base;
c@63 460
c@63 461 if (minor) return base + " minor";
c@63 462 else return base + " major";
c@21 463 }
c@21 464
cannam@247 465 std::string
cannam@247 466 KeyDetector::getBothKeyNames(int index) const
cannam@247 467 {
cannam@247 468 // Keys are numbered with 1 => C, 12 => B
cannam@247 469
cannam@247 470 static const char *names[] = {
cannam@247 471 "C maj / A min",
cannam@247 472 "Db maj / Bb min",
cannam@247 473 "D maj / B min",
cannam@247 474 "Eb maj / C min",
cannam@247 475 "E maj / C# min",
cannam@247 476 "F maj / D min",
cannam@247 477 "F#/Gb maj / Eb/D# min",
cannam@247 478 "G maj / E min",
cannam@247 479 "Ab maj / F min",
cannam@247 480 "A maj / F# min",
cannam@247 481 "Bb maj / G min",
cannam@247 482 "B maj / G# min"
cannam@247 483 };
cannam@247 484
cannam@247 485 if (index < 1 || index > 12) {
cannam@247 486 return "(unknown)";
cannam@247 487 }
cannam@247 488
cannam@247 489 return names[index - 1];
cannam@247 490 }
cannam@247 491
cannam@247 492