changeset 1239:5261a7791f1c 3.0-integration

Merge from branch piper
author Chris Cannam
date Fri, 28 Oct 2016 15:20:58 +0100
parents a1b97df9962e (current diff) dd49630e0d70 (diff)
children 42a4b058f8ba
files base/RealTime.cpp base/Resampler.cpp
diffstat 25 files changed, 1925 insertions(+), 1524 deletions(-) [+]
line wrap: on
line diff
--- a/base/Preferences.cpp	Thu Oct 20 11:16:22 2016 +0100
+++ b/base/Preferences.cpp	Fri Oct 28 15:20:58 2016 +0100
@@ -41,6 +41,7 @@
     m_propertyBoxLayout(VerticallyStacked),
     m_windowType(HanningWindow),
     m_resampleQuality(1),
+    m_runPluginsInProcess(true),
     m_omitRecentTemps(true),
     m_tempDirRoot(""),
     m_fixedSampleRate(0),
@@ -65,6 +66,7 @@
     m_windowType = WindowType
         (settings.value("window-type", int(HanningWindow)).toInt());
     m_resampleQuality = settings.value("resample-quality", 1).toInt();
+    m_runPluginsInProcess = settings.value("run-vamp-plugins-in-process", true).toBool();
     m_fixedSampleRate = settings.value("fixed-sample-rate", 0).toDouble();
     m_resampleOnLoad = settings.value("resample-on-load", false).toBool();
     m_normaliseAudio = settings.value("normalise-audio", false).toBool();
@@ -266,6 +268,10 @@
         return m_resampleQuality;
     }
 
+    if (name == "Run Vamp Plugins In Process") {
+        return m_runPluginsInProcess;
+    }
+    
     if (name == "Omit Temporaries from Recent Files") {
         if (deflt) *deflt = 1;
         return m_omitRecentTemps ? 1 : 0;
@@ -414,6 +420,8 @@
         setWindowType(WindowType(value));
     } else if (name == "Resample Quality") {
         setResampleQuality(value);
+    } else if (name == "Run Vamp Plugins In Process") {
+        setRunPluginsInProcess(value ? true : false);
     } else if (name == "Omit Temporaries from Recent Files") {
         setOmitTempsFromRecentFiles(value ? true : false);
     } else if (name == "Background Mode") {
@@ -519,6 +527,19 @@
 }
 
 void
+Preferences::setRunPluginsInProcess(bool run)
+{
+    if (m_runPluginsInProcess != run) {
+        m_runPluginsInProcess = run;
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        settings.setValue("run-vamp-plugins-in-process", run);
+        settings.endGroup();
+        emit propertyChanged("Run Vamp Plugins In Process");
+    }
+}
+
+void
 Preferences::setOmitTempsFromRecentFiles(bool omit)
 {
     if (m_omitRecentTemps != omit) {
--- a/base/Preferences.h	Thu Oct 20 11:16:22 2016 +0100
+++ b/base/Preferences.h	Fri Oct 28 15:20:58 2016 +0100
@@ -53,6 +53,8 @@
     WindowType getWindowType() const { return m_windowType; }
     int getResampleQuality() const { return m_resampleQuality; }
 
+    bool getRunPluginsInProcess() const { return m_runPluginsInProcess; }
+    
     //!!! harmonise with PaneStack
     enum PropertyBoxLayout {
         VerticallyStacked,
@@ -114,6 +116,7 @@
     void setPropertyBoxLayout(PropertyBoxLayout layout);
     void setWindowType(WindowType type);
     void setResampleQuality(int quality);
+    void setRunPluginsInProcess(bool r);
     void setOmitTempsFromRecentFiles(bool omit);
     void setTemporaryDirectoryRoot(QString tempDirRoot);
     void setFixedSampleRate(sv_samplerate_t);
@@ -151,6 +154,7 @@
     PropertyBoxLayout m_propertyBoxLayout;
     WindowType m_windowType;
     int m_resampleQuality;
+    bool m_runPluginsInProcess;
     bool m_omitRecentTemps;
     QString m_tempDirRoot;
     sv_samplerate_t m_fixedSampleRate;
--- a/base/RealTime.cpp	Thu Oct 20 11:16:22 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,480 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-/*
-   This is a modified version of a source file from the 
-   Rosegarden MIDI and audio sequencer and notation editor.
-   This file copyright 2000-2006 Chris Cannam.
-*/
-
-#include <iostream>
-
-#include <cstdlib>
-#include <sstream>
-
-#include "RealTime.h"
-
-#include "Debug.h"
-
-#include "Preferences.h"
-
-// A RealTime consists of two ints that must be at least 32 bits each.
-// A signed 32-bit int can store values exceeding +/- 2 billion.  This
-// means we can safely use our lower int for nanoseconds, as there are
-// 1 billion nanoseconds in a second and we need to handle double that
-// because of the implementations of addition etc that we use.
-//
-// The maximum valid RealTime on a 32-bit system is somewhere around
-// 68 years: 999999999 nanoseconds longer than the classic Unix epoch.
-
-#define ONE_BILLION 1000000000
-
-RealTime::RealTime(int s, int n) :
-    sec(s), nsec(n)
-{
-    if (sec == 0) {
-	while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; }
-	while (nsec >=  ONE_BILLION) { nsec -= ONE_BILLION; ++sec; }
-    } else if (sec < 0) {
-	while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; }
-	while (nsec > 0)             { nsec -= ONE_BILLION; ++sec; }
-    } else { 
-	while (nsec >=  ONE_BILLION) { nsec -= ONE_BILLION; ++sec; }
-	while (nsec < 0)             { nsec += ONE_BILLION; --sec; }
-    }
-}
-
-RealTime
-RealTime::fromSeconds(double sec)
-{
-    if (sec >= 0) {
-        return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5));
-    } else {
-        return -fromSeconds(-sec);
-    }
-}
-
-RealTime
-RealTime::fromMilliseconds(int msec)
-{
-    return RealTime(msec / 1000, (msec % 1000) * 1000000);
-}
-
-RealTime
-RealTime::fromTimeval(const struct timeval &tv)
-{
-    return RealTime(int(tv.tv_sec), int(tv.tv_usec * 1000));
-}
-
-RealTime
-RealTime::fromXsdDuration(std::string xsdd)
-{
-    RealTime t;
-
-    int year = 0, month = 0, day = 0, hour = 0, minute = 0;
-    double second = 0.0;
-
-    int i = 0;
-
-    const char *s = xsdd.c_str();
-    int len = int(xsdd.length());
-
-    bool negative = false, afterT = false;
-
-    while (i < len) {
-
-        if (s[i] == '-') {
-            if (i == 0) negative = true;
-            ++i;
-            continue;
-        }
-
-        double value = 0.0;
-        char *eptr = 0;
-
-        if (isdigit(s[i]) || s[i] == '.') {
-            value = strtod(&s[i], &eptr);
-            i = int(eptr - s);
-        }
-
-        if (i == len) break;
-
-        switch (s[i]) {
-        case 'Y': year = int(value + 0.1); break;
-        case 'D': day  = int(value + 0.1); break;
-        case 'H': hour = int(value + 0.1); break;
-        case 'M':
-            if (afterT) minute = int(value + 0.1);
-            else month = int(value + 0.1);
-            break;
-        case 'S':
-            second = value;
-            break;
-        case 'T': afterT = true; break;
-        };
-
-        ++i;
-    }
-
-    if (year > 0) {
-        cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero year.\nWith no origin and a limited data size, I will treat a year as exactly 31556952\nseconds and you should expect overflow and/or poor results." << endl;
-        t = t + RealTime(year * 31556952, 0);
-    }
-
-    if (month > 0) {
-        cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero month.\nWith no origin and a limited data size, I will treat a month as exactly 2629746\nseconds and you should expect overflow and/or poor results." << endl;
-        t = t + RealTime(month * 2629746, 0);
-    }
-
-    if (day > 0) {
-        t = t + RealTime(day * 86400, 0);
-    }
-
-    if (hour > 0) {
-        t = t + RealTime(hour * 3600, 0);
-    }
-
-    if (minute > 0) {
-        t = t + RealTime(minute * 60, 0);
-    }
-
-    t = t + fromSeconds(second);
-
-    if (negative) {
-        return -t;
-    } else {
-        return t;
-    }
-}
-
-double
-RealTime::toDouble() const
-{
-    double d = sec;
-    d += double(nsec) / double(ONE_BILLION);
-    return d;
-}
-
-std::ostream &operator<<(std::ostream &out, const RealTime &rt)
-{
-    if (rt < RealTime::zeroTime) {
-	out << "-";
-    } else {
-	out << " ";
-    }
-
-    int s = (rt.sec < 0 ? -rt.sec : rt.sec);
-    int n = (rt.nsec < 0 ? -rt.nsec : rt.nsec);
-
-    out << s << ".";
-
-    int nn(n);
-    if (nn == 0) out << "00000000";
-    else while (nn < (ONE_BILLION / 10)) {
-	out << "0";
-	nn *= 10;
-    }
-    
-    out << n << "R";
-    return out;
-}
-
-std::string
-RealTime::toString(bool align) const
-{
-    std::stringstream out;
-    out << *this;
-    
-    std::string s = out.str();
-
-    if (!align && *this >= RealTime::zeroTime) {
-        // remove leading " "
-        s = s.substr(1, s.length() - 1);
-    }
-
-    // remove trailing R
-    return s.substr(0, s.length() - 1);
-}
-
-RealTime
-RealTime::fromString(std::string s)
-{
-    bool negative = false;
-    int section = 0;
-    std::string ssec, snsec;
-
-    for (size_t i = 0; i < s.length(); ++i) {
-
-        char c = s[i];
-        if (isspace(c)) continue;
-
-        if (section == 0) {
-
-            if (c == '-') negative = true;
-            else if (isdigit(c)) { section = 1; ssec += c; }
-            else if (c == '.') section = 2;
-            else break;
-
-        } else if (section == 1) {
-
-            if (c == '.') section = 2;
-            else if (isdigit(c)) ssec += c;
-            else break;
-
-        } else if (section == 2) {
-
-            if (isdigit(c)) snsec += c;
-            else break;
-        }
-    }
-
-    while (snsec.length() < 8) snsec += '0';
-
-    int sec = atoi(ssec.c_str());
-    int nsec = atoi(snsec.c_str());
-    if (negative) sec = -sec;
-
-//    SVDEBUG << "RealTime::fromString: string " << s << " -> "
-//              << sec << " sec, " << nsec << " nsec" << endl;
-
-    return RealTime(sec, nsec);
-}
-
-std::string
-RealTime::toText(bool fixedDp) const
-{
-    if (*this < RealTime::zeroTime) return "-" + (-*this).toText(fixedDp);
-
-    Preferences *p = Preferences::getInstance();
-    bool hms = true;
-    
-    if (p) {
-        hms = p->getShowHMS();
-        int fps = 0;
-        switch (p->getTimeToTextMode()) {
-        case Preferences::TimeToTextMs: break;
-        case Preferences::TimeToTextUs: fps = 1000000; break;
-        case Preferences::TimeToText24Frame: fps = 24; break;
-        case Preferences::TimeToText25Frame: fps = 25; break;
-        case Preferences::TimeToText30Frame: fps = 30; break;
-        case Preferences::TimeToText50Frame: fps = 50; break;
-        case Preferences::TimeToText60Frame: fps = 60; break;
-        }
-        if (fps != 0) return toFrameText(fps, hms);
-    }
-
-    return toMSText(fixedDp, hms);
-}
-
-static void
-writeSecPart(std::stringstream &out, bool hms, int sec)
-{
-    if (hms) {
-        if (sec >= 3600) {
-            out << (sec / 3600) << ":";
-        }
-
-        if (sec >= 60) {
-            int minutes = (sec % 3600) / 60;
-            if (sec >= 3600 && minutes < 10) out << "0";
-            out << minutes << ":";
-        }
-
-        if (sec >= 10) {
-            out << ((sec % 60) / 10);
-        }
-
-        out << (sec % 10);
-
-    } else {
-        out << sec;
-    }
-}
-
-std::string
-RealTime::toMSText(bool fixedDp, bool hms) const
-{
-    if (*this < RealTime::zeroTime) return "-" + (-*this).toMSText(fixedDp, hms);
-
-    std::stringstream out;
-
-    writeSecPart(out, hms, sec);
-    
-    int ms = msec();
-
-    if (ms != 0) {
-	out << ".";
-	out << (ms / 100);
-	ms = ms % 100;
-	if (ms != 0) {
-	    out << (ms / 10);
-	    ms = ms % 10;
-	} else if (fixedDp) {
-	    out << "0";
-	}
-	if (ms != 0) {
-	    out << ms;
-	} else if (fixedDp) {
-	    out << "0";
-	}
-    } else if (fixedDp) {
-	out << ".000";
-    }
-	
-    std::string s = out.str();
-
-    return s;
-}
-
-std::string
-RealTime::toFrameText(int fps, bool hms) const
-{
-    if (*this < RealTime::zeroTime) return "-" + (-*this).toFrameText(fps, hms);
-
-    std::stringstream out;
-
-    writeSecPart(out, hms, sec);
-
-    // avoid rounding error if fps does not divide into ONE_BILLION
-    int64_t fbig = nsec;
-    fbig *= fps;
-    int f = int(fbig / ONE_BILLION);
-
-    int div = 1;
-    int n = fps - 1;
-    while ((n = n / 10)) {
-        div *= 10;
-    }
-
-    out << ":";
-
-//    cerr << "div = " << div << ", f =  "<< f << endl;
-
-    while (div) {
-        int d = (f / div) % 10;
-        out << d;
-        div /= 10;
-    }
-	
-    std::string s = out.str();
-
-//    cerr << "converted " << toString() << " to " << s << endl;
-
-    return s;
-}
-
-std::string
-RealTime::toSecText() const
-{
-    if (*this < RealTime::zeroTime) return "-" + (-*this).toSecText();
-
-    std::stringstream out;
-
-    writeSecPart(out, true, sec);
-    
-    if (sec < 60) {
-        out << "s";
-    }
-
-    std::string s = out.str();
-
-    return s;
-}
-
-std::string
-RealTime::toXsdDuration() const
-{
-    std::string s = "PT" + toString(false) + "S";
-    return s;
-}
-
-RealTime
-RealTime::operator*(int m) const
-{
-    double t = (double(nsec) / ONE_BILLION) * m;
-    t += sec * m;
-    return fromSeconds(t);
-}
-
-RealTime
-RealTime::operator/(int d) const
-{
-    int secdiv = sec / d;
-    int secrem = sec % d;
-
-    double nsecdiv = (double(nsec) + ONE_BILLION * double(secrem)) / d;
-    
-    return RealTime(secdiv, int(nsecdiv + 0.5));
-}
-
-RealTime
-RealTime::operator*(double m) const
-{
-    double t = (double(nsec) / ONE_BILLION) * m;
-    t += sec * m;
-    return fromSeconds(t);
-}
-
-RealTime
-RealTime::operator/(double d) const
-{
-    double t = (double(nsec) / ONE_BILLION) / d;
-    t += sec / d;
-    return fromSeconds(t);
-}
-
-double 
-RealTime::operator/(const RealTime &r) const
-{
-    double lTotal = double(sec) * ONE_BILLION + double(nsec);
-    double rTotal = double(r.sec) * ONE_BILLION + double(r.nsec);
-    
-    if (rTotal == 0) return 0.0;
-    else return lTotal/rTotal;
-}
-
-static RealTime
-frame2RealTime_i(sv_frame_t frame, sv_frame_t iSampleRate)
-{
-    if (frame < 0) return -frame2RealTime_i(-frame, iSampleRate);
-
-    RealTime rt;
-    sv_frame_t sec = frame / iSampleRate;
-    rt.sec = int(sec);
-    frame -= sec * iSampleRate;
-    rt.nsec = (int)(((double(frame) * 1000000.0) / double(iSampleRate)) * 1000.0);
-    return rt;
-}
-
-sv_frame_t
-RealTime::realTime2Frame(const RealTime &time, sv_samplerate_t sampleRate)
-{
-    if (time < zeroTime) return -realTime2Frame(-time, sampleRate);
-    double s = time.sec + double(time.nsec + 1) / 1000000000.0;
-    return sv_frame_t(s * sampleRate);
-}
-
-RealTime
-RealTime::frame2RealTime(sv_frame_t frame, sv_samplerate_t sampleRate)
-{
-    if (sampleRate == double(int(sampleRate))) {
-        return frame2RealTime_i(frame, int(sampleRate));
-    }
-
-    double sec = double(frame) / sampleRate;
-    return fromSeconds(sec);
-}
-
-const RealTime RealTime::zeroTime(0,0);
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/RealTimeSV.cpp	Fri Oct 28 15:20:58 2016 +0100
@@ -0,0 +1,480 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+/*
+   This is a modified version of a source file from the 
+   Rosegarden MIDI and audio sequencer and notation editor.
+   This file copyright 2000-2006 Chris Cannam.
+*/
+
+#include <iostream>
+
+#include <cstdlib>
+#include <sstream>
+
+#include "RealTime.h"
+
+#include "Debug.h"
+
+#include "Preferences.h"
+
+// A RealTime consists of two ints that must be at least 32 bits each.
+// A signed 32-bit int can store values exceeding +/- 2 billion.  This
+// means we can safely use our lower int for nanoseconds, as there are
+// 1 billion nanoseconds in a second and we need to handle double that
+// because of the implementations of addition etc that we use.
+//
+// The maximum valid RealTime on a 32-bit system is somewhere around
+// 68 years: 999999999 nanoseconds longer than the classic Unix epoch.
+
+#define ONE_BILLION 1000000000
+
+RealTime::RealTime(int s, int n) :
+    sec(s), nsec(n)
+{
+    if (sec == 0) {
+	while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; }
+	while (nsec >=  ONE_BILLION) { nsec -= ONE_BILLION; ++sec; }
+    } else if (sec < 0) {
+	while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; }
+	while (nsec > 0)             { nsec -= ONE_BILLION; ++sec; }
+    } else { 
+	while (nsec >=  ONE_BILLION) { nsec -= ONE_BILLION; ++sec; }
+	while (nsec < 0)             { nsec += ONE_BILLION; --sec; }
+    }
+}
+
+RealTime
+RealTime::fromSeconds(double sec)
+{
+    if (sec >= 0) {
+        return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5));
+    } else {
+        return -fromSeconds(-sec);
+    }
+}
+
+RealTime
+RealTime::fromMilliseconds(int msec)
+{
+    return RealTime(msec / 1000, (msec % 1000) * 1000000);
+}
+
+RealTime
+RealTime::fromTimeval(const struct timeval &tv)
+{
+    return RealTime(int(tv.tv_sec), int(tv.tv_usec * 1000));
+}
+
+RealTime
+RealTime::fromXsdDuration(std::string xsdd)
+{
+    RealTime t;
+
+    int year = 0, month = 0, day = 0, hour = 0, minute = 0;
+    double second = 0.0;
+
+    int i = 0;
+
+    const char *s = xsdd.c_str();
+    int len = int(xsdd.length());
+
+    bool negative = false, afterT = false;
+
+    while (i < len) {
+
+        if (s[i] == '-') {
+            if (i == 0) negative = true;
+            ++i;
+            continue;
+        }
+
+        double value = 0.0;
+        char *eptr = 0;
+
+        if (isdigit(s[i]) || s[i] == '.') {
+            value = strtod(&s[i], &eptr);
+            i = int(eptr - s);
+        }
+
+        if (i == len) break;
+
+        switch (s[i]) {
+        case 'Y': year = int(value + 0.1); break;
+        case 'D': day  = int(value + 0.1); break;
+        case 'H': hour = int(value + 0.1); break;
+        case 'M':
+            if (afterT) minute = int(value + 0.1);
+            else month = int(value + 0.1);
+            break;
+        case 'S':
+            second = value;
+            break;
+        case 'T': afterT = true; break;
+        };
+
+        ++i;
+    }
+
+    if (year > 0) {
+        cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero year.\nWith no origin and a limited data size, I will treat a year as exactly 31556952\nseconds and you should expect overflow and/or poor results." << endl;
+        t = t + RealTime(year * 31556952, 0);
+    }
+
+    if (month > 0) {
+        cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero month.\nWith no origin and a limited data size, I will treat a month as exactly 2629746\nseconds and you should expect overflow and/or poor results." << endl;
+        t = t + RealTime(month * 2629746, 0);
+    }
+
+    if (day > 0) {
+        t = t + RealTime(day * 86400, 0);
+    }
+
+    if (hour > 0) {
+        t = t + RealTime(hour * 3600, 0);
+    }
+
+    if (minute > 0) {
+        t = t + RealTime(minute * 60, 0);
+    }
+
+    t = t + fromSeconds(second);
+
+    if (negative) {
+        return -t;
+    } else {
+        return t;
+    }
+}
+
+double
+RealTime::toDouble() const
+{
+    double d = sec;
+    d += double(nsec) / double(ONE_BILLION);
+    return d;
+}
+
+std::ostream &operator<<(std::ostream &out, const RealTime &rt)
+{
+    if (rt < RealTime::zeroTime) {
+	out << "-";
+    } else {
+	out << " ";
+    }
+
+    int s = (rt.sec < 0 ? -rt.sec : rt.sec);
+    int n = (rt.nsec < 0 ? -rt.nsec : rt.nsec);
+
+    out << s << ".";
+
+    int nn(n);
+    if (nn == 0) out << "00000000";
+    else while (nn < (ONE_BILLION / 10)) {
+	out << "0";
+	nn *= 10;
+    }
+    
+    out << n << "R";
+    return out;
+}
+
+std::string
+RealTime::toString(bool align) const
+{
+    std::stringstream out;
+    out << *this;
+    
+    std::string s = out.str();
+
+    if (!align && *this >= RealTime::zeroTime) {
+        // remove leading " "
+        s = s.substr(1, s.length() - 1);
+    }
+
+    // remove trailing R
+    return s.substr(0, s.length() - 1);
+}
+
+RealTime
+RealTime::fromString(std::string s)
+{
+    bool negative = false;
+    int section = 0;
+    std::string ssec, snsec;
+
+    for (size_t i = 0; i < s.length(); ++i) {
+
+        char c = s[i];
+        if (isspace(c)) continue;
+
+        if (section == 0) {
+
+            if (c == '-') negative = true;
+            else if (isdigit(c)) { section = 1; ssec += c; }
+            else if (c == '.') section = 2;
+            else break;
+
+        } else if (section == 1) {
+
+            if (c == '.') section = 2;
+            else if (isdigit(c)) ssec += c;
+            else break;
+
+        } else if (section == 2) {
+
+            if (isdigit(c)) snsec += c;
+            else break;
+        }
+    }
+
+    while (snsec.length() < 8) snsec += '0';
+
+    int sec = atoi(ssec.c_str());
+    int nsec = atoi(snsec.c_str());
+    if (negative) sec = -sec;
+
+//    SVDEBUG << "RealTime::fromString: string " << s << " -> "
+//              << sec << " sec, " << nsec << " nsec" << endl;
+
+    return RealTime(sec, nsec);
+}
+
+std::string
+RealTime::toText(bool fixedDp) const
+{
+    if (*this < RealTime::zeroTime) return "-" + (-*this).toText(fixedDp);
+
+    Preferences *p = Preferences::getInstance();
+    bool hms = true;
+    
+    if (p) {
+        hms = p->getShowHMS();
+        int fps = 0;
+        switch (p->getTimeToTextMode()) {
+        case Preferences::TimeToTextMs: break;
+        case Preferences::TimeToTextUs: fps = 1000000; break;
+        case Preferences::TimeToText24Frame: fps = 24; break;
+        case Preferences::TimeToText25Frame: fps = 25; break;
+        case Preferences::TimeToText30Frame: fps = 30; break;
+        case Preferences::TimeToText50Frame: fps = 50; break;
+        case Preferences::TimeToText60Frame: fps = 60; break;
+        }
+        if (fps != 0) return toFrameText(fps, hms);
+    }
+
+    return toMSText(fixedDp, hms);
+}
+
+static void
+writeSecPart(std::stringstream &out, bool hms, int sec)
+{
+    if (hms) {
+        if (sec >= 3600) {
+            out << (sec / 3600) << ":";
+        }
+
+        if (sec >= 60) {
+            int minutes = (sec % 3600) / 60;
+            if (sec >= 3600 && minutes < 10) out << "0";
+            out << minutes << ":";
+        }
+
+        if (sec >= 10) {
+            out << ((sec % 60) / 10);
+        }
+
+        out << (sec % 10);
+
+    } else {
+        out << sec;
+    }
+}
+
+std::string
+RealTime::toMSText(bool fixedDp, bool hms) const
+{
+    if (*this < RealTime::zeroTime) return "-" + (-*this).toMSText(fixedDp, hms);
+
+    std::stringstream out;
+
+    writeSecPart(out, hms, sec);
+    
+    int ms = msec();
+
+    if (ms != 0) {
+	out << ".";
+	out << (ms / 100);
+	ms = ms % 100;
+	if (ms != 0) {
+	    out << (ms / 10);
+	    ms = ms % 10;
+	} else if (fixedDp) {
+	    out << "0";
+	}
+	if (ms != 0) {
+	    out << ms;
+	} else if (fixedDp) {
+	    out << "0";
+	}
+    } else if (fixedDp) {
+	out << ".000";
+    }
+	
+    std::string s = out.str();
+
+    return s;
+}
+
+std::string
+RealTime::toFrameText(int fps, bool hms) const
+{
+    if (*this < RealTime::zeroTime) return "-" + (-*this).toFrameText(fps, hms);
+
+    std::stringstream out;
+
+    writeSecPart(out, hms, sec);
+
+    // avoid rounding error if fps does not divide into ONE_BILLION
+    int64_t fbig = nsec;
+    fbig *= fps;
+    int f = int(fbig / ONE_BILLION);
+
+    int div = 1;
+    int n = fps - 1;
+    while ((n = n / 10)) {
+        div *= 10;
+    }
+
+    out << ":";
+
+//    cerr << "div = " << div << ", f =  "<< f << endl;
+
+    while (div) {
+        int d = (f / div) % 10;
+        out << d;
+        div /= 10;
+    }
+	
+    std::string s = out.str();
+
+//    cerr << "converted " << toString() << " to " << s << endl;
+
+    return s;
+}
+
+std::string
+RealTime::toSecText() const
+{
+    if (*this < RealTime::zeroTime) return "-" + (-*this).toSecText();
+
+    std::stringstream out;
+
+    writeSecPart(out, true, sec);
+    
+    if (sec < 60) {
+        out << "s";
+    }
+
+    std::string s = out.str();
+
+    return s;
+}
+
+std::string
+RealTime::toXsdDuration() const
+{
+    std::string s = "PT" + toString(false) + "S";
+    return s;
+}
+
+RealTime
+RealTime::operator*(int m) const
+{
+    double t = (double(nsec) / ONE_BILLION) * m;
+    t += sec * m;
+    return fromSeconds(t);
+}
+
+RealTime
+RealTime::operator/(int d) const
+{
+    int secdiv = sec / d;
+    int secrem = sec % d;
+
+    double nsecdiv = (double(nsec) + ONE_BILLION * double(secrem)) / d;
+    
+    return RealTime(secdiv, int(nsecdiv + 0.5));
+}
+
+RealTime
+RealTime::operator*(double m) const
+{
+    double t = (double(nsec) / ONE_BILLION) * m;
+    t += sec * m;
+    return fromSeconds(t);
+}
+
+RealTime
+RealTime::operator/(double d) const
+{
+    double t = (double(nsec) / ONE_BILLION) / d;
+    t += sec / d;
+    return fromSeconds(t);
+}
+
+double 
+RealTime::operator/(const RealTime &r) const
+{
+    double lTotal = double(sec) * ONE_BILLION + double(nsec);
+    double rTotal = double(r.sec) * ONE_BILLION + double(r.nsec);
+    
+    if (rTotal == 0) return 0.0;
+    else return lTotal/rTotal;
+}
+
+static RealTime
+frame2RealTime_i(sv_frame_t frame, sv_frame_t iSampleRate)
+{
+    if (frame < 0) return -frame2RealTime_i(-frame, iSampleRate);
+
+    RealTime rt;
+    sv_frame_t sec = frame / iSampleRate;
+    rt.sec = int(sec);
+    frame -= sec * iSampleRate;
+    rt.nsec = (int)(((double(frame) * 1000000.0) / double(iSampleRate)) * 1000.0);
+    return rt;
+}
+
+sv_frame_t
+RealTime::realTime2Frame(const RealTime &time, sv_samplerate_t sampleRate)
+{
+    if (time < zeroTime) return -realTime2Frame(-time, sampleRate);
+    double s = time.sec + double(time.nsec + 1) / 1000000000.0;
+    return sv_frame_t(s * sampleRate);
+}
+
+RealTime
+RealTime::frame2RealTime(sv_frame_t frame, sv_samplerate_t sampleRate)
+{
+    if (sampleRate == double(int(sampleRate))) {
+        return frame2RealTime_i(frame, int(sampleRate));
+    }
+
+    double sec = double(frame) / sampleRate;
+    return fromSeconds(sec);
+}
+
+const RealTime RealTime::zeroTime(0,0);
+
--- a/base/Resampler.cpp	Thu Oct 20 11:16:22 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,196 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-/*
-   This is a modified version of a source file from the 
-   Rubber Band audio timestretcher library.
-   This file copyright 2007 Chris Cannam.
-*/
-
-#include "Resampler.h"
-
-#include <cstdlib>
-#include <cmath>
-
-#include <iostream>
-
-#include <samplerate.h>
-
-#include "Debug.h"
-
-class Resampler::D
-{
-public:
-    D(Quality quality, int channels, sv_frame_t chunkSize);
-    ~D();
-
-    sv_frame_t resample(float **in, float **out,
-                 sv_frame_t incount, double ratio,
-                 bool final);
-
-    sv_frame_t resampleInterleaved(float *in, float *out,
-                            sv_frame_t incount, double ratio,
-                            bool final);
-
-    void reset();
-
-protected:
-    SRC_STATE *m_src;
-    float *m_iin;
-    float *m_iout;
-    int m_channels;
-    sv_frame_t m_iinsize;
-    sv_frame_t m_ioutsize;
-};
-
-Resampler::D::D(Quality quality, int channels, sv_frame_t chunkSize) :
-    m_src(0),
-    m_iin(0),
-    m_iout(0),
-    m_channels(channels),
-    m_iinsize(0),
-    m_ioutsize(0)
-{
-    int err = 0;
-    m_src = src_new(quality == Best ? SRC_SINC_BEST_QUALITY :
-                    quality == Fastest ? SRC_LINEAR :
-                    SRC_SINC_FASTEST,
-                    channels, &err);
-
-    //!!! check err, throw
-
-    if (chunkSize > 0 && m_channels > 1) {
-        //!!! alignment?
-        m_iinsize = chunkSize * m_channels;
-        m_ioutsize = chunkSize * m_channels * 2;
-        m_iin = (float *)malloc(m_iinsize * sizeof(float));
-        m_iout = (float *)malloc(m_ioutsize * sizeof(float));
-    }
-}
-
-Resampler::D::~D()
-{
-    src_delete(m_src);
-    if (m_iinsize > 0) {
-        free(m_iin);
-    }
-    if (m_ioutsize > 0) {
-        free(m_iout);
-    }
-}
-
-sv_frame_t
-Resampler::D::resample(float **in, float **out,
-                       sv_frame_t incount, double ratio,
-                       bool final)
-{
-    if (m_channels == 1) {
-        return resampleInterleaved(*in, *out, incount, ratio, final);
-    }
-
-    sv_frame_t outcount = lrint(ceil(double(incount) * ratio));
-
-    if (incount * m_channels > m_iinsize) {
-        m_iinsize = incount * m_channels;
-        m_iin = (float *)realloc(m_iin, m_iinsize * sizeof(float));
-    }
-    if (outcount * m_channels > m_ioutsize) {
-        m_ioutsize = outcount * m_channels;
-        m_iout = (float *)realloc(m_iout, m_ioutsize * sizeof(float));
-    }
-    for (sv_frame_t i = 0; i < incount; ++i) {
-        for (int c = 0; c < m_channels; ++c) {
-            m_iin[i * m_channels + c] = in[c][i];
-        }
-    }
-    
-    sv_frame_t gen = resampleInterleaved(m_iin, m_iout, incount, ratio, final);
-
-    for (sv_frame_t i = 0; i < gen; ++i) {
-        for (int c = 0; c < m_channels; ++c) {
-            out[c][i] = m_iout[i * m_channels + c];
-        }
-    }
-
-    return gen;
-}
-
-sv_frame_t
-Resampler::D::resampleInterleaved(float *in, float *out,
-                                  sv_frame_t incount, double ratio,
-                                  bool final)
-{
-    SRC_DATA data;
-
-    sv_frame_t outcount = lrint(ceil(double(incount) * ratio));
-
-    data.data_in = in;
-    data.data_out = out;
-    data.input_frames = incount;
-    data.output_frames = outcount;
-    data.src_ratio = ratio;
-    data.end_of_input = (final ? 1 : 0);
-
-    int err = src_process(m_src, &data);
-
-    if (err) {
-        cerr << "Resampler: ERROR: src_process returned error: " <<
-            src_strerror(err) << endl;
-        return 0;
-    }
-
-    if (data.input_frames_used != incount) {
-        cerr << "Resampler: NOTE: input_frames_used == " << data.input_frames_used << " (while incount = " << incount << ")" << endl;
-    }
-
-    return data.output_frames_gen;
-}
-
-void
-Resampler::D::reset()
-{
-    src_reset(m_src);
-}
-
-Resampler::Resampler(Quality quality, int channels, sv_frame_t chunkSize)
-{
-    m_d = new D(quality, channels, chunkSize);
-}
-
-Resampler::~Resampler()
-{
-    delete m_d;
-}
-
-sv_frame_t 
-Resampler::resample(float **in, float **out,
-                    sv_frame_t incount, double ratio,
-                    bool final)
-{
-    return m_d->resample(in, out, incount, ratio, final);
-}
-
-sv_frame_t 
-Resampler::resampleInterleaved(float *in, float *out,
-                               sv_frame_t incount, double ratio,
-                               bool final)
-{
-    return m_d->resampleInterleaved(in, out, incount, ratio, final);
-}
-
-void
-Resampler::reset()
-{
-    m_d->reset();
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/ResamplerSV.cpp	Fri Oct 28 15:20:58 2016 +0100
@@ -0,0 +1,196 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+/*
+   This is a modified version of a source file from the 
+   Rubber Band audio timestretcher library.
+   This file copyright 2007 Chris Cannam.
+*/
+
+#include "Resampler.h"
+
+#include <cstdlib>
+#include <cmath>
+
+#include <iostream>
+
+#include <samplerate.h>
+
+#include "Debug.h"
+
+class Resampler::D
+{
+public:
+    D(Quality quality, int channels, sv_frame_t chunkSize);
+    ~D();
+
+    sv_frame_t resample(float **in, float **out,
+                 sv_frame_t incount, double ratio,
+                 bool final);
+
+    sv_frame_t resampleInterleaved(float *in, float *out,
+                            sv_frame_t incount, double ratio,
+                            bool final);
+
+    void reset();
+
+protected:
+    SRC_STATE *m_src;
+    float *m_iin;
+    float *m_iout;
+    int m_channels;
+    sv_frame_t m_iinsize;
+    sv_frame_t m_ioutsize;
+};
+
+Resampler::D::D(Quality quality, int channels, sv_frame_t chunkSize) :
+    m_src(0),
+    m_iin(0),
+    m_iout(0),
+    m_channels(channels),
+    m_iinsize(0),
+    m_ioutsize(0)
+{
+    int err = 0;
+    m_src = src_new(quality == Best ? SRC_SINC_BEST_QUALITY :
+                    quality == Fastest ? SRC_LINEAR :
+                    SRC_SINC_FASTEST,
+                    channels, &err);
+
+    //!!! check err, throw
+
+    if (chunkSize > 0 && m_channels > 1) {
+        //!!! alignment?
+        m_iinsize = chunkSize * m_channels;
+        m_ioutsize = chunkSize * m_channels * 2;
+        m_iin = (float *)malloc(m_iinsize * sizeof(float));
+        m_iout = (float *)malloc(m_ioutsize * sizeof(float));
+    }
+}
+
+Resampler::D::~D()
+{
+    src_delete(m_src);
+    if (m_iinsize > 0) {
+        free(m_iin);
+    }
+    if (m_ioutsize > 0) {
+        free(m_iout);
+    }
+}
+
+sv_frame_t
+Resampler::D::resample(float **in, float **out,
+                       sv_frame_t incount, double ratio,
+                       bool final)
+{
+    if (m_channels == 1) {
+        return resampleInterleaved(*in, *out, incount, ratio, final);
+    }
+
+    sv_frame_t outcount = lrint(ceil(double(incount) * ratio));
+
+    if (incount * m_channels > m_iinsize) {
+        m_iinsize = incount * m_channels;
+        m_iin = (float *)realloc(m_iin, m_iinsize * sizeof(float));
+    }
+    if (outcount * m_channels > m_ioutsize) {
+        m_ioutsize = outcount * m_channels;
+        m_iout = (float *)realloc(m_iout, m_ioutsize * sizeof(float));
+    }
+    for (sv_frame_t i = 0; i < incount; ++i) {
+        for (int c = 0; c < m_channels; ++c) {
+            m_iin[i * m_channels + c] = in[c][i];
+        }
+    }
+    
+    sv_frame_t gen = resampleInterleaved(m_iin, m_iout, incount, ratio, final);
+
+    for (sv_frame_t i = 0; i < gen; ++i) {
+        for (int c = 0; c < m_channels; ++c) {
+            out[c][i] = m_iout[i * m_channels + c];
+        }
+    }
+
+    return gen;
+}
+
+sv_frame_t
+Resampler::D::resampleInterleaved(float *in, float *out,
+                                  sv_frame_t incount, double ratio,
+                                  bool final)
+{
+    SRC_DATA data;
+
+    sv_frame_t outcount = lrint(ceil(double(incount) * ratio));
+
+    data.data_in = in;
+    data.data_out = out;
+    data.input_frames = incount;
+    data.output_frames = outcount;
+    data.src_ratio = ratio;
+    data.end_of_input = (final ? 1 : 0);
+
+    int err = src_process(m_src, &data);
+
+    if (err) {
+        cerr << "Resampler: ERROR: src_process returned error: " <<
+            src_strerror(err) << endl;
+        return 0;
+    }
+
+    if (data.input_frames_used != incount) {
+        cerr << "Resampler: NOTE: input_frames_used == " << data.input_frames_used << " (while incount = " << incount << ")" << endl;
+    }
+
+    return data.output_frames_gen;
+}
+
+void
+Resampler::D::reset()
+{
+    src_reset(m_src);
+}
+
+Resampler::Resampler(Quality quality, int channels, sv_frame_t chunkSize)
+{
+    m_d = new D(quality, channels, chunkSize);
+}
+
+Resampler::~Resampler()
+{
+    delete m_d;
+}
+
+sv_frame_t 
+Resampler::resample(float **in, float **out,
+                    sv_frame_t incount, double ratio,
+                    bool final)
+{
+    return m_d->resample(in, out, incount, ratio, final);
+}
+
+sv_frame_t 
+Resampler::resampleInterleaved(float *in, float *out,
+                               sv_frame_t incount, double ratio,
+                               bool final)
+{
+    return m_d->resampleInterleaved(in, out, incount, ratio, final);
+}
+
+void
+Resampler::reset()
+{
+    m_d->reset();
+}
+
--- a/data/model/SparseModel.h	Thu Oct 20 11:16:22 2016 +0100
+++ b/data/model/SparseModel.h	Fri Oct 28 15:20:58 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;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files.pri	Fri Oct 28 15:20:58 2016 +0100
@@ -0,0 +1,247 @@
+SVCORE_HEADERS = \
+           base/AudioLevel.h \
+           base/AudioPlaySource.h \
+           base/BaseTypes.h \
+           base/Clipboard.h \
+           base/ColumnOp.h \
+           base/Command.h \
+           base/Debug.h \
+           base/Exceptions.h \
+           base/LogRange.h \
+           base/MagnitudeRange.h \
+           base/Pitch.h \
+           base/Playable.h \
+           base/PlayParameterRepository.h \
+           base/PlayParameters.h \
+           base/Preferences.h \
+           base/Profiler.h \
+           base/ProgressPrinter.h \
+           base/ProgressReporter.h \
+           base/PropertyContainer.h \
+           base/RangeMapper.h \
+           base/RealTime.h \
+           base/RecentFiles.h \
+           base/Resampler.h \
+           base/ResourceFinder.h \
+           base/RingBuffer.h \
+           base/Scavenger.h \
+           base/Selection.h \
+           base/Serialiser.h \
+           base/StorageAdviser.h \
+           base/StringBits.h \
+           base/Strings.h \
+           base/TempDirectory.h \
+           base/TempWriteFile.h \
+           base/TextMatcher.h \
+           base/Thread.h \
+           base/UnitDatabase.h \
+           base/ViewManagerBase.h \
+           base/Window.h \
+           base/XmlExportable.h \
+           base/ZoomConstraint.h \
+	   data/fft/FFTapi.h \
+           data/fileio/AudioFileReader.h \
+           data/fileio/AudioFileReaderFactory.h \
+           data/fileio/AudioFileSizeEstimator.h \
+           data/fileio/BZipFileDevice.h \
+           data/fileio/CachedFile.h \
+           data/fileio/CodedAudioFileReader.h \
+           data/fileio/CSVFileReader.h \
+           data/fileio/CSVFileWriter.h \
+           data/fileio/CSVFormat.h \
+           data/fileio/DataFileReader.h \
+           data/fileio/DataFileReaderFactory.h \
+           data/fileio/FileFinder.h \
+           data/fileio/FileReadThread.h \
+           data/fileio/FileSource.h \
+           data/fileio/MIDIFileReader.h \
+           data/fileio/MIDIFileWriter.h \
+           data/fileio/MP3FileReader.h \
+           data/fileio/OggVorbisFileReader.h \
+           data/fileio/PlaylistFileReader.h \
+           data/fileio/QuickTimeFileReader.h \
+           data/fileio/CoreAudioFileReader.h \
+           data/fileio/DecodingWavFileReader.h \
+           data/fileio/WavFileReader.h \
+           data/fileio/WavFileWriter.h \
+           data/midi/MIDIEvent.h \
+           data/midi/MIDIInput.h \
+           data/midi/rtmidi/RtError.h \
+           data/midi/rtmidi/RtMidi.h \
+           data/model/AggregateWaveModel.h \
+           data/model/AlignmentModel.h \
+           data/model/Dense3DModelPeakCache.h \
+           data/model/DenseThreeDimensionalModel.h \
+           data/model/DenseTimeValueModel.h \
+           data/model/EditableDenseThreeDimensionalModel.h \
+           data/model/FFTModel.h \
+           data/model/ImageModel.h \
+           data/model/IntervalModel.h \
+           data/model/Labeller.h \
+           data/model/Model.h \
+           data/model/ModelDataTableModel.h \
+           data/model/NoteModel.h \
+           data/model/FlexiNoteModel.h \
+           data/model/PathModel.h \
+           data/model/PowerOfSqrtTwoZoomConstraint.h \
+           data/model/PowerOfTwoZoomConstraint.h \
+           data/model/RangeSummarisableTimeValueModel.h \
+           data/model/RegionModel.h \
+           data/model/SparseModel.h \
+           data/model/SparseOneDimensionalModel.h \
+           data/model/SparseTimeValueModel.h \
+           data/model/SparseValueModel.h \
+           data/model/TabularModel.h \
+           data/model/TextModel.h \
+           data/model/WaveFileModel.h \
+           data/model/ReadOnlyWaveFileModel.h \
+           data/model/WritableWaveFileModel.h \
+           data/osc/OSCMessage.h \
+           data/osc/OSCQueue.h \
+	   plugin/PluginScan.h \
+           plugin/DSSIPluginFactory.h \
+           plugin/DSSIPluginInstance.h \
+           plugin/FeatureExtractionPluginFactory.h \
+           plugin/LADSPAPluginFactory.h \
+           plugin/LADSPAPluginInstance.h \
+           plugin/NativeVampPluginFactory.h \
+           plugin/PiperVampPluginFactory.h \
+           plugin/PluginIdentifier.h \
+           plugin/PluginXml.h \
+           plugin/RealTimePluginFactory.h \
+           plugin/RealTimePluginInstance.h \
+           plugin/api/dssi.h \
+           plugin/api/ladspa.h \
+           plugin/plugins/SamplePlayer.h \
+           plugin/api/alsa/asoundef.h \
+           plugin/api/alsa/asoundlib.h \
+           plugin/api/alsa/seq.h \
+           plugin/api/alsa/seq_event.h \
+           plugin/api/alsa/seq_midi_event.h \
+           plugin/api/alsa/sound/asequencer.h \
+	   rdf/PluginRDFIndexer.h \
+           rdf/PluginRDFDescription.h \
+           rdf/RDFExporter.h \
+           rdf/RDFFeatureWriter.h \
+           rdf/RDFImporter.h \
+           rdf/RDFTransformFactory.h \
+	   system/Init.h \
+           system/System.h \
+	   transform/CSVFeatureWriter.h \
+           transform/FeatureExtractionModelTransformer.h \
+           transform/FeatureWriter.h \
+           transform/FileFeatureWriter.h \
+           transform/RealTimeEffectModelTransformer.h \
+           transform/Transform.h \
+           transform/TransformDescription.h \
+           transform/TransformFactory.h \
+           transform/ModelTransformer.h \
+           transform/ModelTransformerFactory.h
+	   
+SVCORE_SOURCES = \
+           base/AudioLevel.cpp \
+           base/Clipboard.cpp \
+           base/Command.cpp \
+           base/Debug.cpp \
+           base/Exceptions.cpp \
+           base/LogRange.cpp \
+           base/Pitch.cpp \
+           base/PlayParameterRepository.cpp \
+           base/PlayParameters.cpp \
+           base/Preferences.cpp \
+           base/Profiler.cpp \
+           base/ProgressPrinter.cpp \
+           base/ProgressReporter.cpp \
+           base/PropertyContainer.cpp \
+           base/RangeMapper.cpp \
+           base/RealTimeSV.cpp \
+           base/RecentFiles.cpp \
+           base/ResamplerSV.cpp \
+           base/ResourceFinder.cpp \
+           base/Selection.cpp \
+           base/Serialiser.cpp \
+           base/StorageAdviser.cpp \
+           base/StringBits.cpp \
+           base/Strings.cpp \
+           base/TempDirectory.cpp \
+           base/TempWriteFile.cpp \
+           base/TextMatcher.cpp \
+           base/Thread.cpp \
+           base/UnitDatabase.cpp \
+           base/ViewManagerBase.cpp \
+           base/XmlExportable.cpp \
+	   data/fft/FFTapi.cpp \
+           data/fileio/AudioFileReader.cpp \
+           data/fileio/AudioFileReaderFactory.cpp \
+           data/fileio/AudioFileSizeEstimator.cpp \
+           data/fileio/BZipFileDevice.cpp \
+           data/fileio/CachedFile.cpp \
+           data/fileio/CodedAudioFileReader.cpp \
+           data/fileio/CSVFileReader.cpp \
+           data/fileio/CSVFileWriter.cpp \
+           data/fileio/CSVFormat.cpp \
+           data/fileio/DataFileReaderFactory.cpp \
+           data/fileio/FileReadThread.cpp \
+           data/fileio/FileSource.cpp \
+           data/fileio/MIDIFileReader.cpp \
+           data/fileio/MIDIFileWriter.cpp \
+           data/fileio/MP3FileReader.cpp \
+           data/fileio/OggVorbisFileReader.cpp \
+           data/fileio/PlaylistFileReader.cpp \
+           data/fileio/QuickTimeFileReader.cpp \
+           data/fileio/CoreAudioFileReader.cpp \
+           data/fileio/DecodingWavFileReader.cpp \
+           data/fileio/WavFileReader.cpp \
+           data/fileio/WavFileWriter.cpp \
+           data/midi/MIDIInput.cpp \
+           data/midi/rtmidi/RtMidi.cpp \
+           data/model/AggregateWaveModel.cpp \
+           data/model/AlignmentModel.cpp \
+           data/model/Dense3DModelPeakCache.cpp \
+           data/model/DenseTimeValueModel.cpp \
+           data/model/EditableDenseThreeDimensionalModel.cpp \
+           data/model/FFTModel.cpp \
+           data/model/Model.cpp \
+           data/model/ModelDataTableModel.cpp \
+           data/model/PowerOfSqrtTwoZoomConstraint.cpp \
+           data/model/PowerOfTwoZoomConstraint.cpp \
+           data/model/RangeSummarisableTimeValueModel.cpp \
+           data/model/WaveFileModel.cpp \
+           data/model/ReadOnlyWaveFileModel.cpp \
+           data/model/WritableWaveFileModel.cpp \
+           data/osc/OSCMessage.cpp \
+           data/osc/OSCQueue.cpp \
+	   plugin/PluginScan.cpp \
+           plugin/DSSIPluginFactory.cpp \
+           plugin/DSSIPluginInstance.cpp \
+           plugin/FeatureExtractionPluginFactory.cpp \
+           plugin/LADSPAPluginFactory.cpp \
+           plugin/LADSPAPluginInstance.cpp \
+           plugin/NativeVampPluginFactory.cpp \
+           plugin/PiperVampPluginFactory.cpp \
+           plugin/PluginIdentifier.cpp \
+           plugin/PluginXml.cpp \
+           plugin/RealTimePluginFactory.cpp \
+           plugin/RealTimePluginInstance.cpp \
+           plugin/plugins/SamplePlayer.cpp \
+	   rdf/PluginRDFIndexer.cpp \
+           rdf/PluginRDFDescription.cpp \
+           rdf/RDFExporter.cpp \
+           rdf/RDFFeatureWriter.cpp \
+           rdf/RDFImporter.cpp \
+           rdf/RDFTransformFactory.cpp \
+	   system/Init.cpp \
+           system/System.cpp \
+	   transform/CSVFeatureWriter.cpp \
+           transform/FeatureExtractionModelTransformer.cpp \
+           transform/FileFeatureWriter.cpp \
+           transform/RealTimeEffectModelTransformer.cpp \
+           transform/Transform.cpp \
+           transform/TransformFactory.cpp \
+           transform/ModelTransformer.cpp \
+           transform/ModelTransformerFactory.cpp
+
+!linux* {
+    SVCORE_SOURCES += plugin/api/dssi_alsa_compat.c 
+}
+
--- a/plugin/FeatureExtractionPluginFactory.cpp	Thu Oct 20 11:16:22 2016 +0100
+++ b/plugin/FeatureExtractionPluginFactory.cpp	Fri Oct 28 15:20:58 2016 +0100
@@ -4,7 +4,7 @@
     Sonic Visualiser
     An audio file viewer and annotation editor.
     Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam and QMUL.
+    This file copyright 2006-2016 Chris Cannam and QMUL.
     
     This program is free software; you can redistribute it and/or
     modify it under the terms of the GNU General Public License as
@@ -13,423 +13,32 @@
     COPYING included with this distribution for more information.
 */
 
-#include "FeatureExtractionPluginFactory.h"
-#include "PluginIdentifier.h"
+#include "PiperVampPluginFactory.h"
+#include "NativeVampPluginFactory.h"
 
-#include <vamp-hostsdk/PluginHostAdapter.h>
-#include <vamp-hostsdk/PluginWrapper.h>
+#include <QMutex>
+#include <QMutexLocker>
 
-#include "system/System.h"
-
-#include "PluginScan.h"
-
-#include <QDir>
-#include <QFile>
-#include <QFileInfo>
-#include <QTextStream>
-
-#include <iostream>
-
-#include "base/Profiler.h"
-
-using namespace std;
-
-//#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;
+#include "base/Preferences.h"
 
 FeatureExtractionPluginFactory *
-FeatureExtractionPluginFactory::instance(QString pluginType)
+FeatureExtractionPluginFactory::instance()
 {
-    if (pluginType == "vamp") {
-	if (!_nativeInstance) {
-//	    SVDEBUG << "FeatureExtractionPluginFactory::instance(" << pluginType//		      << "): creating new FeatureExtractionPluginFactory" << endl;
-	    _nativeInstance = new FeatureExtractionPluginFactory();
-	}
-	return _nativeInstance;
-    }
+    static QMutex mutex;
+    static FeatureExtractionPluginFactory *instance = 0;
 
-    else return 0;
-}
+    QMutexLocker locker(&mutex);
+    
+    if (!instance) {
 
-FeatureExtractionPluginFactory *
-FeatureExtractionPluginFactory::instanceFor(QString identifier)
-{
-    QString type, soName, label;
-    PluginIdentifier::parseIdentifier(identifier, type, soName, label);
-    return instance(type);
-}
-
-vector<QString>
-FeatureExtractionPluginFactory::getPluginPath()
-{
-    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>
-FeatureExtractionPluginFactory::getAllPluginIdentifiers()
-{
-    FeatureExtractionPluginFactory *factory;
-    vector<QString> rv;
-    
-    factory = instance("vamp");
-    if (factory) {
-	vector<QString> tmp = factory->getPluginIdentifiers();
-	for (size_t i = 0; i < tmp.size(); ++i) {
-//            cerr << "identifier: " << tmp[i] << endl;
-	    rv.push_back(tmp[i]);
-	}
-    }
-
-    // Plugins can change the locale, revert it to default.
-    RestoreStartupLocale();
-
-    return rv;
-}
-
-vector<QString>
-FeatureExtractionPluginFactory::getPluginIdentifiers()
-{
-    Profiler profiler("FeatureExtractionPluginFactory::getPluginIdentifiers");
-
-    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;
+        if (Preferences::getInstance()->getRunPluginsInProcess()) {
+            cerr << "creating native instance" << endl;
+            instance = new NativeVampPluginFactory();
+        } else {
+            cerr << "creating piper instance" << endl;
+            instance = new PiperVampPluginFactory();
         }
     }
 
-    generateTaxonomy();
-
-    return rv;
+    return instance;
 }
-
-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;
-    }
-
-    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
-FeatureExtractionPluginFactory::getPluginCategory(QString identifier)
-{
-    return m_taxonomy[identifier];
-}
-
-void
-FeatureExtractionPluginFactory::generateTaxonomy()
-{
-    vector<QString> pluginPath = getPluginPath();
-    vector<QString> path;
-
-    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 (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	Fri Oct 28 15:20:58 2016 +0100
@@ -4,7 +4,7 @@
     Sonic Visualiser
     An audio file viewer and annotation editor.
     Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam.
+    This file copyright 2006-2016 Chris Cannam and QMUL.
     
     This program is free software; you can redistribute it and/or
     modify it under the terms of the GNU General Public License as
@@ -13,52 +13,55 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _FEATURE_EXTRACTION_PLUGIN_FACTORY_H_
-#define _FEATURE_EXTRACTION_PLUGIN_FACTORY_H_
-
-#include <QString>
-#include <vector>
-#include <map>
+#ifndef SV_FEATURE_EXTRACTION_PLUGIN_FACTORY_H
+#define SV_FEATURE_EXTRACTION_PLUGIN_FACTORY_H
 
 #include <vamp-hostsdk/Plugin.h>
 
-#include "base/Debug.h"
+#include "vamp-support/PluginStaticData.h"
+
 #include "base/BaseTypes.h"
 
+#include <QString>
+
 class FeatureExtractionPluginFactory
 {
 public:
+    static FeatureExtractionPluginFactory *instance();
+    
     virtual ~FeatureExtractionPluginFactory() { }
 
-    static FeatureExtractionPluginFactory *instance(QString pluginType);
-    static FeatureExtractionPluginFactory *instanceFor(QString identifier);
-    static std::vector<QString> getAllPluginIdentifiers();
+    /**
+     * Return all installed plugin identifiers.
+     */
+    virtual std::vector<QString> getPluginIdentifiers(QString &errorMessage) {
+        return instance()->getPluginIdentifiers(errorMessage);
+    }
 
-    virtual std::vector<QString> getPluginPath();
+    /**
+     * Return static data for the given plugin.
+     */
+    virtual piper_vamp::PluginStaticData getPluginStaticData(QString identifier) {
+        return instance()->getPluginStaticData(identifier);
+    }
 
-    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
+    /**
+     * Instantiate (load) and return pointer to the plugin with the
+     * given identifier, at the given sample rate. We don't set
+     * blockSize or channels on this -- they're negotiated and handled
+     * via initialize() on the plugin itself after loading.
+     */
     virtual Vamp::Plugin *instantiatePlugin(QString identifier,
-                                            sv_samplerate_t inputSampleRate);
+                                            sv_samplerate_t inputSampleRate) {
+        return instance()->instantiatePlugin(identifier, inputSampleRate);
+    }
 
     /**
      * Get category metadata about a plugin (without instantiating it).
      */
-    virtual QString getPluginCategory(QString identifier);
-
-protected:
-    std::vector<QString> m_pluginPath;
-    std::map<QString, QString> m_taxonomy;
-
-    friend class PluginDeletionNotifyAdapter;
-    void pluginDeleted(Vamp::Plugin *);
-    std::map<Vamp::Plugin *, void *> m_handleMap;
-    
-    void generateTaxonomy();
+    virtual QString getPluginCategory(QString identifier) {
+        return instance()->getPluginCategory(identifier);
+    }
 };
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/NativeVampPluginFactory.cpp	Fri Oct 28 15:20:58 2016 +0100
@@ -0,0 +1,432 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006-2016 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "NativeVampPluginFactory.h"
+#include "PluginIdentifier.h"
+
+#include <vamp-hostsdk/PluginHostAdapter.h>
+#include <vamp-hostsdk/PluginWrapper.h>
+
+#include "system/System.h"
+
+#include "PluginScan.h"
+
+#include <QDir>
+#include <QFile>
+#include <QFileInfo>
+#include <QTextStream>
+
+#include <iostream>
+
+#include "base/Profiler.h"
+
+#include <QMutex>
+#include <QMutexLocker>
+
+using namespace std;
+
+//#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
+
+class PluginDeletionNotifyAdapter : public Vamp::HostExt::PluginWrapper {
+public:
+    PluginDeletionNotifyAdapter(Vamp::Plugin *plugin,
+                                NativeVampPluginFactory *factory) :
+        PluginWrapper(plugin), m_factory(factory) { }
+    virtual ~PluginDeletionNotifyAdapter();
+protected:
+    NativeVampPluginFactory *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);
+}
+
+vector<QString>
+NativeVampPluginFactory::getPluginPath()
+{
+    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>
+NativeVampPluginFactory::getPluginIdentifiers(QString &)
+{
+    Profiler profiler("NativeVampPluginFactory::getPluginIdentifiers");
+
+    QMutexLocker locker(&m_mutex);
+
+    if (!m_identifiers.empty()) {
+        return m_identifiers;
+    }
+    
+    QStringList candidates = PluginScan::getInstance()->getCandidateLibrariesFor
+        (PluginScan::VampPlugin);
+    
+    for (QString soname : candidates) {
+
+        void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
+            
+        if (!libraryHandle) {
+            cerr << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl;
+            continue;
+        }
+
+        VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
+            DLSYM(libraryHandle, "vampGetPluginDescriptor");
+
+        if (!fn) {
+            cerr << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl;
+            if (DLCLOSE(libraryHandle) != 0) {
+                cerr << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
+            }
+            continue;
+        }
+
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+            cerr << "NativeVampPluginFactory::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: NativeVampPluginFactory::getPluginIdentifiers: Plugin library "
+                     << soname
+                     << " returns the same plugin identifier \""
+                     << descriptor->identifier << "\" at indices "
+                     << known[descriptor->identifier] << " and "
+                     << index << endl;
+                    cerr << "NativeVampPluginFactory::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);
+                m_identifiers.push_back(id);
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+                cerr << "NativeVampPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl;
+#endif
+                ++index;
+            }
+        }
+            
+        if (DLCLOSE(libraryHandle) != 0) {
+            cerr << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
+        }
+    }
+
+    generateTaxonomy();
+
+    // Plugins can change the locale, revert it to default.
+    RestoreStartupLocale();
+
+    return m_identifiers;
+}
+
+QString
+NativeVampPluginFactory::findPluginFile(QString soname, QString inDir)
+{
+    QString file = "";
+
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+    cerr << "NativeVampPluginFactory::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 << "NativeVampPluginFactory::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 << "NativeVampPluginFactory::findPluginFile: "
+                          << "found \"" << soname << "\" at " << file << endl;
+#endif
+
+                return file;
+            }
+        }
+
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+        cerr << "NativeVampPluginFactory::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 << "NativeVampPluginFactory::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 << "NativeVampPluginFactory::findPluginFile: "
+                  << "not found" << endl;
+#endif
+
+        return "";
+    }
+}
+
+Vamp::Plugin *
+NativeVampPluginFactory::instantiatePlugin(QString identifier,
+                                           sv_samplerate_t inputSampleRate)
+{
+    Profiler profiler("NativeVampPluginFactory::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 << "NativeVampPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl;
+#endif
+	return 0;
+    }
+
+    QString found = findPluginFile(soname);
+
+    if (found == "") {
+        cerr << "NativeVampPluginFactory::instantiatePlugin: Failed to find library file " << soname << endl;
+        return 0;
+    } else if (found != soname) {
+
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+        cerr << "NativeVampPluginFactory::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 << "NativeVampPluginFactory::instantiatePlugin: Failed to load library " << soname << ": " << DLERROR() << endl;
+        return 0;
+    }
+
+    VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
+        DLSYM(libraryHandle, "vampGetPluginDescriptor");
+    
+    if (!fn) {
+        cerr << "NativeVampPluginFactory::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 << "NativeVampPluginFactory::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 << "NativeVampPluginFactory::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: NativeVampPluginFactory::instantiatePlugin: Failed to unload library " << soname << endl;
+        }
+    }
+
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+    cerr << "NativeVampPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl;
+#endif
+    
+    return rv;
+}
+
+void
+NativeVampPluginFactory::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
+NativeVampPluginFactory::getPluginCategory(QString identifier)
+{
+    return m_taxonomy[identifier];
+}
+
+void
+NativeVampPluginFactory::generateTaxonomy()
+{
+    vector<QString> pluginPath = getPluginPath();
+    vector<QString> path;
+
+    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 (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 << "NativeVampPluginFactory: set id \"" << id << "\" to cat \"" << cat << "\"" << endl;
+		}
+	    }
+	}
+    }
+}    
+
+piper_vamp::PluginStaticData
+NativeVampPluginFactory::getPluginStaticData(QString identifier)
+{
+    QMutexLocker locker(&m_mutex);
+
+    if (m_pluginData.find(identifier) != m_pluginData.end()) {
+        return m_pluginData[identifier];
+    }
+    
+    QString type, soname, label;
+    PluginIdentifier::parseIdentifier(identifier, type, soname, label);
+    std::string pluginKey = (soname + ":" + label).toStdString();
+
+    std::vector<std::string> catlist;
+    for (auto s: getPluginCategory(identifier).split(" > ")) {
+        catlist.push_back(s.toStdString());
+    }
+    
+    Vamp::Plugin *p = instantiatePlugin(identifier, 44100);
+    if (!p) return {};
+
+    auto psd = piper_vamp::PluginStaticData::fromPlugin(pluginKey,
+                                                        catlist,
+                                                        p);
+
+    delete p;
+    
+    m_pluginData[identifier] = psd;
+    return psd;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/NativeVampPluginFactory.h	Fri Oct 28 15:20:58 2016 +0100
@@ -0,0 +1,68 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006-2016 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef SV_NATIVE_VAMP_PLUGIN_FACTORY_H
+#define SV_NATIVE_VAMP_PLUGIN_FACTORY_H
+
+#include "FeatureExtractionPluginFactory.h"
+
+#include <vector>
+#include <map>
+
+#include "base/Debug.h"
+
+#include <QMutex>
+
+/**
+ * FeatureExtractionPluginFactory type for Vamp plugins hosted
+ * in-process.
+ */
+class NativeVampPluginFactory : public FeatureExtractionPluginFactory
+{
+public:
+    virtual ~NativeVampPluginFactory() { }
+
+    virtual std::vector<QString> getPluginIdentifiers(QString &errorMessage)
+        override;
+
+    virtual piper_vamp::PluginStaticData getPluginStaticData(QString identifier)
+        override;
+
+    virtual Vamp::Plugin *instantiatePlugin(QString identifier,
+                                            sv_samplerate_t inputSampleRate)
+        override;
+
+    /**
+     * Get category metadata about a plugin (without instantiating it).
+     */
+    virtual QString getPluginCategory(QString identifier) override;
+
+protected:
+    QMutex m_mutex;
+    std::vector<QString> m_pluginPath;
+    std::vector<QString> m_identifiers;
+    std::map<QString, QString> m_taxonomy; // identifier -> category string
+    std::map<QString, piper_vamp::PluginStaticData> m_pluginData; // identifier -> data (created opportunistically)
+
+    friend class PluginDeletionNotifyAdapter;
+    void pluginDeleted(Vamp::Plugin *);
+    std::map<Vamp::Plugin *, void *> m_handleMap;
+
+    QString findPluginFile(QString soname, QString inDir = "");
+    std::vector<QString> getPluginPath();
+    void generateTaxonomy();
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/PiperVampPluginFactory.cpp	Fri Oct 28 15:20:58 2016 +0100
@@ -0,0 +1,182 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "PiperVampPluginFactory.h"
+#include "PluginIdentifier.h"
+
+#include "system/System.h"
+
+#include "PluginScan.h"
+
+#ifdef _WIN32
+#undef VOID
+#undef ERROR
+#define CAPNP_LITE 1
+#endif
+
+#include "vamp-client/AutoPlugin.h"
+
+#include <QDir>
+#include <QFile>
+#include <QFileInfo>
+#include <QTextStream>
+#include <QCoreApplication>
+
+#include <iostream>
+
+#include "base/Profiler.h"
+
+#include "vamp-client/ProcessQtTransport.h"
+#include "vamp-client/CapnpRRClient.h"
+
+using namespace std;
+
+//#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
+
+PiperVampPluginFactory::PiperVampPluginFactory() :
+    // No server unless we find one - don't run arbitrary stuff from the path:
+    m_serverName()
+{
+    // Server must exist either in the same directory as this one or
+    // (preferably) a subdirectory called "piper-bin".
+    //!!! todo: merge this with plugin scan checker thingy used in main.cpp?
+    QString myDir = QCoreApplication::applicationDirPath();
+    QString name = "piper-vamp-simple-server";
+    QString path = myDir + "/piper-bin/" + name;
+    QString suffix = "";
+#ifdef _WIN32
+    suffix = ".exe";
+#endif
+    if (!QFile(path + suffix).exists()) {
+        cerr << "NOTE: Piper Vamp server not found at " << (path + suffix)
+             << ", trying in my own directory" << endl;
+        path = myDir + "/" + name;
+    }
+    if (!QFile(path + suffix).exists()) {
+        cerr << "NOTE: Piper Vamp server not found at " << (path + suffix)
+             << endl;
+    } else {
+        m_serverName = (path + suffix).toStdString();
+    }
+}
+
+vector<QString>
+PiperVampPluginFactory::getPluginIdentifiers(QString &errorMessage)
+{
+    Profiler profiler("PiperVampPluginFactory::getPluginIdentifiers");
+
+    QMutexLocker locker(&m_mutex);
+
+    if (m_serverName == "") {
+        errorMessage = QObject::tr("External plugin host executable does not appear to be installed");
+        return {};
+    }
+    
+    if (m_pluginData.empty()) {
+        populate(errorMessage);
+    }
+
+    vector<QString> rv;
+
+    for (const auto &d: m_pluginData) {
+        rv.push_back(QString("vamp:") + QString::fromStdString(d.second.pluginKey));
+    }
+
+    return rv;
+}
+
+Vamp::Plugin *
+PiperVampPluginFactory::instantiatePlugin(QString identifier,
+                                          sv_samplerate_t inputSampleRate)
+{
+    Profiler profiler("PiperVampPluginFactory::instantiatePlugin");
+
+    auto psd = getPluginStaticData(identifier);
+    if (psd.pluginKey == "") {
+        return 0;
+    }
+    
+    auto ap = new piper_vamp::client::AutoPlugin
+        (m_serverName, psd.pluginKey, float(inputSampleRate), 0);
+    if (!ap->isOK()) {
+        delete ap;
+        return 0;
+    }
+
+    return ap;
+}
+
+piper_vamp::PluginStaticData
+PiperVampPluginFactory::getPluginStaticData(QString identifier)
+{
+    if (m_pluginData.find(identifier) != m_pluginData.end()) {
+        return m_pluginData[identifier];
+    } else {
+        return {};
+    }
+}
+
+QString
+PiperVampPluginFactory::getPluginCategory(QString identifier)
+{
+    if (m_taxonomy.find(identifier) != m_taxonomy.end()) {
+        return m_taxonomy[identifier];
+    } else {
+        return {};
+    }
+}
+
+void
+PiperVampPluginFactory::populate(QString &errorMessage)
+{
+    if (m_serverName == "") return;
+
+    piper_vamp::client::ProcessQtTransport transport(m_serverName, "capnp");
+    if (!transport.isOK()) {
+        errorMessage = QObject::tr("Could not start external plugin host");
+        return;
+    }
+
+    piper_vamp::client::CapnpRRClient client(&transport);
+    piper_vamp::ListResponse lr;
+
+    try {
+        lr = client.listPluginData();
+    } catch (piper_vamp::client::ServerCrashed) {
+        errorMessage = QObject::tr
+            ("External plugin host exited unexpectedly while listing plugins");
+        return;
+    } catch (const std::exception &e) {
+        errorMessage = QObject::tr("External plugin host invocation failed: %1")
+            .arg(e.what());
+        return;
+    }
+
+    for (const auto &pd: lr.available) {
+
+        QString identifier =
+            QString("vamp:") + QString::fromStdString(pd.pluginKey);
+
+        m_pluginData[identifier] = pd;
+
+        QStringList catlist;
+        for (const auto &cs: pd.category) {
+            catlist.push_back(QString::fromStdString(cs));
+        }
+
+        m_taxonomy[identifier] = catlist.join(" > ");
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/PiperVampPluginFactory.h	Fri Oct 28 15:20:58 2016 +0100
@@ -0,0 +1,58 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006-2016 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef SV_PIPER_VAMP_PLUGIN_FACTORY_H
+#define SV_PIPER_VAMP_PLUGIN_FACTORY_H
+
+#include "FeatureExtractionPluginFactory.h"
+
+#include <QMutex>
+#include <vector>
+#include <map>
+
+#include "base/Debug.h"
+
+/**
+ * FeatureExtractionPluginFactory type for Vamp plugins hosted in a
+ * separate process using Piper protocol.
+ */
+class PiperVampPluginFactory : public FeatureExtractionPluginFactory
+{
+public:
+    PiperVampPluginFactory();
+
+    virtual ~PiperVampPluginFactory() { }
+
+    virtual std::vector<QString> getPluginIdentifiers(QString &errorMessage)
+        override;
+
+    virtual piper_vamp::PluginStaticData getPluginStaticData(QString identifier)
+        override;
+    
+    virtual Vamp::Plugin *instantiatePlugin(QString identifier,
+                                            sv_samplerate_t inputSampleRate)
+        override;
+
+    virtual QString getPluginCategory(QString identifier) override;
+
+protected:
+    QMutex m_mutex;
+    std::string m_serverName;
+    std::map<QString, piper_vamp::PluginStaticData> m_pluginData; // identifier -> data
+    std::map<QString, QString> m_taxonomy; // identifier -> category string
+    void populate(QString &errorMessage);
+};
+
+#endif
--- a/plugin/PluginScan.cpp	Thu Oct 20 11:16:22 2016 +0100
+++ b/plugin/PluginScan.cpp	Fri Oct 28 15:20:58 2016 +0100
@@ -23,10 +23,15 @@
 
 using std::string;
 
+//#define DEBUG_PLUGIN_SCAN 1
+
 class PluginScan::Logger : public PluginCandidates::LogCallback
 {
 protected:
     void log(std::string message) {
+#ifdef DEBUG_PLUGIN_SCAN
+        cerr << "PluginScan: " << message;
+#endif
         SVDEBUG << "PluginScan: " << message;
     }
 };
--- a/svcore.pro	Thu Oct 20 11:16:22 2016 +0100
+++ b/svcore.pro	Fri Oct 28 15:20:58 2016 +0100
@@ -2,41 +2,10 @@
 TEMPLATE = lib
 
 INCLUDEPATH += ../vamp-plugin-sdk
-DEFINES += HAVE_VAMP HAVE_VAMPHOSTSDK
 
 exists(config.pri) {
     include(config.pri)
 }
-!exists(config.pri) {
-
-    CONFIG += release
-    DEFINES += NDEBUG BUILD_RELEASE NO_TIMING
-
-    win32-g++ {
-        INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include
-        LIBS += -L../sv-dependency-builds/win32-mingw/lib
-    }
-    win32-msvc* {
-        # We actually expect MSVC to be used only for 64-bit builds,
-        # though the qmake spec is still called win32-msvc*
-        INCLUDEPATH += ../sv-dependency-builds/win64-msvc/include
-        LIBS += -L../sv-dependency-builds/win64-msvc/lib
-    }
-    macx* {
-        INCLUDEPATH += ../sv-dependency-builds/osx/include
-        LIBS += -L../sv-dependency-builds/osx/lib
-    }
-
-    DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_LIBLO HAVE_MAD HAVE_ID3TAG 
-
-    macx* {
-        DEFINES += HAVE_COREAUDIO
-    }
-    win32-msvc* {
-        DEFINES += NOMINMAX _USE_MATH_DEFINES
-        DEFINES -= HAVE_LIBLO
-    }
-}
 
 CONFIG += staticlib qt thread warn_on stl rtti exceptions c++11
 QT += network xml
@@ -44,8 +13,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
 
@@ -58,249 +27,8 @@
 win*:     DEFINES += __WINDOWS_MM__
 solaris*: DEFINES += __RTMIDI_DUMMY_ONLY__
 
-HEADERS += base/AudioLevel.h \
-           base/AudioPlaySource.h \
-           base/BaseTypes.h \
-           base/Clipboard.h \
-           base/ColumnOp.h \
-           base/Command.h \
-           base/Debug.h \
-           base/Exceptions.h \
-           base/LogRange.h \
-           base/MagnitudeRange.h \
-           base/Pitch.h \
-           base/Playable.h \
-           base/PlayParameterRepository.h \
-           base/PlayParameters.h \
-           base/Preferences.h \
-           base/Profiler.h \
-           base/ProgressPrinter.h \
-           base/ProgressReporter.h \
-           base/PropertyContainer.h \
-           base/RangeMapper.h \
-           base/RealTime.h \
-           base/RecentFiles.h \
-           base/Resampler.h \
-           base/ResourceFinder.h \
-           base/RingBuffer.h \
-           base/Scavenger.h \
-           base/Selection.h \
-           base/Serialiser.h \
-           base/StorageAdviser.h \
-           base/StringBits.h \
-           base/Strings.h \
-           base/TempDirectory.h \
-           base/TempWriteFile.h \
-           base/TextMatcher.h \
-           base/Thread.h \
-           base/UnitDatabase.h \
-           base/ViewManagerBase.h \
-           base/Window.h \
-           base/XmlExportable.h \
-           base/ZoomConstraint.h
-SOURCES += base/AudioLevel.cpp \
-           base/Clipboard.cpp \
-           base/Command.cpp \
-           base/Debug.cpp \
-           base/Exceptions.cpp \
-           base/LogRange.cpp \
-           base/Pitch.cpp \
-           base/PlayParameterRepository.cpp \
-           base/PlayParameters.cpp \
-           base/Preferences.cpp \
-           base/Profiler.cpp \
-           base/ProgressPrinter.cpp \
-           base/ProgressReporter.cpp \
-           base/PropertyContainer.cpp \
-           base/RangeMapper.cpp \
-           base/RealTime.cpp \
-           base/RecentFiles.cpp \
-           base/Resampler.cpp \
-           base/ResourceFinder.cpp \
-           base/Selection.cpp \
-           base/Serialiser.cpp \
-           base/StorageAdviser.cpp \
-           base/StringBits.cpp \
-           base/Strings.cpp \
-           base/TempDirectory.cpp \
-           base/TempWriteFile.cpp \
-           base/TextMatcher.cpp \
-           base/Thread.cpp \
-           base/UnitDatabase.cpp \
-           base/ViewManagerBase.cpp \
-           base/XmlExportable.cpp
+include(files.pri)
 
-HEADERS += data/fft/FFTapi.h \
-           data/fileio/AudioFileReader.h \
-           data/fileio/AudioFileReaderFactory.h \
-           data/fileio/AudioFileSizeEstimator.h \
-           data/fileio/BZipFileDevice.h \
-           data/fileio/CachedFile.h \
-           data/fileio/CodedAudioFileReader.h \
-           data/fileio/CSVFileReader.h \
-           data/fileio/CSVFileWriter.h \
-           data/fileio/CSVFormat.h \
-           data/fileio/DataFileReader.h \
-           data/fileio/DataFileReaderFactory.h \
-           data/fileio/FileFinder.h \
-           data/fileio/FileReadThread.h \
-           data/fileio/FileSource.h \
-           data/fileio/MIDIFileReader.h \
-           data/fileio/MIDIFileWriter.h \
-           data/fileio/MP3FileReader.h \
-           data/fileio/OggVorbisFileReader.h \
-           data/fileio/PlaylistFileReader.h \
-           data/fileio/QuickTimeFileReader.h \
-           data/fileio/CoreAudioFileReader.h \
-           data/fileio/DecodingWavFileReader.h \
-           data/fileio/WavFileReader.h \
-           data/fileio/WavFileWriter.h \
-           data/midi/MIDIEvent.h \
-           data/midi/MIDIInput.h \
-           data/midi/rtmidi/RtError.h \
-           data/midi/rtmidi/RtMidi.h \
-           data/model/AggregateWaveModel.h \
-           data/model/AlignmentModel.h \
-           data/model/Dense3DModelPeakCache.h \
-           data/model/DenseThreeDimensionalModel.h \
-           data/model/DenseTimeValueModel.h \
-           data/model/EditableDenseThreeDimensionalModel.h \
-           data/model/FFTModel.h \
-           data/model/ImageModel.h \
-           data/model/IntervalModel.h \
-           data/model/Labeller.h \
-           data/model/Model.h \
-           data/model/ModelDataTableModel.h \
-           data/model/NoteModel.h \
-           data/model/FlexiNoteModel.h \
-           data/model/PathModel.h \
-           data/model/PowerOfSqrtTwoZoomConstraint.h \
-           data/model/PowerOfTwoZoomConstraint.h \
-           data/model/RangeSummarisableTimeValueModel.h \
-           data/model/RegionModel.h \
-           data/model/SparseModel.h \
-           data/model/SparseOneDimensionalModel.h \
-           data/model/SparseTimeValueModel.h \
-           data/model/SparseValueModel.h \
-           data/model/TabularModel.h \
-           data/model/TextModel.h \
-           data/model/WaveFileModel.h \
-           data/model/ReadOnlyWaveFileModel.h \
-           data/model/WritableWaveFileModel.h \
-           data/osc/OSCMessage.h \
-           data/osc/OSCQueue.h 
-SOURCES += data/fft/FFTapi.cpp \
-           data/fileio/AudioFileReader.cpp \
-           data/fileio/AudioFileReaderFactory.cpp \
-           data/fileio/AudioFileSizeEstimator.cpp \
-           data/fileio/BZipFileDevice.cpp \
-           data/fileio/CachedFile.cpp \
-           data/fileio/CodedAudioFileReader.cpp \
-           data/fileio/CSVFileReader.cpp \
-           data/fileio/CSVFileWriter.cpp \
-           data/fileio/CSVFormat.cpp \
-           data/fileio/DataFileReaderFactory.cpp \
-           data/fileio/FileReadThread.cpp \
-           data/fileio/FileSource.cpp \
-           data/fileio/MIDIFileReader.cpp \
-           data/fileio/MIDIFileWriter.cpp \
-           data/fileio/MP3FileReader.cpp \
-           data/fileio/OggVorbisFileReader.cpp \
-           data/fileio/PlaylistFileReader.cpp \
-           data/fileio/QuickTimeFileReader.cpp \
-           data/fileio/CoreAudioFileReader.cpp \
-           data/fileio/DecodingWavFileReader.cpp \
-           data/fileio/WavFileReader.cpp \
-           data/fileio/WavFileWriter.cpp \
-           data/midi/MIDIInput.cpp \
-           data/midi/rtmidi/RtMidi.cpp \
-           data/model/AggregateWaveModel.cpp \
-           data/model/AlignmentModel.cpp \
-           data/model/Dense3DModelPeakCache.cpp \
-           data/model/DenseTimeValueModel.cpp \
-           data/model/EditableDenseThreeDimensionalModel.cpp \
-           data/model/FFTModel.cpp \
-           data/model/Model.cpp \
-           data/model/ModelDataTableModel.cpp \
-           data/model/PowerOfSqrtTwoZoomConstraint.cpp \
-           data/model/PowerOfTwoZoomConstraint.cpp \
-           data/model/RangeSummarisableTimeValueModel.cpp \
-           data/model/WaveFileModel.cpp \
-           data/model/ReadOnlyWaveFileModel.cpp \
-           data/model/WritableWaveFileModel.cpp \
-           data/osc/OSCMessage.cpp \
-           data/osc/OSCQueue.cpp 
+HEADERS = $$(SVCORE_HEADERS)
+SOURCES = $$(SVCORE_SOURCES)
 
-HEADERS += plugin/PluginScan.h \
-           plugin/DSSIPluginFactory.h \
-           plugin/DSSIPluginInstance.h \
-           plugin/FeatureExtractionPluginFactory.h \
-           plugin/LADSPAPluginFactory.h \
-           plugin/LADSPAPluginInstance.h \
-           plugin/PluginIdentifier.h \
-           plugin/PluginXml.h \
-           plugin/RealTimePluginFactory.h \
-           plugin/RealTimePluginInstance.h \
-           plugin/api/dssi.h \
-           plugin/api/ladspa.h \
-           plugin/plugins/SamplePlayer.h \
-           plugin/api/alsa/asoundef.h \
-           plugin/api/alsa/asoundlib.h \
-           plugin/api/alsa/seq.h \
-           plugin/api/alsa/seq_event.h \
-           plugin/api/alsa/seq_midi_event.h \
-           plugin/api/alsa/sound/asequencer.h
-
-
-SOURCES += plugin/PluginScan.cpp \
-           plugin/DSSIPluginFactory.cpp \
-           plugin/DSSIPluginInstance.cpp \
-           plugin/FeatureExtractionPluginFactory.cpp \
-           plugin/LADSPAPluginFactory.cpp \
-           plugin/LADSPAPluginInstance.cpp \
-           plugin/PluginIdentifier.cpp \
-           plugin/PluginXml.cpp \
-           plugin/RealTimePluginFactory.cpp \
-           plugin/RealTimePluginInstance.cpp \
-           plugin/plugins/SamplePlayer.cpp
-
-!linux* {
-SOURCES += plugin/api/dssi_alsa_compat.c 
-}
-
-HEADERS += rdf/PluginRDFIndexer.h \
-           rdf/PluginRDFDescription.h \
-           rdf/RDFExporter.h \
-           rdf/RDFFeatureWriter.h \
-           rdf/RDFImporter.h \
-           rdf/RDFTransformFactory.h
-SOURCES += rdf/PluginRDFIndexer.cpp \
-           rdf/PluginRDFDescription.cpp \
-           rdf/RDFExporter.cpp \
-           rdf/RDFFeatureWriter.cpp \
-           rdf/RDFImporter.cpp \
-           rdf/RDFTransformFactory.cpp
-
-HEADERS += system/Init.h \
-           system/System.h
-SOURCES += system/Init.cpp \
-           system/System.cpp
-
-HEADERS += transform/CSVFeatureWriter.h \
-           transform/FeatureExtractionModelTransformer.h \
-           transform/FeatureWriter.h \
-           transform/FileFeatureWriter.h \
-           transform/RealTimeEffectModelTransformer.h \
-           transform/Transform.h \
-           transform/TransformDescription.h \
-           transform/TransformFactory.h \
-           transform/ModelTransformer.h \
-           transform/ModelTransformerFactory.h
-SOURCES += transform/CSVFeatureWriter.cpp \
-           transform/FeatureExtractionModelTransformer.cpp \
-           transform/FileFeatureWriter.cpp \
-           transform/RealTimeEffectModelTransformer.cpp \
-           transform/Transform.cpp \
-           transform/TransformFactory.cpp \
-           transform/ModelTransformer.cpp \
-           transform/ModelTransformerFactory.cpp
--- a/system/System.h	Thu Oct 20 11:16:22 2016 +0100
+++ b/system/System.h	Fri Oct 28 15:20:58 2016 +0100
@@ -64,12 +64,15 @@
 typedef SSIZE_T ssize_t;
 #endif
 
+#ifdef _MSC_VER
 extern "C" {
-
-#ifdef _MSC_VER
 void usleep(unsigned long usec);
+}
+#else
+#include <unistd.h>
 #endif
 
+extern "C" {
 int gettimeofday(struct timeval *p, void *tz);
 }
 
--- a/transform/FeatureExtractionModelTransformer.cpp	Thu Oct 20 11:16:22 2016 +0100
+++ b/transform/FeatureExtractionModelTransformer.cpp	Fri Oct 28 15:20:58 2016 +0100
@@ -16,6 +16,7 @@
 #include "FeatureExtractionModelTransformer.h"
 
 #include "plugin/FeatureExtractionPluginFactory.h"
+
 #include "plugin/PluginXml.h"
 #include <vamp-hostsdk/Plugin.h>
 
@@ -42,25 +43,23 @@
 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();
 }
 
 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;
     } else {
         SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s), first has plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl;
     }
-    
-    initialise();
 }
 
 static bool
@@ -74,6 +73,10 @@
 bool
 FeatureExtractionModelTransformer::initialise()
 {
+    // This is (now) called from the run thread. The plugin is
+    // constructed, initialised, used, and destroyed all from a single
+    // thread.
+    
     // All transforms must use the same plugin, parameters, and
     // inputs: they can differ only in choice of plugin output. So we
     // initialise based purely on the first transform in the list (but
@@ -91,7 +94,7 @@
     QString pluginId = primaryTransform.getPluginIdentifier();
 
     FeatureExtractionPluginFactory *factory =
-	FeatureExtractionPluginFactory::instanceFor(pluginId);
+        FeatureExtractionPluginFactory::instance();
 
     if (!factory) {
         m_message = tr("No factory available for feature extraction plugin id \"%1\" (unknown plugin type, or internal error?)").arg(pluginId);
@@ -104,6 +107,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,10 +226,27 @@
         createOutputModels(j);
     }
 
+    m_outputMutex.lock();
+    m_haveOutputs = true;
+    m_outputsCondition.wakeAll();
+    m_outputMutex.unlock();
+
     return true;
 }
 
 void
+FeatureExtractionModelTransformer::deinitialise()
+{
+    cerr << "deleting plugin for transform in thread "
+         << QThread::currentThreadId() << endl;
+    
+    delete m_plugin;
+    for (int j = 0; j < (int)m_descriptors.size(); ++j) {
+        delete m_descriptors[j];
+    }
+}
+
+void
 FeatureExtractionModelTransformer::createOutputModels(int n)
 {
     DenseTimeValueModel *input = getConformingInput();
@@ -479,13 +502,21 @@
     }
 }
 
+void
+FeatureExtractionModelTransformer::awaitOutputModels()
+{
+    m_outputMutex.lock();
+    while (!m_haveOutputs) {
+        m_outputsCondition.wait(&m_outputMutex);
+    }
+    m_outputMutex.unlock();
+}
+
 FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()
 {
-//    SVDEBUG << "FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()" << endl;
-    delete m_plugin;
-    for (int j = 0; j < (int)m_descriptors.size(); ++j) {
-        delete m_descriptors[j];
-    }
+    // Parent class dtor set the abandoned flag and waited for the run
+    // thread to exit; the run thread owns the plugin, and should have
+    // destroyed it before exiting (via a call to deinitialise)
 }
 
 FeatureExtractionModelTransformer::Models
@@ -566,6 +597,8 @@
 void
 FeatureExtractionModelTransformer::run()
 {
+    initialise();
+    
     DenseTimeValueModel *input = getConformingInput();
     if (!input) return;
 
@@ -758,6 +791,8 @@
         delete[] buffers[ch];
     }
     delete[] buffers;
+
+    deinitialise();
 }
 
 void
--- a/transform/FeatureExtractionModelTransformer.h	Thu Oct 20 11:16:22 2016 +0100
+++ b/transform/FeatureExtractionModelTransformer.h	Fri Oct 28 15:20:58 2016 +0100
@@ -19,6 +19,8 @@
 #include "ModelTransformer.h"
 
 #include <QString>
+#include <QMutex>
+#include <QWaitCondition>
 
 #include <vamp-hostsdk/Plugin.h>
 
@@ -28,7 +30,7 @@
 class DenseTimeValueModel;
 class SparseTimeValueModel;
 
-class FeatureExtractionModelTransformer : public ModelTransformer
+class FeatureExtractionModelTransformer : public ModelTransformer // + is a Thread
 {
     Q_OBJECT
 
@@ -50,6 +52,7 @@
 
 protected:
     bool initialise();
+    void deinitialise();
 
     virtual void run();
 
@@ -74,7 +77,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	Fri Oct 28 15:20:58 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/ModelTransformerFactory.cpp	Thu Oct 20 11:16:22 2016 +0100
+++ b/transform/ModelTransformerFactory.cpp	Fri Oct 28 15:20:58 2016 +0100
@@ -93,17 +93,7 @@
 
     Vamp::PluginBase *plugin = 0;
 
-    if (FeatureExtractionPluginFactory::instanceFor(id)) {
-
-        cerr << "getConfigurationForTransform: instantiating Vamp plugin" << endl;
-
-        Vamp::Plugin *vp =
-            FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
-            (id, float(inputModel->getSampleRate()));
-
-        plugin = vp;
-
-    } else if (RealTimePluginFactory::instanceFor(id)) {
+    if (RealTimePluginFactory::instanceFor(id)) {
 
         RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id);
 
@@ -120,6 +110,16 @@
             (id, 0, 0, sampleRate, blockSize, channels);
 
         plugin = rtp;
+
+    } else {
+
+        cerr << "getConfigurationForTransform: instantiating Vamp plugin" << endl;
+
+        Vamp::Plugin *vp =
+            FeatureExtractionPluginFactory::instance()->instantiatePlugin
+            (id, float(inputModel->getSampleRate()));
+
+        plugin = vp;
     }
 
     if (plugin) {
@@ -171,20 +171,15 @@
 
     QString id = transforms[0].getPluginIdentifier();
 
-    if (FeatureExtractionPluginFactory::instanceFor(id)) {
-
-        transformer =
-            new FeatureExtractionModelTransformer(input, transforms);
-
-    } else if (RealTimePluginFactory::instanceFor(id)) {
+    if (RealTimePluginFactory::instanceFor(id)) {
 
         transformer =
             new RealTimeEffectModelTransformer(input, transforms[0]);
 
     } else {
-        SVDEBUG << "ModelTransformerFactory::createTransformer: Unknown transform \""
-                  << transforms[0].getIdentifier() << "\"" << endl;
-        return transformer;
+
+        transformer =
+            new FeatureExtractionModelTransformer(input, transforms);
     }
 
     if (transformer) transformer->setObjectName(transforms[0].getIdentifier());
--- a/transform/RealTimeEffectModelTransformer.h	Thu Oct 20 11:16:22 2016 +0100
+++ b/transform/RealTimeEffectModelTransformer.h	Fri Oct 28 15:20:58 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;
--- a/transform/Transform.cpp	Thu Oct 20 11:16:22 2016 +0100
+++ b/transform/Transform.cpp	Fri Oct 28 15:20:58 2016 +0100
@@ -202,12 +202,10 @@
 Transform::Type
 Transform::getType() const
 {
-    if (FeatureExtractionPluginFactory::instanceFor(getPluginIdentifier())) {
-        return FeatureExtraction;
-    } else if (RealTimePluginFactory::instanceFor(getPluginIdentifier())) {
+    if (RealTimePluginFactory::instanceFor(getPluginIdentifier())) {
         return RealTimeEffect;
     } else {
-        return UnknownType;
+        return FeatureExtraction;
     }
 }
 
--- a/transform/TransformFactory.cpp	Thu Oct 20 11:16:22 2016 +0100
+++ b/transform/TransformFactory.cpp	Fri Oct 28 15:20:58 2016 +0100
@@ -16,6 +16,7 @@
 #include "TransformFactory.h"
 
 #include "plugin/FeatureExtractionPluginFactory.h"
+
 #include "plugin/RealTimePluginFactory.h"
 #include "plugin/RealTimePluginInstance.h"
 #include "plugin/PluginXml.h"
@@ -402,80 +403,77 @@
 void
 TransformFactory::populateFeatureExtractionPlugins(TransformDescriptionMap &transforms)
 {
-    std::vector<QString> plugs =
-	FeatureExtractionPluginFactory::getAllPluginIdentifiers();
+    FeatureExtractionPluginFactory *factory =
+        FeatureExtractionPluginFactory::instance();
+
+    QString errorMessage;
+    std::vector<QString> plugs = factory->getPluginIdentifiers(errorMessage);
+    if (errorMessage != "") {
+        m_errorString = tr("Failed to list Vamp plugins: %1").arg(errorMessage);
+    }
+    
     if (m_exiting) return;
 
     for (int i = 0; i < (int)plugs.size(); ++i) {
 
 	QString pluginId = plugs[i];
 
-	FeatureExtractionPluginFactory *factory =
-	    FeatureExtractionPluginFactory::instanceFor(pluginId);
+        piper_vamp::PluginStaticData psd = factory->getPluginStaticData(pluginId);
 
-	if (!factory) {
-	    cerr << "WARNING: TransformFactory::populateTransforms: No feature extraction plugin factory for instance " << pluginId << endl;
-	    continue;
-	}
+        if (psd.pluginKey == "") {
+            cerr << "WARNING: TransformFactory::populateTransforms: No plugin static data available for instance " << pluginId << endl;
+            continue;
+        }
 
-	Vamp::Plugin *plugin = 
-	    factory->instantiatePlugin(pluginId, 44100);
+        QString pluginName = QString::fromStdString(psd.basic.name);
+        QString category = factory->getPluginCategory(pluginId);
+        
+        const auto &basicOutputs = psd.basicOutputInfo;
 
-	if (!plugin) {
-	    cerr << "WARNING: TransformFactory::populateTransforms: Failed to instantiate plugin " << pluginId << endl;
-	    continue;
-	}
-		
-	QString pluginName = plugin->getName().c_str();
-        QString category = factory->getPluginCategory(pluginId);
+        for (const auto &o: basicOutputs) {
 
-	Vamp::Plugin::OutputList outputs =
-	    plugin->getOutputDescriptors();
-
-	for (int j = 0; j < (int)outputs.size(); ++j) {
+            QString outputName = QString::fromStdString(o.name);
 
 	    QString transformId = QString("%1:%2")
-		    .arg(pluginId).arg(outputs[j].identifier.c_str());
+                .arg(pluginId).arg(QString::fromStdString(o.identifier));
 
 	    QString userName;
             QString friendlyName;
-            QString units = outputs[j].unit.c_str();
-            QString description = plugin->getDescription().c_str();
-            QString maker = plugin->getMaker().c_str();
+//!!! return to this            QString units = outputs[j].unit.c_str();
+            QString description = QString::fromStdString(psd.basic.description);
+            QString maker = QString::fromStdString(psd.maker);
             if (maker == "") maker = tr("<unknown maker>");
 
             QString longDescription = description;
 
             if (longDescription == "") {
-                if (outputs.size() == 1) {
+                if (basicOutputs.size() == 1) {
                     longDescription = tr("Extract features using \"%1\" plugin (from %2)")
                         .arg(pluginName).arg(maker);
                 } else {
                     longDescription = tr("Extract features using \"%1\" output of \"%2\" plugin (from %3)")
-                        .arg(outputs[j].name.c_str()).arg(pluginName).arg(maker);
+                        .arg(outputName).arg(pluginName).arg(maker);
                 }
             } else {
-                if (outputs.size() == 1) {
+                if (basicOutputs.size() == 1) {
                     longDescription = tr("%1 using \"%2\" plugin (from %3)")
                         .arg(longDescription).arg(pluginName).arg(maker);
                 } else {
                     longDescription = tr("%1 using \"%2\" output of \"%3\" plugin (from %4)")
-                        .arg(longDescription).arg(outputs[j].name.c_str()).arg(pluginName).arg(maker);
+                        .arg(longDescription).arg(outputName).arg(pluginName).arg(maker);
                 }
             }                    
 
-	    if (outputs.size() == 1) {
+	    if (basicOutputs.size() == 1) {
 		userName = pluginName;
                 friendlyName = pluginName;
 	    } else {
-		userName = QString("%1: %2")
-		    .arg(pluginName)
-		    .arg(outputs[j].name.c_str());
-                friendlyName = outputs[j].name.c_str();
+		userName = QString("%1: %2").arg(pluginName).arg(outputName);
+                friendlyName = outputName;
 	    }
 
-            bool configurable = (!plugin->getPrograms().empty() ||
-                                 !plugin->getParameterDescriptors().empty());
+            bool configurable = (!psd.programs.empty() ||
+                                 !psd.parameters.empty());
 
 #ifdef DEBUG_TRANSFORM_FACTORY
             cerr << "Feature extraction plugin transform: " << transformId << " friendly name: " << friendlyName << endl;
@@ -490,11 +488,10 @@
                                      description,
                                      longDescription,
                                      maker,
-                                     units,
+//!!!                                     units,
+                                     "",
                                      configurable);
 	}
-
-        delete plugin;
     }
 }
 
@@ -797,8 +794,8 @@
 //        cerr << "TransformFactory::instantiateDefaultPluginFor: identifier \""
 //             << identifier << "\" is a feature extraction transform" << endl;
         
-        FeatureExtractionPluginFactory *factory = 
-            FeatureExtractionPluginFactory::instanceFor(pluginId);
+        FeatureExtractionPluginFactory *factory =
+            FeatureExtractionPluginFactory::instance();
 
         if (factory) {
             plugin = factory->instantiatePlugin(pluginId, rate);
@@ -917,22 +914,7 @@
 {
     QString id = identifier.section(':', 0, 2);
 
-    if (FeatureExtractionPluginFactory::instanceFor(id)) {
-
-        Vamp::Plugin *plugin = 
-            FeatureExtractionPluginFactory::instanceFor(id)->
-            instantiatePlugin(id, 44100);
-        if (!plugin) return false;
-
-        min = (int)plugin->getMinChannelCount();
-        max = (int)plugin->getMaxChannelCount();
-        delete plugin;
-
-        return true;
-
-    } else if (RealTimePluginFactory::instanceFor(id)) {
-
-        // don't need to instantiate
+    if (RealTimePluginFactory::instanceFor(id)) {
 
         const RealTimePluginDescriptor *descriptor = 
             RealTimePluginFactory::instanceFor(id)->
@@ -943,6 +925,17 @@
         max = descriptor->audioInputPortCount;
 
         return true;
+
+    } else {
+
+        auto psd = FeatureExtractionPluginFactory::instance()->
+            getPluginStaticData(id);
+        if (psd.pluginKey == "") return false;
+
+        min = (int)psd.minChannelCount;
+        max = (int)psd.maxChannelCount;
+
+        return true;
     }
 
     return false;
--- a/transform/TransformFactory.h	Thu Oct 20 11:16:22 2016 +0100
+++ b/transform/TransformFactory.h	Fri Oct 28 15:20:58 2016 +0100
@@ -196,6 +196,10 @@
     void setParametersFromPluginConfigurationXml(Transform &transform,
                                                  QString xml);
     
+    QString getStartupFailureReport() const {
+        return m_errorString;
+    }
+    
 protected:
     typedef std::map<TransformId, TransformDescription> TransformDescriptionMap;
 
@@ -205,6 +209,8 @@
     TransformDescriptionMap m_uninstalledTransforms;
     bool m_uninstalledTransformsPopulated;
 
+    QString m_errorString;
+    
     void populateTransforms();
     void populateUninstalledTransforms();
     void populateFeatureExtractionPlugins(TransformDescriptionMap &);