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@92
|
28 AdaptiveSpectrogram::AdaptiveSpectrogram(float inputSampleRate) :
|
c@92
|
29 Plugin(inputSampleRate),
|
c@104
|
30 m_w(8),
|
c@104
|
31 m_n(3),
|
c@109
|
32 m_threaded(true),
|
c@109
|
33 m_threadsInUse(false)
|
c@92
|
34 {
|
c@92
|
35 }
|
c@92
|
36
|
c@92
|
37 AdaptiveSpectrogram::~AdaptiveSpectrogram()
|
c@92
|
38 {
|
c@104
|
39 for (int i = 0; i < m_cutThreads.size(); ++i) {
|
c@104
|
40 delete m_cutThreads[i];
|
c@104
|
41 }
|
c@104
|
42 m_cutThreads.clear();
|
c@105
|
43
|
c@110
|
44 for (FFTMap::iterator i = m_fftThreads.begin();
|
c@110
|
45 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@109
|
158 ParameterDescriptor desc3;
|
c@109
|
159 desc3.identifier = "threaded";
|
c@109
|
160 desc3.name = "Multi-threaded processing";
|
c@110
|
161 desc3.description = "Perform calculations using several threads in parallel";
|
c@109
|
162 desc3.unit = "";
|
c@109
|
163 desc3.minValue = 0;
|
c@109
|
164 desc3.maxValue = 1;
|
c@109
|
165 desc3.defaultValue = 1;
|
c@109
|
166 desc3.isQuantized = true;
|
c@109
|
167 desc3.quantizeStep = 1;
|
c@109
|
168 list.push_back(desc3);
|
c@109
|
169
|
c@92
|
170 return list;
|
c@92
|
171 }
|
c@92
|
172
|
c@92
|
173 float
|
c@92
|
174 AdaptiveSpectrogram::getParameter(std::string id) const
|
c@92
|
175 {
|
c@92
|
176 if (id == "n") return m_n+1;
|
c@92
|
177 else if (id == "w") return m_w+1;
|
c@109
|
178 else if (id == "threaded") return (m_threaded ? 1 : 0);
|
c@92
|
179 return 0.f;
|
c@92
|
180 }
|
c@92
|
181
|
c@92
|
182 void
|
c@92
|
183 AdaptiveSpectrogram::setParameter(std::string id, float value)
|
c@92
|
184 {
|
c@92
|
185 if (id == "n") {
|
c@92
|
186 int n = lrintf(value);
|
c@92
|
187 if (n >= 1 && n <= 10) m_n = n-1;
|
c@92
|
188 } else if (id == "w") {
|
c@92
|
189 int w = lrintf(value);
|
c@92
|
190 if (w >= 1 && w <= 14) m_w = w-1;
|
c@109
|
191 } else if (id == "threaded") {
|
c@109
|
192 m_threaded = (value > 0.5);
|
c@109
|
193 }
|
c@92
|
194 }
|
c@92
|
195
|
c@92
|
196 AdaptiveSpectrogram::OutputList
|
c@92
|
197 AdaptiveSpectrogram::getOutputDescriptors() const
|
c@92
|
198 {
|
c@92
|
199 OutputList list;
|
c@92
|
200
|
c@92
|
201 OutputDescriptor d;
|
c@92
|
202 d.identifier = "output";
|
c@92
|
203 d.name = "Output";
|
c@92
|
204 d.description = "The output of the plugin";
|
c@92
|
205 d.unit = "";
|
c@92
|
206 d.hasFixedBinCount = true;
|
c@92
|
207 d.binCount = ((2 << m_w) << m_n) / 2;
|
c@92
|
208 d.hasKnownExtents = false;
|
c@92
|
209 d.isQuantized = false;
|
c@92
|
210 d.sampleType = OutputDescriptor::FixedSampleRate;
|
c@92
|
211 d.sampleRate = m_inputSampleRate / ((2 << m_w) / 2);
|
c@92
|
212 d.hasDuration = false;
|
c@112
|
213 char name[20];
|
c@112
|
214 for (int i = 0; i < d.binCount; ++i) {
|
c@112
|
215 float freq = (m_inputSampleRate / d.binCount) * (i + 1); // no DC bin
|
c@112
|
216 sprintf(name, "%d Hz", int(freq));
|
c@112
|
217 d.binNames.push_back(name);
|
c@112
|
218 }
|
c@92
|
219 list.push_back(d);
|
c@92
|
220
|
c@92
|
221 return list;
|
c@92
|
222 }
|
c@92
|
223
|
c@92
|
224 AdaptiveSpectrogram::FeatureSet
|
c@92
|
225 AdaptiveSpectrogram::getRemainingFeatures()
|
c@92
|
226 {
|
c@92
|
227 FeatureSet fs;
|
c@92
|
228 return fs;
|
c@92
|
229 }
|
c@92
|
230
|
c@100
|
231 AdaptiveSpectrogram::FeatureSet
|
c@100
|
232 AdaptiveSpectrogram::process(const float *const *inputBuffers, RealTime ts)
|
c@100
|
233 {
|
c@100
|
234 FeatureSet fs;
|
c@100
|
235
|
c@100
|
236 int minwid = (2 << m_w), maxwid = ((2 << m_w) << m_n);
|
c@100
|
237
|
c@101
|
238 #ifdef DEBUG_VERBOSE
|
c@100
|
239 cerr << "widths from " << minwid << " to " << maxwid << " ("
|
c@100
|
240 << minwid/2 << " to " << maxwid/2 << " in real parts)" << endl;
|
c@101
|
241 #endif
|
c@100
|
242
|
c@100
|
243 Spectrograms s(minwid/2, maxwid/2, 1);
|
c@100
|
244
|
c@100
|
245 int w = minwid;
|
c@100
|
246 int index = 0;
|
c@100
|
247
|
c@100
|
248 while (w <= maxwid) {
|
c@106
|
249 if (m_fftThreads.find(w) == m_fftThreads.end()) {
|
c@106
|
250 m_fftThreads[w] = new FFTThread(w);
|
c@106
|
251 }
|
c@109
|
252 if (m_threaded) {
|
c@109
|
253 m_fftThreads[w]->startCalculation(inputBuffers[0], s, index, maxwid);
|
c@109
|
254 } else {
|
c@109
|
255 m_fftThreads[w]->setParameters(inputBuffers[0], s, index, maxwid);
|
c@109
|
256 m_fftThreads[w]->performTask();
|
c@109
|
257 }
|
c@100
|
258 w *= 2;
|
c@100
|
259 ++index;
|
c@100
|
260 }
|
c@100
|
261
|
c@109
|
262 if (m_threaded) {
|
c@109
|
263 w = minwid;
|
c@109
|
264 while (w <= maxwid) {
|
c@109
|
265 m_fftThreads[w]->await();
|
c@109
|
266 w *= 2;
|
c@109
|
267 }
|
c@105
|
268 }
|
c@102
|
269
|
c@109
|
270 m_threadsInUse = false;
|
c@104
|
271
|
c@111
|
272 std::cerr << "maxwid/2 = " << maxwid/2 << ", minwid/2 = " << minwid/2 << ", n+1 = " << m_n+1 << ", 2^(n+1) = " << (2<<m_n) << std::endl;
|
c@110
|
273
|
c@110
|
274 Cutting *cutting = cut(s, maxwid/2, 0, 0, maxwid/2, 0);
|
c@100
|
275
|
c@101
|
276 #ifdef DEBUG_VERBOSE
|
c@100
|
277 printCutting(cutting, " ");
|
c@101
|
278 #endif
|
c@100
|
279
|
c@100
|
280 vector<vector<float> > rmat(maxwid/minwid);
|
c@100
|
281 for (int i = 0; i < maxwid/minwid; ++i) {
|
c@100
|
282 rmat[i] = vector<float>(maxwid/2);
|
c@100
|
283 }
|
c@100
|
284
|
c@100
|
285 assemble(s, cutting, rmat, 0, 0, maxwid/minwid, maxwid/2);
|
c@100
|
286
|
c@110
|
287 cutting->erase();
|
c@100
|
288
|
c@100
|
289 for (int i = 0; i < rmat.size(); ++i) {
|
c@100
|
290 Feature f;
|
c@100
|
291 f.hasTimestamp = false;
|
c@100
|
292 f.values = rmat[i];
|
c@100
|
293 fs[0].push_back(f);
|
c@100
|
294 }
|
c@100
|
295
|
c@104
|
296 // std::cerr << "process returning!\n" << std::endl;
|
c@104
|
297
|
c@100
|
298 return fs;
|
c@100
|
299 }
|
c@100
|
300
|
c@100
|
301 void
|
c@104
|
302 AdaptiveSpectrogram::printCutting(Cutting *c, string pfx) const
|
c@100
|
303 {
|
c@100
|
304 if (c->first) {
|
c@100
|
305 if (c->cut == Cutting::Horizontal) {
|
c@100
|
306 cerr << pfx << "H" << endl;
|
c@100
|
307 } else if (c->cut == Cutting::Vertical) {
|
c@100
|
308 cerr << pfx << "V" << endl;
|
c@100
|
309 }
|
c@100
|
310 printCutting(c->first, pfx + " ");
|
c@100
|
311 printCutting(c->second, pfx + " ");
|
c@100
|
312 } else {
|
c@100
|
313 cerr << pfx << "* " << c->value << endl;
|
c@100
|
314 }
|
c@100
|
315 }
|
c@100
|
316
|
c@104
|
317 void
|
c@104
|
318 AdaptiveSpectrogram::getSubCuts(const Spectrograms &s,
|
c@104
|
319 int res,
|
c@104
|
320 int x, int y, int h,
|
c@104
|
321 Cutting *&top, Cutting *&bottom,
|
c@104
|
322 Cutting *&left, Cutting *&right) const
|
c@104
|
323 {
|
c@109
|
324 if (m_threaded && !m_threadsInUse) {
|
c@104
|
325
|
c@109
|
326 m_threadsInUse = true;
|
c@104
|
327
|
c@104
|
328 if (m_cutThreads.empty()) {
|
c@104
|
329 for (int i = 0; i < 4; ++i) {
|
c@104
|
330 CutThread *t = new CutThread(this);
|
c@104
|
331 m_cutThreads.push_back(t);
|
c@104
|
332 }
|
c@104
|
333 }
|
c@104
|
334
|
c@109
|
335 // Cut threads 0 and 1 calculate the top and bottom halves;
|
c@110
|
336 // threads 2 and 3 calculate left and right. See notes in
|
c@110
|
337 // unthreaded code below for more information.
|
c@104
|
338
|
c@110
|
339 m_cutThreads[0]->cut(s, res, x, y + h/2, h/2); // top
|
c@110
|
340 m_cutThreads[1]->cut(s, res, x, y, h/2); // bottom
|
c@110
|
341 m_cutThreads[2]->cut(s, res/2, 2 * x, y/2, h/2); // left
|
c@110
|
342 m_cutThreads[3]->cut(s, res/2, 2 * x + 1, y/2, h/2); // right
|
c@104
|
343
|
c@104
|
344 top = m_cutThreads[0]->get();
|
c@104
|
345 bottom = m_cutThreads[1]->get();
|
c@104
|
346 left = m_cutThreads[2]->get();
|
c@104
|
347 right = m_cutThreads[3]->get();
|
c@104
|
348
|
c@104
|
349 } else {
|
c@104
|
350
|
c@110
|
351 // Unthreaded version
|
c@104
|
352
|
c@104
|
353 // The "vertical" division is a top/bottom split.
|
c@104
|
354 // Splitting this way keeps us in the same resolution,
|
c@104
|
355 // but with two vertical subregions of height h/2.
|
c@104
|
356
|
c@110
|
357 top = cut(s, res, x, y + h/2, h/2, 0);
|
c@110
|
358 bottom = cut(s, res, x, y, h/2, 0);
|
c@104
|
359
|
c@104
|
360 // The "horizontal" division is a left/right split. Splitting
|
c@104
|
361 // this way places us in resolution res/2, which has lower
|
c@104
|
362 // vertical resolution but higher horizontal resolution. We
|
c@104
|
363 // need to double x accordingly.
|
c@104
|
364
|
c@110
|
365 left = cut(s, res/2, 2 * x, y/2, h/2, 0);
|
c@110
|
366 right = cut(s, res/2, 2 * x + 1, y/2, h/2, 0);
|
c@104
|
367 }
|
c@104
|
368 }
|
c@104
|
369
|
c@100
|
370 AdaptiveSpectrogram::Cutting *
|
c@100
|
371 AdaptiveSpectrogram::cut(const Spectrograms &s,
|
c@100
|
372 int res,
|
c@110
|
373 int x, int y, int h,
|
c@110
|
374 BlockAllocator *allocator) const
|
c@100
|
375 {
|
c@100
|
376 // cerr << "res = " << res << ", x = " << x << ", y = " << y << ", h = " << h << endl;
|
c@100
|
377
|
c@110
|
378 Cutting *cutting;
|
c@110
|
379 if (allocator) {
|
c@110
|
380 cutting = (Cutting *)(allocator->allocate());
|
c@110
|
381 cutting->allocator = allocator;
|
c@110
|
382 } else {
|
c@110
|
383 cutting = new Cutting;
|
c@110
|
384 cutting->allocator = 0;
|
c@110
|
385 }
|
c@110
|
386
|
c@100
|
387 if (h > 1 && res > s.minres) {
|
c@100
|
388
|
c@104
|
389 Cutting *top = 0, *bottom = 0, *left = 0, *right = 0;
|
c@104
|
390 getSubCuts(s, res, x, y, h, top, bottom, left, right);
|
c@100
|
391
|
c@100
|
392 double vcost = top->cost + bottom->cost;
|
c@100
|
393 double hcost = left->cost + right->cost;
|
c@100
|
394
|
c@101
|
395 bool normalize = true;
|
c@101
|
396
|
c@101
|
397 if (normalize) {
|
c@101
|
398
|
c@101
|
399 double venergy = top->value + bottom->value;
|
c@101
|
400 vcost = (vcost + (venergy * log(venergy))) / venergy;
|
c@101
|
401
|
c@101
|
402 double henergy = left->value + right->value;
|
c@101
|
403 hcost = (hcost + (henergy * log(henergy))) / henergy;
|
c@101
|
404 }
|
c@101
|
405
|
c@100
|
406 if (vcost > hcost) {
|
c@100
|
407
|
c@100
|
408 // cut horizontally (left/right)
|
c@100
|
409 cutting->cut = Cutting::Horizontal;
|
c@100
|
410 cutting->first = left;
|
c@100
|
411 cutting->second = right;
|
c@100
|
412 cutting->cost = hcost;
|
c@111
|
413 cutting->value = left->value + right->value;
|
c@110
|
414 top->erase();
|
c@110
|
415 bottom->erase();
|
c@100
|
416 return cutting;
|
c@100
|
417
|
c@100
|
418 } else {
|
c@100
|
419
|
c@110
|
420 // cut vertically (top/bottom)
|
c@100
|
421 cutting->cut = Cutting::Vertical;
|
c@100
|
422 cutting->first = top;
|
c@100
|
423 cutting->second = bottom;
|
c@100
|
424 cutting->cost = vcost;
|
c@111
|
425 cutting->value = top->value + bottom->value;
|
c@110
|
426 left->erase();
|
c@110
|
427 right->erase();
|
c@100
|
428 return cutting;
|
c@100
|
429 }
|
c@100
|
430
|
c@100
|
431 } else {
|
c@100
|
432
|
c@100
|
433 // no cuts possible from this level
|
c@100
|
434
|
c@100
|
435 cutting->cut = Cutting::Finished;
|
c@100
|
436 cutting->first = 0;
|
c@100
|
437 cutting->second = 0;
|
c@100
|
438
|
c@100
|
439 int n = 0;
|
c@100
|
440 for (int r = res; r > s.minres; r /= 2) ++n;
|
c@100
|
441 const Spectrogram *spectrogram = s.spectrograms[n];
|
c@100
|
442
|
c@100
|
443 cutting->cost = cost(*spectrogram, x, y);
|
c@100
|
444 cutting->value = value(*spectrogram, x, y);
|
c@100
|
445
|
c@100
|
446 // cerr << "cost for this cell: " << cutting->cost << endl;
|
c@100
|
447
|
c@100
|
448 return cutting;
|
c@100
|
449 }
|
c@100
|
450 }
|
c@100
|
451
|
c@100
|
452 void
|
c@100
|
453 AdaptiveSpectrogram::assemble(const Spectrograms &s,
|
c@100
|
454 const Cutting *cutting,
|
c@100
|
455 vector<vector<float> > &rmat,
|
c@104
|
456 int x, int y, int w, int h) const
|
c@100
|
457 {
|
c@100
|
458 switch (cutting->cut) {
|
c@100
|
459
|
c@100
|
460 case Cutting::Finished:
|
c@100
|
461 for (int i = 0; i < w; ++i) {
|
c@100
|
462 for (int j = 0; j < h; ++j) {
|
c@112
|
463 rmat[x+i][y+j] = cutting->value * cutting->value;
|
c@100
|
464 }
|
c@100
|
465 }
|
c@100
|
466 return;
|
c@100
|
467
|
c@100
|
468 case Cutting::Horizontal:
|
c@100
|
469 assemble(s, cutting->first, rmat, x, y, w/2, h);
|
c@100
|
470 assemble(s, cutting->second, rmat, x+w/2, y, w/2, h);
|
c@100
|
471 break;
|
c@100
|
472
|
c@100
|
473 case Cutting::Vertical:
|
c@100
|
474 assemble(s, cutting->first, rmat, x, y+h/2, w, h/2);
|
c@100
|
475 assemble(s, cutting->second, rmat, x, y, w, h/2);
|
c@100
|
476 break;
|
c@100
|
477 }
|
c@100
|
478 }
|
c@100
|
479
|