annotate runner/FeatureExtractionManager.cpp @ 374:0263e55b68bb

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