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@113
|
322 Cutting *&left, Cutting *&right,
|
c@113
|
323 BlockAllocator *allocator) const
|
c@104
|
324 {
|
c@109
|
325 if (m_threaded && !m_threadsInUse) {
|
c@104
|
326
|
c@109
|
327 m_threadsInUse = true;
|
c@104
|
328
|
c@104
|
329 if (m_cutThreads.empty()) {
|
c@104
|
330 for (int i = 0; i < 4; ++i) {
|
c@104
|
331 CutThread *t = new CutThread(this);
|
c@104
|
332 m_cutThreads.push_back(t);
|
c@104
|
333 }
|
c@104
|
334 }
|
c@104
|
335
|
c@109
|
336 // Cut threads 0 and 1 calculate the top and bottom halves;
|
c@110
|
337 // threads 2 and 3 calculate left and right. See notes in
|
c@110
|
338 // unthreaded code below for more information.
|
c@104
|
339
|
c@110
|
340 m_cutThreads[0]->cut(s, res, x, y + h/2, h/2); // top
|
c@110
|
341 m_cutThreads[1]->cut(s, res, x, y, h/2); // bottom
|
c@110
|
342 m_cutThreads[2]->cut(s, res/2, 2 * x, y/2, h/2); // left
|
c@110
|
343 m_cutThreads[3]->cut(s, res/2, 2 * x + 1, y/2, h/2); // right
|
c@104
|
344
|
c@104
|
345 top = m_cutThreads[0]->get();
|
c@104
|
346 bottom = m_cutThreads[1]->get();
|
c@104
|
347 left = m_cutThreads[2]->get();
|
c@104
|
348 right = m_cutThreads[3]->get();
|
c@104
|
349
|
c@104
|
350 } else {
|
c@104
|
351
|
c@110
|
352 // Unthreaded version
|
c@104
|
353
|
c@104
|
354 // The "vertical" division is a top/bottom split.
|
c@104
|
355 // Splitting this way keeps us in the same resolution,
|
c@104
|
356 // but with two vertical subregions of height h/2.
|
c@104
|
357
|
c@113
|
358 top = cut(s, res, x, y + h/2, h/2, allocator);
|
c@113
|
359 bottom = cut(s, res, x, y, h/2, allocator);
|
c@104
|
360
|
c@104
|
361 // The "horizontal" division is a left/right split. Splitting
|
c@104
|
362 // this way places us in resolution res/2, which has lower
|
c@104
|
363 // vertical resolution but higher horizontal resolution. We
|
c@104
|
364 // need to double x accordingly.
|
c@104
|
365
|
c@113
|
366 left = cut(s, res/2, 2 * x, y/2, h/2, allocator);
|
c@113
|
367 right = cut(s, res/2, 2 * x + 1, y/2, h/2, allocator);
|
c@104
|
368 }
|
c@104
|
369 }
|
c@104
|
370
|
c@100
|
371 AdaptiveSpectrogram::Cutting *
|
c@100
|
372 AdaptiveSpectrogram::cut(const Spectrograms &s,
|
c@100
|
373 int res,
|
c@110
|
374 int x, int y, int h,
|
c@110
|
375 BlockAllocator *allocator) const
|
c@100
|
376 {
|
c@100
|
377 // cerr << "res = " << res << ", x = " << x << ", y = " << y << ", h = " << h << endl;
|
c@100
|
378
|
c@110
|
379 Cutting *cutting;
|
c@110
|
380 if (allocator) {
|
c@110
|
381 cutting = (Cutting *)(allocator->allocate());
|
c@110
|
382 cutting->allocator = allocator;
|
c@110
|
383 } else {
|
c@110
|
384 cutting = new Cutting;
|
c@110
|
385 cutting->allocator = 0;
|
c@110
|
386 }
|
c@110
|
387
|
c@100
|
388 if (h > 1 && res > s.minres) {
|
c@100
|
389
|
c@104
|
390 Cutting *top = 0, *bottom = 0, *left = 0, *right = 0;
|
c@113
|
391 getSubCuts(s, res, x, y, h, top, bottom, left, right, allocator);
|
c@100
|
392
|
c@100
|
393 double vcost = top->cost + bottom->cost;
|
c@100
|
394 double hcost = left->cost + right->cost;
|
c@100
|
395
|
c@101
|
396 bool normalize = true;
|
c@101
|
397
|
c@101
|
398 if (normalize) {
|
c@101
|
399
|
c@101
|
400 double venergy = top->value + bottom->value;
|
c@101
|
401 vcost = (vcost + (venergy * log(venergy))) / venergy;
|
c@101
|
402
|
c@101
|
403 double henergy = left->value + right->value;
|
c@101
|
404 hcost = (hcost + (henergy * log(henergy))) / henergy;
|
c@101
|
405 }
|
c@101
|
406
|
c@100
|
407 if (vcost > hcost) {
|
c@100
|
408
|
c@100
|
409 // cut horizontally (left/right)
|
c@100
|
410 cutting->cut = Cutting::Horizontal;
|
c@100
|
411 cutting->first = left;
|
c@100
|
412 cutting->second = right;
|
c@100
|
413 cutting->cost = hcost;
|
c@111
|
414 cutting->value = left->value + right->value;
|
c@110
|
415 top->erase();
|
c@110
|
416 bottom->erase();
|
c@100
|
417 return cutting;
|
c@100
|
418
|
c@100
|
419 } else {
|
c@100
|
420
|
c@110
|
421 // cut vertically (top/bottom)
|
c@100
|
422 cutting->cut = Cutting::Vertical;
|
c@100
|
423 cutting->first = top;
|
c@100
|
424 cutting->second = bottom;
|
c@100
|
425 cutting->cost = vcost;
|
c@111
|
426 cutting->value = top->value + bottom->value;
|
c@110
|
427 left->erase();
|
c@110
|
428 right->erase();
|
c@100
|
429 return cutting;
|
c@100
|
430 }
|
c@100
|
431
|
c@100
|
432 } else {
|
c@100
|
433
|
c@100
|
434 // no cuts possible from this level
|
c@100
|
435
|
c@100
|
436 cutting->cut = Cutting::Finished;
|
c@100
|
437 cutting->first = 0;
|
c@100
|
438 cutting->second = 0;
|
c@100
|
439
|
c@100
|
440 int n = 0;
|
c@100
|
441 for (int r = res; r > s.minres; r /= 2) ++n;
|
c@100
|
442 const Spectrogram *spectrogram = s.spectrograms[n];
|
c@100
|
443
|
c@100
|
444 cutting->cost = cost(*spectrogram, x, y);
|
c@100
|
445 cutting->value = value(*spectrogram, x, y);
|
c@100
|
446
|
c@100
|
447 // cerr << "cost for this cell: " << cutting->cost << endl;
|
c@100
|
448
|
c@100
|
449 return cutting;
|
c@100
|
450 }
|
c@100
|
451 }
|
c@100
|
452
|
c@100
|
453 void
|
c@100
|
454 AdaptiveSpectrogram::assemble(const Spectrograms &s,
|
c@100
|
455 const Cutting *cutting,
|
c@100
|
456 vector<vector<float> > &rmat,
|
c@104
|
457 int x, int y, int w, int h) const
|
c@100
|
458 {
|
c@100
|
459 switch (cutting->cut) {
|
c@100
|
460
|
c@100
|
461 case Cutting::Finished:
|
c@100
|
462 for (int i = 0; i < w; ++i) {
|
c@100
|
463 for (int j = 0; j < h; ++j) {
|
c@112
|
464 rmat[x+i][y+j] = cutting->value * cutting->value;
|
c@100
|
465 }
|
c@100
|
466 }
|
c@100
|
467 return;
|
c@100
|
468
|
c@100
|
469 case Cutting::Horizontal:
|
c@100
|
470 assemble(s, cutting->first, rmat, x, y, w/2, h);
|
c@100
|
471 assemble(s, cutting->second, rmat, x+w/2, y, w/2, h);
|
c@100
|
472 break;
|
c@100
|
473
|
c@100
|
474 case Cutting::Vertical:
|
c@100
|
475 assemble(s, cutting->first, rmat, x, y+h/2, w, h/2);
|
c@100
|
476 assemble(s, cutting->second, rmat, x, y, w, h/2);
|
c@100
|
477 break;
|
c@100
|
478 }
|
c@100
|
479 }
|
c@100
|
480
|