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@205: #include cannam@175: #include cannam@184: #include cannam@174: cannam@195: #define DEBUG_PLUGIN_SUMMARISING_ADAPTER 1 cannam@195: //#define DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT 1 cannam@194: 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@195: bool initialise(size_t channels, size_t stepSize, size_t blockSize); cannam@195: 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@180: FeatureList getSummaryForOutput(int output, cannam@180: SummaryType type, cannam@180: AveragingMethod avg); cannam@180: cannam@180: FeatureSet getSummaryForAllOutputs(SummaryType type, cannam@180: AveragingMethod avg); cannam@173: cannam@173: protected: cannam@174: Plugin *m_plugin; cannam@181: float m_inputSampleRate; cannam@195: size_t m_stepSize; cannam@195: size_t m_blockSize; cannam@174: cannam@173: SegmentBoundaries m_boundaries; cannam@185: cannam@174: typedef std::vector ValueList; cannam@185: cannam@185: struct Result { // smaller than Feature cannam@185: RealTime time; cannam@185: RealTime duration; cannam@185: ValueList values; // bin number -> value cannam@185: }; cannam@185: cannam@185: typedef std::vector ResultList; cannam@185: cannam@174: struct OutputAccumulator { cannam@185: int bins; cannam@185: ResultList results; cannam@185: OutputAccumulator() : bins(0) { } cannam@174: }; cannam@174: cannam@174: typedef std::map OutputAccumulatorMap; cannam@180: OutputAccumulatorMap m_accumulators; // output number -> accumulator cannam@180: cannam@185: typedef std::map SegmentAccumulatorMap; cannam@185: typedef std::map OutputSegmentAccumulatorMap; cannam@187: OutputSegmentAccumulatorMap m_segmentedAccumulators; // output -> segmented cannam@185: cannam@180: typedef std::map OutputTimestampMap; cannam@180: OutputTimestampMap m_prevTimestamps; // output number -> timestamp cannam@183: OutputTimestampMap m_prevDurations; // output number -> durations cannam@174: cannam@174: struct OutputBinSummary { cannam@180: cannam@180: int count; cannam@180: cannam@180: // extents cannam@195: double minimum; cannam@195: double maximum; cannam@195: double sum; cannam@180: cannam@180: // sample-average results cannam@195: double median; cannam@195: double mode; cannam@195: double variance; cannam@180: cannam@180: // continuous-time average results cannam@195: double median_c; cannam@195: double mode_c; cannam@195: double mean_c; cannam@195: double variance_c; 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@183: bool m_reduced; cannam@195: RealTime m_endTime; cannam@174: cannam@180: void accumulate(const FeatureSet &fs, RealTime, bool final); cannam@180: void accumulate(int output, const Feature &f, RealTime, bool final); cannam@184: void accumulateFinalDurations(); cannam@186: void findSegmentBounds(RealTime t, RealTime &start, RealTime &end); cannam@185: void segment(); cannam@174: void reduce(); cannam@195: cannam@195: std::string getSummaryLabel(SummaryType type, AveragingMethod avg); cannam@173: }; cannam@184: cannam@184: static RealTime INVALID_DURATION(INT_MIN, INT_MIN); 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@195: bool cannam@195: PluginSummarisingAdapter::initialise(size_t channels, cannam@195: size_t stepSize, size_t blockSize) cannam@195: { cannam@195: return cannam@195: PluginWrapper::initialise(channels, stepSize, blockSize) && cannam@195: m_impl->initialise(channels, stepSize, blockSize); cannam@195: } cannam@195: 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@187: void cannam@187: PluginSummarisingAdapter::setSummarySegmentBoundaries(const SegmentBoundaries &b) cannam@187: { cannam@187: m_impl->setSummarySegmentBoundaries(b); cannam@187: } cannam@187: cannam@175: Plugin::FeatureList cannam@180: PluginSummarisingAdapter::getSummaryForOutput(int output, cannam@180: SummaryType type, cannam@180: AveragingMethod avg) cannam@175: { cannam@180: return m_impl->getSummaryForOutput(output, type, avg); cannam@176: } cannam@176: cannam@176: Plugin::FeatureSet cannam@180: PluginSummarisingAdapter::getSummaryForAllOutputs(SummaryType type, cannam@180: AveragingMethod avg) cannam@176: { cannam@180: return m_impl->getSummaryForAllOutputs(type, avg); cannam@175: } cannam@173: cannam@173: PluginSummarisingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : cannam@181: m_plugin(plugin), cannam@183: m_inputSampleRate(inputSampleRate), cannam@183: m_reduced(false) cannam@173: { cannam@173: } cannam@173: cannam@173: PluginSummarisingAdapter::Impl::~Impl() cannam@173: { cannam@173: } cannam@173: cannam@195: bool cannam@195: PluginSummarisingAdapter::Impl::initialise(size_t channels, cannam@195: size_t stepSize, size_t blockSize) cannam@195: { cannam@195: m_stepSize = stepSize; cannam@195: m_blockSize = blockSize; cannam@195: return true; cannam@195: } cannam@195: cannam@174: Plugin::FeatureSet cannam@195: PluginSummarisingAdapter::Impl::process(const float *const *inputBuffers, cannam@195: RealTime timestamp) cannam@174: { cannam@183: if (m_reduced) { cannam@183: std::cerr << "WARNING: Cannot call PluginSummarisingAdapter::process() or getRemainingFeatures() after one of the getSummary methods" << std::endl; cannam@183: } cannam@174: FeatureSet fs = m_plugin->process(inputBuffers, timestamp); cannam@180: accumulate(fs, timestamp, false); cannam@195: m_endTime = timestamp + cannam@195: RealTime::frame2RealTime(m_stepSize, m_inputSampleRate); cannam@174: return fs; cannam@174: } cannam@174: cannam@174: Plugin::FeatureSet cannam@174: PluginSummarisingAdapter::Impl::getRemainingFeatures() cannam@174: { cannam@183: if (m_reduced) { cannam@183: std::cerr << "WARNING: Cannot call PluginSummarisingAdapter::process() or getRemainingFeatures() after one of the getSummary methods" << std::endl; cannam@183: } cannam@174: FeatureSet fs = m_plugin->getRemainingFeatures(); cannam@195: accumulate(fs, m_endTime, true); cannam@174: return fs; cannam@174: } cannam@174: cannam@187: void cannam@187: PluginSummarisingAdapter::Impl::setSummarySegmentBoundaries(const SegmentBoundaries &b) cannam@187: { cannam@187: m_boundaries = b; cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@187: std::cerr << "PluginSummarisingAdapter::setSummarySegmentBoundaries: boundaries are:" << std::endl; cannam@187: for (SegmentBoundaries::const_iterator i = m_boundaries.begin(); cannam@187: i != m_boundaries.end(); ++i) { cannam@187: std::cerr << *i << " "; cannam@187: } cannam@187: std::cerr << std::endl; cannam@194: #endif cannam@187: } cannam@187: cannam@175: Plugin::FeatureList cannam@180: PluginSummarisingAdapter::Impl::getSummaryForOutput(int output, cannam@180: SummaryType type, cannam@180: AveragingMethod avg) cannam@175: { cannam@185: if (!m_reduced) { cannam@188: accumulateFinalDurations(); cannam@185: segment(); cannam@185: reduce(); cannam@185: m_reduced = true; cannam@185: } cannam@183: cannam@180: bool continuous = (avg == ContinuousTimeAverage); cannam@180: 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@195: cannam@175: f.hasTimestamp = true; cannam@175: f.timestamp = i->first; cannam@195: cannam@195: f.hasDuration = true; cannam@195: SummarySegmentMap::const_iterator ii = i; cannam@195: if (++ii == m_summaries[output].end()) { cannam@195: f.duration = m_endTime - f.timestamp; cannam@195: } else { cannam@195: f.duration = ii->first - f.timestamp; cannam@195: } cannam@195: cannam@195: f.label = getSummaryLabel(type, avg); 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@195: double 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@180: if (continuous) { cannam@180: result = summary.mean_c; cannam@180: } else if (summary.count) { cannam@175: result = summary.sum / summary.count; cannam@175: } cannam@175: break; cannam@175: cannam@175: case Median: cannam@180: if (continuous) result = summary.median_c; cannam@180: else result = summary.median; cannam@175: break; cannam@175: cannam@175: case Mode: cannam@180: if (continuous) result = summary.mode_c; cannam@180: else 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@180: if (continuous) result = summary.variance_c; cannam@180: else result = summary.variance; cannam@175: break; cannam@175: cannam@175: case StandardDeviation: cannam@180: if (continuous) result = sqrtf(summary.variance_c); cannam@180: else result = sqrtf(summary.variance); cannam@175: break; cannam@175: cannam@175: case Count: cannam@175: result = summary.count; cannam@175: break; cannam@180: cannam@191: case UnknownSummaryType: cannam@191: break; cannam@191: cannam@180: default: cannam@180: 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@180: PluginSummarisingAdapter::Impl::getSummaryForAllOutputs(SummaryType type, cannam@180: AveragingMethod avg) cannam@176: { cannam@185: if (!m_reduced) { cannam@188: accumulateFinalDurations(); cannam@185: segment(); cannam@185: reduce(); cannam@185: m_reduced = true; cannam@185: } cannam@183: cannam@176: FeatureSet fs; cannam@176: for (OutputSummarySegmentMap::const_iterator i = m_summaries.begin(); cannam@176: i != m_summaries.end(); ++i) { cannam@180: fs[i->first] = getSummaryForOutput(i->first, type, avg); cannam@176: } cannam@176: return fs; cannam@176: } cannam@176: cannam@174: void cannam@174: PluginSummarisingAdapter::Impl::accumulate(const FeatureSet &fs, cannam@180: RealTime timestamp, cannam@180: bool final) 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@182: if (j->hasTimestamp) { cannam@182: accumulate(i->first, *j, j->timestamp, final); cannam@182: } else { cannam@182: //!!! is this correct? cannam@182: accumulate(i->first, *j, timestamp, final); cannam@182: } cannam@174: } cannam@174: } cannam@174: } cannam@174: cannam@195: std::string cannam@195: PluginSummarisingAdapter::Impl::getSummaryLabel(SummaryType type, cannam@195: AveragingMethod avg) cannam@195: { cannam@195: std::string label; cannam@195: std::string avglabel; cannam@195: cannam@195: if (avg == SampleAverage) avglabel = ", sample average"; cannam@195: else avglabel = ", continuous-time average"; cannam@195: cannam@195: switch (type) { cannam@195: case Minimum: label = "(minimum value)"; break; cannam@195: case Maximum: label = "(maximum value)"; break; cannam@195: case Mean: label = "(mean value" + avglabel + ")"; break; cannam@195: case Median: label = "(median value" + avglabel + ")"; break; cannam@195: case Mode: label = "(modal value" + avglabel + ")"; break; cannam@195: case Sum: label = "(sum)"; break; cannam@195: case Variance: label = "(variance" + avglabel + ")"; break; cannam@195: case StandardDeviation: label = "(standard deviation" + avglabel + ")"; break; cannam@195: case Count: label = "(count)"; break; cannam@195: case UnknownSummaryType: label = "(unknown summary)"; break; cannam@195: } cannam@195: cannam@195: return label; cannam@195: } cannam@195: cannam@174: void cannam@174: PluginSummarisingAdapter::Impl::accumulate(int output, cannam@174: const Feature &f, cannam@180: RealTime timestamp, cannam@180: bool final) cannam@174: { cannam@180: //!!! to do: use timestamp to determine which segment we're on cannam@185: cannam@185: //!!! What should happen if a feature's duration spans a segment cannam@185: // boundary? I think we probably want to chop it, and pretend that it cannam@185: // appears in both -- don't we? do we? A very long feature (e.g. key, cannam@185: // if the whole audio is in a single key) might span many or all cannam@185: // segments, and we want that to be reflected in the results (e.g. it cannam@185: // is the modal key in all of those segments, not just the first). cannam@185: // That is actually quite complicated to do! cannam@180: cannam@185: //!!! This affects how we record things. If features spanning a cannam@185: // boundary should be chopped, then we need to have per-segment cannam@185: // accumulators (and the feature value goes into both -- perhaps we cannam@185: // need a separate phase to split the accumulator up into segments). cannam@185: // If features spanning a boundary should be counted only in the first cannam@185: // segment, with their full duration, then we should store them in a cannam@185: // single accumulator and distribute into segments only on reduce. cannam@180: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@184: std::cerr << "output " << output << ": timestamp " << timestamp << ", prev timestamp " << m_prevTimestamps[output] << ", final " << final << std::endl; cannam@194: #endif cannam@182: cannam@184: // At each process step, accumulate() is called once for each cannam@184: // feature on each output within that process's returned feature cannam@184: // list, and with the timestamp passed in being that of the start cannam@184: // of the process block. cannam@182: cannam@184: // At the end (in getRemainingFeatures), accumulate() is called cannam@184: // once for each feature on each output within the feature list cannam@184: // returned by getRemainingFeatures, and with the timestamp being cannam@184: // the same as the last process block and final set to true. cannam@184: cannam@184: // (What if getRemainingFeatures doesn't return any features? We cannam@184: // still need to ensure that the final duration is written. Need cannam@184: // a separate function to close the durations.) cannam@184: cannam@184: // At each call, we pull out the value for the feature and stuff cannam@184: // it into the accumulator's appropriate values array; and we cannam@184: // calculate the duration for the _previous_ feature, or pull it cannam@184: // from the prevDurations array if the previous feature had a cannam@184: // duration in its structure, and stuff that into the cannam@184: // accumulator's appropriate durations array. cannam@184: cannam@184: if (m_prevDurations.find(output) != m_prevDurations.end()) { cannam@184: cannam@184: // Not the first time accumulate has been called for this cannam@184: // output -- there has been a previous feature cannam@184: cannam@184: RealTime prevDuration; cannam@184: cannam@184: // Note that m_prevDurations[output] only contains the cannam@184: // duration field that was contained in the previous feature. cannam@184: // If it didn't have an explicit duration, cannam@184: // m_prevDurations[output] should be INVALID_DURATION and we cannam@184: // will have to calculate the duration from the previous and cannam@184: // current timestamps. cannam@184: cannam@184: if (m_prevDurations[output] != INVALID_DURATION) { cannam@184: prevDuration = m_prevDurations[output]; cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@184: std::cerr << "Previous duration from previous feature: " << prevDuration << std::endl; cannam@194: #endif cannam@184: } else { cannam@184: prevDuration = timestamp - m_prevTimestamps[output]; cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@184: std::cerr << "Previous duration from diff: " << timestamp << " - " cannam@184: << m_prevTimestamps[output] << std::endl; cannam@194: #endif cannam@180: } cannam@184: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@184: std::cerr << "output " << output << ": "; cannam@184: std::cerr << "Pushing previous duration as " << prevDuration << std::endl; cannam@194: #endif cannam@185: cannam@185: m_accumulators[output].results cannam@185: [m_accumulators[output].results.size() - 1] cannam@185: .duration = prevDuration; cannam@180: } cannam@180: cannam@184: if (f.hasDuration) m_prevDurations[output] = f.duration; cannam@184: else m_prevDurations[output] = INVALID_DURATION; cannam@184: cannam@180: m_prevTimestamps[output] = timestamp; cannam@185: cannam@195: if (f.hasDuration) { cannam@195: RealTime et = timestamp; cannam@195: et = et + f.duration; cannam@195: if (et > m_endTime) m_endTime = et; cannam@195: } cannam@180: cannam@185: Result result; cannam@185: result.time = timestamp; cannam@185: result.duration = INVALID_DURATION; cannam@185: cannam@185: if (f.values.size() > m_accumulators[output].bins) { cannam@185: m_accumulators[output].bins = f.values.size(); cannam@185: } cannam@185: cannam@174: for (int i = 0; i < int(f.values.size()); ++i) { cannam@185: result.values.push_back(f.values[i]); cannam@174: } cannam@185: cannam@185: m_accumulators[output].results.push_back(result); cannam@184: } cannam@180: cannam@184: void cannam@184: PluginSummarisingAdapter::Impl::accumulateFinalDurations() cannam@184: { cannam@184: for (OutputTimestampMap::iterator i = m_prevTimestamps.begin(); cannam@184: i != m_prevTimestamps.end(); ++i) { cannam@184: cannam@184: int output = i->first; cannam@185: cannam@185: int acount = m_accumulators[output].results.size(); cannam@185: cannam@185: if (acount == 0) continue; cannam@185: cannam@184: RealTime prevTimestamp = i->second; cannam@184: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@184: std::cerr << "output " << output << ": "; cannam@194: #endif cannam@184: cannam@184: if (m_prevDurations.find(output) != m_prevDurations.end() && cannam@184: m_prevDurations[output] != INVALID_DURATION) { cannam@184: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@184: std::cerr << "Pushing final duration from feature as " << m_prevDurations[output] << std::endl; cannam@194: #endif cannam@184: cannam@185: m_accumulators[output].results[acount - 1].duration = cannam@185: m_prevDurations[output]; cannam@184: cannam@184: } else { cannam@184: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@195: std::cerr << "Pushing final duration from diff as " << m_endTime << " - " << m_prevTimestamps[output] << std::endl; cannam@194: #endif cannam@184: cannam@185: m_accumulators[output].results[acount - 1].duration = cannam@195: m_endTime - m_prevTimestamps[output]; cannam@184: } cannam@188: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@188: std::cerr << "so duration for result no " << acount-1 << " is " cannam@188: << m_accumulators[output].results[acount-1].duration cannam@188: << std::endl; cannam@194: #endif cannam@180: } cannam@174: } cannam@174: cannam@185: void cannam@186: PluginSummarisingAdapter::Impl::findSegmentBounds(RealTime t, cannam@186: RealTime &start, cannam@186: RealTime &end) cannam@186: { cannam@195: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT cannam@186: std::cerr << "findSegmentBounds: t = " << t << std::endl; cannam@194: #endif cannam@186: cannam@187: SegmentBoundaries::const_iterator i = std::upper_bound cannam@186: (m_boundaries.begin(), m_boundaries.end(), t); cannam@186: cannam@186: start = RealTime::zeroTime; cannam@195: end = m_endTime; cannam@186: cannam@186: if (i != m_boundaries.end()) { cannam@188: end = *i; cannam@188: } cannam@186: cannam@188: if (i != m_boundaries.begin()) { cannam@188: start = *--i; cannam@186: } cannam@194: cannam@195: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT cannam@186: std::cerr << "findSegmentBounds: " << t << " is in segment " << start << " -> " << end << std::endl; cannam@194: #endif cannam@186: } cannam@186: cannam@186: void cannam@185: PluginSummarisingAdapter::Impl::segment() cannam@185: { cannam@185: SegmentBoundaries::iterator boundaryitr = m_boundaries.begin(); cannam@185: RealTime segmentStart = RealTime::zeroTime; cannam@186: cannam@185: for (OutputAccumulatorMap::iterator i = m_accumulators.begin(); cannam@185: i != m_accumulators.end(); ++i) { cannam@185: cannam@185: int output = i->first; cannam@185: OutputAccumulator &source = i->second; cannam@185: cannam@195: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT cannam@188: std::cerr << "segment: total results for output " << output << " = " cannam@188: << source.results.size() << std::endl; cannam@194: #endif cannam@188: cannam@187: //!!! This is basically nonsense if the results have no values cannam@187: //!!! (i.e. their times and counts are the only things of cannam@187: //!!! interest) but perhaps it's the user's problem if they cannam@187: //!!! ask for segmentation in that case cannam@187: cannam@186: for (int n = 0; n < source.results.size(); ++n) { cannam@186: cannam@186: // This result spans source.results[n].time to cannam@186: // source.results[n].time + source.results[n].duration. cannam@186: // We need to dispose it into segments appropriately cannam@186: cannam@186: RealTime resultStart = source.results[n].time; cannam@186: RealTime resultEnd = resultStart + source.results[n].duration; cannam@186: cannam@195: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT cannam@187: std::cerr << "output: " << output << ", result start = " << resultStart << ", end = " << resultEnd << std::endl; cannam@194: #endif cannam@187: cannam@186: RealTime segmentStart = RealTime::zeroTime; cannam@186: RealTime segmentEnd = resultEnd - RealTime(1, 0); cannam@186: cannam@186: while (segmentEnd < resultEnd) { cannam@186: cannam@186: findSegmentBounds(resultStart, segmentStart, segmentEnd); cannam@186: cannam@186: RealTime chunkStart = resultStart; cannam@186: if (chunkStart < segmentStart) chunkStart = segmentStart; cannam@186: cannam@186: RealTime chunkEnd = resultEnd; cannam@186: if (chunkEnd > segmentEnd) chunkEnd = segmentEnd; cannam@186: cannam@186: m_segmentedAccumulators[output][segmentStart].bins = source.bins; cannam@186: cannam@186: Result chunk; cannam@186: chunk.time = chunkStart; cannam@186: chunk.duration = chunkEnd - chunkStart; cannam@186: chunk.values = source.results[n].values; cannam@186: cannam@195: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT cannam@186: std::cerr << "chunk for segment " << segmentStart << ": from " << chunk.time << ", duration " << chunk.duration << std::endl; cannam@194: #endif cannam@186: cannam@186: m_segmentedAccumulators[output][segmentStart].results cannam@186: .push_back(chunk); cannam@186: cannam@186: resultStart = chunkEnd; cannam@186: } cannam@186: } cannam@186: } cannam@185: } cannam@185: cannam@181: struct ValueDurationFloatPair cannam@181: { cannam@181: float value; cannam@181: float duration; cannam@181: cannam@181: ValueDurationFloatPair() : value(0), duration(0) { } cannam@181: ValueDurationFloatPair(float v, float d) : value(v), duration(d) { } cannam@181: ValueDurationFloatPair &operator=(const ValueDurationFloatPair &p) { cannam@181: value = p.value; cannam@181: duration = p.duration; cannam@181: return *this; cannam@181: } cannam@181: bool operator<(const ValueDurationFloatPair &p) const { cannam@181: return value < p.value; cannam@181: } cannam@181: }; cannam@181: cannam@181: static double toSec(const RealTime &r) cannam@181: { cannam@181: return r.sec + double(r.nsec) / 1000000000.0; cannam@181: } cannam@181: cannam@174: void cannam@174: PluginSummarisingAdapter::Impl::reduce() cannam@174: { cannam@187: for (OutputSegmentAccumulatorMap::iterator i = cannam@187: m_segmentedAccumulators.begin(); cannam@187: i != m_segmentedAccumulators.end(); ++i) { cannam@174: cannam@174: int output = i->first; cannam@187: SegmentAccumulatorMap &segments = i->second; cannam@174: cannam@187: for (SegmentAccumulatorMap::iterator j = segments.begin(); cannam@187: j != segments.end(); ++j) { cannam@185: cannam@187: RealTime segmentStart = j->first; cannam@187: OutputAccumulator &accumulator = j->second; cannam@180: cannam@187: int sz = accumulator.results.size(); cannam@174: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@188: std::cerr << "reduce: segment starting at " << segmentStart cannam@188: << " on output " << output << " has " << sz << " result(s)" << std::endl; cannam@194: #endif cannam@188: cannam@187: double totalDuration = 0.0; cannam@187: //!!! is this right? cannam@187: if (sz > 0) { cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@188: std::cerr << "last time = " << accumulator.results[sz-1].time cannam@188: << ", duration = " << accumulator.results[sz-1].duration cannam@195: << " (step = " << m_stepSize << ", block = " << m_blockSize << ")" cannam@188: << std::endl; cannam@194: #endif cannam@188: totalDuration = toSec((accumulator.results[sz-1].time + cannam@188: accumulator.results[sz-1].duration) - cannam@188: segmentStart); cannam@185: } cannam@185: cannam@187: for (int bin = 0; bin < accumulator.bins; ++bin) { cannam@181: cannam@187: // work on all values over time for a single bin cannam@181: cannam@187: OutputBinSummary summary; cannam@174: cannam@187: summary.count = sz; cannam@174: cannam@187: summary.minimum = 0.f; cannam@187: summary.maximum = 0.f; cannam@185: cannam@187: summary.median = 0.f; cannam@187: summary.mode = 0.f; cannam@187: summary.sum = 0.f; cannam@187: summary.variance = 0.f; cannam@174: cannam@187: summary.median_c = 0.f; cannam@187: summary.mode_c = 0.f; cannam@187: summary.mean_c = 0.f; cannam@187: summary.variance_c = 0.f; cannam@174: cannam@187: if (sz == 0) continue; cannam@174: cannam@187: std::vector valvec; cannam@181: cannam@181: for (int k = 0; k < sz; ++k) { cannam@187: while (accumulator.results[k].values.size() < cannam@187: accumulator.bins) { cannam@187: accumulator.results[k].values.push_back(0.f); cannam@187: } cannam@181: } cannam@182: cannam@187: for (int k = 0; k < sz; ++k) { cannam@187: float value = accumulator.results[k].values[bin]; cannam@187: valvec.push_back(ValueDurationFloatPair cannam@187: (value, cannam@187: toSec(accumulator.results[k].duration))); cannam@187: } cannam@187: cannam@187: std::sort(valvec.begin(), valvec.end()); cannam@187: cannam@187: summary.minimum = valvec[0].value; cannam@187: summary.maximum = valvec[sz-1].value; cannam@187: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@188: std::cerr << "total duration = " << totalDuration << std::endl; cannam@194: #endif cannam@188: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@195: /* cannam@188: std::cerr << "value vector for medians:" << std::endl; cannam@188: for (int k = 0; k < sz; ++k) { cannam@188: std::cerr << "(" << valvec[k].value << "," << valvec[k].duration << ") "; cannam@188: } cannam@188: std::cerr << std::endl; cannam@195: */ cannam@194: #endif cannam@188: cannam@187: if (sz % 2 == 1) { cannam@187: summary.median = valvec[sz/2].value; cannam@187: } else { cannam@187: summary.median = (valvec[sz/2].value + valvec[sz/2 + 1].value) / 2; cannam@187: } cannam@187: cannam@187: double duracc = 0.0; cannam@187: summary.median_c = valvec[sz-1].value; cannam@181: cannam@181: for (int k = 0; k < sz; ++k) { cannam@187: duracc += valvec[k].duration; cannam@187: if (duracc > totalDuration/2) { cannam@187: summary.median_c = valvec[k].value; cannam@187: break; cannam@187: } cannam@181: } cannam@181: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@187: std::cerr << "median_c = " << summary.median_c << std::endl; cannam@187: std::cerr << "median = " << summary.median << std::endl; cannam@194: #endif cannam@187: cannam@187: std::map distribution; cannam@187: cannam@187: for (int k = 0; k < sz; ++k) { cannam@187: summary.sum += accumulator.results[k].values[bin]; cannam@187: distribution[accumulator.results[k].values[bin]] += 1; cannam@187: } cannam@187: cannam@187: int md = 0; cannam@187: cannam@187: for (std::map::iterator di = distribution.begin(); cannam@187: di != distribution.end(); ++di) { cannam@187: if (di->second > md) { cannam@187: md = di->second; cannam@187: summary.mode = di->first; cannam@187: } cannam@187: } cannam@187: cannam@187: distribution.clear(); cannam@187: cannam@187: std::map distribution_c; cannam@187: cannam@187: for (int k = 0; k < sz; ++k) { cannam@187: distribution_c[accumulator.results[k].values[bin]] cannam@187: += toSec(accumulator.results[k].duration); cannam@187: } cannam@187: cannam@187: double mrd = 0.0; cannam@187: cannam@187: for (std::map::iterator di = distribution_c.begin(); cannam@187: di != distribution_c.end(); ++di) { cannam@187: if (di->second > mrd) { cannam@187: mrd = di->second; cannam@187: summary.mode_c = di->first; cannam@187: } cannam@187: } cannam@187: cannam@187: distribution_c.clear(); cannam@187: cannam@187: if (totalDuration > 0.0) { cannam@187: cannam@187: double sum_c = 0.0; cannam@187: cannam@187: for (int k = 0; k < sz; ++k) { cannam@187: double value = accumulator.results[k].values[bin] cannam@187: * toSec(accumulator.results[k].duration); cannam@187: sum_c += value; cannam@187: } cannam@187: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@187: std::cerr << "mean_c = " << sum_c << " / " << totalDuration << " = " cannam@187: << sum_c / totalDuration << " (sz = " << sz << ")" << std::endl; cannam@194: #endif cannam@187: cannam@187: summary.mean_c = sum_c / totalDuration; cannam@187: cannam@187: for (int k = 0; k < sz; ++k) { cannam@195: double value = accumulator.results[k].values[bin]; cannam@195: // * toSec(accumulator.results[k].duration); cannam@195: summary.variance_c += cannam@195: (value - summary.mean_c) * (value - summary.mean_c) cannam@187: * toSec(accumulator.results[k].duration); cannam@187: } cannam@187: cannam@195: // summary.variance_c /= summary.count; cannam@195: summary.variance_c /= totalDuration; cannam@187: } cannam@187: cannam@195: double mean = summary.sum / summary.count; cannam@187: cannam@194: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER cannam@187: std::cerr << "mean = " << summary.sum << " / " << summary.count << " = " cannam@187: << summary.sum / summary.count << std::endl; cannam@194: #endif cannam@187: cannam@187: for (int k = 0; k < sz; ++k) { cannam@187: float value = accumulator.results[k].values[bin]; cannam@187: summary.variance += (value - mean) * (value - mean); cannam@187: } cannam@187: summary.variance /= summary.count; cannam@187: cannam@187: m_summaries[output][segmentStart][bin] = summary; cannam@181: } cannam@174: } cannam@174: } cannam@175: cannam@187: m_segmentedAccumulators.clear(); cannam@175: m_accumulators.clear(); cannam@174: } cannam@174: cannam@174: cannam@174: } cannam@174: cannam@174: } cannam@174: