changeset 59:fa79c4ec847d host-factory-stuff

* Put hostext stuff in the HostExt sub-namespace * Tidy up system-specific stuff in PluginLoader * Make PluginLoader return a deletion-notifying wrapper which permits the library to be unloaded when no longer in use * Add PluginChannelAdapter * Make vamp-simple-host use PluginChannelAdapter, and use the PluginLoader for plugin-running task. Also some other enhancements to host
author cannam
date Thu, 24 May 2007 15:17:07 +0000
parents 0284955e31e5
children 087c16cca0d6
files Makefile README examples/AmplitudeFollower.h examples/PercussionOnsetDetector.h examples/SpectralCentroid.h examples/ZeroCrossing.h examples/plugins.cpp host/vamp-simple-host.cpp vamp-sdk/Plugin.h vamp-sdk/PluginBase.h vamp-sdk/hostext/PluginChannelAdapter.cpp vamp-sdk/hostext/PluginChannelAdapter.h vamp-sdk/hostext/PluginInputDomainAdapter.cpp vamp-sdk/hostext/PluginInputDomainAdapter.h vamp-sdk/hostext/PluginLoader.cpp vamp-sdk/hostext/PluginLoader.h vamp-sdk/hostext/PluginWrapper.cpp vamp-sdk/hostext/PluginWrapper.h vamp-sdk/hostext/system.h
diffstat 19 files changed, 758 insertions(+), 424 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Thu May 24 10:05:00 2007 +0000
+++ b/Makefile	Thu May 24 15:17:07 2007 +0000
@@ -3,8 +3,6 @@
 # libraries, example plugins, and the test host.  Please adjust to
 # suit your operating system requirements.
 
-## Choose your 
-
 APIDIR		= vamp
 SDKDIR		= vamp-sdk
 HOSTEXTDIR      = vamp-sdk/hostext
@@ -29,7 +27,7 @@
 
 # Compile flags
 #
-CXXFLAGS	:= $(CXXFLAGS) -O2 -Wall -I$(SDKDIR) -I$(HOSTEXTDIR) -I$(APIDIR) -I.
+CXXFLAGS	:= $(CXXFLAGS) -g -Wall -I.
 
 # Libraries required for the plugins.
 # (Note that it is desirable to statically link libstdc++ if possible,
@@ -94,6 +92,7 @@
 		$(SDKDIR)/Plugin.h \
 		$(SDKDIR)/PluginBase.h \
 		$(SDKDIR)/PluginHostAdapter.h \
+		$(HOSTEXTDIR)/PluginChannelAdapter.h \
 		$(HOSTEXTDIR)/PluginInputDomainAdapter.h \
 		$(HOSTEXTDIR)/PluginLoader.h \
 		$(HOSTEXTDIR)/PluginWrapper.h \
@@ -105,6 +104,7 @@
 
 HOSTSDK_OBJECTS	= \
 		$(SDKDIR)/PluginHostAdapter.o \
+		$(HOSTEXTDIR)/PluginChannelAdapter.o \
 		$(HOSTEXTDIR)/PluginInputDomainAdapter.o \
 		$(HOSTEXTDIR)/PluginLoader.o \
 		$(HOSTEXTDIR)/PluginWrapper.o \
@@ -152,9 +152,9 @@
 
 sdk:		$(SDK_STATIC) $(SDK_DYNAMIC) $(HOSTSDK_STATIC) $(HOSTSDK_DYNAMIC)
 
-plugins:	sdk $(PLUGIN_TARGET)
+plugins:	$(PLUGIN_TARGET)
 
-host:		sdk $(HOST_TARGET)
+host:		$(HOST_TARGET)
 
 all:		sdk plugins host test
 
@@ -170,14 +170,14 @@
 $(HOSTSDK_DYNAMIC):	$(HOSTSDK_OBJECTS) $(API_HEADERS) $(HOSTSDK_HEADERS)
 		$(CXX) $(LDFLAGS) $(PLUGIN_LDFLAGS) -o $@ $(HOSTSDK_OBJECTS)
 
-$(PLUGIN_TARGET):	$(PLUGIN_OBJECTS) $(SDK_TARGET) $(PLUGIN_HEADERS)
+$(PLUGIN_TARGET):	$(PLUGIN_OBJECTS) $(SDK_STATIC) $(PLUGIN_HEADERS)
 		$(CXX) $(LDFLAGS) $(PLUGIN_LDFLAGS) -o $@ $(PLUGIN_OBJECTS) $(PLUGIN_LIBS)
 
-$(HOST_TARGET):	$(HOST_OBJECTS) $(HOSTSDK_TARGET) $(HOST_HEADERS)
+$(HOST_TARGET):	$(HOST_OBJECTS) $(HOSTSDK_STATIC) $(HOST_HEADERS)
 		$(CXX) $(LDFLAGS) $(HOST_LDFLAGS) -o $@ $(HOST_OBJECTS) $(HOST_LIBS)
 
 test:		plugins host
-		$(HOST_TARGET) $(PLUGIN_TARGET)
+		VAMP_PATH=$(EXAMPLEDIR) $(HOST_TARGET) -l
 
 clean:		
 		rm -f $(SDK_OBJECTS) $(HOSTSDK_OBJECTS) $(PLUGIN_OBJECTS) $(HOST_OBJECTS)
--- a/README	Thu May 24 10:05:00 2007 +0000
+++ b/README	Thu May 24 15:17:07 2007 +0000
@@ -190,11 +190,14 @@
 =======
 
 Vamp and the Vamp SDK were designed and made at the Centre for Digital
-Music at Queen Mary, University of London.  The SDK code was written
-by Chris Cannam, copyright (c) 2005-2006 Chris Cannam.  Mark Sandler
-and Christian Landone provided ideas and direction, and Mark Levy, Dan
-Stowell, Martin Gasser and Craig Sapp provided testing and other input
-for the 1.0 API and SDK.  The API reuses some ideas from several prior
-plugin systems, notably DSSI (http://dssi.sourceforge.net) and FEAPI
-(http://feapi.sourceforge.net).
+Music at Queen Mary, University of London.
 
+The SDK was written by Chris Cannam, copyright (c) 2005-2007
+Chris Cannam and QMUL.
+
+Mark Sandler and Christian Landone provided ideas and direction, and
+Mark Levy, Dan Stowell, Martin Gasser and Craig Sapp provided testing
+and other input for the 1.0 API and SDK.  The API also uses some ideas
+from prior plugin systems, notably DSSI (http://dssi.sourceforge.net)
+and FEAPI (http://feapi.sourceforge.net).
+
--- a/examples/AmplitudeFollower.h	Thu May 24 10:05:00 2007 +0000
+++ b/examples/AmplitudeFollower.h	Thu May 24 15:17:07 2007 +0000
@@ -37,7 +37,7 @@
 #ifndef _AMPLITUDE_FOLLOWER_PLUGIN_H_
 #define _AMPLITUDE_FOLLOWER_PLUGIN_H_
 
-#include "Plugin.h"
+#include "vamp-sdk/Plugin.h"
 
 /**
  * Example plugin implementing the SuperCollider amplitude follower
--- a/examples/PercussionOnsetDetector.h	Thu May 24 10:05:00 2007 +0000
+++ b/examples/PercussionOnsetDetector.h	Thu May 24 15:17:07 2007 +0000
@@ -37,7 +37,7 @@
 #ifndef _PERCUSSION_ONSET_DETECTOR_PLUGIN_H_
 #define _PERCUSSION_ONSET_DETECTOR_PLUGIN_H_
 
-#include "Plugin.h"
+#include "vamp-sdk/Plugin.h"
 
 /**
  * Example plugin that detects percussive events.
--- a/examples/SpectralCentroid.h	Thu May 24 10:05:00 2007 +0000
+++ b/examples/SpectralCentroid.h	Thu May 24 15:17:07 2007 +0000
@@ -37,7 +37,7 @@
 #ifndef _SPECTRAL_CENTROID_PLUGIN_H_
 #define _SPECTRAL_CENTROID_PLUGIN_H_
 
-#include "Plugin.h"
+#include "vamp-sdk/Plugin.h"
 
 /**
  * Example plugin that calculates the centre of gravity of the
--- a/examples/ZeroCrossing.h	Thu May 24 10:05:00 2007 +0000
+++ b/examples/ZeroCrossing.h	Thu May 24 15:17:07 2007 +0000
@@ -37,7 +37,7 @@
 #ifndef _ZERO_CROSSING_PLUGIN_H_
 #define _ZERO_CROSSING_PLUGIN_H_
 
-#include "Plugin.h"
+#include "vamp-sdk/Plugin.h"
 
 /**
  * Example plugin that calculates the positions and density of
--- a/examples/plugins.cpp	Thu May 24 10:05:00 2007 +0000
+++ b/examples/plugins.cpp	Thu May 24 15:17:07 2007 +0000
@@ -34,9 +34,9 @@
     authorization.
 */
 
-#include <vamp/vamp.h>
+#include "vamp/vamp.h"
+#include "vamp-sdk/PluginAdapter.h"
 
-#include "PluginAdapter.h"
 #include "ZeroCrossing.h"
 #include "SpectralCentroid.h"
 #include "PercussionOnsetDetector.h"
--- a/host/vamp-simple-host.cpp	Thu May 24 10:05:00 2007 +0000
+++ b/host/vamp-simple-host.cpp	Thu May 24 15:17:07 2007 +0000
@@ -35,10 +35,11 @@
     authorization.
 */
 
-#include "PluginHostAdapter.h"
-#include "PluginInputDomainAdapter.h"
-#include "PluginLoader.h"
-#include "vamp.h"
+#include "vamp-sdk/PluginHostAdapter.h"
+#include "vamp-sdk/hostext/PluginChannelAdapter.h"
+#include "vamp-sdk/hostext/PluginInputDomainAdapter.h"
+#include "vamp-sdk/hostext/PluginLoader.h"
+#include "vamp/vamp.h"
 
 #include <iostream>
 #include <sndfile.h>
@@ -53,61 +54,62 @@
 using std::string;
 using std::vector;
 
-#define HOST_VERSION "1.0"
+using Vamp::HostExt::PluginLoader;
+
+#define HOST_VERSION "1.1"
 
 void printFeatures(int, int, int, Vamp::Plugin::FeatureSet);
 void transformInput(float *, size_t);
 void fft(unsigned int, bool, double *, double *, double *, double *);
-void printPluginPath();
+void printPluginPath(bool verbose);
 void enumeratePlugins();
+void listPluginsInLibrary(string soname);
+int runPlugin(string myname, string soname, string id, string output,
+              int outputNo, string inputFile);
 
-/*
-    A very simple Vamp plugin host.  Given the name of a plugin
-    library and the name of a sound file on the command line, it loads
-    the first plugin in the library and runs it on the sound file,
-    dumping the plugin's first output to stdout.
-*/
+void usage(const char *name)
+{
+    cerr << "\n"
+         << name << ": A simple Vamp plugin host.\n\n"
+        "Centre for Digital Music, Queen Mary, University of London.\n"
+        "Copyright 2006-2007 Chris Cannam and QMUL.\n"
+        "Freely redistributable; published under a BSD-style license.\n\n"
+        "Usage:\n\n"
+        "  " << name << " pluginlibrary[." << PLUGIN_SUFFIX << "]:plugin[:output] file.wav\n"
+        "  " << name << " pluginlibrary[." << PLUGIN_SUFFIX << "]:plugin file.wav [outputno]\n\n"
+        "    -- Load plugin id \"plugin\" from \"pluginlibrary\" and run it on the\n"
+        "       audio data in \"file.wav\"; retrieve the named \"output\", or output\n"
+        "       number \"outputno\" (the first output by default) and dump it to\n"
+        "       standard output.\n\n"
+        "  " << name << " -l\n\n"
+        "    -- List the plugin libraries and Vamp plugins in the plugin search path.\n\n"
+        "  " << name << " -p\n\n"
+        "    -- Print out the Vamp plugin search path.\n\n"
+        "  " << name << " -v\n\n"
+        "    -- Display version information only.\n\n"
+         << endl;
+    exit(2);
+}
 
 int main(int argc, char **argv)
 {
+    char *scooter = argv[0];
+    char *name = 0;
+    while (scooter && *scooter) {
+        if (*scooter == '/' || *scooter == '\\') name = ++scooter;
+        else ++scooter;
+    }
+    if (!name || !*name) name = argv[0];
+    
     if (argc < 2 || argc > 4 ||
         (argc == 2 &&
          (!strcmp(argv[1], "-?") ||
           !strcmp(argv[1], "-h") ||
           !strcmp(argv[1], "--help")))) {
 
-        char *scooter = argv[0];
-        char *name = 0;
-        while (scooter && *scooter) {
-            if (*scooter == '/' || *scooter == '\\') name = ++scooter;
-            else ++scooter;
-        }
-        if (!name || !*name) name = argv[0];
-        cerr << "\n"
-             << name << ": A simple Vamp plugin host.\n\n"
-            "Centre for Digital Music, Queen Mary, University of London.\n"
-            "Copyright 2006 Chris Cannam and QMUL.\n"
-            "Freely redistributable; published under a BSD-style license.\n\n"
-            "Usage:\n\n"
-            "  " << name << " pluginlibrary." << PLUGIN_SUFFIX << "\n\n"
-            "    -- Load \"pluginlibrary\" and list the Vamp plugins it contains.\n\n"
-            "  " << name << " pluginlibrary." << PLUGIN_SUFFIX << ":plugin file.wav [outputno]\n\n"
-            "    -- Load plugin id \"plugin\" from \"pluginlibrary\" and run it on the\n"
-            "       audio data in \"file.wav\", dumping the output from \"outputno\"\n"
-            "       (default 0) to standard output.\n\n"
-#ifdef HAVE_OPENDIR
-            "  " << name << " -l\n\n"
-            "    -- List the plugin libraries and Vamp plugins in the plugin search path.\n\n"
-#endif
-            "  " << name << " -p\n\n"
-            "    -- Print out the Vamp plugin search path.\n\n"
-	    "  " << name << " -v\n\n"
-	    "    -- Display version information only.\n\n"
-	    "Note that this host does not use the plugin search path when loadinga plugin.\nIf a plugin library is specified, it should be with a full file path.\n"
-             << endl;
-        return 2;
+        usage(name); // does not return
     }
-    
+
     if (argc == 2 && !strcmp(argv[1], "-v")) {
 	cout << "Simple Vamp plugin host version: " << HOST_VERSION << endl
 	     << "Vamp API version: " << VAMP_API_VERSION << endl
@@ -116,87 +118,57 @@
     }
     
     if (argc == 2 && !strcmp(argv[1], "-l")) {
+        printPluginPath(true);
         enumeratePlugins();
         return 0;
     }
     if (argc == 2 && !strcmp(argv[1], "-p")) {
-        printPluginPath();
+        printPluginPath(false);
         return 0;
     }
 
-    cerr << endl << argv[0] << ": Running..." << endl;
+    cerr << endl << name << ": Running..." << endl;
 
     string soname = argv[1];
     string plugid = "";
+    string output = "";
+    int outputNo = -1;
     string wavname;
     if (argc >= 3) wavname = argv[2];
 
-    int sep = soname.find(":");
-    if (sep >= 0 && sep < int(soname.length())) {
+    string::size_type sep = soname.find(':');
+
+    if (sep != string::npos) {
         plugid = soname.substr(sep + 1);
         soname = soname.substr(0, sep);
-    }
 
-    void *libraryHandle = DLOPEN(soname, RTLD_LAZY);
-
-    if (!libraryHandle) {
-        cerr << argv[0] << ": Failed to open plugin library " 
-                  << soname << ": " << DLERROR() << endl;
-        return 1;
-    }
-
-    cerr << argv[0] << ": Opened plugin library " << soname << endl;
-
-    VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
-        DLSYM(libraryHandle, "vampGetPluginDescriptor");
-    
-    if (!fn) {
-        cerr << argv[0] << ": No Vamp descriptor function in library "
-                  << soname << endl;
-        DLCLOSE(libraryHandle);
-        return 1;
-    }
-
-    cerr << argv[0] << ": Found plugin descriptor function" << endl;
-
-    int index = 0;
-    int plugnumber = -1;
-    const VampPluginDescriptor *descriptor = 0;
-
-    while ((descriptor = fn(VAMP_API_VERSION, index))) {
-
-        Vamp::PluginHostAdapter plugin(descriptor, 48000);
-        cerr << argv[0] << ": Plugin " << (index+1)
-                  << " is \"" << plugin.getIdentifier() << "\"" << endl;
-
-        if (plugin.getIdentifier() == plugid) plugnumber = index;
-        
-        ++index;
-    }
-
-    cerr << argv[0] << ": Done\n" << endl;
-
-    if (wavname == "") {
-        DLCLOSE(libraryHandle);
-        return 0;
-    }
-
-    if (plugnumber < 0) {
-        if (plugid != "") {
-            cerr << "ERROR: No such plugin as " << plugid << " in library"
-                 << endl;
-            DLCLOSE(libraryHandle);
-            return 0;
-        } else {
-            plugnumber = 0;
+        sep = plugid.find(':');
+        if (sep != string::npos) {
+            output = plugid.substr(sep + 1);
+            plugid = plugid.substr(0, sep);
         }
     }
 
-    descriptor = fn(VAMP_API_VERSION, plugnumber);
-    if (!descriptor) {
-        DLCLOSE(libraryHandle);
-        return 0;
+    if (plugid == "") {
+        usage(name);
     }
+
+    if (argc == 4) outputNo = atoi(argv[3]);
+
+    if (output != "" && outputNo != -1) {
+        usage(name);
+    }
+
+    return runPlugin(name, soname, plugid, output, outputNo, wavname);
+}
+
+
+int runPlugin(string myname, string soname, string id,
+              string output, int outputNo, string wavname)
+{
+    PluginLoader *loader = PluginLoader::getInstance();
+
+    PluginLoader::PluginKey key = loader->composePluginKey(soname, id);
     
     SNDFILE *sndfile;
     SF_INFO sfinfo;
@@ -204,49 +176,28 @@
 
     sndfile = sf_open(wavname.c_str(), SFM_READ, &sfinfo);
     if (!sndfile) {
-	cerr << "ERROR: Failed to open input file \"" << wavname << "\": "
-	     << sf_strerror(sndfile) << endl;
-        DLCLOSE(libraryHandle);
+	cerr << myname << ": ERROR: Failed to open input file \""
+             << wavname << "\": " << sf_strerror(sndfile) << endl;
 	return 1;
     }
 
+    Vamp::Plugin *basePlugin = loader->loadPlugin(key, sfinfo.samplerate);
+    if (!basePlugin) {
+        cerr << myname << ": ERROR: Failed to load plugin \"" << id
+             << "\" from library \"" << soname << "\"" << endl;
+        sf_close(sndfile);
+        return 1;
+    }
+
     Vamp::Plugin *plugin =
-        new Vamp::PluginInputDomainAdapter
-        (new Vamp::PluginHostAdapter(descriptor, sfinfo.samplerate));
+        new Vamp::HostExt::PluginChannelAdapter
+        (new Vamp::HostExt::PluginInputDomainAdapter(basePlugin));
 
-    cerr << "Running " << plugin->getIdentifier() << "..." << endl;
+    cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"..." << endl;
 
     int blockSize = plugin->getPreferredBlockSize();
     int stepSize = plugin->getPreferredStepSize();
 
-    cerr << "Preferred block size = " << blockSize << ", step size = "
-         << stepSize << endl;
-
-    if (blockSize == 0) blockSize = 1024;
-
-    bool rightBlockSize = true;
-
-    if (plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain) {
-
-        int p = 1, b = blockSize;
-        while (b) {
-            p <<= 1;
-            b >>= 1;
-        }
-        if (p != blockSize * 2) {
-            cerr << "WARNING: Plugin requested non-power-of-two block size of "
-                 << blockSize << ",\nwhich is not supported by this host.  ";
-            blockSize = p;
-            cerr << "Rounding up to " << blockSize << "." << endl;
-            rightBlockSize = false;
-        }
-        if (stepSize == 0) stepSize = blockSize / 2;
-
-    } else {
-
-        if (stepSize == 0) stepSize = blockSize;
-    }
-
     int channels = sfinfo.channels;
 
     float *filebuf = new float[blockSize * channels];
@@ -259,48 +210,47 @@
     int minch = plugin->getMinChannelCount();
     int maxch = plugin->getMaxChannelCount();
     cerr << "Plugin accepts " << minch << " -> " << maxch << " channel(s)" << endl;
+    cerr << "Sound file has " << channels << " (will mix/augment if necessary)" << endl;
 
     Vamp::Plugin::OutputList outputs = plugin->getOutputDescriptors();
     Vamp::Plugin::OutputDescriptor od;
 
     int returnValue = 1;
 
-    int output = 0;
-    if (argc == 4) output = atoi(argv[3]);
-
-    bool mix = false;
-
-    if (minch > channels || maxch < channels) {
-        if (minch == 1) {
-            cerr << "WARNING: Sound file has " << channels << " channels, mixing down to 1" << endl;
-            mix = true;
-            channels = 1;
-        } else {
-            cerr << "ERROR: Sound file has " << channels << " channels, out of range for plugin" << endl;
-            goto done;
-        }
-    }
-
     if (outputs.empty()) {
-	cerr << "Plugin has no outputs!" << endl;
+	cerr << "ERROR: Plugin has no outputs!" << endl;
         goto done;
     }
 
-    if (int(outputs.size()) <= output) {
-	cerr << "Output " << output << " requested, but plugin has only " << outputs.size() << " output(s)" << endl;
-        goto done;
-    }        
+    if (outputNo < 0) {
 
-    od = outputs[output];
-    cerr << "Output is " << od.identifier << endl;
+        for (size_t oi = 0; oi < outputs.size(); ++oi) {
+            if (outputs[oi].identifier == output) {
+                outputNo = oi;
+                break;
+            }
+        }
+
+        if (outputNo < 0) {
+            cerr << "ERROR: Non-existent output \"" << output << "\" requested" << endl;
+            goto done;
+        }
+
+    } else {
+
+        if (int(outputs.size()) <= outputNo) {
+            cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl;
+            goto done;
+        }        
+    }
+
+    od = outputs[outputNo];
+    cerr << "Output is: \"" << od.identifier << "\"" << endl;
 
     if (!plugin->initialise(channels, stepSize, blockSize)) {
         cerr << "ERROR: Plugin initialise (channels = " << channels
              << ", stepSize = " << stepSize << ", blockSize = "
              << blockSize << ") failed." << endl;
-        if (!rightBlockSize) {
-            cerr << "(Probably because I couldn't provide the plugin's preferred block size.)" << endl;
-        }
         goto done;
     }
 
@@ -319,64 +269,66 @@
         }
 
         for (int c = 0; c < channels; ++c) {
-            for (int j = 0; j < blockSize; ++j) {
+            int j = 0;
+            while (j < count) {
+                plugbuf[c][j] = filebuf[j * sfinfo.channels + c];
+                ++j;
+            }
+            while (j < blockSize) {
                 plugbuf[c][j] = 0.0f;
-            }
-        }
-
-        for (int j = 0; j < blockSize && j < count; ++j) {
-            int tc = 0;
-            for (int c = 0; c < sfinfo.channels; ++c) {
-                tc = c;
-                if (mix) tc = 0;
-                plugbuf[tc][j] += filebuf[j * sfinfo.channels + c];
-            }
-            if (mix) {
-                plugbuf[0][j] /= sfinfo.channels;
+                ++j;
             }
         }
 
         printFeatures
-            (i, sfinfo.samplerate, output, plugin->process
+            (i, sfinfo.samplerate, outputNo, plugin->process
              (plugbuf, Vamp::RealTime::frame2RealTime(i, sfinfo.samplerate)));
     }
 
-    printFeatures(sfinfo.frames, sfinfo.samplerate, output,
+    printFeatures(sfinfo.frames, sfinfo.samplerate, outputNo,
                   plugin->getRemainingFeatures());
 
     returnValue = 0;
 
 done:
     delete plugin;
-
-    DLCLOSE(libraryHandle);
     sf_close(sndfile);
     return returnValue;
 }
 
 void
-printPluginPath()
+printPluginPath(bool verbose)
 {
+    if (verbose) {
+        cout << "\nVamp plugin search path: ";
+    }
+
     vector<string> path = Vamp::PluginHostAdapter::getPluginPath();
     for (size_t i = 0; i < path.size(); ++i) {
-        cerr << path[i] << endl;
+        if (verbose) {
+            cout << "[" << path[i] << "]";
+        } else {
+            cout << path[i] << endl;
+        }
     }
+
+    if (verbose) cout << endl;
 }
 
 void
 enumeratePlugins()
 {
-    Vamp::PluginLoader loader;
+    PluginLoader *loader = PluginLoader::getInstance();
 
-    cerr << endl << "Vamp plugin libraries found in search path:" << endl;
+    cout << "\nVamp plugin libraries found in search path:" << endl;
 
-    std::vector<Vamp::PluginLoader::PluginKey> plugins = loader.listPlugins();
-    typedef std::multimap<std::string, Vamp::PluginLoader::PluginKey>
+    std::vector<PluginLoader::PluginKey> plugins = loader->listPlugins();
+    typedef std::multimap<std::string, PluginLoader::PluginKey>
         LibraryMap;
     LibraryMap libraryMap;
 
     for (size_t i = 0; i < plugins.size(); ++i) {
-        std::string path = loader.getLibraryPathForPlugin(plugins[i]);
+        std::string path = loader->getLibraryPathForPlugin(plugins[i]);
         libraryMap.insert(LibraryMap::value_type(path, plugins[i]));
     }
 
@@ -387,39 +339,38 @@
          i != libraryMap.end(); ++i) {
         
         std::string path = i->first;
-        Vamp::PluginLoader::PluginKey key = i->second;
+        PluginLoader::PluginKey key = i->second;
 
         if (path != prevPath) {
             prevPath = path;
             index = 0;
-            cerr << "\n  " << path << ":" << endl;
+            cout << "\n  " << path << ":" << endl;
         }
 
-        Vamp::Plugin *plugin = loader.load(key, 48000);
+        Vamp::Plugin *plugin = loader->loadPlugin(key, 48000);
         if (plugin) {
 
             char c = char('A' + index);
             if (c > 'Z') c = char('a' + (index - 26));
 
-            cerr << "    [" << c << "] [v"
+            cout << "    [" << c << "] [v"
                  << plugin->getVampApiVersion() << "] "
                  << plugin->getName() << ", \""
                  << plugin->getIdentifier() << "\"" << " ["
                  << plugin->getMaker() << "]" << endl;
 
-//            cerr << "looking up category for " << key << " ..." << endl;
-            Vamp::PluginLoader::PluginCategoryHierarchy category =
-                loader.getPluginCategory(key);
+            PluginLoader::PluginCategoryHierarchy category =
+                loader->getPluginCategory(key);
             if (!category.empty()) {
-                cerr << "       ";
+                cout << "       ";
                 for (size_t ci = 0; ci < category.size(); ++ci) {
-                    cerr << " > " << category[ci];
+                    cout << " > " << category[ci];
                 }
-                cerr << endl;
+                cout << endl;
             }
 
             if (plugin->getDescription() != "") {
-                cerr << "        - " << plugin->getDescription() << endl;
+                cout << "        - " << plugin->getDescription() << endl;
             }
 
             Vamp::Plugin::OutputList outputs =
@@ -427,21 +378,23 @@
 
             if (outputs.size() > 1) {
                 for (size_t j = 0; j < outputs.size(); ++j) {
-                    cerr << "         (" << j << ") "
+                    cout << "         (" << j << ") "
                          << outputs[j].name << ", \""
                          << outputs[j].identifier << "\"" << endl;
                     if (outputs[j].description != "") {
-                        cerr << "             - " 
+                        cout << "             - " 
                              << outputs[j].description << endl;
                     }
                 }
             }
 
             ++index;
+
+            delete plugin;
         }
     }
 
-    cerr << endl;
+    cout << endl;
 }
 
 void
--- a/vamp-sdk/Plugin.h	Thu May 24 10:05:00 2007 +0000
+++ b/vamp-sdk/Plugin.h	Thu May 24 15:17:07 2007 +0000
@@ -379,7 +379,7 @@
 
     /**
      * Used to distinguish between Vamp::Plugin and other potential
-     * sibling subclasses of PluginBase.  Do not implement this
+     * sibling subclasses of PluginBase.  Do not reimplement this
      * function in your subclass.
      */
     virtual std::string getType() const { return "Feature Extraction Plugin"; }
--- a/vamp-sdk/PluginBase.h	Thu May 24 10:05:00 2007 +0000
+++ b/vamp-sdk/PluginBase.h	Thu May 24 15:17:07 2007 +0000
@@ -40,7 +40,7 @@
 #include <string>
 #include <vector>
 
-#define VAMP_SDK_VERSION "1.0"
+#define VAMP_SDK_VERSION "1.1"
 
 namespace Vamp {
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-sdk/hostext/PluginChannelAdapter.cpp	Thu May 24 15:17:07 2007 +0000
@@ -0,0 +1,165 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Vamp
+
+    An API for audio analysis and feature extraction plugins.
+
+    Centre for Digital Music, Queen Mary, University of London.
+    Copyright 2006 Chris Cannam.
+  
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+    Except as contained in this notice, the names of the Centre for
+    Digital Music; Queen Mary, University of London; and Chris Cannam
+    shall not be used in advertising or otherwise to promote the sale,
+    use or other dealings in this Software without prior written
+    authorization.
+*/
+
+#include "PluginChannelAdapter.h"
+
+namespace Vamp {
+
+namespace HostExt {
+
+PluginChannelAdapter::PluginChannelAdapter(Plugin *plugin) :
+    PluginWrapper(plugin),
+    m_blockSize(0),
+    m_inputChannels(0),
+    m_pluginChannels(0),
+    m_buffer(0),
+    m_forwardPtrs(0)
+{
+}
+
+PluginChannelAdapter::~PluginChannelAdapter()
+{
+    if (m_buffer) {
+        if (m_inputChannels > m_pluginChannels) {
+            delete[] m_buffer[0];
+        } else {
+            for (size_t i = 0; i < m_pluginChannels - m_inputChannels; ++i) {
+                delete[] m_buffer[i];
+            }
+        }
+        delete[] m_buffer;
+        m_buffer = 0;
+    }
+
+    if (m_forwardPtrs) {
+        delete[] m_forwardPtrs;
+        m_forwardPtrs = 0;
+    }
+}
+
+bool
+PluginChannelAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize)
+{
+    m_blockSize = blockSize;
+
+    size_t minch = m_plugin->getMinChannelCount();
+    size_t maxch = m_plugin->getMaxChannelCount();
+
+    m_inputChannels = channels;
+
+    if (channels < minch) {
+        m_forwardPtrs = new const float *[minch];
+        if (m_inputChannels > 1) {
+            // We need a set of zero-valued buffers to add to the
+            // forwarded pointers
+            m_buffer = new float*[minch - channels];
+            for (size_t i = 0; i < minch; ++i) {
+                m_buffer[i] = new float[blockSize];
+                for (size_t j = 0; j < blockSize; ++j) {
+                    m_buffer[i][j] = 0.f;
+                }
+            }
+        }
+        m_pluginChannels = minch;
+        return m_plugin->initialise(minch, stepSize, blockSize);
+    }
+
+    if (channels > maxch) {
+        // We only need m_buffer if we are mixing down to a single
+        // channel -- otherwise we can just forward the same float* as
+        // passed in to process(), expecting the excess to be ignored
+        if (maxch == 1) {
+            m_buffer = new float *[1];
+            m_buffer[0] = new float[blockSize];
+        }
+        m_pluginChannels = maxch;
+        return m_plugin->initialise(maxch, stepSize, blockSize);
+    }
+
+    m_pluginChannels = channels;
+    return m_plugin->initialise(channels, stepSize, blockSize);
+}
+
+PluginChannelAdapter::FeatureSet
+PluginChannelAdapter::process(const float *const *inputBuffers,
+                              RealTime timestamp)
+{
+    if (m_inputChannels < m_pluginChannels) {
+
+        if (m_inputChannels == 1) {
+            for (size_t i = 0; i < m_pluginChannels; ++i) {
+                m_forwardPtrs[i] = inputBuffers[0];
+            }
+        } else {
+            for (size_t i = 0; i < m_inputChannels; ++i) {
+                m_forwardPtrs[i] = inputBuffers[i];
+            }
+            for (size_t i = m_inputChannels; i < m_pluginChannels; ++i) {
+                m_forwardPtrs[i] = m_buffer[i - m_inputChannels];
+            }
+        }
+
+        return m_plugin->process(m_forwardPtrs, timestamp);
+    }
+
+    if (m_inputChannels > m_pluginChannels) {
+
+        if (m_pluginChannels == 1) {
+            for (size_t j = 0; j < m_blockSize; ++j) {
+                m_buffer[0][j] = inputBuffers[0][j];
+            }
+            for (size_t i = 1; i < m_inputChannels; ++i) {
+                for (size_t j = 0; j < m_blockSize; ++j) {
+                    m_buffer[0][j] += inputBuffers[i][j];
+                }
+            }
+            for (size_t j = 0; j < m_blockSize; ++j) {
+                m_buffer[0][j] /= m_inputChannels;
+            }
+            return m_plugin->process(m_buffer, timestamp);
+        } else {
+            return m_plugin->process(inputBuffers, timestamp);
+        }
+    }
+
+    return m_plugin->process(inputBuffers, timestamp);
+}
+
+}
+
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-sdk/hostext/PluginChannelAdapter.h	Thu May 24 15:17:07 2007 +0000
@@ -0,0 +1,105 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Vamp
+
+    An API for audio analysis and feature extraction plugins.
+
+    Centre for Digital Music, Queen Mary, University of London.
+    Copyright 2006 Chris Cannam.
+  
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+    Except as contained in this notice, the names of the Centre for
+    Digital Music; Queen Mary, University of London; and Chris Cannam
+    shall not be used in advertising or otherwise to promote the sale,
+    use or other dealings in this Software without prior written
+    authorization.
+*/
+
+#ifndef _VAMP_PLUGIN_CHANNEL_ADAPTER_H_
+#define _VAMP_PLUGIN_CHANNEL_ADAPTER_H_
+
+#include "PluginWrapper.h"
+
+namespace Vamp {
+
+namespace HostExt {
+
+/**
+ * PluginChannelAdapter implements a policy for management of plugins
+ * that expect a different number of input channels from the number
+ * actually available in the source audio data.
+ *
+ * A host using PluginChannelAdapter may ignore the getMinChannelCount
+ * and getMaxChannelCount reported by the plugin, and still expect the
+ * plugin to run.
+ *
+ * PluginChannelAdapter implements the following policy:
+ *
+ * -- If the plugin supports the provided number of channels directly,
+ * PluginChannelAdapter will just run the plugin as normal.
+ *
+ * -- If the plugin only supports exactly one channel but more than
+ * one channel is provided, PluginChannelAdapter will use the mean of
+ * the channels.  This ensures that the resulting values remain within
+ * the same magnitude range as expected for mono data.
+ *
+ * -- If the plugin requires more than one channel but exactly one is
+ * provided, the provided channel will be duplicated across all the
+ * plugin input channels.
+ *
+ * If none of the above apply:
+ * 
+ * -- If the plugin requires more channels than are provided, the
+ * minimum acceptable number of channels will be produced by adding
+ * empty (zero valued) channels to those provided.
+ *
+ * -- If the plugin requires fewer channels than are provided, the
+ * maximum acceptable number of channels will be produced by
+ * discarding the excess channels.
+ *
+ * Hosts requiring a different channel policy from the above will need
+ * to implement it themselves, instead of using PluginChannelAdapter.
+ */
+
+class PluginChannelAdapter : public PluginWrapper
+{
+public:
+    PluginChannelAdapter(Plugin *plugin); // I take ownership of plugin
+    virtual ~PluginChannelAdapter();
+
+    bool initialise(size_t channels, size_t stepSize, size_t blockSize);
+
+    FeatureSet process(const float *const *inputBuffers, RealTime timestamp);
+
+protected:
+    size_t m_blockSize;
+    size_t m_inputChannels;
+    size_t m_pluginChannels;
+    float **m_buffer;
+    const float **m_forwardPtrs;
+};
+
+}
+
+}
+
+#endif
--- a/vamp-sdk/hostext/PluginInputDomainAdapter.cpp	Thu May 24 10:05:00 2007 +0000
+++ b/vamp-sdk/hostext/PluginInputDomainAdapter.cpp	Thu May 24 15:17:07 2007 +0000
@@ -40,6 +40,8 @@
 
 namespace Vamp {
 
+namespace HostExt {
+
 PluginInputDomainAdapter::PluginInputDomainAdapter(Plugin *plugin) :
     PluginWrapper(plugin),
     m_channels(0),
@@ -55,7 +57,7 @@
 bool
 PluginInputDomainAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize)
 {
-    //!!! complain and die if blocksize is not a power of 2
+    //!!! complain and adapt-or-die if blocksize is not a power of 2
 
     if (m_plugin->getInputDomain() == FrequencyDomain) {
         if (m_channels > 0) {
@@ -106,6 +108,8 @@
 size_t
 PluginInputDomainAdapter::getPreferredBlockSize() const
 {
+    //!!! complain and adapt-or-die if blocksize is not a power of 2
+
     size_t block = m_plugin->getPreferredBlockSize();
 
     if (block == 0 && (m_plugin->getInputDomain() == FrequencyDomain)) {
@@ -269,6 +273,7 @@
     }
 }
 
+}
         
 }
 
--- a/vamp-sdk/hostext/PluginInputDomainAdapter.h	Thu May 24 10:05:00 2007 +0000
+++ b/vamp-sdk/hostext/PluginInputDomainAdapter.h	Thu May 24 15:17:07 2007 +0000
@@ -34,12 +34,15 @@
     authorization.
 */
 
-#ifndef _PLUGIN_INPUT_DOMAIN_ADAPTER_H_
+#ifndef _VAMP_PLUGIN_INPUT_DOMAIN_ADAPTER_H_
+#define _VAMP_PLUGIN_INPUT_DOMAIN_ADAPTER_H_
 
 #include "PluginWrapper.h"
 
 namespace Vamp {
 
+namespace HostExt {
+
 /**
  * An adapter that converts time-domain input into frequency-domain
  * input for plugins that need it.  In every other respect this
@@ -78,7 +81,6 @@
     FeatureSet process(const float *const *inputBuffers, RealTime timestamp);
 
 protected:
-    Plugin *m_plugin;
     size_t m_channels;
     size_t m_blockSize;
     float **m_freqbuf;
@@ -92,4 +94,6 @@
 
 }
 
+}
+
 #endif
--- a/vamp-sdk/hostext/PluginLoader.cpp	Thu May 24 10:05:00 2007 +0000
+++ b/vamp-sdk/hostext/PluginLoader.cpp	Thu May 24 15:17:07 2007 +0000
@@ -34,19 +34,39 @@
     authorization.
 */
 
+#include "vamp-sdk/PluginHostAdapter.h"
 #include "PluginLoader.h"
-#include "PluginHostAdapter.h"
-
-#include "system.h"
 
 #include <fstream>
 
-#include <dirent.h> // POSIX directory open and read
+#ifdef _WIN32
+
+#include <windows.h>
+#include <tchar.h>
+#define PLUGIN_SUFFIX "dll"
+
+#else /* ! _WIN32 */
+
+#include <dirent.h>
+#include <dlfcn.h>
+
+#ifdef __APPLE__
+#define PLUGIN_SUFFIX "dylib"
+#else /* ! __APPLE__ */
+#define PLUGIN_SUFFIX "so"
+#endif /* ! __APPLE__ */
+
+#endif /* ! _WIN32 */
 
 using namespace std;
 
 namespace Vamp {
 	
+namespace HostExt {
+
+PluginLoader *
+PluginLoader::m_instance = 0;
+
 PluginLoader::PluginLoader()
 {
 }
@@ -55,73 +75,85 @@
 {
 }
 
+PluginLoader *
+PluginLoader::getInstance()
+{
+    if (!m_instance) m_instance = new PluginLoader();
+    return m_instance;
+}
+
 vector<PluginLoader::PluginKey>
 PluginLoader::listPlugins() 
 {
-    if (m_pluginLibraryMap.empty()) {
-
-        vector<string> path = PluginHostAdapter::getPluginPath();
-
-        size_t suffixLen = strlen(PLUGIN_SUFFIX);
-
-        for (size_t i = 0; i < path.size(); ++i) {
-            
-            vector<string> files = getFilesInDir(path[i], PLUGIN_SUFFIX);
-            
-
-            for (vector<string>::iterator fi = files.begin();
-                 fi != files.end(); ++fi) {
-
-                string basename = *fi;
-                basename = basename.substr(0, basename.length() - suffixLen - 1);
-
-                string fullPath = path[i];
-                fullPath = fullPath + "/" + *fi; //!!! systemize
-                void *handle = DLOPEN(fullPath, RTLD_LAZY);
-
-                if (!handle) {
-                    cerr << "Vamp::PluginLoader: " << *fi
-                              << ": unable to load library (" << DLERROR()
-                              << ")" << endl;
-                    continue;
-                }
-            
-                VampGetPluginDescriptorFunction fn =
-                    (VampGetPluginDescriptorFunction)DLSYM
-                    (handle, "vampGetPluginDescriptor");
-
-                if (!fn) {
-                    DLCLOSE(handle);
-                    continue;
-                }
-
-                int index = 0;
-                const VampPluginDescriptor *descriptor = 0;
-
-                while ((descriptor = fn(VAMP_API_VERSION, index))) {
-                    PluginKey key = basename + ":" + descriptor->identifier;
-                    if (m_pluginLibraryMap.find(key) ==
-                        m_pluginLibraryMap.end()) {
-                        m_pluginLibraryMap[key] = fullPath;
-                    }
-                    ++index;
-                }
-
-                DLCLOSE(handle);
-            }
-        }
-    }
+    if (m_pluginLibraryNameMap.empty()) generateLibraryMap();
 
     vector<PluginKey> plugins;
     for (map<PluginKey, string>::iterator mi =
-             m_pluginLibraryMap.begin();
-         mi != m_pluginLibraryMap.end(); ++mi) {
+             m_pluginLibraryNameMap.begin();
+         mi != m_pluginLibraryNameMap.end(); ++mi) {
         plugins.push_back(mi->first);
     }
 
     return plugins;
 }
 
+void
+PluginLoader::generateLibraryMap()
+{
+    vector<string> path = PluginHostAdapter::getPluginPath();
+
+    for (size_t i = 0; i < path.size(); ++i) {
+        
+        vector<string> files = listFiles(path[i], PLUGIN_SUFFIX);
+
+        for (vector<string>::iterator fi = files.begin();
+             fi != files.end(); ++fi) {
+            
+            string fullPath = path[i];
+            fullPath = splicePath(fullPath, *fi);
+            void *handle = loadLibrary(fullPath);
+            if (!handle) continue;
+            
+            VampGetPluginDescriptorFunction fn =
+                (VampGetPluginDescriptorFunction)lookupInLibrary
+                (handle, "vampGetPluginDescriptor");
+            
+            if (!fn) {
+                unloadLibrary(handle);
+                continue;
+            }
+            
+            int index = 0;
+            const VampPluginDescriptor *descriptor = 0;
+            
+            while ((descriptor = fn(VAMP_API_VERSION, index))) {
+                PluginKey key = composePluginKey(*fi, descriptor->identifier);
+                if (m_pluginLibraryNameMap.find(key) ==
+                    m_pluginLibraryNameMap.end()) {
+                    m_pluginLibraryNameMap[key] = fullPath;
+                }
+                ++index;
+            }
+            
+            unloadLibrary(handle);
+        }
+    }
+}
+
+PluginLoader::PluginKey
+PluginLoader::composePluginKey(string libraryName, string identifier)
+{
+    string basename = libraryName;
+
+    string::size_type li = basename.rfind('/');
+    if (li != string::npos) basename = basename.substr(li + 1);
+
+    li = basename.find('.');
+    if (li != string::npos) basename = basename.substr(0, li);
+
+    return basename + ":" + identifier;
+}
+
 PluginLoader::PluginCategoryHierarchy
 PluginLoader::getPluginCategory(PluginKey plugin)
 {
@@ -133,13 +165,13 @@
 string
 PluginLoader::getLibraryPathForPlugin(PluginKey plugin)
 {
-    if (m_pluginLibraryMap.empty()) (void)listPlugins();
-    if (m_pluginLibraryMap.find(plugin) == m_pluginLibraryMap.end()) return "";
-    return m_pluginLibraryMap[plugin];
+    if (m_pluginLibraryNameMap.empty()) generateLibraryMap();
+    if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) return "";
+    return m_pluginLibraryNameMap[plugin];
 }    
 
 Plugin *
-PluginLoader::load(PluginKey key, float inputSampleRate)
+PluginLoader::loadPlugin(PluginKey key, float inputSampleRate)
 {
     string fullPath = getLibraryPathForPlugin(key);
     if (fullPath == "") return 0;
@@ -152,23 +184,15 @@
 
     string identifier = key.substr(ki + 1);
     
-    void *handle = DLOPEN(fullPath, RTLD_LAZY);
-
-    if (!handle) {
-        cerr << "Vamp::PluginLoader: " << fullPath
-                  << ": unable to load library (" << DLERROR()
-                  << ")" << endl;
-        return 0;
-    }
+    void *handle = loadLibrary(fullPath);
+    if (!handle) return 0;
     
     VampGetPluginDescriptorFunction fn =
-        (VampGetPluginDescriptorFunction)DLSYM
+        (VampGetPluginDescriptorFunction)lookupInLibrary
         (handle, "vampGetPluginDescriptor");
 
     if (!fn) {
-        //!!! refcount this! --!!! no, POSIX says dlopen/dlclose will
-        // reference count. check on win32
-        DLCLOSE(handle);
+        unloadLibrary(handle);
         return 0;
     }
 
@@ -176,46 +200,29 @@
     const VampPluginDescriptor *descriptor = 0;
 
     while ((descriptor = fn(VAMP_API_VERSION, index))) {
+
         if (string(descriptor->identifier) == identifier) {
-            return new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
+
+            Vamp::PluginHostAdapter *plugin =
+                new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
+
+            PluginDeletionNotifyAdapter *adapter =
+                new PluginDeletionNotifyAdapter(plugin, this);
+
+            m_pluginLibraryHandleMap[adapter] = handle;
+            return adapter;
         }
+
         ++index;
     }
-    
-    //!!! flag error
+
+    cerr << "Vamp::HostExt::PluginLoader: Plugin \""
+         << identifier << "\" not found in library \""
+         << fullPath << "\"" << endl;
+
     return 0;
 }
 
-vector<string>
-PluginLoader::getFilesInDir(string dir, string extension)
-{
-    vector<string> files;
-
-    DIR *d = opendir(dir.c_str());
-    if (!d) return files;
-            
-    struct dirent *e = 0;
-    while ((e = readdir(d))) {
-        
-        if (!(e->d_type & DT_REG) || !e->d_name) {
-            continue;
-        }
-        
-        int len = strlen(e->d_name);
-        if (len < int(extension.length() + 2) ||
-            e->d_name[len - extension.length() - 1] != '.' ||
-            strcmp(e->d_name + len - extension.length(), extension.c_str())) {
-            continue;
-        }
-
-        files.push_back(e->d_name);
-    }
-
-    closedir(d);
-
-    return files;
-}
-
 void
 PluginLoader::generateTaxonomy()
 {
@@ -229,6 +236,11 @@
 
     for (vector<string>::iterator i = path.begin();
          i != path.end(); ++i) {
+
+        // It doesn't matter that we're using literal forward-slash in
+        // this bit, as it's only relevant if the path contains
+        // "/lib/", which is only meaningful and only plausible on
+        // systems with forward-slash delimiters
         
         string dir = *i;
         string::size_type li = dir.find(libfragment);
@@ -248,12 +260,12 @@
     for (vector<string>::iterator i = catpath.begin();
          i != catpath.end(); ++i) {
         
-        vector<string> files = getFilesInDir(*i, suffix);
+        vector<string> files = listFiles(*i, suffix);
 
         for (vector<string>::iterator fi = files.begin();
              fi != files.end(); ++fi) {
 
-            string filepath = *i + "/" + *fi; //!!! systemize
+            string filepath = splicePath(*i, *fi);
             ifstream is(filepath.c_str(), ifstream::in | ifstream::binary);
 
             if (is.fail()) {
@@ -299,5 +311,130 @@
     }
 }    
 
+void *
+PluginLoader::loadLibrary(string path)
+{
+    void *handle = 0;
+#ifdef _WIN32
+    handle = LoadLibrary(path.c_str());
+    if (!handle) {
+        cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
+             << path << "\"" << endl;
+    }
+#else
+    handle = dlopen(path.c_str(), RTLD_LAZY);
+    if (!handle) {
+        cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
+             << path << "\": " << dlerror() << endl;
+    }
+#endif
+    return handle;
+}
+
+void
+PluginLoader::unloadLibrary(void *handle)
+{
+#ifdef _WIN32
+    FreeLibrary((HINSTANCE)handle);
+#else
+    dlclose(handle);
+#endif
+}
+
+void *
+PluginLoader::lookupInLibrary(void *handle, const char *symbol)
+{
+#ifdef _WIN32
+    return (void *)GetProcAddress((HINSTANCE)handle, symbol);
+#else
+    return (void *)dlsym(handle, symbol);
+#endif
+}
+
+string
+PluginLoader::splicePath(string a, string b)
+{
+#ifdef _WIN32
+    return a + "\\" + b;
+#else
+    return a + "/" + b;
+#endif
+}
+
+vector<string>
+PluginLoader::listFiles(string dir, string extension)
+{
+    vector<string> files;
+    size_t extlen = extension.length();
+
+#ifdef _WIN32
+
+    string expression = dir + "\\*." + extension;
+    WIN32_FIND_DATA data;
+    HANDLE fh = FindFirstFile(expression.c_str(), &data);
+    if (fh == INVALID_HANDLE_VALUE) return files;
+
+    bool ok = true;
+    while (ok) {
+        files.push_back(data.cFileName);
+        ok = FindNextFile(fh, &data);
+    }
+
+    FindClose(fh);
+
+#else
+    DIR *d = opendir(dir.c_str());
+    if (!d) return files;
+            
+    struct dirent *e = 0;
+    while ((e = readdir(d))) {
+        
+        if (!(e->d_type & DT_REG) || !e->d_name) continue;
+        
+        size_t len = strlen(e->d_name);
+        if (len < extlen + 2 ||
+            e->d_name + len - extlen - 1 != "." + extension) {
+            continue;
+        }
+
+        files.push_back(e->d_name);
+    }
+
+    closedir(d);
+#endif
+
+    return files;
+}
+
+void
+PluginLoader::pluginDeleted(PluginDeletionNotifyAdapter *adapter)
+{
+    void *handle = m_pluginLibraryHandleMap[adapter];
+    if (handle) unloadLibrary(handle);
+    m_pluginLibraryHandleMap.erase(adapter);
+}
+
+PluginLoader::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin,
+                                                                       PluginLoader *loader) :
+    PluginWrapper(plugin),
+    m_loader(loader)
+{
+}
+
+PluginLoader::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
+{
+    // We need to delete the plugin before calling pluginDeleted, as
+    // the delete call may require calling through to the descriptor
+    // (for e.g. cleanup) but pluginDeleted may unload the required
+    // library for the call.  To prevent a double deletion when our
+    // parent's destructor runs (after this one), be sure to set
+    // m_plugin to 0 after deletion.
+    delete m_plugin;
+    m_plugin = 0;
+
+    if (m_loader) m_loader->pluginDeleted(this);
+}
 
 }
+
+}
--- a/vamp-sdk/hostext/PluginLoader.h	Thu May 24 10:05:00 2007 +0000
+++ b/vamp-sdk/hostext/PluginLoader.h	Thu May 24 15:17:07 2007 +0000
@@ -41,40 +41,67 @@
 #include <string>
 #include <map>
 
+#include "PluginWrapper.h"
+
 namespace Vamp {
 
 class Plugin;
 
+namespace HostExt {
+
 class PluginLoader
 {
 public:
-    PluginLoader();
-    virtual ~PluginLoader();
+    static PluginLoader *getInstance();
 
     typedef std::string PluginKey;
     typedef std::vector<std::string> PluginCategoryHierarchy;
 
     std::vector<PluginKey> listPlugins(); //!!! pass in version number?
 
-    //!!! want to be able to just "delete" the plugin later -- hence
-    //have to consider library loading issues -- do we have a wrapper
-    //class that tells us when it's been deleted, and keep a reference
-    //count for the dynamic library?
-    Plugin *load(PluginKey plugin, float inputSampleRate);
+    PluginKey composePluginKey(std::string libraryName, std::string identifier);
+
+    Plugin *loadPlugin(PluginKey plugin, float inputSampleRate);
 
     PluginCategoryHierarchy getPluginCategory(PluginKey plugin);
 
     std::string getLibraryPathForPlugin(PluginKey plugin);
 
 protected:
-    std::map<PluginKey, std::string> m_pluginLibraryMap;
+    PluginLoader();
+    virtual ~PluginLoader();
+
+    class PluginDeletionNotifyAdapter : public PluginWrapper {
+    public:
+        PluginDeletionNotifyAdapter(Plugin *plugin, PluginLoader *loader);
+        virtual ~PluginDeletionNotifyAdapter();
+    protected:
+        PluginLoader *m_loader;
+    };
+
+    virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter);
+
+    std::map<PluginKey, std::string> m_pluginLibraryNameMap;
+    void generateLibraryMap();
+
     std::map<PluginKey, PluginCategoryHierarchy> m_taxonomy;
     void generateTaxonomy();
-    std::vector<std::string> getFilesInDir(std::string dir, std::string ext);
+
+    std::map<Plugin *, void *> m_pluginLibraryHandleMap;
+
+    void *loadLibrary(std::string path);
+    void unloadLibrary(void *handle);
+    void *lookupInLibrary(void *handle, const char *symbol);
+
+    std::string splicePath(std::string a, std::string b);
+    std::vector<std::string> listFiles(std::string dir, std::string ext);
+
+    static PluginLoader *m_instance;
 };
 
 }
 
+}
 
 #endif
 
--- a/vamp-sdk/hostext/PluginWrapper.cpp	Thu May 24 10:05:00 2007 +0000
+++ b/vamp-sdk/hostext/PluginWrapper.cpp	Thu May 24 15:17:07 2007 +0000
@@ -38,8 +38,11 @@
 
 namespace Vamp {
 
+namespace HostExt {
+
 PluginWrapper::PluginWrapper(Plugin *plugin) :
-    Plugin(0)
+    Plugin(0),
+    m_plugin(plugin)
 {
 }
 
@@ -187,3 +190,5 @@
 
 }
 
+}
+
--- a/vamp-sdk/hostext/PluginWrapper.h	Thu May 24 10:05:00 2007 +0000
+++ b/vamp-sdk/hostext/PluginWrapper.h	Thu May 24 15:17:07 2007 +0000
@@ -34,12 +34,15 @@
     authorization.
 */
 
-#ifndef _PLUGIN_WRAPPER_H_
+#ifndef _VAMP_PLUGIN_WRAPPER_H_
+#define _VAMP_PLUGIN_WRAPPER_H_
 
 #include <vamp-sdk/Plugin.h>
 
 namespace Vamp {
 
+namespace HostExt {
+
 class PluginWrapper : public Plugin
 {
 public:
@@ -85,4 +88,6 @@
 
 }
 
+}
+
 #endif
--- a/vamp-sdk/hostext/system.h	Thu May 24 10:05:00 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Vamp
-
-    An API for audio analysis and feature extraction plugins.
-
-    Centre for Digital Music, Queen Mary, University of London.
-    Copyright 2006 Chris Cannam.
-  
-    Permission is hereby granted, free of charge, to any person
-    obtaining a copy of this software and associated documentation
-    files (the "Software"), to deal in the Software without
-    restriction, including without limitation the rights to use, copy,
-    modify, merge, publish, distribute, sublicense, and/or sell copies
-    of the Software, and to permit persons to whom the Software is
-    furnished to do so, subject to the following conditions:
-
-    The above copyright notice and this permission notice shall be
-    included in all copies or substantial portions of the Software.
-
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
-    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
-    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-    Except as contained in this notice, the names of the Centre for
-    Digital Music; Queen Mary, University of London; and Chris Cannam
-    shall not be used in advertising or otherwise to promote the sale,
-    use or other dealings in this Software without prior written
-    authorization.
-*/
-
-#ifndef _SYSTEM_H_
-#define _SYSTEM_H_
-
-#ifdef _WIN32
-
-#include <windows.h>
-
-#define DLOPEN(a,b)  LoadLibrary((a).c_str())
-#define DLSYM(a,b)   GetProcAddress((HINSTANCE)(a),(b))
-#define DLCLOSE(a)   FreeLibrary((HINSTANCE)(a))
-#define DLERROR()    ""
-
-#define PLUGIN_SUFFIX "dll"
-
-#else
-
-#include <dlfcn.h>
-
-#define DLOPEN(a,b)  dlopen((a).c_str(),(b))
-#define DLSYM(a,b)   dlsym((a),(b))
-#define DLCLOSE(a)   dlclose((a))
-#define DLERROR()    dlerror()
-
-#ifdef __APPLE__
-
-#define PLUGIN_SUFFIX  "dylib"
-#define HAVE_OPENDIR 1
-
-#else 
-
-#define PLUGIN_SUFFIX  "so"
-#define HAVE_OPENDIR 1
-
-#endif /* __APPLE__ */
-
-#endif /* ! _WIN32 */
-
-#endif
-