comparison MatchVampPlugin.cpp @ 0:640f92242cc1

* initial import
author cannam
date Wed, 24 Oct 2007 12:13:43 +0000
parents
children de792b8c2801
comparison
equal deleted inserted replaced
-1:000000000000 0:640f92242cc1
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Vamp feature extraction plugin using the MATCH audio alignment
5 algorithm.
6
7 Centre for Digital Music, Queen Mary, University of London.
8 This file copyright 2007 Simon Dixon, Chris Cannam and QMUL.
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License as
12 published by the Free Software Foundation; either version 2 of the
13 License, or (at your option) any later version. See the file
14 COPYING included with this distribution for more information.
15 */
16
17 #include "MatchVampPlugin.h"
18
19 #include "Matcher.h"
20 #include "MatchFeeder.h"
21 #include "Path.h"
22
23 #include <vamp/vamp.h>
24 #include <vamp-sdk/PluginAdapter.h>
25 #include <vamp-sdk/RealTime.h>
26
27 #include <vector>
28 #include <algorithm>
29
30 //static int extant = 0;
31
32 #ifdef _WIN32
33 HANDLE
34 MatchVampPlugin::m_serialisingMutex;
35 #else
36 pthread_mutex_t
37 MatchVampPlugin::m_serialisingMutex;
38 #endif
39
40 bool
41 MatchVampPlugin::m_serialisingMutexInitialised = false;
42
43 MatchVampPlugin::MatchVampPlugin(float inputSampleRate) :
44 Plugin(inputSampleRate),
45 m_serialise(false),
46 m_begin(true),
47 m_locked(false)
48 {
49 if (!m_serialisingMutexInitialised) {
50 m_serialisingMutexInitialised = true;
51 #ifdef _WIN32
52 m_serialisingMutex = CreateMutex(NULL, FALSE, NULL);
53 #else
54 pthread_mutex_init(&m_serialisingMutex, 0);
55 #endif
56 }
57
58 pm1 = 0;
59 pm2 = 0;
60 feeder = 0;
61 // std::cerr << "MatchVampPlugin::MatchVampPlugin(" << this << "): extant = " << ++extant << std::endl;
62 }
63
64 MatchVampPlugin::~MatchVampPlugin()
65 {
66 // std::cerr << "MatchVampPlugin::~MatchVampPlugin(" << this << "): extant = " << --extant << std::endl;
67
68 delete feeder;
69 delete pm1;
70 delete pm2;
71
72 if (m_locked) {
73 #ifdef _WIN32
74 ReleaseMutex(m_serialisingMutex);
75 #else
76 pthread_mutex_unlock(&m_serialisingMutex);
77 #endif
78 m_locked = false;
79 }
80 }
81
82 string
83 MatchVampPlugin::getIdentifier() const
84 {
85 return "match";
86 }
87
88 string
89 MatchVampPlugin::getName() const
90 {
91 return "Match Performance Aligner";
92 }
93
94 string
95 MatchVampPlugin::getDescription() const
96 {
97 return "Calculate alignment between two performances in separate channel inputs";
98 }
99
100 string
101 MatchVampPlugin::getMaker() const
102 {
103 return "Simon Dixon (plugin by Chris Cannam)";
104 }
105
106 int
107 MatchVampPlugin::getPluginVersion() const
108 {
109 return 1;
110 }
111
112 string
113 MatchVampPlugin::getCopyright() const
114 {
115 return "GPL";
116 }
117
118 MatchVampPlugin::ParameterList
119 MatchVampPlugin::getParameterDescriptors() const
120 {
121 ParameterList list;
122
123 ParameterDescriptor desc;
124 desc.identifier = "serialise";
125 desc.name = "Serialise Plugin Invocations";
126 desc.description = "Reduce potential memory load at the expense of multiprocessor performance by serialising multi-threaded plugin runs";
127 desc.minValue = 0;
128 desc.maxValue = 1;
129 desc.defaultValue = 0;
130 desc.isQuantized = true;
131 desc.quantizeStep = 1;
132 list.push_back(desc);
133
134 return list;
135 }
136
137 float
138 MatchVampPlugin::getParameter(std::string name) const
139 {
140 if (name == "serialise") {
141 return m_serialise ? 1.0 : 0.0;
142 }
143 return 0.0;
144 }
145
146 void
147 MatchVampPlugin::setParameter(std::string name, float value)
148 {
149 if (name == "serialise") {
150 m_serialise = (value > 0.5);
151 std::cerr << "MatchVampPlugin::setParameter: set serialise to " << m_serialise << std::endl;
152 }
153 }
154
155 size_t
156 MatchVampPlugin::getPreferredStepSize() const
157 {
158 if (!pm1) createMatchers();
159 return pm1->getHopSize();
160 }
161
162 size_t
163 MatchVampPlugin::getPreferredBlockSize() const
164 {
165 if (!pm1) createMatchers();
166 return pm1->getFFTSize();
167 }
168
169 void
170 MatchVampPlugin::createMatchers() const
171 {
172 pm1 = new Matcher(m_inputSampleRate, 0);
173 pm2 = new Matcher(m_inputSampleRate, pm1);
174 pm1->setOtherMatcher(pm2);
175 feeder = new MatchFeeder(pm1, pm2);
176 }
177
178 bool
179 MatchVampPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize)
180 {
181 if (!pm1) createMatchers();
182 if (channels < getMinChannelCount() ||
183 channels > getMaxChannelCount()) return false;
184 if (stepSize != getPreferredStepSize() ||
185 blockSize != getPreferredBlockSize()) return false;
186 m_begin = true;
187 m_locked = false;
188 return true;
189 }
190
191 void
192 MatchVampPlugin::reset()
193 {
194 //!!!???
195 }
196
197 MatchVampPlugin::OutputList
198 MatchVampPlugin::getOutputDescriptors() const
199 {
200 OutputList list;
201
202 float outRate = 1.0 / 0.020; //!!! this is the default value of hopTime in Matcher
203
204 OutputDescriptor desc;
205 desc.identifier = "path";
206 desc.name = "Path";
207 desc.description = "Alignment path";
208 desc.unit = "";
209 desc.hasFixedBinCount = true;
210 desc.binCount = 1;
211 desc.hasKnownExtents = false;
212 desc.isQuantized = true;
213 desc.quantizeStep = 1;
214 desc.sampleType = OutputDescriptor::VariableSampleRate;
215 desc.sampleRate = outRate;
216 list.push_back(desc);
217
218 desc.identifier = "a_b";
219 desc.name = "A-B Timeline";
220 desc.description = "Timing in performance B corresponding to moments in performance A";
221 desc.unit = "sec";
222 desc.hasFixedBinCount = true;
223 desc.binCount = 1;
224 desc.hasKnownExtents = false;
225 desc.isQuantized = false;
226 desc.sampleType = OutputDescriptor::VariableSampleRate;
227 desc.sampleRate = outRate;
228 list.push_back(desc);
229
230 desc.identifier = "b_a";
231 desc.name = "B-A Timeline";
232 desc.description = "Timing in performance A corresponding to moments in performance B";
233 desc.unit = "sec";
234 desc.hasFixedBinCount = true;
235 desc.binCount = 1;
236 desc.hasKnownExtents = false;
237 desc.isQuantized = false;
238 desc.sampleType = OutputDescriptor::VariableSampleRate;
239 desc.sampleRate = outRate;
240 list.push_back(desc);
241
242 desc.identifier = "a_b_divergence";
243 desc.name = "A-B Divergence";
244 desc.description = "Difference between timings in performances A and B";
245 desc.unit = "sec";
246 desc.hasFixedBinCount = true;
247 desc.binCount = 1;
248 desc.hasKnownExtents = false;
249 desc.isQuantized = false;
250 desc.sampleType = OutputDescriptor::VariableSampleRate;
251 desc.sampleRate = outRate;
252 list.push_back(desc);
253
254 desc.identifier = "a_b_temporatio";
255 desc.name = "A-B Tempo Ratio";
256 desc.description = "Ratio of tempi between performances A and B";
257 desc.unit = "";
258 desc.hasFixedBinCount = true;
259 desc.binCount = 1;
260 desc.hasKnownExtents = false;
261 desc.isQuantized = false;
262 desc.sampleType = OutputDescriptor::VariableSampleRate;
263 desc.sampleRate = outRate;
264 list.push_back(desc);
265
266 return list;
267 }
268
269 MatchVampPlugin::FeatureSet
270 MatchVampPlugin::process(const float *const *inputBuffers,
271 Vamp::RealTime timestamp)
272 {
273 if (m_begin) {
274 if (!m_locked && m_serialise) {
275 m_locked = true;
276 #ifdef _WIN32
277 WaitForSingleObject(m_serialisingMutex, INFINITE);
278 #else
279 pthread_mutex_lock(&m_serialisingMutex);
280 #endif
281 }
282 m_begin = false;
283 }
284
285 // std::cerr << timestamp.toString();
286
287 feeder->feed(inputBuffers);
288
289 // std::cerr << ".";
290 // std::cerr << std::endl;
291
292 return FeatureSet();
293 }
294
295 MatchVampPlugin::FeatureSet
296 MatchVampPlugin::getRemainingFeatures()
297 {
298 int x = pm2->getFrameCount() - 1;
299 int y = pm1->getFrameCount() - 1;
300
301 Finder *finder = feeder->getFinder();
302
303 std::vector<int> pathx;
304 std::vector<int> pathy;
305
306 // std::cerr << "initial x,y = " << x << std::endl;
307
308 while (finder->find(y, x) && ((x > 0) || (y > 0))) {
309
310 pathx.push_back(x);
311 pathy.push_back(y);
312
313 // std::cerr << pathx.size() << ": (" << x << "," << y << ")" << std::endl;
314
315 switch (finder->getDistance() & ADVANCE_BOTH){
316 case ADVANCE_THIS: y--; break;
317 case ADVANCE_OTHER: x--; break;
318 case ADVANCE_BOTH: x--; y--; break;
319 default: // this would indicate a bug, but we wouldn't want to hang
320 std::cerr << "WARNING: MatchVampPlugin::getRemainingFeatures: Neither matcher advanced in path backtrack at (" << x << "," << y << ")" << std::endl;
321 if (x > y) x--; else y--; break;
322 }
323 }
324
325 std::reverse(pathx.begin(), pathx.end());
326 std::reverse(pathy.begin(), pathy.end());
327
328 int smoothedLen = Path().smooth(pathx, pathy, pathx.size());
329
330 FeatureSet returnFeatures;
331
332 int prevx = 0;
333 int prevy = 0;
334
335 for (int i = 0; i < smoothedLen; ++i) {
336
337 int x = pathx[i];
338 int y = pathy[i];
339
340 Vamp::RealTime xt = Vamp::RealTime::frame2RealTime
341 (x * pm1->getHopSize(), lrintf(m_inputSampleRate));
342 Vamp::RealTime yt = Vamp::RealTime::frame2RealTime
343 (y * pm2->getHopSize(), lrintf(m_inputSampleRate));
344
345 Feature feature;
346 feature.hasTimestamp = true;
347 feature.timestamp = xt;
348 feature.values.clear();
349 feature.values.push_back(yt.sec + double(yt.nsec)/1.0e9);
350 returnFeatures[0].push_back(feature);
351
352 if (x != prevx) {
353
354 feature.hasTimestamp = true;
355 feature.timestamp = xt;
356 feature.values.clear();
357 feature.values.push_back(yt.sec + yt.msec()/1000.0);
358 returnFeatures[1].push_back(feature);
359
360 Vamp::RealTime diff = yt - xt;
361 feature.values.clear();
362 feature.values.push_back(diff.sec + diff.msec()/1000.0);
363 returnFeatures[3].push_back(feature);
364
365 if (i > 0) {
366 int lookback = 100; //!!! arbitrary
367 if (lookback > i) lookback = i;
368 int xdiff = x - pathx[i-lookback];
369 int ydiff = y - pathy[i-lookback];
370 if (xdiff != 0 && ydiff != 0) {
371 float ratio = float(ydiff)/float(xdiff);
372 if (ratio < 8 && ratio > (1.0/8)) { //!!! just for now, since we aren't dealing properly with silence yet
373 feature.values.clear();
374 feature.values.push_back(ratio);
375 returnFeatures[4].push_back(feature);
376 }
377 }
378 }
379 }
380
381 if (y != prevy) {
382 feature.hasTimestamp = true;
383 feature.timestamp = yt;
384 feature.values.clear();
385 feature.values.push_back(xt.sec + xt.msec()/1000.0);
386 returnFeatures[2].push_back(feature);
387 }
388
389 prevx = x;
390 prevy = y;
391 }
392
393 delete feeder;
394 delete pm1;
395 delete pm2;
396 feeder = 0;
397 pm1 = 0;
398 pm2 = 0;
399
400 if (m_locked) {
401 #ifdef _WIN32
402 ReleaseMutex(m_serialisingMutex);
403 #else
404 pthread_mutex_unlock(&m_serialisingMutex);
405 #endif
406 m_locked = false;
407 }
408
409 return returnFeatures;
410
411
412 /*
413 for (int i = 0; i < smoothedLen; ++i) {
414 std::cerr << i << ": [" << pathx[i] << "," << pathy[i] << "]" << std::endl;
415 }
416
417 std::cerr << std::endl;
418 std::cerr << "File: A" << std::endl;
419 std::cerr << "Marks: -1" << std::endl;
420 std::cerr << "FixedPoints: true 0" << std::endl;
421 std::cerr << "0" << std::endl;
422 std::cerr << "0" << std::endl;
423 std::cerr << "0" << std::endl;
424 std::cerr << "0" << std::endl;
425 std::cerr << "File: B" << std::endl;
426 std::cerr << "Marks: 0" << std::endl;
427 std::cerr << "FixedPoints: true 0" << std::endl;
428 std::cerr << "0.02" << std::endl;
429 std::cerr << "0.02" << std::endl;
430
431 std::cerr << smoothedLen << std::endl;
432 for (int i = 0; i < smoothedLen; ++i) {
433 std::cerr << pathx[i] << std::endl;
434 }
435
436 std::cerr << smoothedLen << std::endl;
437 for (int i = 0; i < smoothedLen; ++i) {
438 std::cerr << pathy[i] << std::endl;
439 }
440 */
441 }
442
443 static Vamp::PluginAdapter<MatchVampPlugin> mvpAdapter;
444
445 const VampPluginDescriptor *vampGetPluginDescriptor(unsigned int version,
446 unsigned int index)
447 {
448 if (version < 1) return 0;
449
450 switch (index) {
451 case 0: return mvpAdapter.getDescriptor();
452 default: return 0;
453 }
454 }