cannam@208: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
cannam@208: /*
cannam@208:   Piper C++
cannam@208: 
cannam@208:   An API for audio analysis and feature extraction plugins.
cannam@208: 
cannam@208:   Centre for Digital Music, Queen Mary, University of London.
cannam@208:   Copyright 2006-2017 Chris Cannam and QMUL.
cannam@208:   
cannam@208:   Permission is hereby granted, free of charge, to any person
cannam@208:   obtaining a copy of this software and associated documentation
cannam@208:   files (the "Software"), to deal in the Software without
cannam@208:   restriction, including without limitation the rights to use, copy,
cannam@208:   modify, merge, publish, distribute, sublicense, and/or sell copies
cannam@208:   of the Software, and to permit persons to whom the Software is
cannam@208:   furnished to do so, subject to the following conditions:
cannam@208: 
cannam@208:   The above copyright notice and this permission notice shall be
cannam@208:   included in all copies or substantial portions of the Software.
cannam@208: 
cannam@208:   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
cannam@208:   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
cannam@208:   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
cannam@208:   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
cannam@208:   ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
cannam@208:   CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
cannam@208:   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
cannam@208: 
cannam@208:   Except as contained in this notice, the names of the Centre for
cannam@208:   Digital Music; Queen Mary, University of London; and Chris Cannam
cannam@208:   shall not be used in advertising or otherwise to promote the sale,
cannam@208:   use or other dealings in this Software without prior written
cannam@208:   authorization.
cannam@208: */
cannam@208: 
cannam@208: #ifndef PIPER_VAMP_PLUGIN_H
cannam@208: #define PIPER_VAMP_PLUGIN_H
cannam@208: 
cannam@208: #include <vamp-hostsdk/Plugin.h>
cannam@208: #include <vamp-hostsdk/PluginLoader.h>
cannam@208: 
cannam@208: #include "vamp-support/PluginStaticData.h"
cannam@208: #include "vamp-support/PluginConfiguration.h"
cannam@287: #include "vamp-support/PluginProgramParameters.h"
cannam@208: 
cannam@208: #include "PluginClient.h"
cannam@208: 
cannam@208: #include <cstdint>
cannam@208: #include <iostream>
cannam@208: 
cannam@208: namespace piper_vamp {
cannam@208: namespace client {
cannam@208: 
cannam@210: /**
cannam@210:  * PiperVampPlugin presents a Piper feature extractor in the form of a
cannam@210:  * Vamp plugin.
cannam@210:  */
cannam@208: class PiperVampPlugin : public Vamp::Plugin
cannam@208: {
cannam@208:     enum State {
cannam@208:         /**
cannam@208:          * The plugin's corresponding Piper feature extractor has been
cannam@208:          * loaded but no subsequent state change has happened. This is
cannam@208:          * the initial state of PiperVampPlugin on construction, since
cannam@208:          * it is associated with a pre-loaded handle.
cannam@208:          */
cannam@208:         Loaded,
cannam@208:         
cannam@208:         /**
cannam@208:          * The plugin has been configured, and the step and block size
cannam@208:          * received from the host in its last call to initialise()
cannam@208:          * match those that were returned in the configuration
cannam@208:          * response (i.e. the server's desired step and block
cannam@208:          * size). Our m_config record reflects these correct
cannam@208:          * values. The plugin is ready to process.
cannam@208:          */
cannam@208:         Configured,
cannam@208: 
cannam@208:         /**
cannam@208:          * The plugin has been configured, but the step and block size
cannam@208:          * received from the host in its last call to initialise()
cannam@208:          * differ from those returned by the server in the
cannam@208:          * configuration response. Our initialise() call therefore
cannam@208:          * returned false, and the plugin cannot be used until the
cannam@208:          * host calls initialise() again with the "correct" step and
cannam@208:          * block size. Our m_config record reflects these correct
cannam@208:          * values, so the host can retrieve them through
cannam@208:          * getPreferredStepSize and getPreferredBlockSize.
cannam@208:          */
cannam@208:         Misconfigured,
cannam@208: 
cannam@208:         /**
cannam@208:          * The finish() function has been called and the plugin
cannam@208:          * unloaded. No further plugin activity is possible.
cannam@208:          */
cannam@208:         Finished,
cannam@208: 
cannam@208:         /** 
cannam@208:          * A call has failed unrecoverably. No further plugin activity
cannam@208:          * is possible.
cannam@208:          */
cannam@208:         Failed
cannam@208:     };
cannam@208:     
cannam@208: public:
cannam@208:     PiperVampPlugin(PluginClient *client,
cannam@208:                     std::string pluginKey,
cannam@208:                     float inputSampleRate,
cannam@208:                     int adapterFlags,
cannam@208:                     PluginStaticData psd,
cannam@287:                     PluginConfiguration defaultConfig,
cannam@287:                     PluginProgramParameters programParameters) :
cannam@208:         Plugin(inputSampleRate),
cannam@208:         m_client(client),
cannam@208:         m_key(pluginKey),
cannam@208:         m_adapterFlags(adapterFlags),
cannam@208:         m_state(Loaded),
cannam@208:         m_psd(psd),
cannam@208:         m_defaultConfig(defaultConfig),
cannam@287:         m_config(defaultConfig),
cannam@287:         m_programParameters(programParameters)
cannam@208:     { }
cannam@289:     
cannam@208:     virtual ~PiperVampPlugin() {
cannam@208:         if (m_state != Finished && m_state != Failed) {
cannam@208:             try {
cannam@208:                 (void)m_client->finish(this);
cannam@208:             } catch (const std::exception &e) {
cannam@208:                 // Finish can throw, but our destructor must not
cannam@208:                 std::cerr << "WARNING: PiperVampPlugin::~PiperVampPlugin: caught exception from finish(): " << e.what() << std::endl;
cannam@208:             }
cannam@208:         }
cannam@208:     }
cannam@208:     
cannam@280:     std::string getIdentifier() const override {
cannam@208:         return m_psd.basic.identifier;
cannam@208:     }
cannam@208: 
cannam@280:     std::string getName() const override {
cannam@208:         return m_psd.basic.name;
cannam@208:     }
cannam@208: 
cannam@280:     std::string getDescription() const override {
cannam@208:         return m_psd.basic.description;
cannam@208:     }
cannam@208: 
cannam@280:     std::string getMaker() const override {
cannam@208:         return m_psd.maker;
cannam@208:     }
cannam@208: 
cannam@280:     std::string getCopyright() const override {
cannam@208:         return m_psd.copyright;
cannam@208:     }
cannam@208: 
cannam@280:     int getPluginVersion() const override {
cannam@208:         return m_psd.pluginVersion;
cannam@208:     }
cannam@208: 
cannam@280:     ParameterList getParameterDescriptors() const override {
cannam@208:         return m_psd.parameters;
cannam@208:     }
cannam@208: 
cannam@280:     float getParameter(std::string name) const override {
cannam@208:         if (m_config.parameterValues.find(name) != m_config.parameterValues.end()) {
cannam@208:             return m_config.parameterValues.at(name);
cannam@208:         } else {
cannam@208:             return 0.f;
cannam@208:         }
cannam@208:     }
cannam@208: 
cannam@280:     void setParameter(std::string name, float value) override {
cannam@208:         if (m_state == Failed) {
cannam@208:             throw std::logic_error("Plugin is in failed state");
cannam@208:         }
cannam@208:         if (m_state != Loaded) {
cannam@208:             m_state = Failed;
cannam@208:             throw std::logic_error("Can't set parameter after plugin initialised");
cannam@208:         }
cannam@208:         m_config.parameterValues[name] = value;
cannam@208:     }
cannam@208: 
cannam@280:     ProgramList getPrograms() const override {
cannam@208:         return m_psd.programs;
cannam@208:     }
cannam@208: 
cannam@280:     std::string getCurrentProgram() const override {
cannam@208:         return m_config.currentProgram;
cannam@208:     }
cannam@208:     
cannam@280:     void selectProgram(std::string program) override {
cannam@208:         if (m_state == Failed) {
cannam@208:             throw std::logic_error("Plugin is in failed state");
cannam@208:         }
cannam@208:         if (m_state != Loaded) {
cannam@208:             m_state = Failed;
cannam@208:             throw std::logic_error("Can't select program after plugin initialised");
cannam@208:         }
cannam@208:         m_config.currentProgram = program;
cannam@287: 
cannam@287:         const auto &pp = m_programParameters.programParameters;
cannam@287:         if (pp.find(program) != pp.end()) {
cannam@287:             for (auto param: pp.at(program)) {
cannam@287:                 m_config.parameterValues[param.first] = param.second;
cannam@287:             }
cannam@287:         }
cannam@208:     }
cannam@208: 
cannam@280:     bool initialise(size_t inputChannels,
cannam@287:                     size_t stepSize,
cannam@287:                     size_t blockSize) override {
cannam@208: 
cannam@208:         if (m_state == Failed) {
cannam@208:             throw std::logic_error("Plugin is in failed state");
cannam@208:         }
cannam@208: 
cannam@208:         if (m_state == Misconfigured) {
cannam@208:             if (int(stepSize) == m_config.framing.stepSize &&
cannam@208:                 int(blockSize) == m_config.framing.blockSize) {
cannam@208:                 m_state = Configured;
cannam@208:                 return true;
cannam@208:             } else {
cannam@208:                 return false;
cannam@208:             }
cannam@208:         }
cannam@208:         
cannam@208:         if (m_state != Loaded) {
cannam@208:             m_state = Failed;
cannam@208:             throw std::logic_error("Plugin has already been initialised");
cannam@208:         }
cannam@208:         
cannam@208:         m_config.channelCount = int(inputChannels);
cannam@208:         m_config.framing.stepSize = int(stepSize);
cannam@208:         m_config.framing.blockSize = int(blockSize);
cannam@208: 
cannam@208:         try {
cannam@208:             auto response = m_client->configure(this, m_config);
cannam@208:             m_outputs = response.outputs;
cannam@208:             
cannam@208:             // Update with the new preferred step and block size now
cannam@208:             // that the plugin has taken into account its parameter
cannam@208:             // settings. If the values passed in to initialise()
cannam@208:             // weren't suitable, then this ensures that a subsequent
cannam@208:             // call to getPreferredStepSize/BlockSize on this plugin
cannam@208:             // object will at least get acceptable values from now on
cannam@208:             m_config.framing = response.framing;
cannam@208: 
cannam@208:             // And if they didn't match up with the passed-in ones,
cannam@208:             // lodge ourselves in Misconfigured state and report
cannam@208:             // failure so as to provoke the host to call initialise()
cannam@208:             // again before any processing.
cannam@208:             if (m_config.framing.stepSize != int(stepSize) ||
cannam@208:                 m_config.framing.blockSize != int(blockSize)) {
cannam@208:                 m_state = Misconfigured;
cannam@208:                 return false;
cannam@208:             }
cannam@208:             
cannam@264:         } catch (const std::exception &) {
cannam@208:             m_state = Failed;
cannam@208:             throw;
cannam@208:         }
cannam@208: 
cannam@208:         if (!m_outputs.empty()) {
cannam@208:             m_state = Configured;
cannam@208:             return true;
cannam@208:         } else {
cannam@208:             return false;
cannam@208:         }
cannam@208:     }
cannam@208: 
cannam@280:     void reset() override {
cannam@208:         
cannam@208:         if (m_state == Failed) {
cannam@208:             throw std::logic_error("Plugin is in failed state");
cannam@208:         }
cannam@208:         if (m_state == Loaded || m_state == Misconfigured) {
cannam@208:             // reset is a no-op if the plugin hasn't been initialised yet
cannam@208:             return;
cannam@208:         }
cannam@208: 
cannam@208:         try {
cannam@208:             m_client->reset(this, m_config);
cannam@264:         } catch (const std::exception &) {
cannam@208:             m_state = Failed;
cannam@208:             throw;
cannam@208:         }
cannam@208: 
cannam@208:         m_state = Configured;
cannam@208:     }
cannam@208: 
cannam@280:     InputDomain getInputDomain() const override {
cannam@208:         return m_psd.inputDomain;
cannam@208:     }
cannam@208: 
cannam@280:     size_t getPreferredBlockSize() const override {
cannam@208:         // Return this from m_config instead of m_defaultConfig, so
cannam@208:         // that it gets updated in the event of an initialise() call
cannam@208:         // that fails for the wrong value
cannam@208:         return m_config.framing.blockSize;
cannam@208:     }
cannam@208: 
cannam@280:     size_t getPreferredStepSize() const override {
cannam@208:         // Return this from m_config instead of m_defaultConfig, so
cannam@208:         // that it gets updated in the event of an initialise() call
cannam@208:         // that fails for the wrong value
cannam@208:         return m_config.framing.stepSize;
cannam@208:     }
cannam@208: 
cannam@280:     size_t getMinChannelCount() const override {
cannam@208:         return m_psd.minChannelCount;
cannam@208:     }
cannam@208: 
cannam@280:     size_t getMaxChannelCount() const override {
cannam@208:         return m_psd.maxChannelCount;
cannam@208:     }
cannam@208: 
cannam@280:     OutputList getOutputDescriptors() const override {
cannam@208: 
cannam@208:         if (m_state == Failed) {
cannam@208:             throw std::logic_error("Plugin is in failed state");
cannam@208:         }
cannam@208:         if (m_state == Configured) {
cannam@208:             return m_outputs;
cannam@208:         }
cannam@208: 
cannam@208:         //!!! todo: figure out for which hosts (and adapters?) it may
cannam@208:         //!!! be a problem that the output descriptors are incomplete
cannam@208:         //!!! here. Any such hosts/adapters are broken, but I bet they
cannam@208:         //!!! exist
cannam@208:         
cannam@208:         OutputList staticOutputs;
cannam@208:         for (const auto &o: m_psd.basicOutputInfo) {
cannam@208:             OutputDescriptor od;
cannam@208:             od.identifier = o.identifier;
cannam@208:             od.name = o.name;
cannam@208:             od.description = o.description;
cannam@208:             staticOutputs.push_back(od);
cannam@208:         }
cannam@208:         return staticOutputs;
cannam@208:     }
cannam@208: 
cannam@280:     FeatureSet process(const float *const *inputBuffers,
cannam@280:                                Vamp::RealTime timestamp) override {
cannam@208: 
cannam@208:         if (m_state == Failed) {
cannam@208:             throw std::logic_error("Plugin is in failed state");
cannam@208:         }
cannam@208:         if (m_state == Loaded || m_state == Misconfigured) {
cannam@208:             m_state = Failed;
cannam@208:             throw std::logic_error("Plugin has not been initialised");
cannam@208:         }
cannam@208:         if (m_state == Finished) {
cannam@208:             m_state = Failed;
cannam@208:             throw std::logic_error("Plugin has already been disposed of");
cannam@208:         }
cannam@208: 
cannam@208:         std::vector<std::vector<float> > vecbuf;
cannam@273: 
cannam@273:         int bufferSize;
cannam@273:         if (m_psd.inputDomain == FrequencyDomain) {
cannam@273:             bufferSize = 2 * (m_config.framing.blockSize / 2) + 2;
cannam@273:         } else {
cannam@273:             bufferSize = m_config.framing.blockSize;
cannam@273:         }
cannam@273:         
cannam@208:         for (int c = 0; c < m_config.channelCount; ++c) {
cannam@208:             vecbuf.push_back(std::vector<float>
cannam@273:                              (inputBuffers[c], inputBuffers[c] + bufferSize));
cannam@208:         }
cannam@208: 
cannam@208:         try {
cannam@208:             return m_client->process(this, vecbuf, timestamp);
cannam@264:         } catch (const std::exception &) {
cannam@208:             m_state = Failed;
cannam@208:             throw;
cannam@208:         }
cannam@208:     }
cannam@208: 
cannam@280:     FeatureSet getRemainingFeatures() override {
cannam@208: 
cannam@208:         if (m_state == Failed) {
cannam@208:             throw std::logic_error("Plugin is in failed state");
cannam@208:         }
cannam@208:         if (m_state == Loaded || m_state == Misconfigured) {
cannam@208:             m_state = Failed;
cannam@208:             throw std::logic_error("Plugin has not been configured");
cannam@208:         }
cannam@208:         if (m_state == Finished) {
cannam@208:             m_state = Failed;
cannam@208:             throw std::logic_error("Plugin has already been disposed of");
cannam@208:         }
cannam@208: 
cannam@208:         m_state = Finished;
cannam@208: 
cannam@208:         try {
cannam@208:             return m_client->finish(this);
cannam@264:         } catch (const std::exception &) {
cannam@208:             m_state = Failed;
cannam@208:             throw;
cannam@208:         }
cannam@208:     }
cannam@208: 
cannam@208:     // Not Plugin methods, but needed by the PluginClient to support reloads:
cannam@208:     
cannam@208:     virtual float getInputSampleRate() const {
cannam@208:         return m_inputSampleRate;
cannam@208:     }
cannam@208: 
cannam@208:     virtual std::string getPluginKey() const {
cannam@208:         return m_key;
cannam@208:     }
cannam@208: 
cannam@208:     virtual int getAdapterFlags() const {
cannam@208:         return m_adapterFlags;
cannam@208:     }
cannam@208:     
cannam@208: private:
cannam@208:     PluginClient *m_client;
cannam@208:     std::string m_key;
cannam@208:     int m_adapterFlags;
cannam@208:     State m_state;
cannam@208:     PluginStaticData m_psd;
cannam@208:     OutputList m_outputs;
cannam@208:     PluginConfiguration m_defaultConfig;
cannam@208:     PluginConfiguration m_config;
cannam@287:     PluginProgramParameters m_programParameters;
cannam@208: };
cannam@208: 
cannam@208: }
cannam@208: }
cannam@208: 
cannam@208: #endif