annotate transform/ModelTransformerFactory.cpp @ 472:5015e80cc919

* Add more info button to plugin dialog
author Chris Cannam
date Tue, 28 Oct 2008 12:39:53 +0000
parents cff476cfce77
children a70dcfed59c1
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 "ModelTransformerFactory.h"
Chris@320 17
Chris@331 18 #include "FeatureExtractionModelTransformer.h"
Chris@331 19 #include "RealTimeEffectModelTransformer.h"
Chris@320 20
Chris@332 21 #include "TransformFactory.h"
Chris@332 22
Chris@389 23 #include "base/AudioPlaySource.h"
Chris@389 24
Chris@320 25 #include "plugin/FeatureExtractionPluginFactory.h"
Chris@320 26 #include "plugin/RealTimePluginFactory.h"
Chris@320 27 #include "plugin/PluginXml.h"
Chris@320 28
Chris@320 29 #include "widgets/PluginParameterDialog.h"
Chris@320 30
Chris@320 31 #include "data/model/DenseTimeValueModel.h"
Chris@320 32
Chris@320 33 #include "vamp-sdk/PluginHostAdapter.h"
Chris@320 34
Chris@320 35 #include <iostream>
Chris@320 36 #include <set>
Chris@320 37
Chris@320 38 #include <QRegExp>
Chris@320 39
Chris@331 40 ModelTransformerFactory *
Chris@331 41 ModelTransformerFactory::m_instance = new ModelTransformerFactory;
Chris@320 42
Chris@331 43 ModelTransformerFactory *
Chris@331 44 ModelTransformerFactory::getInstance()
Chris@320 45 {
Chris@320 46 return m_instance;
Chris@320 47 }
Chris@320 48
Chris@331 49 ModelTransformerFactory::~ModelTransformerFactory()
Chris@320 50 {
Chris@320 51 }
Chris@320 52
Chris@320 53 bool
Chris@350 54 ModelTransformerFactory::getChannelRange(TransformId identifier,
Chris@350 55 Vamp::PluginBase *plugin,
Chris@332 56 int &minChannels, int &maxChannels)
Chris@320 57 {
Chris@320 58 Vamp::Plugin *vp = 0;
Chris@320 59 if ((vp = dynamic_cast<Vamp::Plugin *>(plugin)) ||
Chris@320 60 (vp = dynamic_cast<Vamp::PluginHostAdapter *>(plugin))) {
Chris@320 61 minChannels = vp->getMinChannelCount();
Chris@320 62 maxChannels = vp->getMaxChannelCount();
Chris@320 63 return true;
Chris@320 64 } else {
Chris@332 65 return TransformFactory::getInstance()->
Chris@332 66 getTransformChannelRange(identifier, minChannels, maxChannels);
Chris@320 67 }
Chris@320 68 }
Chris@320 69
Chris@350 70 ModelTransformer::Input
Chris@350 71 ModelTransformerFactory::getConfigurationForTransform(Transform &transform,
Chris@350 72 const std::vector<Model *> &candidateInputModels,
Chris@350 73 Model *defaultInputModel,
Chris@389 74 AudioPlaySource *source,
Chris@350 75 size_t startFrame,
Chris@350 76 size_t duration)
Chris@320 77 {
Chris@350 78 ModelTransformer::Input input(0);
Chris@350 79
Chris@350 80 if (candidateInputModels.empty()) return input;
Chris@320 81
Chris@320 82 //!!! This will need revision -- we'll have to have a callback
Chris@320 83 //from the dialog for when the candidate input model is changed,
Chris@320 84 //as we'll need to reinitialise the channel settings in the dialog
Chris@345 85 Model *inputModel = candidateInputModels[0];
Chris@320 86 QStringList candidateModelNames;
Chris@345 87 QString defaultModelName;
Chris@320 88 std::map<QString, Model *> modelMap;
Chris@320 89 for (size_t i = 0; i < candidateInputModels.size(); ++i) {
Chris@320 90 QString modelName = candidateInputModels[i]->objectName();
Chris@320 91 QString origModelName = modelName;
Chris@320 92 int dupcount = 1;
Chris@320 93 while (modelMap.find(modelName) != modelMap.end()) {
Chris@320 94 modelName = tr("%1 <%2>").arg(origModelName).arg(++dupcount);
Chris@320 95 }
Chris@320 96 modelMap[modelName] = candidateInputModels[i];
Chris@320 97 candidateModelNames.push_back(modelName);
Chris@345 98 if (candidateInputModels[i] == defaultInputModel) {
Chris@345 99 defaultModelName = modelName;
Chris@345 100 }
Chris@320 101 }
Chris@320 102
Chris@350 103 QString id = transform.getPluginIdentifier();
Chris@350 104 QString output = transform.getOutput();
Chris@320 105 QString outputLabel = "";
Chris@320 106 QString outputDescription = "";
Chris@320 107
Chris@320 108 bool ok = false;
Chris@350 109 QString configurationXml = m_lastConfigurations[transform.getIdentifier()];
Chris@320 110
Chris@350 111 std::cerr << "last configuration: " << configurationXml.toStdString() << std::endl;
Chris@320 112
Chris@320 113 Vamp::PluginBase *plugin = 0;
Chris@320 114
Chris@320 115 bool frequency = false;
Chris@320 116 bool effect = false;
Chris@320 117 bool generator = false;
Chris@320 118
Chris@320 119 if (FeatureExtractionPluginFactory::instanceFor(id)) {
Chris@320 120
Chris@350 121 std::cerr << "getConfigurationForTransform: instantiating Vamp plugin" << std::endl;
Chris@320 122
Chris@320 123 Vamp::Plugin *vp =
Chris@320 124 FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
Chris@320 125 (id, inputModel->getSampleRate());
Chris@320 126
Chris@320 127 if (vp) {
Chris@320 128
Chris@320 129 plugin = vp;
Chris@320 130 frequency = (vp->getInputDomain() == Vamp::Plugin::FrequencyDomain);
Chris@320 131
Chris@320 132 std::vector<Vamp::Plugin::OutputDescriptor> od =
Chris@320 133 vp->getOutputDescriptors();
Chris@320 134 if (od.size() > 1) {
Chris@320 135 for (size_t i = 0; i < od.size(); ++i) {
Chris@320 136 if (od[i].identifier == output.toStdString()) {
Chris@320 137 outputLabel = od[i].name.c_str();
Chris@320 138 outputDescription = od[i].description.c_str();
Chris@320 139 break;
Chris@320 140 }
Chris@320 141 }
Chris@320 142 }
Chris@320 143 }
Chris@320 144
Chris@320 145 } else if (RealTimePluginFactory::instanceFor(id)) {
Chris@320 146
Chris@320 147 RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id);
Chris@320 148 const RealTimePluginDescriptor *desc = factory->getPluginDescriptor(id);
Chris@320 149
Chris@320 150 if (desc->audioInputPortCount > 0 &&
Chris@320 151 desc->audioOutputPortCount > 0 &&
Chris@320 152 !desc->isSynth) {
Chris@320 153 effect = true;
Chris@320 154 }
Chris@320 155
Chris@320 156 if (desc->audioInputPortCount == 0) {
Chris@320 157 generator = true;
Chris@320 158 }
Chris@320 159
Chris@320 160 if (output != "A") {
Chris@320 161 int outputNo = output.toInt();
Chris@320 162 if (outputNo >= 0 && outputNo < int(desc->controlOutputPortCount)) {
Chris@320 163 outputLabel = desc->controlOutputPortNames[outputNo].c_str();
Chris@320 164 }
Chris@320 165 }
Chris@320 166
Chris@320 167 size_t sampleRate = inputModel->getSampleRate();
Chris@320 168 size_t blockSize = 1024;
Chris@320 169 size_t channels = 1;
Chris@320 170 if (effect && source) {
Chris@320 171 sampleRate = source->getTargetSampleRate();
Chris@320 172 blockSize = source->getTargetBlockSize();
Chris@320 173 channels = source->getTargetChannelCount();
Chris@320 174 }
Chris@320 175
Chris@320 176 RealTimePluginInstance *rtp = factory->instantiatePlugin
Chris@320 177 (id, 0, 0, sampleRate, blockSize, channels);
Chris@320 178
Chris@320 179 plugin = rtp;
Chris@320 180
Chris@320 181 if (effect && source && rtp) {
Chris@389 182 source->setAuditioningEffect(rtp);
Chris@320 183 }
Chris@320 184 }
Chris@320 185
Chris@320 186 if (plugin) {
Chris@320 187
Chris@350 188 // Ensure block size etc are valid
Chris@350 189 TransformFactory::getInstance()->
Chris@350 190 makeContextConsistentWithPlugin(transform, plugin);
Chris@320 191
Chris@350 192 // Prepare the plugin with any existing parameters already
Chris@350 193 // found in the transform
Chris@350 194 TransformFactory::getInstance()->
Chris@350 195 setPluginParameters(transform, plugin);
Chris@350 196
Chris@350 197 // For this interactive usage, we want to override those with
Chris@350 198 // whatever the user chose last time around
Chris@350 199 PluginXml(plugin).setParametersFromXml(configurationXml);
Chris@320 200
Chris@320 201 int sourceChannels = 1;
Chris@320 202 if (dynamic_cast<DenseTimeValueModel *>(inputModel)) {
Chris@320 203 sourceChannels = dynamic_cast<DenseTimeValueModel *>(inputModel)
Chris@320 204 ->getChannelCount();
Chris@320 205 }
Chris@320 206
Chris@320 207 int minChannels = 1, maxChannels = sourceChannels;
Chris@350 208 getChannelRange(transform.getIdentifier(), plugin,
Chris@350 209 minChannels, maxChannels);
Chris@320 210
Chris@320 211 int targetChannels = sourceChannels;
Chris@320 212 if (!effect) {
Chris@320 213 if (sourceChannels < minChannels) targetChannels = minChannels;
Chris@320 214 if (sourceChannels > maxChannels) targetChannels = maxChannels;
Chris@320 215 }
Chris@320 216
Chris@350 217 int defaultChannel = -1; //!!! no longer saved! [was context.channel]
Chris@320 218
Chris@320 219 PluginParameterDialog *dialog = new PluginParameterDialog(plugin);
Chris@320 220
Chris@472 221 dialog->setMoreInfoUrl(TransformFactory::getInstance()->
Chris@472 222 getTransformInfoUrl(transform.getIdentifier()));
Chris@472 223
Chris@320 224 if (candidateModelNames.size() > 1 && !generator) {
Chris@345 225 dialog->setCandidateInputModels(candidateModelNames,
Chris@345 226 defaultModelName);
Chris@320 227 }
Chris@320 228
Chris@320 229 if (startFrame != 0 || duration != 0) {
Chris@320 230 dialog->setShowSelectionOnlyOption(true);
Chris@320 231 }
Chris@320 232
Chris@320 233 if (targetChannels > 0) {
Chris@320 234 dialog->setChannelArrangement(sourceChannels, targetChannels,
Chris@320 235 defaultChannel);
Chris@320 236 }
Chris@320 237
Chris@320 238 dialog->setOutputLabel(outputLabel, outputDescription);
Chris@320 239
Chris@320 240 dialog->setShowProcessingOptions(true, frequency);
Chris@320 241
Chris@320 242 if (dialog->exec() == QDialog::Accepted) {
Chris@320 243 ok = true;
Chris@320 244 }
Chris@320 245
Chris@320 246 QString selectedInput = dialog->getInputModel();
Chris@320 247 if (selectedInput != "") {
Chris@320 248 if (modelMap.find(selectedInput) != modelMap.end()) {
Chris@320 249 inputModel = modelMap[selectedInput];
Chris@320 250 std::cerr << "Found selected input \"" << selectedInput.toStdString() << "\" in model map, result is " << inputModel << std::endl;
Chris@320 251 } else {
Chris@320 252 std::cerr << "Failed to find selected input \"" << selectedInput.toStdString() << "\" in model map" << std::endl;
Chris@320 253 }
Chris@320 254 } else {
Chris@320 255 std::cerr << "Selected input empty: \"" << selectedInput.toStdString() << "\"" << std::endl;
Chris@320 256 }
Chris@350 257
Chris@350 258 // Write parameters back to transform object
Chris@350 259 TransformFactory::getInstance()->
Chris@350 260 setParametersFromPlugin(transform, plugin);
Chris@320 261
Chris@350 262 input.setChannel(dialog->getChannel());
Chris@320 263
Chris@350 264 //!!! The dialog ought to be taking & returning transform
Chris@350 265 //objects and input objects and stuff rather than passing
Chris@350 266 //around all this misc stuff, but that's for tomorrow
Chris@350 267 //(whenever that may be)
Chris@350 268
Chris@320 269 if (startFrame != 0 || duration != 0) {
Chris@320 270 if (dialog->getSelectionOnly()) {
Chris@350 271 transform.setStartTime(RealTime::frame2RealTime
Chris@350 272 (startFrame, inputModel->getSampleRate()));
Chris@350 273 transform.setDuration(RealTime::frame2RealTime
Chris@350 274 (duration, inputModel->getSampleRate()));
Chris@320 275 }
Chris@320 276 }
Chris@320 277
Chris@350 278 size_t stepSize = 0, blockSize = 0;
Chris@350 279 WindowType windowType = HanningWindow;
Chris@320 280
Chris@350 281 dialog->getProcessingParameters(stepSize,
Chris@350 282 blockSize,
Chris@350 283 windowType);
Chris@350 284
Chris@350 285 transform.setStepSize(stepSize);
Chris@350 286 transform.setBlockSize(blockSize);
Chris@350 287 transform.setWindowType(windowType);
Chris@350 288
Chris@350 289 TransformFactory::getInstance()->
Chris@350 290 makeContextConsistentWithPlugin(transform, plugin);
Chris@350 291
Chris@350 292 configurationXml = PluginXml(plugin).toXmlString();
Chris@320 293
Chris@320 294 delete dialog;
Chris@320 295
Chris@320 296 if (effect && source) {
Chris@389 297 source->setAuditioningEffect(0); // will delete our plugin
Chris@320 298 } else {
Chris@320 299 delete plugin;
Chris@320 300 }
Chris@320 301 }
Chris@320 302
Chris@350 303 if (ok) {
Chris@350 304 m_lastConfigurations[transform.getIdentifier()] = configurationXml;
Chris@350 305 input.setModel(inputModel);
Chris@350 306 }
Chris@320 307
Chris@350 308 return input;
Chris@320 309 }
Chris@350 310 /*!!!
Chris@328 311 PluginTransformer::ExecutionContext
Chris@331 312 ModelTransformerFactory::getDefaultContextForTransformer(TransformId identifier,
Chris@320 313 Model *inputModel)
Chris@320 314 {
Chris@328 315 PluginTransformer::ExecutionContext context(-1);
Chris@320 316
Chris@320 317 QString id = identifier.section(':', 0, 2);
Chris@320 318
Chris@320 319 if (FeatureExtractionPluginFactory::instanceFor(id)) {
Chris@320 320
Chris@320 321 Vamp::Plugin *vp =
Chris@320 322 FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
Chris@320 323 (id, inputModel ? inputModel->getSampleRate() : 48000);
Chris@320 324
Chris@320 325 if (vp) {
Chris@328 326 context = PluginTransformer::ExecutionContext(-1, vp);
Chris@320 327 delete vp;
Chris@320 328 }
Chris@320 329 }
Chris@320 330
Chris@320 331 return context;
Chris@320 332 }
Chris@350 333 */
Chris@331 334 ModelTransformer *
Chris@350 335 ModelTransformerFactory::createTransformer(const Transform &transform,
Chris@350 336 const ModelTransformer::Input &input)
Chris@320 337 {
Chris@331 338 ModelTransformer *transformer = 0;
Chris@320 339
Chris@350 340 QString id = transform.getPluginIdentifier();
Chris@320 341
Chris@320 342 if (FeatureExtractionPluginFactory::instanceFor(id)) {
Chris@350 343
Chris@350 344 transformer =
Chris@350 345 new FeatureExtractionModelTransformer(input, transform);
Chris@350 346
Chris@331 347 } else if (RealTimePluginFactory::instanceFor(id)) {
Chris@350 348
Chris@350 349 transformer =
Chris@350 350 new RealTimeEffectModelTransformer(input, transform);
Chris@350 351
Chris@320 352 } else {
Chris@331 353 std::cerr << "ModelTransformerFactory::createTransformer: Unknown transform \""
Chris@350 354 << transform.getIdentifier().toStdString() << "\"" << std::endl;
Chris@331 355 return transformer;
Chris@320 356 }
Chris@320 357
Chris@350 358 if (transformer) transformer->setObjectName(transform.getIdentifier());
Chris@331 359 return transformer;
Chris@320 360 }
Chris@320 361
Chris@320 362 Model *
Chris@350 363 ModelTransformerFactory::transform(const Transform &transform,
Chris@361 364 const ModelTransformer::Input &input,
Chris@361 365 QString &message)
Chris@320 366 {
Chris@408 367 std::cerr << "ModelTransformerFactory::transform: Constructing transformer with input model " << input.getModel() << std::endl;
Chris@408 368
Chris@350 369 ModelTransformer *t = createTransformer(transform, input);
Chris@320 370 if (!t) return 0;
Chris@320 371
Chris@331 372 connect(t, SIGNAL(finished()), this, SLOT(transformerFinished()));
Chris@320 373
Chris@328 374 m_runningTransformers.insert(t);
Chris@320 375
Chris@320 376 t->start();
Chris@320 377 Model *model = t->detachOutputModel();
Chris@320 378
Chris@320 379 if (model) {
Chris@350 380 QString imn = input.getModel()->objectName();
Chris@332 381 QString trn =
Chris@332 382 TransformFactory::getInstance()->getTransformFriendlyName
Chris@350 383 (transform.getIdentifier());
Chris@320 384 if (imn != "") {
Chris@320 385 if (trn != "") {
Chris@320 386 model->setObjectName(tr("%1: %2").arg(imn).arg(trn));
Chris@320 387 } else {
Chris@320 388 model->setObjectName(imn);
Chris@320 389 }
Chris@320 390 } else if (trn != "") {
Chris@320 391 model->setObjectName(trn);
Chris@320 392 }
Chris@320 393 } else {
Chris@320 394 t->wait();
Chris@320 395 }
Chris@320 396
Chris@361 397 message = t->getMessage();
Chris@361 398
Chris@320 399 return model;
Chris@320 400 }
Chris@320 401
Chris@320 402 void
Chris@331 403 ModelTransformerFactory::transformerFinished()
Chris@320 404 {
Chris@320 405 QObject *s = sender();
Chris@331 406 ModelTransformer *transformer = dynamic_cast<ModelTransformer *>(s);
Chris@320 407
Chris@436 408 // std::cerr << "ModelTransformerFactory::transformerFinished(" << transformer << ")" << std::endl;
Chris@320 409
Chris@331 410 if (!transformer) {
Chris@331 411 std::cerr << "WARNING: ModelTransformerFactory::transformerFinished: sender is not a transformer" << std::endl;
Chris@320 412 return;
Chris@320 413 }
Chris@320 414
Chris@331 415 if (m_runningTransformers.find(transformer) == m_runningTransformers.end()) {
Chris@331 416 std::cerr << "WARNING: ModelTransformerFactory::transformerFinished("
Chris@331 417 << transformer
Chris@331 418 << "): I have no record of this transformer running!"
Chris@320 419 << std::endl;
Chris@320 420 }
Chris@320 421
Chris@331 422 m_runningTransformers.erase(transformer);
Chris@320 423
Chris@331 424 transformer->wait(); // unnecessary but reassuring
Chris@331 425 delete transformer;
Chris@320 426 }
Chris@320 427
Chris@320 428 void
Chris@331 429 ModelTransformerFactory::modelAboutToBeDeleted(Model *m)
Chris@320 430 {
Chris@328 431 TransformerSet affected;
Chris@320 432
Chris@328 433 for (TransformerSet::iterator i = m_runningTransformers.begin();
Chris@328 434 i != m_runningTransformers.end(); ++i) {
Chris@320 435
Chris@331 436 ModelTransformer *t = *i;
Chris@320 437
Chris@320 438 if (t->getInputModel() == m || t->getOutputModel() == m) {
Chris@320 439 affected.insert(t);
Chris@320 440 }
Chris@320 441 }
Chris@320 442
Chris@328 443 for (TransformerSet::iterator i = affected.begin();
Chris@320 444 i != affected.end(); ++i) {
Chris@320 445
Chris@331 446 ModelTransformer *t = *i;
Chris@320 447
Chris@320 448 t->abandon();
Chris@320 449
Chris@320 450 t->wait(); // this should eventually call back on
Chris@331 451 // transformerFinished, which will remove from
Chris@328 452 // m_runningTransformers and delete.
Chris@320 453 }
Chris@320 454 }
Chris@320 455