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