annotate runner/FeatureExtractionManager.cpp @ 399:a3912193ce69 tip

Default branch is now named default on git as well as hg, in case we ever want to switch to mirroring in the other direction
author Chris Cannam
date Thu, 27 Aug 2020 15:57:37 +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 }