Chris@330: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@330: Chris@330: /* Chris@330: Sonic Visualiser Chris@330: An audio file viewer and annotation editor. Chris@330: Centre for Digital Music, Queen Mary, University of London. Chris@330: This file copyright 2006 Chris Cannam and QMUL. Chris@330: Chris@330: This program is free software; you can redistribute it and/or Chris@330: modify it under the terms of the GNU General Public License as Chris@330: published by the Free Software Foundation; either version 2 of the Chris@330: License, or (at your option) any later version. See the file Chris@330: COPYING included with this distribution for more information. Chris@330: */ Chris@330: Chris@330: #include "TransformFactory.h" Chris@330: Chris@330: #include "plugin/FeatureExtractionPluginFactory.h" Chris@1225: Chris@330: #include "plugin/RealTimePluginFactory.h" Chris@332: #include "plugin/RealTimePluginInstance.h" Chris@330: #include "plugin/PluginXml.h" Chris@330: Chris@475: #include Chris@475: #include Chris@475: #include Chris@330: Chris@457: #include "rdf/PluginRDFIndexer.h" Chris@457: #include "rdf/PluginRDFDescription.h" Chris@457: Chris@446: #include "base/XmlExportable.h" Chris@446: Chris@330: #include Chris@330: #include Chris@1546: #include Chris@330: Chris@330: #include Chris@350: #include Chris@330: Chris@460: #include "base/Thread.h" Chris@460: Chris@810: //#define DEBUG_TRANSFORM_FACTORY 1 Chris@810: Chris@330: TransformFactory * Chris@330: TransformFactory::m_instance = new TransformFactory; Chris@330: Chris@330: TransformFactory * Chris@330: TransformFactory::getInstance() Chris@330: { Chris@330: return m_instance; Chris@330: } Chris@330: Chris@574: void Chris@574: TransformFactory::deleteInstance() Chris@574: { Chris@690: SVDEBUG << "TransformFactory::deleteInstance called" << endl; Chris@574: delete m_instance; Chris@1582: m_instance = nullptr; Chris@574: } Chris@574: Chris@457: TransformFactory::TransformFactory() : Chris@457: m_transformsPopulated(false), Chris@477: m_uninstalledTransformsPopulated(false), Chris@1582: m_thread(nullptr), Chris@976: m_exiting(false), Chris@976: m_populatingSlowly(false) Chris@457: { Chris@457: } Chris@457: Chris@330: TransformFactory::~TransformFactory() Chris@330: { Chris@574: m_exiting = true; Chris@574: if (m_thread) { Chris@600: #ifdef DEBUG_TRANSFORM_FACTORY Chris@690: SVDEBUG << "TransformFactory::~TransformFactory: waiting on thread" << endl; Chris@600: #endif Chris@574: m_thread->wait(); Chris@574: delete m_thread; Chris@600: #ifdef DEBUG_TRANSFORM_FACTORY Chris@690: SVDEBUG << "TransformFactory::~TransformFactory: waited and done" << endl; Chris@600: #endif Chris@574: } Chris@330: } Chris@330: Chris@477: void Chris@477: TransformFactory::startPopulationThread() Chris@477: { Chris@482: m_uninstalledTransformsMutex.lock(); Chris@477: Chris@482: if (m_thread) { Chris@482: m_uninstalledTransformsMutex.unlock(); Chris@482: return; Chris@482: } Chris@482: m_thread = new UninstalledTransformsPopulateThread(this); Chris@477: Chris@482: m_uninstalledTransformsMutex.unlock(); Chris@482: Chris@477: m_thread->start(); Chris@477: } Chris@477: Chris@481: void Chris@481: TransformFactory::UninstalledTransformsPopulateThread::run() Chris@481: { Chris@481: m_factory->m_populatingSlowly = true; Chris@1865: while (!m_factory->havePopulated()) { Chris@1865: sleep(1); Chris@1865: } Chris@481: m_factory->populateUninstalledTransforms(); Chris@481: } Chris@481: Chris@330: TransformList Chris@350: TransformFactory::getAllTransformDescriptions() Chris@330: { Chris@460: populateTransforms(); Chris@330: Chris@330: std::set dset; Chris@1546: for (auto i = m_transforms.begin(); i != m_transforms.end(); ++i) { Chris@600: #ifdef DEBUG_TRANSFORM_FACTORY Chris@1546: cerr << "inserting transform into set: id = " << i->second.identifier << " (" << i->second.name << ")" << endl; Chris@600: #endif Chris@1429: dset.insert(i->second); Chris@330: } Chris@330: Chris@330: TransformList list; Chris@1546: for (auto i = dset.begin(); i != dset.end(); ++i) { Chris@600: #ifdef DEBUG_TRANSFORM_FACTORY Chris@1546: cerr << "inserting transform into list: id = " << i->identifier << " (" << i->name << ")" << endl; Chris@600: #endif Chris@1429: list.push_back(*i); Chris@330: } Chris@330: Chris@330: return list; Chris@330: } Chris@330: Chris@350: TransformDescription Chris@350: TransformFactory::getTransformDescription(TransformId id) Chris@350: { Chris@460: populateTransforms(); Chris@350: Chris@350: if (m_transforms.find(id) == m_transforms.end()) { Chris@350: return TransformDescription(); Chris@350: } Chris@350: Chris@350: return m_transforms[id]; Chris@350: } Chris@350: Chris@485: bool Chris@485: TransformFactory::haveInstalledTransforms() Chris@485: { Chris@485: populateTransforms(); Chris@485: return !m_transforms.empty(); Chris@485: } Chris@485: Chris@457: TransformList Chris@457: TransformFactory::getUninstalledTransformDescriptions() Chris@457: { Chris@479: m_populatingSlowly = false; Chris@460: populateUninstalledTransforms(); Chris@457: Chris@457: std::set dset; Chris@1546: for (auto i = m_uninstalledTransforms.begin(); Chris@1429: i != m_uninstalledTransforms.end(); ++i) { Chris@600: #ifdef DEBUG_TRANSFORM_FACTORY Chris@810: cerr << "inserting transform into set: id = " << i->second.identifier << endl; Chris@600: #endif Chris@1429: dset.insert(i->second); Chris@457: } Chris@457: Chris@457: TransformList list; Chris@1546: for (auto i = dset.begin(); i != dset.end(); ++i) { Chris@600: #ifdef DEBUG_TRANSFORM_FACTORY Chris@810: cerr << "inserting transform into uninstalled list: id = " << i->identifier << endl; Chris@600: #endif Chris@1429: list.push_back(*i); Chris@457: } Chris@457: Chris@457: return list; Chris@457: } Chris@457: Chris@457: TransformDescription Chris@457: TransformFactory::getUninstalledTransformDescription(TransformId id) Chris@457: { Chris@479: m_populatingSlowly = false; Chris@460: populateUninstalledTransforms(); Chris@457: Chris@457: if (m_uninstalledTransforms.find(id) == m_uninstalledTransforms.end()) { Chris@457: return TransformDescription(); Chris@457: } Chris@457: Chris@457: return m_uninstalledTransforms[id]; Chris@457: } Chris@457: Chris@485: bool Chris@485: TransformFactory::haveUninstalledTransforms(bool waitForCheckToComplete) Chris@485: { Chris@485: if (waitForCheckToComplete) { Chris@485: populateUninstalledTransforms(); Chris@485: } else { Chris@485: if (!m_uninstalledTransformsMutex.tryLock()) { Chris@485: return false; Chris@485: } Chris@485: if (!m_uninstalledTransformsPopulated) { Chris@485: m_uninstalledTransformsMutex.unlock(); Chris@485: return false; Chris@485: } Chris@485: m_uninstalledTransformsMutex.unlock(); Chris@485: } Chris@485: Chris@485: return !m_uninstalledTransforms.empty(); Chris@485: } Chris@485: Chris@457: TransformFactory::TransformInstallStatus Chris@457: TransformFactory::getTransformInstallStatus(TransformId id) Chris@457: { Chris@460: populateTransforms(); Chris@457: Chris@457: if (m_transforms.find(id) != m_transforms.end()) { Chris@457: return TransformInstalled; Chris@457: } Chris@473: Chris@473: if (!m_uninstalledTransformsMutex.tryLock()) { Chris@473: // uninstalled transforms are being populated; this may take some time, Chris@473: // and they aren't critical Chris@473: return TransformUnknown; Chris@473: } Chris@473: Chris@473: if (!m_uninstalledTransformsPopulated) { Chris@473: m_uninstalledTransformsMutex.unlock(); Chris@479: m_populatingSlowly = false; Chris@473: populateUninstalledTransforms(); Chris@473: m_uninstalledTransformsMutex.lock(); Chris@473: } Chris@473: Chris@457: if (m_uninstalledTransforms.find(id) != m_uninstalledTransforms.end()) { Chris@482: m_uninstalledTransformsMutex.unlock(); Chris@457: return TransformNotInstalled; Chris@457: } Chris@473: Chris@473: m_uninstalledTransformsMutex.unlock(); Chris@457: return TransformUnknown; Chris@457: } Chris@457: Chris@457: Chris@487: std::vector Chris@330: TransformFactory::getAllTransformTypes() Chris@330: { Chris@460: populateTransforms(); Chris@330: Chris@487: std::set types; Chris@1546: for (auto i = m_transforms.begin(); i != m_transforms.end(); ++i) { Chris@330: types.insert(i->second.type); Chris@330: } Chris@330: Chris@487: std::vector rv; Chris@1546: for (auto i = types.begin(); i != types.end(); ++i) { Chris@330: rv.push_back(*i); Chris@330: } Chris@330: Chris@330: return rv; Chris@330: } Chris@330: Chris@330: std::vector Chris@487: TransformFactory::getTransformCategories(TransformDescription::Type transformType) Chris@330: { Chris@460: populateTransforms(); Chris@330: Chris@1546: std::set> Chris@1546: categories(TransformDescription::compareUserStrings); Chris@1546: Chris@1546: for (auto i = m_transforms.begin(); i != m_transforms.end(); ++i) { Chris@330: if (i->second.type == transformType) { Chris@330: categories.insert(i->second.category); Chris@330: } Chris@330: } Chris@330: Chris@330: bool haveEmpty = false; Chris@330: Chris@330: std::vector rv; Chris@1546: for (auto i = categories.begin(); i != categories.end(); ++i) { Chris@330: if (*i != "") rv.push_back(*i); Chris@330: else haveEmpty = true; Chris@330: } Chris@330: Chris@330: if (haveEmpty) rv.push_back(""); // make sure empty category sorts last Chris@330: Chris@330: return rv; Chris@330: } Chris@330: Chris@330: std::vector Chris@487: TransformFactory::getTransformMakers(TransformDescription::Type transformType) Chris@330: { Chris@460: populateTransforms(); Chris@330: Chris@1546: std::set> Chris@1546: makers(TransformDescription::compareUserStrings); Chris@1546: Chris@1546: for (auto i = m_transforms.begin(); i != m_transforms.end(); ++i) { Chris@330: if (i->second.type == transformType) { Chris@330: makers.insert(i->second.maker); Chris@330: } Chris@330: } Chris@330: Chris@330: bool haveEmpty = false; Chris@330: Chris@330: std::vector rv; Chris@1546: for (auto i = makers.begin(); i != makers.end(); ++i) { Chris@330: if (*i != "") rv.push_back(*i); Chris@330: else haveEmpty = true; Chris@330: } Chris@330: Chris@330: if (haveEmpty) rv.push_back(""); // make sure empty category sorts last Chris@330: Chris@330: return rv; Chris@330: } Chris@330: Chris@487: QString Chris@487: TransformFactory::getTransformTypeName(TransformDescription::Type type) const Chris@487: { Chris@487: switch (type) { Chris@487: case TransformDescription::Analysis: return tr("Analysis"); Chris@487: case TransformDescription::Effects: return tr("Effects"); Chris@487: case TransformDescription::EffectsData: return tr("Effects Data"); Chris@487: case TransformDescription::Generator: return tr("Generator"); Chris@487: case TransformDescription::UnknownType: return tr("Other"); Chris@487: } Chris@489: return tr("Other"); Chris@487: } Chris@487: Chris@1865: bool Chris@1865: TransformFactory::havePopulated() Chris@1865: { Chris@1865: MutexLocker locker(&m_transformsMutex, "TransformFactory::havePopulated"); Chris@1865: return m_transformsPopulated; Chris@1865: } Chris@1865: Chris@330: void Chris@330: TransformFactory::populateTransforms() Chris@330: { Chris@460: MutexLocker locker(&m_transformsMutex, Chris@460: "TransformFactory::populateTransforms"); Chris@460: if (m_transformsPopulated) { Chris@460: return; Chris@460: } Chris@460: Chris@330: TransformDescriptionMap transforms; Chris@330: Chris@330: populateFeatureExtractionPlugins(transforms); Chris@576: if (m_exiting) return; Chris@330: populateRealTimePlugins(transforms); Chris@576: if (m_exiting) return; Chris@330: Chris@330: // disambiguate plugins with similar names Chris@330: Chris@330: std::map names; Chris@330: std::map pluginSources; Chris@330: std::map pluginMakers; Chris@330: Chris@330: for (TransformDescriptionMap::iterator i = transforms.begin(); Chris@330: i != transforms.end(); ++i) { Chris@330: Chris@330: TransformDescription desc = i->second; Chris@330: Chris@330: QString td = desc.name; Chris@330: QString tn = td.section(": ", 0, 0); Chris@330: QString pn = desc.identifier.section(":", 1, 1); Chris@330: Chris@330: if (pluginSources.find(tn) != pluginSources.end()) { Chris@330: if (pluginSources[tn] != pn && pluginMakers[tn] != desc.maker) { Chris@330: ++names[tn]; Chris@330: } Chris@330: } else { Chris@330: ++names[tn]; Chris@330: pluginSources[tn] = pn; Chris@330: pluginMakers[tn] = desc.maker; Chris@330: } Chris@330: } Chris@330: Chris@330: std::map counts; Chris@330: m_transforms.clear(); Chris@330: Chris@330: for (TransformDescriptionMap::iterator i = transforms.begin(); Chris@330: i != transforms.end(); ++i) { Chris@330: Chris@330: TransformDescription desc = i->second; Chris@1429: QString identifier = desc.identifier; Chris@330: QString maker = desc.maker; Chris@330: Chris@330: QString td = desc.name; Chris@330: QString tn = td.section(": ", 0, 0); Chris@330: QString to = td.section(": ", 1); Chris@330: Chris@1429: if (names[tn] > 1) { Chris@330: maker.replace(QRegExp(tr(" [\\(<].*$")), ""); Chris@1429: tn = QString("%1 [%2]").arg(tn).arg(maker); Chris@1429: } Chris@330: Chris@330: if (to != "") { Chris@330: desc.name = QString("%1: %2").arg(tn).arg(to); Chris@330: } else { Chris@330: desc.name = tn; Chris@330: } Chris@330: Chris@1429: m_transforms[identifier] = desc; Chris@1429: } Chris@457: Chris@457: m_transformsPopulated = true; Chris@1865: Chris@1865: #ifdef DEBUG_TRANSFORM_FACTORY Chris@1865: SVCERR << "populateTransforms exiting" << endl; Chris@1865: #endif Chris@1865: Chris@1865: emit transformsPopulated(); Chris@330: } Chris@330: Chris@330: void Chris@330: TransformFactory::populateFeatureExtractionPlugins(TransformDescriptionMap &transforms) Chris@330: { Chris@1225: FeatureExtractionPluginFactory *factory = Chris@1225: FeatureExtractionPluginFactory::instance(); Chris@1227: Chris@1227: QString errorMessage; Chris@1227: std::vector plugs = factory->getPluginIdentifiers(errorMessage); Chris@1227: if (errorMessage != "") { Chris@1227: m_errorString = tr("Failed to list Vamp plugins: %1").arg(errorMessage); Chris@1227: } Chris@1225: Chris@576: if (m_exiting) return; Chris@330: Chris@1546: for (int i = 0; in_range_for(plugs, i); ++i) { Chris@330: Chris@1429: QString pluginId = plugs[i]; Chris@330: Chris@1223: piper_vamp::PluginStaticData psd = factory->getPluginStaticData(pluginId); Chris@330: Chris@1223: if (psd.pluginKey == "") { Chris@1223: cerr << "WARNING: TransformFactory::populateTransforms: No plugin static data available for instance " << pluginId << endl; Chris@1223: continue; Chris@1223: } Chris@1223: Chris@1223: QString pluginName = QString::fromStdString(psd.basic.name); Chris@330: QString category = factory->getPluginCategory(pluginId); Chris@1223: Chris@1223: const auto &basicOutputs = psd.basicOutputInfo; Chris@330: Chris@1223: for (const auto &o: basicOutputs) { Chris@330: Chris@1223: QString outputName = QString::fromStdString(o.name); Chris@330: Chris@1429: QString transformId = QString("%1:%2") Chris@1223: .arg(pluginId).arg(QString::fromStdString(o.identifier)); Chris@330: Chris@1429: QString userName; Chris@330: QString friendlyName; Chris@1223: //!!! return to this QString units = outputs[j].unit.c_str(); Chris@1223: QString description = QString::fromStdString(psd.basic.description); Chris@1223: QString maker = QString::fromStdString(psd.maker); Chris@330: if (maker == "") maker = tr(""); Chris@330: Chris@443: QString longDescription = description; Chris@443: Chris@443: if (longDescription == "") { Chris@1223: if (basicOutputs.size() == 1) { Chris@443: longDescription = tr("Extract features using \"%1\" plugin (from %2)") Chris@330: .arg(pluginName).arg(maker); Chris@330: } else { Chris@443: longDescription = tr("Extract features using \"%1\" output of \"%2\" plugin (from %3)") Chris@1223: .arg(outputName).arg(pluginName).arg(maker); Chris@330: } Chris@330: } else { Chris@1223: if (basicOutputs.size() == 1) { Chris@443: longDescription = tr("%1 using \"%2\" plugin (from %3)") Chris@443: .arg(longDescription).arg(pluginName).arg(maker); Chris@330: } else { Chris@443: longDescription = tr("%1 using \"%2\" output of \"%3\" plugin (from %4)") Chris@1223: .arg(longDescription).arg(outputName).arg(pluginName).arg(maker); Chris@330: } Chris@330: } Chris@330: Chris@1429: if (basicOutputs.size() == 1) { Chris@1429: userName = pluginName; Chris@330: friendlyName = pluginName; Chris@1429: } else { Chris@1429: userName = QString("%1: %2").arg(pluginName).arg(outputName); Chris@1223: friendlyName = outputName; Chris@1429: } Chris@330: Chris@1223: bool configurable = (!psd.programs.empty() || Chris@1223: !psd.parameters.empty()); Chris@330: Chris@600: #ifdef DEBUG_TRANSFORM_FACTORY Chris@686: cerr << "Feature extraction plugin transform: " << transformId << " friendly name: " << friendlyName << endl; Chris@600: #endif Chris@330: Chris@1429: transforms[transformId] = Chris@487: TransformDescription(TransformDescription::Analysis, Chris@332: category, Chris@332: transformId, Chris@332: userName, Chris@332: friendlyName, Chris@332: description, Chris@443: longDescription, Chris@332: maker, Chris@1223: //!!! units, Chris@1223: "", Chris@332: configurable); Chris@1429: } Chris@330: } Chris@330: } Chris@330: Chris@330: void Chris@330: TransformFactory::populateRealTimePlugins(TransformDescriptionMap &transforms) Chris@330: { Chris@330: std::vector plugs = Chris@1429: RealTimePluginFactory::getAllPluginIdentifiers(); Chris@576: if (m_exiting) return; Chris@330: Chris@330: static QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$"); Chris@330: Chris@1546: for (int i = 0; in_range_for(plugs, i); ++i) { Chris@330: Chris@1429: QString pluginId = plugs[i]; Chris@330: Chris@330: RealTimePluginFactory *factory = Chris@330: RealTimePluginFactory::instanceFor(pluginId); Chris@330: Chris@1429: if (!factory) { Chris@1429: cerr << "WARNING: TransformFactory::populateTransforms: No real time plugin factory for instance " << pluginId << endl; Chris@1429: continue; Chris@1429: } Chris@330: Chris@1830: RealTimePluginDescriptor descriptor = Chris@330: factory->getPluginDescriptor(pluginId); Chris@330: Chris@1830: if (descriptor.name == "") { Chris@1429: cerr << "WARNING: TransformFactory::populateTransforms: Failed to query plugin " << pluginId << endl; Chris@1429: continue; Chris@1429: } Chris@1429: Chris@1830: //!!! if (descriptor.controlOutputPortCount == 0 || Chris@1830: // descriptor.audioInputPortCount == 0) continue; Chris@330: Chris@1830: // cout << "TransformFactory::populateRealTimePlugins: plugin " << pluginId << " has " << descriptor.controlOutputPortCount << " control output ports, " << descriptor.audioOutputPortCount << " audio outputs, " << descriptor.audioInputPortCount << " audio inputs" << endl; Chris@1429: Chris@1830: QString pluginName = descriptor.name.c_str(); Chris@330: QString category = factory->getPluginCategory(pluginId); Chris@1830: bool configurable = (descriptor.parameterCount > 0); Chris@1830: QString maker = descriptor.maker.c_str(); Chris@330: if (maker == "") maker = tr(""); Chris@330: Chris@1830: if (descriptor.audioInputPortCount > 0) { Chris@330: Chris@1830: for (int j = 0; j < (int)descriptor.controlOutputPortCount; ++j) { Chris@330: Chris@330: QString transformId = QString("%1:%2").arg(pluginId).arg(j); Chris@330: QString userName; Chris@330: QString units; Chris@330: QString portName; Chris@330: Chris@1830: if (j < (int)descriptor.controlOutputPortNames.size() && Chris@1830: descriptor.controlOutputPortNames[j] != "") { Chris@330: Chris@1830: portName = descriptor.controlOutputPortNames[j].c_str(); Chris@330: Chris@330: userName = tr("%1: %2") Chris@330: .arg(pluginName) Chris@330: .arg(portName); Chris@330: Chris@330: if (unitRE.indexIn(portName) >= 0) { Chris@330: units = unitRE.cap(1); Chris@330: } Chris@330: Chris@1830: } else if (descriptor.controlOutputPortCount > 1) { Chris@330: Chris@330: userName = tr("%1: Output %2") Chris@330: .arg(pluginName) Chris@330: .arg(j + 1); Chris@330: Chris@330: } else { Chris@330: Chris@330: userName = pluginName; Chris@330: } Chris@330: Chris@330: QString description; Chris@330: Chris@330: if (portName != "") { Chris@330: description = tr("Extract \"%1\" data output from \"%2\" effect plugin (from %3)") Chris@330: .arg(portName) Chris@330: .arg(pluginName) Chris@330: .arg(maker); Chris@330: } else { Chris@330: description = tr("Extract data output %1 from \"%2\" effect plugin (from %3)") Chris@330: .arg(j + 1) Chris@330: .arg(pluginName) Chris@330: .arg(maker); Chris@330: } Chris@330: Chris@330: transforms[transformId] = Chris@487: TransformDescription(TransformDescription::EffectsData, Chris@332: category, Chris@332: transformId, Chris@332: userName, Chris@332: userName, Chris@443: "", Chris@332: description, Chris@332: maker, Chris@332: units, Chris@332: configurable); Chris@330: } Chris@330: } Chris@330: Chris@1830: if (!descriptor.isSynth || descriptor.audioInputPortCount > 0) { Chris@330: Chris@1830: if (descriptor.audioOutputPortCount > 0) { Chris@330: Chris@330: QString transformId = QString("%1:A").arg(pluginId); Chris@487: TransformDescription::Type type = TransformDescription::Effects; Chris@330: Chris@330: QString description = tr("Transform audio signal with \"%1\" effect plugin (from %2)") Chris@330: .arg(pluginName) Chris@330: .arg(maker); Chris@330: Chris@1830: if (descriptor.audioInputPortCount == 0) { Chris@487: type = TransformDescription::Generator; Chris@330: QString description = tr("Generate audio signal using \"%1\" plugin (from %2)") Chris@330: .arg(pluginName) Chris@330: .arg(maker); Chris@330: } Chris@330: Chris@330: transforms[transformId] = Chris@330: TransformDescription(type, Chris@332: category, Chris@332: transformId, Chris@332: pluginName, Chris@332: pluginName, Chris@443: "", Chris@332: description, Chris@332: maker, Chris@332: "", Chris@332: configurable); Chris@330: } Chris@330: } Chris@330: } Chris@330: } Chris@330: Chris@457: void Chris@457: TransformFactory::populateUninstalledTransforms() Chris@457: { Chris@576: if (m_exiting) return; Chris@576: Chris@460: populateTransforms(); Chris@576: if (m_exiting) return; Chris@460: Chris@460: MutexLocker locker(&m_uninstalledTransformsMutex, Chris@460: "TransformFactory::populateUninstalledTransforms"); Chris@460: if (m_uninstalledTransformsPopulated) return; Chris@460: Chris@461: PluginRDFIndexer::getInstance()->indexConfiguredURLs(); Chris@576: if (m_exiting) return; Chris@457: Chris@1844: PluginRDFIndexer::getInstance()->performConsistencyChecks(); Chris@1844: Chris@457: //!!! This will be amazingly slow Chris@457: Chris@457: QStringList ids = PluginRDFIndexer::getInstance()->getIndexedPluginIds(); Chris@457: Chris@457: for (QStringList::const_iterator i = ids.begin(); i != ids.end(); ++i) { Chris@457: Chris@457: PluginRDFDescription desc(*i); Chris@457: Chris@457: QString name = desc.getPluginName(); Chris@600: #ifdef DEBUG_TRANSFORM_FACTORY Chris@600: if (name == "") { Chris@1844: SVCERR << "TransformFactory::populateUninstalledTransforms: " Chris@810: << "No name available for plugin " << *i Chris@810: << ", skipping" << endl; Chris@600: continue; Chris@600: } Chris@600: #endif Chris@457: Chris@457: QString description = desc.getPluginDescription(); Chris@457: QString maker = desc.getPluginMaker(); Chris@1845: Provider provider = desc.getPluginProvider(); Chris@457: Chris@457: QStringList oids = desc.getOutputIds(); Chris@457: Chris@457: for (QStringList::const_iterator j = oids.begin(); j != oids.end(); ++j) { Chris@457: Chris@457: TransformId tid = Transform::getIdentifierForPluginOutput(*i, *j); Chris@457: Chris@457: if (m_transforms.find(tid) != m_transforms.end()) { Chris@600: #ifdef DEBUG_TRANSFORM_FACTORY Chris@1844: SVCERR << "TransformFactory::populateUninstalledTransforms: " Chris@1845: << tid << " is installed; adding provider if present, skipping rest" << endl; Chris@600: #endif Chris@1845: if (provider != Provider()) { Chris@1845: if (m_transforms[tid].provider == Provider()) { Chris@1845: m_transforms[tid].provider = provider; Chris@468: } Chris@468: } Chris@457: continue; Chris@457: } Chris@457: Chris@600: #ifdef DEBUG_TRANSFORM_FACTORY Chris@1844: SVCERR << "TransformFactory::populateUninstalledTransforms: " Chris@1844: << "adding " << tid << endl; Chris@600: #endif Chris@457: Chris@457: QString oname = desc.getOutputName(*j); Chris@457: if (oname == "") oname = *j; Chris@457: Chris@457: TransformDescription td; Chris@487: td.type = TransformDescription::Analysis; Chris@457: td.category = ""; Chris@457: td.identifier = tid; Chris@457: Chris@457: if (oids.size() == 1) { Chris@457: td.name = name; Chris@457: } else if (name != "") { Chris@457: td.name = tr("%1: %2").arg(name).arg(oname); Chris@457: } Chris@457: Chris@462: QString longDescription = description; Chris@462: //!!! basically duplicated from above Chris@462: if (longDescription == "") { Chris@462: if (oids.size() == 1) { Chris@462: longDescription = tr("Extract features using \"%1\" plugin (from %2)") Chris@462: .arg(name).arg(maker); Chris@462: } else { Chris@462: longDescription = tr("Extract features using \"%1\" output of \"%2\" plugin (from %3)") Chris@462: .arg(oname).arg(name).arg(maker); Chris@462: } Chris@462: } else { Chris@462: if (oids.size() == 1) { Chris@462: longDescription = tr("%1 using \"%2\" plugin (from %3)") Chris@462: .arg(longDescription).arg(name).arg(maker); Chris@462: } else { Chris@462: longDescription = tr("%1 using \"%2\" output of \"%3\" plugin (from %4)") Chris@462: .arg(longDescription).arg(oname).arg(name).arg(maker); Chris@462: } Chris@462: } Chris@462: Chris@457: td.friendlyName = name; //!!!??? Chris@457: td.description = description; Chris@462: td.longDescription = longDescription; Chris@457: td.maker = maker; Chris@1845: td.provider = provider; Chris@457: td.units = ""; Chris@457: td.configurable = false; Chris@457: Chris@457: m_uninstalledTransforms[tid] = td; Chris@457: } Chris@574: Chris@576: if (m_exiting) return; Chris@457: } Chris@457: Chris@457: m_uninstalledTransformsPopulated = true; Chris@460: Chris@600: #ifdef DEBUG_TRANSFORM_FACTORY Chris@1844: SVCERR << "populateUninstalledTransforms exiting" << endl; Chris@600: #endif Chris@1865: Chris@1865: emit uninstalledTransformsPopulated(); Chris@457: } Chris@350: Chris@350: Transform Chris@1047: TransformFactory::getDefaultTransformFor(TransformId id, sv_samplerate_t rate) Chris@350: { Chris@350: Transform t; Chris@350: t.setIdentifier(id); Chris@1047: if (rate != 0) t.setSampleRate(rate); Chris@350: Chris@1264: SVDEBUG << "TransformFactory::getDefaultTransformFor: identifier \"" Chris@1264: << id << "\"" << endl; Chris@1264: Chris@1830: std::shared_ptr plugin = instantiateDefaultPluginFor(id, rate); Chris@350: Chris@350: if (plugin) { Chris@366: t.setPluginVersion(QString("%1").arg(plugin->getPluginVersion())); Chris@350: setParametersFromPlugin(t, plugin); Chris@350: makeContextConsistentWithPlugin(t, plugin); Chris@350: } Chris@350: Chris@350: return t; Chris@350: } Chris@350: Chris@1830: std::shared_ptr Chris@351: TransformFactory::instantiatePluginFor(const Transform &transform) Chris@351: { Chris@1264: SVDEBUG << "TransformFactory::instantiatePluginFor: identifier \"" Chris@1264: << transform.getIdentifier() << "\"" << endl; Chris@1264: Chris@1830: std::shared_ptr plugin = instantiateDefaultPluginFor Chris@1047: (transform.getIdentifier(), transform.getSampleRate()); Chris@508: Chris@351: if (plugin) { Chris@351: setPluginParameters(transform, plugin); Chris@351: } Chris@508: Chris@351: return plugin; Chris@351: } Chris@351: Chris@1830: std::shared_ptr Chris@1047: TransformFactory::instantiateDefaultPluginFor(TransformId identifier, Chris@1047: sv_samplerate_t rate) Chris@350: { Chris@1879: populateTransforms(); Chris@1879: Chris@350: Transform t; Chris@350: t.setIdentifier(identifier); Chris@1047: if (rate == 0) rate = 44100.0; Chris@350: QString pluginId = t.getPluginIdentifier(); Chris@350: Chris@1830: std::shared_ptr plugin = nullptr; Chris@350: Chris@350: if (t.getType() == Transform::FeatureExtraction) { Chris@350: Chris@1264: SVDEBUG << "TransformFactory::instantiateDefaultPluginFor: identifier \"" Chris@1264: << identifier << "\" is a feature extraction transform" << endl; Chris@1139: Chris@1225: FeatureExtractionPluginFactory *factory = Chris@1225: FeatureExtractionPluginFactory::instance(); Chris@350: Chris@439: if (factory) { Chris@1047: plugin = factory->instantiatePlugin(pluginId, rate); Chris@439: } Chris@350: Chris@1139: } else if (t.getType() == Transform::RealTimeEffect) { Chris@1139: Chris@1264: SVDEBUG << "TransformFactory::instantiateDefaultPluginFor: identifier \"" Chris@1264: << identifier << "\" is a real-time transform" << endl; Chris@350: Chris@350: RealTimePluginFactory *factory = Chris@350: RealTimePluginFactory::instanceFor(pluginId); Chris@439: Chris@439: if (factory) { Chris@439: plugin = factory->instantiatePlugin(pluginId, 0, 0, rate, 1024, 1); Chris@439: } Chris@1139: Chris@1139: } else { Chris@1264: SVDEBUG << "TransformFactory: ERROR: transform id \"" Chris@1264: << identifier << "\" is of unknown type" << endl; Chris@350: } Chris@350: Chris@350: return plugin; Chris@350: } Chris@350: Chris@330: bool Chris@330: TransformFactory::haveTransform(TransformId identifier) Chris@330: { Chris@460: populateTransforms(); Chris@330: return (m_transforms.find(identifier) != m_transforms.end()); Chris@330: } Chris@330: Chris@330: QString Chris@330: TransformFactory::getTransformName(TransformId identifier) Chris@330: { Chris@330: if (m_transforms.find(identifier) != m_transforms.end()) { Chris@1429: return m_transforms[identifier].name; Chris@330: } else return ""; Chris@330: } Chris@330: Chris@330: QString Chris@330: TransformFactory::getTransformFriendlyName(TransformId identifier) Chris@330: { Chris@330: if (m_transforms.find(identifier) != m_transforms.end()) { Chris@1429: return m_transforms[identifier].friendlyName; Chris@330: } else return ""; Chris@330: } Chris@330: Chris@330: QString Chris@330: TransformFactory::getTransformUnits(TransformId identifier) Chris@330: { Chris@330: if (m_transforms.find(identifier) != m_transforms.end()) { Chris@1429: return m_transforms[identifier].units; Chris@330: } else return ""; Chris@330: } Chris@330: Chris@1845: Provider Chris@1845: TransformFactory::getTransformProvider(TransformId identifier) Chris@472: { Chris@472: if (m_transforms.find(identifier) != m_transforms.end()) { Chris@1845: return m_transforms[identifier].provider; Chris@1845: } else return {}; Chris@472: } Chris@472: Chris@350: Vamp::Plugin::InputDomain Chris@350: TransformFactory::getTransformInputDomain(TransformId identifier) Chris@350: { Chris@350: Transform transform; Chris@350: transform.setIdentifier(identifier); Chris@350: Chris@1264: SVDEBUG << "TransformFactory::getTransformInputDomain: identifier \"" Chris@1264: << identifier << "\"" << endl; Chris@1264: Chris@350: if (transform.getType() != Transform::FeatureExtraction) { Chris@350: return Vamp::Plugin::TimeDomain; Chris@350: } Chris@350: Chris@1830: std::shared_ptr plugin = Chris@1830: std::dynamic_pointer_cast Chris@1830: (instantiateDefaultPluginFor(identifier, 0)); Chris@350: Chris@350: if (plugin) { Chris@350: Vamp::Plugin::InputDomain d = plugin->getInputDomain(); Chris@350: return d; Chris@350: } Chris@350: Chris@350: return Vamp::Plugin::TimeDomain; Chris@350: } Chris@350: Chris@330: bool Chris@330: TransformFactory::isTransformConfigurable(TransformId identifier) Chris@330: { Chris@330: if (m_transforms.find(identifier) != m_transforms.end()) { Chris@1429: return m_transforms[identifier].configurable; Chris@330: } else return false; Chris@330: } Chris@330: Chris@330: bool Chris@330: TransformFactory::getTransformChannelRange(TransformId identifier, Chris@330: int &min, int &max) Chris@330: { Chris@330: QString id = identifier.section(':', 0, 2); Chris@330: Chris@1225: if (RealTimePluginFactory::instanceFor(id)) { Chris@350: Chris@1830: RealTimePluginDescriptor descriptor = Chris@330: RealTimePluginFactory::instanceFor(id)-> Chris@330: getPluginDescriptor(id); Chris@1830: if (descriptor.name == "") { Chris@1830: return false; Chris@1830: } Chris@330: Chris@1830: min = descriptor.audioInputPortCount; Chris@1830: max = descriptor.audioInputPortCount; Chris@330: Chris@330: return true; Chris@1225: Chris@1225: } else { Chris@1225: Chris@1225: auto psd = FeatureExtractionPluginFactory::instance()-> Chris@1225: getPluginStaticData(id); Chris@1225: if (psd.pluginKey == "") return false; Chris@1225: Chris@1225: min = (int)psd.minChannelCount; Chris@1225: max = (int)psd.maxChannelCount; Chris@1225: Chris@1225: return true; Chris@330: } Chris@330: Chris@330: return false; Chris@330: } Chris@332: Chris@332: void Chris@332: TransformFactory::setParametersFromPlugin(Transform &transform, Chris@1830: std::shared_ptr plugin) Chris@332: { Chris@332: Transform::ParameterMap pmap; Chris@332: Chris@350: //!!! record plugin & API version Chris@350: Chris@350: //!!! check that this is the right plugin! Chris@350: Chris@332: Vamp::PluginBase::ParameterList parameters = Chris@332: plugin->getParameterDescriptors(); Chris@332: Chris@332: for (Vamp::PluginBase::ParameterList::const_iterator i = parameters.begin(); Chris@332: i != parameters.end(); ++i) { Chris@332: pmap[i->identifier.c_str()] = plugin->getParameter(i->identifier); Chris@810: // cerr << "TransformFactory::setParametersFromPlugin: parameter " Chris@583: // << i->identifier << " -> value " << Chris@687: // pmap[i->identifier.c_str()] << endl; Chris@332: } Chris@332: Chris@332: transform.setParameters(pmap); Chris@332: Chris@332: if (plugin->getPrograms().empty()) { Chris@332: transform.setProgram(""); Chris@332: } else { Chris@332: transform.setProgram(plugin->getCurrentProgram().c_str()); Chris@332: } Chris@332: Chris@1830: std::shared_ptr rtpi = Chris@1830: std::dynamic_pointer_cast(plugin); Chris@332: Chris@332: Transform::ConfigurationMap cmap; Chris@332: Chris@332: if (rtpi) { Chris@332: Chris@332: RealTimePluginInstance::ConfigurationPairMap configurePairs = Chris@332: rtpi->getConfigurePairs(); Chris@332: Chris@332: for (RealTimePluginInstance::ConfigurationPairMap::const_iterator i Chris@332: = configurePairs.begin(); i != configurePairs.end(); ++i) { Chris@332: cmap[i->first.c_str()] = i->second.c_str(); Chris@332: } Chris@332: } Chris@332: Chris@332: transform.setConfiguration(cmap); Chris@332: } Chris@332: Chris@332: void Chris@350: TransformFactory::setPluginParameters(const Transform &transform, Chris@1830: std::shared_ptr plugin) Chris@350: { Chris@350: //!!! check plugin & API version (see e.g. PluginXml::setParameters) Chris@350: Chris@350: //!!! check that this is the right plugin! Chris@350: Chris@1830: std::shared_ptr rtpi = Chris@1830: std::dynamic_pointer_cast(plugin); Chris@350: Chris@350: if (rtpi) { Chris@350: const Transform::ConfigurationMap &cmap = transform.getConfiguration(); Chris@350: for (Transform::ConfigurationMap::const_iterator i = cmap.begin(); Chris@350: i != cmap.end(); ++i) { Chris@350: rtpi->configure(i->first.toStdString(), i->second.toStdString()); Chris@350: } Chris@350: } Chris@350: Chris@350: if (transform.getProgram() != "") { Chris@350: plugin->selectProgram(transform.getProgram().toStdString()); Chris@350: } Chris@350: Chris@350: const Transform::ParameterMap &pmap = transform.getParameters(); Chris@350: Chris@350: Vamp::PluginBase::ParameterList parameters = Chris@350: plugin->getParameterDescriptors(); Chris@350: Chris@350: for (Vamp::PluginBase::ParameterList::const_iterator i = parameters.begin(); Chris@350: i != parameters.end(); ++i) { Chris@350: QString key = i->identifier.c_str(); Chris@350: Transform::ParameterMap::const_iterator pmi = pmap.find(key); Chris@350: if (pmi != pmap.end()) { Chris@350: plugin->setParameter(i->identifier, pmi->second); Chris@350: } Chris@350: } Chris@350: } Chris@350: Chris@350: void Chris@332: TransformFactory::makeContextConsistentWithPlugin(Transform &transform, Chris@1830: std::shared_ptr plugin) Chris@332: { Chris@1830: std::shared_ptr vp = Chris@1830: std::dynamic_pointer_cast(plugin); Chris@332: Chris@332: if (!vp) { Chris@332: // time domain input for real-time effects plugin Chris@332: if (!transform.getBlockSize()) { Chris@332: if (!transform.getStepSize()) transform.setStepSize(1024); Chris@332: transform.setBlockSize(transform.getStepSize()); Chris@332: } else { Chris@332: transform.setStepSize(transform.getBlockSize()); Chris@332: } Chris@332: } else { Chris@332: Vamp::Plugin::InputDomain domain = vp->getInputDomain(); Chris@332: if (!transform.getStepSize()) { Chris@1039: transform.setStepSize((int)vp->getPreferredStepSize()); Chris@332: } Chris@332: if (!transform.getBlockSize()) { Chris@1039: transform.setBlockSize((int)vp->getPreferredBlockSize()); Chris@332: } Chris@332: if (!transform.getBlockSize()) { Chris@332: transform.setBlockSize(1024); Chris@332: } Chris@332: if (!transform.getStepSize()) { Chris@332: if (domain == Vamp::Plugin::FrequencyDomain) { Chris@443: // cerr << "frequency domain, step = " << blockSize/2 << endl; Chris@332: transform.setStepSize(transform.getBlockSize()/2); Chris@332: } else { Chris@443: // cerr << "time domain, step = " << blockSize/2 << endl; Chris@332: transform.setStepSize(transform.getBlockSize()); Chris@332: } Chris@332: } Chris@332: } Chris@332: } Chris@332: Chris@350: QString Chris@350: TransformFactory::getPluginConfigurationXml(const Transform &t) Chris@332: { Chris@350: QString xml; Chris@350: Chris@1264: SVDEBUG << "TransformFactory::getPluginConfigurationXml: identifier \"" Chris@1264: << t.getIdentifier() << "\"" << endl; Chris@1264: Chris@1830: auto plugin = instantiateDefaultPluginFor(t.getIdentifier(), 0); Chris@350: if (!plugin) { Chris@1264: SVDEBUG << "TransformFactory::getPluginConfigurationXml: " Chris@1264: << "Unable to instantiate plugin for transform \"" Chris@1264: << t.getIdentifier() << "\"" << endl; Chris@350: return xml; Chris@332: } Chris@332: Chris@351: setPluginParameters(t, plugin); Chris@351: Chris@350: QTextStream out(&xml); Chris@350: PluginXml(plugin).toXml(out); Chris@332: Chris@350: return xml; Chris@350: } Chris@332: Chris@350: void Chris@350: TransformFactory::setParametersFromPluginConfigurationXml(Transform &t, Chris@350: QString xml) Chris@350: { Chris@1264: SVDEBUG << "TransformFactory::setParametersFromPluginConfigurationXml: identifier \"" Chris@1264: << t.getIdentifier() << "\"" << endl; Chris@1264: Chris@1830: auto plugin = instantiateDefaultPluginFor(t.getIdentifier(), 0); Chris@350: if (!plugin) { Chris@1264: SVDEBUG << "TransformFactory::setParametersFromPluginConfigurationXml: " Chris@1264: << "Unable to instantiate plugin for transform \"" Chris@1264: << t.getIdentifier() << "\"" << endl; Chris@350: return; Chris@332: } Chris@332: Chris@350: PluginXml(plugin).setParametersFromXml(xml); Chris@350: setParametersFromPlugin(t, plugin); Chris@332: } Chris@332: Chris@443: TransformFactory::SearchResults Chris@443: TransformFactory::search(QString keyword) Chris@443: { Chris@443: QStringList keywords; Chris@443: keywords << keyword; Chris@443: return search(keywords); Chris@443: } Chris@443: Chris@443: TransformFactory::SearchResults Chris@443: TransformFactory::search(QStringList keywords) Chris@443: { Chris@460: populateTransforms(); Chris@443: Chris@1842: SearchResults results = searchUnadjusted(keywords); Chris@1842: Chris@447: if (keywords.size() > 1) { Chris@1842: Chris@1842: // If there are any hits for all keywords in a row, put them Chris@1842: // in (replacing previous hits for the same transforms) but Chris@1842: // ensure they score more than any of the others Chris@1842: Chris@1842: int maxScore = 0; Chris@1842: for (auto r: results) { Chris@1842: if (r.second.score > maxScore) { Chris@1842: maxScore = r.second.score; Chris@1842: } Chris@1842: } Chris@1842: Chris@1842: QStringList oneBigKeyword; Chris@1842: oneBigKeyword << keywords.join(" "); Chris@1842: SearchResults oneBigKeywordResults = searchUnadjusted(oneBigKeyword); Chris@1842: for (auto r: oneBigKeywordResults) { Chris@1842: results[r.first] = r.second; Chris@1842: results[r.first].score += maxScore; Chris@1842: } Chris@447: } Chris@447: Chris@1842: return results; Chris@1842: } Chris@1842: Chris@1842: TransformFactory::SearchResults Chris@1842: TransformFactory::searchUnadjusted(QStringList keywords) Chris@1842: { Chris@443: SearchResults results; Chris@457: TextMatcher matcher; Chris@443: Chris@443: for (TransformDescriptionMap::const_iterator i = m_transforms.begin(); Chris@443: i != m_transforms.end(); ++i) { Chris@443: Chris@457: TextMatcher::Match match; Chris@443: Chris@457: match.key = i->first; Chris@443: Chris@487: matcher.test(match, keywords, Chris@487: getTransformTypeName(i->second.type), Chris@487: tr("Plugin type"), 5); Chris@487: Chris@457: matcher.test(match, keywords, i->second.category, tr("Category"), 20); Chris@457: matcher.test(match, keywords, i->second.identifier, tr("System Identifier"), 6); Chris@457: matcher.test(match, keywords, i->second.name, tr("Name"), 30); Chris@457: matcher.test(match, keywords, i->second.description, tr("Description"), 20); Chris@457: matcher.test(match, keywords, i->second.maker, tr("Maker"), 10); Chris@457: matcher.test(match, keywords, i->second.units, tr("Units"), 10); Chris@457: Chris@457: if (match.score > 0) results[i->first] = match; Chris@457: } Chris@457: Chris@460: if (!m_uninstalledTransformsMutex.tryLock()) { Chris@460: // uninstalled transforms are being populated; this may take some time, Chris@484: // and they aren't critical, but we will speed them up if necessary Chris@1264: SVDEBUG << "TransformFactory::search: Uninstalled transforms mutex is held, skipping" << endl; Chris@484: m_populatingSlowly = false; Chris@460: return results; Chris@460: } Chris@460: Chris@460: if (!m_uninstalledTransformsPopulated) { Chris@1264: SVDEBUG << "WARNING: TransformFactory::search: Uninstalled transforms are not populated yet" << endl Chris@1264: << "and are not being populated either -- was the thread not started correctly?" << endl; Chris@460: m_uninstalledTransformsMutex.unlock(); Chris@460: return results; Chris@460: } Chris@460: Chris@460: m_uninstalledTransformsMutex.unlock(); Chris@457: Chris@457: for (TransformDescriptionMap::const_iterator i = m_uninstalledTransforms.begin(); Chris@457: i != m_uninstalledTransforms.end(); ++i) { Chris@457: Chris@457: TextMatcher::Match match; Chris@457: Chris@457: match.key = i->first; Chris@457: Chris@487: matcher.test(match, keywords, Chris@487: getTransformTypeName(i->second.type), Chris@487: tr("Plugin type"), 2); Chris@487: Chris@457: matcher.test(match, keywords, i->second.category, tr("Category"), 10); Chris@457: matcher.test(match, keywords, i->second.identifier, tr("System Identifier"), 3); Chris@457: matcher.test(match, keywords, i->second.name, tr("Name"), 15); Chris@457: matcher.test(match, keywords, i->second.description, tr("Description"), 10); Chris@457: matcher.test(match, keywords, i->second.maker, tr("Maker"), 5); Chris@457: matcher.test(match, keywords, i->second.units, tr("Units"), 5); Chris@443: Chris@443: if (match.score > 0) results[i->first] = match; Chris@443: } Chris@443: Chris@1842: #ifdef DEBUG_TRANSFORM_FACTORY Chris@1842: SVCERR << "TransformFactory::search: keywords are: " << keywords.join(", ") Chris@1842: << endl; Chris@1842: int n = int(results.size()), i = 1; Chris@1842: SVCERR << "TransformFactory::search: results (" << n << "):" << endl; Chris@1842: Chris@1842: for (const auto &r: results) { Chris@1842: QStringList frags; Chris@1842: for (const auto &f: r.second.fragments) { Chris@1842: frags << QString("{\"%1\": \"%2\"}").arg(f.first).arg(f.second); Chris@1842: } Chris@1842: SVCERR << "[" << i << "/" << n << "] id " << r.first Chris@1842: << ": score " << r.second.score Chris@1842: << ", key " << r.second.key << ", fragments " Chris@1842: << frags.join(";") << endl; Chris@1842: ++i; Chris@1842: } Chris@1842: SVCERR << endl; Chris@1842: #endif Chris@1842: Chris@443: return results; Chris@443: } Chris@443: