annotate runner/FeatureExtractionManager.cpp @ 271:7b3a80021b7c piper-nopiper

Add MAD_BUFFER_GUARD padding at end of mp3 buffer, in order to ensure last frame is decoded successfully (otherwise the decoded audio is truncated). Another thing learned from madplay.
author Chris Cannam
date Thu, 24 Nov 2016 17:06:31 +0000
parents 0be716283c61
children 5ba121f3889e
rev   line source
Chris@0 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /*
Chris@0 4 Sonic Annotator
Chris@0 5 A utility for batch feature extraction from audio files.
Chris@0 6 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
Chris@0 7 Copyright 2007-2008 QMUL.
Chris@0 8
Chris@0 9 This program is free software; you can redistribute it and/or
Chris@0 10 modify it under the terms of the GNU General Public License as
Chris@0 11 published by the Free Software Foundation; either version 2 of the
Chris@0 12 License, or (at your option) any later version. See the file
Chris@0 13 COPYING included with this distribution for more information.
Chris@0 14 */
Chris@0 15
Chris@0 16 #include "FeatureExtractionManager.h"
Chris@106 17 #include "MultiplexedReader.h"
Chris@0 18
Chris@0 19 #include <vamp-hostsdk/PluginChannelAdapter.h>
Chris@0 20 #include <vamp-hostsdk/PluginBufferingAdapter.h>
Chris@0 21 #include <vamp-hostsdk/PluginInputDomainAdapter.h>
Chris@0 22 #include <vamp-hostsdk/PluginSummarisingAdapter.h>
Chris@8 23 #include <vamp-hostsdk/PluginWrapper.h>
Chris@0 24 #include <vamp-hostsdk/PluginLoader.h>
Chris@0 25
Chris@263 26 #include "base/Debug.h"
Chris@21 27 #include "base/Exceptions.h"
Chris@21 28
Chris@0 29 #include <iostream>
Chris@0 30
Chris@0 31 using namespace std;
Chris@0 32
Chris@0 33 using Vamp::Plugin;
Chris@0 34 using Vamp::PluginBase;
Chris@0 35 using Vamp::HostExt::PluginLoader;
Chris@0 36 using Vamp::HostExt::PluginChannelAdapter;
Chris@0 37 using Vamp::HostExt::PluginBufferingAdapter;
Chris@0 38 using Vamp::HostExt::PluginInputDomainAdapter;
Chris@0 39 using Vamp::HostExt::PluginSummarisingAdapter;
Chris@8 40 using Vamp::HostExt::PluginWrapper;
Chris@0 41
Chris@0 42 #include "data/fileio/FileSource.h"
Chris@0 43 #include "data/fileio/AudioFileReader.h"
Chris@0 44 #include "data/fileio/AudioFileReaderFactory.h"
Chris@0 45 #include "base/TempDirectory.h"
Chris@0 46 #include "base/ProgressPrinter.h"
Chris@0 47 #include "transform/TransformFactory.h"
Chris@0 48 #include "rdf/RDFTransformFactory.h"
Chris@0 49 #include "transform/FeatureWriter.h"
Chris@0 50
Chris@0 51 #include <QTextStream>
Chris@0 52 #include <QFile>
Chris@0 53 #include <QFileInfo>
Chris@0 54
Chris@263 55 FeatureExtractionManager::FeatureExtractionManager(bool verbose) :
Chris@263 56 m_verbose(verbose),
Chris@0 57 m_summariesOnly(false),
Chris@0 58 // We can read using an arbitrary fixed block size --
Chris@177 59 // PluginBufferingAdapter handles this for us. But while this
Chris@177 60 // doesn't affect the step and block size actually passed to the
Chris@177 61 // plugin, it does affect the overall time range of the audio
Chris@177 62 // input (which gets rounded to the nearest block boundary). So
Chris@177 63 // although a larger blocksize will normally run faster, and we
Chris@177 64 // used a blocksize of 16384 in earlier releases of Sonic
Chris@177 65 // Annotator for that reason, a smaller blocksize produces
Chris@177 66 // "better" results and this is particularly relevant now we
Chris@177 67 // support the start and duration flags for a transform.
Chris@177 68 m_blockSize(1024),
Chris@0 69 m_defaultSampleRate(0),
Chris@0 70 m_sampleRate(0),
Chris@116 71 m_channels(0),
Chris@116 72 m_normalise(false)
Chris@0 73 {
Chris@0 74 }
Chris@0 75
Chris@0 76 FeatureExtractionManager::~FeatureExtractionManager()
Chris@0 77 {
Chris@263 78 SVDEBUG << "FeatureExtractionManager::~FeatureExtractionManager: cleaning up"
Chris@263 79 << endl;
Chris@263 80
Chris@0 81 for (PluginMap::iterator pi = m_plugins.begin();
Chris@0 82 pi != m_plugins.end(); ++pi) {
Chris@0 83 delete pi->first;
Chris@0 84 }
Chris@45 85 foreach (AudioFileReader *r, m_readyReaders) {
Chris@45 86 delete r;
Chris@45 87 }
Chris@263 88
Chris@263 89 SVDEBUG << "FeatureExtractionManager::~FeatureExtractionManager: done" << endl;
Chris@0 90 }
Chris@0 91
Chris@0 92 void FeatureExtractionManager::setChannels(int channels)
Chris@0 93 {
Chris@0 94 m_channels = channels;
Chris@0 95 }
Chris@0 96
Chris@0 97 void FeatureExtractionManager::setDefaultSampleRate(int sampleRate)
Chris@0 98 {
Chris@0 99 m_defaultSampleRate = sampleRate;
Chris@0 100 }
Chris@0 101
Chris@116 102 void FeatureExtractionManager::setNormalise(bool normalise)
Chris@116 103 {
Chris@116 104 m_normalise = normalise;
Chris@116 105 }
Chris@116 106
Chris@0 107 static PluginSummarisingAdapter::SummaryType
Chris@0 108 getSummaryType(string name)
Chris@0 109 {
Chris@0 110 if (name == "min") return PluginSummarisingAdapter::Minimum;
Chris@0 111 if (name == "max") return PluginSummarisingAdapter::Maximum;
Chris@0 112 if (name == "mean") return PluginSummarisingAdapter::Mean;
Chris@0 113 if (name == "median") return PluginSummarisingAdapter::Median;
Chris@0 114 if (name == "mode") return PluginSummarisingAdapter::Mode;
Chris@0 115 if (name == "sum") return PluginSummarisingAdapter::Sum;
Chris@0 116 if (name == "variance") return PluginSummarisingAdapter::Variance;
Chris@0 117 if (name == "sd") return PluginSummarisingAdapter::StandardDeviation;
Chris@0 118 if (name == "count") return PluginSummarisingAdapter::Count;
Chris@0 119 return PluginSummarisingAdapter::UnknownSummaryType;
Chris@0 120 }
Chris@0 121
Chris@102 122 bool
Chris@102 123 FeatureExtractionManager::setSummaryTypes(const set<string> &names,
Chris@102 124 const PluginSummarisingAdapter::SegmentBoundaries &boundaries)
Chris@0 125 {
Chris@0 126 for (SummaryNameSet::const_iterator i = names.begin();
Chris@0 127 i != names.end(); ++i) {
Chris@0 128 if (getSummaryType(*i) == PluginSummarisingAdapter::UnknownSummaryType) {
Chris@263 129 SVCERR << "ERROR: Unknown summary type \"" << *i << "\"" << endl;
Chris@0 130 return false;
Chris@0 131 }
Chris@0 132 }
Chris@0 133 m_summaries = names;
Chris@0 134 m_boundaries = boundaries;
Chris@0 135 return true;
Chris@0 136 }
Chris@0 137
Chris@102 138 void
Chris@102 139 FeatureExtractionManager::setSummariesOnly(bool summariesOnly)
Chris@102 140 {
Chris@102 141 m_summariesOnly = summariesOnly;
Chris@102 142 }
Chris@102 143
Chris@51 144 static PluginInputDomainAdapter::WindowType
Chris@51 145 convertWindowType(WindowType t)
Chris@51 146 {
Chris@51 147 switch (t) {
Chris@51 148 case RectangularWindow:
Chris@51 149 return PluginInputDomainAdapter::RectangularWindow;
Chris@51 150 case BartlettWindow:
Chris@51 151 return PluginInputDomainAdapter::BartlettWindow;
Chris@51 152 case HammingWindow:
Chris@51 153 return PluginInputDomainAdapter::HammingWindow;
Chris@51 154 case HanningWindow:
Chris@51 155 return PluginInputDomainAdapter::HanningWindow;
Chris@51 156 case BlackmanWindow:
Chris@51 157 return PluginInputDomainAdapter::BlackmanWindow;
Chris@51 158 case NuttallWindow:
Chris@51 159 return PluginInputDomainAdapter::NuttallWindow;
Chris@51 160 case BlackmanHarrisWindow:
Chris@51 161 return PluginInputDomainAdapter::BlackmanHarrisWindow;
Chris@138 162 case GaussianWindow:
Chris@138 163 case ParzenWindow:
Chris@138 164 // Not supported in Vamp SDK, fall through
Chris@51 165 default:
Chris@263 166 SVCERR << "ERROR: Unknown or unsupported window type \"" << t << "\", using Hann (\"" << HanningWindow << "\")" << endl;
Chris@51 167 return PluginInputDomainAdapter::HanningWindow;
Chris@51 168 }
Chris@51 169 }
Chris@51 170
Chris@0 171 bool FeatureExtractionManager::addFeatureExtractor
Chris@0 172 (Transform transform, const vector<FeatureWriter*> &writers)
Chris@0 173 {
Chris@0 174 //!!! exceptions rather than return values?
Chris@0 175
Chris@0 176 if (transform.getSampleRate() == 0) {
Chris@0 177 if (m_sampleRate == 0) {
Chris@263 178 SVCERR << "NOTE: Transform does not specify a sample rate, using default rate of " << m_defaultSampleRate << endl;
Chris@0 179 transform.setSampleRate(m_defaultSampleRate);
Chris@0 180 m_sampleRate = m_defaultSampleRate;
Chris@0 181 } else {
Chris@263 182 SVCERR << "NOTE: Transform does not specify a sample rate, using previous transform's rate of " << m_sampleRate << endl;
Chris@0 183 transform.setSampleRate(m_sampleRate);
Chris@0 184 }
Chris@0 185 }
Chris@0 186
Chris@0 187 if (m_sampleRate == 0) {
Chris@0 188 m_sampleRate = transform.getSampleRate();
Chris@0 189 }
Chris@0 190
Chris@0 191 if (transform.getSampleRate() != m_sampleRate) {
Chris@263 192 SVCERR << "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;
Chris@263 193 SVCERR << "WARNING: Using previous rate of " << m_sampleRate << " for this transform as well" << endl;
Chris@0 194 transform.setSampleRate(m_sampleRate);
Chris@0 195 }
Chris@0 196
Chris@0 197 Plugin *plugin = 0;
Chris@0 198
Chris@0 199 // Remember what the original transform looked like, and index
Chris@0 200 // based on this -- because we may be about to fill in the zeros
Chris@0 201 // for step and block size, but we want any further copies with
Chris@0 202 // the same zeros to match this one
Chris@0 203 Transform originalTransform = transform;
Chris@0 204
Chris@0 205 if (m_transformPluginMap.find(transform) == m_transformPluginMap.end()) {
Chris@0 206
Chris@0 207 // Test whether we already have a transform that is identical
Chris@0 208 // to this, except for the output requested and/or the summary
Chris@0 209 // type -- if so, they should share plugin instances (a vital
Chris@0 210 // optimisation)
Chris@0 211
Chris@0 212 for (TransformPluginMap::iterator i = m_transformPluginMap.begin();
Chris@0 213 i != m_transformPluginMap.end(); ++i) {
Chris@0 214 Transform test = i->first;
Chris@0 215 test.setOutput(transform.getOutput());
Chris@0 216 test.setSummaryType(transform.getSummaryType());
Chris@0 217 if (transform == test) {
Chris@263 218 SVCERR << "NOTE: Already have transform identical to this one (for \""
Chris@0 219 << transform.getIdentifier().toStdString()
Chris@0 220 << "\") in every detail except output identifier and/or "
Chris@0 221 << "summary type; sharing its plugin instance" << endl;
Chris@0 222 plugin = i->second;
Chris@0 223 if (transform.getSummaryType() != Transform::NoSummary &&
Chris@0 224 !dynamic_cast<PluginSummarisingAdapter *>(plugin)) {
Chris@0 225 plugin = new PluginSummarisingAdapter(plugin);
Chris@0 226 i->second = plugin;
Chris@0 227 }
Chris@0 228 break;
Chris@0 229 }
Chris@0 230 }
Chris@0 231
Chris@0 232 if (!plugin) {
Chris@0 233
Chris@0 234 TransformFactory *tf = TransformFactory::getInstance();
Chris@0 235
Chris@0 236 PluginBase *pb = tf->instantiatePluginFor(transform);
Chris@0 237 plugin = tf->downcastVampPlugin(pb);
Chris@0 238 if (!plugin) {
Chris@0 239 //!!! todo: handle non-Vamp plugins too, or make the main --list
Chris@0 240 // option print out only Vamp transforms
Chris@263 241 SVCERR << "ERROR: Failed to load plugin for transform \""
Chris@0 242 << transform.getIdentifier().toStdString() << "\"" << endl;
Chris@203 243 if (pb) {
Chris@263 244 SVCERR << "NOTE: (A plugin was loaded, but apparently not a Vamp plugin)" << endl;
Chris@203 245 }
Chris@0 246 delete pb;
Chris@0 247 return false;
Chris@0 248 }
Chris@0 249
Chris@0 250 // We will provide the plugin with arbitrary step and
Chris@0 251 // block sizes (so that we can use the same read/write
Chris@0 252 // block size for all transforms), and to that end we use
Chris@0 253 // a PluginBufferingAdapter. However, we need to know the
Chris@0 254 // underlying step size so that we can provide the right
Chris@0 255 // context for dense outputs. (Although, don't forget
Chris@0 256 // that the PluginBufferingAdapter rewrites
Chris@0 257 // OneSamplePerStep outputs so as to use FixedSampleRate
Chris@0 258 // -- so it supplies the sample rate in the output
Chris@0 259 // feature. I'm not sure whether we can easily use that.)
Chris@0 260
Chris@0 261 size_t pluginStepSize = plugin->getPreferredStepSize();
Chris@0 262 size_t pluginBlockSize = plugin->getPreferredBlockSize();
Chris@0 263
Chris@25 264 PluginInputDomainAdapter *pida = 0;
Chris@25 265
Chris@0 266 // adapt the plugin for buffering, channels, etc.
Chris@0 267 if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
Chris@51 268
Chris@25 269 pida = new PluginInputDomainAdapter(plugin);
Chris@26 270 pida->setProcessTimestampMethod(PluginInputDomainAdapter::ShiftData);
Chris@51 271
Chris@51 272 PluginInputDomainAdapter::WindowType wtype =
Chris@51 273 convertWindowType(transform.getWindowType());
Chris@51 274 pida->setWindowType(wtype);
Chris@25 275 plugin = pida;
Chris@0 276 }
Chris@0 277
Chris@0 278 PluginBufferingAdapter *pba = new PluginBufferingAdapter(plugin);
Chris@0 279 plugin = pba;
Chris@0 280
Chris@0 281 if (transform.getStepSize() != 0) {
Chris@0 282 pba->setPluginStepSize(transform.getStepSize());
Chris@0 283 } else {
Chris@0 284 transform.setStepSize(pluginStepSize);
Chris@0 285 }
Chris@0 286
Chris@0 287 if (transform.getBlockSize() != 0) {
Chris@0 288 pba->setPluginBlockSize(transform.getBlockSize());
Chris@0 289 } else {
Chris@0 290 transform.setBlockSize(pluginBlockSize);
Chris@0 291 }
Chris@0 292
Chris@0 293 plugin = new PluginChannelAdapter(plugin);
Chris@0 294
Chris@0 295 if (!m_summaries.empty() ||
Chris@0 296 transform.getSummaryType() != Transform::NoSummary) {
Chris@0 297 PluginSummarisingAdapter *adapter =
Chris@0 298 new PluginSummarisingAdapter(plugin);
Chris@0 299 adapter->setSummarySegmentBoundaries(m_boundaries);
Chris@0 300 plugin = adapter;
Chris@0 301 }
Chris@0 302
Chris@0 303 if (!plugin->initialise(m_channels, m_blockSize, m_blockSize)) {
Chris@263 304 SVCERR << "ERROR: Plugin initialise (channels = " << m_channels << ", stepSize = " << m_blockSize << ", blockSize = " << m_blockSize << ") failed." << endl;
Chris@0 305 delete plugin;
Chris@0 306 return false;
Chris@0 307 }
Chris@0 308
Chris@263 309 SVDEBUG << "Initialised plugin" << endl;
Chris@0 310
Chris@0 311 size_t actualStepSize = 0;
Chris@0 312 size_t actualBlockSize = 0;
Chris@0 313 pba->getActualStepAndBlockSizes(actualStepSize, actualBlockSize);
Chris@0 314 transform.setStepSize(actualStepSize);
Chris@0 315 transform.setBlockSize(actualBlockSize);
Chris@0 316
Chris@0 317 Plugin::OutputList outputs = plugin->getOutputDescriptors();
Chris@0 318 for (int i = 0; i < (int)outputs.size(); ++i) {
Chris@0 319
Chris@263 320 SVDEBUG << "Newly initialised plugin output " << i << " has bin count " << outputs[i].binCount << endl;
Chris@0 321
Chris@0 322 m_pluginOutputs[plugin][outputs[i].identifier] = outputs[i];
Chris@0 323 m_pluginOutputIndices[outputs[i].identifier] = i;
Chris@0 324 }
Chris@0 325
Chris@263 326 SVCERR << "NOTE: Loaded and initialised plugin for transform \""
Chris@25 327 << transform.getIdentifier().toStdString()
Chris@25 328 << "\" with plugin step size " << actualStepSize
Chris@25 329 << " and block size " << actualBlockSize
Chris@25 330 << " (adapter step and block size " << m_blockSize << ")"
Chris@25 331 << endl;
Chris@25 332
Chris@263 333 SVDEBUG << "NOTE: That transform is: " << transform.toXmlString() << endl;
Chris@233 334
Chris@25 335 if (pida) {
Chris@263 336 SVCERR << "NOTE: PluginInputDomainAdapter timestamp adjustment is "
Chris@25 337 << pida->getTimestampAdjustment() << endl;
Chris@25 338 }
Chris@8 339
Chris@8 340 } else {
Chris@8 341
Chris@8 342 if (transform.getStepSize() == 0 || transform.getBlockSize() == 0) {
Chris@8 343
Chris@8 344 PluginWrapper *pw = dynamic_cast<PluginWrapper *>(plugin);
Chris@8 345 if (pw) {
Chris@8 346 PluginBufferingAdapter *pba =
Chris@8 347 pw->getWrapper<PluginBufferingAdapter>();
Chris@8 348 if (pba) {
Chris@8 349 size_t actualStepSize = 0;
Chris@8 350 size_t actualBlockSize = 0;
Chris@8 351 pba->getActualStepAndBlockSizes(actualStepSize,
Chris@8 352 actualBlockSize);
Chris@8 353 if (transform.getStepSize() == 0) {
Chris@8 354 transform.setStepSize(actualStepSize);
Chris@8 355 }
Chris@8 356 if (transform.getBlockSize() == 0) {
Chris@8 357 transform.setBlockSize(actualBlockSize);
Chris@8 358 }
Chris@8 359 }
Chris@8 360 }
Chris@8 361 }
Chris@0 362 }
Chris@0 363
Chris@130 364 if (transform.getPluginVersion() != "") {
Chris@130 365 if (QString("%1").arg(plugin->getPluginVersion())
Chris@130 366 != transform.getPluginVersion()) {
Chris@263 367 SVCERR << "ERROR: Transform specifies version "
Chris@130 368 << transform.getPluginVersion()
Chris@130 369 << " of plugin \"" << plugin->getIdentifier()
Chris@130 370 << "\", but installed plugin is version "
Chris@130 371 << plugin->getPluginVersion()
Chris@130 372 << endl;
Chris@130 373 return false;
Chris@130 374 }
Chris@130 375 }
Chris@130 376
Chris@0 377 if (transform.getOutput() == "") {
Chris@0 378 transform.setOutput
Chris@0 379 (plugin->getOutputDescriptors()[0].identifier.c_str());
Chris@129 380 } else {
Chris@129 381 if (m_pluginOutputs[plugin].find
Chris@129 382 (transform.getOutput().toLocal8Bit().data()) ==
Chris@129 383 m_pluginOutputs[plugin].end()) {
Chris@263 384 SVCERR << "ERROR: Transform requests nonexistent plugin output \""
Chris@129 385 << transform.getOutput()
Chris@129 386 << "\"" << endl;
Chris@129 387 return false;
Chris@129 388 }
Chris@0 389 }
Chris@0 390
Chris@0 391 m_transformPluginMap[transform] = plugin;
Chris@0 392
Chris@263 393 SVDEBUG << "NOTE: Assigned plugin " << plugin << " for transform: " << transform.toXmlString() << endl;
Chris@233 394
Chris@0 395 if (!(originalTransform == transform)) {
Chris@0 396 m_transformPluginMap[originalTransform] = plugin;
Chris@263 397 SVDEBUG << "NOTE: Also assigned plugin " << plugin << " for original transform: " << originalTransform.toXmlString() << endl;
Chris@0 398 }
Chris@0 399
Chris@0 400 } else {
Chris@0 401
Chris@0 402 plugin = m_transformPluginMap[transform];
Chris@0 403 }
Chris@0 404
Chris@109 405 if (m_plugins.find(plugin) == m_plugins.end()) {
Chris@109 406 m_orderedPlugins.push_back(plugin);
Chris@109 407 }
Chris@109 408
Chris@0 409 m_plugins[plugin][transform] = writers;
Chris@0 410
Chris@0 411 return true;
Chris@0 412 }
Chris@0 413
Chris@0 414 bool FeatureExtractionManager::addDefaultFeatureExtractor
Chris@0 415 (TransformId transformId, const vector<FeatureWriter*> &writers)
Chris@0 416 {
Chris@0 417 TransformFactory *tf = TransformFactory::getInstance();
Chris@0 418
Chris@0 419 if (m_sampleRate == 0) {
Chris@0 420 if (m_defaultSampleRate == 0) {
Chris@263 421 SVCERR << "ERROR: Default transform requested, but no default sample rate available" << endl;
Chris@0 422 return false;
Chris@0 423 } else {
Chris@263 424 SVCERR << "NOTE: Using default sample rate of " << m_defaultSampleRate << " for default transform" << endl;
Chris@0 425 m_sampleRate = m_defaultSampleRate;
Chris@0 426 }
Chris@0 427 }
Chris@0 428
Chris@0 429 Transform transform = tf->getDefaultTransformFor(transformId, m_sampleRate);
Chris@0 430
Chris@203 431 bool result = addFeatureExtractor(transform, writers);
Chris@203 432 if (!result) {
Chris@203 433 if (transform.getType() == Transform::UnknownType) {
Chris@263 434 SVCERR << "(Maybe mixed up filename with transform, or --transform with --default?)" << endl;
Chris@203 435 }
Chris@203 436 }
Chris@203 437 return result;
Chris@0 438 }
Chris@0 439
Chris@0 440 bool FeatureExtractionManager::addFeatureExtractorFromFile
Chris@227 441 (QString transformFile, const vector<FeatureWriter*> &writers)
Chris@0 442 {
Chris@227 443 // We support two formats for transform description files, XML (in
Chris@227 444 // a format specific to Sonic Annotator) and RDF/Turtle. The RDF
Chris@227 445 // format can describe multiple transforms in a single file, the
Chris@227 446 // XML only one.
Chris@227 447
Chris@227 448 // Possible errors we should report:
Chris@227 449 //
Chris@227 450 // 1. File does not exist or cannot be opened
Chris@227 451 // 2. File is ostensibly XML, but is not parseable
Chris@227 452 // 3. File is ostensibly Turtle, but is not parseable
Chris@227 453 // 4. File is XML, but contains no valid transform (e.g. is unrelated XML)
Chris@227 454 // 5. File is Turtle, but contains no valid transform(s)
Chris@227 455 // 6. File is Turtle and contains both valid and invalid transform(s)
Chris@227 456
Chris@227 457 {
Chris@227 458 // We don't actually need to open this here yet, we just hoist
Chris@227 459 // it to the top for error reporting purposes
Chris@227 460 QFile file(transformFile);
Chris@227 461 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
Chris@227 462 // Error case 1. File does not exist or cannot be opened
Chris@263 463 SVCERR << "ERROR: Failed to open transform file \"" << transformFile
Chris@227 464 << "\" for reading" << endl;
Chris@227 465 return false;
Chris@227 466 }
Chris@227 467 }
Chris@227 468
Chris@99 469 bool tryRdf = true;
Chris@227 470 if (transformFile.endsWith(".xml") || transformFile.endsWith(".XML")) {
Chris@99 471 // We don't support RDF-XML (and nor does the underlying
Chris@99 472 // parser library) so skip the RDF parse if the filename
Chris@99 473 // suggests XML, to avoid puking out a load of errors from
Chris@99 474 // feeding XML to a Turtle parser
Chris@99 475 tryRdf = false;
Chris@0 476 }
Chris@99 477
Chris@227 478 bool tryXml = true;
Chris@227 479 if (transformFile.endsWith(".ttl") || transformFile.endsWith(".TTL") ||
Chris@227 480 transformFile.endsWith(".ntriples") || transformFile.endsWith(".NTRIPLES") ||
Chris@227 481 transformFile.endsWith(".n3") || transformFile.endsWith(".N3")) {
Chris@227 482 tryXml = false;
Chris@227 483 }
Chris@227 484
Chris@227 485 QString rdfError, xmlError;
Chris@227 486
Chris@99 487 if (tryRdf) {
Chris@227 488
Chris@99 489 RDFTransformFactory factory
Chris@227 490 (QUrl::fromLocalFile(QFileInfo(transformFile).absoluteFilePath())
Chris@99 491 .toString());
Chris@99 492 ProgressPrinter printer("Parsing transforms RDF file");
Chris@263 493 std::vector<Transform> transforms = factory.getTransforms
Chris@263 494 (m_verbose ? &printer : 0);
Chris@227 495
Chris@227 496 if (factory.isOK()) {
Chris@227 497 if (transforms.empty()) {
Chris@263 498 SVCERR << "ERROR: Transform file \"" << transformFile
Chris@227 499 << "\" is valid RDF but defines no transforms" << endl;
Chris@227 500 return false;
Chris@227 501 } else {
Chris@227 502 bool success = true;
Chris@227 503 for (int i = 0; i < (int)transforms.size(); ++i) {
Chris@227 504 if (!addFeatureExtractor(transforms[i], writers)) {
Chris@227 505 success = false;
Chris@227 506 }
Chris@227 507 }
Chris@227 508 return success;
Chris@227 509 }
Chris@227 510 } else { // !factory.isOK()
Chris@99 511 if (factory.isRDF()) {
Chris@263 512 SVCERR << "ERROR: Invalid transform RDF file \"" << transformFile
Chris@227 513 << "\": " << factory.getErrorString() << endl;
Chris@227 514 return false;
Chris@0 515 }
Chris@227 516
Chris@227 517 // the not-RDF case: fall through without reporting an
Chris@227 518 // error, so we try the file as XML, and if that fails, we
Chris@227 519 // print a general unparseable-file error
Chris@227 520 rdfError = factory.getErrorString();
Chris@99 521 }
Chris@0 522 }
Chris@0 523
Chris@227 524 if (tryXml) {
Chris@227 525
Chris@227 526 QFile file(transformFile);
Chris@227 527 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
Chris@263 528 SVCERR << "ERROR: Failed to open transform file \""
Chris@227 529 << transformFile.toStdString() << "\" for reading" << endl;
Chris@227 530 return false;
Chris@227 531 }
Chris@227 532
Chris@227 533 QTextStream *qts = new QTextStream(&file);
Chris@227 534 QString qs = qts->readAll();
Chris@227 535 delete qts;
Chris@227 536 file.close();
Chris@227 537
Chris@227 538 Transform transform(qs);
Chris@227 539 xmlError = transform.getErrorString();
Chris@227 540
Chris@227 541 if (xmlError == "") {
Chris@227 542
Chris@227 543 if (transform.getIdentifier() == "") {
Chris@263 544 SVCERR << "ERROR: Transform file \"" << transformFile
Chris@227 545 << "\" is valid XML but defines no transform" << endl;
Chris@227 546 return false;
Chris@227 547 }
Chris@227 548
Chris@227 549 return addFeatureExtractor(transform, writers);
Chris@227 550 }
Chris@0 551 }
Chris@0 552
Chris@263 553 SVCERR << "ERROR: Transform file \"" << transformFile
Chris@227 554 << "\" could not be parsed" << endl;
Chris@227 555 if (rdfError != "") {
Chris@263 556 SVCERR << "ERROR: RDF parser reported: " << rdfError << endl;
Chris@227 557 }
Chris@227 558 if (xmlError != "") {
Chris@263 559 SVCERR << "ERROR: XML parser reported: " << xmlError << endl;
Chris@227 560 }
Chris@0 561
Chris@227 562 return false;
Chris@0 563 }
Chris@0 564
Chris@106 565 void FeatureExtractionManager::addSource(QString audioSource, bool willMultiplex)
Chris@0 566 {
Chris@263 567 SVCERR << "Have audio source: \"" << audioSource.toStdString() << "\"" << endl;
Chris@45 568
Chris@45 569 // We don't actually do anything with it here, unless it's the
Chris@45 570 // first audio source and we need it to establish default channel
Chris@45 571 // count and sample rate
Chris@45 572
Chris@45 573 if (m_channels == 0 || m_defaultSampleRate == 0) {
Chris@45 574
Chris@227 575 ProgressPrinter retrievalProgress("Retrieving first input file to determine default rate and channel count...");
Chris@45 576
Chris@263 577 FileSource source(audioSource, m_verbose ? &retrievalProgress : 0);
Chris@45 578 if (!source.isAvailable()) {
Chris@263 579 SVCERR << "ERROR: File or URL \"" << audioSource.toStdString()
Chris@117 580 << "\" could not be located";
Chris@117 581 if (source.getErrorString() != "") {
Chris@263 582 SVCERR << ": " << source.getErrorString();
Chris@117 583 }
Chris@263 584 SVCERR << endl;
Chris@45 585 throw FileNotFound(audioSource);
Chris@45 586 }
Chris@45 587
Chris@45 588 source.waitForData();
Chris@45 589
Chris@45 590 // Open to determine validity, channel count, sample rate only
Chris@45 591 // (then close, and open again later with actual desired rate &c)
Chris@45 592
Chris@45 593 AudioFileReader *reader =
Chris@263 594 AudioFileReaderFactory::createReader
Chris@263 595 (source, 0, m_normalise, m_verbose ? &retrievalProgress : 0);
Chris@45 596
Chris@45 597 if (!reader) {
Chris@45 598 throw FailedToOpenFile(audioSource);
Chris@45 599 }
Chris@45 600
Chris@263 601 if (m_verbose) retrievalProgress.done();
Chris@45 602
Chris@263 603 SVCERR << "File or URL \"" << audioSource.toStdString() << "\" opened successfully" << endl;
Chris@45 604
Chris@113 605 if (!willMultiplex) {
Chris@106 606 if (m_channels == 0) {
Chris@106 607 m_channels = reader->getChannelCount();
Chris@263 608 SVCERR << "Taking default channel count of "
Chris@227 609 << reader->getChannelCount() << " from audio file" << endl;
Chris@106 610 }
Chris@45 611 }
Chris@45 612
Chris@45 613 if (m_defaultSampleRate == 0) {
Chris@45 614 m_defaultSampleRate = reader->getNativeRate();
Chris@263 615 SVCERR << "Taking default sample rate of "
Chris@227 616 << reader->getNativeRate() << "Hz from audio file" << endl;
Chris@263 617 SVCERR << "(Note: Default may be overridden by transforms)" << endl;
Chris@45 618 }
Chris@45 619
Chris@45 620 m_readyReaders[audioSource] = reader;
Chris@45 621 }
Chris@113 622
Chris@113 623 if (willMultiplex) {
Chris@113 624 ++m_channels; // channel count is simply number of sources
Chris@263 625 SVCERR << "Multiplexing, incremented target channel count to "
Chris@113 626 << m_channels << endl;
Chris@113 627 }
Chris@45 628 }
Chris@45 629
Chris@112 630 void FeatureExtractionManager::extractFeatures(QString audioSource)
Chris@45 631 {
Chris@45 632 if (m_plugins.empty()) return;
Chris@45 633
Chris@45 634 testOutputFiles(audioSource);
Chris@45 635
Chris@0 636 if (m_sampleRate == 0) {
Chris@45 637 throw FileOperationFailed
Chris@45 638 (audioSource, "internal error: have sources and plugins, but no sample rate");
Chris@45 639 }
Chris@45 640 if (m_channels == 0) {
Chris@45 641 throw FileOperationFailed
Chris@45 642 (audioSource, "internal error: have sources and plugins, but no channel count");
Chris@0 643 }
Chris@0 644
Chris@106 645 AudioFileReader *reader = prepareReader(audioSource);
Chris@106 646 extractFeaturesFor(reader, audioSource); // Note this also deletes reader
Chris@106 647 }
Chris@102 648
Chris@106 649 void FeatureExtractionManager::extractFeaturesMultiplexed(QStringList sources)
Chris@106 650 {
Chris@106 651 if (m_plugins.empty() || sources.empty()) return;
Chris@106 652
Chris@106 653 QString nominalSource = sources[0];
Chris@106 654
Chris@106 655 testOutputFiles(nominalSource);
Chris@106 656
Chris@106 657 if (m_sampleRate == 0) {
Chris@106 658 throw FileOperationFailed
Chris@106 659 (nominalSource, "internal error: have sources and plugins, but no sample rate");
Chris@106 660 }
Chris@106 661 if (m_channels == 0) {
Chris@106 662 throw FileOperationFailed
Chris@106 663 (nominalSource, "internal error: have sources and plugins, but no channel count");
Chris@106 664 }
Chris@106 665
Chris@106 666 QList<AudioFileReader *> readers;
Chris@106 667 foreach (QString source, sources) {
Chris@106 668 AudioFileReader *reader = prepareReader(source);
Chris@106 669 readers.push_back(reader);
Chris@106 670 }
Chris@106 671
Chris@106 672 AudioFileReader *reader = new MultiplexedReader(readers);
Chris@106 673 extractFeaturesFor(reader, nominalSource); // Note this also deletes reader
Chris@106 674 }
Chris@106 675
Chris@106 676 AudioFileReader *
Chris@106 677 FeatureExtractionManager::prepareReader(QString source)
Chris@106 678 {
Chris@45 679 AudioFileReader *reader = 0;
Chris@106 680 if (m_readyReaders.contains(source)) {
Chris@106 681 reader = m_readyReaders[source];
Chris@106 682 m_readyReaders.remove(source);
Chris@106 683 if (reader->getSampleRate() != m_sampleRate) {
Chris@45 684 // can't use this; open it again
Chris@45 685 delete reader;
Chris@45 686 reader = 0;
Chris@45 687 }
Chris@45 688 }
Chris@45 689 if (!reader) {
Chris@45 690 ProgressPrinter retrievalProgress("Retrieving audio data...");
Chris@263 691 FileSource fs(source, m_verbose ? &retrievalProgress : 0);
Chris@106 692 fs.waitForData();
Chris@263 693 reader = AudioFileReaderFactory::createReader
Chris@263 694 (fs, m_sampleRate, m_normalise, m_verbose ? &retrievalProgress : 0);
Chris@263 695 if (m_verbose) retrievalProgress.done();
Chris@45 696 }
Chris@102 697 if (!reader) {
Chris@107 698 throw FailedToOpenFile(source);
Chris@102 699 }
Chris@219 700 if (reader->getChannelCount() != m_channels ||
Chris@219 701 reader->getNativeRate() != m_sampleRate) {
Chris@263 702 SVCERR << "NOTE: File will be mixed or resampled for processing, to: "
Chris@219 703 << m_channels << "ch at "
Chris@219 704 << m_sampleRate << "Hz" << endl;
Chris@219 705 }
Chris@106 706 return reader;
Chris@106 707 }
Chris@45 708
Chris@106 709 void
Chris@106 710 FeatureExtractionManager::extractFeaturesFor(AudioFileReader *reader,
Chris@106 711 QString audioSource)
Chris@106 712 {
Chris@106 713 // Note: This also deletes reader
Chris@0 714
Chris@263 715 SVCERR << "Audio file \"" << audioSource.toStdString() << "\": "
Chris@45 716 << reader->getChannelCount() << "ch at "
Chris@45 717 << reader->getNativeRate() << "Hz" << endl;
Chris@11 718
Chris@0 719 // allocate audio buffers
Chris@0 720 float **data = new float *[m_channels];
Chris@0 721 for (int c = 0; c < m_channels; ++c) {
Chris@0 722 data[c] = new float[m_blockSize];
Chris@0 723 }
Chris@31 724
Chris@31 725 struct LifespanMgr { // unintrusive hack introduced to ensure
Chris@31 726 // destruction on exceptions
Chris@31 727 AudioFileReader *m_r;
Chris@31 728 int m_c;
Chris@31 729 float **m_d;
Chris@31 730 LifespanMgr(AudioFileReader *r, int c, float **d) :
Chris@31 731 m_r(r), m_c(c), m_d(d) { }
Chris@31 732 ~LifespanMgr() { destroy(); }
Chris@31 733 void destroy() {
Chris@31 734 if (!m_r) return;
Chris@31 735 delete m_r;
Chris@31 736 for (int i = 0; i < m_c; ++i) delete[] m_d[i];
Chris@31 737 delete[] m_d;
Chris@31 738 m_r = 0;
Chris@31 739 }
Chris@31 740 };
Chris@31 741 LifespanMgr lifemgr(reader, m_channels, data);
Chris@0 742
Chris@0 743 size_t frameCount = reader->getFrameCount();
Chris@0 744
Chris@263 745 SVDEBUG << "FeatureExtractionManager: file has " << frameCount << " frames" << endl;
Chris@0 746
Chris@99 747 int earliestStartFrame = 0;
Chris@99 748 int latestEndFrame = frameCount;
Chris@99 749 bool haveExtents = false;
Chris@99 750
Chris@109 751 foreach (Plugin *plugin, m_orderedPlugins) {
Chris@0 752
Chris@109 753 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 754
Chris@263 755 SVDEBUG << "FeatureExtractionManager: Calling reset on " << plugin << endl;
Chris@0 756 plugin->reset();
Chris@0 757
Chris@0 758 for (TransformWriterMap::iterator ti = pi->second.begin();
Chris@0 759 ti != pi->second.end(); ++ti) {
Chris@0 760
Chris@0 761 const Transform &transform = ti->first;
Chris@0 762
Chris@99 763 int startFrame = RealTime::realTime2Frame
Chris@99 764 (transform.getStartTime(), m_sampleRate);
Chris@99 765 int duration = RealTime::realTime2Frame
Chris@99 766 (transform.getDuration(), m_sampleRate);
Chris@99 767 if (duration == 0) {
Chris@99 768 duration = frameCount - startFrame;
Chris@99 769 }
Chris@99 770
Chris@99 771 if (!haveExtents || startFrame < earliestStartFrame) {
Chris@99 772 earliestStartFrame = startFrame;
Chris@99 773 }
Chris@99 774 if (!haveExtents || startFrame + duration > latestEndFrame) {
Chris@99 775 latestEndFrame = startFrame + duration;
Chris@99 776 }
Chris@263 777
Chris@131 778 /*
Chris@263 779 SVDEBUG << "startFrame for transform " << startFrame << endl;
Chris@263 780 SVDEBUG << "duration for transform " << duration << endl;
Chris@263 781 SVDEBUG << "earliestStartFrame becomes " << earliestStartFrame << endl;
Chris@263 782 SVDEBUG << "latestEndFrame becomes " << latestEndFrame << endl;
Chris@131 783 */
Chris@99 784 haveExtents = true;
Chris@0 785
Chris@0 786 string outputId = transform.getOutput().toStdString();
Chris@0 787 if (m_pluginOutputs[plugin].find(outputId) ==
Chris@0 788 m_pluginOutputs[plugin].end()) {
Chris@129 789 // We shouldn't actually reach this point:
Chris@129 790 // addFeatureExtractor tests whether the output exists
Chris@263 791 SVCERR << "ERROR: Nonexistent plugin output \"" << outputId << "\" requested for transform \""
Chris@0 792 << transform.getIdentifier().toStdString() << "\", ignoring this transform"
Chris@0 793 << endl;
Chris@263 794
Chris@263 795 SVDEBUG << "Known outputs for all plugins are as follows:" << endl;
Chris@0 796 for (PluginOutputMap::const_iterator k = m_pluginOutputs.begin();
Chris@0 797 k != m_pluginOutputs.end(); ++k) {
Chris@263 798 SVDEBUG << "Plugin " << k->first << ": ";
Chris@0 799 if (k->second.empty()) {
Chris@263 800 SVDEBUG << "(none)";
Chris@0 801 }
Chris@0 802 for (OutputMap::const_iterator i = k->second.begin();
Chris@0 803 i != k->second.end(); ++i) {
Chris@263 804 SVDEBUG << "\"" << i->first << "\" ";
Chris@0 805 }
Chris@263 806 SVDEBUG << endl;
Chris@0 807 }
Chris@0 808 }
Chris@0 809 }
Chris@0 810 }
Chris@0 811
Chris@99 812 int startFrame = earliestStartFrame;
Chris@99 813 int endFrame = latestEndFrame;
Chris@0 814
Chris@109 815 foreach (Plugin *plugin, m_orderedPlugins) {
Chris@109 816
Chris@109 817 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 818
Chris@0 819 for (TransformWriterMap::const_iterator ti = pi->second.begin();
Chris@0 820 ti != pi->second.end(); ++ti) {
Chris@0 821
Chris@0 822 const vector<FeatureWriter *> &writers = ti->second;
Chris@0 823
Chris@0 824 for (int j = 0; j < (int)writers.size(); ++j) {
Chris@0 825 FeatureWriter::TrackMetadata m;
Chris@0 826 m.title = reader->getTitle();
Chris@0 827 m.maker = reader->getMaker();
Chris@208 828 m.duration = RealTime::frame2RealTime(reader->getFrameCount(),
Chris@208 829 reader->getSampleRate());
Chris@208 830 writers[j]->setTrackMetadata(audioSource, m);
Chris@0 831 }
Chris@0 832 }
Chris@0 833 }
Chris@0 834
Chris@0 835 ProgressPrinter extractionProgress("Extracting and writing features...");
Chris@0 836 int progress = 0;
Chris@0 837
Chris@99 838 for (int i = startFrame; i < endFrame; i += m_blockSize) {
Chris@0 839
Chris@0 840 //!!! inefficient, although much of the inefficiency may be
Chris@99 841 // susceptible to compiler optimisation
Chris@0 842
Chris@258 843 auto frames = reader->getInterleavedFrames(i, m_blockSize);
Chris@0 844
Chris@0 845 // We have to do our own channel handling here; we can't just
Chris@0 846 // leave it to the plugin adapter because the same plugin
Chris@0 847 // adapter may have to serve for input files with various
Chris@0 848 // numbers of channels (so the adapter is simply configured
Chris@34 849 // with a fixed channel count).
Chris@0 850
Chris@0 851 int rc = reader->getChannelCount();
Chris@0 852
Chris@34 853 // m_channels is the number of channels we need for the plugin
Chris@34 854
Chris@34 855 int index;
Chris@34 856 int fc = (int)frames.size();
Chris@46 857
Chris@34 858 if (m_channels == 1) { // only case in which we can sensibly mix down
Chris@34 859 for (int j = 0; j < m_blockSize; ++j) {
Chris@34 860 data[0][j] = 0.f;
Chris@34 861 }
Chris@34 862 for (int c = 0; c < rc; ++c) {
Chris@34 863 for (int j = 0; j < m_blockSize; ++j) {
Chris@0 864 index = j * rc + c;
Chris@34 865 if (index < fc) data[0][j] += frames[index];
Chris@0 866 }
Chris@0 867 }
Chris@34 868 for (int j = 0; j < m_blockSize; ++j) {
Chris@34 869 data[0][j] /= rc;
Chris@34 870 }
Chris@34 871 } else {
Chris@34 872 for (int c = 0; c < m_channels; ++c) {
Chris@34 873 for (int j = 0; j < m_blockSize; ++j) {
Chris@34 874 data[c][j] = 0.f;
Chris@34 875 }
Chris@34 876 if (c < rc) {
Chris@34 877 for (int j = 0; j < m_blockSize; ++j) {
Chris@34 878 index = j * rc + c;
Chris@34 879 if (index < fc) data[c][j] += frames[index];
Chris@34 880 }
Chris@34 881 }
Chris@34 882 }
Chris@34 883 }
Chris@0 884
Chris@0 885 Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime
Chris@0 886 (i, m_sampleRate);
Chris@0 887
Chris@109 888 foreach (Plugin *plugin, m_orderedPlugins) {
Chris@0 889
Chris@109 890 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@123 891
Chris@123 892 // Skip any plugin none of whose transforms have come
Chris@123 893 // around yet. (Though actually, all transforms for a
Chris@123 894 // given plugin must have the same start time -- they can
Chris@123 895 // only differ in output and summary type.)
Chris@123 896 bool inRange = false;
Chris@123 897 for (TransformWriterMap::const_iterator ti = pi->second.begin();
Chris@123 898 ti != pi->second.end(); ++ti) {
Chris@123 899 int startFrame = RealTime::realTime2Frame
Chris@123 900 (ti->first.getStartTime(), m_sampleRate);
Chris@123 901 if (i >= startFrame || i + m_blockSize > startFrame) {
Chris@123 902 inRange = true;
Chris@123 903 break;
Chris@123 904 }
Chris@123 905 }
Chris@123 906 if (!inRange) {
Chris@123 907 continue;
Chris@123 908 }
Chris@123 909
Chris@0 910 Plugin::FeatureSet featureSet = plugin->process(data, timestamp);
Chris@0 911
Chris@0 912 if (!m_summariesOnly) {
Chris@0 913 writeFeatures(audioSource, plugin, featureSet);
Chris@0 914 }
Chris@0 915 }
Chris@0 916
Chris@0 917 int pp = progress;
Chris@6 918 progress = int(((i - startFrame) * 100.0) / (endFrame - startFrame) + 0.1);
Chris@263 919 if (progress > pp && m_verbose) extractionProgress.setProgress(progress);
Chris@0 920 }
Chris@10 921
Chris@263 922 SVDEBUG << "FeatureExtractionManager: deleting audio file reader" << endl;
Chris@12 923
Chris@31 924 lifemgr.destroy(); // deletes reader, data
Chris@57 925
Chris@109 926 foreach (Plugin *plugin, m_orderedPlugins) {
Chris@57 927
Chris@109 928 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 929 Plugin::FeatureSet featureSet = plugin->getRemainingFeatures();
Chris@0 930
Chris@0 931 if (!m_summariesOnly) {
Chris@0 932 writeFeatures(audioSource, plugin, featureSet);
Chris@0 933 }
Chris@0 934
Chris@0 935 if (!m_summaries.empty()) {
Chris@233 936 // Summaries requested on the command line, for all transforms
Chris@0 937 PluginSummarisingAdapter *adapter =
Chris@0 938 dynamic_cast<PluginSummarisingAdapter *>(plugin);
Chris@0 939 if (!adapter) {
Chris@263 940 SVCERR << "WARNING: Summaries requested, but plugin is not a summarising adapter" << endl;
Chris@0 941 } else {
Chris@0 942 for (SummaryNameSet::const_iterator sni = m_summaries.begin();
Chris@0 943 sni != m_summaries.end(); ++sni) {
Chris@0 944 featureSet.clear();
Chris@0 945 //!!! problem here -- we are requesting summaries
Chris@0 946 //!!! for all outputs, but they in principle have
Chris@0 947 //!!! different averaging requirements depending
Chris@0 948 //!!! on whether their features have duration or
Chris@0 949 //!!! not
Chris@0 950 featureSet = adapter->getSummaryForAllOutputs
Chris@0 951 (getSummaryType(*sni),
Chris@0 952 PluginSummarisingAdapter::ContinuousTimeAverage);
Chris@233 953 writeFeatures(audioSource, plugin, featureSet,
Chris@0 954 Transform::stringToSummaryType(sni->c_str()));
Chris@0 955 }
Chris@0 956 }
Chris@0 957 }
Chris@0 958
Chris@233 959 // Summaries specified in transform definitions themselves
Chris@0 960 writeSummaries(audioSource, plugin);
Chris@0 961 }
Chris@0 962
Chris@263 963 if (m_verbose) extractionProgress.done();
Chris@3 964
Chris@0 965 finish();
Chris@0 966
Chris@0 967 TempDirectory::getInstance()->cleanup();
Chris@0 968 }
Chris@0 969
Chris@0 970 void
Chris@0 971 FeatureExtractionManager::writeSummaries(QString audioSource, Plugin *plugin)
Chris@0 972 {
Chris@0 973 // caller should have ensured plugin is in m_plugins
Chris@0 974 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 975
Chris@0 976 for (TransformWriterMap::const_iterator ti = pi->second.begin();
Chris@0 977 ti != pi->second.end(); ++ti) {
Chris@0 978
Chris@0 979 const Transform &transform = ti->first;
Chris@0 980
Chris@263 981 SVDEBUG << "FeatureExtractionManager::writeSummaries: plugin is " << plugin
Chris@263 982 << ", found transform: " << transform.toXmlString() << endl;
Chris@233 983
Chris@0 984 Transform::SummaryType summaryType = transform.getSummaryType();
Chris@0 985 PluginSummarisingAdapter::SummaryType pType =
Chris@0 986 (PluginSummarisingAdapter::SummaryType)summaryType;
Chris@0 987
Chris@0 988 if (transform.getSummaryType() == Transform::NoSummary) {
Chris@263 989 SVDEBUG << "FeatureExtractionManager::writeSummaries: no summary for this transform" << endl;
Chris@0 990 continue;
Chris@0 991 }
Chris@0 992
Chris@0 993 PluginSummarisingAdapter *adapter =
Chris@0 994 dynamic_cast<PluginSummarisingAdapter *>(plugin);
Chris@0 995 if (!adapter) {
Chris@263 996 SVCERR << "FeatureExtractionManager::writeSummaries: INTERNAL ERROR: Summary requested for transform, but plugin is not a summarising adapter" << endl;
Chris@0 997 continue;
Chris@0 998 }
Chris@0 999
Chris@0 1000 Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs
Chris@0 1001 (pType, PluginSummarisingAdapter::ContinuousTimeAverage);
Chris@0 1002
Chris@263 1003 SVDEBUG << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl;
Chris@0 1004
Chris@0 1005 writeFeatures(audioSource, plugin, featureSet, summaryType);
Chris@0 1006 }
Chris@0 1007 }
Chris@0 1008
Chris@0 1009 void FeatureExtractionManager::writeFeatures(QString audioSource,
Chris@0 1010 Plugin *plugin,
Chris@0 1011 const Plugin::FeatureSet &features,
Chris@0 1012 Transform::SummaryType summaryType)
Chris@0 1013 {
Chris@0 1014 // caller should have ensured plugin is in m_plugins
Chris@0 1015 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 1016
Chris@233 1017 // Write features from the feature set passed in, according to the
Chris@233 1018 // transforms listed for the given plugin with the given summary type
Chris@233 1019
Chris@0 1020 for (TransformWriterMap::const_iterator ti = pi->second.begin();
Chris@0 1021 ti != pi->second.end(); ++ti) {
Chris@0 1022
Chris@0 1023 const Transform &transform = ti->first;
Chris@0 1024 const vector<FeatureWriter *> &writers = ti->second;
Chris@0 1025
Chris@263 1026 // SVDEBUG << "writeFeatures: plugin " << plugin << " has transform: " << transform.toXmlString() << endl;
Chris@233 1027
Chris@233 1028 if (transform.getSummaryType() == Transform::NoSummary &&
Chris@233 1029 !m_summaries.empty()) {
Chris@265 1030 SVDEBUG << "writeFeatures: transform has no summary, but summaries requested on command line, so going for it anyway" << endl;
Chris@233 1031 } else if (transform.getSummaryType() != summaryType) {
Chris@233 1032 // Either we're not writing a summary and the transform
Chris@233 1033 // has one, or we're writing a summary but the transform
Chris@233 1034 // has none or a different one; either way, skip it
Chris@265 1035 SVDEBUG << "writeFeatures: transform summary type " << transform.getSummaryType() << " differs from passed-in one " << summaryType << ", skipping" << endl;
Chris@0 1036 continue;
Chris@0 1037 }
Chris@0 1038
Chris@0 1039 string outputId = transform.getOutput().toStdString();
Chris@0 1040
Chris@0 1041 if (m_pluginOutputs[plugin].find(outputId) ==
Chris@0 1042 m_pluginOutputs[plugin].end()) {
Chris@0 1043 continue;
Chris@0 1044 }
Chris@0 1045
Chris@0 1046 const Plugin::OutputDescriptor &desc =
Chris@0 1047 m_pluginOutputs[plugin][outputId];
Chris@0 1048
Chris@0 1049 int outputIndex = m_pluginOutputIndices[outputId];
Chris@0 1050 Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex);
Chris@0 1051 if (fsi == features.end()) continue;
Chris@0 1052
Chris@263 1053 // SVDEBUG << "this transform has " << writers.size() << " writer(s)" << endl;
Chris@233 1054
Chris@0 1055 for (int j = 0; j < (int)writers.size(); ++j) {
Chris@0 1056 writers[j]->write
Chris@0 1057 (audioSource, transform, desc, fsi->second,
Chris@0 1058 Transform::summaryTypeToString(summaryType).toStdString());
Chris@0 1059 }
Chris@0 1060 }
Chris@0 1061 }
Chris@0 1062
Chris@31 1063 void FeatureExtractionManager::testOutputFiles(QString audioSource)
Chris@31 1064 {
Chris@31 1065 for (PluginMap::iterator pi = m_plugins.begin();
Chris@31 1066 pi != m_plugins.end(); ++pi) {
Chris@31 1067
Chris@31 1068 for (TransformWriterMap::iterator ti = pi->second.begin();
Chris@31 1069 ti != pi->second.end(); ++ti) {
Chris@31 1070
Chris@31 1071 vector<FeatureWriter *> &writers = ti->second;
Chris@31 1072
Chris@31 1073 for (int i = 0; i < (int)writers.size(); ++i) {
Chris@31 1074 writers[i]->testOutputFile(audioSource, ti->first.getIdentifier());
Chris@31 1075 }
Chris@31 1076 }
Chris@31 1077 }
Chris@31 1078 }
Chris@31 1079
Chris@0 1080 void FeatureExtractionManager::finish()
Chris@0 1081 {
Chris@109 1082 foreach (Plugin *plugin, m_orderedPlugins) {
Chris@109 1083
Chris@109 1084 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 1085
Chris@0 1086 for (TransformWriterMap::iterator ti = pi->second.begin();
Chris@0 1087 ti != pi->second.end(); ++ti) {
Chris@0 1088
Chris@0 1089 vector<FeatureWriter *> &writers = ti->second;
Chris@0 1090
Chris@0 1091 for (int i = 0; i < (int)writers.size(); ++i) {
Chris@0 1092 writers[i]->flush();
Chris@0 1093 writers[i]->finish();
Chris@0 1094 }
Chris@0 1095 }
Chris@0 1096 }
Chris@0 1097 }
Chris@0 1098
Chris@0 1099 void FeatureExtractionManager::print(Transform transform) const
Chris@0 1100 {
Chris@0 1101 QString qs;
Chris@0 1102 QTextStream qts(&qs);
Chris@0 1103 transform.toXml(qts);
Chris@0 1104 cerr << qs.toStdString() << endl;
Chris@0 1105 }