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 cannam@208: #include 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 cannam@208: #include 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 > 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 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