Mercurial > hg > sonic-annotator
comparison runner/FeatureExtractionManager.cpp @ 1:92911f967a16
* some reorganisation
author | Chris Cannam |
---|---|
date | Thu, 11 Dec 2008 10:26:12 +0000 |
parents | FeatureExtractionManager.cpp@581b1b150a4d |
children | 03a02c1f0a9f |
comparison
equal
deleted
inserted
replaced
0:581b1b150a4d | 1:92911f967a16 |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Sonic Annotator | |
5 A utility for batch feature extraction from audio files. | |
6 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London. | |
7 Copyright 2007-2008 QMUL. | |
8 | |
9 This program is free software; you can redistribute it and/or | |
10 modify it under the terms of the GNU General Public License as | |
11 published by the Free Software Foundation; either version 2 of the | |
12 License, or (at your option) any later version. See the file | |
13 COPYING included with this distribution for more information. | |
14 */ | |
15 | |
16 #include "FeatureExtractionManager.h" | |
17 | |
18 #include <vamp-hostsdk/PluginChannelAdapter.h> | |
19 #include <vamp-hostsdk/PluginBufferingAdapter.h> | |
20 #include <vamp-hostsdk/PluginInputDomainAdapter.h> | |
21 #include <vamp-hostsdk/PluginSummarisingAdapter.h> | |
22 #include <vamp-hostsdk/PluginLoader.h> | |
23 | |
24 #include <iostream> | |
25 | |
26 using namespace std; | |
27 | |
28 using Vamp::Plugin; | |
29 using Vamp::PluginBase; | |
30 using Vamp::HostExt::PluginLoader; | |
31 using Vamp::HostExt::PluginChannelAdapter; | |
32 using Vamp::HostExt::PluginBufferingAdapter; | |
33 using Vamp::HostExt::PluginInputDomainAdapter; | |
34 using Vamp::HostExt::PluginSummarisingAdapter; | |
35 | |
36 #include "data/fileio/FileSource.h" | |
37 #include "data/fileio/AudioFileReader.h" | |
38 #include "data/fileio/AudioFileReaderFactory.h" | |
39 #include "data/fileio/PlaylistFileReader.h" | |
40 #include "base/TempDirectory.h" | |
41 #include "base/ProgressPrinter.h" | |
42 #include "transform/TransformFactory.h" | |
43 #include "rdf/RDFTransformFactory.h" | |
44 #include "transform/FeatureWriter.h" | |
45 | |
46 #include <QTextStream> | |
47 #include <QFile> | |
48 #include <QFileInfo> | |
49 | |
50 FeatureExtractionManager::FeatureExtractionManager() : | |
51 m_summariesOnly(false), | |
52 // We can read using an arbitrary fixed block size -- | |
53 // PluginBufferingAdapter handles this for us. It's likely to be | |
54 // quicker to use larger sizes than smallish ones like 1024 | |
55 m_blockSize(16384), | |
56 m_defaultSampleRate(0), | |
57 m_sampleRate(0), | |
58 m_channels(1) | |
59 { | |
60 } | |
61 | |
62 FeatureExtractionManager::~FeatureExtractionManager() | |
63 { | |
64 for (PluginMap::iterator pi = m_plugins.begin(); | |
65 pi != m_plugins.end(); ++pi) { | |
66 delete pi->first; | |
67 } | |
68 } | |
69 | |
70 void FeatureExtractionManager::setChannels(int channels) | |
71 { | |
72 m_channels = channels; | |
73 } | |
74 | |
75 void FeatureExtractionManager::setDefaultSampleRate(int sampleRate) | |
76 { | |
77 m_defaultSampleRate = sampleRate; | |
78 } | |
79 | |
80 static PluginSummarisingAdapter::SummaryType | |
81 getSummaryType(string name) | |
82 { | |
83 if (name == "min") return PluginSummarisingAdapter::Minimum; | |
84 if (name == "max") return PluginSummarisingAdapter::Maximum; | |
85 if (name == "mean") return PluginSummarisingAdapter::Mean; | |
86 if (name == "median") return PluginSummarisingAdapter::Median; | |
87 if (name == "mode") return PluginSummarisingAdapter::Mode; | |
88 if (name == "sum") return PluginSummarisingAdapter::Sum; | |
89 if (name == "variance") return PluginSummarisingAdapter::Variance; | |
90 if (name == "sd") return PluginSummarisingAdapter::StandardDeviation; | |
91 if (name == "count") return PluginSummarisingAdapter::Count; | |
92 return PluginSummarisingAdapter::UnknownSummaryType; | |
93 } | |
94 | |
95 bool FeatureExtractionManager::setSummaryTypes(const set<string> &names, | |
96 bool summariesOnly, | |
97 const PluginSummarisingAdapter::SegmentBoundaries &boundaries) | |
98 { | |
99 for (SummaryNameSet::const_iterator i = names.begin(); | |
100 i != names.end(); ++i) { | |
101 if (getSummaryType(*i) == PluginSummarisingAdapter::UnknownSummaryType) { | |
102 cerr << "ERROR: Unknown summary type \"" << *i << "\"" << endl; | |
103 return false; | |
104 } | |
105 } | |
106 m_summaries = names; | |
107 m_summariesOnly = summariesOnly; | |
108 m_boundaries = boundaries; | |
109 return true; | |
110 } | |
111 | |
112 bool FeatureExtractionManager::addFeatureExtractor | |
113 (Transform transform, const vector<FeatureWriter*> &writers) | |
114 { | |
115 //!!! exceptions rather than return values? | |
116 | |
117 if (transform.getSampleRate() == 0) { | |
118 if (m_sampleRate == 0) { | |
119 cerr << "NOTE: Transform does not specify a sample rate, using default rate of " << m_defaultSampleRate << endl; | |
120 transform.setSampleRate(m_defaultSampleRate); | |
121 m_sampleRate = m_defaultSampleRate; | |
122 } else { | |
123 cerr << "NOTE: Transform does not specify a sample rate, using previous transform's rate of " << m_sampleRate << endl; | |
124 transform.setSampleRate(m_sampleRate); | |
125 } | |
126 } | |
127 | |
128 if (m_sampleRate == 0) { | |
129 m_sampleRate = transform.getSampleRate(); | |
130 } | |
131 | |
132 if (transform.getSampleRate() != m_sampleRate) { | |
133 cerr << "WARNING: Transform sample rate " << transform.getSampleRate() << " does not match previously specified transform rate of " << m_sampleRate << " -- only a single rate is supported for each run" << endl; | |
134 cerr << "WARNING: Using previous rate of " << m_sampleRate << " for this transform as well" << endl; | |
135 transform.setSampleRate(m_sampleRate); | |
136 } | |
137 | |
138 Plugin *plugin = 0; | |
139 | |
140 // Remember what the original transform looked like, and index | |
141 // based on this -- because we may be about to fill in the zeros | |
142 // for step and block size, but we want any further copies with | |
143 // the same zeros to match this one | |
144 Transform originalTransform = transform; | |
145 | |
146 if (m_transformPluginMap.find(transform) == m_transformPluginMap.end()) { | |
147 | |
148 // Test whether we already have a transform that is identical | |
149 // to this, except for the output requested and/or the summary | |
150 // type -- if so, they should share plugin instances (a vital | |
151 // optimisation) | |
152 | |
153 for (TransformPluginMap::iterator i = m_transformPluginMap.begin(); | |
154 i != m_transformPluginMap.end(); ++i) { | |
155 Transform test = i->first; | |
156 test.setOutput(transform.getOutput()); | |
157 test.setSummaryType(transform.getSummaryType()); | |
158 if (transform == test) { | |
159 cerr << "NOTE: Already have transform identical to this one (for \"" | |
160 << transform.getIdentifier().toStdString() | |
161 << "\") in every detail except output identifier and/or " | |
162 << "summary type; sharing its plugin instance" << endl; | |
163 plugin = i->second; | |
164 if (transform.getSummaryType() != Transform::NoSummary && | |
165 !dynamic_cast<PluginSummarisingAdapter *>(plugin)) { | |
166 plugin = new PluginSummarisingAdapter(plugin); | |
167 i->second = plugin; | |
168 } | |
169 break; | |
170 } | |
171 } | |
172 | |
173 if (!plugin) { | |
174 | |
175 TransformFactory *tf = TransformFactory::getInstance(); | |
176 | |
177 PluginBase *pb = tf->instantiatePluginFor(transform); | |
178 plugin = tf->downcastVampPlugin(pb); | |
179 if (!plugin) { | |
180 //!!! todo: handle non-Vamp plugins too, or make the main --list | |
181 // option print out only Vamp transforms | |
182 cerr << "ERROR: Failed to load plugin for transform \"" | |
183 << transform.getIdentifier().toStdString() << "\"" << endl; | |
184 delete pb; | |
185 return false; | |
186 } | |
187 | |
188 // We will provide the plugin with arbitrary step and | |
189 // block sizes (so that we can use the same read/write | |
190 // block size for all transforms), and to that end we use | |
191 // a PluginBufferingAdapter. However, we need to know the | |
192 // underlying step size so that we can provide the right | |
193 // context for dense outputs. (Although, don't forget | |
194 // that the PluginBufferingAdapter rewrites | |
195 // OneSamplePerStep outputs so as to use FixedSampleRate | |
196 // -- so it supplies the sample rate in the output | |
197 // feature. I'm not sure whether we can easily use that.) | |
198 | |
199 size_t pluginStepSize = plugin->getPreferredStepSize(); | |
200 size_t pluginBlockSize = plugin->getPreferredBlockSize(); | |
201 | |
202 // adapt the plugin for buffering, channels, etc. | |
203 if (plugin->getInputDomain() == Plugin::FrequencyDomain) { | |
204 plugin = new PluginInputDomainAdapter(plugin); | |
205 } | |
206 | |
207 PluginBufferingAdapter *pba = new PluginBufferingAdapter(plugin); | |
208 plugin = pba; | |
209 | |
210 if (transform.getStepSize() != 0) { | |
211 pba->setPluginStepSize(transform.getStepSize()); | |
212 } else { | |
213 transform.setStepSize(pluginStepSize); | |
214 } | |
215 | |
216 if (transform.getBlockSize() != 0) { | |
217 pba->setPluginBlockSize(transform.getBlockSize()); | |
218 } else { | |
219 transform.setBlockSize(pluginBlockSize); | |
220 } | |
221 | |
222 plugin = new PluginChannelAdapter(plugin); | |
223 | |
224 if (!m_summaries.empty() || | |
225 transform.getSummaryType() != Transform::NoSummary) { | |
226 PluginSummarisingAdapter *adapter = | |
227 new PluginSummarisingAdapter(plugin); | |
228 adapter->setSummarySegmentBoundaries(m_boundaries); | |
229 plugin = adapter; | |
230 } | |
231 | |
232 if (!plugin->initialise(m_channels, m_blockSize, m_blockSize)) { | |
233 cerr << "ERROR: Plugin initialise (channels = " << m_channels << ", stepSize = " << m_blockSize << ", blockSize = " << m_blockSize << ") failed." << endl; | |
234 delete plugin; | |
235 return false; | |
236 } | |
237 | |
238 // cerr << "Initialised plugin" << endl; | |
239 | |
240 size_t actualStepSize = 0; | |
241 size_t actualBlockSize = 0; | |
242 pba->getActualStepAndBlockSizes(actualStepSize, actualBlockSize); | |
243 transform.setStepSize(actualStepSize); | |
244 transform.setBlockSize(actualBlockSize); | |
245 | |
246 Plugin::OutputList outputs = plugin->getOutputDescriptors(); | |
247 for (int i = 0; i < (int)outputs.size(); ++i) { | |
248 | |
249 // cerr << "Newly initialised plugin output " << i << " has bin count " << outputs[i].binCount << endl; | |
250 | |
251 m_pluginOutputs[plugin][outputs[i].identifier] = outputs[i]; | |
252 m_pluginOutputIndices[outputs[i].identifier] = i; | |
253 } | |
254 | |
255 cerr << "NOTE: Loaded and initialised plugin " << plugin | |
256 << " for transform \"" | |
257 << transform.getIdentifier().toStdString() << "\"" << endl; | |
258 } | |
259 | |
260 if (transform.getOutput() == "") { | |
261 transform.setOutput | |
262 (plugin->getOutputDescriptors()[0].identifier.c_str()); | |
263 } | |
264 | |
265 m_transformPluginMap[transform] = plugin; | |
266 | |
267 if (!(originalTransform == transform)) { | |
268 m_transformPluginMap[originalTransform] = plugin; | |
269 } | |
270 | |
271 } else { | |
272 | |
273 plugin = m_transformPluginMap[transform]; | |
274 } | |
275 | |
276 m_plugins[plugin][transform] = writers; | |
277 | |
278 return true; | |
279 } | |
280 | |
281 bool FeatureExtractionManager::addDefaultFeatureExtractor | |
282 (TransformId transformId, const vector<FeatureWriter*> &writers) | |
283 { | |
284 TransformFactory *tf = TransformFactory::getInstance(); | |
285 | |
286 if (m_sampleRate == 0) { | |
287 if (m_defaultSampleRate == 0) { | |
288 cerr << "ERROR: Default transform requested, but no default sample rate available" << endl; | |
289 return false; | |
290 } else { | |
291 cerr << "NOTE: Using default sample rate of " << m_defaultSampleRate << " for default transform" << endl; | |
292 m_sampleRate = m_defaultSampleRate; | |
293 } | |
294 } | |
295 | |
296 Transform transform = tf->getDefaultTransformFor(transformId, m_sampleRate); | |
297 | |
298 return addFeatureExtractor(transform, writers); | |
299 } | |
300 | |
301 bool FeatureExtractionManager::addFeatureExtractorFromFile | |
302 (QString transformXmlFile, const vector<FeatureWriter*> &writers) | |
303 { | |
304 RDFTransformFactory factory | |
305 (QUrl::fromLocalFile(QFileInfo(transformXmlFile).absoluteFilePath()) | |
306 .toString()); | |
307 ProgressPrinter printer("Parsing transforms RDF file"); | |
308 std::vector<Transform> transforms = factory.getTransforms(&printer); | |
309 if (!factory.isOK()) { | |
310 cerr << "WARNING: FeatureExtractionManager::addFeatureExtractorFromFile: Failed to parse transforms file: " << factory.getErrorString().toStdString() << endl; | |
311 if (factory.isRDF()) { | |
312 return false; // no point trying it as XML | |
313 } | |
314 } | |
315 if (!transforms.empty()) { | |
316 bool success = true; | |
317 for (int i = 0; i < (int)transforms.size(); ++i) { | |
318 if (!addFeatureExtractor(transforms[i], writers)) { | |
319 success = false; | |
320 } | |
321 } | |
322 return success; | |
323 } | |
324 | |
325 QFile file(transformXmlFile); | |
326 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { | |
327 cerr << "ERROR: Failed to open transform XML file \"" | |
328 << transformXmlFile.toStdString() << "\" for reading" << endl; | |
329 return false; | |
330 } | |
331 | |
332 QTextStream *qts = new QTextStream(&file); | |
333 QString qs = qts->readAll(); | |
334 delete qts; | |
335 file.close(); | |
336 | |
337 Transform transform(qs); | |
338 | |
339 return addFeatureExtractor(transform, writers); | |
340 } | |
341 | |
342 void FeatureExtractionManager::extractFeatures(QString audioSource) | |
343 { | |
344 if (m_plugins.empty()) return; | |
345 | |
346 ProgressPrinter printer("Retrieving audio data..."); | |
347 | |
348 FileSource source(audioSource, &printer); | |
349 if (!source.isAvailable()) { | |
350 cerr << "ERROR: File or URL \"" << audioSource.toStdString() | |
351 << "\" could not be located" << endl; | |
352 exit(1); | |
353 } | |
354 | |
355 source.waitForData(); | |
356 | |
357 if (QFileInfo(audioSource).suffix().toLower() == "m3u") { | |
358 PlaylistFileReader reader(source); | |
359 if (reader.isOK()) { | |
360 vector<QString> files = reader.load(); | |
361 for (int i = 0; i < (int)files.size(); ++i) { | |
362 extractFeatures(files[i]); | |
363 } | |
364 return; | |
365 } else { | |
366 cerr << "ERROR: Playlist \"" << audioSource.toStdString() | |
367 << "\" could not be opened" << endl; | |
368 exit(1); | |
369 } | |
370 } | |
371 | |
372 if (m_sampleRate == 0) { | |
373 cerr << "ERROR: Internal error in FeatureExtractionManager::extractFeatures: Plugin list is non-empty, but no sample rate set" << endl; | |
374 exit(1); | |
375 } | |
376 | |
377 AudioFileReader *reader = | |
378 AudioFileReaderFactory::createReader(source, m_sampleRate, &printer); | |
379 | |
380 if (!reader) { | |
381 cerr << "ERROR: File or URL \"" << audioSource.toStdString() | |
382 << "\" could not be opened" << endl; | |
383 exit(1); | |
384 } | |
385 | |
386 size_t channels = reader->getChannelCount(); | |
387 | |
388 cerr << "Opened " << channels << "-channel file or URL \"" << audioSource.toStdString() << "\"" << endl; | |
389 | |
390 // reject file if it has too few channels, plugin will handle if it has too many | |
391 if ((int)channels < m_channels) { | |
392 //!!! should not be terminating here! | |
393 cerr << "ERROR: File or URL \"" << audioSource.toStdString() << "\" has less than " << m_channels << " channels" << endl; | |
394 exit(1); | |
395 } | |
396 | |
397 // allocate audio buffers | |
398 float **data = new float *[m_channels]; | |
399 for (int c = 0; c < m_channels; ++c) { | |
400 data[c] = new float[m_blockSize]; | |
401 } | |
402 | |
403 size_t frameCount = reader->getFrameCount(); | |
404 | |
405 // cerr << "file has " << frameCount << " frames" << endl; | |
406 | |
407 for (PluginMap::iterator pi = m_plugins.begin(); | |
408 pi != m_plugins.end(); ++pi) { | |
409 | |
410 Plugin *plugin = pi->first; | |
411 | |
412 // std::cerr << "Calling reset on " << plugin << std::endl; | |
413 plugin->reset(); | |
414 | |
415 for (TransformWriterMap::iterator ti = pi->second.begin(); | |
416 ti != pi->second.end(); ++ti) { | |
417 | |
418 const Transform &transform = ti->first; | |
419 | |
420 //!!! we may want to set the start and duration times for extraction | |
421 // in the transform record (defaults of zero indicate extraction | |
422 // from the whole file) | |
423 // transform.setStartTime(RealTime::zeroTime); | |
424 // transform.setDuration | |
425 // (RealTime::frame2RealTime(reader->getFrameCount(), m_sampleRate)); | |
426 | |
427 string outputId = transform.getOutput().toStdString(); | |
428 if (m_pluginOutputs[plugin].find(outputId) == | |
429 m_pluginOutputs[plugin].end()) { | |
430 //!!! throw? | |
431 cerr << "WARNING: Nonexistent plugin output \"" << outputId << "\" requested for transform \"" | |
432 << transform.getIdentifier().toStdString() << "\", ignoring this transform" | |
433 << endl; | |
434 /* | |
435 cerr << "Known outputs for all plugins are as follows:" << endl; | |
436 for (PluginOutputMap::const_iterator k = m_pluginOutputs.begin(); | |
437 k != m_pluginOutputs.end(); ++k) { | |
438 cerr << "Plugin " << k->first << ": "; | |
439 if (k->second.empty()) { | |
440 cerr << "(none)"; | |
441 } | |
442 for (OutputMap::const_iterator i = k->second.begin(); | |
443 i != k->second.end(); ++i) { | |
444 cerr << "\"" << i->first << "\" "; | |
445 } | |
446 cerr << endl; | |
447 } | |
448 */ | |
449 } | |
450 } | |
451 } | |
452 | |
453 long startFrame = 0; | |
454 long endFrame = frameCount; | |
455 | |
456 /*!!! No -- there is no single transform to pull this stuff from -- | |
457 * the transforms may have various start and end times, need to be far | |
458 * cleverer about this if we're going to support them | |
459 | |
460 RealTime trStartRT = transform.getStartTime(); | |
461 RealTime trDurationRT = transform.getDuration(); | |
462 | |
463 long trStart = RealTime::realTime2Frame(trStartRT, m_sampleRate); | |
464 long trDuration = RealTime::realTime2Frame(trDurationRT, m_sampleRate); | |
465 | |
466 if (trStart == 0 || trStart < startFrame) { | |
467 trStart = startFrame; | |
468 } | |
469 | |
470 if (trDuration == 0) { | |
471 trDuration = endFrame - trStart; | |
472 } | |
473 if (trStart + trDuration > endFrame) { | |
474 trDuration = endFrame - trStart; | |
475 } | |
476 | |
477 startFrame = trStart; | |
478 endFrame = trStart + trDuration; | |
479 */ | |
480 | |
481 for (PluginMap::iterator pi = m_plugins.begin(); | |
482 pi != m_plugins.end(); ++pi) { | |
483 | |
484 for (TransformWriterMap::const_iterator ti = pi->second.begin(); | |
485 ti != pi->second.end(); ++ti) { | |
486 | |
487 const vector<FeatureWriter *> &writers = ti->second; | |
488 | |
489 for (int j = 0; j < (int)writers.size(); ++j) { | |
490 FeatureWriter::TrackMetadata m; | |
491 m.title = reader->getTitle(); | |
492 m.maker = reader->getMaker(); | |
493 writers[j]->setTrackMetadata(audioSource, m); | |
494 } | |
495 } | |
496 } | |
497 | |
498 ProgressPrinter extractionProgress("Extracting and writing features..."); | |
499 int progress = 0; | |
500 | |
501 for (long i = startFrame; i < endFrame; i += m_blockSize) { | |
502 | |
503 //!!! inefficient, although much of the inefficiency may be | |
504 // susceptible to optimisation | |
505 | |
506 SampleBlock frames; | |
507 reader->getInterleavedFrames(i, m_blockSize, frames); | |
508 | |
509 // We have to do our own channel handling here; we can't just | |
510 // leave it to the plugin adapter because the same plugin | |
511 // adapter may have to serve for input files with various | |
512 // numbers of channels (so the adapter is simply configured | |
513 // with a fixed channel count, generally 1). | |
514 | |
515 int rc = reader->getChannelCount(); | |
516 | |
517 for (int j = 0; j < m_blockSize; ++j) { | |
518 for (int c = 0; c < m_channels; ++c) { | |
519 int index; | |
520 if (c < rc) { | |
521 index = j * rc + c; | |
522 data[c][j] = 0.f; | |
523 } else { | |
524 index = j * rc + (c % rc); | |
525 } | |
526 if (index < (int)frames.size()) { | |
527 data[c][j] += frames[index]; | |
528 } | |
529 } | |
530 } | |
531 | |
532 Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime | |
533 (i, m_sampleRate); | |
534 | |
535 for (PluginMap::iterator pi = m_plugins.begin(); | |
536 pi != m_plugins.end(); ++pi) { | |
537 | |
538 Plugin *plugin = pi->first; | |
539 Plugin::FeatureSet featureSet = plugin->process(data, timestamp); | |
540 | |
541 if (!m_summariesOnly) { | |
542 writeFeatures(audioSource, plugin, featureSet); | |
543 } | |
544 } | |
545 | |
546 int pp = progress; | |
547 progress = ((i - startFrame) * 100) / (endFrame - startFrame); | |
548 if (progress > pp) extractionProgress.setProgress(progress); | |
549 } | |
550 | |
551 for (PluginMap::iterator pi = m_plugins.begin(); | |
552 pi != m_plugins.end(); ++pi) { | |
553 | |
554 Plugin *plugin = pi->first; | |
555 Plugin::FeatureSet featureSet = plugin->getRemainingFeatures(); | |
556 | |
557 if (!m_summariesOnly) { | |
558 writeFeatures(audioSource, plugin, featureSet); | |
559 } | |
560 | |
561 if (!m_summaries.empty()) { | |
562 PluginSummarisingAdapter *adapter = | |
563 dynamic_cast<PluginSummarisingAdapter *>(plugin); | |
564 if (!adapter) { | |
565 cerr << "WARNING: Summaries requested, but plugin is not a summarising adapter" << endl; | |
566 } else { | |
567 for (SummaryNameSet::const_iterator sni = m_summaries.begin(); | |
568 sni != m_summaries.end(); ++sni) { | |
569 featureSet.clear(); | |
570 //!!! problem here -- we are requesting summaries | |
571 //!!! for all outputs, but they in principle have | |
572 //!!! different averaging requirements depending | |
573 //!!! on whether their features have duration or | |
574 //!!! not | |
575 featureSet = adapter->getSummaryForAllOutputs | |
576 (getSummaryType(*sni), | |
577 PluginSummarisingAdapter::ContinuousTimeAverage); | |
578 writeFeatures(audioSource, plugin, featureSet,//!!! *sni); | |
579 Transform::stringToSummaryType(sni->c_str())); | |
580 } | |
581 } | |
582 } | |
583 | |
584 writeSummaries(audioSource, plugin); | |
585 } | |
586 | |
587 finish(); | |
588 | |
589 extractionProgress.setProgress(100); | |
590 | |
591 TempDirectory::getInstance()->cleanup(); | |
592 } | |
593 | |
594 void | |
595 FeatureExtractionManager::writeSummaries(QString audioSource, Plugin *plugin) | |
596 { | |
597 // caller should have ensured plugin is in m_plugins | |
598 PluginMap::iterator pi = m_plugins.find(plugin); | |
599 | |
600 for (TransformWriterMap::const_iterator ti = pi->second.begin(); | |
601 ti != pi->second.end(); ++ti) { | |
602 | |
603 const Transform &transform = ti->first; | |
604 const vector<FeatureWriter *> &writers = ti->second; | |
605 | |
606 Transform::SummaryType summaryType = transform.getSummaryType(); | |
607 PluginSummarisingAdapter::SummaryType pType = | |
608 (PluginSummarisingAdapter::SummaryType)summaryType; | |
609 | |
610 if (transform.getSummaryType() == Transform::NoSummary) { | |
611 continue; | |
612 } | |
613 | |
614 PluginSummarisingAdapter *adapter = | |
615 dynamic_cast<PluginSummarisingAdapter *>(plugin); | |
616 if (!adapter) { | |
617 cerr << "FeatureExtractionManager::writeSummaries: INTERNAL ERROR: Summary requested for transform, but plugin is not a summarising adapter" << endl; | |
618 continue; | |
619 } | |
620 | |
621 Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs | |
622 (pType, PluginSummarisingAdapter::ContinuousTimeAverage); | |
623 | |
624 // cout << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl; | |
625 | |
626 writeFeatures(audioSource, plugin, featureSet, summaryType); | |
627 } | |
628 } | |
629 | |
630 void FeatureExtractionManager::writeFeatures(QString audioSource, | |
631 Plugin *plugin, | |
632 const Plugin::FeatureSet &features, | |
633 Transform::SummaryType summaryType) | |
634 { | |
635 // caller should have ensured plugin is in m_plugins | |
636 PluginMap::iterator pi = m_plugins.find(plugin); | |
637 | |
638 for (TransformWriterMap::const_iterator ti = pi->second.begin(); | |
639 ti != pi->second.end(); ++ti) { | |
640 | |
641 const Transform &transform = ti->first; | |
642 const vector<FeatureWriter *> &writers = ti->second; | |
643 | |
644 if (transform.getSummaryType() != Transform::NoSummary && | |
645 m_summaries.empty() && | |
646 summaryType == Transform::NoSummary) { | |
647 continue; | |
648 } | |
649 | |
650 if (transform.getSummaryType() != Transform::NoSummary && | |
651 summaryType != Transform::NoSummary && | |
652 transform.getSummaryType() != summaryType) { | |
653 continue; | |
654 } | |
655 | |
656 string outputId = transform.getOutput().toStdString(); | |
657 | |
658 if (m_pluginOutputs[plugin].find(outputId) == | |
659 m_pluginOutputs[plugin].end()) { | |
660 continue; | |
661 } | |
662 | |
663 const Plugin::OutputDescriptor &desc = | |
664 m_pluginOutputs[plugin][outputId]; | |
665 | |
666 int outputIndex = m_pluginOutputIndices[outputId]; | |
667 Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex); | |
668 if (fsi == features.end()) continue; | |
669 | |
670 for (int j = 0; j < (int)writers.size(); ++j) { | |
671 writers[j]->write | |
672 (audioSource, transform, desc, fsi->second, | |
673 Transform::summaryTypeToString(summaryType).toStdString()); | |
674 } | |
675 } | |
676 } | |
677 | |
678 void FeatureExtractionManager::finish() | |
679 { | |
680 for (PluginMap::iterator pi = m_plugins.begin(); | |
681 pi != m_plugins.end(); ++pi) { | |
682 | |
683 for (TransformWriterMap::iterator ti = pi->second.begin(); | |
684 ti != pi->second.end(); ++ti) { | |
685 | |
686 vector<FeatureWriter *> &writers = ti->second; | |
687 | |
688 for (int i = 0; i < (int)writers.size(); ++i) { | |
689 writers[i]->flush(); | |
690 writers[i]->finish(); | |
691 } | |
692 } | |
693 } | |
694 } | |
695 | |
696 void FeatureExtractionManager::print(Transform transform) const | |
697 { | |
698 QString qs; | |
699 QTextStream qts(&qs); | |
700 transform.toXml(qts); | |
701 cerr << qs.toStdString() << endl; | |
702 } |