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@46
|
18 #include "maths/KLDivergence.cpp"
|
c@41
|
19
|
c@41
|
20 using std::string;
|
c@41
|
21 using std::vector;
|
c@41
|
22 using std::cerr;
|
c@41
|
23 using std::endl;
|
c@41
|
24 using std::ostringstream;
|
c@41
|
25
|
c@41
|
26 SimilarityPlugin::SimilarityPlugin(float inputSampleRate) :
|
c@41
|
27 Plugin(inputSampleRate),
|
c@42
|
28 m_type(TypeMFCC),
|
c@41
|
29 m_mfcc(0),
|
c@42
|
30 m_chromagram(0),
|
c@41
|
31 m_decimator(0),
|
c@42
|
32 m_featureColumnSize(20),
|
c@41
|
33 m_blockSize(0),
|
c@41
|
34 m_channels(0)
|
c@41
|
35 {
|
c@41
|
36
|
c@41
|
37 }
|
c@41
|
38
|
c@41
|
39 SimilarityPlugin::~SimilarityPlugin()
|
c@41
|
40 {
|
c@41
|
41 delete m_mfcc;
|
c@42
|
42 delete m_chromagram;
|
c@41
|
43 delete m_decimator;
|
c@41
|
44 }
|
c@41
|
45
|
c@41
|
46 string
|
c@41
|
47 SimilarityPlugin::getIdentifier() const
|
c@41
|
48 {
|
c@41
|
49 return "qm-similarity";
|
c@41
|
50 }
|
c@41
|
51
|
c@41
|
52 string
|
c@41
|
53 SimilarityPlugin::getName() const
|
c@41
|
54 {
|
c@41
|
55 return "Similarity";
|
c@41
|
56 }
|
c@41
|
57
|
c@41
|
58 string
|
c@41
|
59 SimilarityPlugin::getDescription() const
|
c@41
|
60 {
|
c@42
|
61 return "Return a distance matrix for similarity between the input audio channels";
|
c@41
|
62 }
|
c@41
|
63
|
c@41
|
64 string
|
c@41
|
65 SimilarityPlugin::getMaker() const
|
c@41
|
66 {
|
c@45
|
67 return "Mark Levy and Chris Cannam, Queen Mary, University of London";
|
c@41
|
68 }
|
c@41
|
69
|
c@41
|
70 int
|
c@41
|
71 SimilarityPlugin::getPluginVersion() const
|
c@41
|
72 {
|
c@41
|
73 return 1;
|
c@41
|
74 }
|
c@41
|
75
|
c@41
|
76 string
|
c@41
|
77 SimilarityPlugin::getCopyright() const
|
c@41
|
78 {
|
c@41
|
79 return "Copyright (c) 2008 - All Rights Reserved";
|
c@41
|
80 }
|
c@41
|
81
|
c@41
|
82 size_t
|
c@41
|
83 SimilarityPlugin::getMinChannelCount() const
|
c@41
|
84 {
|
c@43
|
85 return 1;
|
c@41
|
86 }
|
c@41
|
87
|
c@41
|
88 size_t
|
c@41
|
89 SimilarityPlugin::getMaxChannelCount() const
|
c@41
|
90 {
|
c@41
|
91 return 1024;
|
c@41
|
92 }
|
c@41
|
93
|
c@41
|
94 bool
|
c@41
|
95 SimilarityPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
c@41
|
96 {
|
c@41
|
97 if (channels < getMinChannelCount() ||
|
c@41
|
98 channels > getMaxChannelCount()) return false;
|
c@41
|
99
|
c@41
|
100 if (stepSize != getPreferredStepSize()) {
|
c@43
|
101 //!!! actually this perhaps shouldn't be an error... similarly
|
c@43
|
102 //using more than getMaxChannelCount channels
|
c@41
|
103 std::cerr << "SimilarityPlugin::initialise: supplied step size "
|
c@41
|
104 << stepSize << " differs from required step size "
|
c@41
|
105 << getPreferredStepSize() << std::endl;
|
c@41
|
106 return false;
|
c@41
|
107 }
|
c@41
|
108
|
c@41
|
109 if (blockSize != getPreferredBlockSize()) {
|
c@41
|
110 std::cerr << "SimilarityPlugin::initialise: supplied block size "
|
c@41
|
111 << blockSize << " differs from required block size "
|
c@41
|
112 << getPreferredBlockSize() << std::endl;
|
c@41
|
113 return false;
|
c@41
|
114 }
|
c@41
|
115
|
c@41
|
116 m_blockSize = blockSize;
|
c@41
|
117 m_channels = channels;
|
c@41
|
118
|
c@44
|
119 m_lastNonEmptyFrame = std::vector<int>(m_channels);
|
c@44
|
120 for (int i = 0; i < m_channels; ++i) m_lastNonEmptyFrame[i] = -1;
|
c@44
|
121 m_frameNo = 0;
|
c@44
|
122
|
c@41
|
123 int decimationFactor = getDecimationFactor();
|
c@41
|
124 if (decimationFactor > 1) {
|
c@42
|
125 m_decimator = new Decimator(m_blockSize, decimationFactor);
|
c@41
|
126 }
|
c@41
|
127
|
c@42
|
128 if (m_type == TypeMFCC) {
|
c@42
|
129
|
c@42
|
130 m_featureColumnSize = 20;
|
c@42
|
131
|
c@45
|
132 MFCCConfig config(lrintf(m_inputSampleRate) / decimationFactor);
|
c@42
|
133 config.fftsize = 2048;
|
c@42
|
134 config.nceps = m_featureColumnSize - 1;
|
c@42
|
135 config.want_c0 = true;
|
c@45
|
136 config.logpower = 1;
|
c@42
|
137 m_mfcc = new MFCC(config);
|
c@42
|
138 m_fftSize = m_mfcc->getfftlength();
|
c@42
|
139
|
c@43
|
140 std::cerr << "MFCC FS = " << config.FS << ", FFT size = " << m_fftSize<< std::endl;
|
c@43
|
141
|
c@42
|
142 } else if (m_type == TypeChroma) {
|
c@42
|
143
|
c@42
|
144 m_featureColumnSize = 12;
|
c@42
|
145
|
c@42
|
146 ChromaConfig config;
|
c@42
|
147 config.FS = lrintf(m_inputSampleRate) / decimationFactor;
|
c@42
|
148 config.min = Pitch::getFrequencyForPitch(24, 0, 440);
|
c@42
|
149 config.max = Pitch::getFrequencyForPitch(96, 0, 440);
|
c@42
|
150 config.BPO = 12;
|
c@42
|
151 config.CQThresh = 0.0054;
|
c@42
|
152 config.isNormalised = true;
|
c@42
|
153 m_chromagram = new Chromagram(config);
|
c@42
|
154 m_fftSize = m_chromagram->getFrameSize();
|
c@42
|
155
|
c@42
|
156 std::cerr << "min = "<< config.min << ", max = " << config.max << std::endl;
|
c@42
|
157
|
c@42
|
158 } else {
|
c@42
|
159
|
c@42
|
160 std::cerr << "SimilarityPlugin::initialise: internal error: unknown type " << m_type << std::endl;
|
c@42
|
161 return false;
|
c@42
|
162 }
|
c@41
|
163
|
c@41
|
164 for (int i = 0; i < m_channels; ++i) {
|
c@42
|
165 m_values.push_back(FeatureMatrix());
|
c@41
|
166 }
|
c@41
|
167
|
c@41
|
168 return true;
|
c@41
|
169 }
|
c@41
|
170
|
c@41
|
171 void
|
c@41
|
172 SimilarityPlugin::reset()
|
c@41
|
173 {
|
c@41
|
174 //!!!
|
c@41
|
175 }
|
c@41
|
176
|
c@41
|
177 int
|
c@41
|
178 SimilarityPlugin::getDecimationFactor() const
|
c@41
|
179 {
|
c@41
|
180 int rate = lrintf(m_inputSampleRate);
|
c@41
|
181 int internalRate = 22050;
|
c@41
|
182 int decimationFactor = rate / internalRate;
|
c@41
|
183 if (decimationFactor < 1) decimationFactor = 1;
|
c@41
|
184
|
c@41
|
185 // must be a power of two
|
c@41
|
186 while (decimationFactor & (decimationFactor - 1)) ++decimationFactor;
|
c@41
|
187
|
c@41
|
188 return decimationFactor;
|
c@41
|
189 }
|
c@41
|
190
|
c@41
|
191 size_t
|
c@41
|
192 SimilarityPlugin::getPreferredStepSize() const
|
c@41
|
193 {
|
c@42
|
194 if (m_blockSize == 0) calculateBlockSize();
|
c@45
|
195 return m_blockSize/2;
|
c@41
|
196 }
|
c@41
|
197
|
c@41
|
198 size_t
|
c@41
|
199 SimilarityPlugin::getPreferredBlockSize() const
|
c@41
|
200 {
|
c@42
|
201 if (m_blockSize == 0) calculateBlockSize();
|
c@42
|
202 return m_blockSize;
|
c@42
|
203 }
|
c@42
|
204
|
c@42
|
205 void
|
c@42
|
206 SimilarityPlugin::calculateBlockSize() const
|
c@42
|
207 {
|
c@42
|
208 if (m_blockSize != 0) return;
|
c@42
|
209 int decimationFactor = getDecimationFactor();
|
c@42
|
210 if (m_type == TypeChroma) {
|
c@42
|
211 ChromaConfig config;
|
c@42
|
212 config.FS = lrintf(m_inputSampleRate) / decimationFactor;
|
c@42
|
213 config.min = Pitch::getFrequencyForPitch(24, 0, 440);
|
c@42
|
214 config.max = Pitch::getFrequencyForPitch(96, 0, 440);
|
c@42
|
215 config.BPO = 12;
|
c@42
|
216 config.CQThresh = 0.0054;
|
c@42
|
217 config.isNormalised = false;
|
c@42
|
218 Chromagram *c = new Chromagram(config);
|
c@42
|
219 size_t sz = c->getFrameSize();
|
c@42
|
220 delete c;
|
c@42
|
221 m_blockSize = sz * decimationFactor;
|
c@42
|
222 } else {
|
c@42
|
223 m_blockSize = 2048 * decimationFactor;
|
c@42
|
224 }
|
c@41
|
225 }
|
c@41
|
226
|
c@41
|
227 SimilarityPlugin::ParameterList SimilarityPlugin::getParameterDescriptors() const
|
c@41
|
228 {
|
c@41
|
229 ParameterList list;
|
c@42
|
230
|
c@42
|
231 ParameterDescriptor desc;
|
c@42
|
232 desc.identifier = "featureType";
|
c@42
|
233 desc.name = "Feature Type";
|
c@45
|
234 desc.description = "Audio feature used for similarity measure. Timbral: use the first 20 MFCCs (19 plus C0). Chromatic: use 12 bin-per-octave chroma.";
|
c@42
|
235 desc.unit = "";
|
c@42
|
236 desc.minValue = 0;
|
c@42
|
237 desc.maxValue = 1;
|
c@42
|
238 desc.defaultValue = 0;
|
c@42
|
239 desc.isQuantized = true;
|
c@42
|
240 desc.quantizeStep = 1;
|
c@42
|
241 desc.valueNames.push_back("Timbral (MFCC)");
|
c@42
|
242 desc.valueNames.push_back("Chromatic (Chroma)");
|
c@42
|
243 list.push_back(desc);
|
c@42
|
244
|
c@41
|
245 return list;
|
c@41
|
246 }
|
c@41
|
247
|
c@41
|
248 float
|
c@41
|
249 SimilarityPlugin::getParameter(std::string param) const
|
c@41
|
250 {
|
c@42
|
251 if (param == "featureType") {
|
c@42
|
252 if (m_type == TypeMFCC) return 0;
|
c@42
|
253 else if (m_type == TypeChroma) return 1;
|
c@42
|
254 else return 0;
|
c@42
|
255 }
|
c@42
|
256
|
c@41
|
257 std::cerr << "WARNING: SimilarityPlugin::getParameter: unknown parameter \""
|
c@41
|
258 << param << "\"" << std::endl;
|
c@41
|
259 return 0.0;
|
c@41
|
260 }
|
c@41
|
261
|
c@41
|
262 void
|
c@41
|
263 SimilarityPlugin::setParameter(std::string param, float value)
|
c@41
|
264 {
|
c@42
|
265 if (param == "featureType") {
|
c@42
|
266 int v = int(value + 0.1);
|
c@42
|
267 Type prevType = m_type;
|
c@42
|
268 if (v == 0) m_type = TypeMFCC;
|
c@42
|
269 else if (v == 1) m_type = TypeChroma;
|
c@42
|
270 if (m_type != prevType) m_blockSize = 0;
|
c@42
|
271 return;
|
c@42
|
272 }
|
c@42
|
273
|
c@41
|
274 std::cerr << "WARNING: SimilarityPlugin::setParameter: unknown parameter \""
|
c@41
|
275 << param << "\"" << std::endl;
|
c@41
|
276 }
|
c@41
|
277
|
c@41
|
278 SimilarityPlugin::OutputList
|
c@41
|
279 SimilarityPlugin::getOutputDescriptors() const
|
c@41
|
280 {
|
c@41
|
281 OutputList list;
|
c@41
|
282
|
c@41
|
283 OutputDescriptor similarity;
|
c@43
|
284 similarity.identifier = "distancematrix";
|
c@43
|
285 similarity.name = "Distance Matrix";
|
c@43
|
286 similarity.description = "Distance matrix for similarity metric. Smaller = more similar. Should be symmetrical.";
|
c@41
|
287 similarity.unit = "";
|
c@41
|
288 similarity.hasFixedBinCount = true;
|
c@41
|
289 similarity.binCount = m_channels;
|
c@41
|
290 similarity.hasKnownExtents = false;
|
c@41
|
291 similarity.isQuantized = false;
|
c@41
|
292 similarity.sampleType = OutputDescriptor::FixedSampleRate;
|
c@41
|
293 similarity.sampleRate = 1;
|
c@41
|
294
|
c@43
|
295 m_distanceMatrixOutput = list.size();
|
c@41
|
296 list.push_back(similarity);
|
c@41
|
297
|
c@43
|
298 OutputDescriptor simvec;
|
c@43
|
299 simvec.identifier = "distancevector";
|
c@43
|
300 simvec.name = "Distance from First Channel";
|
c@43
|
301 simvec.description = "Distance vector for similarity of each channel to the first channel. Smaller = more similar.";
|
c@43
|
302 simvec.unit = "";
|
c@43
|
303 simvec.hasFixedBinCount = true;
|
c@43
|
304 simvec.binCount = m_channels;
|
c@43
|
305 simvec.hasKnownExtents = false;
|
c@43
|
306 simvec.isQuantized = false;
|
c@43
|
307 simvec.sampleType = OutputDescriptor::FixedSampleRate;
|
c@43
|
308 simvec.sampleRate = 1;
|
c@43
|
309
|
c@43
|
310 m_distanceVectorOutput = list.size();
|
c@43
|
311 list.push_back(simvec);
|
c@43
|
312
|
c@44
|
313 OutputDescriptor sortvec;
|
c@44
|
314 sortvec.identifier = "sorteddistancevector";
|
c@44
|
315 sortvec.name = "Ordered Distances from First Channel";
|
c@44
|
316 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
|
317 sortvec.unit = "";
|
c@44
|
318 sortvec.hasFixedBinCount = true;
|
c@44
|
319 sortvec.binCount = m_channels;
|
c@44
|
320 sortvec.hasKnownExtents = false;
|
c@44
|
321 sortvec.isQuantized = false;
|
c@44
|
322 sortvec.sampleType = OutputDescriptor::FixedSampleRate;
|
c@44
|
323 sortvec.sampleRate = 1;
|
c@44
|
324
|
c@44
|
325 m_sortedVectorOutput = list.size();
|
c@44
|
326 list.push_back(sortvec);
|
c@44
|
327
|
c@41
|
328 OutputDescriptor means;
|
c@41
|
329 means.identifier = "means";
|
c@42
|
330 means.name = "Feature Means";
|
c@43
|
331 means.description = "Means of the feature bins. Feature time (sec) corresponds to input channel. Number of bins depends on selected feature type.";
|
c@41
|
332 means.unit = "";
|
c@41
|
333 means.hasFixedBinCount = true;
|
c@43
|
334 means.binCount = m_featureColumnSize;
|
c@41
|
335 means.hasKnownExtents = false;
|
c@41
|
336 means.isQuantized = false;
|
c@43
|
337 means.sampleType = OutputDescriptor::FixedSampleRate;
|
c@43
|
338 means.sampleRate = 1;
|
c@41
|
339
|
c@43
|
340 m_meansOutput = list.size();
|
c@41
|
341 list.push_back(means);
|
c@41
|
342
|
c@41
|
343 OutputDescriptor variances;
|
c@41
|
344 variances.identifier = "variances";
|
c@42
|
345 variances.name = "Feature Variances";
|
c@43
|
346 variances.description = "Variances of the feature bins. Feature time (sec) corresponds to input channel. Number of bins depends on selected feature type.";
|
c@41
|
347 variances.unit = "";
|
c@41
|
348 variances.hasFixedBinCount = true;
|
c@43
|
349 variances.binCount = m_featureColumnSize;
|
c@41
|
350 variances.hasKnownExtents = false;
|
c@41
|
351 variances.isQuantized = false;
|
c@43
|
352 variances.sampleType = OutputDescriptor::FixedSampleRate;
|
c@43
|
353 variances.sampleRate = 1;
|
c@41
|
354
|
c@43
|
355 m_variancesOutput = list.size();
|
c@41
|
356 list.push_back(variances);
|
c@41
|
357
|
c@41
|
358 return list;
|
c@41
|
359 }
|
c@41
|
360
|
c@41
|
361 SimilarityPlugin::FeatureSet
|
c@41
|
362 SimilarityPlugin::process(const float *const *inputBuffers, Vamp::RealTime /* timestamp */)
|
c@41
|
363 {
|
c@41
|
364 double *dblbuf = new double[m_blockSize];
|
c@41
|
365 double *decbuf = dblbuf;
|
c@42
|
366 if (m_decimator) decbuf = new double[m_fftSize];
|
c@42
|
367
|
c@42
|
368 double *raw = 0;
|
c@42
|
369 bool ownRaw = false;
|
c@42
|
370
|
c@42
|
371 if (m_type == TypeMFCC) {
|
c@42
|
372 raw = new double[m_featureColumnSize];
|
c@42
|
373 ownRaw = true;
|
c@42
|
374 }
|
c@41
|
375
|
c@43
|
376 float threshold = 1e-10;
|
c@43
|
377
|
c@41
|
378 for (size_t c = 0; c < m_channels; ++c) {
|
c@41
|
379
|
c@43
|
380 bool empty = true;
|
c@43
|
381
|
c@41
|
382 for (int i = 0; i < m_blockSize; ++i) {
|
c@43
|
383 float val = inputBuffers[c][i];
|
c@43
|
384 if (fabs(val) > threshold) empty = false;
|
c@43
|
385 dblbuf[i] = val;
|
c@41
|
386 }
|
c@41
|
387
|
c@43
|
388 if (empty) continue;
|
c@44
|
389 m_lastNonEmptyFrame[c] = m_frameNo;
|
c@43
|
390
|
c@41
|
391 if (m_decimator) {
|
c@41
|
392 m_decimator->process(dblbuf, decbuf);
|
c@41
|
393 }
|
c@42
|
394
|
c@42
|
395 if (m_type == TypeMFCC) {
|
c@45
|
396 m_mfcc->process(decbuf, raw);
|
c@42
|
397 } else if (m_type == TypeChroma) {
|
c@42
|
398 raw = m_chromagram->process(decbuf);
|
c@42
|
399 }
|
c@41
|
400
|
c@42
|
401 FeatureColumn mf(m_featureColumnSize);
|
c@44
|
402 // std::cout << m_frameNo << ":" << c << ": ";
|
c@44
|
403 for (int i = 0; i < m_featureColumnSize; ++i) {
|
c@44
|
404 mf[i] = raw[i];
|
c@44
|
405 // std::cout << raw[i] << " ";
|
c@44
|
406 }
|
c@44
|
407 // std::cout << std::endl;
|
c@41
|
408
|
c@42
|
409 m_values[c].push_back(mf);
|
c@41
|
410 }
|
c@41
|
411
|
c@41
|
412 if (m_decimator) delete[] decbuf;
|
c@41
|
413 delete[] dblbuf;
|
c@42
|
414
|
c@42
|
415 if (ownRaw) delete[] raw;
|
c@41
|
416
|
c@44
|
417 ++m_frameNo;
|
c@44
|
418
|
c@41
|
419 return FeatureSet();
|
c@41
|
420 }
|
c@41
|
421
|
c@41
|
422 SimilarityPlugin::FeatureSet
|
c@41
|
423 SimilarityPlugin::getRemainingFeatures()
|
c@41
|
424 {
|
c@42
|
425 std::vector<FeatureColumn> m(m_channels);
|
c@42
|
426 std::vector<FeatureColumn> v(m_channels);
|
c@41
|
427
|
c@41
|
428 for (int i = 0; i < m_channels; ++i) {
|
c@41
|
429
|
c@42
|
430 FeatureColumn mean(m_featureColumnSize), variance(m_featureColumnSize);
|
c@41
|
431
|
c@42
|
432 for (int j = 0; j < m_featureColumnSize; ++j) {
|
c@41
|
433
|
c@43
|
434 mean[j] = 0.0;
|
c@43
|
435 variance[j] = 0.0;
|
c@41
|
436 int count;
|
c@41
|
437
|
c@44
|
438 // We want to take values up to, but not including, the
|
c@44
|
439 // last non-empty frame (which may be partial)
|
c@43
|
440
|
c@44
|
441 int sz = m_lastNonEmptyFrame[i];
|
c@44
|
442 if (sz < 0) sz = 0;
|
c@43
|
443
|
c@43
|
444 // std::cout << "\nBin " << j << ":" << std::endl;
|
c@42
|
445
|
c@41
|
446 count = 0;
|
c@43
|
447 for (int k = 0; k < sz; ++k) {
|
c@42
|
448 double val = m_values[i][k][j];
|
c@42
|
449 // std::cout << val << " ";
|
c@41
|
450 if (isnan(val) || isinf(val)) continue;
|
c@41
|
451 mean[j] += val;
|
c@41
|
452 ++count;
|
c@41
|
453 }
|
c@41
|
454 if (count > 0) mean[j] /= count;
|
c@43
|
455 // std::cout << "\n" << count << " non-NaN non-inf values, so mean = " << mean[j] << std::endl;
|
c@41
|
456
|
c@41
|
457 count = 0;
|
c@43
|
458 for (int k = 0; k < sz; ++k) {
|
c@42
|
459 double val = ((m_values[i][k][j] - mean[j]) *
|
c@42
|
460 (m_values[i][k][j] - mean[j]));
|
c@41
|
461 if (isnan(val) || isinf(val)) continue;
|
c@41
|
462 variance[j] += val;
|
c@41
|
463 ++count;
|
c@41
|
464 }
|
c@41
|
465 if (count > 0) variance[j] /= count;
|
c@43
|
466 // std::cout << "... and variance = " << variance[j] << std::endl;
|
c@41
|
467 }
|
c@41
|
468
|
c@41
|
469 m[i] = mean;
|
c@41
|
470 v[i] = variance;
|
c@41
|
471 }
|
c@41
|
472
|
c@42
|
473 // we want to return a matrix of the distances between channels,
|
c@41
|
474 // but Vamp doesn't have a matrix return type so we actually
|
c@41
|
475 // return a series of vectors
|
c@41
|
476
|
c@41
|
477 std::vector<std::vector<double> > distances;
|
c@41
|
478
|
c@42
|
479 // "Despite the fact that MFCCs extracted from music are clearly
|
c@42
|
480 // not Gaussian, [14] showed, somewhat surprisingly, that a
|
c@42
|
481 // similarity function comparing single Gaussians modelling MFCCs
|
c@42
|
482 // for each track can perform as well as mixture models. A great
|
c@42
|
483 // advantage of using single Gaussians is that a simple closed
|
c@42
|
484 // form exists for the KL divergence." -- Mark Levy, "Lightweight
|
c@42
|
485 // measures for timbral similarity of musical audio"
|
c@42
|
486 // (http://www.elec.qmul.ac.uk/easaier/papers/mlevytimbralsimilarity.pdf)
|
c@46
|
487
|
c@46
|
488 KLDivergence kld;
|
c@42
|
489
|
c@41
|
490 for (int i = 0; i < m_channels; ++i) {
|
c@41
|
491 distances.push_back(std::vector<double>());
|
c@41
|
492 for (int j = 0; j < m_channels; ++j) {
|
c@46
|
493 double d = kld.distance(m[i], v[i], m[j], v[j]);
|
c@41
|
494 distances[i].push_back(d);
|
c@41
|
495 }
|
c@41
|
496 }
|
c@41
|
497
|
c@44
|
498 // We give all features a timestamp, otherwise hosts will tend to
|
c@44
|
499 // stamp them at the end of the file, which is annoying
|
c@44
|
500
|
c@41
|
501 FeatureSet returnFeatures;
|
c@41
|
502
|
c@44
|
503 Feature feature;
|
c@44
|
504 feature.hasTimestamp = true;
|
c@44
|
505
|
c@43
|
506 Feature distanceVectorFeature;
|
c@43
|
507 distanceVectorFeature.label = "Distance from first channel";
|
c@44
|
508 distanceVectorFeature.hasTimestamp = true;
|
c@44
|
509 distanceVectorFeature.timestamp = Vamp::RealTime::zeroTime;
|
c@44
|
510
|
c@44
|
511 std::map<double, int> sorted;
|
c@44
|
512
|
c@44
|
513 char labelBuffer[100];
|
c@43
|
514
|
c@41
|
515 for (int i = 0; i < m_channels; ++i) {
|
c@41
|
516
|
c@41
|
517 feature.timestamp = Vamp::RealTime(i, 0);
|
c@41
|
518
|
c@44
|
519 sprintf(labelBuffer, "Means for channel %d", i+1);
|
c@44
|
520 feature.label = labelBuffer;
|
c@44
|
521
|
c@41
|
522 feature.values.clear();
|
c@42
|
523 for (int k = 0; k < m_featureColumnSize; ++k) {
|
c@41
|
524 feature.values.push_back(m[i][k]);
|
c@41
|
525 }
|
c@41
|
526
|
c@43
|
527 returnFeatures[m_meansOutput].push_back(feature);
|
c@41
|
528
|
c@44
|
529 sprintf(labelBuffer, "Variances for channel %d", i+1);
|
c@44
|
530 feature.label = labelBuffer;
|
c@44
|
531
|
c@41
|
532 feature.values.clear();
|
c@42
|
533 for (int k = 0; k < m_featureColumnSize; ++k) {
|
c@41
|
534 feature.values.push_back(v[i][k]);
|
c@41
|
535 }
|
c@41
|
536
|
c@43
|
537 returnFeatures[m_variancesOutput].push_back(feature);
|
c@41
|
538
|
c@41
|
539 feature.values.clear();
|
c@41
|
540 for (int j = 0; j < m_channels; ++j) {
|
c@41
|
541 feature.values.push_back(distances[i][j]);
|
c@41
|
542 }
|
c@43
|
543
|
c@44
|
544 sprintf(labelBuffer, "Distances from channel %d", i+1);
|
c@44
|
545 feature.label = labelBuffer;
|
c@41
|
546
|
c@43
|
547 returnFeatures[m_distanceMatrixOutput].push_back(feature);
|
c@43
|
548
|
c@43
|
549 distanceVectorFeature.values.push_back(distances[0][i]);
|
c@44
|
550
|
c@44
|
551 sorted[distances[0][i]] = i;
|
c@41
|
552 }
|
c@41
|
553
|
c@43
|
554 returnFeatures[m_distanceVectorOutput].push_back(distanceVectorFeature);
|
c@43
|
555
|
c@44
|
556 feature.label = "Order of channels by similarity to first channel";
|
c@44
|
557 feature.values.clear();
|
c@44
|
558 feature.timestamp = Vamp::RealTime(0, 0);
|
c@44
|
559
|
c@44
|
560 for (std::map<double, int>::iterator i = sorted.begin();
|
c@44
|
561 i != sorted.end(); ++i) {
|
c@45
|
562 feature.values.push_back(i->second + 1);
|
c@44
|
563 }
|
c@44
|
564
|
c@44
|
565 returnFeatures[m_sortedVectorOutput].push_back(feature);
|
c@44
|
566
|
c@44
|
567 feature.label = "Ordered distances of channels from first channel";
|
c@44
|
568 feature.values.clear();
|
c@44
|
569 feature.timestamp = Vamp::RealTime(1, 0);
|
c@44
|
570
|
c@44
|
571 for (std::map<double, int>::iterator i = sorted.begin();
|
c@44
|
572 i != sorted.end(); ++i) {
|
c@44
|
573 feature.values.push_back(i->first);
|
c@44
|
574 }
|
c@44
|
575
|
c@44
|
576 returnFeatures[m_sortedVectorOutput].push_back(feature);
|
c@44
|
577
|
c@41
|
578 return returnFeatures;
|
c@41
|
579 }
|