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 }