changeset 90:6429a99abcad

Split out classes
author Chris Cannam <c.cannam@qmul.ac.uk>
date Thu, 13 Oct 2016 10:17:59 +0100
parents 03ed2e0a6c8f
children c897c9a8daf1
files vamp-capnp/VampnProto.h vamp-client/CapnpMessageCompletenessChecker.h vamp-client/Makefile vamp-client/PipedQProcessTransport.h vamp-client/PiperCapnpClient.h vamp-client/PiperClient.h vamp-client/PiperStubPlugin.h vamp-client/SynchronousTransport.h vamp-client/client.cpp vamp-client/client.pro vamp-client/stub.h
diffstat 11 files changed, 706 insertions(+), 588 deletions(-) [+]
line wrap: on
line diff
--- a/vamp-capnp/VampnProto.h	Wed Oct 12 21:34:21 2016 +0100
+++ b/vamp-capnp/VampnProto.h	Thu Oct 13 10:17:59 2016 +0100
@@ -35,7 +35,7 @@
 #include "piper.capnp.h"
 
 #include <capnp/message.h>
-#include <capnp/serialize-packed.h>
+//#include <capnp/serialize-packed.h>
 
 #include <vamp-hostsdk/Plugin.h>
 #include <vamp-hostsdk/PluginLoader.h>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-client/CapnpMessageCompletenessChecker.h	Thu Oct 13 10:17:59 2016 +0100
@@ -0,0 +1,38 @@
+
+#ifndef CAPNP_MESSAGE_COMPLETENESS_CHECKER_H
+#define CAPNP_MESSAGE_COMPLETENESS_CHECKER_H
+
+#include "SynchronousTransport.h" //!!!
+
+#include <capnp/serialize.h>
+
+#include <iostream>
+
+namespace piper { //!!! change
+
+class CapnpMessageCompletenessChecker : public MessageCompletenessChecker
+{
+public:
+    bool isComplete(const std::vector<char> &message) const override {
+
+        // a bit liberal with the copies here
+        size_t wordSize = sizeof(capnp::word);
+	size_t words = message.size() / wordSize;
+	kj::Array<capnp::word> karr(kj::heapArray<capnp::word>(words));
+	memcpy(karr.begin(), message.data(), words * wordSize);
+
+        size_t expected = capnp::expectedSizeInWordsFromPrefix(karr);
+
+        if (words > expected) {
+            std::cerr << "WARNING: obtained more data than expected ("
+                      << words << " " << wordSize << "-byte words, expected "
+                      << expected << ")" << std::endl;
+        }
+        
+        return words >= expected;
+    }
+};
+
+}
+
+#endif
--- a/vamp-client/Makefile	Wed Oct 12 21:34:21 2016 +0100
+++ b/vamp-client/Makefile	Thu Oct 13 10:17:59 2016 +0100
@@ -3,7 +3,7 @@
 # Generated by qmake (3.0) (Qt 5.7.0)
 # Project:  client.pro
 # Template: app
-# Command: /usr/lib/qt/bin/qmake -o Makefile client.pro
+# Command: /usr/bin/qmake -o Makefile client.pro
 #############################################################################
 
 MAKEFILE      = Makefile
@@ -16,7 +16,7 @@
 CFLAGS        = -pipe -O2 -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector-strong -Wall -W -D_REENTRANT -fPIC $(DEFINES)
 CXXFLAGS      = -I../../vamp-plugin-sdk -I.. -O2 -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector-strong -std=gnu++11 -Wall -W -D_REENTRANT -fPIC $(DEFINES)
 INCPATH       = -I. -isystem /usr/include/qt -isystem /usr/include/qt/QtCore -I../o -I/usr/lib/qt/mkspecs/linux-g++
-QMAKE         = /usr/lib/qt/bin/qmake
+QMAKE         = /usr/bin/qmake
 DEL_FILE      = rm -f
 CHK_DIR_EXISTS= test -d
 MKDIR         = mkdir -p
@@ -76,9 +76,6 @@
 		/usr/lib/qt/mkspecs/modules/qt_KCrash.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KDBusAddons.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KDeclarative.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KDEWebKit.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KDNSSD.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KEmoticons.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KGlobalAccel.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KGuiAddons.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KI18n.pri \
@@ -87,22 +84,18 @@
 		/usr/lib/qt/mkspecs/modules/qt_KIOFileWidgets.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KIOGui.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KIOWidgets.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KItemModels.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KItemViews.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KJobWidgets.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KNewStuff.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KNotifications.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KNotifyConfig.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KNTLM.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KParts.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KPlotting.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KService.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KTextWidgets.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KUnitConversion.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KWallet.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KWidgetsAddons.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KWindowSystem.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KXmlGui.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_bluetooth.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_bluetooth_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_bootstrap_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_clucene_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_concurrent.pri \
@@ -130,6 +123,8 @@
 		/usr/lib/qt/mkspecs/modules/qt_lib_multimediawidgets_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_network.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_network_private.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_nfc.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_nfc_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_opengl.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_opengl_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_openglextensions.pri \
@@ -149,10 +144,7 @@
 		/usr/lib/qt/mkspecs/modules/qt_lib_qtmultimediaquicktools_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_quick.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_quick_private.pri \
-		/usr/lib/qt/mkspecs/modules/qt_lib_quickcontrols2.pri \
-		/usr/lib/qt/mkspecs/modules/qt_lib_quickcontrols2_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_quickparticles_private.pri \
-		/usr/lib/qt/mkspecs/modules/qt_lib_quicktemplates2_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_quickwidgets.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_quickwidgets_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_script.pri \
@@ -161,6 +153,8 @@
 		/usr/lib/qt/mkspecs/modules/qt_lib_scripttools_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_sensors.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_sensors_private.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_serialport.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_serialport_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_sql.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_sql_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_svg.pri \
@@ -196,6 +190,7 @@
 		/usr/lib/qt/mkspecs/features/qt_config.prf \
 		/usr/lib/qt/mkspecs/linux-g++/qmake.conf \
 		/usr/lib/qt/mkspecs/features/spec_post.prf \
+		.qmake.stash \
 		/usr/lib/qt/mkspecs/features/exclusive_builds.prf \
 		/usr/lib/qt/mkspecs/features/default_pre.prf \
 		/usr/lib/qt/mkspecs/features/resolve_config.prf \
@@ -210,7 +205,12 @@
 		/usr/lib/qt/mkspecs/features/testcase_targets.prf \
 		/usr/lib/qt/mkspecs/features/yacc.prf \
 		/usr/lib/qt/mkspecs/features/lex.prf \
-		client.pro  client.cpp \
+		client.pro CapnpMessageCompletenessChecker.h \
+		PipedQProcessTransport.h \
+		PiperCapnpClient.h \
+		PiperClient.h \
+		PiperStubPlugin.h \
+		SynchronousTransport.h client.cpp \
 		../vamp-capnp/piper.capnp.c++
 QMAKE_TARGET  = client
 DESTDIR       = 
@@ -247,9 +247,6 @@
 		/usr/lib/qt/mkspecs/modules/qt_KCrash.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KDBusAddons.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KDeclarative.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KDEWebKit.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KDNSSD.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KEmoticons.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KGlobalAccel.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KGuiAddons.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KI18n.pri \
@@ -258,22 +255,18 @@
 		/usr/lib/qt/mkspecs/modules/qt_KIOFileWidgets.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KIOGui.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KIOWidgets.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KItemModels.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KItemViews.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KJobWidgets.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KNewStuff.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KNotifications.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KNotifyConfig.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KNTLM.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KParts.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KPlotting.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KService.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KTextWidgets.pri \
-		/usr/lib/qt/mkspecs/modules/qt_KUnitConversion.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KWallet.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KWidgetsAddons.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KWindowSystem.pri \
 		/usr/lib/qt/mkspecs/modules/qt_KXmlGui.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_bluetooth.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_bluetooth_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_bootstrap_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_clucene_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_concurrent.pri \
@@ -301,6 +294,8 @@
 		/usr/lib/qt/mkspecs/modules/qt_lib_multimediawidgets_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_network.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_network_private.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_nfc.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_nfc_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_opengl.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_opengl_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_openglextensions.pri \
@@ -320,10 +315,7 @@
 		/usr/lib/qt/mkspecs/modules/qt_lib_qtmultimediaquicktools_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_quick.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_quick_private.pri \
-		/usr/lib/qt/mkspecs/modules/qt_lib_quickcontrols2.pri \
-		/usr/lib/qt/mkspecs/modules/qt_lib_quickcontrols2_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_quickparticles_private.pri \
-		/usr/lib/qt/mkspecs/modules/qt_lib_quicktemplates2_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_quickwidgets.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_quickwidgets_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_script.pri \
@@ -332,6 +324,8 @@
 		/usr/lib/qt/mkspecs/modules/qt_lib_scripttools_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_sensors.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_sensors_private.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_serialport.pri \
+		/usr/lib/qt/mkspecs/modules/qt_lib_serialport_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_sql.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_sql_private.pri \
 		/usr/lib/qt/mkspecs/modules/qt_lib_svg.pri \
@@ -367,6 +361,7 @@
 		/usr/lib/qt/mkspecs/features/qt_config.prf \
 		/usr/lib/qt/mkspecs/linux-g++/qmake.conf \
 		/usr/lib/qt/mkspecs/features/spec_post.prf \
+		.qmake.stash \
 		/usr/lib/qt/mkspecs/features/exclusive_builds.prf \
 		/usr/lib/qt/mkspecs/features/default_pre.prf \
 		/usr/lib/qt/mkspecs/features/resolve_config.prf \
@@ -408,9 +403,6 @@
 /usr/lib/qt/mkspecs/modules/qt_KCrash.pri:
 /usr/lib/qt/mkspecs/modules/qt_KDBusAddons.pri:
 /usr/lib/qt/mkspecs/modules/qt_KDeclarative.pri:
-/usr/lib/qt/mkspecs/modules/qt_KDEWebKit.pri:
-/usr/lib/qt/mkspecs/modules/qt_KDNSSD.pri:
-/usr/lib/qt/mkspecs/modules/qt_KEmoticons.pri:
 /usr/lib/qt/mkspecs/modules/qt_KGlobalAccel.pri:
 /usr/lib/qt/mkspecs/modules/qt_KGuiAddons.pri:
 /usr/lib/qt/mkspecs/modules/qt_KI18n.pri:
@@ -419,22 +411,18 @@
 /usr/lib/qt/mkspecs/modules/qt_KIOFileWidgets.pri:
 /usr/lib/qt/mkspecs/modules/qt_KIOGui.pri:
 /usr/lib/qt/mkspecs/modules/qt_KIOWidgets.pri:
-/usr/lib/qt/mkspecs/modules/qt_KItemModels.pri:
 /usr/lib/qt/mkspecs/modules/qt_KItemViews.pri:
 /usr/lib/qt/mkspecs/modules/qt_KJobWidgets.pri:
-/usr/lib/qt/mkspecs/modules/qt_KNewStuff.pri:
 /usr/lib/qt/mkspecs/modules/qt_KNotifications.pri:
-/usr/lib/qt/mkspecs/modules/qt_KNotifyConfig.pri:
 /usr/lib/qt/mkspecs/modules/qt_KNTLM.pri:
-/usr/lib/qt/mkspecs/modules/qt_KParts.pri:
-/usr/lib/qt/mkspecs/modules/qt_KPlotting.pri:
 /usr/lib/qt/mkspecs/modules/qt_KService.pri:
 /usr/lib/qt/mkspecs/modules/qt_KTextWidgets.pri:
-/usr/lib/qt/mkspecs/modules/qt_KUnitConversion.pri:
 /usr/lib/qt/mkspecs/modules/qt_KWallet.pri:
 /usr/lib/qt/mkspecs/modules/qt_KWidgetsAddons.pri:
 /usr/lib/qt/mkspecs/modules/qt_KWindowSystem.pri:
 /usr/lib/qt/mkspecs/modules/qt_KXmlGui.pri:
+/usr/lib/qt/mkspecs/modules/qt_lib_bluetooth.pri:
+/usr/lib/qt/mkspecs/modules/qt_lib_bluetooth_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_bootstrap_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_clucene_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_concurrent.pri:
@@ -462,6 +450,8 @@
 /usr/lib/qt/mkspecs/modules/qt_lib_multimediawidgets_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_network.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_network_private.pri:
+/usr/lib/qt/mkspecs/modules/qt_lib_nfc.pri:
+/usr/lib/qt/mkspecs/modules/qt_lib_nfc_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_opengl.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_opengl_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_openglextensions.pri:
@@ -481,10 +471,7 @@
 /usr/lib/qt/mkspecs/modules/qt_lib_qtmultimediaquicktools_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_quick.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_quick_private.pri:
-/usr/lib/qt/mkspecs/modules/qt_lib_quickcontrols2.pri:
-/usr/lib/qt/mkspecs/modules/qt_lib_quickcontrols2_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_quickparticles_private.pri:
-/usr/lib/qt/mkspecs/modules/qt_lib_quicktemplates2_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_quickwidgets.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_quickwidgets_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_script.pri:
@@ -493,6 +480,8 @@
 /usr/lib/qt/mkspecs/modules/qt_lib_scripttools_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_sensors.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_sensors_private.pri:
+/usr/lib/qt/mkspecs/modules/qt_lib_serialport.pri:
+/usr/lib/qt/mkspecs/modules/qt_lib_serialport_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_sql.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_sql_private.pri:
 /usr/lib/qt/mkspecs/modules/qt_lib_svg.pri:
@@ -528,6 +517,7 @@
 /usr/lib/qt/mkspecs/features/qt_config.prf:
 /usr/lib/qt/mkspecs/linux-g++/qmake.conf:
 /usr/lib/qt/mkspecs/features/spec_post.prf:
+.qmake.stash:
 /usr/lib/qt/mkspecs/features/exclusive_builds.prf:
 /usr/lib/qt/mkspecs/features/default_pre.prf:
 /usr/lib/qt/mkspecs/features/resolve_config.prf:
@@ -558,6 +548,7 @@
 distdir: FORCE
 	@test -d $(DISTDIR) || mkdir -p $(DISTDIR)
 	$(COPY_FILE) --parents $(DIST) $(DISTDIR)/
+	$(COPY_FILE) --parents CapnpMessageCompletenessChecker.h PipedQProcessTransport.h PiperCapnpClient.h PiperClient.h PiperStubPlugin.h SynchronousTransport.h $(DISTDIR)/
 	$(COPY_FILE) --parents client.cpp ../vamp-capnp/piper.capnp.c++ $(DISTDIR)/
 
 
@@ -598,7 +589,11 @@
 
 ####### Compile
 
-../o/client.o: client.cpp stub.h
+../o/client.o: client.cpp PiperClient.h \
+		PiperStubPlugin.h \
+		CapnpMessageCompletenessChecker.h \
+		SynchronousTransport.h \
+		PipedQProcessTransport.h
 	$(CXX) -c $(CXXFLAGS) $(INCPATH) -o ../o/client.o client.cpp
 
 ../o/piper.capnp.o: ../vamp-capnp/piper.capnp.c++ ../vamp-capnp/piper.capnp.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-client/PipedQProcessTransport.h	Thu Oct 13 10:17:59 2016 +0100
@@ -0,0 +1,95 @@
+
+#ifndef PIPER_PIPED_QPROCESS_TRANSPORT_H
+#define PIPER_PIPED_QPROCESS_TRANSPORT_H
+
+#include "SynchronousTransport.h"
+
+#include <QProcess>
+#include <QString>
+
+#include <iostream>
+
+namespace piper { //!!! change
+
+class PipedQProcessTransport : public SynchronousTransport
+{
+public:
+    PipedQProcessTransport(QString processName,
+                           MessageCompletenessChecker *checker) : //!!! ownership
+        m_completenessChecker(checker) {
+        m_process = new QProcess();
+        m_process->setReadChannel(QProcess::StandardOutput);
+        m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel);
+        m_process->start(processName);
+        if (!m_process->waitForStarted()) {
+            std::cerr << "server failed to start" << std::endl;
+            delete m_process;
+            m_process = nullptr;
+        }
+    }
+
+    ~PipedQProcessTransport() {
+        if (m_process) {
+            if (m_process->state() != QProcess::NotRunning) {
+		m_process->closeWriteChannel();
+                m_process->waitForFinished(200);
+                m_process->close();
+                m_process->waitForFinished();
+                std::cerr << "server exited" << std::endl;
+            }
+            delete m_process;
+        }
+    }
+
+    bool isOK() const override {
+        return m_process != nullptr;
+    }
+    
+    std::vector<char>
+    call(const char *ptr, size_t size) override {
+
+        m_process->write(ptr, size);
+        
+        std::vector<char> buffer;
+        size_t wordSize = sizeof(capnp::word);
+        bool complete = false;
+        
+        while (!complete) {
+
+            m_process->waitForReadyRead(1000);
+            qint64 byteCount = m_process->bytesAvailable();
+            qint64 wordCount = byteCount / wordSize;
+
+            if (!wordCount) {
+                if (m_process->state() == QProcess::NotRunning) {
+                    std::cerr << "ERROR: Subprocess exited: Load failed" << std::endl;
+                    throw std::runtime_error("Piper server exited unexpectedly");
+                }
+            } else {
+                // only read whole words
+                byteCount = wordCount * wordSize;
+                size_t formerSize = buffer.size();
+                buffer.resize(formerSize + byteCount);
+                m_process->read(buffer.data() + formerSize, byteCount);
+                complete = m_completenessChecker->isComplete(buffer);
+            }
+        }
+/*
+        cerr << "buffer = ";
+        for (int i = 0; i < buffer.size(); ++i) {
+            if (i % 16 == 0) cerr << "\n";
+            cerr << int(buffer[i]) << " ";
+        }
+        cerr << "\n";
+*/        
+        return buffer;
+    }
+    
+private:
+    MessageCompletenessChecker *m_completenessChecker; //!!! I don't own this (currently)
+    QProcess *m_process; // I own this
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-client/PiperCapnpClient.h	Thu Oct 13 10:17:59 2016 +0100
@@ -0,0 +1,244 @@
+
+#ifndef PIPER_CAPNP_CLIENT_H
+#define PIPER_CAPNP_CLIENT_H
+
+#include "PiperClient.h"
+#include "SynchronousTransport.h"
+
+#include "vamp-support/AssignedPluginHandleMapper.h"
+#include "vamp-capnp/VampnProto.h"
+
+namespace piper { //!!! change
+
+class PiperCapnpClient : public PiperStubPluginClientInterface
+{
+    // unsigned to avoid undefined behaviour on possible wrap
+    typedef uint32_t ReqId;
+    
+public:
+    PiperCapnpClient(SynchronousTransport *transport) : //!!! ownership? shared ptr?
+        m_transport(transport) {
+    }
+
+    ~PiperCapnpClient() {
+    }
+
+    //!!! obviously, factor out all repetitive guff
+
+    //!!! list and load are supposed to be called by application code,
+    //!!! but the rest are only supposed to be called by the plugin --
+    //!!! sort out the api here
+    
+    Vamp::Plugin *
+    load(std::string key, float inputSampleRate, int adapterFlags) {
+
+        if (!m_transport->isOK()) {
+            throw std::runtime_error("Piper server failed to start");
+        }
+
+        Vamp::HostExt::LoadRequest request;
+        request.pluginKey = key;
+        request.inputSampleRate = inputSampleRate;
+        request.adapterFlags = adapterFlags;
+
+        capnp::MallocMessageBuilder message;
+        RpcRequest::Builder builder = message.initRoot<RpcRequest>();
+
+        VampnProto::buildRpcRequest_Load(builder, request);
+        ReqId id = getId();
+        builder.getId().setNumber(id);
+
+        auto arr = messageToFlatArray(message);
+
+        auto responseBuffer = m_transport->call(arr.asChars().begin(),
+                                                arr.asChars().size());
+        
+        //!!! ... --> will also need some way to kill this process
+        //!!! (from another thread)
+
+	auto karr = toKJArray(responseBuffer);
+        capnp::FlatArrayMessageReader responseMessage(karr);
+        RpcResponse::Reader reader = responseMessage.getRoot<RpcResponse>();
+
+        //!!! handle (explicit) error case
+
+        checkResponseType(reader, RpcResponse::Response::Which::LOAD, id);
+        
+        const LoadResponse::Reader &lr = reader.getResponse().getLoad();
+
+        Vamp::HostExt::PluginStaticData psd;
+        Vamp::HostExt::PluginConfiguration defaultConfig;
+        VampnProto::readExtractorStaticData(psd, lr.getStaticData());
+        VampnProto::readConfiguration(defaultConfig, lr.getDefaultConfiguration());
+        
+        Vamp::Plugin *plugin = new PiperStubPlugin(this,
+                                                   inputSampleRate,
+                                                   psd,
+                                                   defaultConfig);
+
+        m_mapper.addPlugin(lr.getHandle(), plugin);
+
+        return plugin;
+    };     
+
+protected:
+    virtual
+    Vamp::Plugin::OutputList
+    configure(PiperStubPlugin *plugin,
+              Vamp::HostExt::PluginConfiguration config) override {
+
+        if (!m_transport->isOK()) {
+            throw std::runtime_error("Piper server failed to start");
+        }
+
+        Vamp::HostExt::ConfigurationRequest request;
+        request.plugin = plugin;
+        request.configuration = config;
+
+        capnp::MallocMessageBuilder message;
+        RpcRequest::Builder builder = message.initRoot<RpcRequest>();
+
+        VampnProto::buildRpcRequest_Configure(builder, request, m_mapper);
+        ReqId id = getId();
+        builder.getId().setNumber(id);
+        
+        auto arr = messageToFlatArray(message);
+        auto responseBuffer = m_transport->call(arr.asChars().begin(),
+                                                arr.asChars().size());
+	auto karr = toKJArray(responseBuffer);
+        capnp::FlatArrayMessageReader responseMessage(karr);
+        RpcResponse::Reader reader = responseMessage.getRoot<RpcResponse>();
+
+        //!!! handle (explicit) error case
+
+        checkResponseType(reader, RpcResponse::Response::Which::CONFIGURE, id);
+
+        Vamp::HostExt::ConfigurationResponse cr;
+        VampnProto::readConfigurationResponse(cr,
+                                              reader.getResponse().getConfigure(),
+                                              m_mapper);
+
+        return cr.outputs;
+    };
+    
+    virtual
+    Vamp::Plugin::FeatureSet
+    process(PiperStubPlugin *plugin,
+            std::vector<std::vector<float> > inputBuffers,
+            Vamp::RealTime timestamp) override {
+
+        if (!m_transport->isOK()) {
+            throw std::runtime_error("Piper server failed to start");
+        }
+
+        Vamp::HostExt::ProcessRequest request;
+        request.plugin = plugin;
+        request.inputBuffers = inputBuffers;
+        request.timestamp = timestamp;
+        
+        capnp::MallocMessageBuilder message;
+        RpcRequest::Builder builder = message.initRoot<RpcRequest>();
+
+        VampnProto::buildRpcRequest_Process(builder, request, m_mapper);
+        ReqId id = getId();
+        builder.getId().setNumber(id);
+        
+        auto arr = messageToFlatArray(message);
+        auto responseBuffer = m_transport->call(arr.asChars().begin(),
+                                                arr.asChars().size());
+	auto karr = toKJArray(responseBuffer);
+        capnp::FlatArrayMessageReader responseMessage(karr);
+        RpcResponse::Reader reader = responseMessage.getRoot<RpcResponse>();
+
+        //!!! handle (explicit) error case
+
+        checkResponseType(reader, RpcResponse::Response::Which::PROCESS, id);
+
+        Vamp::HostExt::ProcessResponse pr;
+        VampnProto::readProcessResponse(pr,
+                                        reader.getResponse().getProcess(),
+                                        m_mapper);
+
+        return pr.features;
+    }
+
+    virtual Vamp::Plugin::FeatureSet
+    finish(PiperStubPlugin *plugin) override {
+
+        if (!m_transport->isOK()) {
+            throw std::runtime_error("Piper server failed to start");
+        }
+
+        Vamp::HostExt::FinishRequest request;
+        request.plugin = plugin;
+        
+        capnp::MallocMessageBuilder message;
+        RpcRequest::Builder builder = message.initRoot<RpcRequest>();
+
+        VampnProto::buildRpcRequest_Finish(builder, request, m_mapper);
+        ReqId id = getId();
+        builder.getId().setNumber(id);
+        
+        auto arr = messageToFlatArray(message);
+        auto responseBuffer = m_transport->call(arr.asChars().begin(),
+                                                arr.asChars().size());
+	auto karr = toKJArray(responseBuffer);
+        capnp::FlatArrayMessageReader responseMessage(karr);
+        RpcResponse::Reader reader = responseMessage.getRoot<RpcResponse>();
+
+        //!!! handle (explicit) error case
+
+        checkResponseType(reader, RpcResponse::Response::Which::FINISH, id);
+
+        Vamp::HostExt::ProcessResponse pr;
+        VampnProto::readFinishResponse(pr,
+                                       reader.getResponse().getFinish(),
+                                       m_mapper);
+
+        m_mapper.removePlugin(m_mapper.pluginToHandle(plugin));
+
+	// Don't delete the plugin. It's the plugin that is supposed
+	// to be calling us here
+        
+        return pr.features;
+    }
+
+private:
+    AssignedPluginHandleMapper m_mapper;
+    ReqId getId() {
+        //!!! todo: mutex
+        static ReqId m_nextId = 0;
+        return m_nextId++;
+    }
+
+    kj::Array<capnp::word>
+    toKJArray(const std::vector<char> &buffer) {
+	// We could do this whole thing with fewer copies, but let's
+	// see whether it matters first
+        size_t wordSize = sizeof(capnp::word);
+	size_t words = buffer.size() / wordSize;
+	kj::Array<capnp::word> karr(kj::heapArray<capnp::word>(words));
+	memcpy(karr.begin(), buffer.data(), words * wordSize);
+	return karr;
+    }
+
+    void
+    checkResponseType(const RpcResponse::Reader &r,
+                      RpcResponse::Response::Which type,
+                      ReqId id) {
+        
+        if (r.getResponse().which() != type) {
+            throw std::runtime_error("Wrong response type");
+        }
+        if (ReqId(r.getId().getNumber()) != id) {
+            throw std::runtime_error("Wrong response id");
+        }
+    }
+
+private:
+    SynchronousTransport *m_transport; //!!! I don't own this, but should I?
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-client/PiperClient.h	Thu Oct 13 10:17:59 2016 +0100
@@ -0,0 +1,33 @@
+
+#ifndef PIPER_CLIENT_H
+#define PIPER_CLIENT_H
+
+#include <vamp-hostsdk/PluginConfiguration.h>
+
+namespace piper { //!!! change
+
+class PiperStubPlugin;
+
+class PiperStubPluginClientInterface
+{
+    friend class PiperStubPlugin;
+    
+protected:
+    virtual
+    Vamp::Plugin::OutputList
+    configure(PiperStubPlugin *plugin,
+              Vamp::HostExt::PluginConfiguration config) = 0;
+    
+    virtual
+    Vamp::Plugin::FeatureSet
+    process(PiperStubPlugin *plugin,
+            std::vector<std::vector<float> > inputBuffers,
+            Vamp::RealTime timestamp) = 0;
+
+    virtual Vamp::Plugin::FeatureSet
+    finish(PiperStubPlugin *plugin) = 0;
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-client/PiperStubPlugin.h	Thu Oct 13 10:17:59 2016 +0100
@@ -0,0 +1,213 @@
+
+#ifndef PIPER_STUB_PLUGIN_H
+#define PIPER_STUB_PLUGIN_H
+
+#include <vamp-hostsdk/Plugin.h>
+#include <vamp-hostsdk/PluginLoader.h>
+#include <vamp-hostsdk/PluginStaticData.h>
+#include <vamp-hostsdk/PluginConfiguration.h>
+
+#include <cstdint>
+
+#include "PiperClient.h"
+
+namespace piper { //!!! should be something else
+
+class PiperStubPlugin : public Vamp::Plugin
+{
+    enum State {
+        Loaded, Configured, Finished
+    };
+    
+public:
+    PiperStubPlugin(PiperStubPluginClientInterface *client,
+                    float inputSampleRate,
+                    Vamp::HostExt::PluginStaticData psd,
+                    Vamp::HostExt::PluginConfiguration defaultConfig) :
+        Plugin(inputSampleRate),
+        m_client(client),
+        m_state(Loaded),
+        m_psd(psd),
+        m_defaultConfig(defaultConfig),
+        m_config(defaultConfig)
+    { }
+
+    virtual ~PiperStubPlugin() {
+        if (m_state != Finished) {
+	    (void)m_client->finish(this);
+        }
+    }
+
+    virtual std::string getIdentifier() const {
+        return m_psd.basic.identifier;
+    }
+
+    virtual std::string getName() const {
+        return m_psd.basic.name;
+    }
+
+    virtual std::string getDescription() const {
+        return m_psd.basic.description;
+    }
+
+    virtual std::string getMaker() const {
+        return m_psd.maker;
+    }
+
+    virtual std::string getCopyright() const {
+        return m_psd.copyright;
+    }
+
+    virtual int getPluginVersion() const {
+        return m_psd.pluginVersion;
+    }
+
+    virtual ParameterList getParameterDescriptors() const {
+        return m_psd.parameters;
+    }
+
+    virtual float getParameter(std::string name) const {
+        if (m_config.parameterValues.find(name) != m_config.parameterValues.end()) {
+            return m_config.parameterValues.at(name);
+        } else {
+            return 0.f;
+        }
+    }
+
+    virtual void setParameter(std::string name, float value) {
+        if (m_state != Loaded) {
+            throw std::logic_error("Can't set parameter after plugin initialised");
+        }
+        m_config.parameterValues[name] = value;
+    }
+
+    virtual ProgramList getPrograms() const {
+        return m_psd.programs;
+    }
+
+    virtual std::string getCurrentProgram() const {
+        return m_config.currentProgram;
+    }
+    
+    virtual void selectProgram(std::string program) {
+        if (m_state != Loaded) {
+            throw std::logic_error("Can't select program after plugin initialised");
+        }
+        m_config.currentProgram = program;
+    }
+
+    virtual bool initialise(size_t inputChannels,
+                            size_t stepSize,
+                            size_t blockSize) {
+
+        if (m_state != Loaded) {
+            throw std::logic_error("Plugin has already been initialised");
+        }
+        
+        m_config.channelCount = inputChannels;
+        m_config.stepSize = stepSize;
+        m_config.blockSize = blockSize;
+
+        m_outputs = m_client->configure(this, m_config);
+
+        if (!m_outputs.empty()) {
+            m_state = Configured;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    virtual void reset() {
+        //!!! hm, how to deal with this? there is no reset() in Piper!
+        throw "Please do not call this function again.";
+    }
+
+    virtual InputDomain getInputDomain() const {
+        return m_psd.inputDomain;
+    }
+
+    virtual size_t getPreferredBlockSize() const {
+        return m_defaultConfig.blockSize;
+    }
+
+    virtual size_t getPreferredStepSize() const {
+        return m_defaultConfig.stepSize;
+    }
+
+    virtual size_t getMinChannelCount() const {
+        return m_psd.minChannelCount;
+    }
+
+    virtual size_t getMaxChannelCount() const {
+        return m_psd.maxChannelCount;
+    }
+
+    virtual OutputList getOutputDescriptors() const {
+        if (m_state == Configured) {
+            return m_outputs;
+        }
+
+        //!!! todo: figure out for which hosts (and adapters?) it may
+        //!!! be a problem that the output descriptors are incomplete
+        //!!! here. Any such hosts/adapters are broken, but I bet they
+        //!!! exist
+        
+        OutputList staticOutputs;
+        for (const auto &o: m_psd.basicOutputInfo) {
+            OutputDescriptor od;
+            od.identifier = o.identifier;
+            od.name = o.name;
+            od.description = o.description;
+            staticOutputs.push_back(od);
+        }
+        return staticOutputs;
+    }
+
+    virtual FeatureSet process(const float *const *inputBuffers,
+			       Vamp::RealTime timestamp) {
+
+        if (m_state == Loaded) {
+            throw std::logic_error("Plugin has not been initialised");
+        }
+        if (m_state == Finished) {
+            throw std::logic_error("Plugin has already been disposed of");
+        }
+
+        //!!! ew
+        std::vector<std::vector<float> > vecbuf;
+        for (int c = 0; c < m_config.channelCount; ++c) {
+            vecbuf.push_back(std::vector<float>
+                             (inputBuffers[c],
+                              inputBuffers[c] + m_config.blockSize));
+        }
+        
+        return m_client->process(this, vecbuf, timestamp);
+    }
+
+    virtual FeatureSet getRemainingFeatures() {
+
+        if (m_state == Loaded) {
+            throw std::logic_error("Plugin has not been configured");
+        }
+        if (m_state == Finished) {
+            throw std::logic_error("Plugin has already been disposed of");
+        }
+
+        m_state = Finished;
+
+        return m_client->finish(this);
+    }
+    
+private:
+    PiperStubPluginClientInterface *m_client;
+    State m_state;
+    Vamp::HostExt::PluginStaticData m_psd;
+    OutputList m_outputs;
+    Vamp::HostExt::PluginConfiguration m_defaultConfig;
+    Vamp::HostExt::PluginConfiguration m_config;
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-client/SynchronousTransport.h	Thu Oct 13 10:17:59 2016 +0100
@@ -0,0 +1,28 @@
+
+#ifndef PIPER_SYNCHRONOUS_TRANSPORT_H
+#define PIPER_SYNCHRONOUS_TRANSPORT_H
+
+#include <vector>
+
+namespace piper {
+
+class SynchronousTransport // interface
+{
+public:
+    //!!! how to handle errors -- exception or return value? often an
+    //!!! error (e.g. server has exited) may mean the transport can no
+    //!!! longer be used at all
+    virtual std::vector<char> call(const char *data, size_t bytes) = 0;
+
+    virtual bool isOK() const = 0;
+};
+
+class MessageCompletenessChecker // interface
+{
+public:
+    virtual bool isComplete(const std::vector<char> &message) const = 0;
+};
+
+}
+
+#endif
--- a/vamp-client/client.cpp	Wed Oct 12 21:34:21 2016 +0100
+++ b/vamp-client/client.cpp	Thu Oct 13 10:17:59 2016 +0100
@@ -1,330 +1,20 @@
 
-#include "stub.h"
-
-#include "vamp-capnp/VampnProto.h"
-
-#include "vamp-support/AssignedPluginHandleMapper.h"
-
-#include <QProcess>
+#include "PiperStubPlugin.h"
+#include "CapnpMessageCompletenessChecker.h"
+#include "PipedQProcessTransport.h"
+#include "PiperCapnpClient.h"
 
 #include <stdexcept>
 
 using std::cerr;
 using std::endl;
 
-// First cut plan: this is to be client-qt.cpp, using a QProcess, so
-// we're using pipes and the server is completely synchronous,
-// handling only one call at once. Our PiperClient will fire off a
-// QProcess and refer to its io device. Each request message is
-// serialised into memory using capnp::MallocMessageBuilder and
-// shunted into the process pipe; we then wait for some bytes to come
-// back and use capnp::expectedSizeInWordsFromPrefix to work out when
-// a whole message is available, reading only that amount from the
-// device and using FlatArrayMessageReader to convert to a response
-// message. If the response message's id does not match the request
-// message's, then the server has gone wrong (it should never be
-// servicing more than one request at a time).
-
-// Next level: Capnp RPC, but I want to get the first level to work
-// first, not least because the server already exists.
-
-namespace piper { //!!! probably something different
-
-class PiperClient : public PiperClientStubRequirements
-{
-    // unsigned to avoid undefined behaviour on possible wrap
-    typedef uint32_t ReqId;
-    
-public:
-    PiperClient() {
-        m_process = new QProcess();
-        m_process->setReadChannel(QProcess::StandardOutput);
-        m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel);
-        m_process->start("../bin/piper-vamp-server"); //!!!
-        if (!m_process->waitForStarted()) {
-            cerr << "server failed to start" << endl;
-            delete m_process;
-            m_process = 0;
-        }
-    }
-
-    ~PiperClient() {
-        if (m_process) {
-            if (m_process->state() != QProcess::NotRunning) {
-		m_process->closeWriteChannel();
-                m_process->waitForFinished(200);
-                m_process->close();
-                m_process->waitForFinished();
-		cerr << "server exited" << endl;
-            }
-            delete m_process;
-        }
-    }
-
-    //!!! obviously, factor out all repetitive guff
-
-    //!!! list and load are supposed to be called by application code,
-    //!!! but the rest are only supposed to be called by the plugin --
-    //!!! sort out the api here
-    
-    Vamp::Plugin *
-    load(std::string key, float inputSampleRate, int adapterFlags) {
-
-        if (!m_process) {
-            throw std::runtime_error("Piper server failed to start");
-        }
-
-        Vamp::HostExt::LoadRequest request;
-        request.pluginKey = key;
-        request.inputSampleRate = inputSampleRate;
-        request.adapterFlags = adapterFlags;
-
-        capnp::MallocMessageBuilder message;
-        RpcRequest::Builder builder = message.initRoot<RpcRequest>();
-
-        VampnProto::buildRpcRequest_Load(builder, request);
-        ReqId id = getId();
-        builder.getId().setNumber(id);
-
-        auto arr = messageToFlatArray(message);
-        m_process->write(arr.asChars().begin(), arr.asChars().size());
-
-        //!!! ... --> will also need some way to kill this process
-        //!!! (from another thread)
-
-        QByteArray buffer = readResponseBuffer();
-	auto karr = toKJArray(buffer);
-        capnp::FlatArrayMessageReader responseMessage(karr);
-        RpcResponse::Reader reader = responseMessage.getRoot<RpcResponse>();
-
-        //!!! handle (explicit) error case
-
-        checkResponseType(reader, RpcResponse::Response::Which::LOAD, id);
-        
-        const LoadResponse::Reader &lr = reader.getResponse().getLoad();
-
-        Vamp::HostExt::PluginStaticData psd;
-        Vamp::HostExt::PluginConfiguration defaultConfig;
-        VampnProto::readExtractorStaticData(psd, lr.getStaticData());
-        VampnProto::readConfiguration(defaultConfig, lr.getDefaultConfiguration());
-        
-        Vamp::Plugin *plugin = new PiperStubPlugin(this,
-                                                   inputSampleRate,
-                                                   psd,
-                                                   defaultConfig);
-
-        m_mapper.addPlugin(lr.getHandle(), plugin);
-
-        return plugin;
-    };     
-    
-    virtual
-    Vamp::Plugin::OutputList
-    configure(PiperStubPlugin *plugin,
-              Vamp::HostExt::PluginConfiguration config) {
-
-        if (!m_process) {
-            throw std::runtime_error("Piper server failed to start");
-        }
-
-        Vamp::HostExt::ConfigurationRequest request;
-        request.plugin = plugin;
-        request.configuration = config;
-
-        capnp::MallocMessageBuilder message;
-        RpcRequest::Builder builder = message.initRoot<RpcRequest>();
-
-        VampnProto::buildRpcRequest_Configure(builder, request, m_mapper);
-        ReqId id = getId();
-        builder.getId().setNumber(id);
-        
-        auto arr = messageToFlatArray(message);
-        m_process->write(arr.asChars().begin(), arr.asChars().size());
-        
-        QByteArray buffer = readResponseBuffer();
-	auto karr = toKJArray(buffer);
-        capnp::FlatArrayMessageReader responseMessage(karr);
-        RpcResponse::Reader reader = responseMessage.getRoot<RpcResponse>();
-
-        //!!! handle (explicit) error case
-
-        checkResponseType(reader, RpcResponse::Response::Which::CONFIGURE, id);
-
-        Vamp::HostExt::ConfigurationResponse cr;
-        VampnProto::readConfigurationResponse(cr,
-                                              reader.getResponse().getConfigure(),
-                                              m_mapper);
-
-        return cr.outputs;
-    };
-    
-    virtual
-    Vamp::Plugin::FeatureSet
-    process(PiperStubPlugin *plugin,
-            std::vector<std::vector<float> > inputBuffers,
-            Vamp::RealTime timestamp) {
-
-        if (!m_process) {
-            throw std::runtime_error("Piper server failed to start");
-        }
-
-        Vamp::HostExt::ProcessRequest request;
-        request.plugin = plugin;
-        request.inputBuffers = inputBuffers;
-        request.timestamp = timestamp;
-        
-        capnp::MallocMessageBuilder message;
-        RpcRequest::Builder builder = message.initRoot<RpcRequest>();
-
-        VampnProto::buildRpcRequest_Process(builder, request, m_mapper);
-        ReqId id = getId();
-        builder.getId().setNumber(id);
-        
-        auto arr = messageToFlatArray(message);
-        m_process->write(arr.asChars().begin(), arr.asChars().size());
-        
-        QByteArray buffer = readResponseBuffer();
-	auto karr = toKJArray(buffer);
-        capnp::FlatArrayMessageReader responseMessage(karr);
-        RpcResponse::Reader reader = responseMessage.getRoot<RpcResponse>();
-
-        //!!! handle (explicit) error case
-
-        checkResponseType(reader, RpcResponse::Response::Which::PROCESS, id);
-
-        Vamp::HostExt::ProcessResponse pr;
-        VampnProto::readProcessResponse(pr,
-                                        reader.getResponse().getProcess(),
-                                        m_mapper);
-
-        return pr.features;
-    }
-
-    virtual Vamp::Plugin::FeatureSet
-    finish(PiperStubPlugin *plugin) {
-
-        if (!m_process) {
-            throw std::runtime_error("Piper server failed to start");
-        }
-
-        Vamp::HostExt::FinishRequest request;
-        request.plugin = plugin;
-        
-        capnp::MallocMessageBuilder message;
-        RpcRequest::Builder builder = message.initRoot<RpcRequest>();
-
-        VampnProto::buildRpcRequest_Finish(builder, request, m_mapper);
-        ReqId id = getId();
-        builder.getId().setNumber(id);
-        
-        auto arr = messageToFlatArray(message);
-        m_process->write(arr.asChars().begin(), arr.asChars().size());
-        
-        QByteArray buffer = readResponseBuffer();
-	auto karr = toKJArray(buffer);
-        capnp::FlatArrayMessageReader responseMessage(karr);
-        RpcResponse::Reader reader = responseMessage.getRoot<RpcResponse>();
-
-        //!!! handle (explicit) error case
-
-        checkResponseType(reader, RpcResponse::Response::Which::FINISH, id);
-
-        Vamp::HostExt::ProcessResponse pr;
-        VampnProto::readFinishResponse(pr,
-                                       reader.getResponse().getFinish(),
-                                       m_mapper);
-
-        m_mapper.removePlugin(m_mapper.pluginToHandle(plugin));
-
-	// Don't delete the plugin. It's the plugin that is supposed
-	// to be calling us here
-        
-        return pr.features;
-    }
-
-private:
-    QProcess *m_process;
-    AssignedPluginHandleMapper m_mapper;
-    ReqId getId() {
-        //!!! todo: mutex
-        static ReqId m_nextId = 0;
-        return m_nextId++;
-    }
-
-    kj::Array<capnp::word>
-    toKJArray(QByteArray qarr) {
-	// We could do this whole thing with fewer copies, but let's
-	// see whether it matters first
-        size_t wordSize = sizeof(capnp::word);
-	size_t words = qarr.size() / wordSize;
-	kj::Array<capnp::word> karr(kj::heapArray<capnp::word>(words));
-	memcpy(karr.begin(), qarr.data(), words * wordSize);
-	return karr;
-    }
-
-    QByteArray
-    readResponseBuffer() { 
-        
-        QByteArray buffer;
-        size_t wordSize = sizeof(capnp::word);
-        bool complete = false;
-        
-        while (!complete) {
-
-            m_process->waitForReadyRead(1000);
-            qint64 byteCount = m_process->bytesAvailable();
-            qint64 wordCount = byteCount / wordSize;
-
-            if (!wordCount) {
-                if (m_process->state() == QProcess::NotRunning) {
-                    cerr << "ERROR: Subprocess exited: Load failed" << endl;
-                    throw std::runtime_error("Piper server exited unexpectedly");
-                }
-            } else {
-                buffer.append(m_process->read(wordCount * wordSize));
-                size_t haveWords = buffer.size() / wordSize;
-                size_t expectedWords =
-                    capnp::expectedSizeInWordsFromPrefix(toKJArray(buffer));
-                if (haveWords >= expectedWords) {
-                    if (haveWords > expectedWords) {
-                        cerr << "WARNING: obtained more data than expected ("
-                             << haveWords << " words, expected " << expectedWords
-                             << ")" << endl;
-                    }
-                    complete = true;
-                }
-            }
-        }
-/*
-        cerr << "buffer = ";
-        for (int i = 0; i < buffer.size(); ++i) {
-            if (i % 16 == 0) cerr << "\n";
-            cerr << int(buffer[i]) << " ";
-        }
-        cerr << "\n";
-*/        
-        return buffer;
-    }
-
-    void
-    checkResponseType(const RpcResponse::Reader &r,
-                      RpcResponse::Response::Which type,
-                      ReqId id) {
-        
-        if (r.getResponse().which() != type) {
-            throw std::runtime_error("Wrong response type");
-        }
-        if (ReqId(r.getId().getNumber()) != id) {
-            throw std::runtime_error("Wrong response id");
-        }
-    }
-};
-    
-}
-
 int main(int, char **)
 {
-    piper::PiperClient client;
+    piper::CapnpMessageCompletenessChecker checker;
+    piper::PipedQProcessTransport transport("../bin/piper-vamp-server", &checker);
+    piper::PiperCapnpClient client(&transport);
+    
     Vamp::Plugin *plugin = client.load("vamp-example-plugins:zerocrossing", 16, 0);
     if (!plugin->initialise(1, 4, 4)) {
         cerr << "initialisation failed" << endl;
--- a/vamp-client/client.pro	Wed Oct 12 21:34:21 2016 +0100
+++ b/vamp-client/client.pro	Thu Oct 13 10:17:59 2016 +0100
@@ -25,5 +25,15 @@
 TARGET = client
 
 SOURCES += \
-	client.cpp ../vamp-capnp/piper.capnp.c++
+        client.cpp \
+        ../vamp-capnp/piper.capnp.c++
+        
+HEADERS += \
+        CapnpMessageCompletenessChecker.h \
+        PipedQProcessTransport.h \
+        PiperCapnpClient.h \
+        PiperClient.h \
+        PiperStubPlugin.h \
+        SynchronousTransport.h
+        
 
--- a/vamp-client/stub.h	Wed Oct 12 21:34:21 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,228 +0,0 @@
-
-#include <vamp-hostsdk/Plugin.h>
-#include <vamp-hostsdk/PluginLoader.h>
-#include <vamp-hostsdk/PluginStaticData.h>
-#include <vamp-hostsdk/PluginConfiguration.h>
-
-#include <cstdint>
-
-namespace piper { //!!! should be something else
-
-class PiperStubPlugin;
-
-class PiperClientStubRequirements
-{
-public:
-    virtual
-    Vamp::Plugin::OutputList
-    configure(PiperStubPlugin *plugin,
-              Vamp::HostExt::PluginConfiguration config) = 0;
-    
-    virtual
-    Vamp::Plugin::FeatureSet
-    process(PiperStubPlugin *plugin,
-            std::vector<std::vector<float> > inputBuffers,
-            Vamp::RealTime timestamp) = 0;
-
-    virtual Vamp::Plugin::FeatureSet
-    finish(PiperStubPlugin *plugin) = 0;
-};
-
-class PiperStubPlugin : public Vamp::Plugin
-{
-    enum State {
-        Loaded, Configured, Finished
-    };
-    
-public:
-    PiperStubPlugin(PiperClientStubRequirements *client,
-                    float inputSampleRate,
-                    Vamp::HostExt::PluginStaticData psd,
-                    Vamp::HostExt::PluginConfiguration defaultConfig) :
-        Plugin(inputSampleRate),
-        m_client(client),
-        m_state(Loaded),
-        m_psd(psd),
-        m_defaultConfig(defaultConfig),
-        m_config(defaultConfig)
-    { }
-
-    virtual ~PiperStubPlugin() {
-        if (m_state != Finished) {
-	    (void)m_client->finish(this);
-        }
-    }
-
-    virtual std::string getIdentifier() const {
-        return m_psd.basic.identifier;
-    }
-
-    virtual std::string getName() const {
-        return m_psd.basic.name;
-    }
-
-    virtual std::string getDescription() const {
-        return m_psd.basic.description;
-    }
-
-    virtual std::string getMaker() const {
-        return m_psd.maker;
-    }
-
-    virtual std::string getCopyright() const {
-        return m_psd.copyright;
-    }
-
-    virtual int getPluginVersion() const {
-        return m_psd.pluginVersion;
-    }
-
-    virtual ParameterList getParameterDescriptors() const {
-        return m_psd.parameters;
-    }
-
-    virtual float getParameter(std::string name) const {
-        if (m_config.parameterValues.find(name) != m_config.parameterValues.end()) {
-            return m_config.parameterValues.at(name);
-        } else {
-            return 0.f;
-        }
-    }
-
-    virtual void setParameter(std::string name, float value) {
-        if (m_state != Loaded) {
-            throw std::logic_error("Can't set parameter after plugin initialised");
-        }
-        m_config.parameterValues[name] = value;
-    }
-
-    virtual ProgramList getPrograms() const {
-        return m_psd.programs;
-    }
-
-    virtual std::string getCurrentProgram() const {
-        return m_config.currentProgram;
-    }
-    
-    virtual void selectProgram(std::string program) {
-        if (m_state != Loaded) {
-            throw std::logic_error("Can't select program after plugin initialised");
-        }
-        m_config.currentProgram = program;
-    }
-
-    virtual bool initialise(size_t inputChannels,
-                            size_t stepSize,
-                            size_t blockSize) {
-
-        if (m_state != Loaded) {
-            throw std::logic_error("Plugin has already been initialised");
-        }
-        
-        m_config.channelCount = inputChannels;
-        m_config.stepSize = stepSize;
-        m_config.blockSize = blockSize;
-
-        m_outputs = m_client->configure(this, m_config);
-
-        if (!m_outputs.empty()) {
-            m_state = Configured;
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    virtual void reset() {
-        //!!! hm, how to deal with this? there is no reset() in Piper!
-        throw "Please do not call this function again.";
-    }
-
-    virtual InputDomain getInputDomain() const {
-        return m_psd.inputDomain;
-    }
-
-    virtual size_t getPreferredBlockSize() const {
-        return m_defaultConfig.blockSize;
-    }
-
-    virtual size_t getPreferredStepSize() const {
-        return m_defaultConfig.stepSize;
-    }
-
-    virtual size_t getMinChannelCount() const {
-        return m_psd.minChannelCount;
-    }
-
-    virtual size_t getMaxChannelCount() const {
-        return m_psd.maxChannelCount;
-    }
-
-    virtual OutputList getOutputDescriptors() const {
-        if (m_state == Configured) {
-            return m_outputs;
-        }
-
-        //!!! todo: figure out for which hosts (and adapters?) it may
-        //!!! be a problem that the output descriptors are incomplete
-        //!!! here. Any such hosts/adapters are broken, but I bet they
-        //!!! exist
-        
-        OutputList staticOutputs;
-        for (const auto &o: m_psd.basicOutputInfo) {
-            OutputDescriptor od;
-            od.identifier = o.identifier;
-            od.name = o.name;
-            od.description = o.description;
-            staticOutputs.push_back(od);
-        }
-        return staticOutputs;
-    }
-
-    virtual FeatureSet process(const float *const *inputBuffers,
-			       Vamp::RealTime timestamp) {
-
-        if (m_state == Loaded) {
-            throw std::logic_error("Plugin has not been initialised");
-        }
-        if (m_state == Finished) {
-            throw std::logic_error("Plugin has already been disposed of");
-        }
-
-        //!!! ew
-        std::vector<std::vector<float> > vecbuf;
-        for (int c = 0; c < m_config.channelCount; ++c) {
-            vecbuf.push_back(std::vector<float>
-                             (inputBuffers[c],
-                              inputBuffers[c] + m_config.blockSize));
-        }
-        
-        return m_client->process(this, vecbuf, timestamp);
-    }
-
-    virtual FeatureSet getRemainingFeatures() {
-
-        if (m_state == Loaded) {
-            throw std::logic_error("Plugin has not been configured");
-        }
-        if (m_state == Finished) {
-            throw std::logic_error("Plugin has already been disposed of");
-        }
-
-        m_state = Finished;
-
-        return m_client->finish(this);
-    }
-    
-private:
-    PiperClientStubRequirements *m_client;
-    State m_state;
-    Vamp::HostExt::PluginStaticData m_psd;
-    OutputList m_outputs;
-    Vamp::HostExt::PluginConfiguration m_defaultConfig;
-    Vamp::HostExt::PluginConfiguration m_config;
-};
-
-
-}
-