cannam@173: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@173: cannam@173: /* cannam@173: Vamp cannam@173: cannam@173: An API for audio analysis and feature extraction plugins. cannam@173: cannam@173: Centre for Digital Music, Queen Mary, University of London. cannam@173: Copyright 2006-2008 Chris Cannam and QMUL. cannam@173: cannam@173: Permission is hereby granted, free of charge, to any person cannam@173: obtaining a copy of this software and associated documentation cannam@173: files (the "Software"), to deal in the Software without cannam@173: restriction, including without limitation the rights to use, copy, cannam@173: modify, merge, publish, distribute, sublicense, and/or sell copies cannam@173: of the Software, and to permit persons to whom the Software is cannam@173: furnished to do so, subject to the following conditions: cannam@173: cannam@173: The above copyright notice and this permission notice shall be cannam@173: included in all copies or substantial portions of the Software. cannam@173: cannam@173: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@173: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@173: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND cannam@173: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR cannam@173: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@173: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@173: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@173: cannam@173: Except as contained in this notice, the names of the Centre for cannam@173: Digital Music; Queen Mary, University of London; and Chris Cannam cannam@173: shall not be used in advertising or otherwise to promote the sale, cannam@173: use or other dealings in this Software without prior written cannam@173: authorization. cannam@173: */ cannam@173: cannam@173: #include "PluginSummarisingAdapter.h" cannam@173: cannam@174: #include cannam@175: #include cannam@174: cannam@173: namespace Vamp { cannam@173: cannam@173: namespace HostExt { cannam@173: cannam@173: class PluginSummarisingAdapter::Impl cannam@173: { cannam@173: public: cannam@173: Impl(Plugin *plugin, float inputSampleRate); cannam@173: ~Impl(); cannam@173: cannam@173: FeatureSet process(const float *const *inputBuffers, RealTime timestamp); cannam@173: FeatureSet getRemainingFeatures(); cannam@173: cannam@173: void setSummarySegmentBoundaries(const SegmentBoundaries &); cannam@173: cannam@176: FeatureList getSummaryForOutput(int output, SummaryType type); cannam@176: FeatureSet getSummaryForAllOutputs(SummaryType type); cannam@173: cannam@173: protected: cannam@174: Plugin *m_plugin; cannam@174: cannam@173: SegmentBoundaries m_boundaries; cannam@174: cannam@174: typedef std::vector ValueList; cannam@174: typedef std::map BinValueMap; cannam@174: cannam@174: struct OutputAccumulator { cannam@174: int count; cannam@174: BinValueMap values; cannam@177: OutputAccumulator() : count(0), values() { } cannam@174: }; cannam@174: cannam@174: typedef std::map OutputAccumulatorMap; cannam@174: OutputAccumulatorMap m_accumulators; cannam@174: cannam@174: struct OutputBinSummary { cannam@174: float minimum; cannam@174: float maximum; cannam@174: float median; cannam@174: float mode; cannam@174: float sum; cannam@174: float variance; cannam@174: int count; cannam@174: }; cannam@174: cannam@174: typedef std::map OutputSummary; cannam@174: typedef std::map SummarySegmentMap; cannam@174: typedef std::map OutputSummarySegmentMap; cannam@174: cannam@174: OutputSummarySegmentMap m_summaries; cannam@174: cannam@174: RealTime m_lastTimestamp; cannam@174: cannam@174: void accumulate(const FeatureSet &fs, RealTime); cannam@174: void accumulate(int output, const Feature &f, RealTime); cannam@174: void reduce(); cannam@173: }; cannam@173: cannam@173: PluginSummarisingAdapter::PluginSummarisingAdapter(Plugin *plugin) : cannam@173: PluginWrapper(plugin) cannam@173: { cannam@173: m_impl = new Impl(plugin, m_inputSampleRate); cannam@173: } cannam@173: cannam@173: PluginSummarisingAdapter::~PluginSummarisingAdapter() cannam@173: { cannam@173: delete m_impl; cannam@173: } cannam@173: cannam@173: Plugin::FeatureSet cannam@173: PluginSummarisingAdapter::process(const float *const *inputBuffers, RealTime timestamp) cannam@173: { cannam@173: return m_impl->process(inputBuffers, timestamp); cannam@173: } cannam@173: cannam@174: Plugin::FeatureSet cannam@174: PluginSummarisingAdapter::getRemainingFeatures() cannam@174: { cannam@174: return m_impl->getRemainingFeatures(); cannam@174: } cannam@174: cannam@175: Plugin::FeatureList cannam@176: PluginSummarisingAdapter::getSummaryForOutput(int output, SummaryType type) cannam@175: { cannam@176: return m_impl->getSummaryForOutput(output, type); cannam@176: } cannam@176: cannam@176: Plugin::FeatureSet cannam@176: PluginSummarisingAdapter::getSummaryForAllOutputs(SummaryType type) cannam@176: { cannam@176: return m_impl->getSummaryForAllOutputs(type); cannam@175: } cannam@173: cannam@173: PluginSummarisingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : cannam@174: m_plugin(plugin) cannam@173: { cannam@173: } cannam@173: cannam@173: PluginSummarisingAdapter::Impl::~Impl() cannam@173: { cannam@173: } cannam@173: cannam@174: Plugin::FeatureSet cannam@174: PluginSummarisingAdapter::Impl::process(const float *const *inputBuffers, RealTime timestamp) cannam@174: { cannam@174: FeatureSet fs = m_plugin->process(inputBuffers, timestamp); cannam@174: accumulate(fs, timestamp); cannam@174: m_lastTimestamp = timestamp; cannam@174: return fs; cannam@174: } cannam@174: cannam@174: Plugin::FeatureSet cannam@174: PluginSummarisingAdapter::Impl::getRemainingFeatures() cannam@174: { cannam@174: FeatureSet fs = m_plugin->getRemainingFeatures(); cannam@174: accumulate(fs, m_lastTimestamp); cannam@174: reduce(); cannam@174: return fs; cannam@174: } cannam@174: cannam@175: Plugin::FeatureList cannam@176: PluginSummarisingAdapter::Impl::getSummaryForOutput(int output, SummaryType type) cannam@175: { cannam@175: //!!! need to ensure that this is only called after processing is cannam@175: //!!! complete (at the moment processing is "completed" in the cannam@175: //!!! call to getRemainingFeatures, but we don't want to require cannam@175: //!!! the host to call getRemainingFeatures at all unless it cannam@175: //!!! actually wants the raw features too -- calling getSummary cannam@175: //!!! should be enough -- we do need to ensure that all data has cannam@175: //!!! been processed though!) cannam@175: FeatureList fl; cannam@175: for (SummarySegmentMap::const_iterator i = m_summaries[output].begin(); cannam@175: i != m_summaries[output].end(); ++i) { cannam@177: cannam@175: Feature f; cannam@175: f.hasTimestamp = true; cannam@175: f.timestamp = i->first; cannam@175: f.hasDuration = false; cannam@177: cannam@175: for (OutputSummary::const_iterator j = i->second.begin(); cannam@175: j != i->second.end(); ++j) { cannam@175: cannam@175: // these will be ordered by bin number, and no bin numbers cannam@175: // will be missing except at the end (because of the way cannam@175: // the accumulators were initially filled in accumulate()) cannam@175: cannam@175: const OutputBinSummary &summary = j->second; cannam@175: float result = 0.f; cannam@175: cannam@175: switch (type) { cannam@175: cannam@175: case Minimum: cannam@175: result = summary.minimum; cannam@175: break; cannam@175: cannam@175: case Maximum: cannam@175: result = summary.maximum; cannam@175: break; cannam@175: cannam@175: case Mean: cannam@175: if (summary.count) { cannam@175: result = summary.sum / summary.count; cannam@175: } cannam@175: break; cannam@175: cannam@175: case Median: cannam@175: result = summary.median; cannam@175: break; cannam@175: cannam@175: case Mode: cannam@175: result = summary.mode; cannam@175: break; cannam@175: cannam@175: case Sum: cannam@175: result = summary.sum; cannam@175: break; cannam@175: cannam@175: case Variance: cannam@175: result = summary.variance; cannam@175: break; cannam@175: cannam@175: case StandardDeviation: cannam@175: result = sqrtf(summary.variance); cannam@175: break; cannam@175: cannam@175: case Count: cannam@175: result = summary.count; cannam@175: break; cannam@175: } cannam@177: cannam@177: f.values.push_back(result); cannam@175: } cannam@175: cannam@175: fl.push_back(f); cannam@175: } cannam@175: return fl; cannam@175: } cannam@175: cannam@176: Plugin::FeatureSet cannam@176: PluginSummarisingAdapter::Impl::getSummaryForAllOutputs(SummaryType type) cannam@176: { cannam@176: FeatureSet fs; cannam@176: for (OutputSummarySegmentMap::const_iterator i = m_summaries.begin(); cannam@176: i != m_summaries.end(); ++i) { cannam@176: fs[i->first] = getSummaryForOutput(i->first, type); cannam@176: } cannam@176: return fs; cannam@176: } cannam@176: cannam@174: void cannam@174: PluginSummarisingAdapter::Impl::accumulate(const FeatureSet &fs, cannam@174: RealTime timestamp) cannam@174: { cannam@174: for (FeatureSet::const_iterator i = fs.begin(); i != fs.end(); ++i) { cannam@174: for (FeatureList::const_iterator j = i->second.begin(); cannam@174: j != i->second.end(); ++j) { cannam@174: accumulate(i->first, *j, timestamp); cannam@174: } cannam@174: } cannam@174: } cannam@174: cannam@174: void cannam@174: PluginSummarisingAdapter::Impl::accumulate(int output, cannam@174: const Feature &f, cannam@174: RealTime timestamp) cannam@174: { cannam@174: //!!! use timestamp to determine which segment we're on cannam@174: m_accumulators[output].count++; cannam@174: for (int i = 0; i < int(f.values.size()); ++i) { cannam@178: cannam@178: cannam@178: //!!! we really want to associate this occurrence of this cannam@178: //!!! value with the duration it covers. cannam@178: cannam@178: //!!! for dense values, the duration can be 1 or the sample cannam@178: //!!! rate or whatever -- doesn't matter so long as it's the cannam@178: //!!! same for every value. cannam@178: cannam@178: //!!! for sparse values, the duration should be that from this cannam@178: //!!! feature to the next. cannam@178: cannam@178: //!!! if the feature has a duration, should be using that cannam@178: //!!! instead. cannam@178: cannam@174: m_accumulators[output].values[i].push_back(f.values[i]); cannam@174: } cannam@174: } cannam@174: cannam@174: void cannam@174: PluginSummarisingAdapter::Impl::reduce() cannam@174: { cannam@174: RealTime segmentStart = RealTime::zeroTime; //!!! cannam@174: cannam@174: for (OutputAccumulatorMap::iterator i = m_accumulators.begin(); cannam@174: i != m_accumulators.end(); ++i) { cannam@174: cannam@174: int output = i->first; cannam@174: OutputAccumulator &accumulator = i->second; cannam@174: cannam@174: for (BinValueMap::iterator j = accumulator.values.begin(); cannam@174: j != accumulator.values.end(); ++j) { cannam@174: cannam@174: int bin = j->first; cannam@174: ValueList &values = j->second; cannam@174: cannam@174: OutputBinSummary summary; cannam@174: summary.minimum = 0.f; cannam@174: summary.maximum = 0.f; cannam@174: summary.median = 0.f; cannam@174: summary.mode = 0.f; cannam@174: summary.sum = 0.f; cannam@174: summary.variance = 0.f; cannam@174: summary.count = accumulator.count; cannam@174: if (summary.count == 0 || values.empty()) continue; cannam@174: cannam@174: std::sort(values.begin(), values.end()); cannam@174: int sz = values.size(); cannam@174: cannam@174: summary.minimum = values[0]; cannam@174: summary.maximum = values[sz-1]; cannam@174: cannam@174: if (sz % 2 == 1) { cannam@174: summary.median = values[sz/2]; cannam@174: } else { cannam@174: summary.median = (values[sz/2] + values[sz/2 + 1]) / 2; cannam@174: } cannam@174: cannam@174: std::map distribution; cannam@174: cannam@174: for (int k = 0; k < sz; ++k) { cannam@174: summary.sum += values[k]; cannam@174: ++distribution[values[k]]; cannam@174: } cannam@174: cannam@174: int md = 0; cannam@174: cannam@174: //!!! I don't like this. Really the mode should be the cannam@174: //!!! value that spans the longest period of time, not the cannam@174: //!!! one that appears in the largest number of distinct cannam@175: //!!! features. I suppose that a median by time rather cannam@175: //!!! than number of features would also be useful. cannam@174: cannam@174: for (std::map::iterator di = distribution.begin(); cannam@174: di != distribution.end(); ++di) { cannam@174: if (di->second > md) { cannam@174: md = di->second; cannam@174: summary.mode = di->first; cannam@174: } cannam@174: } cannam@174: cannam@174: distribution.clear(); cannam@174: cannam@174: float mean = summary.sum / summary.count; cannam@174: cannam@174: for (int k = 0; k < sz; ++k) { cannam@174: summary.variance += (values[k] - mean) * (values[k] - mean); cannam@174: } cannam@174: summary.variance /= summary.count; cannam@174: cannam@174: m_summaries[output][segmentStart][bin] = summary; cannam@174: } cannam@174: } cannam@175: cannam@175: m_accumulators.clear(); cannam@174: } cannam@174: cannam@174: cannam@174: } cannam@174: cannam@174: } cannam@174: