1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
6 * Copyright 2009 Centre for Digital Music, Queen Mary, University of London.
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version. See the file
12 COPYING included with this distribution for more information.
18 #include "SimilarityPlugin.h"
19 #include "base/Pitch.h"
20 #include "dsp/mfcc/MFCC.h"
21 #include "dsp/chromagram/Chromagram.h"
22 #include "dsp/rateconversion/Decimator.h"
23 #include "dsp/rhythm/BeatSpectrum.h"
24 #include "maths/KLDivergence.h"
25 #include "maths/CosineDistance.h"
26 #include "maths/MathUtilities.h"
32 using std::ostringstream;
35 SimilarityPlugin::m_noRhythm = 0.009;
38 SimilarityPlugin::m_allRhythm = 0.991;
40 SimilarityPlugin::SimilarityPlugin(float inputSampleRate) :
41 Plugin(inputSampleRate),
47 m_featureColumnSize(20),
48 m_rhythmWeighting(0.5f),
49 m_rhythmClipDuration(4.f), // seconds
50 m_rhythmClipOrigin(40.f), // seconds
51 m_rhythmClipFrameSize(0),
52 m_rhythmClipFrames(0),
53 m_rhythmColumnSize(20),
60 int rate = lrintf(m_inputSampleRate);
61 int internalRate = 22050;
62 int decimationFactor = rate / internalRate;
63 if (decimationFactor < 1) decimationFactor = 1;
65 // must be a power of two
66 while (decimationFactor & (decimationFactor - 1)) ++decimationFactor;
68 m_processRate = rate / decimationFactor; // may be 22050, 24000 etc
71 SimilarityPlugin::~SimilarityPlugin()
80 SimilarityPlugin::getIdentifier() const
82 return "qm-similarity";
86 SimilarityPlugin::getName() const
92 SimilarityPlugin::getDescription() const
94 return "Return a distance matrix for similarity between the input audio channels";
98 SimilarityPlugin::getMaker() const
100 return "Queen Mary, University of London";
104 SimilarityPlugin::getPluginVersion() const
110 SimilarityPlugin::getCopyright() const
112 return "Plugin by Mark Levy, Kurt Jacobson and Chris Cannam. Copyright (c) 2009 QMUL - All Rights Reserved";
116 SimilarityPlugin::getMinChannelCount() const
122 SimilarityPlugin::getMaxChannelCount() const
128 SimilarityPlugin::getDecimationFactor() const
130 int rate = lrintf(m_inputSampleRate);
131 return rate / m_processRate;
135 SimilarityPlugin::getPreferredStepSize() const
137 if (m_blockSize == 0) calculateBlockSize();
139 // there is also an assumption to this effect in process()
140 // (referring to m_fftSize/2 instead of a literal post-decimation
142 return m_blockSize/2;
146 SimilarityPlugin::getPreferredBlockSize() const
148 if (m_blockSize == 0) calculateBlockSize();
153 SimilarityPlugin::calculateBlockSize() const
155 if (m_blockSize != 0) return;
156 int decimationFactor = getDecimationFactor();
157 m_blockSize = 2048 * decimationFactor;
160 SimilarityPlugin::ParameterList SimilarityPlugin::getParameterDescriptors() const
164 ParameterDescriptor desc;
165 desc.identifier = "featureType";
166 desc.name = "Feature Type";
167 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.";
171 desc.defaultValue = 1;
172 desc.isQuantized = true;
173 desc.quantizeStep = 1;
174 desc.valueNames.push_back("Timbre");
175 desc.valueNames.push_back("Timbre and Rhythm");
176 desc.valueNames.push_back("Chroma");
177 desc.valueNames.push_back("Chroma and Rhythm");
178 desc.valueNames.push_back("Rhythm only");
179 list.push_back(desc);
181 desc.identifier = "rhythmWeighting";
182 desc.name = "Influence of Rhythm";
183 desc.description = "Proportion of similarity measure made up from rhythmic similarity component, from 0 (entirely timbral or chromatic) to 100 (entirely rhythmic).";
187 desc.defaultValue = 0;
188 desc.isQuantized = false;
189 desc.valueNames.clear();
190 list.push_back(desc);
196 SimilarityPlugin::getParameter(std::string param) const
198 if (param == "featureType") {
200 if (m_rhythmWeighting > m_allRhythm) {
207 if (m_rhythmWeighting < m_noRhythm) return 0;
212 if (m_rhythmWeighting < m_noRhythm) return 2;
219 // } else if (param == "rhythmWeighting") {
220 // return nearbyint(m_rhythmWeighting * 100.0);
223 std::cerr << "WARNING: SimilarityPlugin::getParameter: unknown parameter \""
224 << param << "\"" << std::endl;
229 SimilarityPlugin::setParameter(std::string param, float value)
231 if (param == "featureType") {
233 int v = int(value + 0.1);
235 Type newType = m_type;
238 case 0: newType = TypeMFCC; m_rhythmWeighting = 0.0f; break;
239 case 1: newType = TypeMFCC; m_rhythmWeighting = 0.5f; break;
240 case 2: newType = TypeChroma; m_rhythmWeighting = 0.0f; break;
241 case 3: newType = TypeChroma; m_rhythmWeighting = 0.5f; break;
242 case 4: newType = TypeMFCC; m_rhythmWeighting = 1.f; break;
245 if (newType != m_type) m_blockSize = 0;
250 // } else if (param == "rhythmWeighting") {
251 // m_rhythmWeighting = value / 100;
255 std::cerr << "WARNING: SimilarityPlugin::setParameter: unknown parameter \""
256 << param << "\"" << std::endl;
259 SimilarityPlugin::OutputList
260 SimilarityPlugin::getOutputDescriptors() const
264 OutputDescriptor similarity;
265 similarity.identifier = "distancematrix";
266 similarity.name = "Distance Matrix";
267 similarity.description = "Distance matrix for similarity metric. Smaller = more similar. Should be symmetrical.";
268 similarity.unit = "";
269 similarity.hasFixedBinCount = true;
270 similarity.binCount = m_channels;
271 similarity.hasKnownExtents = false;
272 similarity.isQuantized = false;
273 similarity.sampleType = OutputDescriptor::FixedSampleRate;
274 similarity.sampleRate = 1;
276 m_distanceMatrixOutput = list.size();
277 list.push_back(similarity);
279 OutputDescriptor simvec;
280 simvec.identifier = "distancevector";
281 simvec.name = "Distance from First Channel";
282 simvec.description = "Distance vector for similarity of each channel to the first channel. Smaller = more similar.";
284 simvec.hasFixedBinCount = true;
285 simvec.binCount = m_channels;
286 simvec.hasKnownExtents = false;
287 simvec.isQuantized = false;
288 simvec.sampleType = OutputDescriptor::FixedSampleRate;
289 simvec.sampleRate = 1;
291 m_distanceVectorOutput = list.size();
292 list.push_back(simvec);
294 OutputDescriptor sortvec;
295 sortvec.identifier = "sorteddistancevector";
296 sortvec.name = "Ordered Distances from First Channel";
297 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.";
299 sortvec.hasFixedBinCount = true;
300 sortvec.binCount = m_channels;
301 sortvec.hasKnownExtents = false;
302 sortvec.isQuantized = false;
303 sortvec.sampleType = OutputDescriptor::FixedSampleRate;
304 sortvec.sampleRate = 1;
306 m_sortedVectorOutput = list.size();
307 list.push_back(sortvec);
309 OutputDescriptor means;
310 means.identifier = "means";
311 means.name = "Feature Means";
312 means.description = "Means of the feature bins. Feature time (sec) corresponds to input channel. Number of bins depends on selected feature type.";
314 means.hasFixedBinCount = true;
315 means.binCount = m_featureColumnSize;
316 means.hasKnownExtents = false;
317 means.isQuantized = false;
318 means.sampleType = OutputDescriptor::FixedSampleRate;
319 means.sampleRate = 1;
321 m_meansOutput = list.size();
322 list.push_back(means);
324 OutputDescriptor variances;
325 variances.identifier = "variances";
326 variances.name = "Feature Variances";
327 variances.description = "Variances of the feature bins. Feature time (sec) corresponds to input channel. Number of bins depends on selected feature type.";
329 variances.hasFixedBinCount = true;
330 variances.binCount = m_featureColumnSize;
331 variances.hasKnownExtents = false;
332 variances.isQuantized = false;
333 variances.sampleType = OutputDescriptor::FixedSampleRate;
334 variances.sampleRate = 1;
336 m_variancesOutput = list.size();
337 list.push_back(variances);
339 OutputDescriptor beatspectrum;
340 beatspectrum.identifier = "beatspectrum";
341 beatspectrum.name = "Beat Spectra";
342 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.";
343 beatspectrum.unit = "";
344 if (m_rhythmClipFrames > 0) {
345 beatspectrum.hasFixedBinCount = true;
346 beatspectrum.binCount = m_rhythmClipFrames / 2;
348 beatspectrum.hasFixedBinCount = false;
350 beatspectrum.hasKnownExtents = false;
351 beatspectrum.isQuantized = false;
352 beatspectrum.sampleType = OutputDescriptor::FixedSampleRate;
353 beatspectrum.sampleRate = 1;
355 m_beatSpectraOutput = list.size();
356 list.push_back(beatspectrum);
362 SimilarityPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize)
364 if (channels < getMinChannelCount()) return false;
366 // Using more than getMaxChannelCount is not actually a problem
367 // for us. Using "incorrect" step and block sizes would be fine
368 // for timbral or chroma similarity, but will break rhythmic
369 // similarity, so we'd better enforce these.
371 if (stepSize != getPreferredStepSize()) {
372 std::cerr << "SimilarityPlugin::initialise: supplied step size "
373 << stepSize << " differs from required step size "
374 << getPreferredStepSize() << std::endl;
378 if (blockSize != getPreferredBlockSize()) {
379 std::cerr << "SimilarityPlugin::initialise: supplied block size "
380 << blockSize << " differs from required block size "
381 << getPreferredBlockSize() << std::endl;
385 m_blockSize = blockSize;
386 m_channels = channels;
388 m_lastNonEmptyFrame = std::vector<int>(m_channels);
389 for (int i = 0; i < m_channels; ++i) m_lastNonEmptyFrame[i] = -1;
391 m_emptyFrameCount = std::vector<int>(m_channels);
392 for (int i = 0; i < m_channels; ++i) m_emptyFrameCount[i] = 0;
396 int decimationFactor = getDecimationFactor();
397 if (decimationFactor > 1) {
398 m_decimator = new Decimator(m_blockSize, decimationFactor);
401 if (m_type == TypeMFCC) {
403 m_featureColumnSize = 20;
405 MFCCConfig config(m_processRate);
406 config.fftsize = 2048;
407 config.nceps = m_featureColumnSize - 1;
408 config.want_c0 = true;
410 m_mfcc = new MFCC(config);
411 m_fftSize = m_mfcc->getfftlength();
412 m_rhythmClipFrameSize = m_fftSize / 4;
414 // std::cerr << "MFCC FS = " << config.FS << ", FFT size = " << m_fftSize<< std::endl;
416 } else if (m_type == TypeChroma) {
418 m_featureColumnSize = 12;
420 // For simplicity, aim to have the chroma fft size equal to
421 // 2048, the same as the mfcc fft size (so the input block
422 // size does not depend on the feature type and we can use the
423 // same processing parameters for rhythm etc). This is also
424 // why getPreferredBlockSize can confidently return 2048 * the
425 // decimation factor.
427 // The fft size for a chromagram is the filterbank Q value
428 // times the sample rate, divided by the minimum frequency,
429 // rounded up to the nearest power of two.
431 double q = 1.0 / (pow(2.0, (1.0 / 12.0)) - 1.0);
432 double fmin = (q * m_processRate) / 2048.0;
434 // Round fmin up to the nearest MIDI pitch multiple of 12.
435 // So long as fmin is greater than 12 to start with, this
436 // should not change the resulting fft size.
438 int pmin = Pitch::getPitchForFrequency(float(fmin));
439 pmin = ((pmin / 12) + 1) * 12;
440 fmin = Pitch::getFrequencyForPitch(pmin);
442 float fmax = Pitch::getFrequencyForPitch(pmin + 36);
445 config.FS = m_processRate;
449 config.CQThresh = 0.0054;
450 // We don't normalise the chromagram's columns individually;
451 // we normalise the mean at the end instead
452 config.normalise = MathUtilities::NormaliseNone;
453 m_chromagram = new Chromagram(config);
454 m_fftSize = m_chromagram->getFrameSize();
456 if (m_fftSize != 2048) {
457 std::cerr << "WARNING: SimilarityPlugin::initialise: Internal processing FFT size " << m_fftSize << " != expected size 2048 in chroma mode" << std::endl;
460 // std::cerr << "fftsize = " << m_fftSize << std::endl;
462 m_rhythmClipFrameSize = m_fftSize / 4;
464 // std::cerr << "m_rhythmClipFrameSize = " << m_rhythmClipFrameSize << std::endl;
465 // std::cerr << "min = "<< config.min << ", max = " << config.max << std::endl;
469 std::cerr << "SimilarityPlugin::initialise: internal error: unknown type " << m_type << std::endl;
475 int(ceil((m_rhythmClipDuration * m_processRate)
476 / m_rhythmClipFrameSize));
477 // std::cerr << "SimilarityPlugin::initialise: rhythm clip requires "
478 // << m_rhythmClipFrames << " frames of size "
479 // << m_rhythmClipFrameSize << " at process rate "
480 // << m_processRate << " ( = "
481 // << (float(m_rhythmClipFrames * m_rhythmClipFrameSize) / m_processRate) << " sec )"
484 MFCCConfig config(m_processRate);
485 config.fftsize = m_rhythmClipFrameSize;
486 config.nceps = m_rhythmColumnSize - 1;
487 config.want_c0 = true;
489 config.window = RectangularWindow; // because no overlap
490 m_rhythmfcc = new MFCC(config);
493 for (int i = 0; i < m_channels; ++i) {
495 m_values.push_back(FeatureMatrix());
498 m_rhythmValues.push_back(FeatureColumnQueue());
508 SimilarityPlugin::reset()
510 for (int i = 0; i < int(m_values.size()); ++i) {
514 for (int i = 0; i < int(m_rhythmValues.size()); ++i) {
515 m_rhythmValues[i].clear();
518 for (int i = 0; i < int(m_lastNonEmptyFrame.size()); ++i) {
519 m_lastNonEmptyFrame[i] = -1;
522 for (int i = 0; i < int(m_emptyFrameCount.size()); ++i) {
523 m_emptyFrameCount[i] = 0;
529 SimilarityPlugin::FeatureSet
530 SimilarityPlugin::process(const float *const *inputBuffers, Vamp::RealTime /* timestamp */)
536 double *dblbuf = new double[m_blockSize];
537 double *decbuf = dblbuf;
538 if (m_decimator) decbuf = new double[m_fftSize];
540 double *raw = new double[std::max(m_featureColumnSize,
541 m_rhythmColumnSize)];
543 float threshold = 1e-10;
545 bool someRhythmFrameNeeded = false;
547 for (int c = 0; c < m_channels; ++c) {
551 for (int i = 0; i < m_blockSize; ++i) {
552 float val = inputBuffers[c][i];
553 if (fabs(val) > threshold) empty = false;
558 if (needRhythm() && ((m_frameNo % 2) == 0)) {
559 for (int i = 0; i < m_fftSize / m_rhythmClipFrameSize; ++i) {
560 if (int(m_rhythmValues[c].size()) < m_rhythmClipFrames) {
561 FeatureColumn mf(m_rhythmColumnSize);
562 for (int i = 0; i < m_rhythmColumnSize; ++i) {
565 m_rhythmValues[c].push_back(mf);
569 m_emptyFrameCount[c]++;
573 m_lastNonEmptyFrame[c] = m_frameNo;
576 m_decimator->process(dblbuf, decbuf);
581 FeatureColumn mf(m_featureColumnSize);
583 if (m_type == TypeMFCC) {
584 m_mfcc->process(decbuf, raw);
585 for (int i = 0; i < m_featureColumnSize; ++i) {
588 } else if (m_type == TypeChroma) {
589 double *chroma = m_chromagram->process(decbuf);
590 for (int i = 0; i < m_featureColumnSize; ++i) {
595 m_values[c].push_back(mf);
598 // std::cerr << "needRhythm = " << needRhythm() << ", frame = " << m_frameNo << std::endl;
600 if (needRhythm() && ((m_frameNo % 2) == 0)) {
602 // The incoming frames are overlapping; we only use every
603 // other one, because we don't want the overlap (it would
604 // screw up the rhythm)
608 while (frameOffset + m_rhythmClipFrameSize <= m_fftSize) {
610 bool needRhythmFrame = true;
612 if (int(m_rhythmValues[c].size()) >= m_rhythmClipFrames) {
614 needRhythmFrame = false;
616 // assumes hopsize = framesize/2
617 float current = m_frameNo * (m_fftSize/2) + frameOffset;
618 current = current / m_processRate;
619 if (current - m_rhythmClipDuration < m_rhythmClipOrigin) {
620 needRhythmFrame = true;
621 m_rhythmValues[c].pop_front();
624 // if (needRhythmFrame) {
625 // std::cerr << "at current = " <<current << " (frame = " << m_frameNo << "), have " << m_rhythmValues[c].size() << ", need rhythm = " << needRhythmFrame << std::endl;
630 if (needRhythmFrame) {
632 someRhythmFrameNeeded = true;
634 m_rhythmfcc->process(decbuf + frameOffset, raw);
636 FeatureColumn mf(m_rhythmColumnSize);
637 for (int i = 0; i < m_rhythmColumnSize; ++i) {
641 m_rhythmValues[c].push_back(mf);
644 frameOffset += m_rhythmClipFrameSize;
649 if (!needTimbre() && !someRhythmFrameNeeded && ((m_frameNo % 2) == 0)) {
650 // std::cerr << "done!" << std::endl;
654 if (m_decimator) delete[] decbuf;
663 SimilarityPlugin::FeatureMatrix
664 SimilarityPlugin::calculateTimbral(FeatureSet &returnFeatures)
666 FeatureMatrix m(m_channels); // means
667 FeatureMatrix v(m_channels); // variances
669 for (int i = 0; i < m_channels; ++i) {
671 FeatureColumn mean(m_featureColumnSize), variance(m_featureColumnSize);
673 for (int j = 0; j < m_featureColumnSize; ++j) {
679 // We want to take values up to, but not including, the
680 // last non-empty frame (which may be partial)
682 int sz = m_lastNonEmptyFrame[i] - m_emptyFrameCount[i];
684 if (sz >= int(m_values[i].size())) {
685 sz = int(m_values[i].size())-1;
689 for (int k = 0; k < sz; ++k) {
690 double val = m_values[i][k][j];
691 if (ISNAN(val) || ISINF(val)) continue;
695 if (count > 0) mean[j] /= count;
698 for (int k = 0; k < sz; ++k) {
699 double val = ((m_values[i][k][j] - mean[j]) *
700 (m_values[i][k][j] - mean[j]));
701 if (ISNAN(val) || ISINF(val)) continue;
705 if (count > 0) variance[j] /= count;
712 FeatureMatrix distances(m_channels);
714 if (m_type == TypeMFCC) {
716 // "Despite the fact that MFCCs extracted from music are
717 // clearly not Gaussian, [14] showed, somewhat surprisingly,
718 // that a similarity function comparing single Gaussians
719 // modelling MFCCs for each track can perform as well as
720 // mixture models. A great advantage of using single
721 // Gaussians is that a simple closed form exists for the KL
722 // divergence." -- Mark Levy, "Lightweight measures for
723 // timbral similarity of musical audio"
724 // (http://www.elec.qmul.ac.uk/easaier/papers/mlevytimbralsimilarity.pdf)
728 for (int i = 0; i < m_channels; ++i) {
729 for (int j = 0; j < m_channels; ++j) {
730 double d = kld.distanceGaussian(m[i], v[i], m[j], v[j]);
731 distances[i].push_back(d);
737 // We use the KL divergence for distributions of discrete
738 // variables, as chroma are histograms already. Or at least,
739 // they will be when we've normalised them like this:
740 for (int i = 0; i < m_channels; ++i) {
741 MathUtilities::normalise(m[i], MathUtilities::NormaliseUnitSum);
746 for (int i = 0; i < m_channels; ++i) {
747 for (int j = 0; j < m_channels; ++j) {
748 double d = kld.distanceDistribution(m[i], m[j], true);
749 distances[i].push_back(d);
755 feature.hasTimestamp = true;
757 char labelBuffer[100];
759 for (int i = 0; i < m_channels; ++i) {
761 feature.timestamp = Vamp::RealTime(i, 0);
763 sprintf(labelBuffer, "Means for channel %d", i+1);
764 feature.label = labelBuffer;
766 feature.values.clear();
767 for (int k = 0; k < m_featureColumnSize; ++k) {
768 feature.values.push_back(m[i][k]);
771 returnFeatures[m_meansOutput].push_back(feature);
773 sprintf(labelBuffer, "Variances for channel %d", i+1);
774 feature.label = labelBuffer;
776 feature.values.clear();
777 for (int k = 0; k < m_featureColumnSize; ++k) {
778 feature.values.push_back(v[i][k]);
781 returnFeatures[m_variancesOutput].push_back(feature);
787 SimilarityPlugin::FeatureMatrix
788 SimilarityPlugin::calculateRhythmic(FeatureSet &returnFeatures)
790 if (!needRhythm()) return FeatureMatrix();
792 // std::cerr << "SimilarityPlugin::initialise: rhythm clip for channel 0 contains "
793 // << m_rhythmValues[0].size() << " frames of size "
794 // << m_rhythmClipFrameSize << " at process rate "
795 // << m_processRate << " ( = "
796 // << (float(m_rhythmValues[0].size() * m_rhythmClipFrameSize) / m_processRate) << " sec )"
802 // Our rhythm feature matrix is a deque of vectors for practical
803 // reasons, but BeatSpectrum::process wants a vector of vectors
804 // (which is what FeatureMatrix happens to be).
806 FeatureMatrixSet bsinput(m_channels);
807 for (int i = 0; i < m_channels; ++i) {
808 for (int j = 0; j < int(m_rhythmValues[i].size()); ++j) {
809 bsinput[i].push_back(m_rhythmValues[i][j]);
813 FeatureMatrix bs(m_channels);
814 for (int i = 0; i < m_channels; ++i) {
815 bs[i] = bscalc.process(bsinput[i]);
818 FeatureMatrix distances(m_channels);
819 for (int i = 0; i < m_channels; ++i) {
820 for (int j = 0; j < m_channels; ++j) {
821 double d = cd.distance(bs[i], bs[j]);
822 distances[i].push_back(d);
827 feature.hasTimestamp = true;
829 char labelBuffer[100];
831 for (int i = 0; i < m_channels; ++i) {
833 feature.timestamp = Vamp::RealTime(i, 0);
835 sprintf(labelBuffer, "Beat spectrum for channel %d", i+1);
836 feature.label = labelBuffer;
838 feature.values.clear();
839 for (int j = 0; j < int(bs[i].size()); ++j) {
840 feature.values.push_back(bs[i][j]);
843 returnFeatures[m_beatSpectraOutput].push_back(feature);
850 SimilarityPlugin::getDistance(const FeatureMatrix &timbral,
851 const FeatureMatrix &rhythmic,
854 double distance = 1.0;
855 if (needTimbre()) distance *= timbral[i][j];
856 if (needRhythm()) distance *= rhythmic[i][j];
860 SimilarityPlugin::FeatureSet
861 SimilarityPlugin::getRemainingFeatures()
863 FeatureSet returnFeatures;
865 // We want to return a matrix of the distances between channels,
866 // but Vamp doesn't have a matrix return type so we will actually
867 // return a series of vectors
869 FeatureMatrix timbralDistances, rhythmicDistances;
872 timbralDistances = calculateTimbral(returnFeatures);
876 rhythmicDistances = calculateRhythmic(returnFeatures);
879 // We give all features a timestamp, otherwise hosts will tend to
880 // stamp them at the end of the file, which is annoying
883 feature.hasTimestamp = true;
885 Feature distanceVectorFeature;
886 distanceVectorFeature.label = "Distance from first channel";
887 distanceVectorFeature.hasTimestamp = true;
888 distanceVectorFeature.timestamp = Vamp::RealTime::zeroTime;
890 std::map<double, int> sorted;
892 char labelBuffer[100];
894 for (int i = 0; i < m_channels; ++i) {
896 feature.timestamp = Vamp::RealTime(i, 0);
898 feature.values.clear();
899 for (int j = 0; j < m_channels; ++j) {
900 double dist = getDistance(timbralDistances, rhythmicDistances, i, j);
901 feature.values.push_back(dist);
904 sprintf(labelBuffer, "Distances from channel %d", i+1);
905 feature.label = labelBuffer;
907 returnFeatures[m_distanceMatrixOutput].push_back(feature);
910 getDistance(timbralDistances, rhythmicDistances, 0, i);
912 distanceVectorFeature.values.push_back(fromFirst);
913 sorted[fromFirst] = i;
916 returnFeatures[m_distanceVectorOutput].push_back(distanceVectorFeature);
918 feature.label = "Order of channels by similarity to first channel";
919 feature.values.clear();
920 feature.timestamp = Vamp::RealTime(0, 0);
922 for (std::map<double, int>::iterator i = sorted.begin();
923 i != sorted.end(); ++i) {
924 feature.values.push_back(i->second + 1);
927 returnFeatures[m_sortedVectorOutput].push_back(feature);
929 feature.label = "Ordered distances of channels from first channel";
930 feature.values.clear();
931 feature.timestamp = Vamp::RealTime(1, 0);
933 for (std::map<double, int>::iterator i = sorted.begin();
934 i != sorted.end(); ++i) {
935 feature.values.push_back(i->first);
938 returnFeatures[m_sortedVectorOutput].push_back(feature);
940 return returnFeatures;