cannam@233: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
cannam@233: 
cannam@233: /*
cannam@233:     Vamp
cannam@233: 
cannam@233:     An API for audio analysis and feature extraction plugins.
cannam@233: 
cannam@233:     Centre for Digital Music, Queen Mary, University of London.
cannam@290:     Copyright 2006-2009 Chris Cannam and QMUL.
cannam@233:   
cannam@233:     This file is based in part on Don Cross's public domain FFT
cannam@233:     implementation.
cannam@233: 
cannam@233:     Permission is hereby granted, free of charge, to any person
cannam@233:     obtaining a copy of this software and associated documentation
cannam@233:     files (the "Software"), to deal in the Software without
cannam@233:     restriction, including without limitation the rights to use, copy,
cannam@233:     modify, merge, publish, distribute, sublicense, and/or sell copies
cannam@233:     of the Software, and to permit persons to whom the Software is
cannam@233:     furnished to do so, subject to the following conditions:
cannam@233: 
cannam@233:     The above copyright notice and this permission notice shall be
cannam@233:     included in all copies or substantial portions of the Software.
cannam@233: 
cannam@233:     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
cannam@233:     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
cannam@233:     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
cannam@233:     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
cannam@233:     ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
cannam@233:     CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
cannam@233:     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
cannam@233: 
cannam@233:     Except as contained in this notice, the names of the Centre for
cannam@233:     Digital Music; Queen Mary, University of London; and Chris Cannam
cannam@233:     shall not be used in advertising or otherwise to promote the sale,
cannam@233:     use or other dealings in this Software without prior written
cannam@233:     authorization.
cannam@233: */
cannam@233: 
cannam@233: #include <vamp-hostsdk/PluginInputDomainAdapter.h>
cannam@233: 
cannam@233: #include <cmath>
cannam@233: 
Chris@317: #include "Window.h"
Chris@317: 
Chris@434: #include <stdlib.h>
Chris@434: #include <stdio.h>
Chris@434: #include <math.h>
Chris@434: #include <string.h>
Chris@434: #include <limits.h>
cannam@233: 
Chris@445: _VAMP_SDK_HOSTSPACE_BEGIN(PluginInputDomainAdapter.cpp)
Chris@444: 
Chris@445: #include "../vamp-sdk/FFTimpl.cpp"
cannam@263: 
cannam@233: namespace Vamp {
cannam@233: 
cannam@233: namespace HostExt {
cannam@233: 
cannam@233: class PluginInputDomainAdapter::Impl
cannam@233: {
cannam@233: public:
cannam@233:     Impl(Plugin *plugin, float inputSampleRate);
cannam@233:     ~Impl();
cannam@233:     
cannam@233:     bool initialise(size_t channels, size_t stepSize, size_t blockSize);
cannam@288:     void reset();
cannam@233: 
cannam@233:     size_t getPreferredStepSize() const;
cannam@233:     size_t getPreferredBlockSize() const;
cannam@233: 
cannam@233:     FeatureSet process(const float *const *inputBuffers, RealTime timestamp);
cannam@288: 
cannam@288:     void setProcessTimestampMethod(ProcessTimestampMethod m);
cannam@288:     ProcessTimestampMethod getProcessTimestampMethod() const;
cannam@233:     
cannam@233:     RealTime getTimestampAdjustment() const;
cannam@233: 
Chris@317:     WindowType getWindowType() const;
Chris@317:     void setWindowType(WindowType type);
Chris@317: 
cannam@233: protected:
cannam@233:     Plugin *m_plugin;
cannam@233:     float m_inputSampleRate;
cannam@233:     int m_channels;
cannam@288:     int m_stepSize;
cannam@233:     int m_blockSize;
cannam@233:     float **m_freqbuf;
Chris@444:     Kiss::kiss_fft_scalar *m_ri;
Chris@317: 
Chris@317:     WindowType m_windowType;
Chris@444:     typedef Window<Kiss::kiss_fft_scalar> W;
Chris@444:     W *m_window;
cannam@233: 
cannam@288:     ProcessTimestampMethod m_method;
cannam@288:     int m_processCount;
cannam@289:     float **m_shiftBuffers;
cannam@288: 
Chris@444:     Kiss::kiss_fftr_cfg m_cfg;
Chris@444:     Kiss::kiss_fft_cpx *m_cbuf;
cannam@233: 
cannam@289:     FeatureSet processShiftingTimestamp(const float *const *inputBuffers, RealTime timestamp);
cannam@289:     FeatureSet processShiftingData(const float *const *inputBuffers, RealTime timestamp);
cannam@289: 
cannam@233:     size_t makeBlockSizeAcceptable(size_t) const;
Chris@317:     
Chris@444:     W::WindowType convertType(WindowType t) const;
cannam@233: };
cannam@233: 
cannam@233: PluginInputDomainAdapter::PluginInputDomainAdapter(Plugin *plugin) :
cannam@233:     PluginWrapper(plugin)
cannam@233: {
cannam@233:     m_impl = new Impl(plugin, m_inputSampleRate);
cannam@233: }
cannam@233: 
cannam@233: PluginInputDomainAdapter::~PluginInputDomainAdapter()
cannam@233: {
cannam@233:     delete m_impl;
cannam@233: }
cannam@233:   
cannam@233: bool
cannam@233: PluginInputDomainAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize)
cannam@233: {
cannam@233:     return m_impl->initialise(channels, stepSize, blockSize);
cannam@233: }
cannam@233: 
cannam@288: void
cannam@288: PluginInputDomainAdapter::reset()
cannam@288: {
cannam@288:     m_impl->reset();
cannam@288: }
cannam@288: 
cannam@233: Plugin::InputDomain
cannam@233: PluginInputDomainAdapter::getInputDomain() const
cannam@233: {
cannam@233:     return TimeDomain;
cannam@233: }
cannam@233: 
cannam@233: size_t
cannam@233: PluginInputDomainAdapter::getPreferredStepSize() const
cannam@233: {
cannam@233:     return m_impl->getPreferredStepSize();
cannam@233: }
cannam@233: 
cannam@233: size_t
cannam@233: PluginInputDomainAdapter::getPreferredBlockSize() const
cannam@233: {
cannam@233:     return m_impl->getPreferredBlockSize();
cannam@233: }
cannam@233: 
cannam@233: Plugin::FeatureSet
cannam@233: PluginInputDomainAdapter::process(const float *const *inputBuffers, RealTime timestamp)
cannam@233: {
cannam@233:     return m_impl->process(inputBuffers, timestamp);
cannam@233: }
cannam@233: 
cannam@288: void
cannam@288: PluginInputDomainAdapter::setProcessTimestampMethod(ProcessTimestampMethod m)
cannam@288: {
cannam@288:     m_impl->setProcessTimestampMethod(m);
cannam@288: }
cannam@288: 
cannam@288: PluginInputDomainAdapter::ProcessTimestampMethod
cannam@288: PluginInputDomainAdapter::getProcessTimestampMethod() const
cannam@288: {
cannam@288:     return m_impl->getProcessTimestampMethod();
cannam@288: }
cannam@288: 
cannam@233: RealTime
cannam@233: PluginInputDomainAdapter::getTimestampAdjustment() const
cannam@233: {
cannam@233:     return m_impl->getTimestampAdjustment();
cannam@233: }
cannam@233: 
Chris@317: PluginInputDomainAdapter::WindowType
Chris@317: PluginInputDomainAdapter::getWindowType() const
Chris@317: {
Chris@317:     return m_impl->getWindowType();
Chris@317: }
Chris@317: 
Chris@317: void
Chris@317: PluginInputDomainAdapter::setWindowType(WindowType w)
Chris@317: {
Chris@317:     m_impl->setWindowType(w);
Chris@317: }
Chris@317: 
cannam@233: 
cannam@233: PluginInputDomainAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) :
cannam@233:     m_plugin(plugin),
cannam@233:     m_inputSampleRate(inputSampleRate),
cannam@233:     m_channels(0),
cannam@288:     m_stepSize(0),
cannam@233:     m_blockSize(0),
cannam@233:     m_freqbuf(0),
cannam@233:     m_ri(0),
Chris@317:     m_windowType(HanningWindow),
cannam@233:     m_window(0),
cannam@288:     m_method(ShiftTimestamp),
cannam@288:     m_processCount(0),
cannam@289:     m_shiftBuffers(0),
Chris@434:     m_cfg(0),
cannam@233:     m_cbuf(0)
cannam@233: {
cannam@233: }
cannam@233: 
cannam@233: PluginInputDomainAdapter::Impl::~Impl()
cannam@233: {
cannam@233:     // the adapter will delete the plugin
cannam@233: 
cannam@289:     if (m_shiftBuffers) {
cannam@289:         for (int c = 0; c < m_channels; ++c) {
cannam@289:             delete[] m_shiftBuffers[c];
cannam@289:         }
cannam@289:         delete[] m_shiftBuffers;
cannam@289:     }
cannam@289: 
cannam@233:     if (m_channels > 0) {
cannam@233:         for (int c = 0; c < m_channels; ++c) {
cannam@233:             delete[] m_freqbuf[c];
cannam@233:         }
cannam@233:         delete[] m_freqbuf;
Chris@435:         delete[] m_ri;
Chris@434:         if (m_cfg) {
Chris@444:             Kiss::kiss_fftr_free(m_cfg);
Chris@435:             m_cfg = 0;
Chris@434:             delete[] m_cbuf;
Chris@435:             m_cbuf = 0;
cannam@233:         }
Chris@317:         delete m_window;
cannam@233:     }
cannam@233: }
cannam@233: 
cannam@233: // for some visual studii apparently
cannam@233: #ifndef M_PI
cannam@233: #define M_PI 3.14159265358979232846
cannam@233: #endif
cannam@233:     
cannam@233: bool
cannam@233: PluginInputDomainAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t blockSize)
cannam@233: {
cannam@233:     if (m_plugin->getInputDomain() == TimeDomain) {
cannam@233: 
cannam@288:         m_stepSize = int(stepSize);
cannam@233:         m_blockSize = int(blockSize);
cannam@233:         m_channels = int(channels);
cannam@233: 
cannam@233:         return m_plugin->initialise(channels, stepSize, blockSize);
cannam@233:     }
cannam@233: 
cannam@233:     if (blockSize < 2) {
cannam@283:         std::cerr << "ERROR: PluginInputDomainAdapter::initialise: blocksize < 2 not supported" << std::endl;
cannam@233:         return false;
cannam@233:     }                
cannam@233:         
Chris@434:     if (blockSize % 2) {
Chris@434:         std::cerr << "ERROR: PluginInputDomainAdapter::initialise: odd blocksize " << blockSize << " not supported" << std::endl;
cannam@233:         return false;
cannam@233:     }
cannam@233: 
cannam@233:     if (m_channels > 0) {
cannam@233:         for (int c = 0; c < m_channels; ++c) {
cannam@233:             delete[] m_freqbuf[c];
cannam@233:         }
cannam@233:         delete[] m_freqbuf;
Chris@435:         delete[] m_ri;
Chris@434:         if (m_cfg) {
Chris@444:             Kiss::kiss_fftr_free(m_cfg);
Chris@435:             m_cfg = 0;
Chris@434:             delete[] m_cbuf;
Chris@435:             m_cbuf = 0;
cannam@233:         }
Chris@317:         delete m_window;
cannam@233:     }
cannam@233: 
cannam@288:     m_stepSize = int(stepSize);
cannam@233:     m_blockSize = int(blockSize);
cannam@233:     m_channels = int(channels);
cannam@233: 
cannam@233:     m_freqbuf = new float *[m_channels];
cannam@233:     for (int c = 0; c < m_channels; ++c) {
cannam@233:         m_freqbuf[c] = new float[m_blockSize + 2];
cannam@233:     }
Chris@444:     m_ri = new Kiss::kiss_fft_scalar[m_blockSize];
cannam@233: 
Chris@444:     m_window = new W(convertType(m_windowType), m_blockSize);
cannam@233: 
Chris@444:     m_cfg = Kiss::kiss_fftr_alloc(m_blockSize, false, 0, 0);
Chris@444:     m_cbuf = new Kiss::kiss_fft_cpx[m_blockSize/2+1];
cannam@233: 
cannam@288:     m_processCount = 0;
cannam@288: 
Chris@435:     return m_plugin->initialise(channels, stepSize, m_blockSize);
cannam@233: }
cannam@233: 
cannam@288: void
cannam@288: PluginInputDomainAdapter::Impl::reset()
cannam@288: {
cannam@288:     m_processCount = 0;
cannam@288:     m_plugin->reset();
cannam@288: }
cannam@288: 
cannam@233: size_t
cannam@233: PluginInputDomainAdapter::Impl::getPreferredStepSize() const
cannam@233: {
cannam@233:     size_t step = m_plugin->getPreferredStepSize();
cannam@233: 
cannam@233:     if (step == 0 && (m_plugin->getInputDomain() == FrequencyDomain)) {
cannam@233:         step = getPreferredBlockSize() / 2;
cannam@233:     }
cannam@233: 
cannam@233:     return step;
cannam@233: }
cannam@233: 
cannam@233: size_t
cannam@233: PluginInputDomainAdapter::Impl::getPreferredBlockSize() const
cannam@233: {
cannam@233:     size_t block = m_plugin->getPreferredBlockSize();
cannam@233: 
cannam@233:     if (m_plugin->getInputDomain() == FrequencyDomain) {
cannam@233:         if (block == 0) {
cannam@233:             block = 1024;
cannam@233:         } else {
cannam@233:             block = makeBlockSizeAcceptable(block);
cannam@233:         }
cannam@233:     }
cannam@233: 
cannam@233:     return block;
cannam@233: }
cannam@233: 
cannam@233: size_t
cannam@233: PluginInputDomainAdapter::Impl::makeBlockSizeAcceptable(size_t blockSize) const
cannam@233: {
cannam@233:     if (blockSize < 2) {
cannam@233: 
cannam@283:         std::cerr << "WARNING: PluginInputDomainAdapter::initialise: blocksize < 2 not" << std::endl
cannam@233:                   << "supported, increasing from " << blockSize << " to 2" << std::endl;
cannam@233:         blockSize = 2;
Chris@434: 
Chris@434:     } else if (blockSize % 2) {
cannam@233:         
Chris@434:         std::cerr << "WARNING: PluginInputDomainAdapter::initialise: odd blocksize not" << std::endl
Chris@434:                   << "supported, increasing from " << blockSize << " to " << (blockSize+1) << std::endl;
Chris@434:         blockSize = blockSize+1;
cannam@233:     }
cannam@233: 
cannam@233:     return blockSize;
cannam@233: }
cannam@233: 
cannam@233: RealTime
cannam@233: PluginInputDomainAdapter::Impl::getTimestampAdjustment() const
cannam@233: {
cannam@233:     if (m_plugin->getInputDomain() == TimeDomain) {
cannam@233:         return RealTime::zeroTime;
cannam@298:     } else if (m_method == ShiftData || m_method == NoShift) {
cannam@289:         return RealTime::zeroTime;
cannam@233:     } else {
cannam@233:         return RealTime::frame2RealTime
cannam@233:             (m_blockSize/2, int(m_inputSampleRate + 0.5));
cannam@233:     }
cannam@233: }
cannam@233: 
cannam@288: void
cannam@288: PluginInputDomainAdapter::Impl::setProcessTimestampMethod(ProcessTimestampMethod m)
cannam@288: {
cannam@288:     m_method = m;
cannam@288: }
cannam@288: 
cannam@288: PluginInputDomainAdapter::ProcessTimestampMethod
cannam@288: PluginInputDomainAdapter::Impl::getProcessTimestampMethod() const
cannam@288: {
cannam@288:     return m_method;
cannam@288: }
cannam@288: 
Chris@317: void
Chris@317: PluginInputDomainAdapter::Impl::setWindowType(WindowType t)
Chris@317: {
Chris@317:     if (m_windowType == t) return;
Chris@317:     m_windowType = t;
Chris@317:     if (m_window) {
Chris@317:         delete m_window;
Chris@444:         m_window = new W(convertType(m_windowType), m_blockSize);
Chris@317:     }
Chris@317: }
Chris@317: 
Chris@317: PluginInputDomainAdapter::WindowType
Chris@317: PluginInputDomainAdapter::Impl::getWindowType() const
Chris@317: {
Chris@317:     return m_windowType;
Chris@317: }
Chris@317: 
Chris@444: PluginInputDomainAdapter::Impl::W::WindowType
Chris@317: PluginInputDomainAdapter::Impl::convertType(WindowType t) const
Chris@317: {
Chris@317:     switch (t) {
Chris@317:     case RectangularWindow:
Chris@444:         return W::RectangularWindow;
Chris@317:     case BartlettWindow:
Chris@444:         return W::BartlettWindow;
Chris@317:     case HammingWindow:
Chris@444:         return W::HammingWindow;
Chris@317:     case HanningWindow:
Chris@444:         return W::HanningWindow;
Chris@317:     case BlackmanWindow:
Chris@444:         return W::BlackmanWindow;
Chris@317:     case NuttallWindow:
Chris@444:         return W::NuttallWindow;
Chris@317:     case BlackmanHarrisWindow:
Chris@444:         return W::BlackmanHarrisWindow;
Chris@319:     default:
Chris@444: 	return W::HanningWindow;
Chris@317:     }
Chris@317: }
Chris@317: 
cannam@233: Plugin::FeatureSet
cannam@233: PluginInputDomainAdapter::Impl::process(const float *const *inputBuffers,
cannam@233:                                         RealTime timestamp)
cannam@233: {
cannam@233:     if (m_plugin->getInputDomain() == TimeDomain) {
cannam@233:         return m_plugin->process(inputBuffers, timestamp);
cannam@233:     }
cannam@233: 
cannam@298:     if (m_method == ShiftTimestamp || m_method == NoShift) {
cannam@289:         return processShiftingTimestamp(inputBuffers, timestamp);
cannam@289:     } else {
cannam@289:         return processShiftingData(inputBuffers, timestamp);
cannam@289:     }
cannam@289: }
cannam@233: 
cannam@289: Plugin::FeatureSet
cannam@289: PluginInputDomainAdapter::Impl::processShiftingTimestamp(const float *const *inputBuffers,
cannam@289:                                                          RealTime timestamp)
cannam@289: {
Chris@421:     unsigned int roundedRate = 1;
Chris@421:     if (m_inputSampleRate > 0.f) {
Chris@421:         roundedRate = (unsigned int)round(m_inputSampleRate);
Chris@421:     }
Chris@421:     
cannam@298:     if (m_method == ShiftTimestamp) {
Chris@386:         // we may need to add one nsec if timestamp +
Chris@386:         // getTimestampAdjustment() rounds down
cannam@298:         timestamp = timestamp + getTimestampAdjustment();
Chris@386:         RealTime nsec(0, 1);
Chris@421:         if (RealTime::realTime2Frame(timestamp, roundedRate) <
Chris@421:             RealTime::realTime2Frame(timestamp + nsec, roundedRate)) {
Chris@386:             timestamp = timestamp + nsec;
Chris@386:         }
cannam@298:     }
cannam@233: 
cannam@233:     for (int c = 0; c < m_channels; ++c) {
cannam@233: 
Chris@317:         m_window->cut(inputBuffers[c], m_ri);
cannam@233: 
cannam@233:         for (int i = 0; i < m_blockSize/2; ++i) {
cannam@233:             // FFT shift
Chris@444:             Kiss::kiss_fft_scalar value = m_ri[i];
cannam@233:             m_ri[i] = m_ri[i + m_blockSize/2];
cannam@233:             m_ri[i + m_blockSize/2] = value;
cannam@233:         }
cannam@233: 
Chris@444:         Kiss::kiss_fftr(m_cfg, m_ri, m_cbuf);
Chris@434:         
cannam@233:         for (int i = 0; i <= m_blockSize/2; ++i) {
Chris@470:             m_freqbuf[c][i * 2] = float(m_cbuf[i].r);
Chris@470:             m_freqbuf[c][i * 2 + 1] = float(m_cbuf[i].i);
cannam@233:         }
cannam@233:     }
cannam@233: 
cannam@289:     return m_plugin->process(m_freqbuf, timestamp);
cannam@288: }
cannam@288: 
cannam@288: Plugin::FeatureSet
cannam@289: PluginInputDomainAdapter::Impl::processShiftingData(const float *const *inputBuffers,
cannam@289:                                                     RealTime timestamp)
cannam@288: {
cannam@289:     if (m_processCount == 0) {
cannam@289:         if (!m_shiftBuffers) {
cannam@289:             m_shiftBuffers = new float *[m_channels];
cannam@289:             for (int c = 0; c < m_channels; ++c) {
cannam@289:                 m_shiftBuffers[c] = new float[m_blockSize + m_blockSize/2];
cannam@289:             }
cannam@289:         }
cannam@289:         for (int c = 0; c < m_channels; ++c) {
cannam@289:             for (int i = 0; i < m_blockSize + m_blockSize/2; ++i) {
cannam@289:                 m_shiftBuffers[c][i] = 0.f;
cannam@289:             }
cannam@289:         }
cannam@289:     }
cannam@289: 
cannam@289:     for (int c = 0; c < m_channels; ++c) {
cannam@289:         for (int i = m_stepSize; i < m_blockSize + m_blockSize/2; ++i) {
cannam@289:             m_shiftBuffers[c][i - m_stepSize] = m_shiftBuffers[c][i];
cannam@289:         }
cannam@289:         for (int i = 0; i < m_blockSize; ++i) {
cannam@289:             m_shiftBuffers[c][i + m_blockSize/2] = inputBuffers[c][i];
cannam@289:         }
cannam@289:     }
cannam@289: 
cannam@289:     for (int c = 0; c < m_channels; ++c) {
cannam@289: 
Chris@317:         m_window->cut(m_shiftBuffers[c], m_ri);
cannam@289: 
cannam@289:         for (int i = 0; i < m_blockSize/2; ++i) {
cannam@289:             // FFT shift
Chris@444:             Kiss::kiss_fft_scalar value = m_ri[i];
cannam@289:             m_ri[i] = m_ri[i + m_blockSize/2];
cannam@289:             m_ri[i + m_blockSize/2] = value;
cannam@289:         }
cannam@289: 
Chris@444:         Kiss::kiss_fftr(m_cfg, m_ri, m_cbuf);
Chris@434:         
cannam@289:         for (int i = 0; i <= m_blockSize/2; ++i) {
Chris@470:             m_freqbuf[c][i * 2] = float(m_cbuf[i].r);
Chris@470:             m_freqbuf[c][i * 2 + 1] = float(m_cbuf[i].i);
cannam@289:         }
cannam@289:     }
cannam@289: 
cannam@289:     ++m_processCount;
cannam@289: 
cannam@289:     return m_plugin->process(m_freqbuf, timestamp);
cannam@233: }
cannam@233: 
cannam@233: }
cannam@233:         
cannam@233: }
cannam@233: 
cannam@263: _VAMP_SDK_HOSTSPACE_END(PluginInputDomainAdapter.cpp)
cannam@263: