annotate vamp-sdk/hostext/PluginSummarisingAdapter.cpp @ 181:cd16cbf80c87

* More work on continuous-time averaging
author cannam
date Thu, 04 Sep 2008 15:19:24 +0000
parents 9a58bd07aa4d
children 3fcac0f3afdc
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@180 57 FeatureList getSummaryForOutput(int output,
cannam@180 58 SummaryType type,
cannam@180 59 AveragingMethod avg);
cannam@180 60
cannam@180 61 FeatureSet getSummaryForAllOutputs(SummaryType type,
cannam@180 62 AveragingMethod avg);
cannam@173 63
cannam@173 64 protected:
cannam@174 65 Plugin *m_plugin;
cannam@181 66 float m_inputSampleRate;
cannam@174 67
cannam@173 68 SegmentBoundaries m_boundaries;
cannam@174 69
cannam@174 70 typedef std::vector<float> ValueList;
cannam@174 71 typedef std::map<int, ValueList> BinValueMap;
cannam@180 72 typedef std::vector<RealTime> DurationList;
cannam@174 73
cannam@174 74 struct OutputAccumulator {
cannam@174 75 int count;
cannam@180 76 BinValueMap values; // bin number -> values ordered by time
cannam@180 77 DurationList durations;
cannam@180 78 OutputAccumulator() : count(0), values(), durations() { }
cannam@174 79 };
cannam@174 80
cannam@174 81 typedef std::map<int, OutputAccumulator> OutputAccumulatorMap;
cannam@180 82 OutputAccumulatorMap m_accumulators; // output number -> accumulator
cannam@180 83
cannam@180 84 typedef std::map<int, RealTime> OutputTimestampMap;
cannam@180 85 OutputTimestampMap m_prevTimestamps; // output number -> timestamp
cannam@174 86
cannam@174 87 struct OutputBinSummary {
cannam@180 88
cannam@180 89 int count;
cannam@180 90
cannam@180 91 // extents
cannam@174 92 float minimum;
cannam@174 93 float maximum;
cannam@180 94 float sum;
cannam@180 95
cannam@180 96 // sample-average results
cannam@174 97 float median;
cannam@174 98 float mode;
cannam@174 99 float variance;
cannam@180 100
cannam@180 101 // continuous-time average results
cannam@180 102 float median_c;
cannam@180 103 float mode_c;
cannam@180 104 float mean_c;
cannam@180 105 float variance_c;
cannam@174 106 };
cannam@174 107
cannam@174 108 typedef std::map<int, OutputBinSummary> OutputSummary;
cannam@174 109 typedef std::map<RealTime, OutputSummary> SummarySegmentMap;
cannam@174 110 typedef std::map<int, SummarySegmentMap> OutputSummarySegmentMap;
cannam@174 111
cannam@174 112 OutputSummarySegmentMap m_summaries;
cannam@174 113
cannam@174 114 RealTime m_lastTimestamp;
cannam@180 115 RealTime m_prevDuration;
cannam@174 116
cannam@180 117 void accumulate(const FeatureSet &fs, RealTime, bool final);
cannam@180 118 void accumulate(int output, const Feature &f, RealTime, bool final);
cannam@174 119 void reduce();
cannam@173 120 };
cannam@173 121
cannam@173 122 PluginSummarisingAdapter::PluginSummarisingAdapter(Plugin *plugin) :
cannam@173 123 PluginWrapper(plugin)
cannam@173 124 {
cannam@173 125 m_impl = new Impl(plugin, m_inputSampleRate);
cannam@173 126 }
cannam@173 127
cannam@173 128 PluginSummarisingAdapter::~PluginSummarisingAdapter()
cannam@173 129 {
cannam@173 130 delete m_impl;
cannam@173 131 }
cannam@173 132
cannam@173 133 Plugin::FeatureSet
cannam@173 134 PluginSummarisingAdapter::process(const float *const *inputBuffers, RealTime timestamp)
cannam@173 135 {
cannam@173 136 return m_impl->process(inputBuffers, timestamp);
cannam@173 137 }
cannam@173 138
cannam@174 139 Plugin::FeatureSet
cannam@174 140 PluginSummarisingAdapter::getRemainingFeatures()
cannam@174 141 {
cannam@174 142 return m_impl->getRemainingFeatures();
cannam@174 143 }
cannam@174 144
cannam@175 145 Plugin::FeatureList
cannam@180 146 PluginSummarisingAdapter::getSummaryForOutput(int output,
cannam@180 147 SummaryType type,
cannam@180 148 AveragingMethod avg)
cannam@175 149 {
cannam@180 150 return m_impl->getSummaryForOutput(output, type, avg);
cannam@176 151 }
cannam@176 152
cannam@176 153 Plugin::FeatureSet
cannam@180 154 PluginSummarisingAdapter::getSummaryForAllOutputs(SummaryType type,
cannam@180 155 AveragingMethod avg)
cannam@176 156 {
cannam@180 157 return m_impl->getSummaryForAllOutputs(type, avg);
cannam@175 158 }
cannam@173 159
cannam@173 160 PluginSummarisingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) :
cannam@181 161 m_plugin(plugin),
cannam@181 162 m_inputSampleRate(inputSampleRate)
cannam@173 163 {
cannam@173 164 }
cannam@173 165
cannam@173 166 PluginSummarisingAdapter::Impl::~Impl()
cannam@173 167 {
cannam@173 168 }
cannam@173 169
cannam@174 170 Plugin::FeatureSet
cannam@174 171 PluginSummarisingAdapter::Impl::process(const float *const *inputBuffers, RealTime timestamp)
cannam@174 172 {
cannam@174 173 FeatureSet fs = m_plugin->process(inputBuffers, timestamp);
cannam@180 174 accumulate(fs, timestamp, false);
cannam@174 175 m_lastTimestamp = timestamp;
cannam@174 176 return fs;
cannam@174 177 }
cannam@174 178
cannam@174 179 Plugin::FeatureSet
cannam@174 180 PluginSummarisingAdapter::Impl::getRemainingFeatures()
cannam@174 181 {
cannam@174 182 FeatureSet fs = m_plugin->getRemainingFeatures();
cannam@180 183 accumulate(fs, m_lastTimestamp, true);
cannam@174 184 reduce();
cannam@174 185 return fs;
cannam@174 186 }
cannam@174 187
cannam@175 188 Plugin::FeatureList
cannam@180 189 PluginSummarisingAdapter::Impl::getSummaryForOutput(int output,
cannam@180 190 SummaryType type,
cannam@180 191 AveragingMethod avg)
cannam@175 192 {
cannam@180 193 bool continuous = (avg == ContinuousTimeAverage);
cannam@180 194
cannam@175 195 //!!! need to ensure that this is only called after processing is
cannam@175 196 //!!! complete (at the moment processing is "completed" in the
cannam@175 197 //!!! call to getRemainingFeatures, but we don't want to require
cannam@175 198 //!!! the host to call getRemainingFeatures at all unless it
cannam@175 199 //!!! actually wants the raw features too -- calling getSummary
cannam@175 200 //!!! should be enough -- we do need to ensure that all data has
cannam@175 201 //!!! been processed though!)
cannam@175 202 FeatureList fl;
cannam@175 203 for (SummarySegmentMap::const_iterator i = m_summaries[output].begin();
cannam@175 204 i != m_summaries[output].end(); ++i) {
cannam@177 205
cannam@175 206 Feature f;
cannam@175 207 f.hasTimestamp = true;
cannam@175 208 f.timestamp = i->first;
cannam@175 209 f.hasDuration = false;
cannam@177 210
cannam@175 211 for (OutputSummary::const_iterator j = i->second.begin();
cannam@175 212 j != i->second.end(); ++j) {
cannam@175 213
cannam@175 214 // these will be ordered by bin number, and no bin numbers
cannam@175 215 // will be missing except at the end (because of the way
cannam@175 216 // the accumulators were initially filled in accumulate())
cannam@175 217
cannam@175 218 const OutputBinSummary &summary = j->second;
cannam@175 219 float result = 0.f;
cannam@175 220
cannam@175 221 switch (type) {
cannam@175 222
cannam@175 223 case Minimum:
cannam@175 224 result = summary.minimum;
cannam@175 225 break;
cannam@175 226
cannam@175 227 case Maximum:
cannam@175 228 result = summary.maximum;
cannam@175 229 break;
cannam@175 230
cannam@175 231 case Mean:
cannam@180 232 if (continuous) {
cannam@180 233 result = summary.mean_c;
cannam@180 234 } else if (summary.count) {
cannam@175 235 result = summary.sum / summary.count;
cannam@175 236 }
cannam@175 237 break;
cannam@175 238
cannam@175 239 case Median:
cannam@180 240 if (continuous) result = summary.median_c;
cannam@180 241 else result = summary.median;
cannam@175 242 break;
cannam@175 243
cannam@175 244 case Mode:
cannam@180 245 if (continuous) result = summary.mode_c;
cannam@180 246 else result = summary.mode;
cannam@175 247 break;
cannam@175 248
cannam@175 249 case Sum:
cannam@175 250 result = summary.sum;
cannam@175 251 break;
cannam@175 252
cannam@175 253 case Variance:
cannam@180 254 if (continuous) result = summary.variance_c;
cannam@180 255 else result = summary.variance;
cannam@175 256 break;
cannam@175 257
cannam@175 258 case StandardDeviation:
cannam@180 259 if (continuous) result = sqrtf(summary.variance_c);
cannam@180 260 else result = sqrtf(summary.variance);
cannam@175 261 break;
cannam@175 262
cannam@175 263 case Count:
cannam@175 264 result = summary.count;
cannam@175 265 break;
cannam@180 266
cannam@180 267 default:
cannam@180 268 break;
cannam@175 269 }
cannam@177 270
cannam@177 271 f.values.push_back(result);
cannam@175 272 }
cannam@175 273
cannam@175 274 fl.push_back(f);
cannam@175 275 }
cannam@175 276 return fl;
cannam@175 277 }
cannam@175 278
cannam@176 279 Plugin::FeatureSet
cannam@180 280 PluginSummarisingAdapter::Impl::getSummaryForAllOutputs(SummaryType type,
cannam@180 281 AveragingMethod avg)
cannam@176 282 {
cannam@176 283 FeatureSet fs;
cannam@176 284 for (OutputSummarySegmentMap::const_iterator i = m_summaries.begin();
cannam@176 285 i != m_summaries.end(); ++i) {
cannam@180 286 fs[i->first] = getSummaryForOutput(i->first, type, avg);
cannam@176 287 }
cannam@176 288 return fs;
cannam@176 289 }
cannam@176 290
cannam@174 291 void
cannam@174 292 PluginSummarisingAdapter::Impl::accumulate(const FeatureSet &fs,
cannam@180 293 RealTime timestamp,
cannam@180 294 bool final)
cannam@174 295 {
cannam@174 296 for (FeatureSet::const_iterator i = fs.begin(); i != fs.end(); ++i) {
cannam@174 297 for (FeatureList::const_iterator j = i->second.begin();
cannam@174 298 j != i->second.end(); ++j) {
cannam@180 299 accumulate(i->first, *j, timestamp, final);
cannam@174 300 }
cannam@174 301 }
cannam@174 302 }
cannam@174 303
cannam@174 304 void
cannam@174 305 PluginSummarisingAdapter::Impl::accumulate(int output,
cannam@174 306 const Feature &f,
cannam@180 307 RealTime timestamp,
cannam@180 308 bool final)
cannam@174 309 {
cannam@180 310 //!!! to do: use timestamp to determine which segment we're on
cannam@180 311
cannam@174 312 m_accumulators[output].count++;
cannam@180 313
cannam@180 314 if (m_prevDuration == RealTime::zeroTime) {
cannam@180 315 if (m_prevTimestamps.find(output) != m_prevTimestamps.end()) {
cannam@180 316 m_prevDuration = timestamp - m_prevTimestamps[output];
cannam@180 317 }
cannam@180 318 }
cannam@180 319 if (m_prevDuration != RealTime::zeroTime ||
cannam@180 320 !m_accumulators[output].durations.empty()) {
cannam@180 321 // ... i.e. if not first result. We don't push a duration
cannam@180 322 // when we process the first result; then the duration we push
cannam@180 323 // each time is that for the result before the one we're
cannam@180 324 // processing, and we push an extra one at the end. This
cannam@180 325 // permits handling the case where the feature itself doesn't
cannam@180 326 // have a duration field, and we have to calculate it from the
cannam@180 327 // time to the following feature. The net effect is simply
cannam@180 328 // that values[n] and durations[n] refer to the same result.
cannam@180 329 m_accumulators[output].durations.push_back(m_prevDuration);
cannam@180 330 }
cannam@180 331
cannam@180 332 m_prevTimestamps[output] = timestamp;
cannam@180 333
cannam@174 334 for (int i = 0; i < int(f.values.size()); ++i) {
cannam@174 335 m_accumulators[output].values[i].push_back(f.values[i]);
cannam@174 336 }
cannam@180 337
cannam@180 338 if (final) {
cannam@180 339 RealTime finalDuration;
cannam@180 340 if (f.hasDuration) finalDuration = f.duration;
cannam@180 341 m_accumulators[output].durations.push_back(finalDuration);
cannam@180 342 }
cannam@180 343
cannam@180 344 if (f.hasDuration) m_prevDuration = f.duration;
cannam@180 345 else m_prevDuration = RealTime::zeroTime;
cannam@174 346 }
cannam@174 347
cannam@181 348 struct ValueDurationFloatPair
cannam@181 349 {
cannam@181 350 float value;
cannam@181 351 float duration;
cannam@181 352
cannam@181 353 ValueDurationFloatPair() : value(0), duration(0) { }
cannam@181 354 ValueDurationFloatPair(float v, float d) : value(v), duration(d) { }
cannam@181 355 ValueDurationFloatPair &operator=(const ValueDurationFloatPair &p) {
cannam@181 356 value = p.value;
cannam@181 357 duration = p.duration;
cannam@181 358 return *this;
cannam@181 359 }
cannam@181 360 bool operator<(const ValueDurationFloatPair &p) const {
cannam@181 361 return value < p.value;
cannam@181 362 }
cannam@181 363 };
cannam@181 364
cannam@181 365 static double toSec(const RealTime &r)
cannam@181 366 {
cannam@181 367 return r.sec + double(r.nsec) / 1000000000.0;
cannam@181 368 }
cannam@181 369
cannam@174 370 void
cannam@174 371 PluginSummarisingAdapter::Impl::reduce()
cannam@174 372 {
cannam@174 373 RealTime segmentStart = RealTime::zeroTime; //!!!
cannam@174 374
cannam@174 375 for (OutputAccumulatorMap::iterator i = m_accumulators.begin();
cannam@174 376 i != m_accumulators.end(); ++i) {
cannam@174 377
cannam@174 378 int output = i->first;
cannam@174 379 OutputAccumulator &accumulator = i->second;
cannam@174 380
cannam@181 381 double totalDuration;
cannam@181 382 for (int k = 0; k < accumulator.durations.size(); ++k) {
cannam@181 383 totalDuration += toSec(accumulator.durations[k]);
cannam@180 384 }
cannam@180 385
cannam@174 386 for (BinValueMap::iterator j = accumulator.values.begin();
cannam@174 387 j != accumulator.values.end(); ++j) {
cannam@174 388
cannam@180 389 // work on all values over time for a single bin
cannam@180 390
cannam@174 391 int bin = j->first;
cannam@181 392 const ValueList &values = j->second;
cannam@180 393 const DurationList &durations = accumulator.durations;
cannam@174 394
cannam@174 395 OutputBinSummary summary;
cannam@180 396
cannam@180 397 summary.count = accumulator.count;
cannam@180 398
cannam@174 399 summary.minimum = 0.f;
cannam@174 400 summary.maximum = 0.f;
cannam@180 401
cannam@174 402 summary.median = 0.f;
cannam@174 403 summary.mode = 0.f;
cannam@174 404 summary.sum = 0.f;
cannam@174 405 summary.variance = 0.f;
cannam@180 406
cannam@180 407 summary.median_c = 0.f;
cannam@180 408 summary.mode_c = 0.f;
cannam@180 409 summary.mean_c = 0.f;
cannam@180 410 summary.variance_c = 0.f;
cannam@180 411
cannam@174 412 if (summary.count == 0 || values.empty()) continue;
cannam@174 413
cannam@174 414 int sz = values.size();
cannam@174 415
cannam@180 416 if (sz != durations.size()) {
cannam@180 417 std::cerr << "WARNING: sz " << sz << " != durations.size() "
cannam@180 418 << durations.size() << std::endl;
cannam@181 419 // while (durations.size() < sz) {
cannam@181 420 // durations.push_back(RealTime::zeroTime);
cannam@181 421 // }
cannam@181 422 //!!! then what?
cannam@180 423 }
cannam@180 424
cannam@181 425 std::vector<ValueDurationFloatPair> valvec;
cannam@181 426
cannam@181 427 for (int k = 0; k < sz; ++k) {
cannam@181 428 valvec.push_back(ValueDurationFloatPair(values[k],
cannam@181 429 toSec(durations[k])));
cannam@181 430 }
cannam@181 431
cannam@181 432 std::sort(valvec.begin(), valvec.end());
cannam@181 433
cannam@181 434 summary.minimum = valvec[0].value;
cannam@181 435 summary.maximum = valvec[sz-1].value;
cannam@174 436
cannam@174 437 if (sz % 2 == 1) {
cannam@181 438 summary.median = valvec[sz/2].value;
cannam@174 439 } else {
cannam@181 440 summary.median = (valvec[sz/2].value + valvec[sz/2 + 1].value) / 2;
cannam@174 441 }
cannam@181 442
cannam@181 443 double duracc = 0.0;
cannam@181 444 summary.median_c = valvec[sz-1].value;
cannam@174 445
cannam@181 446 for (int k = 0; k < sz; ++k) {
cannam@181 447 duracc += valvec[k].duration;
cannam@181 448 if (duracc > totalDuration/2) {
cannam@181 449 summary.median_c = valvec[k].value;
cannam@181 450 break;
cannam@181 451 }
cannam@181 452 }
cannam@181 453
cannam@174 454 std::map<float, int> distribution;
cannam@174 455
cannam@174 456 for (int k = 0; k < sz; ++k) {
cannam@174 457 summary.sum += values[k];
cannam@180 458 distribution[values[k]] += 1;
cannam@174 459 }
cannam@174 460
cannam@174 461 int md = 0;
cannam@174 462
cannam@174 463 for (std::map<float, int>::iterator di = distribution.begin();
cannam@174 464 di != distribution.end(); ++di) {
cannam@174 465 if (di->second > md) {
cannam@174 466 md = di->second;
cannam@174 467 summary.mode = di->first;
cannam@174 468 }
cannam@174 469 }
cannam@174 470
cannam@174 471 distribution.clear();
cannam@174 472
cannam@180 473 //!!! we want to omit this bit if the features all have
cannam@180 474 //!!! equal duration (and set mode_c equal to mode instead)
cannam@180 475
cannam@181 476 std::map<float, double> distribution_c;
cannam@180 477
cannam@180 478 for (int k = 0; k < sz; ++k) {
cannam@181 479 distribution_c[values[k]] += toSec(durations[k]);
cannam@180 480 }
cannam@180 481
cannam@181 482 double mrd = 0.0;
cannam@180 483
cannam@181 484 for (std::map<float, double>::iterator di = distribution_c.begin();
cannam@180 485 di != distribution_c.end(); ++di) {
cannam@180 486 if (di->second > mrd) {
cannam@180 487 mrd = di->second;
cannam@180 488 summary.mode_c = di->first;
cannam@180 489 }
cannam@180 490 }
cannam@180 491
cannam@180 492 distribution_c.clear();
cannam@180 493
cannam@181 494 if (totalDuration > 0.0) {
cannam@181 495
cannam@181 496 double sum_c = 0.0;
cannam@181 497
cannam@181 498 for (int k = 0; k < sz; ++k) {
cannam@181 499 double value = values[k] * toSec(durations[k]);
cannam@181 500 sum_c += value;
cannam@181 501 }
cannam@181 502
cannam@181 503 summary.mean_c = sum_c / totalDuration;
cannam@181 504
cannam@181 505 for (int k = 0; k < sz; ++k) {
cannam@181 506 double value = values[k] * toSec(durations[k]);
cannam@181 507 summary.variance_c +=
cannam@181 508 (value - summary.mean_c) * (value - summary.mean_c);
cannam@181 509 }
cannam@181 510
cannam@181 511 summary.variance_c /= summary.count;
cannam@181 512 }
cannam@181 513
cannam@181 514 //!!! still to handle: median_c
cannam@180 515
cannam@174 516 float mean = summary.sum / summary.count;
cannam@174 517
cannam@174 518 for (int k = 0; k < sz; ++k) {
cannam@174 519 summary.variance += (values[k] - mean) * (values[k] - mean);
cannam@174 520 }
cannam@174 521 summary.variance /= summary.count;
cannam@174 522
cannam@174 523 m_summaries[output][segmentStart][bin] = summary;
cannam@174 524 }
cannam@174 525 }
cannam@175 526
cannam@175 527 m_accumulators.clear();
cannam@174 528 }
cannam@174 529
cannam@174 530
cannam@174 531 }
cannam@174 532
cannam@174 533 }
cannam@174 534