annotate runner/FeatureExtractionManager.cpp @ 121:5200446bbc6b test-reorg

Check for multiple transforms. But one of these tests fails, so we'd better go back to development branch and sort out why!
author Chris Cannam
date Wed, 08 Oct 2014 15:38:34 +0100
parents 5be4995f4029
children b514bae9555e
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@21 26 #include "base/Exceptions.h"
Chris@21 27
Chris@0 28 #include <iostream>
Chris@0 29
Chris@0 30 using namespace std;
Chris@0 31
Chris@0 32 using Vamp::Plugin;
Chris@0 33 using Vamp::PluginBase;
Chris@0 34 using Vamp::HostExt::PluginLoader;
Chris@0 35 using Vamp::HostExt::PluginChannelAdapter;
Chris@0 36 using Vamp::HostExt::PluginBufferingAdapter;
Chris@0 37 using Vamp::HostExt::PluginInputDomainAdapter;
Chris@0 38 using Vamp::HostExt::PluginSummarisingAdapter;
Chris@8 39 using Vamp::HostExt::PluginWrapper;
Chris@0 40
Chris@0 41 #include "data/fileio/FileSource.h"
Chris@0 42 #include "data/fileio/AudioFileReader.h"
Chris@0 43 #include "data/fileio/AudioFileReaderFactory.h"
Chris@0 44 #include "base/TempDirectory.h"
Chris@0 45 #include "base/ProgressPrinter.h"
Chris@0 46 #include "transform/TransformFactory.h"
Chris@0 47 #include "rdf/RDFTransformFactory.h"
Chris@0 48 #include "transform/FeatureWriter.h"
Chris@0 49
Chris@0 50 #include <QTextStream>
Chris@0 51 #include <QFile>
Chris@0 52 #include <QFileInfo>
Chris@0 53
Chris@0 54 FeatureExtractionManager::FeatureExtractionManager() :
Chris@0 55 m_summariesOnly(false),
Chris@0 56 // We can read using an arbitrary fixed block size --
Chris@0 57 // PluginBufferingAdapter handles this for us. It's likely to be
Chris@0 58 // quicker to use larger sizes than smallish ones like 1024
Chris@0 59 m_blockSize(16384),
Chris@0 60 m_defaultSampleRate(0),
Chris@0 61 m_sampleRate(0),
Chris@116 62 m_channels(0),
Chris@116 63 m_normalise(false)
Chris@0 64 {
Chris@0 65 }
Chris@0 66
Chris@0 67 FeatureExtractionManager::~FeatureExtractionManager()
Chris@0 68 {
Chris@0 69 for (PluginMap::iterator pi = m_plugins.begin();
Chris@0 70 pi != m_plugins.end(); ++pi) {
Chris@0 71 delete pi->first;
Chris@0 72 }
Chris@45 73 foreach (AudioFileReader *r, m_readyReaders) {
Chris@45 74 delete r;
Chris@45 75 }
Chris@0 76 }
Chris@0 77
Chris@0 78 void FeatureExtractionManager::setChannels(int channels)
Chris@0 79 {
Chris@0 80 m_channels = channels;
Chris@0 81 }
Chris@0 82
Chris@0 83 void FeatureExtractionManager::setDefaultSampleRate(int sampleRate)
Chris@0 84 {
Chris@0 85 m_defaultSampleRate = sampleRate;
Chris@0 86 }
Chris@0 87
Chris@116 88 void FeatureExtractionManager::setNormalise(bool normalise)
Chris@116 89 {
Chris@116 90 m_normalise = normalise;
Chris@116 91 }
Chris@116 92
Chris@0 93 static PluginSummarisingAdapter::SummaryType
Chris@0 94 getSummaryType(string name)
Chris@0 95 {
Chris@0 96 if (name == "min") return PluginSummarisingAdapter::Minimum;
Chris@0 97 if (name == "max") return PluginSummarisingAdapter::Maximum;
Chris@0 98 if (name == "mean") return PluginSummarisingAdapter::Mean;
Chris@0 99 if (name == "median") return PluginSummarisingAdapter::Median;
Chris@0 100 if (name == "mode") return PluginSummarisingAdapter::Mode;
Chris@0 101 if (name == "sum") return PluginSummarisingAdapter::Sum;
Chris@0 102 if (name == "variance") return PluginSummarisingAdapter::Variance;
Chris@0 103 if (name == "sd") return PluginSummarisingAdapter::StandardDeviation;
Chris@0 104 if (name == "count") return PluginSummarisingAdapter::Count;
Chris@0 105 return PluginSummarisingAdapter::UnknownSummaryType;
Chris@0 106 }
Chris@0 107
Chris@102 108 bool
Chris@102 109 FeatureExtractionManager::setSummaryTypes(const set<string> &names,
Chris@102 110 const PluginSummarisingAdapter::SegmentBoundaries &boundaries)
Chris@0 111 {
Chris@0 112 for (SummaryNameSet::const_iterator i = names.begin();
Chris@0 113 i != names.end(); ++i) {
Chris@0 114 if (getSummaryType(*i) == PluginSummarisingAdapter::UnknownSummaryType) {
Chris@0 115 cerr << "ERROR: Unknown summary type \"" << *i << "\"" << endl;
Chris@0 116 return false;
Chris@0 117 }
Chris@0 118 }
Chris@0 119 m_summaries = names;
Chris@0 120 m_boundaries = boundaries;
Chris@0 121 return true;
Chris@0 122 }
Chris@0 123
Chris@102 124 void
Chris@102 125 FeatureExtractionManager::setSummariesOnly(bool summariesOnly)
Chris@102 126 {
Chris@102 127 m_summariesOnly = summariesOnly;
Chris@102 128 }
Chris@102 129
Chris@51 130 static PluginInputDomainAdapter::WindowType
Chris@51 131 convertWindowType(WindowType t)
Chris@51 132 {
Chris@51 133 switch (t) {
Chris@51 134 case RectangularWindow:
Chris@51 135 return PluginInputDomainAdapter::RectangularWindow;
Chris@51 136 case BartlettWindow:
Chris@51 137 return PluginInputDomainAdapter::BartlettWindow;
Chris@51 138 case HammingWindow:
Chris@51 139 return PluginInputDomainAdapter::HammingWindow;
Chris@51 140 case HanningWindow:
Chris@51 141 return PluginInputDomainAdapter::HanningWindow;
Chris@51 142 case BlackmanWindow:
Chris@51 143 return PluginInputDomainAdapter::BlackmanWindow;
Chris@51 144 case NuttallWindow:
Chris@51 145 return PluginInputDomainAdapter::NuttallWindow;
Chris@51 146 case BlackmanHarrisWindow:
Chris@51 147 return PluginInputDomainAdapter::BlackmanHarrisWindow;
Chris@51 148 default:
Chris@51 149 cerr << "ERROR: Unknown or unsupported window type \"" << t << "\", using Hann (\"" << HanningWindow << "\")" << endl;
Chris@51 150 return PluginInputDomainAdapter::HanningWindow;
Chris@51 151 }
Chris@51 152 }
Chris@51 153
Chris@0 154 bool FeatureExtractionManager::addFeatureExtractor
Chris@0 155 (Transform transform, const vector<FeatureWriter*> &writers)
Chris@0 156 {
Chris@0 157 //!!! exceptions rather than return values?
Chris@0 158
Chris@0 159 if (transform.getSampleRate() == 0) {
Chris@0 160 if (m_sampleRate == 0) {
Chris@0 161 cerr << "NOTE: Transform does not specify a sample rate, using default rate of " << m_defaultSampleRate << endl;
Chris@0 162 transform.setSampleRate(m_defaultSampleRate);
Chris@0 163 m_sampleRate = m_defaultSampleRate;
Chris@0 164 } else {
Chris@0 165 cerr << "NOTE: Transform does not specify a sample rate, using previous transform's rate of " << m_sampleRate << endl;
Chris@0 166 transform.setSampleRate(m_sampleRate);
Chris@0 167 }
Chris@0 168 }
Chris@0 169
Chris@0 170 if (m_sampleRate == 0) {
Chris@0 171 m_sampleRate = transform.getSampleRate();
Chris@0 172 }
Chris@0 173
Chris@0 174 if (transform.getSampleRate() != m_sampleRate) {
Chris@0 175 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;
Chris@0 176 cerr << "WARNING: Using previous rate of " << m_sampleRate << " for this transform as well" << endl;
Chris@0 177 transform.setSampleRate(m_sampleRate);
Chris@0 178 }
Chris@0 179
Chris@0 180 Plugin *plugin = 0;
Chris@0 181
Chris@0 182 // Remember what the original transform looked like, and index
Chris@0 183 // based on this -- because we may be about to fill in the zeros
Chris@0 184 // for step and block size, but we want any further copies with
Chris@0 185 // the same zeros to match this one
Chris@0 186 Transform originalTransform = transform;
Chris@0 187
Chris@0 188 if (m_transformPluginMap.find(transform) == m_transformPluginMap.end()) {
Chris@0 189
Chris@0 190 // Test whether we already have a transform that is identical
Chris@0 191 // to this, except for the output requested and/or the summary
Chris@0 192 // type -- if so, they should share plugin instances (a vital
Chris@0 193 // optimisation)
Chris@0 194
Chris@0 195 for (TransformPluginMap::iterator i = m_transformPluginMap.begin();
Chris@0 196 i != m_transformPluginMap.end(); ++i) {
Chris@0 197 Transform test = i->first;
Chris@0 198 test.setOutput(transform.getOutput());
Chris@0 199 test.setSummaryType(transform.getSummaryType());
Chris@0 200 if (transform == test) {
Chris@0 201 cerr << "NOTE: Already have transform identical to this one (for \""
Chris@0 202 << transform.getIdentifier().toStdString()
Chris@0 203 << "\") in every detail except output identifier and/or "
Chris@0 204 << "summary type; sharing its plugin instance" << endl;
Chris@0 205 plugin = i->second;
Chris@0 206 if (transform.getSummaryType() != Transform::NoSummary &&
Chris@0 207 !dynamic_cast<PluginSummarisingAdapter *>(plugin)) {
Chris@0 208 plugin = new PluginSummarisingAdapter(plugin);
Chris@0 209 i->second = plugin;
Chris@0 210 }
Chris@0 211 break;
Chris@0 212 }
Chris@0 213 }
Chris@0 214
Chris@0 215 if (!plugin) {
Chris@0 216
Chris@0 217 TransformFactory *tf = TransformFactory::getInstance();
Chris@0 218
Chris@0 219 PluginBase *pb = tf->instantiatePluginFor(transform);
Chris@0 220 plugin = tf->downcastVampPlugin(pb);
Chris@0 221 if (!plugin) {
Chris@0 222 //!!! todo: handle non-Vamp plugins too, or make the main --list
Chris@0 223 // option print out only Vamp transforms
Chris@0 224 cerr << "ERROR: Failed to load plugin for transform \""
Chris@0 225 << transform.getIdentifier().toStdString() << "\"" << endl;
Chris@0 226 delete pb;
Chris@0 227 return false;
Chris@0 228 }
Chris@0 229
Chris@0 230 // We will provide the plugin with arbitrary step and
Chris@0 231 // block sizes (so that we can use the same read/write
Chris@0 232 // block size for all transforms), and to that end we use
Chris@0 233 // a PluginBufferingAdapter. However, we need to know the
Chris@0 234 // underlying step size so that we can provide the right
Chris@0 235 // context for dense outputs. (Although, don't forget
Chris@0 236 // that the PluginBufferingAdapter rewrites
Chris@0 237 // OneSamplePerStep outputs so as to use FixedSampleRate
Chris@0 238 // -- so it supplies the sample rate in the output
Chris@0 239 // feature. I'm not sure whether we can easily use that.)
Chris@0 240
Chris@0 241 size_t pluginStepSize = plugin->getPreferredStepSize();
Chris@0 242 size_t pluginBlockSize = plugin->getPreferredBlockSize();
Chris@0 243
Chris@25 244 PluginInputDomainAdapter *pida = 0;
Chris@25 245
Chris@0 246 // adapt the plugin for buffering, channels, etc.
Chris@0 247 if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
Chris@51 248
Chris@25 249 pida = new PluginInputDomainAdapter(plugin);
Chris@26 250 pida->setProcessTimestampMethod(PluginInputDomainAdapter::ShiftData);
Chris@51 251
Chris@51 252 PluginInputDomainAdapter::WindowType wtype =
Chris@51 253 convertWindowType(transform.getWindowType());
Chris@51 254 pida->setWindowType(wtype);
Chris@25 255 plugin = pida;
Chris@0 256 }
Chris@0 257
Chris@0 258 PluginBufferingAdapter *pba = new PluginBufferingAdapter(plugin);
Chris@0 259 plugin = pba;
Chris@0 260
Chris@0 261 if (transform.getStepSize() != 0) {
Chris@0 262 pba->setPluginStepSize(transform.getStepSize());
Chris@0 263 } else {
Chris@0 264 transform.setStepSize(pluginStepSize);
Chris@0 265 }
Chris@0 266
Chris@0 267 if (transform.getBlockSize() != 0) {
Chris@0 268 pba->setPluginBlockSize(transform.getBlockSize());
Chris@0 269 } else {
Chris@0 270 transform.setBlockSize(pluginBlockSize);
Chris@0 271 }
Chris@0 272
Chris@0 273 plugin = new PluginChannelAdapter(plugin);
Chris@0 274
Chris@0 275 if (!m_summaries.empty() ||
Chris@0 276 transform.getSummaryType() != Transform::NoSummary) {
Chris@0 277 PluginSummarisingAdapter *adapter =
Chris@0 278 new PluginSummarisingAdapter(plugin);
Chris@0 279 adapter->setSummarySegmentBoundaries(m_boundaries);
Chris@0 280 plugin = adapter;
Chris@0 281 }
Chris@0 282
Chris@0 283 if (!plugin->initialise(m_channels, m_blockSize, m_blockSize)) {
Chris@0 284 cerr << "ERROR: Plugin initialise (channels = " << m_channels << ", stepSize = " << m_blockSize << ", blockSize = " << m_blockSize << ") failed." << endl;
Chris@0 285 delete plugin;
Chris@0 286 return false;
Chris@0 287 }
Chris@0 288
Chris@0 289 // cerr << "Initialised plugin" << endl;
Chris@0 290
Chris@0 291 size_t actualStepSize = 0;
Chris@0 292 size_t actualBlockSize = 0;
Chris@0 293 pba->getActualStepAndBlockSizes(actualStepSize, actualBlockSize);
Chris@0 294 transform.setStepSize(actualStepSize);
Chris@0 295 transform.setBlockSize(actualBlockSize);
Chris@0 296
Chris@0 297 Plugin::OutputList outputs = plugin->getOutputDescriptors();
Chris@0 298 for (int i = 0; i < (int)outputs.size(); ++i) {
Chris@0 299
Chris@0 300 // cerr << "Newly initialised plugin output " << i << " has bin count " << outputs[i].binCount << endl;
Chris@0 301
Chris@0 302 m_pluginOutputs[plugin][outputs[i].identifier] = outputs[i];
Chris@0 303 m_pluginOutputIndices[outputs[i].identifier] = i;
Chris@0 304 }
Chris@0 305
Chris@10 306 cerr << "NOTE: Loaded and initialised plugin for transform \""
Chris@25 307 << transform.getIdentifier().toStdString()
Chris@25 308 << "\" with plugin step size " << actualStepSize
Chris@25 309 << " and block size " << actualBlockSize
Chris@25 310 << " (adapter step and block size " << m_blockSize << ")"
Chris@25 311 << endl;
Chris@25 312
Chris@25 313 if (pida) {
Chris@25 314 cerr << "NOTE: PluginInputDomainAdapter timestamp adjustment is "
Chris@25 315
Chris@25 316 << pida->getTimestampAdjustment() << endl;
Chris@25 317 }
Chris@8 318
Chris@8 319 } else {
Chris@8 320
Chris@8 321 if (transform.getStepSize() == 0 || transform.getBlockSize() == 0) {
Chris@8 322
Chris@8 323 PluginWrapper *pw = dynamic_cast<PluginWrapper *>(plugin);
Chris@8 324 if (pw) {
Chris@8 325 PluginBufferingAdapter *pba =
Chris@8 326 pw->getWrapper<PluginBufferingAdapter>();
Chris@8 327 if (pba) {
Chris@8 328 size_t actualStepSize = 0;
Chris@8 329 size_t actualBlockSize = 0;
Chris@8 330 pba->getActualStepAndBlockSizes(actualStepSize,
Chris@8 331 actualBlockSize);
Chris@8 332 if (transform.getStepSize() == 0) {
Chris@8 333 transform.setStepSize(actualStepSize);
Chris@8 334 }
Chris@8 335 if (transform.getBlockSize() == 0) {
Chris@8 336 transform.setBlockSize(actualBlockSize);
Chris@8 337 }
Chris@8 338 }
Chris@8 339 }
Chris@8 340 }
Chris@0 341 }
Chris@0 342
Chris@0 343 if (transform.getOutput() == "") {
Chris@0 344 transform.setOutput
Chris@0 345 (plugin->getOutputDescriptors()[0].identifier.c_str());
Chris@0 346 }
Chris@0 347
Chris@0 348 m_transformPluginMap[transform] = plugin;
Chris@0 349
Chris@0 350 if (!(originalTransform == transform)) {
Chris@0 351 m_transformPluginMap[originalTransform] = plugin;
Chris@0 352 }
Chris@0 353
Chris@0 354 } else {
Chris@0 355
Chris@0 356 plugin = m_transformPluginMap[transform];
Chris@0 357 }
Chris@0 358
Chris@109 359 if (m_plugins.find(plugin) == m_plugins.end()) {
Chris@109 360 m_orderedPlugins.push_back(plugin);
Chris@109 361 }
Chris@109 362
Chris@0 363 m_plugins[plugin][transform] = writers;
Chris@0 364
Chris@0 365 return true;
Chris@0 366 }
Chris@0 367
Chris@0 368 bool FeatureExtractionManager::addDefaultFeatureExtractor
Chris@0 369 (TransformId transformId, const vector<FeatureWriter*> &writers)
Chris@0 370 {
Chris@0 371 TransformFactory *tf = TransformFactory::getInstance();
Chris@0 372
Chris@0 373 if (m_sampleRate == 0) {
Chris@0 374 if (m_defaultSampleRate == 0) {
Chris@0 375 cerr << "ERROR: Default transform requested, but no default sample rate available" << endl;
Chris@0 376 return false;
Chris@0 377 } else {
Chris@0 378 cerr << "NOTE: Using default sample rate of " << m_defaultSampleRate << " for default transform" << endl;
Chris@0 379 m_sampleRate = m_defaultSampleRate;
Chris@0 380 }
Chris@0 381 }
Chris@0 382
Chris@0 383 Transform transform = tf->getDefaultTransformFor(transformId, m_sampleRate);
Chris@0 384
Chris@0 385 return addFeatureExtractor(transform, writers);
Chris@0 386 }
Chris@0 387
Chris@0 388 bool FeatureExtractionManager::addFeatureExtractorFromFile
Chris@0 389 (QString transformXmlFile, const vector<FeatureWriter*> &writers)
Chris@0 390 {
Chris@99 391 bool tryRdf = true;
Chris@99 392
Chris@99 393 if (transformXmlFile.endsWith(".xml") || transformXmlFile.endsWith(".XML")) {
Chris@99 394 // We don't support RDF-XML (and nor does the underlying
Chris@99 395 // parser library) so skip the RDF parse if the filename
Chris@99 396 // suggests XML, to avoid puking out a load of errors from
Chris@99 397 // feeding XML to a Turtle parser
Chris@99 398 tryRdf = false;
Chris@0 399 }
Chris@99 400
Chris@99 401 if (tryRdf) {
Chris@99 402 RDFTransformFactory factory
Chris@99 403 (QUrl::fromLocalFile(QFileInfo(transformXmlFile).absoluteFilePath())
Chris@99 404 .toString());
Chris@99 405 ProgressPrinter printer("Parsing transforms RDF file");
Chris@99 406 std::vector<Transform> transforms = factory.getTransforms(&printer);
Chris@99 407 if (!factory.isOK()) {
Chris@99 408 cerr << "WARNING: FeatureExtractionManager::addFeatureExtractorFromFile: Failed to parse transforms file: " << factory.getErrorString().toStdString() << endl;
Chris@99 409 if (factory.isRDF()) {
Chris@99 410 return false; // no point trying it as XML
Chris@0 411 }
Chris@0 412 }
Chris@99 413 if (!transforms.empty()) {
Chris@99 414 bool success = true;
Chris@99 415 for (int i = 0; i < (int)transforms.size(); ++i) {
Chris@99 416 if (!addFeatureExtractor(transforms[i], writers)) {
Chris@99 417 success = false;
Chris@99 418 }
Chris@99 419 }
Chris@99 420 return success;
Chris@99 421 }
Chris@0 422 }
Chris@0 423
Chris@0 424 QFile file(transformXmlFile);
Chris@0 425 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
Chris@0 426 cerr << "ERROR: Failed to open transform XML file \""
Chris@0 427 << transformXmlFile.toStdString() << "\" for reading" << endl;
Chris@0 428 return false;
Chris@0 429 }
Chris@0 430
Chris@0 431 QTextStream *qts = new QTextStream(&file);
Chris@0 432 QString qs = qts->readAll();
Chris@0 433 delete qts;
Chris@0 434 file.close();
Chris@0 435
Chris@0 436 Transform transform(qs);
Chris@0 437
Chris@0 438 return addFeatureExtractor(transform, writers);
Chris@0 439 }
Chris@0 440
Chris@106 441 void FeatureExtractionManager::addSource(QString audioSource, bool willMultiplex)
Chris@0 442 {
Chris@45 443 std::cerr << "Have audio source: \"" << audioSource.toStdString() << "\"" << std::endl;
Chris@45 444
Chris@45 445 // We don't actually do anything with it here, unless it's the
Chris@45 446 // first audio source and we need it to establish default channel
Chris@45 447 // count and sample rate
Chris@45 448
Chris@45 449 if (m_channels == 0 || m_defaultSampleRate == 0) {
Chris@45 450
Chris@45 451 ProgressPrinter retrievalProgress("Determining default rate and channel count from first input file...");
Chris@45 452
Chris@45 453 FileSource source(audioSource, &retrievalProgress);
Chris@45 454 if (!source.isAvailable()) {
Chris@45 455 cerr << "ERROR: File or URL \"" << audioSource.toStdString()
Chris@117 456 << "\" could not be located";
Chris@117 457 if (source.getErrorString() != "") {
Chris@117 458 cerr << ": " << source.getErrorString();
Chris@117 459 }
Chris@117 460 cerr << endl;
Chris@45 461 throw FileNotFound(audioSource);
Chris@45 462 }
Chris@45 463
Chris@45 464 source.waitForData();
Chris@45 465
Chris@45 466 // Open to determine validity, channel count, sample rate only
Chris@45 467 // (then close, and open again later with actual desired rate &c)
Chris@45 468
Chris@45 469 AudioFileReader *reader =
Chris@116 470 AudioFileReaderFactory::createReader(source, 0,
Chris@116 471 m_normalise,
Chris@95 472 &retrievalProgress);
Chris@45 473
Chris@45 474 if (!reader) {
Chris@45 475 throw FailedToOpenFile(audioSource);
Chris@45 476 }
Chris@45 477
Chris@45 478 retrievalProgress.done();
Chris@45 479
Chris@45 480 cerr << "File or URL \"" << audioSource.toStdString() << "\" opened successfully" << endl;
Chris@45 481
Chris@113 482 if (!willMultiplex) {
Chris@106 483 if (m_channels == 0) {
Chris@106 484 m_channels = reader->getChannelCount();
Chris@106 485 cerr << "Taking default channel count of "
Chris@106 486 << reader->getChannelCount() << " from file" << endl;
Chris@106 487 }
Chris@45 488 }
Chris@45 489
Chris@45 490 if (m_defaultSampleRate == 0) {
Chris@45 491 m_defaultSampleRate = reader->getNativeRate();
Chris@45 492 cerr << "Taking default sample rate of "
Chris@45 493 << reader->getNativeRate() << "Hz from file" << endl;
Chris@45 494 cerr << "(Note: Default may be overridden by transforms)" << endl;
Chris@45 495 }
Chris@45 496
Chris@45 497 m_readyReaders[audioSource] = reader;
Chris@45 498 }
Chris@113 499
Chris@113 500 if (willMultiplex) {
Chris@113 501 ++m_channels; // channel count is simply number of sources
Chris@113 502 cerr << "Multiplexing, incremented target channel count to "
Chris@113 503 << m_channels << endl;
Chris@113 504 }
Chris@45 505 }
Chris@45 506
Chris@112 507 void FeatureExtractionManager::extractFeatures(QString audioSource)
Chris@45 508 {
Chris@45 509 if (m_plugins.empty()) return;
Chris@45 510
Chris@45 511 testOutputFiles(audioSource);
Chris@45 512
Chris@0 513 if (m_sampleRate == 0) {
Chris@45 514 throw FileOperationFailed
Chris@45 515 (audioSource, "internal error: have sources and plugins, but no sample rate");
Chris@45 516 }
Chris@45 517 if (m_channels == 0) {
Chris@45 518 throw FileOperationFailed
Chris@45 519 (audioSource, "internal error: have sources and plugins, but no channel count");
Chris@0 520 }
Chris@0 521
Chris@106 522 AudioFileReader *reader = prepareReader(audioSource);
Chris@106 523 extractFeaturesFor(reader, audioSource); // Note this also deletes reader
Chris@106 524 }
Chris@102 525
Chris@106 526 void FeatureExtractionManager::extractFeaturesMultiplexed(QStringList sources)
Chris@106 527 {
Chris@106 528 if (m_plugins.empty() || sources.empty()) return;
Chris@106 529
Chris@106 530 QString nominalSource = sources[0];
Chris@106 531
Chris@106 532 testOutputFiles(nominalSource);
Chris@106 533
Chris@106 534 if (m_sampleRate == 0) {
Chris@106 535 throw FileOperationFailed
Chris@106 536 (nominalSource, "internal error: have sources and plugins, but no sample rate");
Chris@106 537 }
Chris@106 538 if (m_channels == 0) {
Chris@106 539 throw FileOperationFailed
Chris@106 540 (nominalSource, "internal error: have sources and plugins, but no channel count");
Chris@106 541 }
Chris@106 542
Chris@106 543 QList<AudioFileReader *> readers;
Chris@106 544 foreach (QString source, sources) {
Chris@106 545 AudioFileReader *reader = prepareReader(source);
Chris@106 546 readers.push_back(reader);
Chris@106 547 }
Chris@106 548
Chris@106 549 AudioFileReader *reader = new MultiplexedReader(readers);
Chris@106 550 extractFeaturesFor(reader, nominalSource); // Note this also deletes reader
Chris@106 551 }
Chris@106 552
Chris@106 553 AudioFileReader *
Chris@106 554 FeatureExtractionManager::prepareReader(QString source)
Chris@106 555 {
Chris@45 556 AudioFileReader *reader = 0;
Chris@106 557 if (m_readyReaders.contains(source)) {
Chris@106 558 reader = m_readyReaders[source];
Chris@106 559 m_readyReaders.remove(source);
Chris@106 560 if (reader->getSampleRate() != m_sampleRate) {
Chris@45 561 // can't use this; open it again
Chris@45 562 delete reader;
Chris@45 563 reader = 0;
Chris@45 564 }
Chris@45 565 }
Chris@45 566 if (!reader) {
Chris@45 567 ProgressPrinter retrievalProgress("Retrieving audio data...");
Chris@106 568 FileSource fs(source, &retrievalProgress);
Chris@106 569 fs.waitForData();
Chris@116 570 reader = AudioFileReaderFactory::createReader(fs, m_sampleRate,
Chris@116 571 m_normalise,
Chris@116 572 &retrievalProgress);
Chris@45 573 retrievalProgress.done();
Chris@45 574 }
Chris@102 575 if (!reader) {
Chris@107 576 throw FailedToOpenFile(source);
Chris@102 577 }
Chris@106 578 return reader;
Chris@106 579 }
Chris@45 580
Chris@106 581 void
Chris@106 582 FeatureExtractionManager::extractFeaturesFor(AudioFileReader *reader,
Chris@106 583 QString audioSource)
Chris@106 584 {
Chris@106 585 // Note: This also deletes reader
Chris@0 586
Chris@45 587 cerr << "Audio file \"" << audioSource.toStdString() << "\": "
Chris@45 588 << reader->getChannelCount() << "ch at "
Chris@45 589 << reader->getNativeRate() << "Hz" << endl;
Chris@45 590 if (reader->getChannelCount() != m_channels ||
Chris@45 591 reader->getNativeRate() != m_sampleRate) {
Chris@113 592 cerr << "NOTE: File will be mixed or resampled for processing, to: "
Chris@45 593 << m_channels << "ch at "
Chris@45 594 << m_sampleRate << "Hz" << endl;
Chris@45 595 }
Chris@11 596
Chris@0 597 // allocate audio buffers
Chris@0 598 float **data = new float *[m_channels];
Chris@0 599 for (int c = 0; c < m_channels; ++c) {
Chris@0 600 data[c] = new float[m_blockSize];
Chris@0 601 }
Chris@31 602
Chris@31 603 struct LifespanMgr { // unintrusive hack introduced to ensure
Chris@31 604 // destruction on exceptions
Chris@31 605 AudioFileReader *m_r;
Chris@31 606 int m_c;
Chris@31 607 float **m_d;
Chris@31 608 LifespanMgr(AudioFileReader *r, int c, float **d) :
Chris@31 609 m_r(r), m_c(c), m_d(d) { }
Chris@31 610 ~LifespanMgr() { destroy(); }
Chris@31 611 void destroy() {
Chris@31 612 if (!m_r) return;
Chris@31 613 delete m_r;
Chris@31 614 for (int i = 0; i < m_c; ++i) delete[] m_d[i];
Chris@31 615 delete[] m_d;
Chris@31 616 m_r = 0;
Chris@31 617 }
Chris@31 618 };
Chris@31 619 LifespanMgr lifemgr(reader, m_channels, data);
Chris@0 620
Chris@0 621 size_t frameCount = reader->getFrameCount();
Chris@0 622
Chris@0 623 // cerr << "file has " << frameCount << " frames" << endl;
Chris@0 624
Chris@99 625 int earliestStartFrame = 0;
Chris@99 626 int latestEndFrame = frameCount;
Chris@99 627 bool haveExtents = false;
Chris@99 628
Chris@109 629 foreach (Plugin *plugin, m_orderedPlugins) {
Chris@0 630
Chris@109 631 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 632
Chris@109 633 std::cerr << "Calling reset on " << plugin << std::endl;
Chris@0 634 plugin->reset();
Chris@0 635
Chris@0 636 for (TransformWriterMap::iterator ti = pi->second.begin();
Chris@0 637 ti != pi->second.end(); ++ti) {
Chris@0 638
Chris@0 639 const Transform &transform = ti->first;
Chris@0 640
Chris@99 641 int startFrame = RealTime::realTime2Frame
Chris@99 642 (transform.getStartTime(), m_sampleRate);
Chris@99 643 int duration = RealTime::realTime2Frame
Chris@99 644 (transform.getDuration(), m_sampleRate);
Chris@99 645 if (duration == 0) {
Chris@99 646 duration = frameCount - startFrame;
Chris@99 647 }
Chris@99 648
Chris@99 649 if (!haveExtents || startFrame < earliestStartFrame) {
Chris@99 650 earliestStartFrame = startFrame;
Chris@99 651 }
Chris@99 652 if (!haveExtents || startFrame + duration > latestEndFrame) {
Chris@99 653 latestEndFrame = startFrame + duration;
Chris@99 654 }
Chris@99 655
Chris@109 656 cerr << "startFrame for transform " << startFrame << endl;
Chris@109 657 cerr << "duration for transform " << duration << endl;
Chris@109 658 cerr << "earliestStartFrame becomes " << earliestStartFrame << endl;
Chris@109 659 cerr << "latestEndFrame becomes " << latestEndFrame << endl;
Chris@109 660
Chris@99 661 haveExtents = true;
Chris@0 662
Chris@0 663 string outputId = transform.getOutput().toStdString();
Chris@0 664 if (m_pluginOutputs[plugin].find(outputId) ==
Chris@0 665 m_pluginOutputs[plugin].end()) {
Chris@0 666 //!!! throw?
Chris@0 667 cerr << "WARNING: Nonexistent plugin output \"" << outputId << "\" requested for transform \""
Chris@0 668 << transform.getIdentifier().toStdString() << "\", ignoring this transform"
Chris@0 669 << endl;
Chris@0 670 /*
Chris@0 671 cerr << "Known outputs for all plugins are as follows:" << endl;
Chris@0 672 for (PluginOutputMap::const_iterator k = m_pluginOutputs.begin();
Chris@0 673 k != m_pluginOutputs.end(); ++k) {
Chris@0 674 cerr << "Plugin " << k->first << ": ";
Chris@0 675 if (k->second.empty()) {
Chris@0 676 cerr << "(none)";
Chris@0 677 }
Chris@0 678 for (OutputMap::const_iterator i = k->second.begin();
Chris@0 679 i != k->second.end(); ++i) {
Chris@0 680 cerr << "\"" << i->first << "\" ";
Chris@0 681 }
Chris@0 682 cerr << endl;
Chris@0 683 }
Chris@0 684 */
Chris@0 685 }
Chris@0 686 }
Chris@0 687 }
Chris@0 688
Chris@99 689 int startFrame = earliestStartFrame;
Chris@99 690 int endFrame = latestEndFrame;
Chris@0 691
Chris@109 692 foreach (Plugin *plugin, m_orderedPlugins) {
Chris@109 693
Chris@109 694 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 695
Chris@0 696 for (TransformWriterMap::const_iterator ti = pi->second.begin();
Chris@0 697 ti != pi->second.end(); ++ti) {
Chris@0 698
Chris@0 699 const vector<FeatureWriter *> &writers = ti->second;
Chris@0 700
Chris@0 701 for (int j = 0; j < (int)writers.size(); ++j) {
Chris@0 702 FeatureWriter::TrackMetadata m;
Chris@0 703 m.title = reader->getTitle();
Chris@0 704 m.maker = reader->getMaker();
Chris@19 705 if (m.title != "" && m.maker != "") {
Chris@19 706 writers[j]->setTrackMetadata(audioSource, m);
Chris@19 707 }
Chris@0 708 }
Chris@0 709 }
Chris@0 710 }
Chris@0 711
Chris@0 712 ProgressPrinter extractionProgress("Extracting and writing features...");
Chris@0 713 int progress = 0;
Chris@0 714
Chris@99 715 for (int i = startFrame; i < endFrame; i += m_blockSize) {
Chris@0 716
Chris@0 717 //!!! inefficient, although much of the inefficiency may be
Chris@99 718 // susceptible to compiler optimisation
Chris@0 719
Chris@0 720 SampleBlock frames;
Chris@0 721 reader->getInterleavedFrames(i, m_blockSize, frames);
Chris@0 722
Chris@0 723 // We have to do our own channel handling here; we can't just
Chris@0 724 // leave it to the plugin adapter because the same plugin
Chris@0 725 // adapter may have to serve for input files with various
Chris@0 726 // numbers of channels (so the adapter is simply configured
Chris@34 727 // with a fixed channel count).
Chris@0 728
Chris@0 729 int rc = reader->getChannelCount();
Chris@0 730
Chris@34 731 // m_channels is the number of channels we need for the plugin
Chris@34 732
Chris@34 733 int index;
Chris@34 734 int fc = (int)frames.size();
Chris@46 735
Chris@34 736 if (m_channels == 1) { // only case in which we can sensibly mix down
Chris@34 737 for (int j = 0; j < m_blockSize; ++j) {
Chris@34 738 data[0][j] = 0.f;
Chris@34 739 }
Chris@34 740 for (int c = 0; c < rc; ++c) {
Chris@34 741 for (int j = 0; j < m_blockSize; ++j) {
Chris@0 742 index = j * rc + c;
Chris@34 743 if (index < fc) data[0][j] += frames[index];
Chris@0 744 }
Chris@0 745 }
Chris@34 746 for (int j = 0; j < m_blockSize; ++j) {
Chris@34 747 data[0][j] /= rc;
Chris@34 748 }
Chris@34 749 } else {
Chris@34 750 for (int c = 0; c < m_channels; ++c) {
Chris@34 751 for (int j = 0; j < m_blockSize; ++j) {
Chris@34 752 data[c][j] = 0.f;
Chris@34 753 }
Chris@34 754 if (c < rc) {
Chris@34 755 for (int j = 0; j < m_blockSize; ++j) {
Chris@34 756 index = j * rc + c;
Chris@34 757 if (index < fc) data[c][j] += frames[index];
Chris@34 758 }
Chris@34 759 }
Chris@34 760 }
Chris@34 761 }
Chris@0 762
Chris@0 763 Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime
Chris@0 764 (i, m_sampleRate);
Chris@0 765
Chris@109 766 foreach (Plugin *plugin, m_orderedPlugins) {
Chris@0 767
Chris@109 768 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 769 Plugin::FeatureSet featureSet = plugin->process(data, timestamp);
Chris@0 770
Chris@0 771 if (!m_summariesOnly) {
Chris@0 772 writeFeatures(audioSource, plugin, featureSet);
Chris@0 773 }
Chris@0 774 }
Chris@0 775
Chris@0 776 int pp = progress;
Chris@6 777 progress = int(((i - startFrame) * 100.0) / (endFrame - startFrame) + 0.1);
Chris@0 778 if (progress > pp) extractionProgress.setProgress(progress);
Chris@0 779 }
Chris@10 780
Chris@22 781 // std::cerr << "FeatureExtractionManager: deleting audio file reader" << std::endl;
Chris@12 782
Chris@31 783 lifemgr.destroy(); // deletes reader, data
Chris@57 784
Chris@109 785 foreach (Plugin *plugin, m_orderedPlugins) {
Chris@57 786
Chris@109 787 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 788 Plugin::FeatureSet featureSet = plugin->getRemainingFeatures();
Chris@0 789
Chris@0 790 if (!m_summariesOnly) {
Chris@0 791 writeFeatures(audioSource, plugin, featureSet);
Chris@0 792 }
Chris@0 793
Chris@0 794 if (!m_summaries.empty()) {
Chris@0 795 PluginSummarisingAdapter *adapter =
Chris@0 796 dynamic_cast<PluginSummarisingAdapter *>(plugin);
Chris@0 797 if (!adapter) {
Chris@0 798 cerr << "WARNING: Summaries requested, but plugin is not a summarising adapter" << endl;
Chris@0 799 } else {
Chris@0 800 for (SummaryNameSet::const_iterator sni = m_summaries.begin();
Chris@0 801 sni != m_summaries.end(); ++sni) {
Chris@0 802 featureSet.clear();
Chris@0 803 //!!! problem here -- we are requesting summaries
Chris@0 804 //!!! for all outputs, but they in principle have
Chris@0 805 //!!! different averaging requirements depending
Chris@0 806 //!!! on whether their features have duration or
Chris@0 807 //!!! not
Chris@0 808 featureSet = adapter->getSummaryForAllOutputs
Chris@0 809 (getSummaryType(*sni),
Chris@0 810 PluginSummarisingAdapter::ContinuousTimeAverage);
Chris@0 811 writeFeatures(audioSource, plugin, featureSet,//!!! *sni);
Chris@0 812 Transform::stringToSummaryType(sni->c_str()));
Chris@0 813 }
Chris@0 814 }
Chris@0 815 }
Chris@0 816
Chris@0 817 writeSummaries(audioSource, plugin);
Chris@0 818 }
Chris@0 819
Chris@3 820 extractionProgress.done();
Chris@3 821
Chris@0 822 finish();
Chris@0 823
Chris@0 824 TempDirectory::getInstance()->cleanup();
Chris@0 825 }
Chris@0 826
Chris@0 827 void
Chris@0 828 FeatureExtractionManager::writeSummaries(QString audioSource, Plugin *plugin)
Chris@0 829 {
Chris@0 830 // caller should have ensured plugin is in m_plugins
Chris@0 831 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 832
Chris@0 833 for (TransformWriterMap::const_iterator ti = pi->second.begin();
Chris@0 834 ti != pi->second.end(); ++ti) {
Chris@0 835
Chris@0 836 const Transform &transform = ti->first;
Chris@0 837 const vector<FeatureWriter *> &writers = ti->second;
Chris@0 838
Chris@0 839 Transform::SummaryType summaryType = transform.getSummaryType();
Chris@0 840 PluginSummarisingAdapter::SummaryType pType =
Chris@0 841 (PluginSummarisingAdapter::SummaryType)summaryType;
Chris@0 842
Chris@0 843 if (transform.getSummaryType() == Transform::NoSummary) {
Chris@0 844 continue;
Chris@0 845 }
Chris@0 846
Chris@0 847 PluginSummarisingAdapter *adapter =
Chris@0 848 dynamic_cast<PluginSummarisingAdapter *>(plugin);
Chris@0 849 if (!adapter) {
Chris@0 850 cerr << "FeatureExtractionManager::writeSummaries: INTERNAL ERROR: Summary requested for transform, but plugin is not a summarising adapter" << endl;
Chris@0 851 continue;
Chris@0 852 }
Chris@0 853
Chris@0 854 Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs
Chris@0 855 (pType, PluginSummarisingAdapter::ContinuousTimeAverage);
Chris@0 856
Chris@0 857 // cout << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl;
Chris@0 858
Chris@0 859 writeFeatures(audioSource, plugin, featureSet, summaryType);
Chris@0 860 }
Chris@0 861 }
Chris@0 862
Chris@0 863 void FeatureExtractionManager::writeFeatures(QString audioSource,
Chris@0 864 Plugin *plugin,
Chris@0 865 const Plugin::FeatureSet &features,
Chris@0 866 Transform::SummaryType summaryType)
Chris@0 867 {
Chris@0 868 // caller should have ensured plugin is in m_plugins
Chris@0 869 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 870
Chris@0 871 for (TransformWriterMap::const_iterator ti = pi->second.begin();
Chris@0 872 ti != pi->second.end(); ++ti) {
Chris@0 873
Chris@0 874 const Transform &transform = ti->first;
Chris@0 875 const vector<FeatureWriter *> &writers = ti->second;
Chris@0 876
Chris@0 877 if (transform.getSummaryType() != Transform::NoSummary &&
Chris@0 878 m_summaries.empty() &&
Chris@0 879 summaryType == Transform::NoSummary) {
Chris@0 880 continue;
Chris@0 881 }
Chris@0 882
Chris@0 883 if (transform.getSummaryType() != Transform::NoSummary &&
Chris@0 884 summaryType != Transform::NoSummary &&
Chris@0 885 transform.getSummaryType() != summaryType) {
Chris@0 886 continue;
Chris@0 887 }
Chris@0 888
Chris@0 889 string outputId = transform.getOutput().toStdString();
Chris@0 890
Chris@0 891 if (m_pluginOutputs[plugin].find(outputId) ==
Chris@0 892 m_pluginOutputs[plugin].end()) {
Chris@0 893 continue;
Chris@0 894 }
Chris@0 895
Chris@0 896 const Plugin::OutputDescriptor &desc =
Chris@0 897 m_pluginOutputs[plugin][outputId];
Chris@0 898
Chris@0 899 int outputIndex = m_pluginOutputIndices[outputId];
Chris@0 900 Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex);
Chris@0 901 if (fsi == features.end()) continue;
Chris@0 902
Chris@0 903 for (int j = 0; j < (int)writers.size(); ++j) {
Chris@0 904 writers[j]->write
Chris@0 905 (audioSource, transform, desc, fsi->second,
Chris@0 906 Transform::summaryTypeToString(summaryType).toStdString());
Chris@0 907 }
Chris@0 908 }
Chris@0 909 }
Chris@0 910
Chris@31 911 void FeatureExtractionManager::testOutputFiles(QString audioSource)
Chris@31 912 {
Chris@31 913 for (PluginMap::iterator pi = m_plugins.begin();
Chris@31 914 pi != m_plugins.end(); ++pi) {
Chris@31 915
Chris@31 916 for (TransformWriterMap::iterator ti = pi->second.begin();
Chris@31 917 ti != pi->second.end(); ++ti) {
Chris@31 918
Chris@31 919 vector<FeatureWriter *> &writers = ti->second;
Chris@31 920
Chris@31 921 for (int i = 0; i < (int)writers.size(); ++i) {
Chris@31 922 writers[i]->testOutputFile(audioSource, ti->first.getIdentifier());
Chris@31 923 }
Chris@31 924 }
Chris@31 925 }
Chris@31 926 }
Chris@31 927
Chris@0 928 void FeatureExtractionManager::finish()
Chris@0 929 {
Chris@109 930 foreach (Plugin *plugin, m_orderedPlugins) {
Chris@109 931
Chris@109 932 PluginMap::iterator pi = m_plugins.find(plugin);
Chris@0 933
Chris@0 934 for (TransformWriterMap::iterator ti = pi->second.begin();
Chris@0 935 ti != pi->second.end(); ++ti) {
Chris@0 936
Chris@0 937 vector<FeatureWriter *> &writers = ti->second;
Chris@0 938
Chris@0 939 for (int i = 0; i < (int)writers.size(); ++i) {
Chris@0 940 writers[i]->flush();
Chris@0 941 writers[i]->finish();
Chris@0 942 }
Chris@0 943 }
Chris@0 944 }
Chris@0 945 }
Chris@0 946
Chris@0 947 void FeatureExtractionManager::print(Transform transform) const
Chris@0 948 {
Chris@0 949 QString qs;
Chris@0 950 QTextStream qts(&qs);
Chris@0 951 transform.toXml(qts);
Chris@0 952 cerr << qs.toStdString() << endl;
Chris@0 953 }