diff transform/ModelTransformerFactory.cpp @ 388:370aa9714ef5

* Move plugin/transform to plain transform. This way transform can depend on model and GUI classes, but plugin doesn't have to.
author Chris Cannam
date Wed, 12 Mar 2008 18:02:17 +0000
parents plugin/transform/ModelTransformerFactory.cpp@399ea254afd6
children a1b6d2e33cab
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transform/ModelTransformerFactory.cpp	Wed Mar 12 18:02:17 2008 +0000
@@ -0,0 +1,450 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+   
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "ModelTransformerFactory.h"
+
+#include "FeatureExtractionModelTransformer.h"
+#include "RealTimeEffectModelTransformer.h"
+
+#include "TransformFactory.h"
+
+#include "plugin/FeatureExtractionPluginFactory.h"
+#include "plugin/RealTimePluginFactory.h"
+#include "plugin/PluginXml.h"
+
+#include "widgets/PluginParameterDialog.h"
+
+#include "data/model/DenseTimeValueModel.h"
+
+#include "vamp-sdk/PluginHostAdapter.h"
+
+#include "audioio/AudioCallbackPlaySource.h" //!!! shouldn't include here
+
+#include <iostream>
+#include <set>
+
+#include <QRegExp>
+
+ModelTransformerFactory *
+ModelTransformerFactory::m_instance = new ModelTransformerFactory;
+
+ModelTransformerFactory *
+ModelTransformerFactory::getInstance()
+{
+    return m_instance;
+}
+
+ModelTransformerFactory::~ModelTransformerFactory()
+{
+}
+
+bool
+ModelTransformerFactory::getChannelRange(TransformId identifier,
+                                         Vamp::PluginBase *plugin,
+                                         int &minChannels, int &maxChannels)
+{
+    Vamp::Plugin *vp = 0;
+    if ((vp = dynamic_cast<Vamp::Plugin *>(plugin)) ||
+        (vp = dynamic_cast<Vamp::PluginHostAdapter *>(plugin))) {
+        minChannels = vp->getMinChannelCount();
+        maxChannels = vp->getMaxChannelCount();
+        return true;
+    } else {
+        return TransformFactory::getInstance()->
+            getTransformChannelRange(identifier, minChannels, maxChannels);
+    }
+}
+
+ModelTransformer::Input
+ModelTransformerFactory::getConfigurationForTransform(Transform &transform,
+                                                      const std::vector<Model *> &candidateInputModels,
+                                                      Model *defaultInputModel,
+                                                      AudioCallbackPlaySource *source,
+                                                      size_t startFrame,
+                                                      size_t duration)
+{
+    ModelTransformer::Input input(0);
+
+    if (candidateInputModels.empty()) return input;
+
+    //!!! This will need revision -- we'll have to have a callback
+    //from the dialog for when the candidate input model is changed,
+    //as we'll need to reinitialise the channel settings in the dialog
+    Model *inputModel = candidateInputModels[0];
+    QStringList candidateModelNames;
+    QString defaultModelName;
+    std::map<QString, Model *> modelMap;
+    for (size_t i = 0; i < candidateInputModels.size(); ++i) {
+        QString modelName = candidateInputModels[i]->objectName();
+        QString origModelName = modelName;
+        int dupcount = 1;
+        while (modelMap.find(modelName) != modelMap.end()) {
+            modelName = tr("%1 <%2>").arg(origModelName).arg(++dupcount);
+        }
+        modelMap[modelName] = candidateInputModels[i];
+        candidateModelNames.push_back(modelName);
+        if (candidateInputModels[i] == defaultInputModel) {
+            defaultModelName = modelName;
+        }
+    }
+
+    QString id = transform.getPluginIdentifier();
+    QString output = transform.getOutput();
+    QString outputLabel = "";
+    QString outputDescription = "";
+    
+    bool ok = false;
+    QString configurationXml = m_lastConfigurations[transform.getIdentifier()];
+
+    std::cerr << "last configuration: " << configurationXml.toStdString() << std::endl;
+
+    Vamp::PluginBase *plugin = 0;
+
+    bool frequency = false;
+    bool effect = false;
+    bool generator = false;
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+
+        std::cerr << "getConfigurationForTransform: instantiating Vamp plugin" << std::endl;
+
+        Vamp::Plugin *vp =
+            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
+            (id, inputModel->getSampleRate());
+
+        if (vp) {
+
+            plugin = vp;
+            frequency = (vp->getInputDomain() == Vamp::Plugin::FrequencyDomain);
+
+            std::vector<Vamp::Plugin::OutputDescriptor> od =
+                vp->getOutputDescriptors();
+            if (od.size() > 1) {
+                for (size_t i = 0; i < od.size(); ++i) {
+                    if (od[i].identifier == output.toStdString()) {
+                        outputLabel = od[i].name.c_str();
+                        outputDescription = od[i].description.c_str();
+                        break;
+                    }
+                }
+            }
+        }
+
+    } else if (RealTimePluginFactory::instanceFor(id)) {
+
+        RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id);
+        const RealTimePluginDescriptor *desc = factory->getPluginDescriptor(id);
+
+        if (desc->audioInputPortCount > 0 && 
+            desc->audioOutputPortCount > 0 &&
+            !desc->isSynth) {
+            effect = true;
+        }
+
+        if (desc->audioInputPortCount == 0) {
+            generator = true;
+        }
+
+        if (output != "A") {
+            int outputNo = output.toInt();
+            if (outputNo >= 0 && outputNo < int(desc->controlOutputPortCount)) {
+                outputLabel = desc->controlOutputPortNames[outputNo].c_str();
+            }
+        }
+
+        size_t sampleRate = inputModel->getSampleRate();
+        size_t blockSize = 1024;
+        size_t channels = 1;
+        if (effect && source) {
+            sampleRate = source->getTargetSampleRate();
+            blockSize = source->getTargetBlockSize();
+            channels = source->getTargetChannelCount();
+        }
+
+        RealTimePluginInstance *rtp = factory->instantiatePlugin
+            (id, 0, 0, sampleRate, blockSize, channels);
+
+        plugin = rtp;
+
+        if (effect && source && rtp) {
+            source->setAuditioningPlugin(rtp);
+        }
+    }
+
+    if (plugin) {
+
+        // Ensure block size etc are valid
+        TransformFactory::getInstance()->
+            makeContextConsistentWithPlugin(transform, plugin);
+
+        // Prepare the plugin with any existing parameters already
+        // found in the transform
+        TransformFactory::getInstance()->
+            setPluginParameters(transform, plugin);
+        
+        // For this interactive usage, we want to override those with
+        // whatever the user chose last time around
+        PluginXml(plugin).setParametersFromXml(configurationXml);
+
+        int sourceChannels = 1;
+        if (dynamic_cast<DenseTimeValueModel *>(inputModel)) {
+            sourceChannels = dynamic_cast<DenseTimeValueModel *>(inputModel)
+                ->getChannelCount();
+        }
+
+        int minChannels = 1, maxChannels = sourceChannels;
+        getChannelRange(transform.getIdentifier(), plugin,
+                        minChannels, maxChannels);
+
+        int targetChannels = sourceChannels;
+        if (!effect) {
+            if (sourceChannels < minChannels) targetChannels = minChannels;
+            if (sourceChannels > maxChannels) targetChannels = maxChannels;
+        }
+
+        int defaultChannel = -1; //!!! no longer saved! [was context.channel]
+
+        PluginParameterDialog *dialog = new PluginParameterDialog(plugin);
+
+        if (candidateModelNames.size() > 1 && !generator) {
+            dialog->setCandidateInputModels(candidateModelNames,
+                                            defaultModelName);
+        }
+
+        if (startFrame != 0 || duration != 0) {
+            dialog->setShowSelectionOnlyOption(true);
+        }
+
+        if (targetChannels > 0) {
+            dialog->setChannelArrangement(sourceChannels, targetChannels,
+                                          defaultChannel);
+        }
+        
+        dialog->setOutputLabel(outputLabel, outputDescription);
+        
+        dialog->setShowProcessingOptions(true, frequency);
+
+        if (dialog->exec() == QDialog::Accepted) {
+            ok = true;
+        }
+
+        QString selectedInput = dialog->getInputModel();
+        if (selectedInput != "") {
+            if (modelMap.find(selectedInput) != modelMap.end()) {
+                inputModel = modelMap[selectedInput];
+                std::cerr << "Found selected input \"" << selectedInput.toStdString() << "\" in model map, result is " << inputModel << std::endl;
+            } else {
+                std::cerr << "Failed to find selected input \"" << selectedInput.toStdString() << "\" in model map" << std::endl;
+            }
+        } else {
+            std::cerr << "Selected input empty: \"" << selectedInput.toStdString() << "\"" << std::endl;
+        }
+        
+        // Write parameters back to transform object
+        TransformFactory::getInstance()->
+            setParametersFromPlugin(transform, plugin);
+
+        input.setChannel(dialog->getChannel());
+        
+        //!!! The dialog ought to be taking & returning transform
+        //objects and input objects and stuff rather than passing
+        //around all this misc stuff, but that's for tomorrow
+        //(whenever that may be)
+
+        if (startFrame != 0 || duration != 0) {
+            if (dialog->getSelectionOnly()) {
+                transform.setStartTime(RealTime::frame2RealTime
+                                       (startFrame, inputModel->getSampleRate()));
+                transform.setDuration(RealTime::frame2RealTime
+                                      (duration, inputModel->getSampleRate()));
+            }
+        }
+
+        size_t stepSize = 0, blockSize = 0;
+        WindowType windowType = HanningWindow;
+
+        dialog->getProcessingParameters(stepSize,
+                                        blockSize,
+                                        windowType);
+
+        transform.setStepSize(stepSize);
+        transform.setBlockSize(blockSize);
+        transform.setWindowType(windowType);
+
+        TransformFactory::getInstance()->
+            makeContextConsistentWithPlugin(transform, plugin);
+
+        configurationXml = PluginXml(plugin).toXmlString();
+
+        delete dialog;
+
+        if (effect && source) {
+            source->setAuditioningPlugin(0); // will delete our plugin
+        } else {
+            delete plugin;
+        }
+    }
+
+    if (ok) {
+        m_lastConfigurations[transform.getIdentifier()] = configurationXml;
+        input.setModel(inputModel);
+    }
+
+    return input;
+}
+/*!!!
+PluginTransformer::ExecutionContext
+ModelTransformerFactory::getDefaultContextForTransformer(TransformId identifier,
+                                                Model *inputModel)
+{
+    PluginTransformer::ExecutionContext context(-1);
+
+    QString id = identifier.section(':', 0, 2);
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+
+        Vamp::Plugin *vp =
+            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
+            (id, inputModel ? inputModel->getSampleRate() : 48000);
+
+        if (vp) {
+            context = PluginTransformer::ExecutionContext(-1, vp);
+            delete vp;
+        }
+    }
+
+    return context;
+}
+*/
+ModelTransformer *
+ModelTransformerFactory::createTransformer(const Transform &transform,
+                                           const ModelTransformer::Input &input)
+{
+    ModelTransformer *transformer = 0;
+
+    QString id = transform.getPluginIdentifier();
+
+    if (FeatureExtractionPluginFactory::instanceFor(id)) {
+
+        transformer =
+            new FeatureExtractionModelTransformer(input, transform);
+
+    } else if (RealTimePluginFactory::instanceFor(id)) {
+
+        transformer =
+            new RealTimeEffectModelTransformer(input, transform);
+
+    } else {
+        std::cerr << "ModelTransformerFactory::createTransformer: Unknown transform \""
+                  << transform.getIdentifier().toStdString() << "\"" << std::endl;
+        return transformer;
+    }
+
+    if (transformer) transformer->setObjectName(transform.getIdentifier());
+    return transformer;
+}
+
+Model *
+ModelTransformerFactory::transform(const Transform &transform,
+                                   const ModelTransformer::Input &input,
+                                   QString &message)
+{
+    ModelTransformer *t = createTransformer(transform, input);
+    if (!t) return 0;
+
+    connect(t, SIGNAL(finished()), this, SLOT(transformerFinished()));
+
+    m_runningTransformers.insert(t);
+
+    t->start();
+    Model *model = t->detachOutputModel();
+
+    if (model) {
+        QString imn = input.getModel()->objectName();
+        QString trn =
+            TransformFactory::getInstance()->getTransformFriendlyName
+            (transform.getIdentifier());
+        if (imn != "") {
+            if (trn != "") {
+                model->setObjectName(tr("%1: %2").arg(imn).arg(trn));
+            } else {
+                model->setObjectName(imn);
+            }
+        } else if (trn != "") {
+            model->setObjectName(trn);
+        }
+    } else {
+        t->wait();
+    }
+
+    message = t->getMessage();
+
+    return model;
+}
+
+void
+ModelTransformerFactory::transformerFinished()
+{
+    QObject *s = sender();
+    ModelTransformer *transformer = dynamic_cast<ModelTransformer *>(s);
+    
+    std::cerr << "ModelTransformerFactory::transformerFinished(" << transformer << ")" << std::endl;
+
+    if (!transformer) {
+	std::cerr << "WARNING: ModelTransformerFactory::transformerFinished: sender is not a transformer" << std::endl;
+	return;
+    }
+
+    if (m_runningTransformers.find(transformer) == m_runningTransformers.end()) {
+        std::cerr << "WARNING: ModelTransformerFactory::transformerFinished(" 
+                  << transformer
+                  << "): I have no record of this transformer running!"
+                  << std::endl;
+    }
+
+    m_runningTransformers.erase(transformer);
+
+    transformer->wait(); // unnecessary but reassuring
+    delete transformer;
+}
+
+void
+ModelTransformerFactory::modelAboutToBeDeleted(Model *m)
+{
+    TransformerSet affected;
+
+    for (TransformerSet::iterator i = m_runningTransformers.begin();
+         i != m_runningTransformers.end(); ++i) {
+
+        ModelTransformer *t = *i;
+
+        if (t->getInputModel() == m || t->getOutputModel() == m) {
+            affected.insert(t);
+        }
+    }
+
+    for (TransformerSet::iterator i = affected.begin();
+         i != affected.end(); ++i) {
+
+        ModelTransformer *t = *i;
+
+        t->abandon();
+
+        t->wait(); // this should eventually call back on
+                   // transformerFinished, which will remove from
+                   // m_runningTransformers and delete.
+    }
+}
+