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@174
|
71 };
|
cannam@174
|
72
|
cannam@174
|
73 typedef std::map<int, OutputAccumulator> OutputAccumulatorMap;
|
cannam@174
|
74 OutputAccumulatorMap m_accumulators;
|
cannam@174
|
75
|
cannam@174
|
76 struct OutputBinSummary {
|
cannam@174
|
77 float minimum;
|
cannam@174
|
78 float maximum;
|
cannam@174
|
79 float median;
|
cannam@174
|
80 float mode;
|
cannam@174
|
81 float sum;
|
cannam@174
|
82 float variance;
|
cannam@174
|
83 int count;
|
cannam@174
|
84 };
|
cannam@174
|
85
|
cannam@174
|
86 typedef std::map<int, OutputBinSummary> OutputSummary;
|
cannam@174
|
87 typedef std::map<RealTime, OutputSummary> SummarySegmentMap;
|
cannam@174
|
88 typedef std::map<int, SummarySegmentMap> OutputSummarySegmentMap;
|
cannam@174
|
89
|
cannam@174
|
90 OutputSummarySegmentMap m_summaries;
|
cannam@174
|
91
|
cannam@174
|
92 RealTime m_lastTimestamp;
|
cannam@174
|
93
|
cannam@174
|
94 void accumulate(const FeatureSet &fs, RealTime);
|
cannam@174
|
95 void accumulate(int output, const Feature &f, RealTime);
|
cannam@174
|
96 void reduce();
|
cannam@173
|
97 };
|
cannam@173
|
98
|
cannam@173
|
99 PluginSummarisingAdapter::PluginSummarisingAdapter(Plugin *plugin) :
|
cannam@173
|
100 PluginWrapper(plugin)
|
cannam@173
|
101 {
|
cannam@173
|
102 m_impl = new Impl(plugin, m_inputSampleRate);
|
cannam@173
|
103 }
|
cannam@173
|
104
|
cannam@173
|
105 PluginSummarisingAdapter::~PluginSummarisingAdapter()
|
cannam@173
|
106 {
|
cannam@173
|
107 delete m_impl;
|
cannam@173
|
108 }
|
cannam@173
|
109
|
cannam@173
|
110 Plugin::FeatureSet
|
cannam@173
|
111 PluginSummarisingAdapter::process(const float *const *inputBuffers, RealTime timestamp)
|
cannam@173
|
112 {
|
cannam@173
|
113 return m_impl->process(inputBuffers, timestamp);
|
cannam@173
|
114 }
|
cannam@173
|
115
|
cannam@174
|
116 Plugin::FeatureSet
|
cannam@174
|
117 PluginSummarisingAdapter::getRemainingFeatures()
|
cannam@174
|
118 {
|
cannam@174
|
119 return m_impl->getRemainingFeatures();
|
cannam@174
|
120 }
|
cannam@174
|
121
|
cannam@175
|
122 Plugin::FeatureList
|
cannam@176
|
123 PluginSummarisingAdapter::getSummaryForOutput(int output, SummaryType type)
|
cannam@175
|
124 {
|
cannam@176
|
125 return m_impl->getSummaryForOutput(output, type);
|
cannam@176
|
126 }
|
cannam@176
|
127
|
cannam@176
|
128 Plugin::FeatureSet
|
cannam@176
|
129 PluginSummarisingAdapter::getSummaryForAllOutputs(SummaryType type)
|
cannam@176
|
130 {
|
cannam@176
|
131 return m_impl->getSummaryForAllOutputs(type);
|
cannam@175
|
132 }
|
cannam@173
|
133
|
cannam@173
|
134 PluginSummarisingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) :
|
cannam@174
|
135 m_plugin(plugin)
|
cannam@173
|
136 {
|
cannam@173
|
137 }
|
cannam@173
|
138
|
cannam@173
|
139 PluginSummarisingAdapter::Impl::~Impl()
|
cannam@173
|
140 {
|
cannam@173
|
141 }
|
cannam@173
|
142
|
cannam@174
|
143 Plugin::FeatureSet
|
cannam@174
|
144 PluginSummarisingAdapter::Impl::process(const float *const *inputBuffers, RealTime timestamp)
|
cannam@174
|
145 {
|
cannam@174
|
146 FeatureSet fs = m_plugin->process(inputBuffers, timestamp);
|
cannam@174
|
147 accumulate(fs, timestamp);
|
cannam@174
|
148 m_lastTimestamp = timestamp;
|
cannam@174
|
149 return fs;
|
cannam@174
|
150 }
|
cannam@174
|
151
|
cannam@174
|
152 Plugin::FeatureSet
|
cannam@174
|
153 PluginSummarisingAdapter::Impl::getRemainingFeatures()
|
cannam@174
|
154 {
|
cannam@174
|
155 FeatureSet fs = m_plugin->getRemainingFeatures();
|
cannam@174
|
156 accumulate(fs, m_lastTimestamp);
|
cannam@174
|
157 reduce();
|
cannam@174
|
158 return fs;
|
cannam@174
|
159 }
|
cannam@174
|
160
|
cannam@175
|
161 Plugin::FeatureList
|
cannam@176
|
162 PluginSummarisingAdapter::Impl::getSummaryForOutput(int output, SummaryType type)
|
cannam@175
|
163 {
|
cannam@175
|
164 //!!! need to ensure that this is only called after processing is
|
cannam@175
|
165 //!!! complete (at the moment processing is "completed" in the
|
cannam@175
|
166 //!!! call to getRemainingFeatures, but we don't want to require
|
cannam@175
|
167 //!!! the host to call getRemainingFeatures at all unless it
|
cannam@175
|
168 //!!! actually wants the raw features too -- calling getSummary
|
cannam@175
|
169 //!!! should be enough -- we do need to ensure that all data has
|
cannam@175
|
170 //!!! been processed though!)
|
cannam@175
|
171 FeatureList fl;
|
cannam@175
|
172 for (SummarySegmentMap::const_iterator i = m_summaries[output].begin();
|
cannam@175
|
173 i != m_summaries[output].end(); ++i) {
|
cannam@175
|
174 Feature f;
|
cannam@175
|
175 f.hasTimestamp = true;
|
cannam@175
|
176 f.timestamp = i->first;
|
cannam@175
|
177 f.hasDuration = false;
|
cannam@175
|
178 for (OutputSummary::const_iterator j = i->second.begin();
|
cannam@175
|
179 j != i->second.end(); ++j) {
|
cannam@175
|
180
|
cannam@175
|
181 // these will be ordered by bin number, and no bin numbers
|
cannam@175
|
182 // will be missing except at the end (because of the way
|
cannam@175
|
183 // the accumulators were initially filled in accumulate())
|
cannam@175
|
184
|
cannam@175
|
185 const OutputBinSummary &summary = j->second;
|
cannam@175
|
186 float result = 0.f;
|
cannam@175
|
187
|
cannam@175
|
188 switch (type) {
|
cannam@175
|
189
|
cannam@175
|
190 case Minimum:
|
cannam@175
|
191 result = summary.minimum;
|
cannam@175
|
192 break;
|
cannam@175
|
193
|
cannam@175
|
194 case Maximum:
|
cannam@175
|
195 result = summary.maximum;
|
cannam@175
|
196 break;
|
cannam@175
|
197
|
cannam@175
|
198 case Mean:
|
cannam@175
|
199 if (summary.count) {
|
cannam@175
|
200 result = summary.sum / summary.count;
|
cannam@175
|
201 }
|
cannam@175
|
202 break;
|
cannam@175
|
203
|
cannam@175
|
204 case Median:
|
cannam@175
|
205 result = summary.median;
|
cannam@175
|
206 break;
|
cannam@175
|
207
|
cannam@175
|
208 case Mode:
|
cannam@175
|
209 result = summary.mode;
|
cannam@175
|
210 break;
|
cannam@175
|
211
|
cannam@175
|
212 case Sum:
|
cannam@175
|
213 result = summary.sum;
|
cannam@175
|
214 break;
|
cannam@175
|
215
|
cannam@175
|
216 case Variance:
|
cannam@175
|
217 result = summary.variance;
|
cannam@175
|
218 break;
|
cannam@175
|
219
|
cannam@175
|
220 case StandardDeviation:
|
cannam@175
|
221 result = sqrtf(summary.variance);
|
cannam@175
|
222 break;
|
cannam@175
|
223
|
cannam@175
|
224 case Count:
|
cannam@175
|
225 result = summary.count;
|
cannam@175
|
226 break;
|
cannam@175
|
227 }
|
cannam@175
|
228 }
|
cannam@175
|
229
|
cannam@175
|
230 fl.push_back(f);
|
cannam@175
|
231 }
|
cannam@175
|
232 return fl;
|
cannam@175
|
233 }
|
cannam@175
|
234
|
cannam@176
|
235 Plugin::FeatureSet
|
cannam@176
|
236 PluginSummarisingAdapter::Impl::getSummaryForAllOutputs(SummaryType type)
|
cannam@176
|
237 {
|
cannam@176
|
238 FeatureSet fs;
|
cannam@176
|
239 for (OutputSummarySegmentMap::const_iterator i = m_summaries.begin();
|
cannam@176
|
240 i != m_summaries.end(); ++i) {
|
cannam@176
|
241 fs[i->first] = getSummaryForOutput(i->first, type);
|
cannam@176
|
242 }
|
cannam@176
|
243 return fs;
|
cannam@176
|
244 }
|
cannam@176
|
245
|
cannam@174
|
246 void
|
cannam@174
|
247 PluginSummarisingAdapter::Impl::accumulate(const FeatureSet &fs,
|
cannam@174
|
248 RealTime timestamp)
|
cannam@174
|
249 {
|
cannam@174
|
250 for (FeatureSet::const_iterator i = fs.begin(); i != fs.end(); ++i) {
|
cannam@174
|
251 for (FeatureList::const_iterator j = i->second.begin();
|
cannam@174
|
252 j != i->second.end(); ++j) {
|
cannam@174
|
253 accumulate(i->first, *j, timestamp);
|
cannam@174
|
254 }
|
cannam@174
|
255 }
|
cannam@174
|
256 }
|
cannam@174
|
257
|
cannam@174
|
258 void
|
cannam@174
|
259 PluginSummarisingAdapter::Impl::accumulate(int output,
|
cannam@174
|
260 const Feature &f,
|
cannam@174
|
261 RealTime timestamp)
|
cannam@174
|
262 {
|
cannam@174
|
263 //!!! use timestamp to determine which segment we're on
|
cannam@174
|
264 m_accumulators[output].count++;
|
cannam@174
|
265 for (int i = 0; i < int(f.values.size()); ++i) {
|
cannam@174
|
266 m_accumulators[output].values[i].push_back(f.values[i]);
|
cannam@174
|
267 }
|
cannam@174
|
268 }
|
cannam@174
|
269
|
cannam@174
|
270 void
|
cannam@174
|
271 PluginSummarisingAdapter::Impl::reduce()
|
cannam@174
|
272 {
|
cannam@174
|
273 RealTime segmentStart = RealTime::zeroTime; //!!!
|
cannam@174
|
274
|
cannam@174
|
275 for (OutputAccumulatorMap::iterator i = m_accumulators.begin();
|
cannam@174
|
276 i != m_accumulators.end(); ++i) {
|
cannam@174
|
277
|
cannam@174
|
278 int output = i->first;
|
cannam@174
|
279 OutputAccumulator &accumulator = i->second;
|
cannam@174
|
280
|
cannam@174
|
281 for (BinValueMap::iterator j = accumulator.values.begin();
|
cannam@174
|
282 j != accumulator.values.end(); ++j) {
|
cannam@174
|
283
|
cannam@174
|
284 int bin = j->first;
|
cannam@174
|
285 ValueList &values = j->second;
|
cannam@174
|
286
|
cannam@174
|
287 OutputBinSummary summary;
|
cannam@174
|
288 summary.minimum = 0.f;
|
cannam@174
|
289 summary.maximum = 0.f;
|
cannam@174
|
290 summary.median = 0.f;
|
cannam@174
|
291 summary.mode = 0.f;
|
cannam@174
|
292 summary.sum = 0.f;
|
cannam@174
|
293 summary.variance = 0.f;
|
cannam@174
|
294 summary.count = accumulator.count;
|
cannam@174
|
295 if (summary.count == 0 || values.empty()) continue;
|
cannam@174
|
296
|
cannam@174
|
297 std::sort(values.begin(), values.end());
|
cannam@174
|
298 int sz = values.size();
|
cannam@174
|
299
|
cannam@174
|
300 summary.minimum = values[0];
|
cannam@174
|
301 summary.maximum = values[sz-1];
|
cannam@174
|
302
|
cannam@174
|
303 if (sz % 2 == 1) {
|
cannam@174
|
304 summary.median = values[sz/2];
|
cannam@174
|
305 } else {
|
cannam@174
|
306 summary.median = (values[sz/2] + values[sz/2 + 1]) / 2;
|
cannam@174
|
307 }
|
cannam@174
|
308
|
cannam@174
|
309 std::map<float, int> distribution;
|
cannam@174
|
310
|
cannam@174
|
311 for (int k = 0; k < sz; ++k) {
|
cannam@174
|
312 summary.sum += values[k];
|
cannam@174
|
313 ++distribution[values[k]];
|
cannam@174
|
314 }
|
cannam@174
|
315
|
cannam@174
|
316 int md = 0;
|
cannam@174
|
317
|
cannam@174
|
318 //!!! I don't like this. Really the mode should be the
|
cannam@174
|
319 //!!! value that spans the longest period of time, not the
|
cannam@174
|
320 //!!! one that appears in the largest number of distinct
|
cannam@175
|
321 //!!! features. I suppose that a median by time rather
|
cannam@175
|
322 //!!! than number of features would also be useful.
|
cannam@174
|
323
|
cannam@174
|
324 for (std::map<float, int>::iterator di = distribution.begin();
|
cannam@174
|
325 di != distribution.end(); ++di) {
|
cannam@174
|
326 if (di->second > md) {
|
cannam@174
|
327 md = di->second;
|
cannam@174
|
328 summary.mode = di->first;
|
cannam@174
|
329 }
|
cannam@174
|
330 }
|
cannam@174
|
331
|
cannam@174
|
332 distribution.clear();
|
cannam@174
|
333
|
cannam@174
|
334 float mean = summary.sum / summary.count;
|
cannam@174
|
335
|
cannam@174
|
336 for (int k = 0; k < sz; ++k) {
|
cannam@174
|
337 summary.variance += (values[k] - mean) * (values[k] - mean);
|
cannam@174
|
338 }
|
cannam@174
|
339 summary.variance /= summary.count;
|
cannam@174
|
340
|
cannam@174
|
341 m_summaries[output][segmentStart][bin] = summary;
|
cannam@174
|
342 }
|
cannam@174
|
343 }
|
cannam@175
|
344
|
cannam@175
|
345 m_accumulators.clear();
|
cannam@174
|
346 }
|
cannam@174
|
347
|
cannam@174
|
348
|
cannam@174
|
349 }
|
cannam@174
|
350
|
cannam@174
|
351 }
|
cannam@174
|
352
|