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
|