cannam@233: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@233: cannam@233: /* cannam@233: Vamp cannam@233: cannam@233: An API for audio analysis and feature extraction plugins. cannam@233: cannam@233: Centre for Digital Music, Queen Mary, University of London. cannam@290: Copyright 2006-2009 Chris Cannam and QMUL. cannam@233: cannam@233: Permission is hereby granted, free of charge, to any person cannam@233: obtaining a copy of this software and associated documentation cannam@233: files (the "Software"), to deal in the Software without cannam@233: restriction, including without limitation the rights to use, copy, cannam@233: modify, merge, publish, distribute, sublicense, and/or sell copies cannam@233: of the Software, and to permit persons to whom the Software is cannam@233: furnished to do so, subject to the following conditions: cannam@233: cannam@233: The above copyright notice and this permission notice shall be cannam@233: included in all copies or substantial portions of the Software. cannam@233: cannam@233: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@233: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@233: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND cannam@233: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR cannam@233: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@233: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@233: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@233: cannam@233: Except as contained in this notice, the names of the Centre for cannam@233: Digital Music; Queen Mary, University of London; and Chris Cannam cannam@233: shall not be used in advertising or otherwise to promote the sale, cannam@233: use or other dealings in this Software without prior written cannam@233: authorization. cannam@233: */ cannam@233: cannam@233: #include cannam@233: cannam@233: #include cannam@233: #include cannam@233: #include cannam@233: #include cannam@233: Chris@418: using namespace std; Chris@418: cannam@273: //#define DEBUG_PLUGIN_SUMMARISING_ADAPTER 1 cannam@233: //#define DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT 1 cannam@233: cannam@263: _VAMP_SDK_HOSTSPACE_BEGIN(PluginSummarisingAdapter.cpp) cannam@263: cannam@233: namespace Vamp { cannam@233: cannam@233: namespace HostExt { cannam@233: cannam@233: class PluginSummarisingAdapter::Impl cannam@233: { cannam@233: public: cannam@233: Impl(Plugin *plugin, float inputSampleRate); cannam@233: ~Impl(); cannam@233: cannam@233: bool initialise(size_t channels, size_t stepSize, size_t blockSize); cannam@233: cannam@275: void reset(); cannam@275: cannam@233: FeatureSet process(const float *const *inputBuffers, RealTime timestamp); cannam@233: FeatureSet getRemainingFeatures(); cannam@233: cannam@233: void setSummarySegmentBoundaries(const SegmentBoundaries &); cannam@233: cannam@233: FeatureList getSummaryForOutput(int output, cannam@233: SummaryType type, cannam@233: AveragingMethod avg); cannam@233: cannam@233: FeatureSet getSummaryForAllOutputs(SummaryType type, cannam@233: AveragingMethod avg); cannam@233: cannam@233: protected: cannam@233: Plugin *m_plugin; cannam@233: float m_inputSampleRate; cannam@233: size_t m_stepSize; cannam@233: size_t m_blockSize; cannam@233: cannam@233: SegmentBoundaries m_boundaries; cannam@233: Chris@418: typedef vector ValueList; cannam@233: cannam@233: struct Result { // smaller than Feature cannam@233: RealTime time; cannam@233: RealTime duration; cannam@233: ValueList values; // bin number -> value cannam@233: }; cannam@233: Chris@418: typedef vector ResultList; cannam@233: cannam@233: struct OutputAccumulator { cannam@233: int bins; cannam@233: ResultList results; cannam@233: OutputAccumulator() : bins(0) { } cannam@233: }; cannam@233: Chris@418: typedef map OutputAccumulatorMap; cannam@233: OutputAccumulatorMap m_accumulators; // output number -> accumulator cannam@233: Chris@418: typedef map SegmentAccumulatorMap; Chris@418: typedef map OutputSegmentAccumulatorMap; cannam@233: OutputSegmentAccumulatorMap m_segmentedAccumulators; // output -> segmented cannam@233: Chris@418: typedef map OutputTimestampMap; cannam@233: OutputTimestampMap m_prevTimestamps; // output number -> timestamp cannam@233: OutputTimestampMap m_prevDurations; // output number -> durations cannam@233: cannam@233: struct OutputBinSummary { cannam@233: cannam@233: int count; cannam@233: cannam@233: // extents cannam@233: double minimum; cannam@233: double maximum; cannam@233: double sum; cannam@233: cannam@233: // sample-average results cannam@233: double median; cannam@233: double mode; cannam@233: double variance; cannam@233: cannam@233: // continuous-time average results cannam@233: double median_c; cannam@233: double mode_c; cannam@233: double mean_c; cannam@233: double variance_c; cannam@233: }; cannam@233: Chris@418: typedef map OutputSummary; Chris@418: typedef map SummarySegmentMap; Chris@418: typedef map OutputSummarySegmentMap; cannam@233: cannam@233: OutputSummarySegmentMap m_summaries; cannam@233: cannam@233: bool m_reduced; cannam@233: RealTime m_endTime; cannam@233: cannam@233: void accumulate(const FeatureSet &fs, RealTime, bool final); cannam@233: void accumulate(int output, const Feature &f, RealTime, bool final); cannam@233: void accumulateFinalDurations(); cannam@233: void findSegmentBounds(RealTime t, RealTime &start, RealTime &end); cannam@233: void segment(); cannam@233: void reduce(); cannam@233: Chris@418: string getSummaryLabel(SummaryType type, AveragingMethod avg); cannam@233: }; cannam@233: cannam@233: static RealTime INVALID_DURATION(INT_MIN, INT_MIN); cannam@233: cannam@233: PluginSummarisingAdapter::PluginSummarisingAdapter(Plugin *plugin) : cannam@233: PluginWrapper(plugin) cannam@233: { cannam@233: m_impl = new Impl(plugin, m_inputSampleRate); cannam@233: } cannam@233: cannam@233: PluginSummarisingAdapter::~PluginSummarisingAdapter() cannam@233: { cannam@233: delete m_impl; cannam@233: } cannam@233: cannam@233: bool cannam@233: PluginSummarisingAdapter::initialise(size_t channels, cannam@233: size_t stepSize, size_t blockSize) cannam@233: { cannam@233: return cannam@233: PluginWrapper::initialise(channels, stepSize, blockSize) && cannam@233: m_impl->initialise(channels, stepSize, blockSize); cannam@233: } cannam@233: cannam@275: void cannam@275: PluginSummarisingAdapter::reset() cannam@275: { cannam@275: m_impl->reset(); cannam@275: } cannam@275: cannam@233: Plugin::FeatureSet cannam@233: PluginSummarisingAdapter::process(const float *const *inputBuffers, RealTime timestamp) cannam@233: { cannam@233: return m_impl->process(inputBuffers, timestamp); cannam@233: } cannam@233: cannam@233: Plugin::FeatureSet cannam@233: PluginSummarisingAdapter::getRemainingFeatures() cannam@233: { cannam@233: return m_impl->getRemainingFeatures(); cannam@233: } cannam@233: cannam@233: void cannam@233: PluginSummarisingAdapter::setSummarySegmentBoundaries(const SegmentBoundaries &b) cannam@233: { cannam@233: m_impl->setSummarySegmentBoundaries(b); cannam@233: } cannam@233: cannam@233: Plugin::FeatureList cannam@233: PluginSummarisingAdapter::getSummaryForOutput(int output, cannam@233: SummaryType type, cannam@233: AveragingMethod avg) cannam@233: { cannam@233: return m_impl->getSummaryForOutput(output, type, avg); cannam@233: } cannam@233: cannam@233: Plugin::FeatureSet cannam@233: PluginSummarisingAdapter::getSummaryForAllOutputs(SummaryType type, cannam@233: AveragingMethod avg) cannam@233: { cannam@233: return m_impl->getSummaryForAllOutputs(type, avg); cannam@233: } cannam@233: cannam@233: PluginSummarisingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : cannam@233: m_plugin(plugin), cannam@233: m_inputSampleRate(inputSampleRate), cannam@233: m_reduced(false) cannam@233: { cannam@233: } cannam@233: cannam@233: PluginSummarisingAdapter::Impl::~Impl() cannam@233: { cannam@233: } cannam@233: cannam@233: bool Chris@385: PluginSummarisingAdapter::Impl::initialise(size_t, size_t stepSize, size_t blockSize) cannam@233: { cannam@233: m_stepSize = stepSize; cannam@233: m_blockSize = blockSize; cannam@233: return true; cannam@233: } cannam@233: cannam@275: void cannam@275: PluginSummarisingAdapter::Impl::reset() cannam@275: { cannam@275: m_accumulators.clear(); cannam@275: m_segmentedAccumulators.clear(); cannam@275: m_prevTimestamps.clear(); cannam@275: m_prevDurations.clear(); cannam@275: m_summaries.clear(); cannam@275: m_reduced = false; cannam@275: m_endTime = RealTime(); cannam@275: m_plugin->reset(); cannam@275: } cannam@275: cannam@233: Plugin::FeatureSet cannam@233: PluginSummarisingAdapter::Impl::process(const float *const *inputBuffers, cannam@233: RealTime timestamp) cannam@233: { cannam@233: if (m_reduced) { Chris@418: cerr << "WARNING: Cannot call PluginSummarisingAdapter::process() or getRemainingFeatures() after one of the getSummary methods" << endl; cannam@233: } cannam@233: FeatureSet fs = m_plugin->process(inputBuffers, timestamp); cannam@233: accumulate(fs, timestamp, false); cannam@233: m_endTime = timestamp + cannam@272: RealTime::frame2RealTime(m_stepSize, int(m_inputSampleRate + 0.5)); Chris@418: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "timestamp = " << timestamp << ", end time becomes " << m_endTime Chris@418: << endl; Chris@418: #endif cannam@233: return fs; cannam@233: } cannam@233: cannam@233: Plugin::FeatureSet cannam@233: PluginSummarisingAdapter::Impl::getRemainingFeatures() cannam@233: { cannam@233: if (m_reduced) { Chris@418: cerr << "WARNING: Cannot call PluginSummarisingAdapter::process() or getRemainingFeatures() after one of the getSummary methods" << endl; cannam@233: } cannam@233: FeatureSet fs = m_plugin->getRemainingFeatures(); cannam@233: accumulate(fs, m_endTime, true); cannam@233: return fs; cannam@233: } cannam@233: cannam@233: void cannam@233: PluginSummarisingAdapter::Impl::setSummarySegmentBoundaries(const SegmentBoundaries &b) cannam@233: { cannam@233: m_boundaries = b; cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "PluginSummarisingAdapter::setSummarySegmentBoundaries: boundaries are:" << endl; cannam@233: for (SegmentBoundaries::const_iterator i = m_boundaries.begin(); cannam@233: i != m_boundaries.end(); ++i) { Chris@418: cerr << *i << " "; cannam@233: } Chris@418: cerr << endl; cannam@233: #endif cannam@233: } cannam@233: cannam@233: Plugin::FeatureList cannam@233: PluginSummarisingAdapter::Impl::getSummaryForOutput(int output, cannam@233: SummaryType type, cannam@233: AveragingMethod avg) cannam@233: { cannam@233: if (!m_reduced) { cannam@233: accumulateFinalDurations(); cannam@233: segment(); cannam@233: reduce(); cannam@233: m_reduced = true; cannam@233: } cannam@233: cannam@233: bool continuous = (avg == ContinuousTimeAverage); cannam@233: cannam@233: FeatureList fl; cannam@233: for (SummarySegmentMap::const_iterator i = m_summaries[output].begin(); cannam@233: i != m_summaries[output].end(); ++i) { cannam@233: cannam@233: Feature f; cannam@233: cannam@233: f.hasTimestamp = true; cannam@233: f.timestamp = i->first; cannam@233: cannam@233: f.hasDuration = true; cannam@233: SummarySegmentMap::const_iterator ii = i; cannam@233: if (++ii == m_summaries[output].end()) { cannam@233: f.duration = m_endTime - f.timestamp; cannam@233: } else { cannam@233: f.duration = ii->first - f.timestamp; cannam@233: } cannam@233: cannam@233: f.label = getSummaryLabel(type, avg); cannam@233: cannam@233: for (OutputSummary::const_iterator j = i->second.begin(); cannam@233: j != i->second.end(); ++j) { cannam@233: cannam@233: // these will be ordered by bin number, and no bin numbers cannam@233: // will be missing except at the end (because of the way cannam@233: // the accumulators were initially filled in accumulate()) cannam@233: cannam@233: const OutputBinSummary &summary = j->second; cannam@233: double result = 0.f; cannam@233: cannam@233: switch (type) { cannam@233: cannam@233: case Minimum: cannam@233: result = summary.minimum; cannam@233: break; cannam@233: cannam@233: case Maximum: cannam@233: result = summary.maximum; cannam@233: break; cannam@233: cannam@233: case Mean: cannam@233: if (continuous) { cannam@233: result = summary.mean_c; cannam@233: } else if (summary.count) { cannam@233: result = summary.sum / summary.count; cannam@233: } cannam@233: break; cannam@233: cannam@233: case Median: cannam@233: if (continuous) result = summary.median_c; cannam@233: else result = summary.median; cannam@233: break; cannam@233: cannam@233: case Mode: cannam@233: if (continuous) result = summary.mode_c; cannam@233: else result = summary.mode; cannam@233: break; cannam@233: cannam@233: case Sum: cannam@233: result = summary.sum; cannam@233: break; cannam@233: cannam@233: case Variance: cannam@233: if (continuous) result = summary.variance_c; cannam@233: else result = summary.variance; cannam@233: break; cannam@233: cannam@233: case StandardDeviation: Chris@421: if (continuous) result = sqrt(summary.variance_c); Chris@421: else result = sqrt(summary.variance); cannam@233: break; cannam@233: cannam@233: case Count: cannam@233: result = summary.count; cannam@233: break; cannam@233: cannam@233: case UnknownSummaryType: cannam@233: break; cannam@233: cannam@233: default: cannam@233: break; cannam@233: } cannam@233: Chris@421: f.values.push_back(float(result)); cannam@233: } cannam@233: cannam@233: fl.push_back(f); cannam@233: } cannam@233: return fl; cannam@233: } cannam@233: cannam@233: Plugin::FeatureSet cannam@233: PluginSummarisingAdapter::Impl::getSummaryForAllOutputs(SummaryType type, cannam@233: AveragingMethod avg) cannam@233: { cannam@233: if (!m_reduced) { cannam@233: accumulateFinalDurations(); cannam@233: segment(); cannam@233: reduce(); cannam@233: m_reduced = true; cannam@233: } cannam@233: cannam@233: FeatureSet fs; cannam@233: for (OutputSummarySegmentMap::const_iterator i = m_summaries.begin(); cannam@233: i != m_summaries.end(); ++i) { cannam@233: fs[i->first] = getSummaryForOutput(i->first, type, avg); cannam@233: } cannam@233: return fs; cannam@233: } cannam@233: cannam@233: void cannam@233: PluginSummarisingAdapter::Impl::accumulate(const FeatureSet &fs, cannam@233: RealTime timestamp, cannam@233: bool final) cannam@233: { cannam@233: for (FeatureSet::const_iterator i = fs.begin(); i != fs.end(); ++i) { cannam@233: for (FeatureList::const_iterator j = i->second.begin(); cannam@233: j != i->second.end(); ++j) { cannam@233: if (j->hasTimestamp) { cannam@233: accumulate(i->first, *j, j->timestamp, final); cannam@233: } else { cannam@233: //!!! is this correct? cannam@233: accumulate(i->first, *j, timestamp, final); cannam@233: } cannam@233: } cannam@233: } cannam@233: } cannam@233: Chris@418: string cannam@233: PluginSummarisingAdapter::Impl::getSummaryLabel(SummaryType type, cannam@233: AveragingMethod avg) cannam@233: { Chris@418: string label; Chris@418: string avglabel; cannam@233: cannam@233: if (avg == SampleAverage) avglabel = ", sample average"; cannam@233: else avglabel = ", continuous-time average"; cannam@233: cannam@233: switch (type) { cannam@233: case Minimum: label = "(minimum value)"; break; cannam@233: case Maximum: label = "(maximum value)"; break; cannam@233: case Mean: label = "(mean value" + avglabel + ")"; break; cannam@233: case Median: label = "(median value" + avglabel + ")"; break; cannam@233: case Mode: label = "(modal value" + avglabel + ")"; break; cannam@233: case Sum: label = "(sum)"; break; cannam@233: case Variance: label = "(variance" + avglabel + ")"; break; cannam@233: case StandardDeviation: label = "(standard deviation" + avglabel + ")"; break; cannam@233: case Count: label = "(count)"; break; cannam@233: case UnknownSummaryType: label = "(unknown summary)"; break; cannam@233: } cannam@233: cannam@233: return label; cannam@233: } cannam@233: cannam@233: void cannam@233: PluginSummarisingAdapter::Impl::accumulate(int output, cannam@233: const Feature &f, cannam@233: RealTime timestamp, Chris@418: bool Chris@418: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: final Chris@418: #endif Chris@418: ) cannam@233: { cannam@248: // What should happen if a feature's duration spans a segment cannam@248: // boundary? I think we probably want to chop it, and pretend cannam@248: // that it appears in both. A very long feature (e.g. key, if the cannam@248: // whole audio is in a single key) might span many or all cannam@248: // segments, and we want that to be reflected in the results cannam@248: // (e.g. it is the modal key in all of those segments, not just cannam@248: // the first). This is actually quite complicated to do. cannam@233: cannam@248: // If features spanning a boundary should be chopped, then we need cannam@248: // to have per-segment accumulators (and the feature value goes cannam@248: // into both -- with a separate phase to split the accumulator up cannam@248: // into segments). cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "output " << output << ": timestamp " << timestamp << ", prev timestamp " << m_prevTimestamps[output] << ", final " << final << endl; cannam@233: #endif cannam@233: cannam@233: // At each process step, accumulate() is called once for each cannam@233: // feature on each output within that process's returned feature cannam@233: // list, and with the timestamp passed in being that of the start cannam@233: // of the process block. cannam@233: cannam@233: // At the end (in getRemainingFeatures), accumulate() is called cannam@233: // once for each feature on each output within the feature list cannam@233: // returned by getRemainingFeatures, and with the timestamp being cannam@233: // the same as the last process block and final set to true. cannam@233: cannam@233: // (What if getRemainingFeatures doesn't return any features? We cannam@233: // still need to ensure that the final duration is written. Need cannam@233: // a separate function to close the durations.) cannam@233: cannam@233: // At each call, we pull out the value for the feature and stuff cannam@233: // it into the accumulator's appropriate values array; and we cannam@233: // calculate the duration for the _previous_ feature, or pull it cannam@233: // from the prevDurations array if the previous feature had a cannam@233: // duration in its structure, and stuff that into the cannam@233: // accumulator's appropriate durations array. cannam@233: cannam@233: if (m_prevDurations.find(output) != m_prevDurations.end()) { cannam@233: cannam@233: // Not the first time accumulate has been called for this cannam@233: // output -- there has been a previous feature cannam@233: cannam@233: RealTime prevDuration; cannam@233: cannam@233: // Note that m_prevDurations[output] only contains the cannam@233: // duration field that was contained in the previous feature. cannam@233: // If it didn't have an explicit duration, cannam@233: // m_prevDurations[output] should be INVALID_DURATION and we cannam@233: // will have to calculate the duration from the previous and cannam@233: // current timestamps. cannam@233: cannam@233: if (m_prevDurations[output] != INVALID_DURATION) { cannam@233: prevDuration = m_prevDurations[output]; cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "Previous duration from previous feature: " << prevDuration << endl; cannam@233: #endif cannam@233: } else { cannam@233: prevDuration = timestamp - m_prevTimestamps[output]; cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "Previous duration from diff: " << timestamp << " - " Chris@418: << m_prevTimestamps[output] << endl; cannam@233: #endif cannam@233: } cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "output " << output << ": "; Chris@418: cerr << "Pushing previous duration as " << prevDuration << endl; cannam@233: #endif cannam@233: cannam@233: m_accumulators[output].results cannam@233: [m_accumulators[output].results.size() - 1] cannam@233: .duration = prevDuration; cannam@233: } cannam@233: cannam@233: if (f.hasDuration) m_prevDurations[output] = f.duration; cannam@233: else m_prevDurations[output] = INVALID_DURATION; cannam@233: cannam@233: m_prevTimestamps[output] = timestamp; cannam@233: cannam@233: if (f.hasDuration) { cannam@233: RealTime et = timestamp; cannam@233: et = et + f.duration; cannam@233: if (et > m_endTime) m_endTime = et; Chris@418: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "feature has duration, updating end time to " << m_endTime << endl; Chris@418: #endif cannam@233: } cannam@233: cannam@233: Result result; cannam@233: result.time = timestamp; cannam@233: result.duration = INVALID_DURATION; cannam@233: cannam@265: if (int(f.values.size()) > m_accumulators[output].bins) { Chris@421: m_accumulators[output].bins = int(f.values.size()); cannam@233: } cannam@233: cannam@233: for (int i = 0; i < int(f.values.size()); ++i) { cannam@233: result.values.push_back(f.values[i]); cannam@233: } cannam@233: cannam@233: m_accumulators[output].results.push_back(result); cannam@233: } cannam@233: cannam@233: void cannam@233: PluginSummarisingAdapter::Impl::accumulateFinalDurations() cannam@233: { cannam@233: for (OutputTimestampMap::iterator i = m_prevTimestamps.begin(); cannam@233: i != m_prevTimestamps.end(); ++i) { cannam@233: cannam@233: int output = i->first; cannam@233: Chris@421: int acount = int(m_accumulators[output].results.size()); cannam@233: cannam@233: if (acount == 0) continue; cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "output " << output << ": "; cannam@233: #endif cannam@233: cannam@233: if (m_prevDurations.find(output) != m_prevDurations.end() && cannam@233: m_prevDurations[output] != INVALID_DURATION) { cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "Pushing final duration from feature as " << m_prevDurations[output] << endl; cannam@233: #endif cannam@233: cannam@233: m_accumulators[output].results[acount - 1].duration = cannam@233: m_prevDurations[output]; cannam@233: cannam@233: } else { cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "Pushing final duration from diff as " << m_endTime << " - " << m_prevTimestamps[output] << endl; cannam@233: #endif cannam@233: cannam@233: m_accumulators[output].results[acount - 1].duration = cannam@233: m_endTime - m_prevTimestamps[output]; cannam@233: } cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "so duration for result no " << acount-1 << " is " cannam@233: << m_accumulators[output].results[acount-1].duration Chris@418: << endl; cannam@233: #endif cannam@233: } cannam@233: } cannam@233: cannam@233: void cannam@233: PluginSummarisingAdapter::Impl::findSegmentBounds(RealTime t, cannam@233: RealTime &start, cannam@233: RealTime &end) cannam@233: { cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT Chris@418: cerr << "findSegmentBounds: t = " << t << endl; cannam@233: #endif cannam@233: Chris@418: SegmentBoundaries::const_iterator i = upper_bound cannam@233: (m_boundaries.begin(), m_boundaries.end(), t); cannam@233: cannam@233: start = RealTime::zeroTime; cannam@233: end = m_endTime; cannam@233: cannam@233: if (i != m_boundaries.end()) { cannam@233: end = *i; cannam@233: } cannam@233: cannam@233: if (i != m_boundaries.begin()) { cannam@233: start = *--i; cannam@233: } cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT Chris@418: cerr << "findSegmentBounds: " << t << " is in segment " << start << " -> " << end << endl; cannam@233: #endif cannam@233: } cannam@233: cannam@233: void cannam@233: PluginSummarisingAdapter::Impl::segment() cannam@233: { cannam@274: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT Chris@418: cerr << "segment: starting" << endl; cannam@274: #endif cannam@274: cannam@233: for (OutputAccumulatorMap::iterator i = m_accumulators.begin(); cannam@233: i != m_accumulators.end(); ++i) { cannam@233: cannam@233: int output = i->first; cannam@233: OutputAccumulator &source = i->second; cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT Chris@418: cerr << "segment: total results for output " << output << " = " Chris@418: << source.results.size() << endl; cannam@233: #endif cannam@233: cannam@248: // This is basically nonsense if the results have no values cannam@248: // (i.e. their times and counts are the only things of cannam@248: // interest)... but perhaps it's the user's problem if they cannam@248: // ask for segmentation (or any summary at all) in that case cannam@233: cannam@265: for (int n = 0; n < int(source.results.size()); ++n) { cannam@233: cannam@233: // This result spans source.results[n].time to cannam@233: // source.results[n].time + source.results[n].duration. cannam@233: // We need to dispose it into segments appropriately cannam@233: cannam@233: RealTime resultStart = source.results[n].time; cannam@233: RealTime resultEnd = resultStart + source.results[n].duration; cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT Chris@418: cerr << "output: " << output << ", result start = " << resultStart << ", end = " << resultEnd << endl; cannam@233: #endif cannam@233: cannam@233: RealTime segmentStart = RealTime::zeroTime; cannam@233: RealTime segmentEnd = resultEnd - RealTime(1, 0); cannam@233: cannam@274: RealTime prevSegmentStart = segmentStart - RealTime(1, 0); cannam@274: cannam@233: while (segmentEnd < resultEnd) { cannam@233: cannam@274: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT Chris@418: cerr << "segment end " << segmentEnd << " < result end " Chris@418: << resultEnd << " (with result start " << resultStart << ")" << endl; cannam@274: #endif cannam@274: cannam@233: findSegmentBounds(resultStart, segmentStart, segmentEnd); cannam@274: cannam@274: if (segmentStart == prevSegmentStart) { cannam@274: // This can happen when we reach the end of the cannam@274: // input, if a feature's end time overruns the cannam@274: // input audio end time cannam@274: break; cannam@274: } cannam@274: prevSegmentStart = segmentStart; cannam@233: cannam@233: RealTime chunkStart = resultStart; cannam@233: if (chunkStart < segmentStart) chunkStart = segmentStart; cannam@233: cannam@233: RealTime chunkEnd = resultEnd; cannam@233: if (chunkEnd > segmentEnd) chunkEnd = segmentEnd; cannam@233: cannam@233: m_segmentedAccumulators[output][segmentStart].bins = source.bins; cannam@233: cannam@233: Result chunk; cannam@233: chunk.time = chunkStart; cannam@233: chunk.duration = chunkEnd - chunkStart; cannam@233: chunk.values = source.results[n].values; cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT Chris@418: cerr << "chunk for segment " << segmentStart << ": from " << chunk.time << ", duration " << chunk.duration << endl; cannam@233: #endif cannam@233: cannam@233: m_segmentedAccumulators[output][segmentStart].results cannam@233: .push_back(chunk); cannam@233: cannam@233: resultStart = chunkEnd; cannam@233: } cannam@233: } cannam@233: } cannam@233: } cannam@233: cannam@233: struct ValueDurationFloatPair cannam@233: { cannam@233: float value; cannam@233: float duration; cannam@233: cannam@233: ValueDurationFloatPair() : value(0), duration(0) { } cannam@233: ValueDurationFloatPair(float v, float d) : value(v), duration(d) { } cannam@233: ValueDurationFloatPair &operator=(const ValueDurationFloatPair &p) { cannam@233: value = p.value; cannam@233: duration = p.duration; cannam@233: return *this; cannam@233: } cannam@233: bool operator<(const ValueDurationFloatPair &p) const { cannam@233: return value < p.value; cannam@233: } cannam@233: }; cannam@233: cannam@233: static double toSec(const RealTime &r) cannam@233: { cannam@233: return r.sec + double(r.nsec) / 1000000000.0; cannam@233: } cannam@233: cannam@233: void cannam@233: PluginSummarisingAdapter::Impl::reduce() cannam@233: { cannam@233: for (OutputSegmentAccumulatorMap::iterator i = cannam@233: m_segmentedAccumulators.begin(); cannam@233: i != m_segmentedAccumulators.end(); ++i) { cannam@233: cannam@233: int output = i->first; cannam@233: SegmentAccumulatorMap &segments = i->second; cannam@233: cannam@233: for (SegmentAccumulatorMap::iterator j = segments.begin(); cannam@233: j != segments.end(); ++j) { cannam@233: cannam@233: RealTime segmentStart = j->first; cannam@233: OutputAccumulator &accumulator = j->second; cannam@233: Chris@421: int sz = int(accumulator.results.size()); cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "reduce: segment starting at " << segmentStart Chris@418: << " on output " << output << " has " << sz << " result(s)" << endl; cannam@233: #endif cannam@233: cannam@233: double totalDuration = 0.0; cannam@233: //!!! is this right? cannam@233: if (sz > 0) { cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "last time = " << accumulator.results[sz-1].time cannam@233: << ", duration = " << accumulator.results[sz-1].duration cannam@233: << " (step = " << m_stepSize << ", block = " << m_blockSize << ")" Chris@418: << endl; cannam@233: #endif cannam@233: totalDuration = toSec((accumulator.results[sz-1].time + cannam@233: accumulator.results[sz-1].duration) - cannam@233: segmentStart); cannam@233: } cannam@233: cannam@233: for (int bin = 0; bin < accumulator.bins; ++bin) { cannam@233: cannam@233: // work on all values over time for a single bin cannam@233: Chris@418: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "bin " << bin << ":" << endl; Chris@418: #endif Chris@418: cannam@233: OutputBinSummary summary; cannam@233: cannam@233: summary.count = sz; cannam@233: cannam@233: summary.minimum = 0.f; cannam@233: summary.maximum = 0.f; cannam@233: cannam@233: summary.median = 0.f; cannam@233: summary.mode = 0.f; cannam@233: summary.sum = 0.f; cannam@233: summary.variance = 0.f; cannam@233: cannam@233: summary.median_c = 0.f; cannam@233: summary.mode_c = 0.f; cannam@233: summary.mean_c = 0.f; cannam@233: summary.variance_c = 0.f; cannam@233: cannam@233: if (sz == 0) continue; cannam@233: Chris@418: vector valvec; cannam@233: cannam@233: for (int k = 0; k < sz; ++k) { cannam@265: while (int(accumulator.results[k].values.size()) < cannam@233: accumulator.bins) { cannam@233: accumulator.results[k].values.push_back(0.f); cannam@233: } cannam@233: } cannam@233: cannam@233: for (int k = 0; k < sz; ++k) { cannam@233: float value = accumulator.results[k].values[bin]; Chris@421: valvec.push_back Chris@421: (ValueDurationFloatPair Chris@421: (value, Chris@421: float(toSec(accumulator.results[k].duration)))); cannam@233: } cannam@233: Chris@418: sort(valvec.begin(), valvec.end()); cannam@233: cannam@233: summary.minimum = valvec[0].value; cannam@233: summary.maximum = valvec[sz-1].value; cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "total duration = " << totalDuration << endl; cannam@233: #endif cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@233: /* Chris@418: cerr << "value vector for medians:" << endl; cannam@233: for (int k = 0; k < sz; ++k) { Chris@418: cerr << "(" << valvec[k].value << "," << valvec[k].duration << ") "; cannam@233: } Chris@418: cerr << endl; cannam@233: */ cannam@233: #endif cannam@233: cannam@233: if (sz % 2 == 1) { cannam@233: summary.median = valvec[sz/2].value; cannam@233: } else { cannam@233: summary.median = (valvec[sz/2].value + valvec[sz/2 + 1].value) / 2; cannam@233: } cannam@233: cannam@233: double duracc = 0.0; cannam@233: summary.median_c = valvec[sz-1].value; cannam@233: cannam@233: for (int k = 0; k < sz; ++k) { cannam@233: duracc += valvec[k].duration; cannam@233: if (duracc > totalDuration/2) { cannam@233: summary.median_c = valvec[k].value; cannam@233: break; cannam@233: } cannam@233: } cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "median_c = " << summary.median_c << endl; Chris@418: cerr << "median = " << summary.median << endl; cannam@233: #endif cannam@233: Chris@418: map distribution; cannam@233: Chris@444: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@444: cerr << "summing (discrete): "; Chris@444: #endif cannam@233: for (int k = 0; k < sz; ++k) { Chris@444: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@444: cerr << accumulator.results[k].values[bin] << " "; Chris@444: #endif cannam@233: summary.sum += accumulator.results[k].values[bin]; cannam@233: distribution[accumulator.results[k].values[bin]] += 1; cannam@233: } Chris@444: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@444: cerr << endl; Chris@444: #endif cannam@233: cannam@233: int md = 0; cannam@233: Chris@418: for (map::iterator di = distribution.begin(); cannam@233: di != distribution.end(); ++di) { cannam@233: if (di->second > md) { cannam@233: md = di->second; cannam@233: summary.mode = di->first; cannam@233: } cannam@233: } cannam@233: cannam@233: distribution.clear(); cannam@233: Chris@418: map distribution_c; cannam@233: cannam@233: for (int k = 0; k < sz; ++k) { cannam@233: distribution_c[accumulator.results[k].values[bin]] cannam@233: += toSec(accumulator.results[k].duration); cannam@233: } cannam@233: cannam@233: double mrd = 0.0; cannam@233: Chris@418: for (map::iterator di = distribution_c.begin(); cannam@233: di != distribution_c.end(); ++di) { cannam@233: if (di->second > mrd) { cannam@497: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@497: cerr << "element " << di->first << " spans time " cannam@497: << di->second << " and so is an improved mode " cannam@497: << "candidate over element " << summary.mode_c cannam@497: << " which spanned " << mrd << endl; cannam@497: #endif cannam@233: mrd = di->second; cannam@233: summary.mode_c = di->first; cannam@233: } cannam@233: } cannam@233: cannam@233: distribution_c.clear(); cannam@233: cannam@233: if (totalDuration > 0.0) { cannam@233: cannam@233: double sum_c = 0.0; cannam@233: Chris@444: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@444: cerr << "summing (continuous): "; Chris@444: #endif cannam@233: for (int k = 0; k < sz; ++k) { Chris@444: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@444: cerr << accumulator.results[k].values[bin] << "*" Chris@444: << toSec(accumulator.results[k].duration) << " "; Chris@444: #endif cannam@233: double value = accumulator.results[k].values[bin] cannam@233: * toSec(accumulator.results[k].duration); cannam@233: sum_c += value; cannam@233: } Chris@444: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@444: cerr << endl; Chris@444: #endif cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "mean_c = " << sum_c << " / " << totalDuration << " = " Chris@418: << sum_c / totalDuration << " (sz = " << sz << ")" << endl; cannam@233: #endif cannam@233: cannam@233: summary.mean_c = sum_c / totalDuration; cannam@233: cannam@233: for (int k = 0; k < sz; ++k) { cannam@233: double value = accumulator.results[k].values[bin]; cannam@233: // * toSec(accumulator.results[k].duration); cannam@233: summary.variance_c += cannam@233: (value - summary.mean_c) * (value - summary.mean_c) cannam@233: * toSec(accumulator.results[k].duration); cannam@233: } cannam@233: cannam@233: // summary.variance_c /= summary.count; cannam@233: summary.variance_c /= totalDuration; cannam@233: } cannam@233: cannam@233: double mean = summary.sum / summary.count; cannam@233: cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER Chris@418: cerr << "mean = " << summary.sum << " / " << summary.count << " = " Chris@418: << summary.sum / summary.count << endl; cannam@233: #endif cannam@233: cannam@233: for (int k = 0; k < sz; ++k) { cannam@233: float value = accumulator.results[k].values[bin]; cannam@233: summary.variance += (value - mean) * (value - mean); cannam@233: } cannam@233: summary.variance /= summary.count; cannam@233: cannam@233: m_summaries[output][segmentStart][bin] = summary; cannam@233: } cannam@233: } cannam@233: } cannam@233: cannam@233: m_segmentedAccumulators.clear(); cannam@233: m_accumulators.clear(); cannam@233: } cannam@233: cannam@233: cannam@233: } cannam@233: cannam@233: } cannam@233: cannam@263: _VAMP_SDK_HOSTSPACE_END(PluginSummarisingAdapter.cpp) cannam@263: