Mercurial > hg > vamp-plugin-sdk
comparison src/vamp-hostsdk/hostext/PluginSummarisingAdapter.cpp @ 227:6b30e064cab7 distinct-libraries
* more moving
author | cannam |
---|---|
date | Thu, 06 Nov 2008 14:13:12 +0000 |
parents | |
children | 5ee166dccfff |
comparison
equal
deleted
inserted
replaced
226:14029eb08472 | 227:6b30e064cab7 |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Vamp | |
5 | |
6 An API for audio analysis and feature extraction plugins. | |
7 | |
8 Centre for Digital Music, Queen Mary, University of London. | |
9 Copyright 2006-2008 Chris Cannam and QMUL. | |
10 | |
11 Permission is hereby granted, free of charge, to any person | |
12 obtaining a copy of this software and associated documentation | |
13 files (the "Software"), to deal in the Software without | |
14 restriction, including without limitation the rights to use, copy, | |
15 modify, merge, publish, distribute, sublicense, and/or sell copies | |
16 of the Software, and to permit persons to whom the Software is | |
17 furnished to do so, subject to the following conditions: | |
18 | |
19 The above copyright notice and this permission notice shall be | |
20 included in all copies or substantial portions of the Software. | |
21 | |
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
24 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR | |
26 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |
27 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
28 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
29 | |
30 Except as contained in this notice, the names of the Centre for | |
31 Digital Music; Queen Mary, University of London; and Chris Cannam | |
32 shall not be used in advertising or otherwise to promote the sale, | |
33 use or other dealings in this Software without prior written | |
34 authorization. | |
35 */ | |
36 | |
37 #include "PluginSummarisingAdapter.h" | |
38 | |
39 #include <map> | |
40 #include <algorithm> | |
41 #include <cmath> | |
42 #include <climits> | |
43 | |
44 #define DEBUG_PLUGIN_SUMMARISING_ADAPTER 1 | |
45 //#define DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT 1 | |
46 | |
47 namespace Vamp { | |
48 | |
49 namespace HostExt { | |
50 | |
51 class PluginSummarisingAdapter::Impl | |
52 { | |
53 public: | |
54 Impl(Plugin *plugin, float inputSampleRate); | |
55 ~Impl(); | |
56 | |
57 bool initialise(size_t channels, size_t stepSize, size_t blockSize); | |
58 | |
59 FeatureSet process(const float *const *inputBuffers, RealTime timestamp); | |
60 FeatureSet getRemainingFeatures(); | |
61 | |
62 void setSummarySegmentBoundaries(const SegmentBoundaries &); | |
63 | |
64 FeatureList getSummaryForOutput(int output, | |
65 SummaryType type, | |
66 AveragingMethod avg); | |
67 | |
68 FeatureSet getSummaryForAllOutputs(SummaryType type, | |
69 AveragingMethod avg); | |
70 | |
71 protected: | |
72 Plugin *m_plugin; | |
73 float m_inputSampleRate; | |
74 size_t m_stepSize; | |
75 size_t m_blockSize; | |
76 | |
77 SegmentBoundaries m_boundaries; | |
78 | |
79 typedef std::vector<float> ValueList; | |
80 | |
81 struct Result { // smaller than Feature | |
82 RealTime time; | |
83 RealTime duration; | |
84 ValueList values; // bin number -> value | |
85 }; | |
86 | |
87 typedef std::vector<Result> ResultList; | |
88 | |
89 struct OutputAccumulator { | |
90 int bins; | |
91 ResultList results; | |
92 OutputAccumulator() : bins(0) { } | |
93 }; | |
94 | |
95 typedef std::map<int, OutputAccumulator> OutputAccumulatorMap; | |
96 OutputAccumulatorMap m_accumulators; // output number -> accumulator | |
97 | |
98 typedef std::map<RealTime, OutputAccumulator> SegmentAccumulatorMap; | |
99 typedef std::map<int, SegmentAccumulatorMap> OutputSegmentAccumulatorMap; | |
100 OutputSegmentAccumulatorMap m_segmentedAccumulators; // output -> segmented | |
101 | |
102 typedef std::map<int, RealTime> OutputTimestampMap; | |
103 OutputTimestampMap m_prevTimestamps; // output number -> timestamp | |
104 OutputTimestampMap m_prevDurations; // output number -> durations | |
105 | |
106 struct OutputBinSummary { | |
107 | |
108 int count; | |
109 | |
110 // extents | |
111 double minimum; | |
112 double maximum; | |
113 double sum; | |
114 | |
115 // sample-average results | |
116 double median; | |
117 double mode; | |
118 double variance; | |
119 | |
120 // continuous-time average results | |
121 double median_c; | |
122 double mode_c; | |
123 double mean_c; | |
124 double variance_c; | |
125 }; | |
126 | |
127 typedef std::map<int, OutputBinSummary> OutputSummary; | |
128 typedef std::map<RealTime, OutputSummary> SummarySegmentMap; | |
129 typedef std::map<int, SummarySegmentMap> OutputSummarySegmentMap; | |
130 | |
131 OutputSummarySegmentMap m_summaries; | |
132 | |
133 bool m_reduced; | |
134 RealTime m_endTime; | |
135 | |
136 void accumulate(const FeatureSet &fs, RealTime, bool final); | |
137 void accumulate(int output, const Feature &f, RealTime, bool final); | |
138 void accumulateFinalDurations(); | |
139 void findSegmentBounds(RealTime t, RealTime &start, RealTime &end); | |
140 void segment(); | |
141 void reduce(); | |
142 | |
143 std::string getSummaryLabel(SummaryType type, AveragingMethod avg); | |
144 }; | |
145 | |
146 static RealTime INVALID_DURATION(INT_MIN, INT_MIN); | |
147 | |
148 PluginSummarisingAdapter::PluginSummarisingAdapter(Plugin *plugin) : | |
149 PluginWrapper(plugin) | |
150 { | |
151 m_impl = new Impl(plugin, m_inputSampleRate); | |
152 } | |
153 | |
154 PluginSummarisingAdapter::~PluginSummarisingAdapter() | |
155 { | |
156 delete m_impl; | |
157 } | |
158 | |
159 bool | |
160 PluginSummarisingAdapter::initialise(size_t channels, | |
161 size_t stepSize, size_t blockSize) | |
162 { | |
163 return | |
164 PluginWrapper::initialise(channels, stepSize, blockSize) && | |
165 m_impl->initialise(channels, stepSize, blockSize); | |
166 } | |
167 | |
168 Plugin::FeatureSet | |
169 PluginSummarisingAdapter::process(const float *const *inputBuffers, RealTime timestamp) | |
170 { | |
171 return m_impl->process(inputBuffers, timestamp); | |
172 } | |
173 | |
174 Plugin::FeatureSet | |
175 PluginSummarisingAdapter::getRemainingFeatures() | |
176 { | |
177 return m_impl->getRemainingFeatures(); | |
178 } | |
179 | |
180 void | |
181 PluginSummarisingAdapter::setSummarySegmentBoundaries(const SegmentBoundaries &b) | |
182 { | |
183 m_impl->setSummarySegmentBoundaries(b); | |
184 } | |
185 | |
186 Plugin::FeatureList | |
187 PluginSummarisingAdapter::getSummaryForOutput(int output, | |
188 SummaryType type, | |
189 AveragingMethod avg) | |
190 { | |
191 return m_impl->getSummaryForOutput(output, type, avg); | |
192 } | |
193 | |
194 Plugin::FeatureSet | |
195 PluginSummarisingAdapter::getSummaryForAllOutputs(SummaryType type, | |
196 AveragingMethod avg) | |
197 { | |
198 return m_impl->getSummaryForAllOutputs(type, avg); | |
199 } | |
200 | |
201 PluginSummarisingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : | |
202 m_plugin(plugin), | |
203 m_inputSampleRate(inputSampleRate), | |
204 m_reduced(false) | |
205 { | |
206 } | |
207 | |
208 PluginSummarisingAdapter::Impl::~Impl() | |
209 { | |
210 } | |
211 | |
212 bool | |
213 PluginSummarisingAdapter::Impl::initialise(size_t channels, | |
214 size_t stepSize, size_t blockSize) | |
215 { | |
216 m_stepSize = stepSize; | |
217 m_blockSize = blockSize; | |
218 return true; | |
219 } | |
220 | |
221 Plugin::FeatureSet | |
222 PluginSummarisingAdapter::Impl::process(const float *const *inputBuffers, | |
223 RealTime timestamp) | |
224 { | |
225 if (m_reduced) { | |
226 std::cerr << "WARNING: Cannot call PluginSummarisingAdapter::process() or getRemainingFeatures() after one of the getSummary methods" << std::endl; | |
227 } | |
228 FeatureSet fs = m_plugin->process(inputBuffers, timestamp); | |
229 accumulate(fs, timestamp, false); | |
230 m_endTime = timestamp + | |
231 RealTime::frame2RealTime(m_stepSize, m_inputSampleRate); | |
232 return fs; | |
233 } | |
234 | |
235 Plugin::FeatureSet | |
236 PluginSummarisingAdapter::Impl::getRemainingFeatures() | |
237 { | |
238 if (m_reduced) { | |
239 std::cerr << "WARNING: Cannot call PluginSummarisingAdapter::process() or getRemainingFeatures() after one of the getSummary methods" << std::endl; | |
240 } | |
241 FeatureSet fs = m_plugin->getRemainingFeatures(); | |
242 accumulate(fs, m_endTime, true); | |
243 return fs; | |
244 } | |
245 | |
246 void | |
247 PluginSummarisingAdapter::Impl::setSummarySegmentBoundaries(const SegmentBoundaries &b) | |
248 { | |
249 m_boundaries = b; | |
250 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
251 std::cerr << "PluginSummarisingAdapter::setSummarySegmentBoundaries: boundaries are:" << std::endl; | |
252 for (SegmentBoundaries::const_iterator i = m_boundaries.begin(); | |
253 i != m_boundaries.end(); ++i) { | |
254 std::cerr << *i << " "; | |
255 } | |
256 std::cerr << std::endl; | |
257 #endif | |
258 } | |
259 | |
260 Plugin::FeatureList | |
261 PluginSummarisingAdapter::Impl::getSummaryForOutput(int output, | |
262 SummaryType type, | |
263 AveragingMethod avg) | |
264 { | |
265 if (!m_reduced) { | |
266 accumulateFinalDurations(); | |
267 segment(); | |
268 reduce(); | |
269 m_reduced = true; | |
270 } | |
271 | |
272 bool continuous = (avg == ContinuousTimeAverage); | |
273 | |
274 FeatureList fl; | |
275 for (SummarySegmentMap::const_iterator i = m_summaries[output].begin(); | |
276 i != m_summaries[output].end(); ++i) { | |
277 | |
278 Feature f; | |
279 | |
280 f.hasTimestamp = true; | |
281 f.timestamp = i->first; | |
282 | |
283 f.hasDuration = true; | |
284 SummarySegmentMap::const_iterator ii = i; | |
285 if (++ii == m_summaries[output].end()) { | |
286 f.duration = m_endTime - f.timestamp; | |
287 } else { | |
288 f.duration = ii->first - f.timestamp; | |
289 } | |
290 | |
291 f.label = getSummaryLabel(type, avg); | |
292 | |
293 for (OutputSummary::const_iterator j = i->second.begin(); | |
294 j != i->second.end(); ++j) { | |
295 | |
296 // these will be ordered by bin number, and no bin numbers | |
297 // will be missing except at the end (because of the way | |
298 // the accumulators were initially filled in accumulate()) | |
299 | |
300 const OutputBinSummary &summary = j->second; | |
301 double result = 0.f; | |
302 | |
303 switch (type) { | |
304 | |
305 case Minimum: | |
306 result = summary.minimum; | |
307 break; | |
308 | |
309 case Maximum: | |
310 result = summary.maximum; | |
311 break; | |
312 | |
313 case Mean: | |
314 if (continuous) { | |
315 result = summary.mean_c; | |
316 } else if (summary.count) { | |
317 result = summary.sum / summary.count; | |
318 } | |
319 break; | |
320 | |
321 case Median: | |
322 if (continuous) result = summary.median_c; | |
323 else result = summary.median; | |
324 break; | |
325 | |
326 case Mode: | |
327 if (continuous) result = summary.mode_c; | |
328 else result = summary.mode; | |
329 break; | |
330 | |
331 case Sum: | |
332 result = summary.sum; | |
333 break; | |
334 | |
335 case Variance: | |
336 if (continuous) result = summary.variance_c; | |
337 else result = summary.variance; | |
338 break; | |
339 | |
340 case StandardDeviation: | |
341 if (continuous) result = sqrtf(summary.variance_c); | |
342 else result = sqrtf(summary.variance); | |
343 break; | |
344 | |
345 case Count: | |
346 result = summary.count; | |
347 break; | |
348 | |
349 case UnknownSummaryType: | |
350 break; | |
351 | |
352 default: | |
353 break; | |
354 } | |
355 | |
356 f.values.push_back(result); | |
357 } | |
358 | |
359 fl.push_back(f); | |
360 } | |
361 return fl; | |
362 } | |
363 | |
364 Plugin::FeatureSet | |
365 PluginSummarisingAdapter::Impl::getSummaryForAllOutputs(SummaryType type, | |
366 AveragingMethod avg) | |
367 { | |
368 if (!m_reduced) { | |
369 accumulateFinalDurations(); | |
370 segment(); | |
371 reduce(); | |
372 m_reduced = true; | |
373 } | |
374 | |
375 FeatureSet fs; | |
376 for (OutputSummarySegmentMap::const_iterator i = m_summaries.begin(); | |
377 i != m_summaries.end(); ++i) { | |
378 fs[i->first] = getSummaryForOutput(i->first, type, avg); | |
379 } | |
380 return fs; | |
381 } | |
382 | |
383 void | |
384 PluginSummarisingAdapter::Impl::accumulate(const FeatureSet &fs, | |
385 RealTime timestamp, | |
386 bool final) | |
387 { | |
388 for (FeatureSet::const_iterator i = fs.begin(); i != fs.end(); ++i) { | |
389 for (FeatureList::const_iterator j = i->second.begin(); | |
390 j != i->second.end(); ++j) { | |
391 if (j->hasTimestamp) { | |
392 accumulate(i->first, *j, j->timestamp, final); | |
393 } else { | |
394 //!!! is this correct? | |
395 accumulate(i->first, *j, timestamp, final); | |
396 } | |
397 } | |
398 } | |
399 } | |
400 | |
401 std::string | |
402 PluginSummarisingAdapter::Impl::getSummaryLabel(SummaryType type, | |
403 AveragingMethod avg) | |
404 { | |
405 std::string label; | |
406 std::string avglabel; | |
407 | |
408 if (avg == SampleAverage) avglabel = ", sample average"; | |
409 else avglabel = ", continuous-time average"; | |
410 | |
411 switch (type) { | |
412 case Minimum: label = "(minimum value)"; break; | |
413 case Maximum: label = "(maximum value)"; break; | |
414 case Mean: label = "(mean value" + avglabel + ")"; break; | |
415 case Median: label = "(median value" + avglabel + ")"; break; | |
416 case Mode: label = "(modal value" + avglabel + ")"; break; | |
417 case Sum: label = "(sum)"; break; | |
418 case Variance: label = "(variance" + avglabel + ")"; break; | |
419 case StandardDeviation: label = "(standard deviation" + avglabel + ")"; break; | |
420 case Count: label = "(count)"; break; | |
421 case UnknownSummaryType: label = "(unknown summary)"; break; | |
422 } | |
423 | |
424 return label; | |
425 } | |
426 | |
427 void | |
428 PluginSummarisingAdapter::Impl::accumulate(int output, | |
429 const Feature &f, | |
430 RealTime timestamp, | |
431 bool final) | |
432 { | |
433 //!!! to do: use timestamp to determine which segment we're on | |
434 | |
435 //!!! What should happen if a feature's duration spans a segment | |
436 // boundary? I think we probably want to chop it, and pretend that it | |
437 // appears in both -- don't we? do we? A very long feature (e.g. key, | |
438 // if the whole audio is in a single key) might span many or all | |
439 // segments, and we want that to be reflected in the results (e.g. it | |
440 // is the modal key in all of those segments, not just the first). | |
441 // That is actually quite complicated to do! | |
442 | |
443 //!!! This affects how we record things. If features spanning a | |
444 // boundary should be chopped, then we need to have per-segment | |
445 // accumulators (and the feature value goes into both -- perhaps we | |
446 // need a separate phase to split the accumulator up into segments). | |
447 // If features spanning a boundary should be counted only in the first | |
448 // segment, with their full duration, then we should store them in a | |
449 // single accumulator and distribute into segments only on reduce. | |
450 | |
451 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
452 std::cerr << "output " << output << ": timestamp " << timestamp << ", prev timestamp " << m_prevTimestamps[output] << ", final " << final << std::endl; | |
453 #endif | |
454 | |
455 // At each process step, accumulate() is called once for each | |
456 // feature on each output within that process's returned feature | |
457 // list, and with the timestamp passed in being that of the start | |
458 // of the process block. | |
459 | |
460 // At the end (in getRemainingFeatures), accumulate() is called | |
461 // once for each feature on each output within the feature list | |
462 // returned by getRemainingFeatures, and with the timestamp being | |
463 // the same as the last process block and final set to true. | |
464 | |
465 // (What if getRemainingFeatures doesn't return any features? We | |
466 // still need to ensure that the final duration is written. Need | |
467 // a separate function to close the durations.) | |
468 | |
469 // At each call, we pull out the value for the feature and stuff | |
470 // it into the accumulator's appropriate values array; and we | |
471 // calculate the duration for the _previous_ feature, or pull it | |
472 // from the prevDurations array if the previous feature had a | |
473 // duration in its structure, and stuff that into the | |
474 // accumulator's appropriate durations array. | |
475 | |
476 if (m_prevDurations.find(output) != m_prevDurations.end()) { | |
477 | |
478 // Not the first time accumulate has been called for this | |
479 // output -- there has been a previous feature | |
480 | |
481 RealTime prevDuration; | |
482 | |
483 // Note that m_prevDurations[output] only contains the | |
484 // duration field that was contained in the previous feature. | |
485 // If it didn't have an explicit duration, | |
486 // m_prevDurations[output] should be INVALID_DURATION and we | |
487 // will have to calculate the duration from the previous and | |
488 // current timestamps. | |
489 | |
490 if (m_prevDurations[output] != INVALID_DURATION) { | |
491 prevDuration = m_prevDurations[output]; | |
492 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
493 std::cerr << "Previous duration from previous feature: " << prevDuration << std::endl; | |
494 #endif | |
495 } else { | |
496 prevDuration = timestamp - m_prevTimestamps[output]; | |
497 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
498 std::cerr << "Previous duration from diff: " << timestamp << " - " | |
499 << m_prevTimestamps[output] << std::endl; | |
500 #endif | |
501 } | |
502 | |
503 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
504 std::cerr << "output " << output << ": "; | |
505 std::cerr << "Pushing previous duration as " << prevDuration << std::endl; | |
506 #endif | |
507 | |
508 m_accumulators[output].results | |
509 [m_accumulators[output].results.size() - 1] | |
510 .duration = prevDuration; | |
511 } | |
512 | |
513 if (f.hasDuration) m_prevDurations[output] = f.duration; | |
514 else m_prevDurations[output] = INVALID_DURATION; | |
515 | |
516 m_prevTimestamps[output] = timestamp; | |
517 | |
518 if (f.hasDuration) { | |
519 RealTime et = timestamp; | |
520 et = et + f.duration; | |
521 if (et > m_endTime) m_endTime = et; | |
522 } | |
523 | |
524 Result result; | |
525 result.time = timestamp; | |
526 result.duration = INVALID_DURATION; | |
527 | |
528 if (f.values.size() > m_accumulators[output].bins) { | |
529 m_accumulators[output].bins = f.values.size(); | |
530 } | |
531 | |
532 for (int i = 0; i < int(f.values.size()); ++i) { | |
533 result.values.push_back(f.values[i]); | |
534 } | |
535 | |
536 m_accumulators[output].results.push_back(result); | |
537 } | |
538 | |
539 void | |
540 PluginSummarisingAdapter::Impl::accumulateFinalDurations() | |
541 { | |
542 for (OutputTimestampMap::iterator i = m_prevTimestamps.begin(); | |
543 i != m_prevTimestamps.end(); ++i) { | |
544 | |
545 int output = i->first; | |
546 | |
547 int acount = m_accumulators[output].results.size(); | |
548 | |
549 if (acount == 0) continue; | |
550 | |
551 RealTime prevTimestamp = i->second; | |
552 | |
553 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
554 std::cerr << "output " << output << ": "; | |
555 #endif | |
556 | |
557 if (m_prevDurations.find(output) != m_prevDurations.end() && | |
558 m_prevDurations[output] != INVALID_DURATION) { | |
559 | |
560 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
561 std::cerr << "Pushing final duration from feature as " << m_prevDurations[output] << std::endl; | |
562 #endif | |
563 | |
564 m_accumulators[output].results[acount - 1].duration = | |
565 m_prevDurations[output]; | |
566 | |
567 } else { | |
568 | |
569 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
570 std::cerr << "Pushing final duration from diff as " << m_endTime << " - " << m_prevTimestamps[output] << std::endl; | |
571 #endif | |
572 | |
573 m_accumulators[output].results[acount - 1].duration = | |
574 m_endTime - m_prevTimestamps[output]; | |
575 } | |
576 | |
577 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
578 std::cerr << "so duration for result no " << acount-1 << " is " | |
579 << m_accumulators[output].results[acount-1].duration | |
580 << std::endl; | |
581 #endif | |
582 } | |
583 } | |
584 | |
585 void | |
586 PluginSummarisingAdapter::Impl::findSegmentBounds(RealTime t, | |
587 RealTime &start, | |
588 RealTime &end) | |
589 { | |
590 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT | |
591 std::cerr << "findSegmentBounds: t = " << t << std::endl; | |
592 #endif | |
593 | |
594 SegmentBoundaries::const_iterator i = std::upper_bound | |
595 (m_boundaries.begin(), m_boundaries.end(), t); | |
596 | |
597 start = RealTime::zeroTime; | |
598 end = m_endTime; | |
599 | |
600 if (i != m_boundaries.end()) { | |
601 end = *i; | |
602 } | |
603 | |
604 if (i != m_boundaries.begin()) { | |
605 start = *--i; | |
606 } | |
607 | |
608 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT | |
609 std::cerr << "findSegmentBounds: " << t << " is in segment " << start << " -> " << end << std::endl; | |
610 #endif | |
611 } | |
612 | |
613 void | |
614 PluginSummarisingAdapter::Impl::segment() | |
615 { | |
616 SegmentBoundaries::iterator boundaryitr = m_boundaries.begin(); | |
617 RealTime segmentStart = RealTime::zeroTime; | |
618 | |
619 for (OutputAccumulatorMap::iterator i = m_accumulators.begin(); | |
620 i != m_accumulators.end(); ++i) { | |
621 | |
622 int output = i->first; | |
623 OutputAccumulator &source = i->second; | |
624 | |
625 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT | |
626 std::cerr << "segment: total results for output " << output << " = " | |
627 << source.results.size() << std::endl; | |
628 #endif | |
629 | |
630 //!!! This is basically nonsense if the results have no values | |
631 //!!! (i.e. their times and counts are the only things of | |
632 //!!! interest) but perhaps it's the user's problem if they | |
633 //!!! ask for segmentation in that case | |
634 | |
635 for (int n = 0; n < source.results.size(); ++n) { | |
636 | |
637 // This result spans source.results[n].time to | |
638 // source.results[n].time + source.results[n].duration. | |
639 // We need to dispose it into segments appropriately | |
640 | |
641 RealTime resultStart = source.results[n].time; | |
642 RealTime resultEnd = resultStart + source.results[n].duration; | |
643 | |
644 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT | |
645 std::cerr << "output: " << output << ", result start = " << resultStart << ", end = " << resultEnd << std::endl; | |
646 #endif | |
647 | |
648 RealTime segmentStart = RealTime::zeroTime; | |
649 RealTime segmentEnd = resultEnd - RealTime(1, 0); | |
650 | |
651 while (segmentEnd < resultEnd) { | |
652 | |
653 findSegmentBounds(resultStart, segmentStart, segmentEnd); | |
654 | |
655 RealTime chunkStart = resultStart; | |
656 if (chunkStart < segmentStart) chunkStart = segmentStart; | |
657 | |
658 RealTime chunkEnd = resultEnd; | |
659 if (chunkEnd > segmentEnd) chunkEnd = segmentEnd; | |
660 | |
661 m_segmentedAccumulators[output][segmentStart].bins = source.bins; | |
662 | |
663 Result chunk; | |
664 chunk.time = chunkStart; | |
665 chunk.duration = chunkEnd - chunkStart; | |
666 chunk.values = source.results[n].values; | |
667 | |
668 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT | |
669 std::cerr << "chunk for segment " << segmentStart << ": from " << chunk.time << ", duration " << chunk.duration << std::endl; | |
670 #endif | |
671 | |
672 m_segmentedAccumulators[output][segmentStart].results | |
673 .push_back(chunk); | |
674 | |
675 resultStart = chunkEnd; | |
676 } | |
677 } | |
678 } | |
679 } | |
680 | |
681 struct ValueDurationFloatPair | |
682 { | |
683 float value; | |
684 float duration; | |
685 | |
686 ValueDurationFloatPair() : value(0), duration(0) { } | |
687 ValueDurationFloatPair(float v, float d) : value(v), duration(d) { } | |
688 ValueDurationFloatPair &operator=(const ValueDurationFloatPair &p) { | |
689 value = p.value; | |
690 duration = p.duration; | |
691 return *this; | |
692 } | |
693 bool operator<(const ValueDurationFloatPair &p) const { | |
694 return value < p.value; | |
695 } | |
696 }; | |
697 | |
698 static double toSec(const RealTime &r) | |
699 { | |
700 return r.sec + double(r.nsec) / 1000000000.0; | |
701 } | |
702 | |
703 void | |
704 PluginSummarisingAdapter::Impl::reduce() | |
705 { | |
706 for (OutputSegmentAccumulatorMap::iterator i = | |
707 m_segmentedAccumulators.begin(); | |
708 i != m_segmentedAccumulators.end(); ++i) { | |
709 | |
710 int output = i->first; | |
711 SegmentAccumulatorMap &segments = i->second; | |
712 | |
713 for (SegmentAccumulatorMap::iterator j = segments.begin(); | |
714 j != segments.end(); ++j) { | |
715 | |
716 RealTime segmentStart = j->first; | |
717 OutputAccumulator &accumulator = j->second; | |
718 | |
719 int sz = accumulator.results.size(); | |
720 | |
721 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
722 std::cerr << "reduce: segment starting at " << segmentStart | |
723 << " on output " << output << " has " << sz << " result(s)" << std::endl; | |
724 #endif | |
725 | |
726 double totalDuration = 0.0; | |
727 //!!! is this right? | |
728 if (sz > 0) { | |
729 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
730 std::cerr << "last time = " << accumulator.results[sz-1].time | |
731 << ", duration = " << accumulator.results[sz-1].duration | |
732 << " (step = " << m_stepSize << ", block = " << m_blockSize << ")" | |
733 << std::endl; | |
734 #endif | |
735 totalDuration = toSec((accumulator.results[sz-1].time + | |
736 accumulator.results[sz-1].duration) - | |
737 segmentStart); | |
738 } | |
739 | |
740 for (int bin = 0; bin < accumulator.bins; ++bin) { | |
741 | |
742 // work on all values over time for a single bin | |
743 | |
744 OutputBinSummary summary; | |
745 | |
746 summary.count = sz; | |
747 | |
748 summary.minimum = 0.f; | |
749 summary.maximum = 0.f; | |
750 | |
751 summary.median = 0.f; | |
752 summary.mode = 0.f; | |
753 summary.sum = 0.f; | |
754 summary.variance = 0.f; | |
755 | |
756 summary.median_c = 0.f; | |
757 summary.mode_c = 0.f; | |
758 summary.mean_c = 0.f; | |
759 summary.variance_c = 0.f; | |
760 | |
761 if (sz == 0) continue; | |
762 | |
763 std::vector<ValueDurationFloatPair> valvec; | |
764 | |
765 for (int k = 0; k < sz; ++k) { | |
766 while (accumulator.results[k].values.size() < | |
767 accumulator.bins) { | |
768 accumulator.results[k].values.push_back(0.f); | |
769 } | |
770 } | |
771 | |
772 for (int k = 0; k < sz; ++k) { | |
773 float value = accumulator.results[k].values[bin]; | |
774 valvec.push_back(ValueDurationFloatPair | |
775 (value, | |
776 toSec(accumulator.results[k].duration))); | |
777 } | |
778 | |
779 std::sort(valvec.begin(), valvec.end()); | |
780 | |
781 summary.minimum = valvec[0].value; | |
782 summary.maximum = valvec[sz-1].value; | |
783 | |
784 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
785 std::cerr << "total duration = " << totalDuration << std::endl; | |
786 #endif | |
787 | |
788 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
789 /* | |
790 std::cerr << "value vector for medians:" << std::endl; | |
791 for (int k = 0; k < sz; ++k) { | |
792 std::cerr << "(" << valvec[k].value << "," << valvec[k].duration << ") "; | |
793 } | |
794 std::cerr << std::endl; | |
795 */ | |
796 #endif | |
797 | |
798 if (sz % 2 == 1) { | |
799 summary.median = valvec[sz/2].value; | |
800 } else { | |
801 summary.median = (valvec[sz/2].value + valvec[sz/2 + 1].value) / 2; | |
802 } | |
803 | |
804 double duracc = 0.0; | |
805 summary.median_c = valvec[sz-1].value; | |
806 | |
807 for (int k = 0; k < sz; ++k) { | |
808 duracc += valvec[k].duration; | |
809 if (duracc > totalDuration/2) { | |
810 summary.median_c = valvec[k].value; | |
811 break; | |
812 } | |
813 } | |
814 | |
815 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
816 std::cerr << "median_c = " << summary.median_c << std::endl; | |
817 std::cerr << "median = " << summary.median << std::endl; | |
818 #endif | |
819 | |
820 std::map<float, int> distribution; | |
821 | |
822 for (int k = 0; k < sz; ++k) { | |
823 summary.sum += accumulator.results[k].values[bin]; | |
824 distribution[accumulator.results[k].values[bin]] += 1; | |
825 } | |
826 | |
827 int md = 0; | |
828 | |
829 for (std::map<float, int>::iterator di = distribution.begin(); | |
830 di != distribution.end(); ++di) { | |
831 if (di->second > md) { | |
832 md = di->second; | |
833 summary.mode = di->first; | |
834 } | |
835 } | |
836 | |
837 distribution.clear(); | |
838 | |
839 std::map<float, double> distribution_c; | |
840 | |
841 for (int k = 0; k < sz; ++k) { | |
842 distribution_c[accumulator.results[k].values[bin]] | |
843 += toSec(accumulator.results[k].duration); | |
844 } | |
845 | |
846 double mrd = 0.0; | |
847 | |
848 for (std::map<float, double>::iterator di = distribution_c.begin(); | |
849 di != distribution_c.end(); ++di) { | |
850 if (di->second > mrd) { | |
851 mrd = di->second; | |
852 summary.mode_c = di->first; | |
853 } | |
854 } | |
855 | |
856 distribution_c.clear(); | |
857 | |
858 if (totalDuration > 0.0) { | |
859 | |
860 double sum_c = 0.0; | |
861 | |
862 for (int k = 0; k < sz; ++k) { | |
863 double value = accumulator.results[k].values[bin] | |
864 * toSec(accumulator.results[k].duration); | |
865 sum_c += value; | |
866 } | |
867 | |
868 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
869 std::cerr << "mean_c = " << sum_c << " / " << totalDuration << " = " | |
870 << sum_c / totalDuration << " (sz = " << sz << ")" << std::endl; | |
871 #endif | |
872 | |
873 summary.mean_c = sum_c / totalDuration; | |
874 | |
875 for (int k = 0; k < sz; ++k) { | |
876 double value = accumulator.results[k].values[bin]; | |
877 // * toSec(accumulator.results[k].duration); | |
878 summary.variance_c += | |
879 (value - summary.mean_c) * (value - summary.mean_c) | |
880 * toSec(accumulator.results[k].duration); | |
881 } | |
882 | |
883 // summary.variance_c /= summary.count; | |
884 summary.variance_c /= totalDuration; | |
885 } | |
886 | |
887 double mean = summary.sum / summary.count; | |
888 | |
889 #ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER | |
890 std::cerr << "mean = " << summary.sum << " / " << summary.count << " = " | |
891 << summary.sum / summary.count << std::endl; | |
892 #endif | |
893 | |
894 for (int k = 0; k < sz; ++k) { | |
895 float value = accumulator.results[k].values[bin]; | |
896 summary.variance += (value - mean) * (value - mean); | |
897 } | |
898 summary.variance /= summary.count; | |
899 | |
900 m_summaries[output][segmentStart][bin] = summary; | |
901 } | |
902 } | |
903 } | |
904 | |
905 m_segmentedAccumulators.clear(); | |
906 m_accumulators.clear(); | |
907 } | |
908 | |
909 | |
910 } | |
911 | |
912 } | |
913 |