Chris@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@0: Sonic Visualiser Chris@0: An audio file viewer and annotation editor. Chris@0: Centre for Digital Music, Queen Mary, University of London. Chris@0: This file copyright 2006 Chris Cannam. Chris@0: Chris@0: This program is free software; you can redistribute it and/or Chris@0: modify it under the terms of the GNU General Public License as Chris@0: published by the Free Software Foundation; either version 2 of the Chris@0: License, or (at your option) any later version. See the file Chris@0: COPYING included with this distribution for more information. Chris@0: */ Chris@0: Chris@0: #include "TransformFactory.h" Chris@0: Chris@0: #include "FeatureExtractionPluginTransform.h" Chris@0: #include "RealTimePluginTransform.h" Chris@0: Chris@0: #include "plugin/FeatureExtractionPluginFactory.h" Chris@0: #include "plugin/RealTimePluginFactory.h" Chris@0: #include "plugin/PluginXml.h" Chris@0: Chris@0: #include "widgets/PluginParameterDialog.h" Chris@0: Chris@1: #include "data/model/DenseTimeValueModel.h" Chris@0: Chris@28: #include "vamp-sdk/PluginHostAdapter.h" Chris@28: Chris@41: #include "sv/audioio/AudioCallbackPlaySource.h" //!!! shouldn't include here Chris@41: Chris@0: #include Chris@0: #include Chris@0: Chris@0: #include Chris@0: Chris@0: TransformFactory * Chris@0: TransformFactory::m_instance = new TransformFactory; Chris@0: Chris@0: TransformFactory * Chris@0: TransformFactory::getInstance() Chris@0: { Chris@0: return m_instance; Chris@0: } Chris@0: Chris@0: TransformFactory::~TransformFactory() Chris@0: { Chris@0: } Chris@0: Chris@0: TransformFactory::TransformList Chris@0: TransformFactory::getAllTransforms() Chris@0: { Chris@0: if (m_transforms.empty()) populateTransforms(); Chris@0: Chris@34: std::set dset; Chris@0: for (TransformDescriptionMap::const_iterator i = m_transforms.begin(); Chris@0: i != m_transforms.end(); ++i) { Chris@34: dset.insert(i->second); Chris@34: } Chris@34: Chris@34: TransformList list; Chris@34: for (std::set::const_iterator i = dset.begin(); Chris@34: i != dset.end(); ++i) { Chris@34: list.push_back(*i); Chris@0: } Chris@0: Chris@0: return list; Chris@0: } Chris@0: Chris@0: std::vector Chris@0: TransformFactory::getAllTransformTypes() Chris@0: { Chris@0: if (m_transforms.empty()) populateTransforms(); Chris@0: Chris@0: std::set types; Chris@0: for (TransformDescriptionMap::const_iterator i = m_transforms.begin(); Chris@0: i != m_transforms.end(); ++i) { Chris@0: types.insert(i->second.type); Chris@0: } Chris@0: Chris@0: std::vector rv; Chris@0: for (std::set::iterator i = types.begin(); i != types.end(); ++i) { Chris@0: rv.push_back(*i); Chris@0: } Chris@0: Chris@0: return rv; Chris@0: } Chris@0: Chris@33: std::vector Chris@33: TransformFactory::getTransformCategories(QString transformType) Chris@33: { Chris@33: if (m_transforms.empty()) populateTransforms(); Chris@33: Chris@33: std::set categories; Chris@33: for (TransformDescriptionMap::const_iterator i = m_transforms.begin(); Chris@33: i != m_transforms.end(); ++i) { Chris@33: if (i->second.type == transformType) { Chris@33: categories.insert(i->second.category); Chris@33: } Chris@33: } Chris@33: Chris@33: bool haveEmpty = false; Chris@33: Chris@33: std::vector rv; Chris@33: for (std::set::iterator i = categories.begin(); Chris@33: i != categories.end(); ++i) { Chris@33: if (*i != "") rv.push_back(*i); Chris@33: else haveEmpty = true; Chris@33: } Chris@33: Chris@33: if (haveEmpty) rv.push_back(""); // make sure empty category sorts last Chris@33: Chris@33: return rv; Chris@33: } Chris@33: Chris@33: std::vector Chris@33: TransformFactory::getTransformMakers(QString transformType) Chris@33: { Chris@33: if (m_transforms.empty()) populateTransforms(); Chris@33: Chris@33: std::set makers; Chris@33: for (TransformDescriptionMap::const_iterator i = m_transforms.begin(); Chris@33: i != m_transforms.end(); ++i) { Chris@33: if (i->second.type == transformType) { Chris@33: makers.insert(i->second.maker); Chris@33: } Chris@33: } Chris@33: Chris@33: bool haveEmpty = false; Chris@33: Chris@33: std::vector rv; Chris@33: for (std::set::iterator i = makers.begin(); Chris@33: i != makers.end(); ++i) { Chris@33: if (*i != "") rv.push_back(*i); Chris@33: else haveEmpty = true; Chris@33: } Chris@33: Chris@33: if (haveEmpty) rv.push_back(""); // make sure empty category sorts last Chris@33: Chris@33: return rv; Chris@33: } Chris@33: Chris@0: void Chris@0: TransformFactory::populateTransforms() Chris@0: { Chris@0: TransformDescriptionMap transforms; Chris@0: Chris@0: populateFeatureExtractionPlugins(transforms); Chris@0: populateRealTimePlugins(transforms); Chris@0: Chris@0: // disambiguate plugins with similar descriptions Chris@0: Chris@0: std::map descriptions; Chris@0: Chris@0: for (TransformDescriptionMap::iterator i = transforms.begin(); Chris@0: i != transforms.end(); ++i) { Chris@0: Chris@0: TransformDesc desc = i->second; Chris@0: Chris@0: ++descriptions[desc.description]; Chris@0: ++descriptions[QString("%1 [%2]").arg(desc.description).arg(desc.maker)]; Chris@0: } Chris@0: Chris@0: std::map counts; Chris@0: m_transforms.clear(); Chris@0: Chris@0: for (TransformDescriptionMap::iterator i = transforms.begin(); Chris@0: i != transforms.end(); ++i) { Chris@0: Chris@0: TransformDesc desc = i->second; Chris@0: QString name = desc.name; Chris@0: QString description = desc.description; Chris@0: QString maker = desc.maker; Chris@0: Chris@0: if (descriptions[description] > 1) { Chris@0: description = QString("%1 [%2]").arg(description).arg(maker); Chris@0: if (descriptions[description] > 1) { Chris@0: description = QString("%1 <%2>") Chris@0: .arg(description).arg(++counts[description]); Chris@0: } Chris@0: } Chris@0: Chris@0: desc.description = description; Chris@0: m_transforms[name] = desc; Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: TransformFactory::populateFeatureExtractionPlugins(TransformDescriptionMap &transforms) Chris@0: { Chris@0: std::vector plugs = Chris@0: FeatureExtractionPluginFactory::getAllPluginIdentifiers(); Chris@0: Chris@0: for (size_t i = 0; i < plugs.size(); ++i) { Chris@0: Chris@0: QString pluginId = plugs[i]; Chris@0: Chris@0: FeatureExtractionPluginFactory *factory = Chris@0: FeatureExtractionPluginFactory::instanceFor(pluginId); Chris@0: Chris@0: if (!factory) { Chris@0: std::cerr << "WARNING: TransformFactory::populateTransforms: No feature extraction plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl; Chris@0: continue; Chris@0: } Chris@0: Chris@0: Vamp::Plugin *plugin = Chris@0: factory->instantiatePlugin(pluginId, 48000); Chris@0: Chris@0: if (!plugin) { Chris@0: std::cerr << "WARNING: TransformFactory::populateTransforms: Failed to instantiate plugin " << pluginId.toLocal8Bit().data() << std::endl; Chris@0: continue; Chris@0: } Chris@0: Chris@0: QString pluginDescription = plugin->getDescription().c_str(); Chris@33: QString category = factory->getPluginCategory(pluginId); Chris@33: Chris@0: Vamp::Plugin::OutputList outputs = Chris@0: plugin->getOutputDescriptors(); Chris@0: Chris@0: for (size_t j = 0; j < outputs.size(); ++j) { Chris@0: Chris@0: QString transformName = QString("%1:%2") Chris@0: .arg(pluginId).arg(outputs[j].name.c_str()); Chris@0: Chris@0: QString userDescription; Chris@0: QString friendlyName; Chris@0: QString units = outputs[j].unit.c_str(); Chris@0: Chris@0: if (outputs.size() == 1) { Chris@0: userDescription = pluginDescription; Chris@0: friendlyName = pluginDescription; Chris@0: } else { Chris@0: userDescription = QString("%1: %2") Chris@0: .arg(pluginDescription) Chris@0: .arg(outputs[j].description.c_str()); Chris@0: friendlyName = outputs[j].description.c_str(); Chris@0: } Chris@0: Chris@0: bool configurable = (!plugin->getPrograms().empty() || Chris@0: !plugin->getParameterDescriptors().empty()); Chris@0: Chris@0: transforms[transformName] = Chris@34: TransformDesc(tr("Analysis"), Chris@33: category, Chris@0: transformName, Chris@0: userDescription, Chris@0: friendlyName, Chris@0: plugin->getMaker().c_str(), Chris@0: units, Chris@0: configurable); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: TransformFactory::populateRealTimePlugins(TransformDescriptionMap &transforms) Chris@0: { Chris@0: std::vector plugs = Chris@0: RealTimePluginFactory::getAllPluginIdentifiers(); Chris@0: Chris@34: static QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$"); Chris@0: Chris@0: for (size_t i = 0; i < plugs.size(); ++i) { Chris@0: Chris@0: QString pluginId = plugs[i]; Chris@0: Chris@0: RealTimePluginFactory *factory = Chris@0: RealTimePluginFactory::instanceFor(pluginId); Chris@0: Chris@0: if (!factory) { Chris@0: std::cerr << "WARNING: TransformFactory::populateTransforms: No real time plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl; Chris@0: continue; Chris@0: } Chris@0: Chris@0: const RealTimePluginDescriptor *descriptor = Chris@0: factory->getPluginDescriptor(pluginId); Chris@0: Chris@0: if (!descriptor) { Chris@0: std::cerr << "WARNING: TransformFactory::populateTransforms: Failed to query plugin " << pluginId.toLocal8Bit().data() << std::endl; Chris@0: continue; Chris@0: } Chris@0: Chris@34: //!!! if (descriptor->controlOutputPortCount == 0 || Chris@34: // descriptor->audioInputPortCount == 0) continue; Chris@0: Chris@35: // std::cout << "TransformFactory::populateRealTimePlugins: plugin " << pluginId.toStdString() << " has " << descriptor->controlOutputPortCount << " control output ports, " << descriptor->audioOutputPortCount << " audio outputs, " << descriptor->audioInputPortCount << " audio inputs" << std::endl; Chris@0: Chris@0: QString pluginDescription = descriptor->name.c_str(); Chris@33: QString category = factory->getPluginCategory(pluginId); Chris@34: bool configurable = (descriptor->parameterCount > 0); Chris@0: Chris@34: if (descriptor->audioInputPortCount > 0) { Chris@0: Chris@34: for (size_t j = 0; j < descriptor->controlOutputPortCount; ++j) { Chris@0: Chris@34: QString transformName = QString("%1:%2").arg(pluginId).arg(j); Chris@34: QString userDescription; Chris@34: QString units; Chris@0: Chris@34: if (j < descriptor->controlOutputPortNames.size() && Chris@34: descriptor->controlOutputPortNames[j] != "") { Chris@0: Chris@34: QString portName = descriptor->controlOutputPortNames[j].c_str(); Chris@0: Chris@34: userDescription = tr("%1: %2") Chris@34: .arg(pluginDescription) Chris@34: .arg(portName); Chris@34: Chris@34: if (unitRE.indexIn(portName) >= 0) { Chris@34: units = unitRE.cap(1); Chris@34: } Chris@34: Chris@34: } else if (descriptor->controlOutputPortCount > 1) { Chris@34: Chris@34: userDescription = tr("%1: Output %2") Chris@34: .arg(pluginDescription) Chris@34: .arg(j + 1); Chris@34: Chris@34: } else { Chris@34: Chris@34: userDescription = pluginDescription; Chris@0: } Chris@0: Chris@0: Chris@34: transforms[transformName] = Chris@34: TransformDesc(tr("Effects Measurements"), Chris@34: category, Chris@34: transformName, Chris@34: userDescription, Chris@34: userDescription, Chris@34: descriptor->maker.c_str(), Chris@34: units, Chris@34: configurable); Chris@34: } Chris@34: } Chris@0: Chris@34: if (!descriptor->isSynth || descriptor->audioInputPortCount > 0) { Chris@0: Chris@34: if (descriptor->audioOutputPortCount > 0) { Chris@34: Chris@34: QString transformName = QString("%1:A").arg(pluginId); Chris@34: QString type = tr("Effects"); Chris@34: if (descriptor->audioInputPortCount == 0) { Chris@34: type = tr("Generators"); Chris@34: } Chris@34: Chris@34: transforms[transformName] = Chris@34: TransformDesc(type, Chris@34: category, Chris@34: transformName, Chris@34: pluginDescription, Chris@34: pluginDescription, Chris@34: descriptor->maker.c_str(), Chris@34: "", Chris@34: configurable); Chris@0: } Chris@34: } Chris@0: } Chris@0: } Chris@0: Chris@0: QString Chris@0: TransformFactory::getTransformDescription(TransformName name) Chris@0: { Chris@0: if (m_transforms.find(name) != m_transforms.end()) { Chris@0: return m_transforms[name].description; Chris@0: } else return ""; Chris@0: } Chris@0: Chris@0: QString Chris@0: TransformFactory::getTransformFriendlyName(TransformName name) Chris@0: { Chris@0: if (m_transforms.find(name) != m_transforms.end()) { Chris@0: return m_transforms[name].friendlyName; Chris@0: } else return ""; Chris@0: } Chris@0: Chris@0: QString Chris@0: TransformFactory::getTransformUnits(TransformName name) Chris@0: { Chris@0: if (m_transforms.find(name) != m_transforms.end()) { Chris@0: return m_transforms[name].units; Chris@0: } else return ""; Chris@0: } Chris@0: Chris@0: bool Chris@0: TransformFactory::isTransformConfigurable(TransformName name) Chris@0: { Chris@0: if (m_transforms.find(name) != m_transforms.end()) { Chris@0: return m_transforms[name].configurable; Chris@0: } else return false; Chris@0: } Chris@0: Chris@0: bool Chris@0: TransformFactory::getTransformChannelRange(TransformName name, Chris@0: int &min, int &max) Chris@0: { Chris@0: QString id = name.section(':', 0, 2); Chris@0: Chris@0: if (FeatureExtractionPluginFactory::instanceFor(id)) { Chris@0: Chris@0: Vamp::Plugin *plugin = Chris@0: FeatureExtractionPluginFactory::instanceFor(id)-> Chris@0: instantiatePlugin(id, 48000); Chris@0: if (!plugin) return false; Chris@0: Chris@0: min = plugin->getMinChannelCount(); Chris@0: max = plugin->getMaxChannelCount(); Chris@0: delete plugin; Chris@0: Chris@0: return true; Chris@0: Chris@0: } else if (RealTimePluginFactory::instanceFor(id)) { Chris@0: Chris@0: const RealTimePluginDescriptor *descriptor = Chris@0: RealTimePluginFactory::instanceFor(id)-> Chris@0: getPluginDescriptor(id); Chris@0: if (!descriptor) return false; Chris@0: Chris@0: min = descriptor->audioInputPortCount; Chris@0: max = descriptor->audioInputPortCount; Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: Chris@0: bool Chris@0: TransformFactory::getChannelRange(TransformName name, Vamp::PluginBase *plugin, Chris@0: int &minChannels, int &maxChannels) Chris@0: { Chris@0: Vamp::Plugin *vp = 0; Chris@28: if ((vp = dynamic_cast(plugin)) || Chris@28: (vp = dynamic_cast(plugin))) { Chris@0: minChannels = vp->getMinChannelCount(); Chris@0: maxChannels = vp->getMaxChannelCount(); Chris@0: return true; Chris@0: } else { Chris@0: return getTransformChannelRange(name, minChannels, maxChannels); Chris@0: } Chris@0: } Chris@0: Chris@0: bool Chris@0: TransformFactory::getConfigurationForTransform(TransformName name, Chris@0: Model *inputModel, Chris@27: PluginTransform::ExecutionContext &context, Chris@41: QString &configurationXml, Chris@41: AudioCallbackPlaySource *source) Chris@0: { Chris@0: QString id = name.section(':', 0, 2); Chris@0: QString output = name.section(':', 3); Chris@0: Chris@0: bool ok = false; Chris@0: configurationXml = m_lastConfigurations[name]; Chris@0: Chris@0: // std::cerr << "last configuration: " << configurationXml.toStdString() << std::endl; Chris@0: Chris@0: Vamp::PluginBase *plugin = 0; Chris@0: Chris@10: bool frequency = false; Chris@41: bool effect = false; Chris@10: Chris@0: if (FeatureExtractionPluginFactory::instanceFor(id)) { Chris@0: Chris@10: Vamp::Plugin *vp = Chris@10: FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin Chris@0: (id, inputModel->getSampleRate()); Chris@10: if (vp) { Chris@10: plugin = vp; Chris@10: frequency = (vp->getInputDomain() == Vamp::Plugin::FrequencyDomain); Chris@10: } Chris@0: Chris@0: } else if (RealTimePluginFactory::instanceFor(id)) { Chris@0: Chris@41: RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id); Chris@41: const RealTimePluginDescriptor *desc = factory->getPluginDescriptor(id); Chris@41: Chris@41: if (desc->audioInputPortCount > 0 && Chris@41: desc->audioOutputPortCount > 0 && Chris@41: !desc->isSynth) { Chris@41: effect = true; Chris@41: } Chris@41: Chris@41: size_t sampleRate = inputModel->getSampleRate(); Chris@41: size_t blockSize = 1024; Chris@41: size_t channels = 1; Chris@41: if (effect && source) { Chris@41: sampleRate = source->getTargetSampleRate(); Chris@41: blockSize = source->getTargetBlockSize(); Chris@41: channels = source->getTargetChannelCount(); Chris@41: } Chris@41: Chris@41: RealTimePluginInstance *rtp = factory->instantiatePlugin Chris@41: (id, 0, 0, sampleRate, blockSize, channels); Chris@41: Chris@41: plugin = rtp; Chris@41: Chris@41: if (effect && source && rtp) { Chris@41: source->setAuditioningPlugin(rtp); Chris@41: } Chris@0: } Chris@0: Chris@0: if (plugin) { Chris@27: Chris@27: context = PluginTransform::ExecutionContext(context.channel, plugin); Chris@27: Chris@0: if (configurationXml != "") { Chris@0: PluginXml(plugin).setParametersFromXml(configurationXml); Chris@0: } Chris@0: Chris@0: int sourceChannels = 1; Chris@0: if (dynamic_cast(inputModel)) { Chris@0: sourceChannels = dynamic_cast(inputModel) Chris@0: ->getChannelCount(); Chris@0: } Chris@0: Chris@0: int minChannels = 1, maxChannels = sourceChannels; Chris@0: getChannelRange(name, plugin, minChannels, maxChannels); Chris@0: Chris@0: int targetChannels = sourceChannels; Chris@0: if (sourceChannels < minChannels) targetChannels = minChannels; Chris@0: if (sourceChannels > maxChannels) targetChannels = maxChannels; Chris@0: Chris@27: int defaultChannel = context.channel; Chris@0: Chris@0: PluginParameterDialog *dialog = new PluginParameterDialog(plugin, Chris@0: sourceChannels, Chris@0: targetChannels, Chris@0: defaultChannel, Chris@10: output, Chris@10: true, Chris@10: frequency); Chris@0: if (dialog->exec() == QDialog::Accepted) { Chris@0: ok = true; Chris@0: } Chris@42: Chris@0: configurationXml = PluginXml(plugin).toXmlString(); Chris@27: context.channel = dialog->getChannel(); Chris@26: Chris@27: dialog->getProcessingParameters(context.stepSize, Chris@27: context.blockSize, Chris@27: context.windowType); Chris@27: Chris@27: context.makeConsistentWithPlugin(plugin); Chris@26: Chris@0: delete dialog; Chris@41: Chris@41: if (effect && source) { Chris@41: source->setAuditioningPlugin(0); // will delete our plugin Chris@41: } else { Chris@41: delete plugin; Chris@41: } Chris@0: } Chris@0: Chris@0: if (ok) m_lastConfigurations[name] = configurationXml; Chris@0: Chris@0: return ok; Chris@0: } Chris@0: Chris@0: Transform * Chris@0: TransformFactory::createTransform(TransformName name, Model *inputModel, Chris@27: const PluginTransform::ExecutionContext &context, Chris@27: QString configurationXml, bool start) Chris@0: { Chris@0: Transform *transform = 0; Chris@0: Chris@0: QString id = name.section(':', 0, 2); Chris@0: QString output = name.section(':', 3); Chris@0: Chris@0: if (FeatureExtractionPluginFactory::instanceFor(id)) { Chris@0: transform = new FeatureExtractionPluginTransform(inputModel, Chris@0: id, Chris@27: context, Chris@0: configurationXml, Chris@0: output); Chris@0: } else if (RealTimePluginFactory::instanceFor(id)) { Chris@0: transform = new RealTimePluginTransform(inputModel, Chris@0: id, Chris@27: context, Chris@0: configurationXml, Chris@0: getTransformUnits(name), Chris@34: output == "A" ? -1 : Chris@0: output.toInt()); Chris@0: } else { Chris@0: std::cerr << "TransformFactory::createTransform: Unknown transform \"" Chris@0: << name.toStdString() << "\"" << std::endl; Chris@0: return transform; Chris@0: } Chris@0: Chris@0: if (start && transform) transform->start(); Chris@0: transform->setObjectName(name); Chris@0: return transform; Chris@0: } Chris@0: Chris@0: Model * Chris@0: TransformFactory::transform(TransformName name, Model *inputModel, Chris@27: const PluginTransform::ExecutionContext &context, Chris@27: QString configurationXml) Chris@0: { Chris@27: Transform *t = createTransform(name, inputModel, context, Chris@0: configurationXml, false); Chris@0: Chris@0: if (!t) return 0; Chris@0: Chris@0: connect(t, SIGNAL(finished()), this, SLOT(transformFinished())); Chris@0: Chris@0: t->start(); Chris@0: return t->detachOutputModel(); Chris@0: } Chris@0: Chris@0: void Chris@0: TransformFactory::transformFinished() Chris@0: { Chris@0: QObject *s = sender(); Chris@0: Transform *transform = dynamic_cast(s); Chris@0: Chris@0: if (!transform) { Chris@0: std::cerr << "WARNING: TransformFactory::transformFinished: sender is not a transform" << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: transform->wait(); // unnecessary but reassuring Chris@0: delete transform; Chris@0: } Chris@0: