cannam@92: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@92: cannam@92: /* cannam@92: Vamp cannam@92: cannam@92: An API for audio analysis and feature extraction plugins. cannam@92: cannam@92: Centre for Digital Music, Queen Mary, University of London. cannam@92: Copyright 2006-2007 Chris Cannam and QMUL. cannam@102: This file by Mark Levy and Chris Cannam. cannam@92: cannam@92: Permission is hereby granted, free of charge, to any person cannam@92: obtaining a copy of this software and associated documentation cannam@92: files (the "Software"), to deal in the Software without cannam@92: restriction, including without limitation the rights to use, copy, cannam@92: modify, merge, publish, distribute, sublicense, and/or sell copies cannam@92: of the Software, and to permit persons to whom the Software is cannam@92: furnished to do so, subject to the following conditions: cannam@92: cannam@92: The above copyright notice and this permission notice shall be cannam@92: included in all copies or substantial portions of the Software. cannam@92: cannam@92: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@92: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@92: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND cannam@92: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR cannam@92: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@92: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@92: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@92: cannam@92: Except as contained in this notice, the names of the Centre for cannam@92: Digital Music; Queen Mary, University of London; and Chris Cannam cannam@92: shall not be used in advertising or otherwise to promote the sale, cannam@92: use or other dealings in this Software without prior written cannam@92: authorization. cannam@92: */ cannam@92: cannam@92: #include cannam@92: #include cannam@92: cannam@92: #include "PluginBufferingAdapter.h" cannam@92: cannam@92: using std::vector; cannam@92: using std::map; cannam@92: cannam@92: namespace Vamp { cannam@92: cannam@92: namespace HostExt { cannam@92: cannam@92: class PluginBufferingAdapter::Impl cannam@92: { cannam@92: public: cannam@92: Impl(Plugin *plugin, float inputSampleRate); cannam@92: ~Impl(); cannam@92: cannam@92: bool initialise(size_t channels, size_t stepSize, size_t blockSize); cannam@92: cannam@92: OutputList getOutputDescriptors() const; cannam@92: cannam@104: void reset(); cannam@104: cannam@92: FeatureSet process(const float *const *inputBuffers, RealTime timestamp); cannam@92: cannam@92: FeatureSet getRemainingFeatures(); cannam@92: cannam@92: protected: cannam@102: class RingBuffer cannam@102: { cannam@102: public: cannam@102: RingBuffer(int n) : cannam@102: m_buffer(new float[n+1]), m_writer(0), m_reader(0), m_size(n+1) { } cannam@102: virtual ~RingBuffer() { delete[] m_buffer; } cannam@102: cannam@102: int getSize() const { return m_size-1; } cannam@102: void reset() { m_writer = 0; m_reader = 0; } cannam@102: cannam@102: int getReadSpace() const { cannam@102: int writer = m_writer, reader = m_reader, space; cannam@102: if (writer > reader) space = writer - reader; cannam@102: else if (writer < reader) space = (writer + m_size) - reader; cannam@102: else space = 0; cannam@102: return space; cannam@102: } cannam@102: cannam@102: int getWriteSpace() const { cannam@102: int writer = m_writer; cannam@102: int reader = m_reader; cannam@102: int space = (reader + m_size - writer - 1); cannam@102: if (space >= m_size) space -= m_size; cannam@102: return space; cannam@102: } cannam@102: cannam@102: int peek(float *destination, int n) const { cannam@102: cannam@102: int available = getReadSpace(); cannam@102: cannam@102: if (n > available) { cannam@102: for (int i = available; i < n; ++i) { cannam@102: destination[i] = 0.f; cannam@102: } cannam@102: n = available; cannam@102: } cannam@102: if (n == 0) return n; cannam@102: cannam@102: int reader = m_reader; cannam@102: int here = m_size - reader; cannam@102: const float *const bufbase = m_buffer + reader; cannam@102: cannam@102: if (here >= n) { cannam@102: for (int i = 0; i < n; ++i) { cannam@102: destination[i] = bufbase[i]; cannam@102: } cannam@102: } else { cannam@102: for (int i = 0; i < here; ++i) { cannam@102: destination[i] = bufbase[i]; cannam@102: } cannam@102: float *const destbase = destination + here; cannam@102: const int nh = n - here; cannam@102: for (int i = 0; i < nh; ++i) { cannam@102: destbase[i] = m_buffer[i]; cannam@102: } cannam@102: } cannam@102: cannam@102: return n; cannam@102: } cannam@102: cannam@102: int skip(int n) { cannam@102: cannam@102: int available = getReadSpace(); cannam@102: if (n > available) { cannam@102: n = available; cannam@102: } cannam@102: if (n == 0) return n; cannam@102: cannam@102: int reader = m_reader; cannam@102: reader += n; cannam@102: while (reader >= m_size) reader -= m_size; cannam@102: m_reader = reader; cannam@102: return n; cannam@102: } cannam@102: cannam@102: int write(const float *source, int n) { cannam@102: cannam@102: int available = getWriteSpace(); cannam@102: if (n > available) { cannam@102: n = available; cannam@102: } cannam@102: if (n == 0) return n; cannam@102: cannam@102: int writer = m_writer; cannam@102: int here = m_size - writer; cannam@102: float *const bufbase = m_buffer + writer; cannam@102: cannam@102: if (here >= n) { cannam@102: for (int i = 0; i < n; ++i) { cannam@102: bufbase[i] = source[i]; cannam@102: } cannam@102: } else { cannam@102: for (int i = 0; i < here; ++i) { cannam@102: bufbase[i] = source[i]; cannam@102: } cannam@102: const int nh = n - here; cannam@102: const float *const srcbase = source + here; cannam@102: float *const buf = m_buffer; cannam@102: for (int i = 0; i < nh; ++i) { cannam@102: buf[i] = srcbase[i]; cannam@102: } cannam@102: } cannam@102: cannam@102: writer += n; cannam@102: while (writer >= m_size) writer -= m_size; cannam@102: m_writer = writer; cannam@102: cannam@102: return n; cannam@102: } cannam@102: cannam@102: int zero(int n) { cannam@102: cannam@102: int available = getWriteSpace(); cannam@102: if (n > available) { cannam@102: n = available; cannam@102: } cannam@102: if (n == 0) return n; cannam@102: cannam@102: int writer = m_writer; cannam@102: int here = m_size - writer; cannam@102: float *const bufbase = m_buffer + writer; cannam@102: cannam@102: if (here >= n) { cannam@102: for (int i = 0; i < n; ++i) { cannam@102: bufbase[i] = 0.f; cannam@102: } cannam@102: } else { cannam@102: for (int i = 0; i < here; ++i) { cannam@102: bufbase[i] = 0.f; cannam@102: } cannam@102: const int nh = n - here; cannam@102: for (int i = 0; i < nh; ++i) { cannam@102: m_buffer[i] = 0.f; cannam@102: } cannam@102: } cannam@102: cannam@102: writer += n; cannam@102: while (writer >= m_size) writer -= m_size; cannam@102: m_writer = writer; cannam@102: cannam@102: return n; cannam@102: } cannam@102: cannam@102: protected: cannam@102: float *m_buffer; cannam@102: int m_writer; cannam@102: int m_reader; cannam@102: int m_size; cannam@102: cannam@102: private: cannam@102: RingBuffer(const RingBuffer &); // not provided cannam@102: RingBuffer &operator=(const RingBuffer &); // not provided cannam@102: }; cannam@102: cannam@92: Plugin *m_plugin; cannam@92: size_t m_inputStepSize; cannam@92: size_t m_inputBlockSize; cannam@92: size_t m_stepSize; cannam@92: size_t m_blockSize; cannam@92: size_t m_channels; cannam@102: vector m_queue; cannam@102: float **m_buffers; cannam@92: float m_inputSampleRate; cannam@104: RealTime m_timestamp; cannam@104: bool m_unrun; cannam@92: OutputList m_outputs; cannam@92: cannam@92: void processBlock(FeatureSet& allFeatureSets, RealTime timestamp); cannam@92: }; cannam@92: cannam@92: PluginBufferingAdapter::PluginBufferingAdapter(Plugin *plugin) : cannam@92: PluginWrapper(plugin) cannam@92: { cannam@92: m_impl = new Impl(plugin, m_inputSampleRate); cannam@92: } cannam@92: cannam@92: PluginBufferingAdapter::~PluginBufferingAdapter() cannam@92: { cannam@92: delete m_impl; cannam@92: } cannam@92: cannam@92: bool cannam@92: PluginBufferingAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize) cannam@92: { cannam@92: return m_impl->initialise(channels, stepSize, blockSize); cannam@92: } cannam@92: cannam@92: PluginBufferingAdapter::OutputList cannam@92: PluginBufferingAdapter::getOutputDescriptors() const cannam@92: { cannam@92: return m_impl->getOutputDescriptors(); cannam@92: } cannam@104: cannam@104: void cannam@104: PluginBufferingAdapter::reset() cannam@104: { cannam@104: m_impl->reset(); cannam@104: } cannam@92: cannam@92: PluginBufferingAdapter::FeatureSet cannam@92: PluginBufferingAdapter::process(const float *const *inputBuffers, cannam@92: RealTime timestamp) cannam@92: { cannam@92: return m_impl->process(inputBuffers, timestamp); cannam@92: } cannam@92: cannam@92: PluginBufferingAdapter::FeatureSet cannam@92: PluginBufferingAdapter::getRemainingFeatures() cannam@92: { cannam@92: return m_impl->getRemainingFeatures(); cannam@92: } cannam@92: cannam@92: PluginBufferingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : cannam@92: m_plugin(plugin), cannam@92: m_inputStepSize(0), cannam@92: m_inputBlockSize(0), cannam@92: m_stepSize(0), cannam@92: m_blockSize(0), cannam@92: m_channels(0), cannam@102: m_queue(0), cannam@92: m_buffers(0), cannam@92: m_inputSampleRate(inputSampleRate), cannam@104: m_timestamp(RealTime::zeroTime), cannam@104: m_unrun(true) cannam@92: { cannam@92: m_outputs = plugin->getOutputDescriptors(); cannam@92: } cannam@92: cannam@92: PluginBufferingAdapter::Impl::~Impl() cannam@92: { cannam@92: // the adapter will delete the plugin cannam@102: cannam@102: for (size_t i = 0; i < m_channels; ++i) { cannam@102: delete m_queue[i]; cannam@102: delete[] m_buffers[i]; cannam@102: } cannam@102: delete[] m_buffers; cannam@92: } cannam@92: cannam@92: size_t cannam@92: PluginBufferingAdapter::getPreferredStepSize() const cannam@92: { cannam@92: return getPreferredBlockSize(); cannam@92: } cannam@92: cannam@92: bool cannam@92: PluginBufferingAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t blockSize) cannam@92: { cannam@92: if (stepSize != blockSize) { cannam@92: std::cerr << "PluginBufferingAdapter::initialise: input stepSize must be equal to blockSize for this adapter (stepSize = " << stepSize << ", blockSize = " << blockSize << ")" << std::endl; cannam@92: return false; cannam@92: } cannam@92: cannam@92: m_channels = channels; cannam@92: m_inputStepSize = stepSize; cannam@92: m_inputBlockSize = blockSize; cannam@92: cannam@92: // use the step and block sizes which the plugin prefers cannam@92: m_stepSize = m_plugin->getPreferredStepSize(); cannam@92: m_blockSize = m_plugin->getPreferredBlockSize(); cannam@92: cannam@92: // or sensible defaults if it has no preference cannam@92: if (m_blockSize == 0) { cannam@92: m_blockSize = 1024; cannam@92: } cannam@92: if (m_stepSize == 0) { cannam@92: if (m_plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain) { cannam@92: m_stepSize = m_blockSize/2; cannam@92: } else { cannam@92: m_stepSize = m_blockSize; cannam@92: } cannam@92: } else if (m_stepSize > m_blockSize) { cannam@92: if (m_plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain) { cannam@92: m_blockSize = m_stepSize * 2; cannam@92: } else { cannam@92: m_blockSize = m_stepSize; cannam@92: } cannam@92: } cannam@92: cannam@92: std::cerr << "PluginBufferingAdapter::initialise: stepSize " << m_inputStepSize << " -> " << m_stepSize cannam@92: << ", blockSize " << m_inputBlockSize << " -> " << m_blockSize << std::endl; cannam@92: cannam@92: // current implementation breaks if step is greater than block cannam@92: if (m_stepSize > m_blockSize) { cannam@92: std::cerr << "PluginBufferingAdapter::initialise: plugin's preferred stepSize greater than blockSize, giving up!" << std::endl; cannam@92: return false; cannam@92: } cannam@102: cannam@102: m_buffers = new float *[m_channels]; cannam@102: cannam@102: for (size_t i = 0; i < m_channels; ++i) { cannam@102: m_queue.push_back(new RingBuffer(m_blockSize + m_inputBlockSize)); cannam@102: m_buffers[i] = new float[m_blockSize]; cannam@102: } cannam@92: cannam@92: return m_plugin->initialise(m_channels, m_stepSize, m_blockSize); cannam@92: } cannam@92: cannam@92: PluginBufferingAdapter::OutputList cannam@92: PluginBufferingAdapter::Impl::getOutputDescriptors() const cannam@92: { cannam@92: OutputList outs = m_plugin->getOutputDescriptors(); cannam@92: for (size_t i = 0; i < outs.size(); ++i) { cannam@92: if (outs[i].sampleType == OutputDescriptor::OneSamplePerStep) { cannam@92: outs[i].sampleRate = 1.f / m_stepSize; cannam@92: } cannam@92: outs[i].sampleType = OutputDescriptor::VariableSampleRate; cannam@92: } cannam@92: return outs; cannam@92: } cannam@92: cannam@104: void cannam@104: PluginBufferingAdapter::Impl::reset() cannam@104: { cannam@104: m_timestamp = RealTime::zeroTime; cannam@104: m_unrun = true; cannam@104: cannam@104: for (size_t i = 0; i < m_queue.size(); ++i) { cannam@104: m_queue[i]->reset(); cannam@104: } cannam@104: } cannam@104: cannam@92: PluginBufferingAdapter::FeatureSet cannam@92: PluginBufferingAdapter::Impl::process(const float *const *inputBuffers, cannam@92: RealTime timestamp) cannam@92: { cannam@92: FeatureSet allFeatureSets; cannam@104: cannam@104: if (m_unrun) { cannam@104: m_timestamp = timestamp; cannam@104: m_unrun = false; cannam@104: } cannam@92: cannam@92: // queue the new input cannam@92: cannam@102: for (size_t i = 0; i < m_channels; ++i) { cannam@102: int written = m_queue[i]->write(inputBuffers[i], m_inputBlockSize); cannam@102: if (written < int(m_inputBlockSize) && i == 0) { cannam@102: std::cerr << "WARNING: PluginBufferingAdapter::Impl::process: " cannam@102: << "Buffer overflow: wrote " << written cannam@102: << " of " << m_inputBlockSize cannam@102: << " input samples (for plugin step size " cannam@102: << m_stepSize << ", block size " << m_blockSize << ")" cannam@102: << std::endl; cannam@102: } cannam@102: } cannam@92: cannam@92: // process as much as we can cannam@102: cannam@102: while (m_queue[0]->getReadSpace() >= int(m_blockSize)) { cannam@92: processBlock(allFeatureSets, timestamp); cannam@92: } cannam@92: cannam@92: return allFeatureSets; cannam@92: } cannam@92: cannam@92: PluginBufferingAdapter::FeatureSet cannam@92: PluginBufferingAdapter::Impl::getRemainingFeatures() cannam@92: { cannam@92: FeatureSet allFeatureSets; cannam@92: cannam@92: // process remaining samples in queue cannam@102: while (m_queue[0]->getReadSpace() >= int(m_blockSize)) { cannam@92: processBlock(allFeatureSets, m_timestamp); cannam@92: } cannam@92: cannam@92: // pad any last samples remaining and process cannam@102: if (m_queue[0]->getReadSpace() > 0) { cannam@102: for (size_t i = 0; i < m_channels; ++i) { cannam@102: m_queue[i]->zero(m_blockSize - m_queue[i]->getReadSpace()); cannam@102: } cannam@92: processBlock(allFeatureSets, m_timestamp); cannam@92: } cannam@92: cannam@92: // get remaining features cannam@102: cannam@92: FeatureSet featureSet = m_plugin->getRemainingFeatures(); cannam@102: cannam@92: for (map::iterator iter = featureSet.begin(); cannam@102: iter != featureSet.end(); ++iter) { cannam@92: FeatureList featureList = iter->second; cannam@102: for (size_t i = 0; i < featureList.size(); ++i) { cannam@102: allFeatureSets[iter->first].push_back(featureList[i]); cannam@102: } cannam@92: } cannam@92: cannam@92: return allFeatureSets; cannam@92: } cannam@92: cannam@92: void cannam@102: PluginBufferingAdapter::Impl::processBlock(FeatureSet& allFeatureSets, cannam@102: RealTime timestamp) cannam@92: { cannam@102: for (size_t i = 0; i < m_channels; ++i) { cannam@102: m_queue[i]->peek(m_buffers[i], m_blockSize); cannam@102: } cannam@102: cannam@92: FeatureSet featureSet = m_plugin->process(m_buffers, m_timestamp); cannam@92: cannam@92: for (map::iterator iter = featureSet.begin(); cannam@102: iter != featureSet.end(); ++iter) { cannam@92: cannam@92: FeatureList featureList = iter->second; cannam@92: int outputNo = iter->first; cannam@92: cannam@102: for (size_t i = 0; i < featureList.size(); ++i) { cannam@92: cannam@92: // make sure the timestamp is set cannam@102: switch (m_outputs[outputNo].sampleType) { cannam@102: cannam@92: case OutputDescriptor::OneSamplePerStep: cannam@92: // use our internal timestamp - OK???? cannam@92: featureList[i].timestamp = m_timestamp; cannam@92: break; cannam@102: cannam@92: case OutputDescriptor::FixedSampleRate: cannam@92: // use our internal timestamp cannam@92: featureList[i].timestamp = m_timestamp; cannam@92: break; cannam@102: cannam@92: case OutputDescriptor::VariableSampleRate: cannam@92: break; // plugin must set timestamp cannam@102: cannam@92: default: cannam@92: break; cannam@92: } cannam@92: cannam@92: allFeatureSets[outputNo].push_back(featureList[i]); cannam@92: } cannam@92: } cannam@92: cannam@92: // step forward cannam@102: cannam@102: for (size_t i = 0; i < m_channels; ++i) { cannam@102: m_queue[i]->skip(m_stepSize); cannam@102: } cannam@92: cannam@92: // fake up the timestamp each time we step forward cannam@102: cannam@102: long frame = RealTime::realTime2Frame(m_timestamp, cannam@102: int(m_inputSampleRate + 0.5)); cannam@102: m_timestamp = RealTime::frame2RealTime(frame + m_stepSize, cannam@102: int(m_inputSampleRate + 0.5)); cannam@92: } cannam@92: cannam@92: } cannam@92: cannam@92: } cannam@92: cannam@92: