c@92
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
c@92
|
2
|
c@92
|
3 /*
|
c@92
|
4 QM Vamp Plugin Set
|
c@92
|
5
|
c@92
|
6 Centre for Digital Music, Queen Mary, University of London.
|
c@92
|
7 All rights reserved.
|
c@92
|
8 */
|
c@92
|
9
|
c@92
|
10 #include "AdaptiveSpectrogram.h"
|
c@92
|
11
|
c@92
|
12 #include <cstdlib>
|
c@92
|
13 #include <cstring>
|
c@92
|
14
|
c@92
|
15 #include <iostream>
|
c@92
|
16
|
c@92
|
17 #include <dsp/transforms/FFT.h>
|
c@92
|
18
|
c@92
|
19 using std::string;
|
c@92
|
20 using std::vector;
|
c@92
|
21 using std::cerr;
|
c@92
|
22 using std::endl;
|
c@92
|
23
|
c@92
|
24 using Vamp::RealTime;
|
c@92
|
25
|
c@99
|
26 //#define DEBUG_VERBOSE 1
|
c@99
|
27
|
c@104
|
28 static const int cutThreadCount = 4;
|
c@104
|
29
|
c@92
|
30 AdaptiveSpectrogram::AdaptiveSpectrogram(float inputSampleRate) :
|
c@92
|
31 Plugin(inputSampleRate),
|
c@104
|
32 m_w(8),
|
c@104
|
33 m_n(3),
|
c@104
|
34 m_first(true)
|
c@92
|
35 {
|
c@92
|
36 }
|
c@92
|
37
|
c@92
|
38 AdaptiveSpectrogram::~AdaptiveSpectrogram()
|
c@92
|
39 {
|
c@104
|
40 for (int i = 0; i < m_cutThreads.size(); ++i) {
|
c@104
|
41 delete m_cutThreads[i];
|
c@104
|
42 }
|
c@104
|
43 m_cutThreads.clear();
|
c@105
|
44
|
c@106
|
45 for (FFTMap::iterator i = m_fftThreads.begin(); i != m_fftThreads.end(); ++i) {
|
c@106
|
46 delete i->second;
|
c@105
|
47 }
|
c@105
|
48 m_fftThreads.clear();
|
c@92
|
49 }
|
c@92
|
50
|
c@92
|
51 string
|
c@92
|
52 AdaptiveSpectrogram::getIdentifier() const
|
c@92
|
53 {
|
c@93
|
54 return "qm-adaptivespectrogram";
|
c@92
|
55 }
|
c@92
|
56
|
c@92
|
57 string
|
c@92
|
58 AdaptiveSpectrogram::getName() const
|
c@92
|
59 {
|
c@92
|
60 return "Adaptive Spectrogram";
|
c@92
|
61 }
|
c@92
|
62
|
c@92
|
63 string
|
c@92
|
64 AdaptiveSpectrogram::getDescription() const
|
c@92
|
65 {
|
c@92
|
66 return "Produce an adaptive spectrogram by adaptive selection from spectrograms at multiple resolutions";
|
c@92
|
67 }
|
c@92
|
68
|
c@92
|
69 string
|
c@92
|
70 AdaptiveSpectrogram::getMaker() const
|
c@92
|
71 {
|
c@92
|
72 return "Queen Mary, University of London";
|
c@92
|
73 }
|
c@92
|
74
|
c@92
|
75 int
|
c@92
|
76 AdaptiveSpectrogram::getPluginVersion() const
|
c@92
|
77 {
|
c@92
|
78 return 1;
|
c@92
|
79 }
|
c@92
|
80
|
c@92
|
81 string
|
c@92
|
82 AdaptiveSpectrogram::getCopyright() const
|
c@92
|
83 {
|
c@92
|
84 return "Plugin by Wen Xue and Chris Cannam. Copyright (c) 2009 Wen Xue and QMUL - All Rights Reserved";
|
c@92
|
85 }
|
c@92
|
86
|
c@92
|
87 size_t
|
c@92
|
88 AdaptiveSpectrogram::getPreferredStepSize() const
|
c@92
|
89 {
|
c@92
|
90 return ((2 << m_w) << m_n) / 2;
|
c@92
|
91 }
|
c@92
|
92
|
c@92
|
93 size_t
|
c@92
|
94 AdaptiveSpectrogram::getPreferredBlockSize() const
|
c@92
|
95 {
|
c@92
|
96 return (2 << m_w) << m_n;
|
c@92
|
97 }
|
c@92
|
98
|
c@92
|
99 bool
|
c@92
|
100 AdaptiveSpectrogram::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
c@92
|
101 {
|
c@92
|
102 if (channels < getMinChannelCount() ||
|
c@92
|
103 channels > getMaxChannelCount()) return false;
|
c@92
|
104
|
c@92
|
105 return true;
|
c@92
|
106 }
|
c@92
|
107
|
c@92
|
108 void
|
c@92
|
109 AdaptiveSpectrogram::reset()
|
c@92
|
110 {
|
c@92
|
111
|
c@92
|
112 }
|
c@92
|
113
|
c@92
|
114 AdaptiveSpectrogram::ParameterList
|
c@92
|
115 AdaptiveSpectrogram::getParameterDescriptors() const
|
c@92
|
116 {
|
c@92
|
117 ParameterList list;
|
c@92
|
118
|
c@92
|
119 ParameterDescriptor desc;
|
c@92
|
120 desc.identifier = "n";
|
c@92
|
121 desc.name = "Number of resolutions";
|
c@92
|
122 desc.description = "Number of consecutive powers of two to use as spectrogram resolutions, starting with the minimum resolution specified";
|
c@92
|
123 desc.unit = "";
|
c@92
|
124 desc.minValue = 1;
|
c@92
|
125 desc.maxValue = 10;
|
c@104
|
126 desc.defaultValue = 4;
|
c@92
|
127 desc.isQuantized = true;
|
c@92
|
128 desc.quantizeStep = 1;
|
c@92
|
129 list.push_back(desc);
|
c@92
|
130
|
c@92
|
131 ParameterDescriptor desc2;
|
c@92
|
132 desc2.identifier = "w";
|
c@92
|
133 desc2.name = "Smallest resolution";
|
c@92
|
134 desc2.description = "Smallest of the consecutive powers of two to use as spectrogram resolutions";
|
c@92
|
135 desc2.unit = "";
|
c@92
|
136 desc2.minValue = 1;
|
c@92
|
137 desc2.maxValue = 14;
|
c@104
|
138 desc2.defaultValue = 9;
|
c@92
|
139 desc2.isQuantized = true;
|
c@92
|
140 desc2.quantizeStep = 1;
|
c@92
|
141 // I am so lazy
|
c@92
|
142 desc2.valueNames.push_back("2");
|
c@92
|
143 desc2.valueNames.push_back("4");
|
c@92
|
144 desc2.valueNames.push_back("8");
|
c@92
|
145 desc2.valueNames.push_back("16");
|
c@92
|
146 desc2.valueNames.push_back("32");
|
c@92
|
147 desc2.valueNames.push_back("64");
|
c@92
|
148 desc2.valueNames.push_back("128");
|
c@92
|
149 desc2.valueNames.push_back("256");
|
c@92
|
150 desc2.valueNames.push_back("512");
|
c@92
|
151 desc2.valueNames.push_back("1024");
|
c@92
|
152 desc2.valueNames.push_back("2048");
|
c@92
|
153 desc2.valueNames.push_back("4096");
|
c@92
|
154 desc2.valueNames.push_back("8192");
|
c@92
|
155 desc2.valueNames.push_back("16384");
|
c@92
|
156 list.push_back(desc2);
|
c@92
|
157
|
c@92
|
158 return list;
|
c@92
|
159 }
|
c@92
|
160
|
c@92
|
161 float
|
c@92
|
162 AdaptiveSpectrogram::getParameter(std::string id) const
|
c@92
|
163 {
|
c@92
|
164 if (id == "n") return m_n+1;
|
c@92
|
165 else if (id == "w") return m_w+1;
|
c@92
|
166 return 0.f;
|
c@92
|
167 }
|
c@92
|
168
|
c@92
|
169 void
|
c@92
|
170 AdaptiveSpectrogram::setParameter(std::string id, float value)
|
c@92
|
171 {
|
c@92
|
172 if (id == "n") {
|
c@92
|
173 int n = lrintf(value);
|
c@92
|
174 if (n >= 1 && n <= 10) m_n = n-1;
|
c@92
|
175 } else if (id == "w") {
|
c@92
|
176 int w = lrintf(value);
|
c@92
|
177 if (w >= 1 && w <= 14) m_w = w-1;
|
c@92
|
178 }
|
c@92
|
179 }
|
c@92
|
180
|
c@92
|
181 AdaptiveSpectrogram::OutputList
|
c@92
|
182 AdaptiveSpectrogram::getOutputDescriptors() const
|
c@92
|
183 {
|
c@92
|
184 OutputList list;
|
c@92
|
185
|
c@92
|
186 OutputDescriptor d;
|
c@92
|
187 d.identifier = "output";
|
c@92
|
188 d.name = "Output";
|
c@92
|
189 d.description = "The output of the plugin";
|
c@92
|
190 d.unit = "";
|
c@92
|
191 d.hasFixedBinCount = true;
|
c@92
|
192 d.binCount = ((2 << m_w) << m_n) / 2;
|
c@92
|
193 d.hasKnownExtents = false;
|
c@92
|
194 d.isQuantized = false;
|
c@92
|
195 d.sampleType = OutputDescriptor::FixedSampleRate;
|
c@92
|
196 d.sampleRate = m_inputSampleRate / ((2 << m_w) / 2);
|
c@92
|
197 d.hasDuration = false;
|
c@92
|
198 list.push_back(d);
|
c@92
|
199
|
c@92
|
200 return list;
|
c@92
|
201 }
|
c@92
|
202
|
c@92
|
203 AdaptiveSpectrogram::FeatureSet
|
c@92
|
204 AdaptiveSpectrogram::getRemainingFeatures()
|
c@92
|
205 {
|
c@92
|
206 FeatureSet fs;
|
c@92
|
207 return fs;
|
c@92
|
208 }
|
c@92
|
209
|
c@100
|
210 AdaptiveSpectrogram::FeatureSet
|
c@100
|
211 AdaptiveSpectrogram::process(const float *const *inputBuffers, RealTime ts)
|
c@100
|
212 {
|
c@100
|
213 FeatureSet fs;
|
c@100
|
214
|
c@100
|
215 int minwid = (2 << m_w), maxwid = ((2 << m_w) << m_n);
|
c@100
|
216
|
c@101
|
217 #ifdef DEBUG_VERBOSE
|
c@100
|
218 cerr << "widths from " << minwid << " to " << maxwid << " ("
|
c@100
|
219 << minwid/2 << " to " << maxwid/2 << " in real parts)" << endl;
|
c@101
|
220 #endif
|
c@100
|
221
|
c@100
|
222 Spectrograms s(minwid/2, maxwid/2, 1);
|
c@100
|
223
|
c@100
|
224 int w = minwid;
|
c@100
|
225 int index = 0;
|
c@100
|
226
|
c@100
|
227 while (w <= maxwid) {
|
c@106
|
228 if (m_fftThreads.find(w) == m_fftThreads.end()) {
|
c@106
|
229 m_fftThreads[w] = new FFTThread(w);
|
c@106
|
230 }
|
c@106
|
231 m_fftThreads[w]->calculate(inputBuffers[0], s, index, maxwid);
|
c@100
|
232 w *= 2;
|
c@100
|
233 ++index;
|
c@100
|
234 }
|
c@100
|
235
|
c@105
|
236 w = minwid;
|
c@105
|
237 while (w <= maxwid) {
|
c@106
|
238 m_fftThreads[w]->await();
|
c@105
|
239 w *= 2;
|
c@105
|
240 }
|
c@102
|
241
|
c@104
|
242 m_first = true;//!!!
|
c@104
|
243
|
c@100
|
244 Cutting *cutting = cut(s, maxwid/2, 0, 0, maxwid/2);
|
c@100
|
245
|
c@101
|
246 #ifdef DEBUG_VERBOSE
|
c@100
|
247 printCutting(cutting, " ");
|
c@101
|
248 #endif
|
c@100
|
249
|
c@100
|
250 vector<vector<float> > rmat(maxwid/minwid);
|
c@100
|
251 for (int i = 0; i < maxwid/minwid; ++i) {
|
c@100
|
252 rmat[i] = vector<float>(maxwid/2);
|
c@100
|
253 }
|
c@100
|
254
|
c@100
|
255 assemble(s, cutting, rmat, 0, 0, maxwid/minwid, maxwid/2);
|
c@100
|
256
|
c@100
|
257 delete cutting;
|
c@100
|
258
|
c@100
|
259 for (int i = 0; i < rmat.size(); ++i) {
|
c@100
|
260 Feature f;
|
c@100
|
261 f.hasTimestamp = false;
|
c@100
|
262 f.values = rmat[i];
|
c@100
|
263 fs[0].push_back(f);
|
c@100
|
264 }
|
c@100
|
265
|
c@104
|
266 // std::cerr << "process returning!\n" << std::endl;
|
c@104
|
267
|
c@100
|
268 return fs;
|
c@100
|
269 }
|
c@100
|
270
|
c@100
|
271 void
|
c@104
|
272 AdaptiveSpectrogram::printCutting(Cutting *c, string pfx) const
|
c@100
|
273 {
|
c@100
|
274 if (c->first) {
|
c@100
|
275 if (c->cut == Cutting::Horizontal) {
|
c@100
|
276 cerr << pfx << "H" << endl;
|
c@100
|
277 } else if (c->cut == Cutting::Vertical) {
|
c@100
|
278 cerr << pfx << "V" << endl;
|
c@100
|
279 }
|
c@100
|
280 printCutting(c->first, pfx + " ");
|
c@100
|
281 printCutting(c->second, pfx + " ");
|
c@100
|
282 } else {
|
c@100
|
283 cerr << pfx << "* " << c->value << endl;
|
c@100
|
284 }
|
c@100
|
285 }
|
c@100
|
286
|
c@104
|
287 void
|
c@104
|
288 AdaptiveSpectrogram::getSubCuts(const Spectrograms &s,
|
c@104
|
289 int res,
|
c@104
|
290 int x, int y, int h,
|
c@104
|
291 Cutting *&top, Cutting *&bottom,
|
c@104
|
292 Cutting *&left, Cutting *&right) const
|
c@104
|
293 {
|
c@104
|
294 if (m_first) {//!!!
|
c@104
|
295
|
c@104
|
296 m_first = false;
|
c@104
|
297
|
c@104
|
298 if (m_cutThreads.empty()) {
|
c@104
|
299 for (int i = 0; i < 4; ++i) {
|
c@104
|
300 // for (int i = 0; i < 1; ++i) {
|
c@104
|
301 CutThread *t = new CutThread(this);
|
c@105
|
302 // t->start();
|
c@104
|
303 m_cutThreads.push_back(t);
|
c@104
|
304 }
|
c@104
|
305 // sleep(1); //!!!
|
c@104
|
306 }
|
c@104
|
307
|
c@104
|
308 // int threadIndices[4];
|
c@104
|
309 // int found = 0;
|
c@104
|
310 // for (int i = 0; i < m_cutThreads.size(); ++i) {
|
c@104
|
311 // if (!m_cutThreads[i]->busy()) {
|
c@104
|
312 // threadIndices[found] = i;
|
c@104
|
313 // if (++found == 4) break;
|
c@104
|
314 // }
|
c@104
|
315 // }
|
c@104
|
316
|
c@104
|
317 // if (found == 4) {
|
c@104
|
318
|
c@104
|
319 // enough threads available; use them. Need to avoid threads calling back on cut() in this class before we have made all of our threads busy (otherwise the recursive call is likely to claim threads further down our threadIndices before we do) -- hence m_threadMutex
|
c@104
|
320
|
c@104
|
321 //!!! no, thread mutex not a good way, need a claim() call on each thread or something
|
c@104
|
322
|
c@104
|
323 // m_threadMutex.lock();
|
c@104
|
324 m_cutThreads[0]->cut(s, res, x, y + h/2, h/2); // top
|
c@104
|
325 m_cutThreads[1]->cut(s, res, x, y, h/2); // bottom
|
c@104
|
326 m_cutThreads[2]->cut(s, res/2, 2 * x, y/2, h/2); // left
|
c@104
|
327 m_cutThreads[3]->cut(s, res/2, 2 * x + 1, y/2, h/2); // right
|
c@104
|
328
|
c@104
|
329 // std::cerr << "set up all four" << std::endl;
|
c@104
|
330
|
c@104
|
331 top = m_cutThreads[0]->get();
|
c@104
|
332 bottom = m_cutThreads[1]->get();
|
c@104
|
333 left = m_cutThreads[2]->get();
|
c@104
|
334 right = m_cutThreads[3]->get();
|
c@104
|
335 /*
|
c@104
|
336 bottom = cut(s, res, x, y, h/2);
|
c@104
|
337
|
c@104
|
338 // The "horizontal" division is a left/right split. Splitting
|
c@104
|
339 // this way places us in resolution res/2, which has lower
|
c@104
|
340 // vertical resolution but higher horizontal resolution. We
|
c@104
|
341 // need to double x accordingly.
|
c@104
|
342
|
c@104
|
343 left = cut(s, res/2, 2 * x, y/2, h/2);
|
c@104
|
344 right = cut(s, res/2, 2 * x + 1, y/2, h/2);
|
c@104
|
345 */
|
c@104
|
346 // std::cerr << "got all four" << std::endl;
|
c@104
|
347
|
c@104
|
348 } else {
|
c@104
|
349
|
c@104
|
350 // unthreaded
|
c@104
|
351
|
c@104
|
352 // The "vertical" division is a top/bottom split.
|
c@104
|
353 // Splitting this way keeps us in the same resolution,
|
c@104
|
354 // but with two vertical subregions of height h/2.
|
c@104
|
355
|
c@104
|
356 top = cut(s, res, x, y + h/2, h/2);
|
c@104
|
357 bottom = cut(s, res, x, y, h/2);
|
c@104
|
358
|
c@104
|
359 // The "horizontal" division is a left/right split. Splitting
|
c@104
|
360 // this way places us in resolution res/2, which has lower
|
c@104
|
361 // vertical resolution but higher horizontal resolution. We
|
c@104
|
362 // need to double x accordingly.
|
c@104
|
363
|
c@104
|
364 left = cut(s, res/2, 2 * x, y/2, h/2);
|
c@104
|
365 right = cut(s, res/2, 2 * x + 1, y/2, h/2);
|
c@104
|
366 }
|
c@104
|
367 }
|
c@104
|
368
|
c@100
|
369 AdaptiveSpectrogram::Cutting *
|
c@100
|
370 AdaptiveSpectrogram::cut(const Spectrograms &s,
|
c@100
|
371 int res,
|
c@104
|
372 int x, int y, int h) const
|
c@100
|
373 {
|
c@100
|
374 // cerr << "res = " << res << ", x = " << x << ", y = " << y << ", h = " << h << endl;
|
c@100
|
375
|
c@100
|
376 if (h > 1 && res > s.minres) {
|
c@100
|
377
|
c@104
|
378 Cutting *top = 0, *bottom = 0, *left = 0, *right = 0;
|
c@104
|
379 getSubCuts(s, res, x, y, h, top, bottom, left, right);
|
c@100
|
380
|
c@100
|
381 double vcost = top->cost + bottom->cost;
|
c@100
|
382 double hcost = left->cost + right->cost;
|
c@100
|
383
|
c@101
|
384 bool normalize = true;
|
c@101
|
385
|
c@101
|
386 if (normalize) {
|
c@101
|
387
|
c@101
|
388 double venergy = top->value + bottom->value;
|
c@101
|
389 vcost = (vcost + (venergy * log(venergy))) / venergy;
|
c@101
|
390
|
c@101
|
391 double henergy = left->value + right->value;
|
c@101
|
392 hcost = (hcost + (henergy * log(henergy))) / henergy;
|
c@101
|
393 }
|
c@101
|
394
|
c@100
|
395 if (vcost > hcost) {
|
c@100
|
396
|
c@100
|
397 // cut horizontally (left/right)
|
c@100
|
398
|
c@100
|
399 Cutting *cutting = new Cutting;
|
c@100
|
400 cutting->cut = Cutting::Horizontal;
|
c@100
|
401 cutting->first = left;
|
c@100
|
402 cutting->second = right;
|
c@100
|
403 cutting->cost = hcost;
|
c@100
|
404 cutting->value = left->value + right->value;
|
c@100
|
405 delete top;
|
c@100
|
406 delete bottom;
|
c@100
|
407 return cutting;
|
c@100
|
408
|
c@100
|
409 } else {
|
c@100
|
410
|
c@100
|
411 Cutting *cutting = new Cutting;
|
c@100
|
412 cutting->cut = Cutting::Vertical;
|
c@100
|
413 cutting->first = top;
|
c@100
|
414 cutting->second = bottom;
|
c@100
|
415 cutting->cost = vcost;
|
c@100
|
416 cutting->value = top->value + bottom->value;
|
c@100
|
417 delete left;
|
c@100
|
418 delete right;
|
c@100
|
419 return cutting;
|
c@100
|
420 }
|
c@100
|
421
|
c@100
|
422 } else {
|
c@100
|
423
|
c@100
|
424 // no cuts possible from this level
|
c@100
|
425
|
c@100
|
426 Cutting *cutting = new Cutting;
|
c@100
|
427 cutting->cut = Cutting::Finished;
|
c@100
|
428 cutting->first = 0;
|
c@100
|
429 cutting->second = 0;
|
c@100
|
430
|
c@100
|
431 int n = 0;
|
c@100
|
432 for (int r = res; r > s.minres; r /= 2) ++n;
|
c@100
|
433 const Spectrogram *spectrogram = s.spectrograms[n];
|
c@100
|
434
|
c@100
|
435 cutting->cost = cost(*spectrogram, x, y);
|
c@100
|
436 cutting->value = value(*spectrogram, x, y);
|
c@100
|
437
|
c@100
|
438 // cerr << "cost for this cell: " << cutting->cost << endl;
|
c@100
|
439
|
c@100
|
440 return cutting;
|
c@100
|
441 }
|
c@100
|
442 }
|
c@100
|
443
|
c@100
|
444 void
|
c@100
|
445 AdaptiveSpectrogram::assemble(const Spectrograms &s,
|
c@100
|
446 const Cutting *cutting,
|
c@100
|
447 vector<vector<float> > &rmat,
|
c@104
|
448 int x, int y, int w, int h) const
|
c@100
|
449 {
|
c@100
|
450 switch (cutting->cut) {
|
c@100
|
451
|
c@100
|
452 case Cutting::Finished:
|
c@100
|
453 for (int i = 0; i < w; ++i) {
|
c@100
|
454 for (int j = 0; j < h; ++j) {
|
c@100
|
455 rmat[x+i][y+j] = cutting->value;
|
c@100
|
456 }
|
c@100
|
457 }
|
c@100
|
458 return;
|
c@100
|
459
|
c@100
|
460 case Cutting::Horizontal:
|
c@100
|
461 assemble(s, cutting->first, rmat, x, y, w/2, h);
|
c@100
|
462 assemble(s, cutting->second, rmat, x+w/2, y, w/2, h);
|
c@100
|
463 break;
|
c@100
|
464
|
c@100
|
465 case Cutting::Vertical:
|
c@100
|
466 assemble(s, cutting->first, rmat, x, y+h/2, w, h/2);
|
c@100
|
467 assemble(s, cutting->second, rmat, x, y, w, h/2);
|
c@100
|
468 break;
|
c@100
|
469 }
|
c@100
|
470 }
|
c@100
|
471
|