annotate vamp-sdk/hostext/PluginSummarisingAdapter.cpp @ 180:9a58bd07aa4d

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