cannam@56
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
cannam@56
|
2
|
cannam@56
|
3 /*
|
cannam@56
|
4 Vamp
|
cannam@56
|
5
|
cannam@56
|
6 An API for audio analysis and feature extraction plugins.
|
cannam@56
|
7
|
cannam@56
|
8 Centre for Digital Music, Queen Mary, University of London.
|
cannam@56
|
9 Copyright 2006 Chris Cannam.
|
cannam@56
|
10
|
cannam@56
|
11 Permission is hereby granted, free of charge, to any person
|
cannam@56
|
12 obtaining a copy of this software and associated documentation
|
cannam@56
|
13 files (the "Software"), to deal in the Software without
|
cannam@56
|
14 restriction, including without limitation the rights to use, copy,
|
cannam@56
|
15 modify, merge, publish, distribute, sublicense, and/or sell copies
|
cannam@56
|
16 of the Software, and to permit persons to whom the Software is
|
cannam@56
|
17 furnished to do so, subject to the following conditions:
|
cannam@56
|
18
|
cannam@56
|
19 The above copyright notice and this permission notice shall be
|
cannam@56
|
20 included in all copies or substantial portions of the Software.
|
cannam@56
|
21
|
cannam@56
|
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
cannam@56
|
23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
cannam@56
|
24 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
cannam@56
|
25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
cannam@56
|
26 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
cannam@56
|
27 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
cannam@56
|
28 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
cannam@56
|
29
|
cannam@56
|
30 Except as contained in this notice, the names of the Centre for
|
cannam@56
|
31 Digital Music; Queen Mary, University of London; and Chris Cannam
|
cannam@56
|
32 shall not be used in advertising or otherwise to promote the sale,
|
cannam@56
|
33 use or other dealings in this Software without prior written
|
cannam@56
|
34 authorization.
|
cannam@56
|
35 */
|
cannam@56
|
36
|
cannam@56
|
37 #include "PluginInputDomainAdapter.h"
|
cannam@56
|
38
|
cannam@56
|
39 #include <cmath>
|
cannam@56
|
40
|
cannam@56
|
41 namespace Vamp {
|
cannam@56
|
42
|
cannam@59
|
43 namespace HostExt {
|
cannam@59
|
44
|
cannam@56
|
45 PluginInputDomainAdapter::PluginInputDomainAdapter(Plugin *plugin) :
|
cannam@57
|
46 PluginWrapper(plugin),
|
cannam@56
|
47 m_channels(0),
|
cannam@56
|
48 m_blockSize(0),
|
cannam@56
|
49 m_freqbuf(0)
|
cannam@56
|
50 {
|
cannam@56
|
51 }
|
cannam@56
|
52
|
cannam@56
|
53 PluginInputDomainAdapter::~PluginInputDomainAdapter()
|
cannam@56
|
54 {
|
cannam@56
|
55 }
|
cannam@56
|
56
|
cannam@56
|
57 bool
|
cannam@56
|
58 PluginInputDomainAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
cannam@56
|
59 {
|
cannam@61
|
60 if (m_plugin->getInputDomain() == TimeDomain) {
|
cannam@56
|
61
|
cannam@61
|
62 m_blockSize = blockSize;
|
cannam@61
|
63 m_channels = channels;
|
cannam@61
|
64
|
cannam@61
|
65 return m_plugin->initialise(channels, stepSize, blockSize);
|
cannam@56
|
66 }
|
cannam@56
|
67
|
cannam@61
|
68 if (blockSize < 2) {
|
cannam@61
|
69 std::cerr << "ERROR: Vamp::HostExt::PluginInputDomainAdapter::initialise: blocksize < 2 not supported" << std::endl;
|
cannam@61
|
70 return false;
|
cannam@61
|
71 }
|
cannam@61
|
72
|
cannam@61
|
73 if (blockSize & (blockSize-1)) {
|
cannam@61
|
74 std::cerr << "ERROR: Vamp::HostExt::PluginInputDomainAdapter::initialise: non-power-of-two\nblocksize " << blockSize << " not supported" << std::endl;
|
cannam@61
|
75 return false;
|
cannam@61
|
76 }
|
cannam@61
|
77
|
cannam@61
|
78 if (m_channels > 0) {
|
cannam@61
|
79 for (size_t c = 0; c < m_channels; ++c) {
|
cannam@61
|
80 delete[] m_freqbuf[c];
|
cannam@61
|
81 }
|
cannam@61
|
82 delete[] m_freqbuf;
|
cannam@61
|
83 delete[] m_ri;
|
cannam@61
|
84 delete[] m_ro;
|
cannam@61
|
85 delete[] m_io;
|
cannam@61
|
86 }
|
cannam@61
|
87
|
cannam@61
|
88 m_blockSize = blockSize;
|
cannam@56
|
89 m_channels = channels;
|
cannam@56
|
90
|
cannam@61
|
91 m_freqbuf = new float *[m_channels];
|
cannam@61
|
92 for (size_t c = 0; c < m_channels; ++c) {
|
cannam@61
|
93 m_freqbuf[c] = new float[m_blockSize + 2];
|
cannam@56
|
94 }
|
cannam@61
|
95 m_ri = new double[m_blockSize];
|
cannam@61
|
96 m_ro = new double[m_blockSize];
|
cannam@61
|
97 m_io = new double[m_blockSize];
|
cannam@56
|
98
|
cannam@56
|
99 return m_plugin->initialise(channels, stepSize, blockSize);
|
cannam@56
|
100 }
|
cannam@56
|
101
|
cannam@57
|
102 Plugin::InputDomain
|
cannam@57
|
103 PluginInputDomainAdapter::getInputDomain() const
|
cannam@56
|
104 {
|
cannam@57
|
105 return TimeDomain;
|
cannam@56
|
106 }
|
cannam@56
|
107
|
cannam@56
|
108 size_t
|
cannam@56
|
109 PluginInputDomainAdapter::getPreferredStepSize() const
|
cannam@56
|
110 {
|
cannam@56
|
111 size_t step = m_plugin->getPreferredStepSize();
|
cannam@56
|
112
|
cannam@56
|
113 if (step == 0 && (m_plugin->getInputDomain() == FrequencyDomain)) {
|
cannam@56
|
114 step = getPreferredBlockSize() / 2;
|
cannam@56
|
115 }
|
cannam@56
|
116
|
cannam@56
|
117 return step;
|
cannam@56
|
118 }
|
cannam@56
|
119
|
cannam@56
|
120 size_t
|
cannam@56
|
121 PluginInputDomainAdapter::getPreferredBlockSize() const
|
cannam@56
|
122 {
|
cannam@56
|
123 size_t block = m_plugin->getPreferredBlockSize();
|
cannam@56
|
124
|
cannam@61
|
125 if (m_plugin->getInputDomain() == FrequencyDomain) {
|
cannam@61
|
126 if (block == 0) {
|
cannam@61
|
127 block = 1024;
|
cannam@61
|
128 } else {
|
cannam@61
|
129 block = makeBlockSizeAcceptable(block);
|
cannam@61
|
130 }
|
cannam@56
|
131 }
|
cannam@56
|
132
|
cannam@56
|
133 return block;
|
cannam@56
|
134 }
|
cannam@56
|
135
|
cannam@61
|
136 size_t
|
cannam@61
|
137 PluginInputDomainAdapter::makeBlockSizeAcceptable(size_t blockSize) const
|
cannam@61
|
138 {
|
cannam@61
|
139 if (blockSize < 2) {
|
cannam@61
|
140
|
cannam@61
|
141 std::cerr << "WARNING: Vamp::HostExt::PluginInputDomainAdapter::initialise: blocksize < 2 not" << std::endl
|
cannam@61
|
142 << "supported, increasing from " << blockSize << " to 2" << std::endl;
|
cannam@61
|
143 blockSize = 2;
|
cannam@61
|
144
|
cannam@61
|
145 } else if (blockSize & (blockSize-1)) {
|
cannam@61
|
146
|
cannam@61
|
147 // not a power of two, can't handle that with our current fft
|
cannam@61
|
148 // implementation
|
cannam@61
|
149
|
cannam@61
|
150 size_t nearest = blockSize;
|
cannam@61
|
151 size_t power = 0;
|
cannam@61
|
152 while (nearest > 1) {
|
cannam@61
|
153 nearest >>= 1;
|
cannam@61
|
154 ++power;
|
cannam@61
|
155 }
|
cannam@61
|
156 nearest = 1;
|
cannam@61
|
157 while (power) {
|
cannam@61
|
158 nearest <<= 1;
|
cannam@61
|
159 --power;
|
cannam@61
|
160 }
|
cannam@61
|
161
|
cannam@61
|
162 if (blockSize - nearest > (nearest*2) - blockSize) {
|
cannam@61
|
163 nearest = nearest*2;
|
cannam@61
|
164 }
|
cannam@61
|
165
|
cannam@61
|
166 std::cerr << "WARNING: Vamp::HostExt::PluginInputDomainAdapter::initialise: non-power-of-two\nblocksize " << blockSize << " not supported, using blocksize " << nearest << " instead" << std::endl;
|
cannam@61
|
167 blockSize = nearest;
|
cannam@61
|
168 }
|
cannam@61
|
169
|
cannam@61
|
170 return blockSize;
|
cannam@61
|
171 }
|
cannam@61
|
172
|
cannam@56
|
173 Plugin::FeatureSet
|
cannam@56
|
174 PluginInputDomainAdapter::process(const float *const *inputBuffers, RealTime timestamp)
|
cannam@56
|
175 {
|
cannam@56
|
176 if (m_plugin->getInputDomain() == TimeDomain) {
|
cannam@56
|
177 return m_plugin->process(inputBuffers, timestamp);
|
cannam@56
|
178 }
|
cannam@56
|
179
|
cannam@61
|
180 // The timestamp supplied should be (according to the Vamp::Plugin
|
cannam@61
|
181 // spec) the time of the start of the time-domain input block.
|
cannam@61
|
182 // However, we want to pass to the plugin an FFT output calculated
|
cannam@61
|
183 // from the block of samples _centred_ on that timestamp.
|
cannam@61
|
184 //
|
cannam@61
|
185 // We have two options:
|
cannam@61
|
186 //
|
cannam@61
|
187 // 1. Buffer the input, calculating the fft of the values at the
|
cannam@61
|
188 // passed-in block minus blockSize/2 rather than starting at the
|
cannam@61
|
189 // passed-in block. So each time we call process on the plugin,
|
cannam@61
|
190 // we are passing in the same timestamp as was passed to our own
|
cannam@61
|
191 // process plugin, but not (the frequency domain representation
|
cannam@61
|
192 // of) the same set of samples. Advantages: avoids confusion in
|
cannam@61
|
193 // the host by ensuring the returned values have timestamps
|
cannam@61
|
194 // comparable with that passed in to this function (in fact this
|
cannam@61
|
195 // is pretty much essential for one-value-per-block outputs);
|
cannam@61
|
196 // consistent with hosts such as SV that deal with the
|
cannam@61
|
197 // frequency-domain transform themselves. Disadvantages: means
|
cannam@61
|
198 // making the not necessarily correct assumption that the samples
|
cannam@61
|
199 // preceding the first official block are all zero (or some other
|
cannam@61
|
200 // known value).
|
cannam@61
|
201 //
|
cannam@61
|
202 // 2. Increase the passed-in timestamps by half the blocksize. So
|
cannam@61
|
203 // when we call process, we are passing in the frequency domain
|
cannam@61
|
204 // representation of the same set of samples as passed to us, but
|
cannam@61
|
205 // with a different timestamp. Advantages: simplicity; avoids
|
cannam@61
|
206 // iffy assumption mentioned above. Disadvantages: inconsistency
|
cannam@61
|
207 // with SV in cases where stepSize != blockSize/2; potential
|
cannam@61
|
208 // confusion arising from returned timestamps being calculated
|
cannam@61
|
209 // from the adjusted input timestamps rather than the original
|
cannam@61
|
210 // ones (and inaccuracy where the returned timestamp is implied,
|
cannam@61
|
211 // as in one-value-per-block).
|
cannam@61
|
212 //
|
cannam@61
|
213 // Neither way is ideal, but I don't think either is strictly
|
cannam@61
|
214 // incorrect either. I think this is just a case where the same
|
cannam@61
|
215 // plugin can legitimately produce differing results from the same
|
cannam@61
|
216 // input data, depending on how that data is packaged.
|
cannam@61
|
217 //
|
cannam@61
|
218 // We'll go for option 2, adjusting the timestamps. Note in
|
cannam@61
|
219 // particular that this means some results can differ from those
|
cannam@61
|
220 // produced by SV.
|
cannam@61
|
221
|
cannam@61
|
222 std::cerr << "PluginInputDomainAdapter: sampleRate " << m_inputSampleRate << ", blocksize " << m_blockSize << ", adjusting time from " << timestamp;
|
cannam@61
|
223
|
cannam@61
|
224 timestamp = timestamp + RealTime::frame2RealTime(m_blockSize/2,
|
cannam@61
|
225 m_inputSampleRate);
|
cannam@61
|
226
|
cannam@61
|
227 std::cerr << " to " << timestamp << std::endl;
|
cannam@60
|
228
|
cannam@56
|
229 for (size_t c = 0; c < m_channels; ++c) {
|
cannam@56
|
230
|
cannam@56
|
231 for (size_t i = 0; i < m_blockSize; ++i) {
|
cannam@56
|
232 // Hanning window
|
cannam@56
|
233 m_ri[i] = double(inputBuffers[c][i])
|
cannam@56
|
234 * (0.50 - 0.50 * cos((2 * M_PI * i)
|
cannam@56
|
235 / m_blockSize));
|
cannam@56
|
236 }
|
cannam@56
|
237
|
cannam@56
|
238 for (size_t i = 0; i < m_blockSize/2; ++i) {
|
cannam@56
|
239 // FFT shift
|
cannam@56
|
240 double value = m_ri[i];
|
cannam@56
|
241 m_ri[i] = m_ri[i + m_blockSize/2];
|
cannam@56
|
242 m_ri[i + m_blockSize/2] = value;
|
cannam@56
|
243 }
|
cannam@56
|
244
|
cannam@56
|
245 fft(m_blockSize, false, m_ri, 0, m_ro, m_io);
|
cannam@56
|
246
|
cannam@61
|
247 for (size_t i = 0; i <= m_blockSize/2; ++i) {
|
cannam@56
|
248 m_freqbuf[c][i * 2] = m_ro[i];
|
cannam@56
|
249 m_freqbuf[c][i * 2 + 1] = m_io[i];
|
cannam@56
|
250 }
|
cannam@56
|
251 }
|
cannam@56
|
252
|
cannam@56
|
253 return m_plugin->process(m_freqbuf, timestamp);
|
cannam@56
|
254 }
|
cannam@56
|
255
|
cannam@56
|
256 void
|
cannam@56
|
257 PluginInputDomainAdapter::fft(unsigned int n, bool inverse,
|
cannam@56
|
258 double *ri, double *ii, double *ro, double *io)
|
cannam@56
|
259 {
|
cannam@56
|
260 if (!ri || !ro || !io) return;
|
cannam@56
|
261
|
cannam@56
|
262 unsigned int bits;
|
cannam@56
|
263 unsigned int i, j, k, m;
|
cannam@56
|
264 unsigned int blockSize, blockEnd;
|
cannam@56
|
265
|
cannam@56
|
266 double tr, ti;
|
cannam@56
|
267
|
cannam@56
|
268 if (n < 2) return;
|
cannam@56
|
269 if (n & (n-1)) return;
|
cannam@56
|
270
|
cannam@56
|
271 double angle = 2.0 * M_PI;
|
cannam@56
|
272 if (inverse) angle = -angle;
|
cannam@56
|
273
|
cannam@56
|
274 for (i = 0; ; ++i) {
|
cannam@56
|
275 if (n & (1 << i)) {
|
cannam@56
|
276 bits = i;
|
cannam@56
|
277 break;
|
cannam@56
|
278 }
|
cannam@56
|
279 }
|
cannam@56
|
280
|
cannam@56
|
281 static unsigned int tableSize = 0;
|
cannam@56
|
282 static int *table = 0;
|
cannam@56
|
283
|
cannam@56
|
284 if (tableSize != n) {
|
cannam@56
|
285
|
cannam@56
|
286 delete[] table;
|
cannam@56
|
287
|
cannam@56
|
288 table = new int[n];
|
cannam@56
|
289
|
cannam@56
|
290 for (i = 0; i < n; ++i) {
|
cannam@56
|
291
|
cannam@56
|
292 m = i;
|
cannam@56
|
293
|
cannam@56
|
294 for (j = k = 0; j < bits; ++j) {
|
cannam@56
|
295 k = (k << 1) | (m & 1);
|
cannam@56
|
296 m >>= 1;
|
cannam@56
|
297 }
|
cannam@56
|
298
|
cannam@56
|
299 table[i] = k;
|
cannam@56
|
300 }
|
cannam@56
|
301
|
cannam@56
|
302 tableSize = n;
|
cannam@56
|
303 }
|
cannam@56
|
304
|
cannam@56
|
305 if (ii) {
|
cannam@56
|
306 for (i = 0; i < n; ++i) {
|
cannam@56
|
307 ro[table[i]] = ri[i];
|
cannam@56
|
308 io[table[i]] = ii[i];
|
cannam@56
|
309 }
|
cannam@56
|
310 } else {
|
cannam@56
|
311 for (i = 0; i < n; ++i) {
|
cannam@56
|
312 ro[table[i]] = ri[i];
|
cannam@56
|
313 io[table[i]] = 0.0;
|
cannam@56
|
314 }
|
cannam@56
|
315 }
|
cannam@56
|
316
|
cannam@56
|
317 blockEnd = 1;
|
cannam@56
|
318
|
cannam@56
|
319 for (blockSize = 2; blockSize <= n; blockSize <<= 1) {
|
cannam@56
|
320
|
cannam@56
|
321 double delta = angle / (double)blockSize;
|
cannam@56
|
322 double sm2 = -sin(-2 * delta);
|
cannam@56
|
323 double sm1 = -sin(-delta);
|
cannam@56
|
324 double cm2 = cos(-2 * delta);
|
cannam@56
|
325 double cm1 = cos(-delta);
|
cannam@56
|
326 double w = 2 * cm1;
|
cannam@56
|
327 double ar[3], ai[3];
|
cannam@56
|
328
|
cannam@56
|
329 for (i = 0; i < n; i += blockSize) {
|
cannam@56
|
330
|
cannam@56
|
331 ar[2] = cm2;
|
cannam@56
|
332 ar[1] = cm1;
|
cannam@56
|
333
|
cannam@56
|
334 ai[2] = sm2;
|
cannam@56
|
335 ai[1] = sm1;
|
cannam@56
|
336
|
cannam@56
|
337 for (j = i, m = 0; m < blockEnd; j++, m++) {
|
cannam@56
|
338
|
cannam@56
|
339 ar[0] = w * ar[1] - ar[2];
|
cannam@56
|
340 ar[2] = ar[1];
|
cannam@56
|
341 ar[1] = ar[0];
|
cannam@56
|
342
|
cannam@56
|
343 ai[0] = w * ai[1] - ai[2];
|
cannam@56
|
344 ai[2] = ai[1];
|
cannam@56
|
345 ai[1] = ai[0];
|
cannam@56
|
346
|
cannam@56
|
347 k = j + blockEnd;
|
cannam@56
|
348 tr = ar[0] * ro[k] - ai[0] * io[k];
|
cannam@56
|
349 ti = ar[0] * io[k] + ai[0] * ro[k];
|
cannam@56
|
350
|
cannam@56
|
351 ro[k] = ro[j] - tr;
|
cannam@56
|
352 io[k] = io[j] - ti;
|
cannam@56
|
353
|
cannam@56
|
354 ro[j] += tr;
|
cannam@56
|
355 io[j] += ti;
|
cannam@56
|
356 }
|
cannam@56
|
357 }
|
cannam@56
|
358
|
cannam@56
|
359 blockEnd = blockSize;
|
cannam@56
|
360 }
|
cannam@56
|
361
|
cannam@56
|
362 if (inverse) {
|
cannam@56
|
363
|
cannam@56
|
364 double denom = (double)n;
|
cannam@56
|
365
|
cannam@56
|
366 for (i = 0; i < n; i++) {
|
cannam@56
|
367 ro[i] /= denom;
|
cannam@56
|
368 io[i] /= denom;
|
cannam@56
|
369 }
|
cannam@56
|
370 }
|
cannam@56
|
371 }
|
cannam@56
|
372
|
cannam@59
|
373 }
|
cannam@56
|
374
|
cannam@56
|
375 }
|
cannam@56
|
376
|