comparison vamp-sdk/hostext/PluginInputDomainAdapter.cpp @ 64:9d3272c7db60

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