annotate transform/FeatureExtractionModelTransformer.cpp @ 1881:b504df98c3be

Ensure completion on output model is started at zero, so if it's checked before the input model has become ready and the transform has begun, it is not accidentally reported as complete (affected re-aligning models in Sonic Lineup when replacing the session)
author Chris Cannam
date Fri, 26 Jun 2020 11:45:39 +0100
parents 1b688ab5f1b3
children
rev   line source
Chris@320 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@320 2
Chris@320 3 /*
Chris@320 4 Sonic Visualiser
Chris@320 5 An audio file viewer and annotation editor.
Chris@320 6 Centre for Digital Music, Queen Mary, University of London.
Chris@320 7 This file copyright 2006 Chris Cannam and QMUL.
Chris@320 8
Chris@320 9 This program is free software; you can redistribute it and/or
Chris@320 10 modify it under the terms of the GNU General Public License as
Chris@320 11 published by the Free Software Foundation; either version 2 of the
Chris@320 12 License, or (at your option) any later version. See the file
Chris@320 13 COPYING included with this distribution for more information.
Chris@320 14 */
Chris@320 15
Chris@331 16 #include "FeatureExtractionModelTransformer.h"
Chris@320 17
Chris@320 18 #include "plugin/FeatureExtractionPluginFactory.h"
Chris@1225 19
Chris@320 20 #include "plugin/PluginXml.h"
Chris@475 21 #include <vamp-hostsdk/Plugin.h>
Chris@320 22
Chris@320 23 #include "data/model/Model.h"
Chris@320 24 #include "base/Window.h"
Chris@387 25 #include "base/Exceptions.h"
Chris@320 26 #include "data/model/SparseOneDimensionalModel.h"
Chris@320 27 #include "data/model/SparseTimeValueModel.h"
Chris@1777 28 #include "data/model/BasicCompressedDenseThreeDimensionalModel.h"
Chris@320 29 #include "data/model/DenseTimeValueModel.h"
Chris@320 30 #include "data/model/NoteModel.h"
Chris@441 31 #include "data/model/RegionModel.h"
Chris@320 32 #include "data/model/FFTModel.h"
Chris@320 33 #include "data/model/WaveFileModel.h"
Chris@558 34 #include "rdf/PluginRDFDescription.h"
Chris@320 35
Chris@350 36 #include "TransformFactory.h"
Chris@350 37
Chris@320 38 #include <iostream>
Chris@320 39
Chris@859 40 #include <QSettings>
Chris@859 41
Chris@1558 42 //#define DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN 1
Chris@1558 43
Chris@350 44 FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in,
Chris@859 45 const Transform &transform) :
Chris@350 46 ModelTransformer(in, transform),
Chris@1582 47 m_plugin(nullptr),
Chris@1211 48 m_haveOutputs(false)
Chris@320 49 {
Chris@1080 50 SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl;
Chris@849 51 }
Chris@849 52
Chris@849 53 FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in,
Chris@859 54 const Transforms &transforms) :
Chris@849 55 ModelTransformer(in, transforms),
Chris@1582 56 m_plugin(nullptr),
Chris@1211 57 m_haveOutputs(false)
Chris@849 58 {
Chris@1080 59 if (m_transforms.empty()) {
Chris@1080 60 SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s)" << endl;
Chris@1080 61 } else {
Chris@1080 62 SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s), first has plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl;
Chris@1080 63 }
Chris@849 64 }
Chris@849 65
Chris@849 66 static bool
Chris@849 67 areTransformsSimilar(const Transform &t1, const Transform &t2)
Chris@849 68 {
Chris@849 69 Transform t2o(t2);
Chris@849 70 t2o.setOutput(t1.getOutput());
Chris@849 71 return t1 == t2o;
Chris@849 72 }
Chris@849 73
Chris@849 74 bool
Chris@849 75 FeatureExtractionModelTransformer::initialise()
Chris@849 76 {
Chris@1237 77 // This is (now) called from the run thread. The plugin is
Chris@1237 78 // constructed, initialised, used, and destroyed all from a single
Chris@1237 79 // thread.
Chris@1237 80
Chris@849 81 // All transforms must use the same plugin, parameters, and
Chris@849 82 // inputs: they can differ only in choice of plugin output. So we
Chris@849 83 // initialise based purely on the first transform in the list (but
Chris@849 84 // first check that they are actually similar as promised)
Chris@849 85
Chris@1739 86 for (int j = 1; in_range_for(m_transforms, j); ++j) {
Chris@849 87 if (!areTransformsSimilar(m_transforms[0], m_transforms[j])) {
Chris@849 88 m_message = tr("Transforms supplied to a single FeatureExtractionModelTransformer instance must be similar in every respect except plugin output");
Chris@1368 89 SVCERR << m_message << endl;
Chris@849 90 return false;
Chris@849 91 }
Chris@849 92 }
Chris@849 93
Chris@849 94 Transform primaryTransform = m_transforms[0];
Chris@849 95
Chris@849 96 QString pluginId = primaryTransform.getPluginIdentifier();
Chris@320 97
Chris@1226 98 FeatureExtractionPluginFactory *factory =
Chris@1226 99 FeatureExtractionPluginFactory::instance();
Chris@320 100
Chris@320 101 if (!factory) {
Chris@361 102 m_message = tr("No factory available for feature extraction plugin id \"%1\" (unknown plugin type, or internal error?)").arg(pluginId);
Chris@1368 103 SVCERR << m_message << endl;
Chris@1429 104 return false;
Chris@320 105 }
Chris@320 106
Chris@1739 107 auto input = ModelById::getAs<DenseTimeValueModel>(getInputModel());
Chris@350 108 if (!input) {
Chris@361 109 m_message = tr("Input model for feature extraction plugin \"%1\" is of wrong type (internal error?)").arg(pluginId);
Chris@1368 110 SVCERR << m_message << endl;
Chris@849 111 return false;
Chris@350 112 }
Chris@320 113
Chris@1264 114 SVDEBUG << "FeatureExtractionModelTransformer: Instantiating plugin for transform in thread "
Chris@1264 115 << QThread::currentThreadId() << endl;
Chris@1211 116
Chris@1040 117 m_plugin = factory->instantiatePlugin(pluginId, input->getSampleRate());
Chris@320 118 if (!m_plugin) {
Chris@361 119 m_message = tr("Failed to instantiate plugin \"%1\"").arg(pluginId);
Chris@1368 120 SVCERR << m_message << endl;
Chris@1429 121 return false;
Chris@320 122 }
Chris@320 123
Chris@350 124 TransformFactory::getInstance()->makeContextConsistentWithPlugin
Chris@849 125 (primaryTransform, m_plugin);
Chris@1368 126
Chris@350 127 TransformFactory::getInstance()->setPluginParameters
Chris@849 128 (primaryTransform, m_plugin);
Chris@1368 129
Chris@930 130 int channelCount = input->getChannelCount();
Chris@930 131 if ((int)m_plugin->getMaxChannelCount() < channelCount) {
Chris@1429 132 channelCount = 1;
Chris@320 133 }
Chris@930 134 if ((int)m_plugin->getMinChannelCount() > channelCount) {
Chris@361 135 m_message = tr("Cannot provide enough channels to feature extraction plugin \"%1\" (plugin min is %2, max %3; input model has %4)")
Chris@361 136 .arg(pluginId)
Chris@361 137 .arg(m_plugin->getMinChannelCount())
Chris@361 138 .arg(m_plugin->getMaxChannelCount())
Chris@361 139 .arg(input->getChannelCount());
Chris@1368 140 SVCERR << m_message << endl;
Chris@1429 141 return false;
Chris@320 142 }
Chris@1374 143
Chris@1374 144 int step = primaryTransform.getStepSize();
Chris@1374 145 int block = primaryTransform.getBlockSize();
Chris@1368 146
Chris@690 147 SVDEBUG << "Initialising feature extraction plugin with channels = "
Chris@1374 148 << channelCount << ", step = " << step
Chris@1374 149 << ", block = " << block << endl;
Chris@320 150
Chris@1374 151 if (!m_plugin->initialise(channelCount, step, block)) {
Chris@1374 152
Chris@1374 153 int preferredStep = int(m_plugin->getPreferredStepSize());
Chris@1374 154 int preferredBlock = int(m_plugin->getPreferredBlockSize());
Chris@1264 155
Chris@1374 156 if (step != preferredStep || block != preferredBlock) {
Chris@361 157
Chris@1374 158 SVDEBUG << "Initialisation failed, trying again with preferred step = "
Chris@1374 159 << preferredStep << ", block = " << preferredBlock << endl;
Chris@361 160
Chris@1739 161 if (!m_plugin->initialise(channelCount,
Chris@1739 162 preferredStep,
Chris@1739 163 preferredBlock)) {
Chris@361 164
Chris@1264 165 SVDEBUG << "Initialisation failed again" << endl;
Chris@1264 166
Chris@361 167 m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
Chris@1368 168 SVCERR << m_message << endl;
Chris@849 169 return false;
Chris@361 170
Chris@361 171 } else {
Chris@1264 172
Chris@1264 173 SVDEBUG << "Initialisation succeeded this time" << endl;
Chris@1374 174
Chris@1374 175 // Set these values into the primary transform in the list
Chris@1374 176 m_transforms[0].setStepSize(preferredStep);
Chris@1374 177 m_transforms[0].setBlockSize(preferredBlock);
Chris@1264 178
Chris@361 179 m_message = tr("Feature extraction plugin \"%1\" rejected the given step and block sizes (%2 and %3); using plugin defaults (%4 and %5) instead")
Chris@361 180 .arg(pluginId)
Chris@1374 181 .arg(step)
Chris@1374 182 .arg(block)
Chris@1374 183 .arg(preferredStep)
Chris@1374 184 .arg(preferredBlock);
Chris@1368 185 SVCERR << m_message << endl;
Chris@361 186 }
Chris@361 187
Chris@361 188 } else {
Chris@361 189
Chris@1374 190 SVDEBUG << "Initialisation failed (with step = " << step
Chris@1374 191 << " and block = " << block
Chris@1374 192 << ", both matching the plugin's preference)" << endl;
Chris@1264 193
Chris@361 194 m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
Chris@1368 195 SVCERR << m_message << endl;
Chris@849 196 return false;
Chris@361 197 }
Chris@1264 198 } else {
Chris@1264 199 SVDEBUG << "Initialisation succeeded" << endl;
Chris@320 200 }
Chris@320 201
Chris@849 202 if (primaryTransform.getPluginVersion() != "") {
Chris@366 203 QString pv = QString("%1").arg(m_plugin->getPluginVersion());
Chris@849 204 if (pv != primaryTransform.getPluginVersion()) {
Chris@366 205 QString vm = tr("Transform was configured for version %1 of plugin \"%2\", but the plugin being used is version %3")
Chris@849 206 .arg(primaryTransform.getPluginVersion())
Chris@366 207 .arg(pluginId)
Chris@366 208 .arg(pv);
Chris@366 209 if (m_message != "") {
Chris@366 210 m_message = QString("%1; %2").arg(vm).arg(m_message);
Chris@366 211 } else {
Chris@366 212 m_message = vm;
Chris@366 213 }
Chris@1368 214 SVCERR << m_message << endl;
Chris@366 215 }
Chris@366 216 }
Chris@366 217
Chris@320 218 Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors();
Chris@320 219
Chris@320 220 if (outputs.empty()) {
Chris@361 221 m_message = tr("Plugin \"%1\" has no outputs").arg(pluginId);
Chris@1368 222 SVCERR << m_message << endl;
Chris@1429 223 return false;
Chris@320 224 }
Chris@320 225
Chris@1739 226 for (int j = 0; in_range_for(m_transforms, j); ++j) {
Chris@849 227
Chris@1739 228 for (int i = 0; in_range_for(outputs, i); ++i) {
Chris@1739 229
Chris@849 230 if (m_transforms[j].getOutput() == "" ||
Chris@1739 231 outputs[i].identifier ==
Chris@1739 232 m_transforms[j].getOutput().toStdString()) {
Chris@1739 233
Chris@849 234 m_outputNos.push_back(i);
Chris@1739 235 m_descriptors.push_back(outputs[i]);
Chris@849 236 m_fixedRateFeatureNos.push_back(-1); // we increment before use
Chris@849 237 break;
Chris@849 238 }
Chris@849 239 }
Chris@849 240
Chris@1739 241 if (!in_range_for(m_descriptors, j)) {
Chris@849 242 m_message = tr("Plugin \"%1\" has no output named \"%2\"")
Chris@849 243 .arg(pluginId)
Chris@849 244 .arg(m_transforms[j].getOutput());
Chris@1368 245 SVCERR << m_message << endl;
Chris@849 246 return false;
Chris@849 247 }
Chris@320 248 }
Chris@320 249
Chris@1739 250 for (int j = 0; in_range_for(m_transforms, j); ++j) {
Chris@876 251 createOutputModels(j);
Chris@1881 252 setCompletion(j, 0);
Chris@849 253 }
Chris@849 254
Chris@1211 255 m_outputMutex.lock();
Chris@1211 256 m_haveOutputs = true;
Chris@1211 257 m_outputsCondition.wakeAll();
Chris@1211 258 m_outputMutex.unlock();
Chris@1211 259
Chris@849 260 return true;
Chris@558 261 }
Chris@558 262
Chris@558 263 void
Chris@1237 264 FeatureExtractionModelTransformer::deinitialise()
Chris@1237 265 {
Chris@1264 266 SVDEBUG << "FeatureExtractionModelTransformer: deleting plugin for transform in thread "
Chris@1264 267 << QThread::currentThreadId() << endl;
Chris@1372 268
Chris@1372 269 try {
Chris@1830 270 m_plugin = {}; // does not necessarily delete, as it's a
Chris@1830 271 // shared_ptr, but in the design case it will
Chris@1372 272 } catch (const std::exception &e) {
Chris@1372 273 // A destructor shouldn't throw an exception. But at one point
Chris@1372 274 // (now fixed) our plugin stub destructor could have
Chris@1372 275 // accidentally done so, so just in case:
Chris@1372 276 SVCERR << "FeatureExtractionModelTransformer: caught exception while deleting plugin: " << e.what() << endl;
Chris@1372 277 m_message = e.what();
Chris@1372 278 }
Chris@1739 279
Chris@1739 280 m_descriptors.clear();
Chris@1237 281 }
Chris@1237 282
Chris@1237 283 void
Chris@876 284 FeatureExtractionModelTransformer::createOutputModels(int n)
Chris@558 285 {
Chris@1739 286 auto input = ModelById::getAs<DenseTimeValueModel>(getInputModel());
Chris@1739 287 if (!input) return;
Chris@712 288
Chris@849 289 PluginRDFDescription description(m_transforms[n].getPluginIdentifier());
Chris@849 290 QString outputId = m_transforms[n].getOutput();
Chris@558 291
Chris@320 292 int binCount = 1;
Chris@320 293 float minValue = 0.0, maxValue = 0.0;
Chris@320 294 bool haveExtents = false;
Chris@1739 295 bool haveBinCount = m_descriptors[n].hasFixedBinCount;
Chris@876 296
Chris@876 297 if (haveBinCount) {
Chris@1739 298 binCount = (int)m_descriptors[n].binCount;
Chris@320 299 }
Chris@320 300
Chris@876 301 m_needAdditionalModels[n] = false;
Chris@876 302
Chris@1739 303 if (binCount > 0 && m_descriptors[n].hasKnownExtents) {
Chris@1739 304 minValue = m_descriptors[n].minValue;
Chris@1739 305 maxValue = m_descriptors[n].maxValue;
Chris@320 306 haveExtents = true;
Chris@320 307 }
Chris@320 308
Chris@1040 309 sv_samplerate_t modelRate = input->getSampleRate();
Chris@1254 310 sv_samplerate_t outputRate = modelRate;
Chris@930 311 int modelResolution = 1;
Chris@712 312
Chris@1739 313 if (m_descriptors[n].sampleType !=
Chris@785 314 Vamp::Plugin::OutputDescriptor::OneSamplePerStep) {
Chris@1254 315
Chris@1739 316 outputRate = m_descriptors[n].sampleRate;
Chris@1254 317
Chris@1254 318 //!!! SV doesn't actually support display of models that have
Chris@1254 319 //!!! different underlying rates together -- so we always set
Chris@1254 320 //!!! the model rate to be the input model's rate, and adjust
Chris@1254 321 //!!! the resolution appropriately. We can't properly display
Chris@1254 322 //!!! data with a higher resolution than the base model at all
Chris@1254 323 if (outputRate > input->getSampleRate()) {
Chris@1264 324 SVDEBUG << "WARNING: plugin reports output sample rate as "
Chris@1264 325 << outputRate
Chris@1264 326 << " (can't display features with finer resolution than the input rate of "
Chris@1264 327 << modelRate << ")" << endl;
Chris@1254 328 outputRate = modelRate;
Chris@785 329 }
Chris@785 330 }
Chris@785 331
Chris@1739 332 switch (m_descriptors[n].sampleType) {
Chris@320 333
Chris@320 334 case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
Chris@1429 335 if (outputRate != 0.0) {
Chris@1429 336 modelResolution = int(round(modelRate / outputRate));
Chris@1429 337 }
Chris@1429 338 break;
Chris@320 339
Chris@320 340 case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
Chris@1429 341 modelResolution = m_transforms[n].getStepSize();
Chris@1429 342 break;
Chris@320 343
Chris@320 344 case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
Chris@1254 345 if (outputRate <= 0.0) {
Chris@1739 346 SVDEBUG << "WARNING: Fixed sample-rate plugin reports invalid sample rate " << m_descriptors[n].sampleRate << "; defaulting to input rate of " << input->getSampleRate() << endl;
Chris@1071 347 modelResolution = 1;
Chris@451 348 } else {
Chris@1254 349 modelResolution = int(round(modelRate / outputRate));
Chris@1254 350 // cerr << "modelRate = " << modelRate << ", descriptor rate = " << outputRate << ", modelResolution = " << modelResolution << endl;
Chris@451 351 }
Chris@1429 352 break;
Chris@320 353 }
Chris@320 354
Chris@441 355 bool preDurationPlugin = (m_plugin->getVampApiVersion() < 2);
Chris@441 356
Chris@1739 357 std::shared_ptr<Model> out;
Chris@849 358
Chris@441 359 if (binCount == 0 &&
Chris@1739 360 (preDurationPlugin || !m_descriptors[n].hasDuration)) {
Chris@320 361
Chris@445 362 // Anything with no value and no duration is an instant
Chris@445 363
Chris@1881 364 SVDEBUG << "FeatureExtractionModelTransformer::createOutputModels: "
Chris@1881 365 << "creating a SparseOneDimensionalModel" << endl;
Chris@1881 366
Chris@1739 367 out = std::make_shared<SparseOneDimensionalModel>
Chris@1739 368 (modelRate, modelResolution, false);
Chris@1739 369
Chris@558 370 QString outputEventTypeURI = description.getOutputEventTypeURI(outputId);
Chris@849 371 out->setRDFTypeURI(outputEventTypeURI);
Chris@558 372
Chris@441 373 } else if ((preDurationPlugin && binCount > 1 &&
Chris@1739 374 (m_descriptors[n].sampleType ==
Chris@441 375 Vamp::Plugin::OutputDescriptor::VariableSampleRate)) ||
Chris@1739 376 (!preDurationPlugin && m_descriptors[n].hasDuration)) {
Chris@441 377
Chris@441 378 // For plugins using the old v1 API without explicit duration,
Chris@441 379 // we treat anything that has multiple bins (i.e. that has the
Chris@441 380 // potential to have value and duration) and a variable sample
Chris@441 381 // rate as a note model, taking its values as pitch, duration
Chris@441 382 // and velocity (if present) respectively. This is the same
Chris@441 383 // behaviour as always applied by SV to these plugins in the
Chris@441 384 // past.
Chris@441 385
Chris@441 386 // For plugins with the newer API, we treat anything with
Chris@441 387 // duration as either a note model with pitch and velocity, or
Chris@441 388 // a region model.
Chris@441 389
Chris@441 390 // How do we know whether it's an interval or note model?
Chris@441 391 // What's the essential difference? Is a note model any
Chris@441 392 // interval model using a Hz or "MIDI pitch" scale? There
Chris@441 393 // isn't really a reliable test for "MIDI pitch"... Does a
Chris@441 394 // note model always have velocity? This is a good question
Chris@441 395 // to be addressed by accompanying RDF, but for the moment we
Chris@441 396 // will do the following...
Chris@441 397
Chris@441 398 bool isNoteModel = false;
Chris@441 399
Chris@441 400 // Regions have only value (and duration -- we can't extract a
Chris@441 401 // region model from an old-style plugin that doesn't support
Chris@441 402 // duration)
Chris@441 403 if (binCount > 1) isNoteModel = true;
Chris@441 404
Chris@595 405 // Regions do not have units of Hz or MIDI things (a sweeping
Chris@595 406 // assumption!)
Chris@1739 407 if (m_descriptors[n].unit == "Hz" ||
Chris@1739 408 m_descriptors[n].unit.find("MIDI") != std::string::npos ||
Chris@1739 409 m_descriptors[n].unit.find("midi") != std::string::npos) {
Chris@595 410 isNoteModel = true;
Chris@595 411 }
Chris@441 412
Chris@441 413 // If we had a "sparse 3D model", we would have the additional
Chris@441 414 // problem of determining whether to use that here (if bin
Chris@441 415 // count > 1). But we don't.
Chris@441 416
Chris@859 417 QSettings settings;
Chris@859 418
Chris@1647 419 if (isNoteModel) {
Chris@441 420
Chris@1769 421 QSettings settings;
Chris@1769 422 settings.beginGroup("Transformer");
Chris@1769 423 bool flexi = settings.value("use-flexi-note-model", false).toBool();
Chris@1769 424 settings.endGroup();
Chris@1769 425
Chris@1881 426 SVDEBUG << "FeatureExtractionModelTransformer::createOutputModels: "
Chris@1881 427 << "creating a NoteModel (flexi = " << flexi << ")" << endl;
Chris@1769 428
Chris@441 429 NoteModel *model;
Chris@441 430 if (haveExtents) {
Chris@859 431 model = new NoteModel
Chris@1769 432 (modelRate, modelResolution, minValue, maxValue, false,
Chris@1769 433 flexi ? NoteModel::FLEXI_NOTE : NoteModel::NORMAL_NOTE);
Chris@441 434 } else {
Chris@859 435 model = new NoteModel
Chris@1769 436 (modelRate, modelResolution, false,
Chris@1769 437 flexi ? NoteModel::FLEXI_NOTE : NoteModel::NORMAL_NOTE);
gyorgyf@786 438 }
Chris@1739 439 model->setScaleUnits(m_descriptors[n].unit.c_str());
Chris@1739 440 out.reset(model);
gyorgyf@786 441
Chris@441 442 } else {
Chris@441 443
Chris@1881 444 SVDEBUG << "FeatureExtractionModelTransformer::createOutputModels: "
Chris@1881 445 << "creating a RegionModel" << endl;
Chris@1881 446
Chris@441 447 RegionModel *model;
Chris@441 448 if (haveExtents) {
Chris@441 449 model = new RegionModel
Chris@441 450 (modelRate, modelResolution, minValue, maxValue, false);
Chris@441 451 } else {
Chris@441 452 model = new RegionModel
Chris@441 453 (modelRate, modelResolution, false);
Chris@441 454 }
Chris@1739 455 model->setScaleUnits(m_descriptors[n].unit.c_str());
Chris@1739 456 out.reset(model);
Chris@441 457 }
Chris@441 458
Chris@558 459 QString outputEventTypeURI = description.getOutputEventTypeURI(outputId);
Chris@849 460 out->setRDFTypeURI(outputEventTypeURI);
Chris@558 461
Chris@876 462 } else if (binCount == 1 ||
Chris@1739 463 (m_descriptors[n].sampleType ==
Chris@441 464 Vamp::Plugin::OutputDescriptor::VariableSampleRate)) {
Chris@441 465
Chris@441 466 // Anything that is not a 1D, note, or interval model and that
Chris@441 467 // has only one value per result must be a sparse time value
Chris@441 468 // model.
Chris@441 469
Chris@441 470 // Anything that is not a 1D, note, or interval model and that
Chris@876 471 // has a variable sample rate is treated as a set of sparse
Chris@876 472 // time value models, one per output bin, because we lack a
Chris@441 473 // sparse 3D model.
Chris@320 474
Chris@876 475 // Anything that is not a 1D, note, or interval model and that
Chris@876 476 // has a fixed sample rate but an unknown number of values per
Chris@876 477 // result is also treated as a set of sparse time value models.
Chris@876 478
Chris@876 479 // For sets of sparse time value models, we create a single
Chris@876 480 // model first as the "standard" output and then create models
Chris@876 481 // for bins 1+ in the additional model map (mapping the output
Chris@876 482 // descriptor to a list of models indexed by bin-1). But we
Chris@876 483 // don't create the additional models yet, as this case has to
Chris@876 484 // work even if the number of bins is unknown at this point --
Chris@877 485 // we create an additional model (copying its parameters from
Chris@877 486 // the default one) each time a new bin is encountered.
Chris@876 487
Chris@876 488 if (!haveBinCount || binCount > 1) {
Chris@876 489 m_needAdditionalModels[n] = true;
Chris@876 490 }
Chris@876 491
Chris@1881 492 SVDEBUG << "FeatureExtractionModelTransformer::createOutputModels: "
Chris@1881 493 << "creating a SparseTimeValueModel "
Chris@1881 494 << "(additional models to come? -> "
Chris@1881 495 << m_needAdditionalModels[n] << ")" << endl;
Chris@1881 496
Chris@320 497 SparseTimeValueModel *model;
Chris@320 498 if (haveExtents) {
Chris@320 499 model = new SparseTimeValueModel
Chris@320 500 (modelRate, modelResolution, minValue, maxValue, false);
Chris@320 501 } else {
Chris@320 502 model = new SparseTimeValueModel
Chris@320 503 (modelRate, modelResolution, false);
Chris@320 504 }
Chris@558 505
Chris@558 506 Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors();
Chris@849 507 model->setScaleUnits(outputs[m_outputNos[n]].unit.c_str());
Chris@320 508
Chris@1739 509 out.reset(model);
Chris@320 510
Chris@558 511 QString outputEventTypeURI = description.getOutputEventTypeURI(outputId);
Chris@849 512 out->setRDFTypeURI(outputEventTypeURI);
Chris@558 513
Chris@441 514 } else {
Chris@320 515
Chris@441 516 // Anything that is not a 1D, note, or interval model and that
Chris@441 517 // has a fixed sample rate and more than one value per result
Chris@441 518 // must be a dense 3D model.
Chris@320 519
Chris@1881 520 SVDEBUG << "FeatureExtractionModelTransformer::createOutputModels: "
Chris@1881 521 << "creating a BasicCompressedDenseThreeDimensionalModel"
Chris@1881 522 << endl;
Chris@1881 523
Chris@1777 524 auto model =
Chris@1777 525 new BasicCompressedDenseThreeDimensionalModel
Chris@1777 526 (modelRate, modelResolution, binCount, false);
Chris@320 527
Chris@1739 528 if (!m_descriptors[n].binNames.empty()) {
Chris@1429 529 std::vector<QString> names;
Chris@1739 530 for (int i = 0; i < (int)m_descriptors[n].binNames.size(); ++i) {
Chris@1739 531 names.push_back(m_descriptors[n].binNames[i].c_str());
Chris@1429 532 }
Chris@1429 533 model->setBinNames(names);
Chris@1429 534 }
Chris@320 535
Chris@1739 536 out.reset(model);
Chris@558 537
Chris@558 538 QString outputSignalTypeURI = description.getOutputSignalTypeURI(outputId);
Chris@849 539 out->setRDFTypeURI(outputSignalTypeURI);
Chris@320 540 }
Chris@333 541
Chris@849 542 if (out) {
Chris@1739 543 out->setSourceModel(getInputModel());
Chris@1752 544 m_outputs.push_back(ModelById::add(out));
Chris@849 545 }
Chris@320 546 }
Chris@320 547
Chris@1211 548 void
Chris@1211 549 FeatureExtractionModelTransformer::awaitOutputModels()
Chris@1211 550 {
Chris@1211 551 m_outputMutex.lock();
Chris@1368 552 while (!m_haveOutputs && !m_abandoned) {
Chris@1368 553 m_outputsCondition.wait(&m_outputMutex, 500);
Chris@1211 554 }
Chris@1211 555 m_outputMutex.unlock();
Chris@1211 556 }
Chris@1211 557
Chris@331 558 FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()
Chris@320 559 {
Chris@1237 560 // Parent class dtor set the abandoned flag and waited for the run
Chris@1237 561 // thread to exit; the run thread owns the plugin, and should have
Chris@1237 562 // destroyed it before exiting (via a call to deinitialise)
Chris@320 563 }
Chris@320 564
Chris@876 565 FeatureExtractionModelTransformer::Models
Chris@876 566 FeatureExtractionModelTransformer::getAdditionalOutputModels()
Chris@876 567 {
Chris@876 568 Models mm;
Chris@1739 569 for (auto mp : m_additionalModels) {
Chris@1739 570 for (auto m: mp.second) {
Chris@1739 571 mm.push_back(m.second);
Chris@876 572 }
Chris@876 573 }
Chris@876 574 return mm;
Chris@876 575 }
Chris@876 576
Chris@877 577 bool
Chris@877 578 FeatureExtractionModelTransformer::willHaveAdditionalOutputModels()
Chris@877 579 {
Chris@1739 580 for (auto p : m_needAdditionalModels) {
Chris@1739 581 if (p.second) return true;
Chris@877 582 }
Chris@877 583 return false;
Chris@877 584 }
Chris@877 585
Chris@1739 586 ModelId
Chris@876 587 FeatureExtractionModelTransformer::getAdditionalModel(int n, int binNo)
Chris@876 588 {
Chris@876 589 if (binNo == 0) {
Chris@1739 590 SVCERR << "Internal error: binNo == 0 in getAdditionalModel (should be using primary model, not calling getAdditionalModel)" << endl;
Chris@1739 591 return {};
Chris@876 592 }
Chris@876 593
Chris@1739 594 if (!in_range_for(m_outputs, n)) {
Chris@1739 595 SVCERR << "getAdditionalModel: Output " << n << " out of range" << endl;
Chris@1739 596 return {};
Chris@1739 597 }
Chris@876 598
Chris@1739 599 if (!in_range_for(m_needAdditionalModels, n) ||
Chris@1739 600 !m_needAdditionalModels[n]) {
Chris@1739 601 return {};
Chris@1739 602 }
Chris@1739 603
Chris@1739 604 if (!m_additionalModels[n][binNo].isNone()) {
Chris@1739 605 return m_additionalModels[n][binNo];
Chris@1739 606 }
Chris@876 607
Chris@1739 608 SVDEBUG << "getAdditionalModel(" << n << ", " << binNo
Chris@1739 609 << "): creating" << endl;
Chris@876 610
Chris@1739 611 auto baseModel = ModelById::getAs<SparseTimeValueModel>(m_outputs[n]);
Chris@1739 612 if (!baseModel) {
Chris@1739 613 SVCERR << "getAdditionalModel: Output model not conformable, or has vanished" << endl;
Chris@1739 614 return {};
Chris@1739 615 }
Chris@1739 616
Chris@1739 617 SVDEBUG << "getAdditionalModel(" << n << ", " << binNo
Chris@1739 618 << "): (from " << baseModel << ")" << endl;
Chris@876 619
Chris@876 620 SparseTimeValueModel *additional =
Chris@876 621 new SparseTimeValueModel(baseModel->getSampleRate(),
Chris@876 622 baseModel->getResolution(),
Chris@876 623 baseModel->getValueMinimum(),
Chris@876 624 baseModel->getValueMaximum(),
Chris@876 625 false);
Chris@876 626
Chris@876 627 additional->setScaleUnits(baseModel->getScaleUnits());
Chris@876 628 additional->setRDFTypeURI(baseModel->getRDFTypeURI());
Chris@876 629
Chris@1752 630 ModelId additionalId = ModelById::add
Chris@1752 631 (std::shared_ptr<SparseTimeValueModel>(additional));
Chris@1739 632 m_additionalModels[n][binNo] = additionalId;
Chris@1739 633 return additionalId;
Chris@320 634 }
Chris@320 635
Chris@320 636 void
Chris@331 637 FeatureExtractionModelTransformer::run()
Chris@320 638 {
Chris@1373 639 try {
Chris@1373 640 if (!initialise()) {
Chris@1373 641 abandon();
Chris@1373 642 return;
Chris@1373 643 }
Chris@1373 644 } catch (const std::exception &e) {
Chris@1368 645 abandon();
Chris@1373 646 m_message = e.what();
Chris@1368 647 return;
Chris@1368 648 }
Chris@320 649
Chris@1368 650 if (m_outputs.empty()) {
Chris@1368 651 abandon();
Chris@1368 652 return;
Chris@1368 653 }
Chris@320 654
Chris@850 655 Transform primaryTransform = m_transforms[0];
Chris@850 656
Chris@1775 657 ModelId inputId = getInputModel();
Chris@1775 658
Chris@1739 659 bool ready = false;
Chris@1739 660 while (!ready && !m_abandoned) {
Chris@1739 661 { // scope so as to release input shared_ptr before sleeping
Chris@1775 662 auto input = ModelById::getAs<DenseTimeValueModel>(inputId);
Chris@1784 663 if (!input || !input->isOK()) {
Chris@1739 664 abandon();
Chris@1739 665 return;
Chris@1739 666 }
Chris@1739 667 ready = input->isReady();
Chris@1739 668 }
Chris@1739 669 if (!ready) {
Chris@1775 670 SVDEBUG << "FeatureExtractionModelTransformer::run: Waiting for input model "
Chris@1775 671 << inputId << " to be ready..." << endl;
Chris@1739 672 usleep(500000);
Chris@1739 673 }
Chris@320 674 }
Chris@497 675 if (m_abandoned) return;
Chris@320 676
Chris@1775 677 #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
Chris@1775 678 SVDEBUG << "FeatureExtractionModelTransformer::run: Input model "
Chris@1775 679 << inputId << " is ready, going ahead" << endl;
Chris@1775 680 #endif
Chris@1739 681
Chris@1755 682 sv_samplerate_t sampleRate;
Chris@1755 683 int channelCount;
Chris@1755 684 sv_frame_t startFrame;
Chris@1755 685 sv_frame_t endFrame;
Chris@1755 686
Chris@1755 687 { // scope so as not to have this borrowed pointer retained around
Chris@1755 688 // the edges of the process loop
Chris@1755 689 auto input = ModelById::getAs<DenseTimeValueModel>(inputId);
Chris@1755 690 if (!input) {
Chris@1755 691 abandon();
Chris@1755 692 return;
Chris@1755 693 }
Chris@320 694
Chris@1755 695 sampleRate = input->getSampleRate();
Chris@1755 696
Chris@1755 697 channelCount = input->getChannelCount();
Chris@1755 698 if ((int)m_plugin->getMaxChannelCount() < channelCount) {
Chris@1755 699 channelCount = 1;
Chris@1755 700 }
Chris@1755 701
Chris@1755 702 startFrame = input->getStartFrame();
Chris@1755 703 endFrame = input->getEndFrame();
Chris@320 704 }
Chris@320 705
Chris@320 706 float **buffers = new float*[channelCount];
Chris@930 707 for (int ch = 0; ch < channelCount; ++ch) {
Chris@1429 708 buffers[ch] = new float[primaryTransform.getBlockSize() + 2];
Chris@320 709 }
Chris@320 710
Chris@930 711 int stepSize = primaryTransform.getStepSize();
Chris@930 712 int blockSize = primaryTransform.getBlockSize();
Chris@350 713
Chris@320 714 bool frequencyDomain = (m_plugin->getInputDomain() ==
Chris@320 715 Vamp::Plugin::FrequencyDomain);
Chris@1558 716
Chris@320 717 std::vector<FFTModel *> fftModels;
Chris@320 718
Chris@320 719 if (frequencyDomain) {
Chris@1775 720 #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
Chris@1775 721 SVDEBUG << "FeatureExtractionModelTransformer::run: Input is frequency-domain" << endl;
Chris@1775 722 #endif
Chris@930 723 for (int ch = 0; ch < channelCount; ++ch) {
Chris@320 724 FFTModel *model = new FFTModel
Chris@1752 725 (inputId,
Chris@1739 726 channelCount == 1 ? m_input.getChannel() : ch,
Chris@1739 727 primaryTransform.getWindowType(),
Chris@1739 728 blockSize,
Chris@1739 729 stepSize,
Chris@1739 730 blockSize);
Chris@1080 731 if (!model->isOK() || model->getError() != "") {
Chris@1080 732 QString err = model->getError();
Chris@320 733 delete model;
Chris@1739 734 for (int j = 0; in_range_for(m_outputNos, j); ++j) {
Chris@850 735 setCompletion(j, 100);
Chris@850 736 }
Chris@1775 737 SVDEBUG << "FeatureExtractionModelTransformer::run: Failed to create FFT model for input model " << inputId << ": " << err << endl;
Chris@1784 738 m_message = "Failed to create the FFT model for this feature extraction model transformer: error is: " + err;
Chris@1784 739 for (int cch = 0; cch < ch; ++cch) {
Chris@1784 740 delete fftModels[cch];
Chris@1784 741 }
Chris@1784 742 abandon();
Chris@1784 743 return;
Chris@320 744 }
Chris@320 745 fftModels.push_back(model);
Chris@320 746 }
Chris@1775 747 #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
Chris@1775 748 SVDEBUG << "FeatureExtractionModelTransformer::run: Created FFT model(s) for frequency-domain input" << endl;
Chris@1775 749 #endif
Chris@320 750 }
Chris@320 751
Chris@850 752 RealTime contextStartRT = primaryTransform.getStartTime();
Chris@850 753 RealTime contextDurationRT = primaryTransform.getDuration();
Chris@350 754
Chris@1040 755 sv_frame_t contextStart =
Chris@350 756 RealTime::realTime2Frame(contextStartRT, sampleRate);
Chris@350 757
Chris@1040 758 sv_frame_t contextDuration =
Chris@350 759 RealTime::realTime2Frame(contextDurationRT, sampleRate);
Chris@320 760
Chris@320 761 if (contextStart == 0 || contextStart < startFrame) {
Chris@320 762 contextStart = startFrame;
Chris@320 763 }
Chris@320 764
Chris@320 765 if (contextDuration == 0) {
Chris@320 766 contextDuration = endFrame - contextStart;
Chris@320 767 }
Chris@320 768 if (contextStart + contextDuration > endFrame) {
Chris@320 769 contextDuration = endFrame - contextStart;
Chris@320 770 }
Chris@320 771
Chris@1039 772 sv_frame_t blockFrame = contextStart;
Chris@320 773
Chris@320 774 long prevCompletion = 0;
Chris@320 775
Chris@1739 776 for (int j = 0; in_range_for(m_outputNos, j); ++j) {
Chris@850 777 setCompletion(j, 0);
Chris@850 778 }
Chris@320 779
Chris@1582 780 float *reals = nullptr;
Chris@1582 781 float *imaginaries = nullptr;
Chris@556 782 if (frequencyDomain) {
Chris@556 783 reals = new float[blockSize/2 + 1];
Chris@556 784 imaginaries = new float[blockSize/2 + 1];
Chris@556 785 }
Chris@556 786
Chris@678 787 QString error = "";
Chris@678 788
Chris@1372 789 try {
Chris@1372 790 while (!m_abandoned) {
Chris@320 791
Chris@1372 792 if (frequencyDomain) {
Chris@1372 793 if (blockFrame - int(blockSize)/2 >
Chris@1739 794 contextStart + contextDuration) {
Chris@1739 795 break;
Chris@1739 796 }
Chris@1372 797 } else {
Chris@1372 798 if (blockFrame >=
Chris@1739 799 contextStart + contextDuration) {
Chris@1739 800 break;
Chris@1739 801 }
Chris@1372 802 }
Chris@320 803
Chris@1558 804 #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
Chris@1558 805 SVDEBUG << "FeatureExtractionModelTransformer::run: blockFrame "
Chris@1558 806 << blockFrame << ", endFrame " << endFrame << ", blockSize "
Chris@1558 807 << blockSize << endl;
Chris@1558 808 #endif
Chris@1558 809
Chris@1372 810 int completion = int
Chris@1372 811 ((((blockFrame - contextStart) / stepSize) * 99) /
Chris@1372 812 (contextDuration / stepSize + 1));
Chris@320 813
Chris@1762 814 bool haveAllModels = true;
Chris@1755 815 if (!ModelById::get(inputId)) {
Chris@1762 816 #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
Chris@1762 817 SVDEBUG << "FeatureExtractionModelTransformer::run: Input model " << inputId << " no longer exists" << endl;
Chris@1762 818 #endif
Chris@1762 819 haveAllModels = false;
Chris@1762 820 } else {
Chris@1763 821 #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
Chris@1762 822 SVDEBUG << "Input model " << inputId << " still exists" << endl;
Chris@1763 823 #endif
Chris@1762 824 }
Chris@1762 825 for (auto mid: m_outputs) {
Chris@1762 826 if (!ModelById::get(mid)) {
Chris@1762 827 #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
Chris@1762 828 SVDEBUG << "FeatureExtractionModelTransformer::run: Output model " << mid << " no longer exists" << endl;
Chris@1762 829 #endif
Chris@1762 830 haveAllModels = false;
Chris@1762 831 } else {
Chris@1762 832 #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
Chris@1762 833 SVDEBUG << "Output model " << mid << " still exists" << endl;
Chris@1762 834 #endif
Chris@1762 835 }
Chris@1762 836 }
Chris@1762 837 if (!haveAllModels) {
Chris@1755 838 abandon();
Chris@1762 839 break;
Chris@1755 840 }
Chris@1762 841
Chris@1762 842 #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
Chris@1762 843 SVDEBUG << "FeatureExtractionModelTransformer::run: All models still exist" << endl;
Chris@1762 844 #endif
Chris@1755 845
Chris@1739 846 // channelCount is either input->channelCount or 1
Chris@320 847
Chris@1372 848 if (frequencyDomain) {
Chris@1372 849 for (int ch = 0; ch < channelCount; ++ch) {
Chris@1372 850 int column = int((blockFrame - startFrame) / stepSize);
Chris@1372 851 if (fftModels[ch]->getValuesAt(column, reals, imaginaries)) {
Chris@1372 852 for (int i = 0; i <= blockSize/2; ++i) {
Chris@1372 853 buffers[ch][i*2] = reals[i];
Chris@1372 854 buffers[ch][i*2+1] = imaginaries[i];
Chris@1372 855 }
Chris@1372 856 } else {
Chris@1372 857 for (int i = 0; i <= blockSize/2; ++i) {
Chris@1372 858 buffers[ch][i*2] = 0.f;
Chris@1372 859 buffers[ch][i*2+1] = 0.f;
Chris@1372 860 }
Chris@1558 861 }
Chris@1558 862
Chris@1372 863 error = fftModels[ch]->getError();
Chris@1372 864 if (error != "") {
Chris@1372 865 SVCERR << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << endl;
Chris@1372 866 m_abandoned = true;
Chris@1372 867 m_message = error;
Chris@1372 868 break;
Chris@1008 869 }
Chris@1372 870 }
Chris@1372 871 } else {
Chris@1372 872 getFrames(channelCount, blockFrame, blockSize, buffers);
Chris@1372 873 }
Chris@1372 874
Chris@1372 875 if (m_abandoned) break;
Chris@1372 876
Chris@1740 877 auto features = m_plugin->process
Chris@1558 878 (buffers,
Chris@1558 879 RealTime::frame2RealTime(blockFrame, sampleRate)
Chris@1558 880 .toVampRealTime());
Chris@1558 881
Chris@1372 882 if (m_abandoned) break;
Chris@1372 883
Chris@1740 884 for (int j = 0; in_range_for(m_outputNos, j); ++j) {
Chris@1740 885 for (int fi = 0; in_range_for(features[m_outputNos[j]], fi); ++fi) {
Chris@1740 886 auto feature = features[m_outputNos[j]][fi];
Chris@1372 887 addFeature(j, blockFrame, feature);
Chris@678 888 }
Chris@363 889 }
Chris@1372 890
Chris@1372 891 if (blockFrame == contextStart || completion > prevCompletion) {
Chris@1740 892 for (int j = 0; in_range_for(m_outputNos, j); ++j) {
Chris@1372 893 setCompletion(j, completion);
Chris@1372 894 }
Chris@1372 895 prevCompletion = completion;
Chris@1372 896 }
Chris@1372 897
Chris@1372 898 blockFrame += stepSize;
Chris@1372 899
Chris@320 900 }
Chris@320 901
Chris@1372 902 if (!m_abandoned) {
Chris@1740 903 auto features = m_plugin->getRemainingFeatures();
Chris@497 904
Chris@1740 905 for (int j = 0; in_range_for(m_outputNos, j); ++j) {
Chris@1740 906 for (int fi = 0; in_range_for(features[m_outputNos[j]], fi); ++fi) {
Chris@1740 907 auto feature = features[m_outputNos[j]][fi];
Chris@1372 908 addFeature(j, blockFrame, feature);
Chris@1762 909 if (m_abandoned) {
Chris@1762 910 break;
Chris@1762 911 }
Chris@1372 912 }
Chris@850 913 }
Chris@850 914 }
Chris@1372 915 } catch (const std::exception &e) {
Chris@1372 916 SVCERR << "FeatureExtractionModelTransformer::run: Exception caught: "
Chris@1372 917 << e.what() << endl;
Chris@1372 918 m_abandoned = true;
Chris@1372 919 m_message = e.what();
Chris@497 920 }
Chris@320 921
Chris@850 922 for (int j = 0; j < (int)m_outputNos.size(); ++j) {
Chris@850 923 setCompletion(j, 100);
Chris@850 924 }
Chris@320 925
Chris@320 926 if (frequencyDomain) {
Chris@930 927 for (int ch = 0; ch < channelCount; ++ch) {
Chris@320 928 delete fftModels[ch];
Chris@320 929 }
Chris@556 930 delete[] reals;
Chris@556 931 delete[] imaginaries;
Chris@320 932 }
Chris@974 933
Chris@974 934 for (int ch = 0; ch < channelCount; ++ch) {
Chris@974 935 delete[] buffers[ch];
Chris@974 936 }
Chris@974 937 delete[] buffers;
Chris@1237 938
Chris@1237 939 deinitialise();
Chris@320 940 }
Chris@320 941
Chris@320 942 void
Chris@363 943 FeatureExtractionModelTransformer::getFrames(int channelCount,
Chris@1039 944 sv_frame_t startFrame,
Chris@1039 945 sv_frame_t size,
Chris@363 946 float **buffers)
Chris@320 947 {
Chris@1039 948 sv_frame_t offset = 0;
Chris@320 949
Chris@320 950 if (startFrame < 0) {
Chris@363 951 for (int c = 0; c < channelCount; ++c) {
Chris@1039 952 for (sv_frame_t i = 0; i < size && startFrame + i < 0; ++i) {
Chris@363 953 buffers[c][i] = 0.0f;
Chris@363 954 }
Chris@320 955 }
Chris@320 956 offset = -startFrame;
Chris@320 957 size -= offset;
Chris@320 958 if (size <= 0) return;
Chris@320 959 startFrame = 0;
Chris@320 960 }
Chris@320 961
Chris@1739 962 auto input = ModelById::getAs<DenseTimeValueModel>(getInputModel());
Chris@1739 963 if (!input) {
Chris@1739 964 return;
Chris@1739 965 }
Chris@363 966
Chris@1039 967 sv_frame_t got = 0;
Chris@350 968
Chris@363 969 if (channelCount == 1) {
Chris@363 970
Chris@1096 971 auto data = input->getData(m_input.getChannel(), startFrame, size);
Chris@1096 972 got = data.size();
Chris@1096 973
Chris@1096 974 copy(data.begin(), data.end(), buffers[0] + offset);
Chris@363 975
Chris@363 976 if (m_input.getChannel() == -1 && input->getChannelCount() > 1) {
Chris@363 977 // use mean instead of sum, as plugin input
Chris@363 978 float cc = float(input->getChannelCount());
Chris@1096 979 for (sv_frame_t i = 0; i < got; ++i) {
Chris@363 980 buffers[0][i + offset] /= cc;
Chris@363 981 }
Chris@363 982 }
Chris@363 983
Chris@363 984 } else {
Chris@363 985
Chris@1096 986 auto data = input->getMultiChannelData(0, channelCount-1, startFrame, size);
Chris@1096 987 if (!data.empty()) {
Chris@1096 988 got = data[0].size();
Chris@1096 989 for (int c = 0; in_range_for(data, c); ++c) {
Chris@1096 990 copy(data[c].begin(), data[c].end(), buffers[c] + offset);
Chris@363 991 }
Chris@363 992 }
Chris@363 993 }
Chris@320 994
Chris@320 995 while (got < size) {
Chris@363 996 for (int c = 0; c < channelCount; ++c) {
Chris@363 997 buffers[c][got + offset] = 0.0;
Chris@363 998 }
Chris@320 999 ++got;
Chris@320 1000 }
Chris@320 1001 }
Chris@320 1002
Chris@320 1003 void
Chris@850 1004 FeatureExtractionModelTransformer::addFeature(int n,
Chris@1039 1005 sv_frame_t blockFrame,
Chris@850 1006 const Vamp::Plugin::Feature &feature)
Chris@320 1007 {
Chris@1739 1008 auto input = ModelById::get(getInputModel());
Chris@1739 1009 if (!input) return;
Chris@1739 1010
Chris@1739 1011 sv_samplerate_t inputRate = input->getSampleRate();
Chris@320 1012
Chris@843 1013 // cerr << "FeatureExtractionModelTransformer::addFeature: blockFrame = "
Chris@712 1014 // << blockFrame << ", hasTimestamp = " << feature.hasTimestamp
Chris@712 1015 // << ", timestamp = " << feature.timestamp << ", hasDuration = "
Chris@712 1016 // << feature.hasDuration << ", duration = " << feature.duration
Chris@843 1017 // << endl;
Chris@320 1018
Chris@1039 1019 sv_frame_t frame = blockFrame;
Chris@320 1020
Chris@1739 1021 if (m_descriptors[n].sampleType ==
Chris@1429 1022 Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
Chris@320 1023
Chris@1429 1024 if (!feature.hasTimestamp) {
Chris@1429 1025 SVDEBUG
Chris@1429 1026 << "WARNING: FeatureExtractionModelTransformer::addFeature: "
Chris@1429 1027 << "Feature has variable sample rate but no timestamp!"
Chris@1429 1028 << endl;
Chris@1429 1029 return;
Chris@1429 1030 } else {
Chris@1429 1031 frame = RealTime::realTime2Frame(feature.timestamp, inputRate);
Chris@1429 1032 }
Chris@320 1033
Chris@1071 1034 // cerr << "variable sample rate: timestamp = " << feature.timestamp
Chris@1071 1035 // << " at input rate " << inputRate << " -> " << frame << endl;
Chris@1071 1036
Chris@1739 1037 } else if (m_descriptors[n].sampleType ==
Chris@1429 1038 Vamp::Plugin::OutputDescriptor::FixedSampleRate) {
Chris@320 1039
Chris@1739 1040 sv_samplerate_t rate = m_descriptors[n].sampleRate;
Chris@1071 1041 if (rate <= 0.0) {
Chris@1071 1042 rate = inputRate;
Chris@1071 1043 }
Chris@1071 1044
Chris@779 1045 if (!feature.hasTimestamp) {
Chris@849 1046 ++m_fixedRateFeatureNos[n];
Chris@779 1047 } else {
Chris@779 1048 RealTime ts(feature.timestamp.sec, feature.timestamp.nsec);
Chris@1071 1049 m_fixedRateFeatureNos[n] = (int)lrint(ts.toDouble() * rate);
Chris@779 1050 }
Chris@862 1051
Chris@1071 1052 // cerr << "m_fixedRateFeatureNo = " << m_fixedRateFeatureNos[n]
Chris@1739 1053 // << ", m_descriptor->sampleRate = " << m_descriptors[n].sampleRate
Chris@862 1054 // << ", inputRate = " << inputRate
Chris@862 1055 // << " giving frame = ";
Chris@1071 1056 frame = lrint((double(m_fixedRateFeatureNos[n]) / rate) * inputRate);
Chris@1071 1057 // cerr << frame << endl;
Chris@320 1058 }
Chris@862 1059
Chris@862 1060 if (frame < 0) {
Chris@1264 1061 SVDEBUG
Chris@862 1062 << "WARNING: FeatureExtractionModelTransformer::addFeature: "
Chris@862 1063 << "Negative frame counts are not supported (frame = " << frame
Chris@862 1064 << " from timestamp " << feature.timestamp
Chris@862 1065 << "), dropping feature"
Chris@862 1066 << endl;
Chris@862 1067 return;
Chris@862 1068 }
Chris@862 1069
Chris@441 1070 // Rather than repeat the complicated tests from the constructor
Chris@441 1071 // to determine what sort of model we must be adding the features
Chris@441 1072 // to, we instead test what sort of model the constructor decided
Chris@441 1073 // to create.
Chris@320 1074
Chris@1739 1075 ModelId outputId = m_outputs[n];
Chris@1740 1076
Chris@1740 1077 if (isOutputType<SparseOneDimensionalModel>(n)) {
Chris@1740 1078
Chris@1740 1079 auto model = ModelById::getAs<SparseOneDimensionalModel>(outputId);
Chris@1740 1080 if (!model) return;
Chris@1740 1081 model->add(Event(frame, feature.label.c_str()));
Chris@1740 1082
Chris@1740 1083 } else if (isOutputType<SparseTimeValueModel>(n)) {
Chris@1740 1084
Chris@1740 1085 auto model = ModelById::getAs<SparseTimeValueModel>(outputId);
Chris@1740 1086 if (!model) return;
Chris@1740 1087
Chris@1740 1088 for (int i = 0; in_range_for(feature.values, i); ++i) {
Chris@1740 1089
Chris@1740 1090 float value = feature.values[i];
Chris@1740 1091
Chris@1740 1092 QString label = feature.label.c_str();
Chris@1740 1093 if (feature.values.size() > 1) {
Chris@1740 1094 label = QString("[%1] %2").arg(i+1).arg(label);
Chris@1740 1095 }
Chris@1740 1096
Chris@1740 1097 auto targetModel = model;
Chris@1740 1098
Chris@1740 1099 if (m_needAdditionalModels[n] && i > 0) {
Chris@1740 1100 targetModel = ModelById::getAs<SparseTimeValueModel>
Chris@1740 1101 (getAdditionalModel(n, i));
Chris@1740 1102 if (!targetModel) targetModel = model;
Chris@1740 1103 }
Chris@1740 1104
Chris@1740 1105 targetModel->add(Event(frame, value, label));
Chris@1740 1106 }
Chris@1740 1107
Chris@1740 1108 } else if (isOutputType<NoteModel>(n) || isOutputType<RegionModel>(n)) {
Chris@1739 1109
Chris@1740 1110 int index = 0;
Chris@1740 1111
Chris@1740 1112 float value = 0.0;
Chris@1740 1113 if ((int)feature.values.size() > index) {
Chris@1740 1114 value = feature.values[index++];
Chris@1739 1115 }
Chris@441 1116
Chris@1740 1117 sv_frame_t duration = 1;
Chris@1740 1118 if (feature.hasDuration) {
Chris@1740 1119 duration = RealTime::realTime2Frame(feature.duration, inputRate);
Chris@1740 1120 } else {
Chris@1740 1121 if (in_range_for(feature.values, index)) {
Chris@1740 1122 duration = lrintf(feature.values[index++]);
Chris@1739 1123 }
Chris@1739 1124 }
Chris@1739 1125
Chris@1740 1126 auto noteModel = ModelById::getAs<NoteModel>(outputId);
Chris@1740 1127 if (noteModel) {
Chris@1739 1128
Chris@1740 1129 float velocity = 100;
Chris@1739 1130 if ((int)feature.values.size() > index) {
Chris@1740 1131 velocity = feature.values[index++];
Chris@454 1132 }
Chris@1740 1133 if (velocity < 0) velocity = 127;
Chris@1740 1134 if (velocity > 127) velocity = 127;
Chris@1740 1135
Chris@1740 1136 noteModel->add(Event(frame, value, // value is pitch
Chris@1740 1137 duration,
Chris@1740 1138 velocity / 127.f,
Chris@1740 1139 feature.label.c_str()));
Chris@1740 1140 }
Chris@454 1141
Chris@1740 1142 auto regionModel = ModelById::getAs<RegionModel>(outputId);
Chris@1740 1143 if (regionModel) {
Chris@1740 1144
Chris@1740 1145 if (feature.hasDuration && !feature.values.empty()) {
Chris@1740 1146
Chris@1740 1147 for (int i = 0; in_range_for(feature.values, i); ++i) {
Chris@1740 1148
Chris@1740 1149 float value = feature.values[i];
Chris@1740 1150
Chris@1740 1151 QString label = feature.label.c_str();
Chris@1740 1152 if (feature.values.size() > 1) {
Chris@1740 1153 label = QString("[%1] %2").arg(i+1).arg(label);
Chris@1739 1154 }
Chris@1740 1155
Chris@1739 1156 regionModel->add(Event(frame,
Chris@1739 1157 value,
Chris@1739 1158 duration,
Chris@1740 1159 label));
Chris@1739 1160 }
Chris@1740 1161 } else {
Chris@1740 1162
Chris@1740 1163 regionModel->add(Event(frame,
Chris@1740 1164 value,
Chris@1740 1165 duration,
Chris@1740 1166 feature.label.c_str()));
Chris@441 1167 }
Chris@441 1168 }
Chris@1740 1169
Chris@1777 1170 } else if (isOutputType<BasicCompressedDenseThreeDimensionalModel>(n)) {
Chris@1740 1171
Chris@1740 1172 auto model = ModelById::getAs
Chris@1777 1173 <BasicCompressedDenseThreeDimensionalModel>(outputId);
Chris@1740 1174 if (!model) return;
Chris@1740 1175
Chris@1837 1176 DenseThreeDimensionalModel::Column values;
Chris@1837 1177 values.insert(values.begin(),
Chris@1837 1178 feature.values.begin(), feature.values.end());
Chris@1740 1179
Chris@1740 1180 if (!feature.hasTimestamp && m_fixedRateFeatureNos[n] >= 0) {
Chris@1740 1181 model->setColumn(m_fixedRateFeatureNos[n], values);
Chris@1740 1182 } else {
Chris@1740 1183 model->setColumn(int(frame / model->getResolution()), values);
Chris@1740 1184 }
Chris@1762 1185 } else {
Chris@1762 1186
Chris@1762 1187 SVDEBUG << "FeatureExtractionModelTransformer::addFeature: Unknown output model type - possibly a deleted model" << endl;
Chris@1762 1188 abandon();
Chris@1739 1189 }
Chris@320 1190 }
Chris@320 1191
Chris@320 1192 void
Chris@850 1193 FeatureExtractionModelTransformer::setCompletion(int n, int completion)
Chris@320 1194 {
Chris@1558 1195 #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
Chris@1558 1196 SVDEBUG << "FeatureExtractionModelTransformer::setCompletion("
Chris@1558 1197 << completion << ")" << endl;
Chris@1558 1198 #endif
Chris@320 1199
Chris@1740 1200 (void)
Chris@1740 1201 (setOutputCompletion<SparseOneDimensionalModel>(n, completion) ||
Chris@1740 1202 setOutputCompletion<SparseTimeValueModel>(n, completion) ||
Chris@1740 1203 setOutputCompletion<NoteModel>(n, completion) ||
Chris@1740 1204 setOutputCompletion<RegionModel>(n, completion) ||
Chris@1777 1205 setOutputCompletion<BasicCompressedDenseThreeDimensionalModel>(n, completion));
Chris@320 1206 }
Chris@320 1207