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 <vamp-hostsdk/PluginSummarisingAdapter.h>
cannam@233: 
cannam@233: #include <map>
cannam@233: #include <algorithm>
cannam@233: #include <cmath>
cannam@233: #include <climits>
cannam@233: 
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: 
cannam@233:     typedef std::vector<float> 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: 
cannam@233:     typedef std::vector<Result> ResultList;
cannam@233: 
cannam@233:     struct OutputAccumulator {
cannam@233:         int bins;
cannam@233:         ResultList results;
cannam@233:         OutputAccumulator() : bins(0) { }
cannam@233:     };
cannam@233: 
cannam@233:     typedef std::map<int, OutputAccumulator> OutputAccumulatorMap;
cannam@233:     OutputAccumulatorMap m_accumulators; // output number -> accumulator
cannam@233: 
cannam@233:     typedef std::map<RealTime, OutputAccumulator> SegmentAccumulatorMap;
cannam@233:     typedef std::map<int, SegmentAccumulatorMap> OutputSegmentAccumulatorMap;
cannam@233:     OutputSegmentAccumulatorMap m_segmentedAccumulators; // output -> segmented
cannam@233: 
cannam@233:     typedef std::map<int, RealTime> 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: 
cannam@233:     typedef std::map<int, OutputBinSummary> OutputSummary;
cannam@233:     typedef std::map<RealTime, OutputSummary> SummarySegmentMap;
cannam@233:     typedef std::map<int, SummarySegmentMap> 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: 
cannam@233:     std::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
cannam@233: PluginSummarisingAdapter::Impl::initialise(size_t channels,
cannam@233:                                            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) {
cannam@233:         std::cerr << "WARNING: Cannot call PluginSummarisingAdapter::process() or getRemainingFeatures() after one of the getSummary methods" << std::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));
cannam@233:     return fs;
cannam@233: }
cannam@233: 
cannam@233: Plugin::FeatureSet
cannam@233: PluginSummarisingAdapter::Impl::getRemainingFeatures()
cannam@233: {
cannam@233:     if (m_reduced) {
cannam@233:         std::cerr << "WARNING: Cannot call PluginSummarisingAdapter::process() or getRemainingFeatures() after one of the getSummary methods" << std::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
cannam@233:     std::cerr << "PluginSummarisingAdapter::setSummarySegmentBoundaries: boundaries are:" << std::endl;
cannam@233:     for (SegmentBoundaries::const_iterator i = m_boundaries.begin();
cannam@233:          i != m_boundaries.end(); ++i) {
cannam@233:         std::cerr << *i << "  ";
cannam@233:     }
cannam@233:     std::cerr << std::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:
cannam@233:                 if (continuous) result = sqrtf(summary.variance_c);
cannam@233:                 else result = sqrtf(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:             
cannam@233:             f.values.push_back(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: 
cannam@233: std::string
cannam@233: PluginSummarisingAdapter::Impl::getSummaryLabel(SummaryType type,
cannam@233:                                                 AveragingMethod avg)
cannam@233: {
cannam@233:     std::string label;
cannam@233:     std::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,
cannam@233:                                            bool final)
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
cannam@233:     std::cerr << "output " << output << ": timestamp " << timestamp << ", prev timestamp " << m_prevTimestamps[output] << ", final " << final << std::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
cannam@233:             std::cerr << "Previous duration from previous feature: " << prevDuration << std::endl;
cannam@233: #endif
cannam@233:         } else {
cannam@233:             prevDuration = timestamp - m_prevTimestamps[output];
cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER
cannam@233:             std::cerr << "Previous duration from diff: " << timestamp << " - "
cannam@233:                       << m_prevTimestamps[output] << std::endl;
cannam@233: #endif
cannam@233:         }
cannam@233: 
cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER
cannam@233:         std::cerr << "output " << output << ": ";
cannam@233:         std::cerr << "Pushing previous duration as " << prevDuration << std::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;
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) {
cannam@233:         m_accumulators[output].bins = 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: 
cannam@233:         int acount = m_accumulators[output].results.size();
cannam@233: 
cannam@233:         if (acount == 0) continue;
cannam@233: 
cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER
cannam@233:         std::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
cannam@233:             std::cerr << "Pushing final duration from feature as " << m_prevDurations[output] << std::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
cannam@233:             std::cerr << "Pushing final duration from diff as " << m_endTime << " - " << m_prevTimestamps[output] << std::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
cannam@233:         std::cerr << "so duration for result no " << acount-1 << " is "
cannam@233:                   << m_accumulators[output].results[acount-1].duration
cannam@233:                   << std::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
cannam@233:     std::cerr << "findSegmentBounds: t = " << t <<  std::endl;
cannam@233: #endif
cannam@233: 
cannam@233:     SegmentBoundaries::const_iterator i = std::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
cannam@233:     std::cerr << "findSegmentBounds: " << t << " is in segment " << start << " -> " << end << std::endl;
cannam@233: #endif
cannam@233: }
cannam@233: 
cannam@233: void
cannam@233: PluginSummarisingAdapter::Impl::segment()
cannam@233: {
cannam@233:     SegmentBoundaries::iterator boundaryitr = m_boundaries.begin();
cannam@233:     
cannam@274: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT
cannam@274:     std::cerr << "segment: starting" << std::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
cannam@233:         std::cerr << "segment: total results for output " << output << " = "
cannam@233:                   << source.results.size() << std::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
cannam@233:             std::cerr << "output: " << output << ", result start = " << resultStart << ", end = " << resultEnd << std::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
cannam@274:                 std::cerr << "segment end " << segmentEnd << " < result end "
cannam@274:                           << resultEnd << " (with result start " << resultStart << ")" <<  std::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
cannam@233:                 std::cerr << "chunk for segment " << segmentStart << ": from " << chunk.time << ", duration " << chunk.duration << std::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: 
cannam@233:             int sz = accumulator.results.size();
cannam@233: 
cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER
cannam@233:             std::cerr << "reduce: segment starting at " << segmentStart
cannam@233:                       << " on output " << output << " has " << sz << " result(s)" << std::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
cannam@233:                 std::cerr << "last time = " << accumulator.results[sz-1].time 
cannam@233:                           << ", duration = " << accumulator.results[sz-1].duration
cannam@233:                           << " (step = " << m_stepSize << ", block = " << m_blockSize << ")"
cannam@233:                           << std::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: 
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: 
cannam@233:                 std::vector<ValueDurationFloatPair> 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];
cannam@233:                     valvec.push_back(ValueDurationFloatPair
cannam@233:                                      (value,
cannam@233:                                       toSec(accumulator.results[k].duration)));
cannam@233:                 }
cannam@233: 
cannam@233:                 std::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
cannam@233:                 std::cerr << "total duration = " << totalDuration << std::endl;
cannam@233: #endif
cannam@233: 
cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER
cannam@233: /*
cannam@233:                 std::cerr << "value vector for medians:" << std::endl;
cannam@233:                 for (int k = 0; k < sz; ++k) {
cannam@233:                     std::cerr << "(" << valvec[k].value << "," << valvec[k].duration << ") ";
cannam@233:                 }
cannam@233:                 std::cerr << std::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
cannam@233:                 std::cerr << "median_c = " << summary.median_c << std::endl;
cannam@233:                 std::cerr << "median = " << summary.median << std::endl;
cannam@233: #endif
cannam@233:                 
cannam@233:                 std::map<float, int> distribution;
cannam@233: 
cannam@233:                 for (int k = 0; k < sz; ++k) {
cannam@233:                     summary.sum += accumulator.results[k].values[bin];
cannam@233:                     distribution[accumulator.results[k].values[bin]] += 1;
cannam@233:                 }
cannam@233: 
cannam@233:                 int md = 0;
cannam@233: 
cannam@233:                 for (std::map<float, int>::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: 
cannam@233:                 std::map<float, double> 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: 
cannam@233:                 for (std::map<float, double>::iterator di = distribution_c.begin();
cannam@233:                      di != distribution_c.end(); ++di) {
cannam@233:                     if (di->second > mrd) {
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: 
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:                         sum_c += value;
cannam@233:                     }
cannam@233: 
cannam@233: #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER
cannam@233:                     std::cerr << "mean_c = " << sum_c << " / " << totalDuration << " = "
cannam@233:                               << sum_c / totalDuration << " (sz = " << sz << ")" << std::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
cannam@233:                 std::cerr << "mean = " << summary.sum << " / " << summary.count << " = "
cannam@233:                           << summary.sum / summary.count << std::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: