annotate plugins/KeyDetect.cpp @ 137:ed55dc6aabf5

* README and version updates, minor Linux build update
author Chris Cannam <c.cannam@qmul.ac.uk>
date Tue, 05 Apr 2011 11:56:34 +0100
parents dcf5800f0f00
children c9c562f37dd7
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 //using std::cerr;
c@21 20 using std::endl;
c@21 21
c@21 22 #include <cmath>
c@21 23
c@21 24
c@60 25 // Order for circle-of-5ths plotting
c@60 26 static int conversion[24] =
c@60 27 { 7, 12, 5, 10, 3, 8, 1, 6, 11, 4, 9, 2,
c@60 28 16, 21, 14, 19, 24, 17, 22, 15, 20, 13, 18, 23 };
c@60 29
c@60 30
c@21 31 KeyDetector::KeyDetector(float inputSampleRate) :
c@21 32 Plugin(inputSampleRate),
c@21 33 m_stepSize(0),
c@21 34 m_blockSize(0),
c@21 35 m_tuningFrequency(440),
c@21 36 m_length(10),
c@21 37 m_getKeyMode(0),
c@21 38 m_inputFrame(0),
c@21 39 m_prevKey(-1)
c@21 40 {
c@21 41 }
c@21 42
c@21 43 KeyDetector::~KeyDetector()
c@21 44 {
c@21 45 delete m_getKeyMode;
c@21 46 if ( m_inputFrame ) {
c@21 47 delete [] m_inputFrame;
c@21 48 }
c@21 49 }
c@21 50
c@21 51 string
c@22 52 KeyDetector::getIdentifier() const
c@21 53 {
c@21 54 return "qm-keydetector";
c@21 55 }
c@21 56
c@21 57 string
c@22 58 KeyDetector::getName() const
c@22 59 {
c@22 60 return "Key Detector";
c@22 61 }
c@22 62
c@22 63 string
c@21 64 KeyDetector::getDescription() const
c@21 65 {
c@50 66 return "Estimate the key of the music";
c@21 67 }
c@21 68
c@21 69 string
c@21 70 KeyDetector::getMaker() const
c@21 71 {
c@50 72 return "Queen Mary, University of London";
c@21 73 }
c@21 74
c@21 75 int
c@21 76 KeyDetector::getPluginVersion() const
c@21 77 {
c@118 78 return 4;
c@21 79 }
c@21 80
c@21 81 string
c@21 82 KeyDetector::getCopyright() const
c@21 83 {
c@118 84 return "Plugin by Katy Noland and Christian Landone. Copyright (c) 2006-2009 QMUL - All Rights Reserved";
c@21 85 }
c@21 86
c@21 87 KeyDetector::ParameterList
c@21 88 KeyDetector::getParameterDescriptors() const
c@21 89 {
c@21 90 ParameterList list;
c@21 91
c@21 92 ParameterDescriptor desc;
c@22 93 desc.identifier = "tuning";
c@22 94 desc.name = "Tuning Frequency";
c@52 95 desc.description = "Frequency of concert A";
c@21 96 desc.unit = "Hz";
c@21 97 desc.minValue = 420;
c@21 98 desc.maxValue = 460;
c@21 99 desc.defaultValue = 440;
c@21 100 desc.isQuantized = false;
c@21 101 list.push_back(desc);
c@21 102
c@22 103 desc.identifier = "length";
c@22 104 desc.name = "Window Length";
c@21 105 desc.unit = "chroma frames";
c@52 106 desc.description = "Number of chroma analysis frames per key estimation";
c@21 107 desc.minValue = 1;
c@21 108 desc.maxValue = 30;
c@21 109 desc.defaultValue = 10;
c@21 110 desc.isQuantized = true;
c@21 111 desc.quantizeStep = 1;
c@21 112 list.push_back(desc);
c@21 113
c@21 114 return list;
c@21 115 }
c@21 116
c@21 117 float
c@21 118 KeyDetector::getParameter(std::string param) const
c@21 119 {
c@21 120 if (param == "tuning") {
c@21 121 return m_tuningFrequency;
c@21 122 }
c@21 123 if (param == "length") {
c@21 124 return m_length;
c@21 125 }
c@52 126 std::cerr << "WARNING: KeyDetector::getParameter: unknown parameter \""
c@21 127 << param << "\"" << std::endl;
c@21 128 return 0.0;
c@21 129 }
c@21 130
c@21 131 void
c@21 132 KeyDetector::setParameter(std::string param, float value)
c@21 133 {
c@21 134 if (param == "tuning") {
c@21 135 m_tuningFrequency = value;
c@21 136 } else if (param == "length") {
c@21 137 m_length = int(value + 0.1);
c@21 138 } else {
c@52 139 std::cerr << "WARNING: KeyDetector::setParameter: unknown parameter \""
c@21 140 << param << "\"" << std::endl;
c@21 141 }
c@21 142 }
c@21 143
c@21 144 bool
c@21 145 KeyDetector::initialise(size_t channels, size_t stepSize, size_t blockSize)
c@21 146 {
c@21 147 if (m_getKeyMode) {
c@21 148 delete m_getKeyMode;
c@21 149 m_getKeyMode = 0;
c@21 150 }
c@21 151
c@21 152 if (channels < getMinChannelCount() ||
c@21 153 channels > getMaxChannelCount()) return false;
c@21 154
c@21 155 m_getKeyMode = new GetKeyMode(int(m_inputSampleRate + 0.1),
c@21 156 m_tuningFrequency,
c@21 157 m_length, m_length);
c@21 158
c@21 159 m_stepSize = m_getKeyMode->getHopSize();
c@21 160 m_blockSize = m_getKeyMode->getBlockSize();
c@21 161
c@21 162 if (stepSize != m_stepSize || blockSize != m_blockSize) {
c@52 163 std::cerr << "KeyDetector::initialise: ERROR: step/block sizes "
c@21 164 << stepSize << "/" << blockSize << " differ from required "
c@21 165 << m_stepSize << "/" << m_blockSize << std::endl;
c@21 166 delete m_getKeyMode;
c@21 167 m_getKeyMode = 0;
c@21 168 return false;
c@21 169 }
c@21 170
c@21 171 m_inputFrame = new double[m_blockSize];
c@21 172
c@21 173 m_prevKey = -1;
c@95 174 m_first = true;
c@95 175
c@21 176 return true;
c@21 177 }
c@21 178
c@21 179 void
c@21 180 KeyDetector::reset()
c@21 181 {
c@21 182 if (m_getKeyMode) {
c@21 183 delete m_getKeyMode;
c@21 184 m_getKeyMode = new GetKeyMode(int(m_inputSampleRate + 0.1),
c@21 185 m_tuningFrequency,
c@21 186 m_length, m_length);
c@21 187 }
c@21 188
c@21 189 if (m_inputFrame) {
c@21 190 for( unsigned int i = 0; i < m_blockSize; i++ ) {
c@21 191 m_inputFrame[ i ] = 0.0;
c@21 192 }
c@21 193 }
c@21 194
c@21 195 m_prevKey = -1;
c@95 196 m_first = true;
c@21 197 }
c@21 198
c@21 199
c@21 200 KeyDetector::OutputList
c@21 201 KeyDetector::getOutputDescriptors() const
c@21 202 {
c@21 203 OutputList list;
c@21 204
c@72 205 float osr = 0.0f;
c@72 206 if (m_stepSize == 0) (void)getPreferredStepSize();
c@72 207 osr = m_inputSampleRate / m_stepSize;
c@72 208
c@21 209 OutputDescriptor d;
c@22 210 d.identifier = "tonic";
c@22 211 d.name = "Tonic Pitch";
c@21 212 d.unit = "";
c@52 213 d.description = "Tonic of the estimated key (from C = 1 to B = 12)";
c@21 214 d.hasFixedBinCount = true;
c@21 215 d.binCount = 1;
c@21 216 d.hasKnownExtents = true;
c@21 217 d.isQuantized = true;
c@48 218 d.minValue = 1;
c@48 219 d.maxValue = 12;
c@21 220 d.quantizeStep = 1;
c@72 221 d.sampleRate = osr;
c@72 222 d.sampleType = OutputDescriptor::VariableSampleRate;
c@21 223 list.push_back(d);
c@21 224
c@22 225 d.identifier = "mode";
c@22 226 d.name = "Key Mode";
c@21 227 d.unit = "";
c@52 228 d.description = "Major or minor mode of the estimated key (major = 0, minor = 1)";
c@21 229 d.hasFixedBinCount = true;
c@21 230 d.binCount = 1;
c@21 231 d.hasKnownExtents = true;
c@21 232 d.isQuantized = true;
c@21 233 d.minValue = 0;
c@21 234 d.maxValue = 1;
c@21 235 d.quantizeStep = 1;
c@72 236 d.sampleRate = osr;
c@72 237 d.sampleType = OutputDescriptor::VariableSampleRate;
c@21 238 list.push_back(d);
c@21 239
c@22 240 d.identifier = "key";
c@22 241 d.name = "Key";
c@21 242 d.unit = "";
c@52 243 d.description = "Estimated key (from C major = 1 to B major = 12 and C minor = 13 to B minor = 24)";
c@21 244 d.hasFixedBinCount = true;
c@21 245 d.binCount = 1;
c@21 246 d.hasKnownExtents = true;
c@21 247 d.isQuantized = true;
c@48 248 d.minValue = 1;
c@48 249 d.maxValue = 24;
c@21 250 d.quantizeStep = 1;
c@72 251 d.sampleRate = osr;
c@72 252 d.sampleType = OutputDescriptor::VariableSampleRate;
c@21 253 list.push_back(d);
c@21 254
c@60 255 d.identifier = "keystrength";
c@60 256 d.name = "Key Strength Plot";
c@60 257 d.unit = "";
c@60 258 d.description = "Correlation of the chroma vector with stored key profile for each major and minor key";
c@60 259 d.hasFixedBinCount = true;
c@60 260 d.binCount = 25;
c@60 261 d.hasKnownExtents = false;
c@60 262 d.isQuantized = false;
c@73 263 d.sampleType = OutputDescriptor::OneSamplePerStep;
c@60 264 for (int i = 0; i < 24; ++i) {
c@60 265 if (i == 12) d.binNames.push_back(" ");
c@60 266 int idx = conversion[i];
c@63 267 std::string label = getKeyName(idx > 12 ? idx-12 : idx,
c@63 268 i >= 12,
c@63 269 true);
c@60 270 d.binNames.push_back(label);
c@60 271 }
c@60 272 list.push_back(d);
c@60 273
c@21 274 return list;
c@21 275 }
c@21 276
c@21 277 KeyDetector::FeatureSet
c@21 278 KeyDetector::process(const float *const *inputBuffers,
c@21 279 Vamp::RealTime now)
c@21 280 {
c@21 281 if (m_stepSize == 0) {
c@21 282 return FeatureSet();
c@21 283 }
c@21 284
c@21 285 FeatureSet returnFeatures;
c@21 286
c@21 287 for ( unsigned int i = 0 ; i < m_blockSize; i++ ) {
c@21 288 m_inputFrame[i] = (double)inputBuffers[0][i];
c@21 289 }
c@21 290
c@21 291 // int key = (m_getKeyMode->process(m_inputFrame) % 24);
c@21 292 int key = m_getKeyMode->process(m_inputFrame);
c@63 293 bool minor = m_getKeyMode->isModeMinor(key);
c@21 294 int tonic = key;
c@21 295 if (tonic > 12) tonic -= 12;
c@21 296
c@21 297 int prevTonic = m_prevKey;
c@21 298 if (prevTonic > 12) prevTonic -= 12;
c@21 299
c@95 300 if (m_first || (tonic != prevTonic)) {
c@21 301 Feature feature;
c@72 302 feature.hasTimestamp = true;
c@72 303 feature.timestamp = now;
c@21 304 // feature.timestamp = now;
c@21 305 feature.values.push_back((float)tonic);
c@63 306 feature.label = getKeyName(tonic, minor, false);
c@21 307 returnFeatures[0].push_back(feature); // tonic
c@21 308 }
c@21 309
c@95 310 if (m_first || (minor != (m_getKeyMode->isModeMinor(m_prevKey)))) {
c@21 311 Feature feature;
c@72 312 feature.hasTimestamp = true;
c@72 313 feature.timestamp = now;
c@63 314 feature.values.push_back(minor ? 1.f : 0.f);
c@21 315 feature.label = (minor ? "Minor" : "Major");
c@21 316 returnFeatures[1].push_back(feature); // mode
c@21 317 }
c@21 318
c@95 319 if (m_first || (key != m_prevKey)) {
c@21 320 Feature feature;
c@72 321 feature.hasTimestamp = true;
c@72 322 feature.timestamp = now;
c@21 323 feature.values.push_back((float)key);
c@63 324 feature.label = getKeyName(tonic, minor, true);
c@21 325 returnFeatures[2].push_back(feature); // key
c@21 326 }
c@21 327
c@21 328 m_prevKey = key;
c@95 329 m_first = false;
c@21 330
c@60 331 Feature ksf;
c@60 332 ksf.values.reserve(25);
c@60 333 double *keystrengths = m_getKeyMode->getKeyStrengths();
c@60 334 for (int i = 0; i < 24; ++i) {
c@60 335 if (i == 12) ksf.values.push_back(-1);
c@60 336 ksf.values.push_back(keystrengths[conversion[i]-1]);
c@60 337 }
c@73 338 ksf.hasTimestamp = false;
c@60 339 returnFeatures[3].push_back(ksf);
c@60 340
c@21 341 return returnFeatures;
c@21 342 }
c@21 343
c@21 344 KeyDetector::FeatureSet
c@21 345 KeyDetector::getRemainingFeatures()
c@21 346 {
c@21 347 return FeatureSet();
c@21 348 }
c@21 349
c@21 350
c@21 351 size_t
c@21 352 KeyDetector::getPreferredStepSize() const
c@21 353 {
c@21 354 if (!m_stepSize) {
c@21 355 GetKeyMode gkm(int(m_inputSampleRate + 0.1),
c@21 356 m_tuningFrequency, m_length, m_length);
c@21 357 m_stepSize = gkm.getHopSize();
c@21 358 m_blockSize = gkm.getBlockSize();
c@21 359 }
c@21 360 return m_stepSize;
c@21 361 }
c@21 362
c@21 363 size_t
c@21 364 KeyDetector::getPreferredBlockSize() const
c@21 365 {
c@21 366 if (!m_blockSize) {
c@21 367 GetKeyMode gkm(int(m_inputSampleRate + 0.1),
c@21 368 m_tuningFrequency, m_length, m_length);
c@21 369 m_stepSize = gkm.getHopSize();
c@21 370 m_blockSize = gkm.getBlockSize();
c@21 371 }
c@21 372 return m_blockSize;
c@21 373 }
c@21 374
c@63 375 std::string
c@63 376 KeyDetector::getKeyName(int index, bool minor, bool includeMajMin) const
c@21 377 {
c@48 378 // Keys are numbered with 1 => C, 12 => B
c@48 379 // This is based on chromagram base set to a C in qm-dsp's GetKeyMode.cpp
c@63 380
c@63 381 static const char *namesMajor[] = {
c@63 382 "C", "Db", "D", "Eb",
c@21 383 "E", "F", "F# / Gb", "G",
c@63 384 "Ab", "A", "Bb", "B"
c@21 385 };
c@63 386
c@63 387 static const char *namesMinor[] = {
c@65 388 "C", "C#", "D", "Eb / D#",
c@65 389 "E", "F", "F#", "G",
c@65 390 "G#", "A", "Bb", "B"
c@63 391 };
c@63 392
c@21 393 if (index < 1 || index > 12) {
c@21 394 return "(unknown)";
c@21 395 }
c@63 396
c@63 397 std::string base;
c@63 398
c@63 399 if (minor) base = namesMinor[index - 1];
c@63 400 else base = namesMajor[index - 1];
c@63 401
c@63 402 if (!includeMajMin) return base;
c@63 403
c@63 404 if (minor) return base + " minor";
c@63 405 else return base + " major";
c@21 406 }
c@21 407