Mercurial > hg > match-vamp
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 } |