annotate vamp-sdk/hostext/PluginSummarisingAdapter.cpp @ 178:a5ede8515893

* note in comment
author cannam
date Wed, 06 Aug 2008 16:18:02 +0000
parents 2258794251be
children 9a58bd07aa4d
rev   line source
cannam@173 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
cannam@173 2
cannam@173 3 /*
cannam@173 4 Vamp
cannam@173 5
cannam@173 6 An API for audio analysis and feature extraction plugins.
cannam@173 7
cannam@173 8 Centre for Digital Music, Queen Mary, University of London.
cannam@173 9 Copyright 2006-2008 Chris Cannam and QMUL.
cannam@173 10
cannam@173 11 Permission is hereby granted, free of charge, to any person
cannam@173 12 obtaining a copy of this software and associated documentation
cannam@173 13 files (the "Software"), to deal in the Software without
cannam@173 14 restriction, including without limitation the rights to use, copy,
cannam@173 15 modify, merge, publish, distribute, sublicense, and/or sell copies
cannam@173 16 of the Software, and to permit persons to whom the Software is
cannam@173 17 furnished to do so, subject to the following conditions:
cannam@173 18
cannam@173 19 The above copyright notice and this permission notice shall be
cannam@173 20 included in all copies or substantial portions of the Software.
cannam@173 21
cannam@173 22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
cannam@173 23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
cannam@173 24 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
cannam@173 25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
cannam@173 26 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
cannam@173 27 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
cannam@173 28 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
cannam@173 29
cannam@173 30 Except as contained in this notice, the names of the Centre for
cannam@173 31 Digital Music; Queen Mary, University of London; and Chris Cannam
cannam@173 32 shall not be used in advertising or otherwise to promote the sale,
cannam@173 33 use or other dealings in this Software without prior written
cannam@173 34 authorization.
cannam@173 35 */
cannam@173 36
cannam@173 37 #include "PluginSummarisingAdapter.h"
cannam@173 38
cannam@174 39 #include <map>
cannam@175 40 #include <cmath>
cannam@174 41
cannam@173 42 namespace Vamp {
cannam@173 43
cannam@173 44 namespace HostExt {
cannam@173 45
cannam@173 46 class PluginSummarisingAdapter::Impl
cannam@173 47 {
cannam@173 48 public:
cannam@173 49 Impl(Plugin *plugin, float inputSampleRate);
cannam@173 50 ~Impl();
cannam@173 51
cannam@173 52 FeatureSet process(const float *const *inputBuffers, RealTime timestamp);
cannam@173 53 FeatureSet getRemainingFeatures();
cannam@173 54
cannam@173 55 void setSummarySegmentBoundaries(const SegmentBoundaries &);
cannam@173 56
cannam@176 57 FeatureList getSummaryForOutput(int output, SummaryType type);
cannam@176 58 FeatureSet getSummaryForAllOutputs(SummaryType type);
cannam@173 59
cannam@173 60 protected:
cannam@174 61 Plugin *m_plugin;
cannam@174 62
cannam@173 63 SegmentBoundaries m_boundaries;
cannam@174 64
cannam@174 65 typedef std::vector<float> ValueList;
cannam@174 66 typedef std::map<int, ValueList> BinValueMap;
cannam@174 67
cannam@174 68 struct OutputAccumulator {
cannam@174 69 int count;
cannam@174 70 BinValueMap values;
cannam@177 71 OutputAccumulator() : count(0), values() { }
cannam@174 72 };
cannam@174 73
cannam@174 74 typedef std::map<int, OutputAccumulator> OutputAccumulatorMap;
cannam@174 75 OutputAccumulatorMap m_accumulators;
cannam@174 76
cannam@174 77 struct OutputBinSummary {
cannam@174 78 float minimum;
cannam@174 79 float maximum;
cannam@174 80 float median;
cannam@174 81 float mode;
cannam@174 82 float sum;
cannam@174 83 float variance;
cannam@174 84 int count;
cannam@174 85 };
cannam@174 86
cannam@174 87 typedef std::map<int, OutputBinSummary> OutputSummary;
cannam@174 88 typedef std::map<RealTime, OutputSummary> SummarySegmentMap;
cannam@174 89 typedef std::map<int, SummarySegmentMap> OutputSummarySegmentMap;
cannam@174 90
cannam@174 91 OutputSummarySegmentMap m_summaries;
cannam@174 92
cannam@174 93 RealTime m_lastTimestamp;
cannam@174 94
cannam@174 95 void accumulate(const FeatureSet &fs, RealTime);
cannam@174 96 void accumulate(int output, const Feature &f, RealTime);
cannam@174 97 void reduce();
cannam@173 98 };
cannam@173 99
cannam@173 100 PluginSummarisingAdapter::PluginSummarisingAdapter(Plugin *plugin) :
cannam@173 101 PluginWrapper(plugin)
cannam@173 102 {
cannam@173 103 m_impl = new Impl(plugin, m_inputSampleRate);
cannam@173 104 }
cannam@173 105
cannam@173 106 PluginSummarisingAdapter::~PluginSummarisingAdapter()
cannam@173 107 {
cannam@173 108 delete m_impl;
cannam@173 109 }
cannam@173 110
cannam@173 111 Plugin::FeatureSet
cannam@173 112 PluginSummarisingAdapter::process(const float *const *inputBuffers, RealTime timestamp)
cannam@173 113 {
cannam@173 114 return m_impl->process(inputBuffers, timestamp);
cannam@173 115 }
cannam@173 116
cannam@174 117 Plugin::FeatureSet
cannam@174 118 PluginSummarisingAdapter::getRemainingFeatures()
cannam@174 119 {
cannam@174 120 return m_impl->getRemainingFeatures();
cannam@174 121 }
cannam@174 122
cannam@175 123 Plugin::FeatureList
cannam@176 124 PluginSummarisingAdapter::getSummaryForOutput(int output, SummaryType type)
cannam@175 125 {
cannam@176 126 return m_impl->getSummaryForOutput(output, type);
cannam@176 127 }
cannam@176 128
cannam@176 129 Plugin::FeatureSet
cannam@176 130 PluginSummarisingAdapter::getSummaryForAllOutputs(SummaryType type)
cannam@176 131 {
cannam@176 132 return m_impl->getSummaryForAllOutputs(type);
cannam@175 133 }
cannam@173 134
cannam@173 135 PluginSummarisingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) :
cannam@174 136 m_plugin(plugin)
cannam@173 137 {
cannam@173 138 }
cannam@173 139
cannam@173 140 PluginSummarisingAdapter::Impl::~Impl()
cannam@173 141 {
cannam@173 142 }
cannam@173 143
cannam@174 144 Plugin::FeatureSet
cannam@174 145 PluginSummarisingAdapter::Impl::process(const float *const *inputBuffers, RealTime timestamp)
cannam@174 146 {
cannam@174 147 FeatureSet fs = m_plugin->process(inputBuffers, timestamp);
cannam@174 148 accumulate(fs, timestamp);
cannam@174 149 m_lastTimestamp = timestamp;
cannam@174 150 return fs;
cannam@174 151 }
cannam@174 152
cannam@174 153 Plugin::FeatureSet
cannam@174 154 PluginSummarisingAdapter::Impl::getRemainingFeatures()
cannam@174 155 {
cannam@174 156 FeatureSet fs = m_plugin->getRemainingFeatures();
cannam@174 157 accumulate(fs, m_lastTimestamp);
cannam@174 158 reduce();
cannam@174 159 return fs;
cannam@174 160 }
cannam@174 161
cannam@175 162 Plugin::FeatureList
cannam@176 163 PluginSummarisingAdapter::Impl::getSummaryForOutput(int output, SummaryType type)
cannam@175 164 {
cannam@175 165 //!!! need to ensure that this is only called after processing is
cannam@175 166 //!!! complete (at the moment processing is "completed" in the
cannam@175 167 //!!! call to getRemainingFeatures, but we don't want to require
cannam@175 168 //!!! the host to call getRemainingFeatures at all unless it
cannam@175 169 //!!! actually wants the raw features too -- calling getSummary
cannam@175 170 //!!! should be enough -- we do need to ensure that all data has
cannam@175 171 //!!! been processed though!)
cannam@175 172 FeatureList fl;
cannam@175 173 for (SummarySegmentMap::const_iterator i = m_summaries[output].begin();
cannam@175 174 i != m_summaries[output].end(); ++i) {
cannam@177 175
cannam@175 176 Feature f;
cannam@175 177 f.hasTimestamp = true;
cannam@175 178 f.timestamp = i->first;
cannam@175 179 f.hasDuration = false;
cannam@177 180
cannam@175 181 for (OutputSummary::const_iterator j = i->second.begin();
cannam@175 182 j != i->second.end(); ++j) {
cannam@175 183
cannam@175 184 // these will be ordered by bin number, and no bin numbers
cannam@175 185 // will be missing except at the end (because of the way
cannam@175 186 // the accumulators were initially filled in accumulate())
cannam@175 187
cannam@175 188 const OutputBinSummary &summary = j->second;
cannam@175 189 float result = 0.f;
cannam@175 190
cannam@175 191 switch (type) {
cannam@175 192
cannam@175 193 case Minimum:
cannam@175 194 result = summary.minimum;
cannam@175 195 break;
cannam@175 196
cannam@175 197 case Maximum:
cannam@175 198 result = summary.maximum;
cannam@175 199 break;
cannam@175 200
cannam@175 201 case Mean:
cannam@175 202 if (summary.count) {
cannam@175 203 result = summary.sum / summary.count;
cannam@175 204 }
cannam@175 205 break;
cannam@175 206
cannam@175 207 case Median:
cannam@175 208 result = summary.median;
cannam@175 209 break;
cannam@175 210
cannam@175 211 case Mode:
cannam@175 212 result = summary.mode;
cannam@175 213 break;
cannam@175 214
cannam@175 215 case Sum:
cannam@175 216 result = summary.sum;
cannam@175 217 break;
cannam@175 218
cannam@175 219 case Variance:
cannam@175 220 result = summary.variance;
cannam@175 221 break;
cannam@175 222
cannam@175 223 case StandardDeviation:
cannam@175 224 result = sqrtf(summary.variance);
cannam@175 225 break;
cannam@175 226
cannam@175 227 case Count:
cannam@175 228 result = summary.count;
cannam@175 229 break;
cannam@175 230 }
cannam@177 231
cannam@177 232 f.values.push_back(result);
cannam@175 233 }
cannam@175 234
cannam@175 235 fl.push_back(f);
cannam@175 236 }
cannam@175 237 return fl;
cannam@175 238 }
cannam@175 239
cannam@176 240 Plugin::FeatureSet
cannam@176 241 PluginSummarisingAdapter::Impl::getSummaryForAllOutputs(SummaryType type)
cannam@176 242 {
cannam@176 243 FeatureSet fs;
cannam@176 244 for (OutputSummarySegmentMap::const_iterator i = m_summaries.begin();
cannam@176 245 i != m_summaries.end(); ++i) {
cannam@176 246 fs[i->first] = getSummaryForOutput(i->first, type);
cannam@176 247 }
cannam@176 248 return fs;
cannam@176 249 }
cannam@176 250
cannam@174 251 void
cannam@174 252 PluginSummarisingAdapter::Impl::accumulate(const FeatureSet &fs,
cannam@174 253 RealTime timestamp)
cannam@174 254 {
cannam@174 255 for (FeatureSet::const_iterator i = fs.begin(); i != fs.end(); ++i) {
cannam@174 256 for (FeatureList::const_iterator j = i->second.begin();
cannam@174 257 j != i->second.end(); ++j) {
cannam@174 258 accumulate(i->first, *j, timestamp);
cannam@174 259 }
cannam@174 260 }
cannam@174 261 }
cannam@174 262
cannam@174 263 void
cannam@174 264 PluginSummarisingAdapter::Impl::accumulate(int output,
cannam@174 265 const Feature &f,
cannam@174 266 RealTime timestamp)
cannam@174 267 {
cannam@174 268 //!!! use timestamp to determine which segment we're on
cannam@174 269 m_accumulators[output].count++;
cannam@174 270 for (int i = 0; i < int(f.values.size()); ++i) {
cannam@178 271
cannam@178 272
cannam@178 273 //!!! we really want to associate this occurrence of this
cannam@178 274 //!!! value with the duration it covers.
cannam@178 275
cannam@178 276 //!!! for dense values, the duration can be 1 or the sample
cannam@178 277 //!!! rate or whatever -- doesn't matter so long as it's the
cannam@178 278 //!!! same for every value.
cannam@178 279
cannam@178 280 //!!! for sparse values, the duration should be that from this
cannam@178 281 //!!! feature to the next.
cannam@178 282
cannam@178 283 //!!! if the feature has a duration, should be using that
cannam@178 284 //!!! instead.
cannam@178 285
cannam@174 286 m_accumulators[output].values[i].push_back(f.values[i]);
cannam@174 287 }
cannam@174 288 }
cannam@174 289
cannam@174 290 void
cannam@174 291 PluginSummarisingAdapter::Impl::reduce()
cannam@174 292 {
cannam@174 293 RealTime segmentStart = RealTime::zeroTime; //!!!
cannam@174 294
cannam@174 295 for (OutputAccumulatorMap::iterator i = m_accumulators.begin();
cannam@174 296 i != m_accumulators.end(); ++i) {
cannam@174 297
cannam@174 298 int output = i->first;
cannam@174 299 OutputAccumulator &accumulator = i->second;
cannam@174 300
cannam@174 301 for (BinValueMap::iterator j = accumulator.values.begin();
cannam@174 302 j != accumulator.values.end(); ++j) {
cannam@174 303
cannam@174 304 int bin = j->first;
cannam@174 305 ValueList &values = j->second;
cannam@174 306
cannam@174 307 OutputBinSummary summary;
cannam@174 308 summary.minimum = 0.f;
cannam@174 309 summary.maximum = 0.f;
cannam@174 310 summary.median = 0.f;
cannam@174 311 summary.mode = 0.f;
cannam@174 312 summary.sum = 0.f;
cannam@174 313 summary.variance = 0.f;
cannam@174 314 summary.count = accumulator.count;
cannam@174 315 if (summary.count == 0 || values.empty()) continue;
cannam@174 316
cannam@174 317 std::sort(values.begin(), values.end());
cannam@174 318 int sz = values.size();
cannam@174 319
cannam@174 320 summary.minimum = values[0];
cannam@174 321 summary.maximum = values[sz-1];
cannam@174 322
cannam@174 323 if (sz % 2 == 1) {
cannam@174 324 summary.median = values[sz/2];
cannam@174 325 } else {
cannam@174 326 summary.median = (values[sz/2] + values[sz/2 + 1]) / 2;
cannam@174 327 }
cannam@174 328
cannam@174 329 std::map<float, int> distribution;
cannam@174 330
cannam@174 331 for (int k = 0; k < sz; ++k) {
cannam@174 332 summary.sum += values[k];
cannam@174 333 ++distribution[values[k]];
cannam@174 334 }
cannam@174 335
cannam@174 336 int md = 0;
cannam@174 337
cannam@174 338 //!!! I don't like this. Really the mode should be the
cannam@174 339 //!!! value that spans the longest period of time, not the
cannam@174 340 //!!! one that appears in the largest number of distinct
cannam@175 341 //!!! features. I suppose that a median by time rather
cannam@175 342 //!!! than number of features would also be useful.
cannam@174 343
cannam@174 344 for (std::map<float, int>::iterator di = distribution.begin();
cannam@174 345 di != distribution.end(); ++di) {
cannam@174 346 if (di->second > md) {
cannam@174 347 md = di->second;
cannam@174 348 summary.mode = di->first;
cannam@174 349 }
cannam@174 350 }
cannam@174 351
cannam@174 352 distribution.clear();
cannam@174 353
cannam@174 354 float mean = summary.sum / summary.count;
cannam@174 355
cannam@174 356 for (int k = 0; k < sz; ++k) {
cannam@174 357 summary.variance += (values[k] - mean) * (values[k] - mean);
cannam@174 358 }
cannam@174 359 summary.variance /= summary.count;
cannam@174 360
cannam@174 361 m_summaries[output][segmentStart][bin] = summary;
cannam@174 362 }
cannam@174 363 }
cannam@175 364
cannam@175 365 m_accumulators.clear();
cannam@174 366 }
cannam@174 367
cannam@174 368
cannam@174 369 }
cannam@174 370
cannam@174 371 }
cannam@174 372