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