changeset 1222:771a17925576 piper

Merge from branch 3.0-integration
author Chris Cannam
date Thu, 20 Oct 2016 11:19:34 +0100
parents 77320e522253 (diff) a1b97df9962e (current diff)
children c2207877689d
files data/fileio/MatrixFile.cpp data/fileio/MatrixFile.h data/model/SparseModel.h svcore.pro
diffstat 8 files changed, 131 insertions(+), 371 deletions(-) [+]
line wrap: on
line diff
--- a/data/model/SparseModel.h	Thu Oct 20 11:16:22 2016 +0100
+++ b/data/model/SparseModel.h	Thu Oct 20 11:19:34 2016 +0100
@@ -730,8 +730,8 @@
     {
 	QMutexLocker locker(&m_mutex);
 	m_resolution = resolution;
+        m_rows.clear();
     }
-    m_rows.clear();
     emit modelChanged();
 }
 
@@ -743,8 +743,8 @@
 	QMutexLocker locker(&m_mutex);
 	m_points.clear();
         m_pointCount = 0;
+        m_rows.clear();
     }
-    m_rows.clear();
     emit modelChanged();
 }
 
@@ -752,12 +752,11 @@
 void
 SparseModel<PointType>::addPoint(const PointType &point)
 {
-    {
-	QMutexLocker locker(&m_mutex);
-	m_points.insert(point);
-        m_pointCount++;
-        if (point.getLabel() != "") m_hasTextLabels = true;
-    }
+    QMutexLocker locker(&m_mutex);
+
+    m_points.insert(point);
+    m_pointCount++;
+    if (point.getLabel() != "") m_hasTextLabels = true;
 
     // Even though this model is nominally sparse, there may still be
     // too many signals going on here (especially as they'll probably
@@ -784,18 +783,16 @@
 bool
 SparseModel<PointType>::containsPoint(const PointType &point)
 {
-    {
-	QMutexLocker locker(&m_mutex);
+    QMutexLocker locker(&m_mutex);
 
-	PointListIterator i = m_points.lower_bound(point);
-	typename PointType::Comparator comparator;
-	while (i != m_points.end()) {
-	    if (i->frame > point.frame) break;
-	    if (!comparator(*i, point) && !comparator(point, *i)) {
-                return true;
-	    }
-	    ++i;
-	}
+    PointListIterator i = m_points.lower_bound(point);
+    typename PointType::Comparator comparator;
+    while (i != m_points.end()) {
+        if (i->frame > point.frame) break;
+        if (!comparator(*i, point) && !comparator(point, *i)) {
+            return true;
+        }
+        ++i;
     }
 
     return false;
@@ -805,21 +802,20 @@
 void
 SparseModel<PointType>::deletePoint(const PointType &point)
 {
-    {
-	QMutexLocker locker(&m_mutex);
+    QMutexLocker locker(&m_mutex);
 
-	PointListIterator i = m_points.lower_bound(point);
-	typename PointType::Comparator comparator;
-	while (i != m_points.end()) {
-	    if (i->frame > point.frame) break;
-	    if (!comparator(*i, point) && !comparator(point, *i)) {
-		m_points.erase(i);
-                m_pointCount--;
-		break;
+    PointListIterator i = m_points.lower_bound(point);
+    typename PointType::Comparator comparator;
+    while (i != m_points.end()) {
+        if (i->frame > point.frame) break;
+        if (!comparator(*i, point) && !comparator(point, *i)) {
+            m_points.erase(i);
+            m_pointCount--;
+            break;
 	    }
-	    ++i;
-	}
+        ++i;
     }
+
 //    std::cout << "SparseOneDimensionalModel: emit modelChanged("
 //	      << point.frame << ")" << std::endl;
     m_rows.clear(); //!!! inefficient
@@ -832,6 +828,8 @@
 {
 //    std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl;
 
+    QMutexLocker locker(&m_mutex);
+
     if (m_completion != completion) {
 	m_completion = completion;
 
--- a/plugin/FeatureExtractionPluginFactory.cpp	Thu Oct 20 11:16:22 2016 +0100
+++ b/plugin/FeatureExtractionPluginFactory.cpp	Thu Oct 20 11:19:34 2016 +0100
@@ -16,13 +16,12 @@
 #include "FeatureExtractionPluginFactory.h"
 #include "PluginIdentifier.h"
 
-#include <vamp-hostsdk/PluginHostAdapter.h>
-#include <vamp-hostsdk/PluginWrapper.h>
-
 #include "system/System.h"
 
 #include "PluginScan.h"
 
+#include "vamp-client/AutoPlugin.h"
+
 #include <QDir>
 #include <QFile>
 #include <QFileInfo>
@@ -36,27 +35,6 @@
 
 //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
 
-class PluginDeletionNotifyAdapter : public Vamp::HostExt::PluginWrapper {
-public:
-    PluginDeletionNotifyAdapter(Vamp::Plugin *plugin,
-                                FeatureExtractionPluginFactory *factory) :
-        PluginWrapper(plugin), m_factory(factory) { }
-    virtual ~PluginDeletionNotifyAdapter();
-protected:
-    FeatureExtractionPluginFactory *m_factory;
-};
-
-PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
-{
-    // see notes in vamp-sdk/hostext/PluginLoader.cpp from which this is drawn
-    Vamp::Plugin *p = m_plugin;
-    delete m_plugin;
-    m_plugin = 0;
-    // acceptable use after free here, as pluginDeleted uses p only as
-    // pointer key and does not deref it
-    if (m_factory) m_factory->pluginDeleted(p);
-}
-
 static FeatureExtractionPluginFactory *_nativeInstance = 0;
 
 FeatureExtractionPluginFactory *
@@ -81,14 +59,11 @@
     return instance(type);
 }
 
-vector<QString>
-FeatureExtractionPluginFactory::getPluginPath()
+FeatureExtractionPluginFactory::FeatureExtractionPluginFactory() :
+    m_serverName("piper-cpp/bin/piper-vamp-server"),
+    m_transport(m_serverName),
+    m_client(&m_transport)
 {
-    if (!m_pluginPath.empty()) return m_pluginPath;
-
-    vector<string> p = Vamp::PluginHostAdapter::getPluginPath();
-    for (size_t i = 0; i < p.size(); ++i) m_pluginPath.push_back(p[i].c_str());
-    return m_pluginPath;
 }
 
 vector<QString>
@@ -117,269 +92,40 @@
 {
     Profiler profiler("FeatureExtractionPluginFactory::getPluginIdentifiers");
 
+    QMutexLocker locker(&m_mutex);
+
+    if (m_pluginData.empty()) {
+        populate();
+    }
+
     vector<QString> rv;
 
-    QStringList candidates = PluginScan::getInstance()->getCandidateLibrariesFor
-        (PluginScan::VampPlugin);
-    
-    for (QString soname : candidates) {
-
-        void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
-            
-        if (!libraryHandle) {
-            cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl;
-            continue;
-        }
-
-        VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
-            DLSYM(libraryHandle, "vampGetPluginDescriptor");
-
-        if (!fn) {
-            cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl;
-            if (DLCLOSE(libraryHandle) != 0) {
-                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
-            }
-            continue;
-        }
-
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-            cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl;
-#endif
-
-        const VampPluginDescriptor *descriptor = 0;
-        int index = 0;
-
-        map<string, int> known;
-        bool ok = true;
-
-        while ((descriptor = fn(VAMP_API_VERSION, index))) {
-
-            if (known.find(descriptor->identifier) != known.end()) {
-                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Plugin library "
-                     << soname
-                     << " returns the same plugin identifier \""
-                     << descriptor->identifier << "\" at indices "
-                     << known[descriptor->identifier] << " and "
-                     << index << endl;
-                    cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
-                ok = false;
-                break;
-            } else {
-                known[descriptor->identifier] = index;
-            }
-
-            ++index;
-        }
-
-        if (ok) {
-
-            index = 0;
-
-            while ((descriptor = fn(VAMP_API_VERSION, index))) {
-
-                QString id = PluginIdentifier::createIdentifier
-                    ("vamp", soname, descriptor->identifier);
-                rv.push_back(id);
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-                cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl;
-#endif
-                ++index;
-            }
-        }
-            
-        if (DLCLOSE(libraryHandle) != 0) {
-            cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
-        }
+    for (const auto &d: m_pluginData) {
+        rv.push_back(QString("vamp:") + QString::fromStdString(d.pluginKey));
     }
 
-    generateTaxonomy();
-
     return rv;
 }
 
-QString
-FeatureExtractionPluginFactory::findPluginFile(QString soname, QString inDir)
-{
-    QString file = "";
-
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-    cerr << "FeatureExtractionPluginFactory::findPluginFile(\""
-              << soname << "\", \"" << inDir << "\")"
-              << endl;
-#endif
-
-    if (inDir != "") {
-
-        QDir dir(inDir, PLUGIN_GLOB,
-                 QDir::Name | QDir::IgnoreCase,
-                 QDir::Files | QDir::Readable);
-        if (!dir.exists()) return "";
-
-        file = dir.filePath(QFileInfo(soname).fileName());
-
-        if (QFileInfo(file).exists() && QFileInfo(file).isFile()) {
-
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-            cerr << "FeatureExtractionPluginFactory::findPluginFile: "
-                      << "found trivially at " << file << endl;
-#endif
-
-            return file;
-        }
-
-	for (unsigned int j = 0; j < dir.count(); ++j) {
-            file = dir.filePath(dir[j]);
-            if (QFileInfo(file).baseName() == QFileInfo(soname).baseName()) {
-
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-                cerr << "FeatureExtractionPluginFactory::findPluginFile: "
-                          << "found \"" << soname << "\" at " << file << endl;
-#endif
-
-                return file;
-            }
-        }
-
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        cerr << "FeatureExtractionPluginFactory::findPluginFile (with dir): "
-                  << "not found" << endl;
-#endif
-
-        return "";
-
-    } else {
-
-        QFileInfo fi(soname);
-
-        if (fi.isAbsolute() && fi.exists() && fi.isFile()) {
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-            cerr << "FeatureExtractionPluginFactory::findPluginFile: "
-                      << "found trivially at " << soname << endl;
-#endif
-            return soname;
-        }
-
-        if (fi.isAbsolute() && fi.absolutePath() != "") {
-            file = findPluginFile(soname, fi.absolutePath());
-            if (file != "") return file;
-        }
-
-        vector<QString> path = getPluginPath();
-        for (vector<QString>::iterator i = path.begin();
-             i != path.end(); ++i) {
-            if (*i != "") {
-                file = findPluginFile(soname, *i);
-                if (file != "") return file;
-            }
-        }
-
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        cerr << "FeatureExtractionPluginFactory::findPluginFile: "
-                  << "not found" << endl;
-#endif
-
-        return "";
-    }
-}
-
 Vamp::Plugin *
 FeatureExtractionPluginFactory::instantiatePlugin(QString identifier,
 						  sv_samplerate_t inputSampleRate)
 {
     Profiler profiler("FeatureExtractionPluginFactory::instantiatePlugin");
-
-    Vamp::Plugin *rv = 0;
-    Vamp::PluginHostAdapter *plugin = 0;
-
-    const VampPluginDescriptor *descriptor = 0;
-    int index = 0;
-
+    
     QString type, soname, label;
     PluginIdentifier::parseIdentifier(identifier, type, soname, label);
-    if (type != "vamp") {
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl;
-#endif
-	return 0;
+    std::string pluginKey = (soname + ":" + label).toStdString();
+
+    auto ap = new piper_vamp::client::AutoPlugin
+        (m_serverName, pluginKey, float(inputSampleRate), 0);
+
+    if (!ap->isOK()) {
+        delete ap;
+        return 0;
+    } else {
+        return ap;
     }
-
-    QString found = findPluginFile(soname);
-
-    if (found == "") {
-        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to find library file " << soname << endl;
-        return 0;
-    } else if (found != soname) {
-
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl;
-        cerr << soname << " -> " << found << endl;
-#endif
-
-    }        
-
-    soname = found;
-
-    void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
-            
-    if (!libraryHandle) {
-        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to load library " << soname << ": " << DLERROR() << endl;
-        return 0;
-    }
-
-    VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
-        DLSYM(libraryHandle, "vampGetPluginDescriptor");
-    
-    if (!fn) {
-        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl;
-        goto done;
-    }
-
-    while ((descriptor = fn(VAMP_API_VERSION, index))) {
-        if (label == descriptor->identifier) break;
-        ++index;
-    }
-
-    if (!descriptor) {
-        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to find plugin \"" << label << "\" in library " << soname << endl;
-        goto done;
-    }
-
-    plugin = new Vamp::PluginHostAdapter(descriptor, float(inputSampleRate));
-
-    if (plugin) {
-        m_handleMap[plugin] = libraryHandle;
-        rv = new PluginDeletionNotifyAdapter(plugin, this);
-    }
-
-//    SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: Constructed Vamp plugin, rv is " << rv << endl;
-
-    //!!! need to dlclose() when plugins from a given library are unloaded
-
-done:
-    if (!rv) {
-        if (DLCLOSE(libraryHandle) != 0) {
-            cerr << "WARNING: FeatureExtractionPluginFactory::instantiatePlugin: Failed to unload library " << soname << endl;
-        }
-    }
-
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-    cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl;
-#endif
-    
-    return rv;
-}
-
-void
-FeatureExtractionPluginFactory::pluginDeleted(Vamp::Plugin *plugin)
-{
-    void *handle = m_handleMap[plugin];
-    if (handle) {
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        cerr << "unloading library " << handle << " for plugin " << plugin << endl;
-#endif
-        DLCLOSE(handle);
-    }
-    m_handleMap.erase(plugin);
 }
 
 QString
@@ -389,47 +135,21 @@
 }
 
 void
-FeatureExtractionPluginFactory::generateTaxonomy()
+FeatureExtractionPluginFactory::populate()
 {
-    vector<QString> pluginPath = getPluginPath();
-    vector<QString> path;
+    piper_vamp::ListResponse lr = m_client.listPluginData();
+    m_pluginData = lr.available;
 
-    for (size_t i = 0; i < pluginPath.size(); ++i) {
-	if (pluginPath[i].contains("/lib/")) {
-	    QString p(pluginPath[i]);
-            path.push_back(p);
-	    p.replace("/lib/", "/share/");
-	    path.push_back(p);
-	}
-	path.push_back(pluginPath[i]);
+    for (const auto &pd: m_pluginData) {
+
+        QString identifier =
+            QString("vamp:") + QString::fromStdString(pd.pluginKey);
+
+        QStringList catlist;
+        for (const auto &cs: pd.category) {
+            catlist.push_back(QString::fromStdString(cs));
+        }
+        m_taxonomy[identifier] = catlist.join(" > ");
     }
+}
 
-    for (size_t i = 0; i < path.size(); ++i) {
-
-	QDir dir(path[i], "*.cat");
-
-//	SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl;
-	for (unsigned int j = 0; j < dir.count(); ++j) {
-
-	    QFile file(path[i] + "/" + dir[j]);
-
-//	    SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl;
-
-	    if (file.open(QIODevice::ReadOnly)) {
-//		    cerr << "...opened" << endl;
-		QTextStream stream(&file);
-		QString line;
-
-		while (!stream.atEnd()) {
-		    line = stream.readLine();
-//		    cerr << "line is: \"" << line << "\"" << endl;
-		    QString id = PluginIdentifier::canonicalise
-                        (line.section("::", 0, 0));
-		    QString cat = line.section("::", 1, 1);
-		    m_taxonomy[id] = cat;
-//		    cerr << "FeatureExtractionPluginFactory: set id \"" << id << "\" to cat \"" << cat << "\"" << endl;
-		}
-	    }
-	}
-    }
-}    
--- a/plugin/FeatureExtractionPluginFactory.h	Thu Oct 20 11:16:22 2016 +0100
+++ b/plugin/FeatureExtractionPluginFactory.h	Thu Oct 20 11:19:34 2016 +0100
@@ -17,6 +17,7 @@
 #define _FEATURE_EXTRACTION_PLUGIN_FACTORY_H_
 
 #include <QString>
+#include <QMutex>
 #include <vector>
 #include <map>
 
@@ -25,20 +26,20 @@
 #include "base/Debug.h"
 #include "base/BaseTypes.h"
 
+#include "vamp-client/ProcessQtTransport.h"
+#include "vamp-client/CapnpRRClient.h"
+
 class FeatureExtractionPluginFactory
 {
 public:
+    FeatureExtractionPluginFactory();
     virtual ~FeatureExtractionPluginFactory() { }
 
     static FeatureExtractionPluginFactory *instance(QString pluginType);
     static FeatureExtractionPluginFactory *instanceFor(QString identifier);
     static std::vector<QString> getAllPluginIdentifiers();
 
-    virtual std::vector<QString> getPluginPath();
-
     virtual std::vector<QString> getPluginIdentifiers();
-    
-    virtual QString findPluginFile(QString soname, QString inDir = "");
 
     // We don't set blockSize or channels on this -- they're
     // negotiated and handled via initialize() on the plugin
@@ -51,14 +52,15 @@
     virtual QString getPluginCategory(QString identifier);
 
 protected:
-    std::vector<QString> m_pluginPath;
+    std::string m_serverName;
+    
+    piper_vamp::client::ProcessQtTransport m_transport;
+    piper_vamp::client::CapnpRRClient m_client;
+
+    QMutex m_mutex;
+    std::vector<piper_vamp::PluginStaticData> m_pluginData;
     std::map<QString, QString> m_taxonomy;
-
-    friend class PluginDeletionNotifyAdapter;
-    void pluginDeleted(Vamp::Plugin *);
-    std::map<Vamp::Plugin *, void *> m_handleMap;
-    
-    void generateTaxonomy();
+    void populate();
 };
 
 #endif
--- a/svcore.pro	Thu Oct 20 11:16:22 2016 +0100
+++ b/svcore.pro	Thu Oct 20 11:19:34 2016 +0100
@@ -44,8 +44,8 @@
 
 TARGET = svcore
 
-DEPENDPATH += . data plugin plugin/api/alsa
-INCLUDEPATH += . data plugin plugin/api/alsa ../dataquay ../checker
+DEPENDPATH += . data plugin plugin/api/alsa ../dataquay ../checker ../piper-cpp
+INCLUDEPATH += . data plugin plugin/api/alsa ../dataquay ../checker ../piper-cpp
 OBJECTS_DIR = o
 MOC_DIR = o
 
--- a/transform/FeatureExtractionModelTransformer.cpp	Thu Oct 20 11:16:22 2016 +0100
+++ b/transform/FeatureExtractionModelTransformer.cpp	Thu Oct 20 11:19:34 2016 +0100
@@ -42,17 +42,19 @@
 FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in,
                                                                      const Transform &transform) :
     ModelTransformer(in, transform),
-    m_plugin(0)
+    m_plugin(0),
+    m_haveOutputs(false)
 {
     SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl;
 
-    initialise();
+//    initialise();
 }
 
 FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in,
                                                                      const Transforms &transforms) :
     ModelTransformer(in, transforms),
-    m_plugin(0)
+    m_plugin(0),
+    m_haveOutputs(false)
 {
     if (m_transforms.empty()) {
         SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s)" << endl;
@@ -60,7 +62,7 @@
         SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s), first has plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl;
     }
     
-    initialise();
+//    initialise();
 }
 
 static bool
@@ -104,6 +106,9 @@
         return false;
     }
 
+    cerr << "instantiating plugin for transform in thread "
+         << QThread::currentThreadId() << endl;
+    
     m_plugin = factory->instantiatePlugin(pluginId, input->getSampleRate());
     if (!m_plugin) {
         m_message = tr("Failed to instantiate plugin \"%1\"").arg(pluginId);
@@ -220,6 +225,11 @@
         createOutputModels(j);
     }
 
+    m_outputMutex.lock();
+    m_haveOutputs = true;
+    m_outputsCondition.wakeAll();
+    m_outputMutex.unlock();
+
     return true;
 }
 
@@ -479,6 +489,16 @@
     }
 }
 
+void
+FeatureExtractionModelTransformer::awaitOutputModels()
+{
+    m_outputMutex.lock();
+    while (!m_haveOutputs) {
+        m_outputsCondition.wait(&m_outputMutex);
+    }
+    m_outputMutex.unlock();
+}
+
 FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()
 {
 //    SVDEBUG << "FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()" << endl;
@@ -566,6 +586,8 @@
 void
 FeatureExtractionModelTransformer::run()
 {
+    initialise();
+    
     DenseTimeValueModel *input = getConformingInput();
     if (!input) return;
 
@@ -709,6 +731,9 @@
 
         if (m_abandoned) break;
 
+    cerr << "calling process() from thread "
+         << QThread::currentThreadId() << endl;
+    
 	Vamp::Plugin::FeatureSet features = m_plugin->process
 	    (buffers, RealTime::frame2RealTime(blockFrame, sampleRate).toVampRealTime());
 
--- a/transform/FeatureExtractionModelTransformer.h	Thu Oct 20 11:16:22 2016 +0100
+++ b/transform/FeatureExtractionModelTransformer.h	Thu Oct 20 11:19:34 2016 +0100
@@ -19,6 +19,8 @@
 #include "ModelTransformer.h"
 
 #include <QString>
+#include <QMutex>
+#include <QWaitCondition>
 
 #include <vamp-hostsdk/Plugin.h>
 
@@ -74,7 +76,12 @@
     void getFrames(int channelCount, sv_frame_t startFrame, sv_frame_t size,
                    float **buffer);
 
-    // just casts
+    bool m_haveOutputs;
+    QMutex m_outputMutex;
+    QWaitCondition m_outputsCondition;
+    void awaitOutputModels();
+    
+    // just casts:
 
     DenseTimeValueModel *getConformingInput();
 
--- a/transform/ModelTransformer.h	Thu Oct 20 11:16:22 2016 +0100
+++ b/transform/ModelTransformer.h	Thu Oct 20 11:19:34 2016 +0100
@@ -89,16 +89,20 @@
      * be initialised; an error message may be available via
      * getMessage() in this situation.
      */
-    Models getOutputModels() { return m_outputs; }
+    Models getOutputModels() {
+        awaitOutputModels();
+        return m_outputs;
+    }
 
     /**
      * Return the set of output models, also detaching them from the
      * transformer so that they will not be deleted when the
      * transformer is.  The caller takes ownership of the models.
      */
-    Models detachOutputModels() { 
+    Models detachOutputModels() {
+        awaitOutputModels();
         m_detached = true; 
-        return getOutputModels(); 
+        return m_outputs;
     }
 
     /**
@@ -138,6 +142,8 @@
     ModelTransformer(Input input, const Transform &transform);
     ModelTransformer(Input input, const Transforms &transforms);
 
+    virtual void awaitOutputModels() = 0;
+    
     Transforms m_transforms;
     Input m_input; // I don't own the model in this
     Models m_outputs; // I own this, unless...
--- a/transform/RealTimeEffectModelTransformer.h	Thu Oct 20 11:16:22 2016 +0100
+++ b/transform/RealTimeEffectModelTransformer.h	Thu Oct 20 11:19:34 2016 +0100
@@ -31,6 +31,8 @@
 protected:
     virtual void run();
 
+    virtual void awaitOutputModels() { } // they're created synchronously
+    
     QString m_units;
     RealTimePluginInstance *m_plugin;
     int m_outputNo;