annotate plugins/SimilarityPlugin.cpp @ 53:3f8b9ae44f92

* remove some debug output
author Chris Cannam <c.cannam@qmul.ac.uk>
date Wed, 30 Jan 2008 14:33:44 +0000
parents 4fe04e706839
children ee9d180a5ad6
rev   line source
c@41 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
c@41 2
c@41 3 /*
c@41 4 * SegmenterPlugin.cpp
c@41 5 *
c@41 6 * Copyright 2008 Centre for Digital Music, Queen Mary, University of London.
c@41 7 * All rights reserved.
c@41 8 */
c@41 9
c@41 10 #include <iostream>
c@44 11 #include <cstdio>
c@41 12
c@41 13 #include "SimilarityPlugin.h"
c@42 14 #include "base/Pitch.h"
c@41 15 #include "dsp/mfcc/MFCC.h"
c@42 16 #include "dsp/chromagram/Chromagram.h"
c@41 17 #include "dsp/rateconversion/Decimator.h"
c@47 18 #include "dsp/rhythm/BeatSpectrum.h"
c@47 19 #include "maths/KLDivergence.h"
c@47 20 #include "maths/CosineDistance.h"
c@49 21 #include "maths/MathUtilities.h"
c@41 22
c@41 23 using std::string;
c@41 24 using std::vector;
c@41 25 using std::cerr;
c@41 26 using std::endl;
c@41 27 using std::ostringstream;
c@41 28
c@47 29 const float
c@47 30 SimilarityPlugin::m_noRhythm = 0.009;
c@47 31
c@47 32 const float
c@47 33 SimilarityPlugin::m_allRhythm = 0.991;
c@47 34
c@41 35 SimilarityPlugin::SimilarityPlugin(float inputSampleRate) :
c@41 36 Plugin(inputSampleRate),
c@42 37 m_type(TypeMFCC),
c@41 38 m_mfcc(0),
c@47 39 m_rhythmfcc(0),
c@42 40 m_chromagram(0),
c@41 41 m_decimator(0),
c@42 42 m_featureColumnSize(20),
c@48 43 m_rhythmWeighting(0.5f),
c@47 44 m_rhythmClipDuration(4.f), // seconds
c@47 45 m_rhythmClipOrigin(40.f), // seconds
c@47 46 m_rhythmClipFrameSize(0),
c@47 47 m_rhythmClipFrames(0),
c@47 48 m_rhythmColumnSize(20),
c@41 49 m_blockSize(0),
c@47 50 m_channels(0),
c@47 51 m_processRate(0),
c@47 52 m_frameNo(0),
c@47 53 m_done(false)
c@41 54 {
c@47 55 int rate = lrintf(m_inputSampleRate);
c@47 56 int internalRate = 22050;
c@47 57 int decimationFactor = rate / internalRate;
c@47 58 if (decimationFactor < 1) decimationFactor = 1;
c@47 59
c@47 60 // must be a power of two
c@47 61 while (decimationFactor & (decimationFactor - 1)) ++decimationFactor;
c@47 62
c@47 63 m_processRate = rate / decimationFactor; // may be 22050, 24000 etc
c@41 64 }
c@41 65
c@41 66 SimilarityPlugin::~SimilarityPlugin()
c@41 67 {
c@41 68 delete m_mfcc;
c@47 69 delete m_rhythmfcc;
c@42 70 delete m_chromagram;
c@41 71 delete m_decimator;
c@41 72 }
c@41 73
c@41 74 string
c@41 75 SimilarityPlugin::getIdentifier() const
c@41 76 {
c@41 77 return "qm-similarity";
c@41 78 }
c@41 79
c@41 80 string
c@41 81 SimilarityPlugin::getName() const
c@41 82 {
c@41 83 return "Similarity";
c@41 84 }
c@41 85
c@41 86 string
c@41 87 SimilarityPlugin::getDescription() const
c@41 88 {
c@42 89 return "Return a distance matrix for similarity between the input audio channels";
c@41 90 }
c@41 91
c@41 92 string
c@41 93 SimilarityPlugin::getMaker() const
c@41 94 {
c@50 95 return "Queen Mary, University of London";
c@41 96 }
c@41 97
c@41 98 int
c@41 99 SimilarityPlugin::getPluginVersion() const
c@41 100 {
c@41 101 return 1;
c@41 102 }
c@41 103
c@41 104 string
c@41 105 SimilarityPlugin::getCopyright() const
c@41 106 {
c@50 107 return "Plugin by Mark Levy, Kurt Jacobson and Chris Cannam. Copyright (c) 2008 QMUL - All Rights Reserved";
c@41 108 }
c@41 109
c@41 110 size_t
c@41 111 SimilarityPlugin::getMinChannelCount() const
c@41 112 {
c@43 113 return 1;
c@41 114 }
c@41 115
c@41 116 size_t
c@41 117 SimilarityPlugin::getMaxChannelCount() const
c@41 118 {
c@41 119 return 1024;
c@41 120 }
c@41 121
c@41 122 bool
c@41 123 SimilarityPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize)
c@41 124 {
c@52 125 if (channels < getMinChannelCount()) return false;
c@52 126
c@52 127 // Using more than getMaxChannelCount is not actually a problem
c@52 128 // for us. Using "incorrect" step and block sizes would be fine
c@52 129 // for timbral or chroma similarity, but will break rhythmic
c@52 130 // similarity, so we'd better enforce these.
c@41 131
c@41 132 if (stepSize != getPreferredStepSize()) {
c@41 133 std::cerr << "SimilarityPlugin::initialise: supplied step size "
c@41 134 << stepSize << " differs from required step size "
c@41 135 << getPreferredStepSize() << std::endl;
c@41 136 return false;
c@41 137 }
c@41 138
c@41 139 if (blockSize != getPreferredBlockSize()) {
c@41 140 std::cerr << "SimilarityPlugin::initialise: supplied block size "
c@41 141 << blockSize << " differs from required block size "
c@41 142 << getPreferredBlockSize() << std::endl;
c@41 143 return false;
c@41 144 }
c@41 145
c@41 146 m_blockSize = blockSize;
c@41 147 m_channels = channels;
c@41 148
c@44 149 m_lastNonEmptyFrame = std::vector<int>(m_channels);
c@44 150 for (int i = 0; i < m_channels; ++i) m_lastNonEmptyFrame[i] = -1;
c@44 151 m_frameNo = 0;
c@44 152
c@41 153 int decimationFactor = getDecimationFactor();
c@41 154 if (decimationFactor > 1) {
c@42 155 m_decimator = new Decimator(m_blockSize, decimationFactor);
c@41 156 }
c@41 157
c@42 158 if (m_type == TypeMFCC) {
c@42 159
c@42 160 m_featureColumnSize = 20;
c@42 161
c@47 162 MFCCConfig config(m_processRate);
c@42 163 config.fftsize = 2048;
c@42 164 config.nceps = m_featureColumnSize - 1;
c@42 165 config.want_c0 = true;
c@45 166 config.logpower = 1;
c@42 167 m_mfcc = new MFCC(config);
c@42 168 m_fftSize = m_mfcc->getfftlength();
c@47 169 m_rhythmClipFrameSize = m_fftSize / 4;
c@42 170
c@53 171 // std::cerr << "MFCC FS = " << config.FS << ", FFT size = " << m_fftSize<< std::endl;
c@43 172
c@42 173 } else if (m_type == TypeChroma) {
c@42 174
c@42 175 m_featureColumnSize = 12;
c@42 176
c@42 177 ChromaConfig config;
c@47 178 config.FS = m_processRate;
c@42 179 config.min = Pitch::getFrequencyForPitch(24, 0, 440);
c@42 180 config.max = Pitch::getFrequencyForPitch(96, 0, 440);
c@42 181 config.BPO = 12;
c@42 182 config.CQThresh = 0.0054;
c@49 183 // We don't normalise the chromagram's columns individually;
c@49 184 // we normalise the mean at the end instead
c@49 185 config.normalise = MathUtilities::NormaliseNone;
c@42 186 m_chromagram = new Chromagram(config);
c@42 187 m_fftSize = m_chromagram->getFrameSize();
c@42 188
c@53 189 // std::cerr << "fftsize = " << m_fftSize << std::endl;
c@47 190
c@47 191 m_rhythmClipFrameSize = m_fftSize / 16;
c@47 192 while (m_rhythmClipFrameSize < 512) m_rhythmClipFrameSize *= 2;
c@53 193 // std::cerr << "m_rhythmClipFrameSize = " << m_rhythmClipFrameSize << std::endl;
c@47 194
c@53 195 // std::cerr << "min = "<< config.min << ", max = " << config.max << std::endl;
c@42 196
c@42 197 } else {
c@42 198
c@42 199 std::cerr << "SimilarityPlugin::initialise: internal error: unknown type " << m_type << std::endl;
c@42 200 return false;
c@42 201 }
c@41 202
c@47 203 if (needRhythm()) {
c@47 204 m_rhythmClipFrames =
c@47 205 int(ceil((m_rhythmClipDuration * m_processRate)
c@47 206 / m_rhythmClipFrameSize));
c@47 207 std::cerr << "SimilarityPlugin::initialise: rhythm clip is "
c@47 208 << m_rhythmClipFrames << " frames of size "
c@47 209 << m_rhythmClipFrameSize << " at process rate "
c@47 210 << m_processRate << " ( = "
c@47 211 << (float(m_rhythmClipFrames * m_rhythmClipFrameSize) / m_processRate) << " sec )"
c@47 212 << std::endl;
c@47 213
c@47 214 MFCCConfig config(m_processRate);
c@47 215 config.fftsize = m_rhythmClipFrameSize;
c@47 216 config.nceps = m_featureColumnSize - 1;
c@47 217 config.want_c0 = true;
c@47 218 config.logpower = 1;
c@47 219 config.window = RectangularWindow; // because no overlap
c@47 220 m_rhythmfcc = new MFCC(config);
c@47 221 }
c@47 222
c@41 223 for (int i = 0; i < m_channels; ++i) {
c@47 224
c@42 225 m_values.push_back(FeatureMatrix());
c@47 226
c@47 227 if (needRhythm()) {
c@47 228 m_rhythmValues.push_back(FeatureColumnQueue());
c@47 229 }
c@41 230 }
c@41 231
c@47 232 m_done = false;
c@47 233
c@41 234 return true;
c@41 235 }
c@41 236
c@41 237 void
c@41 238 SimilarityPlugin::reset()
c@41 239 {
c@41 240 //!!!
c@47 241 m_done = false;
c@41 242 }
c@41 243
c@41 244 int
c@41 245 SimilarityPlugin::getDecimationFactor() const
c@41 246 {
c@41 247 int rate = lrintf(m_inputSampleRate);
c@47 248 return rate / m_processRate;
c@41 249 }
c@41 250
c@41 251 size_t
c@41 252 SimilarityPlugin::getPreferredStepSize() const
c@41 253 {
c@42 254 if (m_blockSize == 0) calculateBlockSize();
c@47 255
c@47 256 // there is also an assumption to this effect in process()
c@47 257 // (referring to m_fftSize/2 instead of a literal post-decimation
c@47 258 // step size):
c@45 259 return m_blockSize/2;
c@41 260 }
c@41 261
c@41 262 size_t
c@41 263 SimilarityPlugin::getPreferredBlockSize() const
c@41 264 {
c@42 265 if (m_blockSize == 0) calculateBlockSize();
c@42 266 return m_blockSize;
c@42 267 }
c@42 268
c@42 269 void
c@42 270 SimilarityPlugin::calculateBlockSize() const
c@42 271 {
c@42 272 if (m_blockSize != 0) return;
c@42 273 int decimationFactor = getDecimationFactor();
c@42 274 if (m_type == TypeChroma) {
c@42 275 ChromaConfig config;
c@47 276 config.FS = m_processRate;
c@42 277 config.min = Pitch::getFrequencyForPitch(24, 0, 440);
c@42 278 config.max = Pitch::getFrequencyForPitch(96, 0, 440);
c@42 279 config.BPO = 12;
c@42 280 config.CQThresh = 0.0054;
c@49 281 config.normalise = MathUtilities::NormaliseNone;
c@42 282 Chromagram *c = new Chromagram(config);
c@42 283 size_t sz = c->getFrameSize();
c@42 284 delete c;
c@42 285 m_blockSize = sz * decimationFactor;
c@42 286 } else {
c@42 287 m_blockSize = 2048 * decimationFactor;
c@42 288 }
c@41 289 }
c@41 290
c@41 291 SimilarityPlugin::ParameterList SimilarityPlugin::getParameterDescriptors() const
c@41 292 {
c@41 293 ParameterList list;
c@42 294
c@42 295 ParameterDescriptor desc;
c@42 296 desc.identifier = "featureType";
c@42 297 desc.name = "Feature Type";
c@48 298 desc.description = "Audio feature used for similarity measure. Timbral: use the first 20 MFCCs (19 plus C0). Chromatic: use 12 bin-per-octave chroma. Rhythmic: compare beat spectra of short regions.";
c@42 299 desc.unit = "";
c@42 300 desc.minValue = 0;
c@48 301 desc.maxValue = 4;
c@48 302 desc.defaultValue = 1;
c@42 303 desc.isQuantized = true;
c@42 304 desc.quantizeStep = 1;
c@48 305 desc.valueNames.push_back("Timbre");
c@48 306 desc.valueNames.push_back("Timbre and Rhythm");
c@48 307 desc.valueNames.push_back("Chroma");
c@48 308 desc.valueNames.push_back("Chroma and Rhythm");
c@48 309 desc.valueNames.push_back("Rhythm only");
c@42 310 list.push_back(desc);
c@48 311 /*
c@47 312 desc.identifier = "rhythmWeighting";
c@47 313 desc.name = "Influence of Rhythm";
c@47 314 desc.description = "Proportion of similarity measure made up from rhythmic similarity component, from 0 (entirely timbral or chromatic) to 100 (entirely rhythmic).";
c@47 315 desc.unit = "%";
c@47 316 desc.minValue = 0;
c@47 317 desc.maxValue = 100;
c@47 318 desc.defaultValue = 0;
c@48 319 desc.isQuantized = false;
c@47 320 desc.valueNames.clear();
c@47 321 list.push_back(desc);
c@48 322 */
c@41 323 return list;
c@41 324 }
c@41 325
c@41 326 float
c@41 327 SimilarityPlugin::getParameter(std::string param) const
c@41 328 {
c@42 329 if (param == "featureType") {
c@48 330
c@48 331 if (m_rhythmWeighting > m_allRhythm) {
c@48 332 return 4;
c@48 333 }
c@48 334
c@48 335 switch (m_type) {
c@48 336
c@48 337 case TypeMFCC:
c@48 338 if (m_rhythmWeighting < m_noRhythm) return 0;
c@48 339 else return 1;
c@48 340 break;
c@48 341
c@48 342 case TypeChroma:
c@48 343 if (m_rhythmWeighting < m_noRhythm) return 2;
c@48 344 else return 3;
c@48 345 break;
c@48 346 }
c@48 347
c@48 348 return 1;
c@48 349
c@48 350 // } else if (param == "rhythmWeighting") {
c@48 351 // return nearbyint(m_rhythmWeighting * 100.0);
c@42 352 }
c@42 353
c@41 354 std::cerr << "WARNING: SimilarityPlugin::getParameter: unknown parameter \""
c@41 355 << param << "\"" << std::endl;
c@41 356 return 0.0;
c@41 357 }
c@41 358
c@41 359 void
c@41 360 SimilarityPlugin::setParameter(std::string param, float value)
c@41 361 {
c@42 362 if (param == "featureType") {
c@48 363
c@42 364 int v = int(value + 0.1);
c@48 365
c@48 366 Type newType = m_type;
c@48 367
c@48 368 switch (v) {
c@48 369 case 0: newType = TypeMFCC; m_rhythmWeighting = 0.0f; break;
c@48 370 case 1: newType = TypeMFCC; m_rhythmWeighting = 0.5f; break;
c@48 371 case 2: newType = TypeChroma; m_rhythmWeighting = 0.0f; break;
c@48 372 case 3: newType = TypeChroma; m_rhythmWeighting = 0.5f; break;
c@48 373 case 4: newType = TypeMFCC; m_rhythmWeighting = 1.f; break;
c@48 374 }
c@48 375
c@48 376 if (newType != m_type) m_blockSize = 0;
c@48 377
c@48 378 m_type = newType;
c@42 379 return;
c@48 380
c@48 381 // } else if (param == "rhythmWeighting") {
c@48 382 // m_rhythmWeighting = value / 100;
c@48 383 // return;
c@42 384 }
c@42 385
c@41 386 std::cerr << "WARNING: SimilarityPlugin::setParameter: unknown parameter \""
c@41 387 << param << "\"" << std::endl;
c@41 388 }
c@41 389
c@41 390 SimilarityPlugin::OutputList
c@41 391 SimilarityPlugin::getOutputDescriptors() const
c@41 392 {
c@41 393 OutputList list;
c@41 394
c@41 395 OutputDescriptor similarity;
c@43 396 similarity.identifier = "distancematrix";
c@43 397 similarity.name = "Distance Matrix";
c@43 398 similarity.description = "Distance matrix for similarity metric. Smaller = more similar. Should be symmetrical.";
c@41 399 similarity.unit = "";
c@41 400 similarity.hasFixedBinCount = true;
c@41 401 similarity.binCount = m_channels;
c@41 402 similarity.hasKnownExtents = false;
c@41 403 similarity.isQuantized = false;
c@41 404 similarity.sampleType = OutputDescriptor::FixedSampleRate;
c@41 405 similarity.sampleRate = 1;
c@41 406
c@43 407 m_distanceMatrixOutput = list.size();
c@41 408 list.push_back(similarity);
c@41 409
c@43 410 OutputDescriptor simvec;
c@43 411 simvec.identifier = "distancevector";
c@43 412 simvec.name = "Distance from First Channel";
c@43 413 simvec.description = "Distance vector for similarity of each channel to the first channel. Smaller = more similar.";
c@43 414 simvec.unit = "";
c@43 415 simvec.hasFixedBinCount = true;
c@43 416 simvec.binCount = m_channels;
c@43 417 simvec.hasKnownExtents = false;
c@43 418 simvec.isQuantized = false;
c@43 419 simvec.sampleType = OutputDescriptor::FixedSampleRate;
c@43 420 simvec.sampleRate = 1;
c@43 421
c@43 422 m_distanceVectorOutput = list.size();
c@43 423 list.push_back(simvec);
c@43 424
c@44 425 OutputDescriptor sortvec;
c@44 426 sortvec.identifier = "sorteddistancevector";
c@44 427 sortvec.name = "Ordered Distances from First Channel";
c@44 428 sortvec.description = "Vector of the order of other channels in similarity to the first, followed by distance vector for similarity of each to the first. Smaller = more similar.";
c@44 429 sortvec.unit = "";
c@44 430 sortvec.hasFixedBinCount = true;
c@44 431 sortvec.binCount = m_channels;
c@44 432 sortvec.hasKnownExtents = false;
c@44 433 sortvec.isQuantized = false;
c@44 434 sortvec.sampleType = OutputDescriptor::FixedSampleRate;
c@44 435 sortvec.sampleRate = 1;
c@44 436
c@44 437 m_sortedVectorOutput = list.size();
c@44 438 list.push_back(sortvec);
c@44 439
c@41 440 OutputDescriptor means;
c@41 441 means.identifier = "means";
c@42 442 means.name = "Feature Means";
c@43 443 means.description = "Means of the feature bins. Feature time (sec) corresponds to input channel. Number of bins depends on selected feature type.";
c@41 444 means.unit = "";
c@41 445 means.hasFixedBinCount = true;
c@43 446 means.binCount = m_featureColumnSize;
c@41 447 means.hasKnownExtents = false;
c@41 448 means.isQuantized = false;
c@43 449 means.sampleType = OutputDescriptor::FixedSampleRate;
c@43 450 means.sampleRate = 1;
c@41 451
c@43 452 m_meansOutput = list.size();
c@41 453 list.push_back(means);
c@41 454
c@41 455 OutputDescriptor variances;
c@41 456 variances.identifier = "variances";
c@42 457 variances.name = "Feature Variances";
c@43 458 variances.description = "Variances of the feature bins. Feature time (sec) corresponds to input channel. Number of bins depends on selected feature type.";
c@41 459 variances.unit = "";
c@41 460 variances.hasFixedBinCount = true;
c@43 461 variances.binCount = m_featureColumnSize;
c@41 462 variances.hasKnownExtents = false;
c@41 463 variances.isQuantized = false;
c@43 464 variances.sampleType = OutputDescriptor::FixedSampleRate;
c@43 465 variances.sampleRate = 1;
c@41 466
c@43 467 m_variancesOutput = list.size();
c@41 468 list.push_back(variances);
c@41 469
c@47 470 OutputDescriptor beatspectrum;
c@47 471 beatspectrum.identifier = "beatspectrum";
c@47 472 beatspectrum.name = "Beat Spectra";
c@47 473 beatspectrum.description = "Rhythmic self-similarity vectors (beat spectra) for the input channels. Feature time (sec) corresponds to input channel. Not returned if rhythm weighting is zero.";
c@47 474 beatspectrum.unit = "";
c@47 475 if (m_rhythmClipFrames > 0) {
c@47 476 beatspectrum.hasFixedBinCount = true;
c@47 477 beatspectrum.binCount = m_rhythmClipFrames / 2;
c@47 478 } else {
c@47 479 beatspectrum.hasFixedBinCount = false;
c@47 480 }
c@47 481 beatspectrum.hasKnownExtents = false;
c@47 482 beatspectrum.isQuantized = false;
c@47 483 beatspectrum.sampleType = OutputDescriptor::FixedSampleRate;
c@47 484 beatspectrum.sampleRate = 1;
c@47 485
c@47 486 m_beatSpectraOutput = list.size();
c@47 487 list.push_back(beatspectrum);
c@47 488
c@41 489 return list;
c@41 490 }
c@41 491
c@41 492 SimilarityPlugin::FeatureSet
c@41 493 SimilarityPlugin::process(const float *const *inputBuffers, Vamp::RealTime /* timestamp */)
c@41 494 {
c@47 495 if (m_done) {
c@47 496 return FeatureSet();
c@47 497 }
c@47 498
c@41 499 double *dblbuf = new double[m_blockSize];
c@41 500 double *decbuf = dblbuf;
c@42 501 if (m_decimator) decbuf = new double[m_fftSize];
c@42 502
c@47 503 double *raw = new double[std::max(m_featureColumnSize,
c@47 504 m_rhythmColumnSize)];
c@41 505
c@43 506 float threshold = 1e-10;
c@43 507
c@47 508 bool someRhythmFrameNeeded = false;
c@47 509
c@41 510 for (size_t c = 0; c < m_channels; ++c) {
c@41 511
c@43 512 bool empty = true;
c@43 513
c@41 514 for (int i = 0; i < m_blockSize; ++i) {
c@43 515 float val = inputBuffers[c][i];
c@43 516 if (fabs(val) > threshold) empty = false;
c@43 517 dblbuf[i] = val;
c@41 518 }
c@41 519
c@47 520 if (empty) {
c@47 521 if (needRhythm() && ((m_frameNo % 2) == 0)) {
c@47 522 for (int i = 0; i < m_fftSize / m_rhythmClipFrameSize; ++i) {
c@47 523 if (m_rhythmValues[c].size() < m_rhythmClipFrames) {
c@47 524 FeatureColumn mf(m_rhythmColumnSize);
c@47 525 for (int i = 0; i < m_rhythmColumnSize; ++i) {
c@47 526 mf[i] = 0.0;
c@47 527 }
c@47 528 m_rhythmValues[c].push_back(mf);
c@47 529 }
c@47 530 }
c@47 531 }
c@47 532 continue;
c@47 533 }
c@47 534
c@44 535 m_lastNonEmptyFrame[c] = m_frameNo;
c@43 536
c@41 537 if (m_decimator) {
c@41 538 m_decimator->process(dblbuf, decbuf);
c@41 539 }
c@42 540
c@47 541 if (needTimbre()) {
c@47 542
c@47 543 if (m_type == TypeMFCC) {
c@47 544 m_mfcc->process(decbuf, raw);
c@47 545 } else if (m_type == TypeChroma) {
c@47 546 raw = m_chromagram->process(decbuf);
c@47 547 }
c@41 548
c@47 549 FeatureColumn mf(m_featureColumnSize);
c@47 550 for (int i = 0; i < m_featureColumnSize; ++i) {
c@47 551 mf[i] = raw[i];
c@47 552 }
c@47 553
c@47 554 m_values[c].push_back(mf);
c@44 555 }
c@41 556
c@47 557 // std::cerr << "needRhythm = " << needRhythm() << ", frame = " << m_frameNo << std::endl;
c@47 558
c@47 559 if (needRhythm() && ((m_frameNo % 2) == 0)) {
c@47 560
c@47 561 // The incoming frames are overlapping; we only use every
c@47 562 // other one, because we don't want the overlap (it would
c@47 563 // screw up the rhythm)
c@47 564
c@47 565 int frameOffset = 0;
c@47 566
c@47 567 while (frameOffset + m_rhythmClipFrameSize <= m_fftSize) {
c@47 568
c@47 569 bool needRhythmFrame = true;
c@47 570
c@47 571 if (m_rhythmValues[c].size() >= m_rhythmClipFrames) {
c@47 572
c@47 573 needRhythmFrame = false;
c@47 574
c@47 575 // assumes hopsize = framesize/2
c@47 576 float current = m_frameNo * (m_fftSize/2) + frameOffset;
c@47 577 current = current / m_processRate;
c@47 578 if (current - m_rhythmClipDuration < m_rhythmClipOrigin) {
c@47 579 needRhythmFrame = true;
c@47 580 m_rhythmValues[c].pop_front();
c@47 581 }
c@47 582
c@53 583 // if (needRhythmFrame) {
c@53 584 // std::cerr << "at current = " <<current << " (frame = " << m_frameNo << "), have " << m_rhythmValues[c].size() << ", need rhythm = " << needRhythmFrame << std::endl;
c@53 585 // }
c@47 586
c@47 587 }
c@47 588
c@47 589 if (needRhythmFrame) {
c@47 590
c@47 591 someRhythmFrameNeeded = true;
c@47 592
c@47 593 m_rhythmfcc->process(decbuf + frameOffset, raw);
c@47 594
c@47 595 FeatureColumn mf(m_rhythmColumnSize);
c@47 596 for (int i = 0; i < m_rhythmColumnSize; ++i) {
c@47 597 mf[i] = raw[i];
c@47 598 }
c@47 599
c@47 600 m_rhythmValues[c].push_back(mf);
c@47 601 }
c@47 602
c@47 603 frameOffset += m_rhythmClipFrameSize;
c@47 604 }
c@47 605 }
c@47 606 }
c@47 607
c@47 608 if (!needTimbre() && !someRhythmFrameNeeded && ((m_frameNo % 2) == 0)) {
c@53 609 // std::cerr << "done!" << std::endl;
c@47 610 m_done = true;
c@41 611 }
c@41 612
c@41 613 if (m_decimator) delete[] decbuf;
c@41 614 delete[] dblbuf;
c@47 615 delete[] raw;
c@41 616
c@44 617 ++m_frameNo;
c@44 618
c@41 619 return FeatureSet();
c@41 620 }
c@41 621
c@47 622 SimilarityPlugin::FeatureMatrix
c@47 623 SimilarityPlugin::calculateTimbral(FeatureSet &returnFeatures)
c@41 624 {
c@47 625 FeatureMatrix m(m_channels); // means
c@47 626 FeatureMatrix v(m_channels); // variances
c@41 627
c@41 628 for (int i = 0; i < m_channels; ++i) {
c@41 629
c@42 630 FeatureColumn mean(m_featureColumnSize), variance(m_featureColumnSize);
c@41 631
c@42 632 for (int j = 0; j < m_featureColumnSize; ++j) {
c@41 633
c@43 634 mean[j] = 0.0;
c@43 635 variance[j] = 0.0;
c@41 636 int count;
c@41 637
c@44 638 // We want to take values up to, but not including, the
c@44 639 // last non-empty frame (which may be partial)
c@43 640
c@44 641 int sz = m_lastNonEmptyFrame[i];
c@44 642 if (sz < 0) sz = 0;
c@43 643
c@41 644 count = 0;
c@43 645 for (int k = 0; k < sz; ++k) {
c@42 646 double val = m_values[i][k][j];
c@41 647 if (isnan(val) || isinf(val)) continue;
c@41 648 mean[j] += val;
c@41 649 ++count;
c@41 650 }
c@41 651 if (count > 0) mean[j] /= count;
c@41 652
c@41 653 count = 0;
c@43 654 for (int k = 0; k < sz; ++k) {
c@42 655 double val = ((m_values[i][k][j] - mean[j]) *
c@42 656 (m_values[i][k][j] - mean[j]));
c@41 657 if (isnan(val) || isinf(val)) continue;
c@41 658 variance[j] += val;
c@41 659 ++count;
c@41 660 }
c@41 661 if (count > 0) variance[j] /= count;
c@41 662 }
c@41 663
c@41 664 m[i] = mean;
c@41 665 v[i] = variance;
c@41 666 }
c@41 667
c@47 668 FeatureMatrix distances(m_channels);
c@42 669
c@48 670 if (m_type == TypeMFCC) {
c@48 671
c@48 672 // "Despite the fact that MFCCs extracted from music are
c@48 673 // clearly not Gaussian, [14] showed, somewhat surprisingly,
c@48 674 // that a similarity function comparing single Gaussians
c@48 675 // modelling MFCCs for each track can perform as well as
c@48 676 // mixture models. A great advantage of using single
c@48 677 // Gaussians is that a simple closed form exists for the KL
c@48 678 // divergence." -- Mark Levy, "Lightweight measures for
c@48 679 // timbral similarity of musical audio"
c@48 680 // (http://www.elec.qmul.ac.uk/easaier/papers/mlevytimbralsimilarity.pdf)
c@48 681
c@48 682 KLDivergence kld;
c@48 683
c@48 684 for (int i = 0; i < m_channels; ++i) {
c@48 685 for (int j = 0; j < m_channels; ++j) {
c@48 686 double d = kld.distanceGaussian(m[i], v[i], m[j], v[j]);
c@48 687 distances[i].push_back(d);
c@48 688 }
c@48 689 }
c@48 690
c@48 691 } else {
c@48 692
c@49 693 // We use the KL divergence for distributions of discrete
c@49 694 // variables, as chroma are histograms already. Or at least,
c@49 695 // they will be when we've normalised them like this:
c@49 696 for (int i = 0; i < m_channels; ++i) {
c@49 697 MathUtilities::normalise(m[i], MathUtilities::NormaliseUnitSum);
c@49 698 }
c@48 699
c@48 700 KLDivergence kld;
c@48 701
c@48 702 for (int i = 0; i < m_channels; ++i) {
c@48 703 for (int j = 0; j < m_channels; ++j) {
c@48 704 double d = kld.distanceDistribution(m[i], m[j], true);
c@48 705 distances[i].push_back(d);
c@48 706 }
c@41 707 }
c@41 708 }
c@47 709
c@44 710 Feature feature;
c@44 711 feature.hasTimestamp = true;
c@44 712
c@44 713 char labelBuffer[100];
c@43 714
c@41 715 for (int i = 0; i < m_channels; ++i) {
c@41 716
c@41 717 feature.timestamp = Vamp::RealTime(i, 0);
c@41 718
c@44 719 sprintf(labelBuffer, "Means for channel %d", i+1);
c@44 720 feature.label = labelBuffer;
c@44 721
c@41 722 feature.values.clear();
c@42 723 for (int k = 0; k < m_featureColumnSize; ++k) {
c@41 724 feature.values.push_back(m[i][k]);
c@41 725 }
c@41 726
c@43 727 returnFeatures[m_meansOutput].push_back(feature);
c@41 728
c@44 729 sprintf(labelBuffer, "Variances for channel %d", i+1);
c@44 730 feature.label = labelBuffer;
c@44 731
c@41 732 feature.values.clear();
c@42 733 for (int k = 0; k < m_featureColumnSize; ++k) {
c@41 734 feature.values.push_back(v[i][k]);
c@41 735 }
c@41 736
c@43 737 returnFeatures[m_variancesOutput].push_back(feature);
c@47 738 }
c@47 739
c@47 740 return distances;
c@47 741 }
c@47 742
c@47 743 SimilarityPlugin::FeatureMatrix
c@47 744 SimilarityPlugin::calculateRhythmic(FeatureSet &returnFeatures)
c@47 745 {
c@47 746 if (!needRhythm()) return FeatureMatrix();
c@47 747
c@47 748 BeatSpectrum bscalc;
c@47 749 CosineDistance cd;
c@47 750
c@47 751 // Our rhythm feature matrix is a deque of vectors for practical
c@47 752 // reasons, but BeatSpectrum::process wants a vector of vectors
c@47 753 // (which is what FeatureMatrix happens to be).
c@47 754
c@47 755 FeatureMatrixSet bsinput(m_channels);
c@47 756 for (int i = 0; i < m_channels; ++i) {
c@47 757 for (int j = 0; j < m_rhythmValues[i].size(); ++j) {
c@47 758 bsinput[i].push_back(m_rhythmValues[i][j]);
c@47 759 }
c@47 760 }
c@47 761
c@47 762 FeatureMatrix bs(m_channels);
c@47 763 for (int i = 0; i < m_channels; ++i) {
c@47 764 bs[i] = bscalc.process(bsinput[i]);
c@47 765 }
c@47 766
c@47 767 FeatureMatrix distances(m_channels);
c@47 768 for (int i = 0; i < m_channels; ++i) {
c@47 769 for (int j = 0; j < m_channels; ++j) {
c@47 770 double d = cd.distance(bs[i], bs[j]);
c@47 771 distances[i].push_back(d);
c@47 772 }
c@47 773 }
c@47 774
c@47 775 Feature feature;
c@47 776 feature.hasTimestamp = true;
c@47 777
c@47 778 char labelBuffer[100];
c@47 779
c@47 780 for (int i = 0; i < m_channels; ++i) {
c@47 781
c@47 782 feature.timestamp = Vamp::RealTime(i, 0);
c@47 783
c@47 784 sprintf(labelBuffer, "Beat spectrum for channel %d", i+1);
c@47 785 feature.label = labelBuffer;
c@47 786
c@47 787 feature.values.clear();
c@47 788 for (int j = 0; j < bs[i].size(); ++j) {
c@47 789 feature.values.push_back(bs[i][j]);
c@47 790 }
c@47 791
c@47 792 returnFeatures[m_beatSpectraOutput].push_back(feature);
c@47 793 }
c@47 794
c@47 795 return distances;
c@47 796 }
c@47 797
c@47 798 double
c@47 799 SimilarityPlugin::getDistance(const FeatureMatrix &timbral,
c@47 800 const FeatureMatrix &rhythmic,
c@47 801 int i, int j)
c@47 802 {
c@47 803 double distance = 1.0;
c@47 804 if (needTimbre()) distance *= timbral[i][j];
c@47 805 if (needRhythm()) distance *= rhythmic[i][j];
c@47 806 return distance;
c@47 807 }
c@47 808
c@47 809 SimilarityPlugin::FeatureSet
c@47 810 SimilarityPlugin::getRemainingFeatures()
c@47 811 {
c@47 812 FeatureSet returnFeatures;
c@47 813
c@47 814 // We want to return a matrix of the distances between channels,
c@47 815 // but Vamp doesn't have a matrix return type so we will actually
c@47 816 // return a series of vectors
c@47 817
c@47 818 FeatureMatrix timbralDistances, rhythmicDistances;
c@47 819
c@47 820 if (needTimbre()) {
c@47 821 timbralDistances = calculateTimbral(returnFeatures);
c@47 822 }
c@47 823
c@47 824 if (needRhythm()) {
c@47 825 rhythmicDistances = calculateRhythmic(returnFeatures);
c@47 826 }
c@47 827
c@47 828 // We give all features a timestamp, otherwise hosts will tend to
c@47 829 // stamp them at the end of the file, which is annoying
c@47 830
c@47 831 Feature feature;
c@47 832 feature.hasTimestamp = true;
c@47 833
c@47 834 Feature distanceVectorFeature;
c@47 835 distanceVectorFeature.label = "Distance from first channel";
c@47 836 distanceVectorFeature.hasTimestamp = true;
c@47 837 distanceVectorFeature.timestamp = Vamp::RealTime::zeroTime;
c@47 838
c@47 839 std::map<double, int> sorted;
c@47 840
c@47 841 char labelBuffer[100];
c@47 842
c@47 843 for (int i = 0; i < m_channels; ++i) {
c@47 844
c@47 845 feature.timestamp = Vamp::RealTime(i, 0);
c@41 846
c@41 847 feature.values.clear();
c@41 848 for (int j = 0; j < m_channels; ++j) {
c@47 849 double dist = getDistance(timbralDistances, rhythmicDistances, i, j);
c@47 850 feature.values.push_back(dist);
c@41 851 }
c@43 852
c@44 853 sprintf(labelBuffer, "Distances from channel %d", i+1);
c@44 854 feature.label = labelBuffer;
c@41 855
c@43 856 returnFeatures[m_distanceMatrixOutput].push_back(feature);
c@43 857
c@47 858 double fromFirst =
c@47 859 getDistance(timbralDistances, rhythmicDistances, 0, i);
c@44 860
c@47 861 distanceVectorFeature.values.push_back(fromFirst);
c@47 862 sorted[fromFirst] = i;
c@41 863 }
c@41 864
c@43 865 returnFeatures[m_distanceVectorOutput].push_back(distanceVectorFeature);
c@43 866
c@44 867 feature.label = "Order of channels by similarity to first channel";
c@44 868 feature.values.clear();
c@44 869 feature.timestamp = Vamp::RealTime(0, 0);
c@44 870
c@44 871 for (std::map<double, int>::iterator i = sorted.begin();
c@44 872 i != sorted.end(); ++i) {
c@45 873 feature.values.push_back(i->second + 1);
c@44 874 }
c@44 875
c@44 876 returnFeatures[m_sortedVectorOutput].push_back(feature);
c@44 877
c@44 878 feature.label = "Ordered distances of channels from first channel";
c@44 879 feature.values.clear();
c@44 880 feature.timestamp = Vamp::RealTime(1, 0);
c@44 881
c@44 882 for (std::map<double, int>::iterator i = sorted.begin();
c@44 883 i != sorted.end(); ++i) {
c@44 884 feature.values.push_back(i->first);
c@44 885 }
c@44 886
c@44 887 returnFeatures[m_sortedVectorOutput].push_back(feature);
c@44 888
c@41 889 return returnFeatures;
c@41 890 }