changeset 92:c94c066a4897

* Add Mark L's PluginBufferingAdapter
author cannam
date Fri, 02 Nov 2007 14:54:04 +0000
parents 200a663bace1
children cd72c0473341
files Makefile host/vamp-simple-host.cpp vamp-sdk/hostext/PluginBufferingAdapter.cpp vamp-sdk/hostext/PluginBufferingAdapter.h vamp-sdk/hostext/PluginInputDomainAdapter.cpp vamp-sdk/hostext/PluginLoader.cpp vamp-sdk/hostext/PluginLoader.h
diffstat 7 files changed, 463 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- 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 \
--- 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;
--- /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 <vector>
+#include <map>
+
+#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<vector<float> > 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<int, FeatureList>::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<int, FeatureList>::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;
+}
+
+}
+	
+}
+
+
--- /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 <vamp-sdk/hostext/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
--- 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),
--- 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 <fstream>
 #include <cctype> // tolower
@@ -366,6 +367,10 @@
                 }
             }
 
+            if (adapterFlags & ADAPT_BUFFER_SIZE) {
+                adapter = new PluginBufferingAdapter(adapter);
+            }
+
             if (adapterFlags & ADAPT_CHANNEL_COUNT) {
                 adapter = new PluginChannelAdapter(adapter);
             }
--- 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
     };