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