# HG changeset patch # User cannam # Date 1194015244 0 # Node ID c94c066a4897be5520c1253851606a6d28095f28 # Parent 200a663bace1aa2d214421b9cd5a344433db0127 * Add Mark L's PluginBufferingAdapter diff -r 200a663bace1 -r c94c066a4897 Makefile --- a/Makefile Thu Nov 01 10:25:35 2007 +0000 +++ b/Makefile Fri Nov 02 14:54:04 2007 +0000 @@ -40,8 +40,8 @@ # because our plugin exposes only a C API so there are no boundary # compatibility problems.) # -PLUGIN_LIBS = $(SDKDIR)/libvamp-sdk.a -#PLUGIN_LIBS = $(SDKDIR)/libvamp-sdk.a $(shell g++ -print-file-name=libstdc++.a) +#PLUGIN_LIBS = $(SDKDIR)/libvamp-sdk.a +PLUGIN_LIBS = $(SDKDIR)/libvamp-sdk.a $(shell g++ -print-file-name=libstdc++.a) # File extension for a dynamically loadable object # @@ -79,7 +79,7 @@ # Flags required to tell the compiler to create a dynamically loadable object # -DYNAMIC_LDFLAGS = -shared -Wl,-Bsymbolic +DYNAMIC_LDFLAGS = --static-libgcc -shared -Wl,-Bsymbolic PLUGIN_LDFLAGS = $(DYNAMIC_LDFLAGS) SDK_DYNAMIC_LDFLAGS = $(DYNAMIC_LDFLAGS) -Wl,-soname=$(INSTALL_SDK_LIBNAME) HOSTSDK_DYNAMIC_LDFLAGS = $(DYNAMIC_LDFLAGS) -Wl,-soname=$(INSTALL_HOSTSDK_LIBNAME) @@ -107,6 +107,7 @@ $(SDKDIR)/RealTime.h HOSTEXT_HEADERS = \ + $(HOSTEXTDIR)/PluginBufferingAdapter.h \ $(HOSTEXTDIR)/PluginChannelAdapter.h \ $(HOSTEXTDIR)/PluginInputDomainAdapter.h \ $(HOSTEXTDIR)/PluginLoader.h \ @@ -118,6 +119,7 @@ HOSTSDK_OBJECTS = \ $(SDKDIR)/PluginHostAdapter.o \ + $(HOSTEXTDIR)/PluginBufferingAdapter.o \ $(HOSTEXTDIR)/PluginChannelAdapter.o \ $(HOSTEXTDIR)/PluginInputDomainAdapter.o \ $(HOSTEXTDIR)/PluginLoader.o \ diff -r 200a663bace1 -r c94c066a4897 host/vamp-simple-host.cpp --- a/host/vamp-simple-host.cpp Thu Nov 01 10:25:35 2007 +0000 +++ b/host/vamp-simple-host.cpp Fri Nov 02 14:54:04 2007 +0000 @@ -228,7 +228,7 @@ } Vamp::Plugin *plugin = loader->loadPlugin - (key, sfinfo.samplerate, PluginLoader::ADAPT_ALL); + (key, sfinfo.samplerate, PluginLoader::ADAPT_ALL_SAFE); if (!plugin) { cerr << myname << ": ERROR: Failed to load plugin \"" << id << "\" from library \"" << soname << "\"" << endl; diff -r 200a663bace1 -r c94c066a4897 vamp-sdk/hostext/PluginBufferingAdapter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vamp-sdk/hostext/PluginBufferingAdapter.cpp Fri Nov 02 14:54:04 2007 +0000 @@ -0,0 +1,327 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006-2007 Chris Cannam and QMUL. + This file by Mark Levy, Copyright 2007 QMUL. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include +#include + +#include "PluginBufferingAdapter.h" + +using std::vector; +using std::map; + +namespace Vamp { + +namespace HostExt { + +class PluginBufferingAdapter::Impl +{ +public: + Impl(Plugin *plugin, float inputSampleRate); + ~Impl(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + Plugin *m_plugin; + size_t m_inputStepSize; + size_t m_inputBlockSize; + size_t m_stepSize; + size_t m_blockSize; + size_t m_channels; + vector > m_queue; + float **m_buffers; // in fact an array of pointers into the queue + size_t m_inputPos; // start position in the queue of next input block + float m_inputSampleRate; + RealTime m_timestamp; + OutputList m_outputs; + + void processBlock(FeatureSet& allFeatureSets, RealTime timestamp); +}; + +PluginBufferingAdapter::PluginBufferingAdapter(Plugin *plugin) : + PluginWrapper(plugin) +{ + m_impl = new Impl(plugin, m_inputSampleRate); +} + +PluginBufferingAdapter::~PluginBufferingAdapter() +{ + delete m_impl; +} + +bool +PluginBufferingAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + return m_impl->initialise(channels, stepSize, blockSize); +} + +PluginBufferingAdapter::OutputList +PluginBufferingAdapter::getOutputDescriptors() const +{ + return m_impl->getOutputDescriptors(); +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::process(const float *const *inputBuffers, + RealTime timestamp) +{ + return m_impl->process(inputBuffers, timestamp); +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::getRemainingFeatures() +{ + return m_impl->getRemainingFeatures(); +} + +PluginBufferingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : + m_plugin(plugin), + m_inputStepSize(0), + m_inputBlockSize(0), + m_stepSize(0), + m_blockSize(0), + m_channels(0), + m_buffers(0), + m_inputPos(0), + m_inputSampleRate(inputSampleRate), + m_timestamp() +{ + m_outputs = plugin->getOutputDescriptors(); +} + +PluginBufferingAdapter::Impl::~Impl() +{ + // the adapter will delete the plugin + + delete [] m_buffers; +} + +size_t +PluginBufferingAdapter::getPreferredStepSize() const +{ + return getPreferredBlockSize(); +} + +bool +PluginBufferingAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (stepSize != blockSize) { + std::cerr << "PluginBufferingAdapter::initialise: input stepSize must be equal to blockSize for this adapter (stepSize = " << stepSize << ", blockSize = " << blockSize << ")" << std::endl; + return false; + } + + m_channels = channels; + m_inputStepSize = stepSize; + m_inputBlockSize = blockSize; + + // use the step and block sizes which the plugin prefers + m_stepSize = m_plugin->getPreferredStepSize(); + m_blockSize = m_plugin->getPreferredBlockSize(); + + // or sensible defaults if it has no preference + if (m_blockSize == 0) { + m_blockSize = 1024; + } + if (m_stepSize == 0) { + if (m_plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain) { + m_stepSize = m_blockSize/2; + } else { + m_stepSize = m_blockSize; + } + } else if (m_stepSize > m_blockSize) { + if (m_plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain) { + m_blockSize = m_stepSize * 2; + } else { + m_blockSize = m_stepSize; + } + } + + std::cerr << "PluginBufferingAdapter::initialise: stepSize " << m_inputStepSize << " -> " << m_stepSize + << ", blockSize " << m_inputBlockSize << " -> " << m_blockSize << std::endl; + + // current implementation breaks if step is greater than block + if (m_stepSize > m_blockSize) { + std::cerr << "PluginBufferingAdapter::initialise: plugin's preferred stepSize greater than blockSize, giving up!" << std::endl; + return false; + } + + m_queue.resize(m_channels); + m_buffers = new float*[m_channels]; + + return m_plugin->initialise(m_channels, m_stepSize, m_blockSize); +} + +PluginBufferingAdapter::OutputList +PluginBufferingAdapter::Impl::getOutputDescriptors() const +{ + OutputList outs = m_plugin->getOutputDescriptors(); + for (size_t i = 0; i < outs.size(); ++i) { + if (outs[i].sampleType == OutputDescriptor::OneSamplePerStep) { + outs[i].sampleRate = 1.f / m_stepSize; + } + outs[i].sampleType = OutputDescriptor::VariableSampleRate; + } + return outs; +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::Impl::process(const float *const *inputBuffers, + RealTime timestamp) +{ + FeatureSet allFeatureSets; + + // queue the new input + + //std::cerr << "unread " << m_queue[0].size() - m_inputPos << " samples" << std::endl; + //std::cerr << "queueing " << m_inputBlockSize - (m_queue[0].size() - m_inputPos) << " samples" << std::endl; + + for (size_t i = 0; i < m_channels; ++i) + for (size_t j = m_queue[0].size() - m_inputPos; j < m_inputBlockSize; ++j) + m_queue[i].push_back(inputBuffers[i][j]); + + m_inputPos += m_inputStepSize; + + // process as much as we can + while (m_queue[0].size() >= m_blockSize) + { + processBlock(allFeatureSets, timestamp); + m_inputPos -= m_stepSize; + + //std::cerr << m_queue[0].size() << " samples still left in queue" << std::endl; + //std::cerr << "inputPos = " << m_inputPos << std::endl; + } + + return allFeatureSets; +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::Impl::getRemainingFeatures() +{ + FeatureSet allFeatureSets; + + // process remaining samples in queue + while (m_queue[0].size() >= m_blockSize) + { + processBlock(allFeatureSets, m_timestamp); + } + + // pad any last samples remaining and process + if (m_queue[0].size() > 0) + { + for (size_t i = 0; i < m_channels; ++i) + while (m_queue[i].size() < m_blockSize) + m_queue[i].push_back(0.0); + processBlock(allFeatureSets, m_timestamp); + } + + // get remaining features + FeatureSet featureSet = m_plugin->getRemainingFeatures(); + for (map::iterator iter = featureSet.begin(); + iter != featureSet.end(); ++iter) + { + FeatureList featureList = iter->second; + for (size_t i = 0; i < featureList.size(); ++i) + allFeatureSets[iter->first].push_back(featureList[i]); + } + + return allFeatureSets; +} + +void +PluginBufferingAdapter::Impl::processBlock(FeatureSet& allFeatureSets, RealTime timestamp) +{ + //std::cerr << m_queue[0].size() << " samples left in queue" << std::endl; + + // point the buffers to the head of the queue + for (size_t i = 0; i < m_channels; ++i) + m_buffers[i] = &m_queue[i][0]; + + FeatureSet featureSet = m_plugin->process(m_buffers, m_timestamp); + + for (map::iterator iter = featureSet.begin(); + iter != featureSet.end(); ++iter) + { + + FeatureList featureList = iter->second; + int outputNo = iter->first; + + for (size_t i = 0; i < featureList.size(); ++i) + { + + // make sure the timestamp is set + switch (m_outputs[outputNo].sampleType) + { + case OutputDescriptor::OneSamplePerStep: + // use our internal timestamp - OK???? + featureList[i].timestamp = m_timestamp; + break; + case OutputDescriptor::FixedSampleRate: + // use our internal timestamp + featureList[i].timestamp = m_timestamp; + break; + case OutputDescriptor::VariableSampleRate: + break; // plugin must set timestamp + default: + break; + } + + allFeatureSets[outputNo].push_back(featureList[i]); + } + } + + // step forward + for (size_t i = 0; i < m_channels; ++i) + m_queue[i].erase(m_queue[i].begin(), m_queue[i].begin() + m_stepSize); + + // fake up the timestamp each time we step forward + //std::cerr << m_timestamp; + long frame = RealTime::realTime2Frame(m_timestamp, int(m_inputSampleRate + 0.5)); + m_timestamp = RealTime::frame2RealTime(frame + m_stepSize, int(m_inputSampleRate + 0.5)); + //std::cerr << "--->" << m_timestamp << std::endl; +} + +} + +} + + diff -r 200a663bace1 -r c94c066a4897 vamp-sdk/hostext/PluginBufferingAdapter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vamp-sdk/hostext/PluginBufferingAdapter.h Fri Nov 02 14:54:04 2007 +0000 @@ -0,0 +1,97 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006-2007 Chris Cannam and QMUL. + This file by Mark Levy, Copyright 2007 QMUL. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _VAMP_PLUGIN_BUFFERING_ADAPTER_H_ +#define _VAMP_PLUGIN_BUFFERING_ADAPTER_H_ + +#include "PluginWrapper.h" + +namespace Vamp { + +namespace HostExt { + +/** + * \class PluginBufferingAdapter PluginBufferingAdapter.h + * + * PluginBufferingAdapter is a Vamp plugin adapter that allows plugins + * to be used by a host supplying an audio stream in non-overlapping + * buffers of arbitrary size. + * + * A host using PluginBufferingAdapter may ignore the preferred step + * and block size reported by the plugin, and still expect the plugin + * to run. The value of blockSize and stepSize passed to initialise + * should be the size of the buffer which the host will supply; the + * stepSize should be equal to the blockSize. + * + * If the internal step size used for the plugin differs from that + * supplied by the host, the adapter will modify the sample rate + * specifications for the plugin outputs (setting them all to + * VariableSampleRate) and set timestamps on the output features for + * outputs that formerly used a different sample rate specification. + * This is necessary in order to obtain correct time stamping. + * + * In other respects, the PluginBufferingAdapter behaves identically + * to the plugin that it wraps. The wrapped plugin will be deleted + * when the wrapper is deleted. + */ + +class PluginBufferingAdapter : public PluginWrapper +{ +public: + PluginBufferingAdapter(Plugin *plugin); // I take ownership of plugin + virtual ~PluginBufferingAdapter(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + + size_t getPreferredStepSize() const; + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + class Impl; + Impl *m_impl; +}; + +} + +} + +#endif diff -r 200a663bace1 -r c94c066a4897 vamp-sdk/hostext/PluginInputDomainAdapter.cpp --- a/vamp-sdk/hostext/PluginInputDomainAdapter.cpp Thu Nov 01 10:25:35 2007 +0000 +++ b/vamp-sdk/hostext/PluginInputDomainAdapter.cpp Fri Nov 02 14:54:04 2007 +0000 @@ -112,7 +112,7 @@ return m_impl->process(inputBuffers, timestamp); } - PluginInputDomainAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : +PluginInputDomainAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : m_plugin(plugin), m_inputSampleRate(inputSampleRate), m_channels(0), diff -r 200a663bace1 -r c94c066a4897 vamp-sdk/hostext/PluginLoader.cpp --- a/vamp-sdk/hostext/PluginLoader.cpp Thu Nov 01 10:25:35 2007 +0000 +++ b/vamp-sdk/hostext/PluginLoader.cpp Fri Nov 02 14:54:04 2007 +0000 @@ -38,6 +38,7 @@ #include "PluginLoader.h" #include "PluginInputDomainAdapter.h" #include "PluginChannelAdapter.h" +#include "PluginBufferingAdapter.h" #include #include // tolower @@ -366,6 +367,10 @@ } } + if (adapterFlags & ADAPT_BUFFER_SIZE) { + adapter = new PluginBufferingAdapter(adapter); + } + if (adapterFlags & ADAPT_CHANNEL_COUNT) { adapter = new PluginChannelAdapter(adapter); } diff -r 200a663bace1 -r c94c066a4897 vamp-sdk/hostext/PluginLoader.h --- a/vamp-sdk/hostext/PluginLoader.h Thu Nov 01 10:25:35 2007 +0000 +++ b/vamp-sdk/hostext/PluginLoader.h Fri Nov 02 14:54:04 2007 +0000 @@ -142,15 +142,38 @@ * to be mixed down to mono, etc., without having to worry about * doing that itself. * - * ADAPT_ALL - Perform all available adaptations, where meaningful. + * ADAPT_BUFFER_SIZE - Wrap the plugin in a PluginBufferingAdapter + * permitting the host to provide audio input using any block + * size, with no overlap, regardless of the plugin's preferred + * block size (suitable for hosts that read from non-seekable + * streaming media, for example). This adapter introduces some + * run-time overhead and also changes the semantics of the plugin + * slightly (see the PluginBufferingAdapter header documentation + * for details). + * + * ADAPT_ALL_SAFE - Perform all available adaptations that are + * meaningful for the plugin and "safe". Currently this means to + * ADAPT_INPUT_DOMAIN if the plugin wants FrequencyDomain input; + * ADAPT_CHANNEL_COUNT always; and ADAPT_BUFFER_SIZE only if the + * input domain is being adapted but the plugin's preferred block + * size is not a power of two (the PluginInputDomainAdapter cannot + * handle non-power-of-two block sizes). * - * See PluginInputDomainAdapter and PluginChannelAdapter for more - * details of the classes that the loader may use if these flags - * are set. + * ADAPT_ALL - Perform all available adaptations that are + * meaningful for the plugin. + * + * See PluginInputDomainAdapter, PluginChannelAdapter and + * PluginBufferingAdapter for more details of the classes that the + * loader may use if these flags are set. */ enum AdapterFlags { + ADAPT_INPUT_DOMAIN = 0x01, ADAPT_CHANNEL_COUNT = 0x02, + ADAPT_BUFFER_SIZE = 0x04, + + ADAPT_ALL_SAFE = 0x80, + ADAPT_ALL = 0xff };