1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
6 An API for audio analysis and feature extraction plugins.
8 Centre for Digital Music, Queen Mary, University of London.
9 Copyright 2006-2007 Chris Cannam and QMUL.
11 This file is based in part on Don Cross's public domain FFT
14 Permission is hereby granted, free of charge, to any person
15 obtaining a copy of this software and associated documentation
16 files (the "Software"), to deal in the Software without
17 restriction, including without limitation the rights to use, copy,
18 modify, merge, publish, distribute, sublicense, and/or sell copies
19 of the Software, and to permit persons to whom the Software is
20 furnished to do so, subject to the following conditions:
22 The above copyright notice and this permission notice shall be
23 included in all copies or substantial portions of the Software.
25 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
29 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
30 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 Except as contained in this notice, the names of the Centre for
34 Digital Music; Queen Mary, University of London; and Chris Cannam
35 shall not be used in advertising or otherwise to promote the sale,
36 use or other dealings in this Software without prior written
40 #include <vamp-hostsdk/PluginInputDomainAdapter.h>
46 * If you want to compile using FFTW instead of the built-in FFT
47 * implementation for the PluginInputDomainAdapter, define HAVE_FFTW3
50 * Be aware that FFTW is licensed under the GPL -- unlike this SDK,
51 * which is provided under a more liberal BSD license in order to
52 * permit use in closed source applications. The use of FFTW would
53 * mean that your code would need to be licensed under the GPL as
54 * well. Do not define this symbol unless you understand and accept
55 * the implications of this.
57 * Parties such as Linux distribution packagers who redistribute this
58 * SDK for use in other programs should _not_ define this symbol, as
59 * it would change the effective licensing terms under which the SDK
60 * was available to third party developers.
62 * The default is not to use FFTW, and to use the built-in FFT instead.
64 * Note: The FFTW code uses FFTW_MEASURE, and so will perform badly on
65 * its first invocation unless the host has saved and restored FFTW
66 * wisdom (see the FFTW documentation).
73 _VAMP_SDK_HOSTSPACE_BEGIN(PluginInputDomainAdapter.cpp)
79 class PluginInputDomainAdapter::Impl
82 Impl(Plugin *plugin, float inputSampleRate);
85 bool initialise(size_t channels, size_t stepSize, size_t blockSize);
87 size_t getPreferredStepSize() const;
88 size_t getPreferredBlockSize() const;
90 FeatureSet process(const float *const *inputBuffers, RealTime timestamp);
92 RealTime getTimestampAdjustment() const;
96 float m_inputSampleRate;
106 fftw_complex *m_cbuf;
110 void fft(unsigned int n, bool inverse,
111 double *ri, double *ii, double *ro, double *io);
114 size_t makeBlockSizeAcceptable(size_t) const;
117 PluginInputDomainAdapter::PluginInputDomainAdapter(Plugin *plugin) :
118 PluginWrapper(plugin)
120 m_impl = new Impl(plugin, m_inputSampleRate);
123 PluginInputDomainAdapter::~PluginInputDomainAdapter()
129 PluginInputDomainAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize)
131 return m_impl->initialise(channels, stepSize, blockSize);
135 PluginInputDomainAdapter::getInputDomain() const
141 PluginInputDomainAdapter::getPreferredStepSize() const
143 return m_impl->getPreferredStepSize();
147 PluginInputDomainAdapter::getPreferredBlockSize() const
149 return m_impl->getPreferredBlockSize();
153 PluginInputDomainAdapter::process(const float *const *inputBuffers, RealTime timestamp)
155 return m_impl->process(inputBuffers, timestamp);
159 PluginInputDomainAdapter::getTimestampAdjustment() const
161 return m_impl->getTimestampAdjustment();
165 PluginInputDomainAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) :
167 m_inputSampleRate(inputSampleRate),
183 PluginInputDomainAdapter::Impl::~Impl()
185 // the adapter will delete the plugin
187 if (m_channels > 0) {
188 for (int c = 0; c < m_channels; ++c) {
189 delete[] m_freqbuf[c];
194 fftw_destroy_plan(m_plan);
208 // for some visual studii apparently
210 #define M_PI 3.14159265358979232846
214 PluginInputDomainAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t blockSize)
216 if (m_plugin->getInputDomain() == TimeDomain) {
218 m_blockSize = int(blockSize);
219 m_channels = int(channels);
221 return m_plugin->initialise(channels, stepSize, blockSize);
225 std::cerr << "ERROR: Vamp::HostExt::PluginInputDomainAdapter::Impl::initialise: blocksize < 2 not supported" << std::endl;
229 if (blockSize & (blockSize-1)) {
230 std::cerr << "ERROR: Vamp::HostExt::PluginInputDomainAdapter::Impl::initialise: non-power-of-two\nblocksize " << blockSize << " not supported" << std::endl;
234 if (m_channels > 0) {
235 for (int c = 0; c < m_channels; ++c) {
236 delete[] m_freqbuf[c];
241 fftw_destroy_plan(m_plan);
254 m_blockSize = int(blockSize);
255 m_channels = int(channels);
257 m_freqbuf = new float *[m_channels];
258 for (int c = 0; c < m_channels; ++c) {
259 m_freqbuf[c] = new float[m_blockSize + 2];
261 m_window = new double[m_blockSize];
263 for (int i = 0; i < m_blockSize; ++i) {
265 m_window[i] = (0.50 - 0.50 * cos((2.0 * M_PI * i) / m_blockSize));
269 m_ri = (double *)fftw_malloc(blockSize * sizeof(double));
270 m_cbuf = (fftw_complex *)fftw_malloc((blockSize/2 + 1) * sizeof(fftw_complex));
271 m_plan = fftw_plan_dft_r2c_1d(blockSize, m_ri, m_cbuf, FFTW_MEASURE);
273 m_ri = new double[m_blockSize];
274 m_ro = new double[m_blockSize];
275 m_io = new double[m_blockSize];
278 return m_plugin->initialise(channels, stepSize, blockSize);
282 PluginInputDomainAdapter::Impl::getPreferredStepSize() const
284 size_t step = m_plugin->getPreferredStepSize();
286 if (step == 0 && (m_plugin->getInputDomain() == FrequencyDomain)) {
287 step = getPreferredBlockSize() / 2;
294 PluginInputDomainAdapter::Impl::getPreferredBlockSize() const
296 size_t block = m_plugin->getPreferredBlockSize();
298 if (m_plugin->getInputDomain() == FrequencyDomain) {
302 block = makeBlockSizeAcceptable(block);
310 PluginInputDomainAdapter::Impl::makeBlockSizeAcceptable(size_t blockSize) const
314 std::cerr << "WARNING: Vamp::HostExt::PluginInputDomainAdapter::Impl::initialise: blocksize < 2 not" << std::endl
315 << "supported, increasing from " << blockSize << " to 2" << std::endl;
318 } else if (blockSize & (blockSize-1)) {
321 // not an issue with FFTW
324 // not a power of two, can't handle that with our built-in FFT
327 size_t nearest = blockSize;
329 while (nearest > 1) {
339 if (blockSize - nearest > (nearest*2) - blockSize) {
343 std::cerr << "WARNING: Vamp::HostExt::PluginInputDomainAdapter::Impl::initialise: non-power-of-two\nblocksize " << blockSize << " not supported, using blocksize " << nearest << " instead" << std::endl;
353 PluginInputDomainAdapter::Impl::getTimestampAdjustment() const
355 if (m_plugin->getInputDomain() == TimeDomain) {
356 return RealTime::zeroTime;
358 return RealTime::frame2RealTime
359 (m_blockSize/2, int(m_inputSampleRate + 0.5));
364 PluginInputDomainAdapter::Impl::process(const float *const *inputBuffers,
367 if (m_plugin->getInputDomain() == TimeDomain) {
368 return m_plugin->process(inputBuffers, timestamp);
371 // The timestamp supplied should be (according to the Vamp::Plugin
372 // spec) the time of the start of the time-domain input block.
373 // However, we want to pass to the plugin an FFT output calculated
374 // from the block of samples _centred_ on that timestamp.
376 // We have two options:
378 // 1. Buffer the input, calculating the fft of the values at the
379 // passed-in block minus blockSize/2 rather than starting at the
380 // passed-in block. So each time we call process on the plugin,
381 // we are passing in the same timestamp as was passed to our own
382 // process plugin, but not (the frequency domain representation
383 // of) the same set of samples. Advantages: avoids confusion in
384 // the host by ensuring the returned values have timestamps
385 // comparable with that passed in to this function (in fact this
386 // is pretty much essential for one-value-per-block outputs);
387 // consistent with hosts such as SV that deal with the
388 // frequency-domain transform themselves. Disadvantages: means
389 // making the not necessarily correct assumption that the samples
390 // preceding the first official block are all zero (or some other
393 // 2. Increase the passed-in timestamps by half the blocksize. So
394 // when we call process, we are passing in the frequency domain
395 // representation of the same set of samples as passed to us, but
396 // with a different timestamp. Advantages: simplicity; avoids
397 // iffy assumption mentioned above. Disadvantages: inconsistency
398 // with SV in cases where stepSize != blockSize/2; potential
399 // confusion arising from returned timestamps being calculated
400 // from the adjusted input timestamps rather than the original
401 // ones (and inaccuracy where the returned timestamp is implied,
402 // as in one-value-per-block).
404 // Neither way is ideal, but I don't think either is strictly
405 // incorrect either. I think this is just a case where the same
406 // plugin can legitimately produce differing results from the same
407 // input data, depending on how that data is packaged.
409 // We'll go for option 2, adjusting the timestamps. Note in
410 // particular that this means some results can differ from those
413 // std::cerr << "PluginInputDomainAdapter: sampleRate " << m_inputSampleRate << ", blocksize " << m_blockSize << ", adjusting time from " << timestamp;
415 timestamp = timestamp + getTimestampAdjustment();
417 // std::cerr << " to " << timestamp << std::endl;
419 for (int c = 0; c < m_channels; ++c) {
421 for (int i = 0; i < m_blockSize; ++i) {
422 m_ri[i] = double(inputBuffers[c][i]) * m_window[i];
425 for (int i = 0; i < m_blockSize/2; ++i) {
427 double value = m_ri[i];
428 m_ri[i] = m_ri[i + m_blockSize/2];
429 m_ri[i + m_blockSize/2] = value;
434 fftw_execute(m_plan);
436 for (int i = 0; i <= m_blockSize/2; ++i) {
437 m_freqbuf[c][i * 2] = float(m_cbuf[i][0]);
438 m_freqbuf[c][i * 2 + 1] = float(m_cbuf[i][1]);
443 fft(m_blockSize, false, m_ri, 0, m_ro, m_io);
445 for (int i = 0; i <= m_blockSize/2; ++i) {
446 m_freqbuf[c][i * 2] = float(m_ro[i]);
447 m_freqbuf[c][i * 2 + 1] = float(m_io[i]);
453 return m_plugin->process(m_freqbuf, timestamp);
459 PluginInputDomainAdapter::Impl::fft(unsigned int n, bool inverse,
460 double *ri, double *ii, double *ro, double *io)
462 if (!ri || !ro || !io) return;
465 unsigned int i, j, k, m;
466 unsigned int blockSize, blockEnd;
471 if (n & (n-1)) return;
473 double angle = 2.0 * M_PI;
474 if (inverse) angle = -angle;
483 static unsigned int tableSize = 0;
484 static int *table = 0;
486 if (tableSize != n) {
492 for (i = 0; i < n; ++i) {
496 for (j = k = 0; j < bits; ++j) {
497 k = (k << 1) | (m & 1);
508 for (i = 0; i < n; ++i) {
509 ro[table[i]] = ri[i];
510 io[table[i]] = ii[i];
513 for (i = 0; i < n; ++i) {
514 ro[table[i]] = ri[i];
521 for (blockSize = 2; blockSize <= n; blockSize <<= 1) {
523 double delta = angle / (double)blockSize;
524 double sm2 = -sin(-2 * delta);
525 double sm1 = -sin(-delta);
526 double cm2 = cos(-2 * delta);
527 double cm1 = cos(-delta);
531 for (i = 0; i < n; i += blockSize) {
539 for (j = i, m = 0; m < blockEnd; j++, m++) {
541 ar[0] = w * ar[1] - ar[2];
545 ai[0] = w * ai[1] - ai[2];
550 tr = ar[0] * ro[k] - ai[0] * io[k];
551 ti = ar[0] * io[k] + ai[0] * ro[k];
561 blockEnd = blockSize;
566 double denom = (double)n;
568 for (i = 0; i < n; i++) {
581 _VAMP_SDK_HOSTSPACE_END(PluginInputDomainAdapter.cpp)