changeset 1:92911f967a16

* some reorganisation
author Chris Cannam
date Thu, 11 Dec 2008 10:26:12 +0000
parents 581b1b150a4d
children 475f4623feba
files AudioDBFeatureWriter.cpp AudioDBFeatureWriter.h DefaultFeatureWriter.cpp DefaultFeatureWriter.h FeatureExtractionManager.cpp FeatureExtractionManager.h FeatureWriterFactory.cpp FeatureWriterFactory.h deploy_mac.sh main.cpp pull-svn.sh qm-keydetector.n3 runner.pro runner/AudioDBFeatureWriter.cpp runner/AudioDBFeatureWriter.h runner/DefaultFeatureWriter.cpp runner/DefaultFeatureWriter.h runner/FeatureExtractionManager.cpp runner/FeatureExtractionManager.h runner/FeatureWriterFactory.cpp runner/FeatureWriterFactory.h runner/main.cpp runner/runner.pro test-queries/test-query test-queries/test-query-dense-output test-queries/test-query-plugin-for-transform test-queries/test-query-plugin-output-types test-queries/test-query-pluginid-for-rdf test-queries/test-query-transform-params test-queries/test-query-transforms test-queries/test-roqet tests/misc-queries/test-query tests/misc-queries/test-query-dense-output tests/misc-queries/test-query-plugin-for-transform tests/misc-queries/test-query-plugin-output-types tests/misc-queries/test-query-pluginid-for-rdf tests/misc-queries/test-query-transform-params tests/misc-queries/test-query-transforms tests/misc-queries/test-roqet vamp-example-plugins:percussiononsets.n3
diffstat 40 files changed, 2238 insertions(+), 2406 deletions(-) [+]
line wrap: on
line diff
--- a/AudioDBFeatureWriter.cpp	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,214 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Annotator
-    A utility for batch feature extraction from audio files.
-    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
-    Copyright 2007-2008 QMUL.
-
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#include <fstream>
-
-#include <QFileInfo>
-
-#include "AudioDBFeatureWriter.h"
-
-using namespace std;
-using namespace Vamp;
-
-string
-AudioDBFeatureWriter::catalogueIdParam = "catid";
-
-string
-AudioDBFeatureWriter::baseDirParam = "basedir";
-
-struct AudioDBFeatureWriter::TrackStream
-{
-    QString trackid;
-    ofstream* ofs;
-};
-
-AudioDBFeatureWriter::AudioDBFeatureWriter() : 
-    catalogueId("catalog"), baseDir("audiodb")
-{
-    
-}
-
-AudioDBFeatureWriter::~AudioDBFeatureWriter()
-{
-    // close all open files
-    for (map<string, TrackStream>::iterator iter = dbfiles.begin(); iter != dbfiles.end(); ++iter)
-    {
-        if (iter->second.ofs) {
-            iter->second.ofs->close();
-            delete iter->second.ofs;
-        }
-    }
-    
-    // TODO: error handling on close
-}
-
-AudioDBFeatureWriter::ParameterList
-AudioDBFeatureWriter::getSupportedParameters() const
-{
-    ParameterList pl;
-    Parameter p;
-
-    p.name = catalogueIdParam;
-    p.description = "Catalogue ID";
-    p.hasArg = true;
-    pl.push_back(p);
-
-    p.name = baseDirParam;
-    p.description = "Base output directory path";
-    p.hasArg = true;
-    pl.push_back(p);
-
-    return pl;
-}
-
-void
-AudioDBFeatureWriter::setParameters(map<string, string> &params)
-{
-    if (params.find(catalogueIdParam) != params.end()) {
-        setCatalogueId(params[catalogueIdParam]);
-        params.erase(catalogueIdParam);
-    }
-    if (params.find(baseDirParam) != params.end()) {
-        setBaseDirectory(params[baseDirParam]);
-        params.erase(baseDirParam);
-    }
-}
-
-void
-AudioDBFeatureWriter::setCatalogueId(const string &catid)
-{
-    catalogueId = catid;
-}
-
-void
-AudioDBFeatureWriter::setBaseDirectory(const string &base)
-{
-    baseDir = base;
-}
-
-void AudioDBFeatureWriter::write(QString trackid,
-                                 const Transform &transform,
-                                 const Vamp::Plugin::OutputDescriptor& output, 
-                                 const Vamp::Plugin::FeatureList& featureList,
-                                 std::string summaryType)
-{
-    //!!! use summaryType
-    if (summaryType != "") {
-        //!!! IMPLEMENT
-        cerr << "ERROR: AudioDBFeatureWriter::write: Writing summaries is not yet implemented!" << endl;
-        exit(1);
-    }
-
-
-    // binary output for FeatureSet
-    
-    // feature-dimension feature-1 feature-2 ...
-    // timestamp-1 timestamp-2 ...
-    
-    // audioDB has to write each feature to a different file
-    // assume a simple naming convention of
-    // <catalog-id>/<track-id>.<feature-id>
-    // with timestamps in a corresponding <catalog-id>/<track-id>.<feature-id>.timestamp file
-    // (start and end times in seconds for each frame -- somewhat optional)
-    
-    // the feature writer holds a map of open file descriptors
-    // the catalog-id is passed in to the feature writer's constructor    
-    
-    // NB -- all "floats" in the file should in fact be doubles
-
-    // TODO:
-    // - write feature end rather than start times, once end time is available in vamp
-    // - write a power file, probably by wrapping plugin in a PluginPowerAdapter :)
-    
-    if (output.binCount == 0)    // this kind of feature just outputs timestamps and labels, assume of no interest to audioDB
-        return;    
-        
-    for (int i = 0; i < featureList.size(); ++i)
-    {
-        // replace output files if necessary
-        if (replaceDBFile(trackid, output.identifier))
-        {                
-            // write the feature length for the next track feature record
-            // binCount has to be set
-            // - it can be zero, i.e. if the output is really a set of labels + timestamps
-            *dbfiles[output.identifier].ofs /*<< ios::binary*/ << output.binCount;
-            
-            cerr << "writing bin count " << output.binCount << " for " << output.identifier << endl;
-        }
-        
-        if (replaceDBFile(trackid, output.identifier + ".timestamp"))
-        {
-            // write the start time to the timestamp file
-            // as we want it for the first feature in the file
-            *dbfiles[output.identifier + ".timestamp"].ofs << featureList[i].timestamp.toString() << endl;
-        }
-
-        if (dbfiles[output.identifier].ofs) {
-            for (int j = 0; j < featureList[i].values.size(); ++j)
-                *dbfiles[output.identifier].ofs /*<< ios::binary*/ << featureList[i].values[j];
-        
-            // write the *end* time of each feature to the timestamp file
-            // NOT IMPLEMENTED YET
-//            *dbfiles[output.identifier + ".timestamp"].ofs << featureList[i].timestamp.toString() << endl;
-        }
-    }
-}
-
-bool AudioDBFeatureWriter::openDBFile(QString trackid, const string& identifier)
-{
-    QString trackBase = QFileInfo(trackid).fileName();
-    string filepath = baseDir + "/" + catalogueId  + "/"
-        + trackBase.toStdString() + "." + identifier;
-    cerr << "AudioDBFeatureWriter::openDBFile: filepath is \"" << filepath << "\"" << endl;
-    ofstream* ofs = new ofstream(filepath.c_str());
-    if (!*ofs)
-    {    
-        cerr << "ERROR AudioDBFeatureWriter::openDBFile(): can't open file " << filepath << endl;
-        return false;
-    }
-    TrackStream ts;
-    ts.trackid = trackid;
-    ts.ofs = ofs;
-    dbfiles[identifier] = ts;
-    return true;
-}
-
-// replace file if no file open for this track, else return false
-bool AudioDBFeatureWriter::replaceDBFile(QString trackid, 
-                                         const string& identifier)
-{
-    if (dbfiles.find(identifier) != dbfiles.end() && dbfiles[identifier].trackid == trackid)
-        return false;    // have an open file for this track
-    
-    if (dbfiles.find(identifier) != dbfiles.end() && dbfiles[identifier].trackid != trackid)
-    {
-        // close the current file
-        if (dbfiles[identifier].ofs) {
-            dbfiles[identifier].ofs->close();
-            delete dbfiles[identifier].ofs;
-            dbfiles[identifier].ofs = 0;
-        }
-    }
-    
-    // open a new file
-    if (!openDBFile(trackid, identifier)) {
-        dbfiles[identifier].ofs = 0;
-        return false; //!!! should throw an exception, otherwise we'll try to open the file again and again every time we want to write to it
-    }
-    
-    return true;
-}
-    
-    
--- a/AudioDBFeatureWriter.h	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Annotator
-    A utility for batch feature extraction from audio files.
-    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
-    Copyright 2007-2008 QMUL.
-
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#ifndef _AUDIO_DB_FEATURE_WRITER_H_
-#define _AUDIO_DB_FEATURE_WRITER_H_
-
-#include <string>
-#include <map>
-
-using std::string;
-using std::map;
-
-#include "transform/FeatureWriter.h"
-
-class AudioDBFeatureWriter : public FeatureWriter
-{
-public:
-    AudioDBFeatureWriter();
-    virtual ~AudioDBFeatureWriter();
-
-    virtual ParameterList getSupportedParameters() const;
-    virtual void setParameters(map<string, string> &params);
-
-    virtual void setCatalogueId(const string &);
-    virtual void setBaseDirectory(const string &);
-
-    virtual void write(QString trackid,
-                       const Transform &transform,
-                       const Vamp::Plugin::OutputDescriptor &output,
-                       const Vamp::Plugin::FeatureList &features,
-                       std::string summaryType = "");
-    
-    virtual void finish() { }
-
-private:
-    string catalogueId;
-    string baseDir;
-
-    static string catalogueIdParam;
-    static string baseDirParam;
-    
-    struct TrackStream;
-    map<string, TrackStream> dbfiles;
-    
-    bool openDBFile(QString trackid, const string& identifier);
-    bool replaceDBFile(QString trackid, const string& identifier);
-};
-
-#endif
--- a/DefaultFeatureWriter.cpp	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Annotator
-    A utility for batch feature extraction from audio files.
-    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
-    Copyright 2007-2008 QMUL.
-
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#include <iostream>
-#include <map>
-
-using namespace std;
-
-#include "DefaultFeatureWriter.h"
-
-void DefaultFeatureWriter::write(QString trackid,
-                                 const Transform &transform,
-                                 const Vamp::Plugin::OutputDescriptor& output,
-                                 const Vamp::Plugin::FeatureList& featureList,
-                                 std::string summaryType)
-{
-    // generic XML output
-    
-    /*
-     
-     <feature>
-        <name>output.name</name>
-        <timestamp>feature.timestamp</timestamp>    
-        <values>output.binName[0]:feature.value[0]...</values>
-        <label>feature.label</label>
-     </feature>
-     
-     */
-    
-    for (int i = 0; i < featureList.size(); ++i)
-    {
-        if (summaryType == "") {
-            cout << "<feature>" << endl;
-        } else {
-            cout << "<summary type=\"" << summaryType << "\">" << endl;
-        }
-        cout << "\t<name>" << output.name << "</name>" << endl;
-        if (featureList[i].hasTimestamp) {
-            cout << "\t<timestamp>" << featureList[i].timestamp << "</timestamp>" << endl;    
-        }
-        if (featureList[i].hasDuration) {
-            cout << "\t<duration>" << featureList[i].duration << "</duration>" << endl;    
-        }
-        if (featureList[i].values.size() > 0)
-        {
-            cout << "\t<values>";
-            for (int j = 0; j < featureList[i].values.size(); ++j)
-            {
-                if (j > 0)
-                    cout << " ";
-                if (output.binNames.size() > 0)
-                    cout << output.binNames[j] << ":";
-                cout << featureList[i].values[j];
-            }
-            cout << "</values>" << endl;
-        }
-        if (featureList[i].label.length() > 0)
-            cout << "\t<label>" << featureList[i].label << "</label>" << endl;            
-        if (summaryType == "") {
-            cout << "</feature>" << endl;
-        } else {
-            cout << "</summary>" << endl;
-        }
-    }
-}
--- a/DefaultFeatureWriter.h	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Annotator
-    A utility for batch feature extraction from audio files.
-    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
-    Copyright 2007-2008 QMUL.
-
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#ifndef _DEFAULT_FEATURE_WRITER_H_
-#define _DEFAULT_FEATURE_WRITER_H_
-
-
-#include "transform/FeatureWriter.h"
-
-class DefaultFeatureWriter : public FeatureWriter
-{
-public:
-    virtual ~DefaultFeatureWriter() { }
-    virtual void write(QString trackid,
-                       const Transform &transform,
-                       const Vamp::Plugin::OutputDescriptor &output,
-                       const Vamp::Plugin::FeatureList &features,
-                       std::string summaryType = "");
-    virtual void finish() { }
-};
-
-#endif
--- a/FeatureExtractionManager.cpp	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,702 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Annotator
-    A utility for batch feature extraction from audio files.
-    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
-    Copyright 2007-2008 QMUL.
-
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#include "FeatureExtractionManager.h"
-
-#include <vamp-hostsdk/PluginChannelAdapter.h>
-#include <vamp-hostsdk/PluginBufferingAdapter.h>
-#include <vamp-hostsdk/PluginInputDomainAdapter.h>
-#include <vamp-hostsdk/PluginSummarisingAdapter.h>
-#include <vamp-hostsdk/PluginLoader.h>
-
-#include <iostream>
-
-using namespace std;
-
-using Vamp::Plugin;
-using Vamp::PluginBase;
-using Vamp::HostExt::PluginLoader;
-using Vamp::HostExt::PluginChannelAdapter;
-using Vamp::HostExt::PluginBufferingAdapter;
-using Vamp::HostExt::PluginInputDomainAdapter;
-using Vamp::HostExt::PluginSummarisingAdapter;
-
-#include "data/fileio/FileSource.h"
-#include "data/fileio/AudioFileReader.h"
-#include "data/fileio/AudioFileReaderFactory.h"
-#include "data/fileio/PlaylistFileReader.h"
-#include "base/TempDirectory.h"
-#include "base/ProgressPrinter.h"
-#include "transform/TransformFactory.h"
-#include "rdf/RDFTransformFactory.h"
-#include "transform/FeatureWriter.h"
-
-#include <QTextStream>
-#include <QFile>
-#include <QFileInfo>
-
-FeatureExtractionManager::FeatureExtractionManager() :
-    m_summariesOnly(false),
-    // We can read using an arbitrary fixed block size --
-    // PluginBufferingAdapter handles this for us.  It's likely to be
-    // quicker to use larger sizes than smallish ones like 1024
-    m_blockSize(16384),
-    m_defaultSampleRate(0),
-    m_sampleRate(0),
-    m_channels(1)
-{
-}
-
-FeatureExtractionManager::~FeatureExtractionManager()
-{
-    for (PluginMap::iterator pi = m_plugins.begin();
-         pi != m_plugins.end(); ++pi) {
-        delete pi->first;
-    }
-}
-
-void FeatureExtractionManager::setChannels(int channels)
-{
-    m_channels = channels;
-}
-
-void FeatureExtractionManager::setDefaultSampleRate(int sampleRate)
-{
-    m_defaultSampleRate = sampleRate;
-}
-
-static PluginSummarisingAdapter::SummaryType
-getSummaryType(string name)
-{
-    if (name == "min")      return PluginSummarisingAdapter::Minimum;
-    if (name == "max")      return PluginSummarisingAdapter::Maximum;
-    if (name == "mean")     return PluginSummarisingAdapter::Mean;
-    if (name == "median")   return PluginSummarisingAdapter::Median;
-    if (name == "mode")     return PluginSummarisingAdapter::Mode;
-    if (name == "sum")      return PluginSummarisingAdapter::Sum;
-    if (name == "variance") return PluginSummarisingAdapter::Variance;
-    if (name == "sd")       return PluginSummarisingAdapter::StandardDeviation;
-    if (name == "count")    return PluginSummarisingAdapter::Count;
-    return PluginSummarisingAdapter::UnknownSummaryType;
-}
-
-bool FeatureExtractionManager::setSummaryTypes(const set<string> &names,
-                                               bool summariesOnly,
-                                               const PluginSummarisingAdapter::SegmentBoundaries &boundaries)
-{
-    for (SummaryNameSet::const_iterator i = names.begin();
-         i != names.end(); ++i) {
-        if (getSummaryType(*i) == PluginSummarisingAdapter::UnknownSummaryType) {
-            cerr << "ERROR: Unknown summary type \"" << *i << "\"" << endl;
-            return false;
-        }
-    }
-    m_summaries = names;
-    m_summariesOnly = summariesOnly;
-    m_boundaries = boundaries;
-    return true;
-}
-
-bool FeatureExtractionManager::addFeatureExtractor
-(Transform transform, const vector<FeatureWriter*> &writers)
-{
-    //!!! exceptions rather than return values?
-
-    if (transform.getSampleRate() == 0) {
-        if (m_sampleRate == 0) {
-            cerr << "NOTE: Transform does not specify a sample rate, using default rate of " << m_defaultSampleRate << endl;
-            transform.setSampleRate(m_defaultSampleRate);
-            m_sampleRate = m_defaultSampleRate;
-        } else {
-            cerr << "NOTE: Transform does not specify a sample rate, using previous transform's rate of " << m_sampleRate << endl;
-            transform.setSampleRate(m_sampleRate);
-        }
-    }
-
-    if (m_sampleRate == 0) {
-        m_sampleRate = transform.getSampleRate();
-    }
-
-    if (transform.getSampleRate() != m_sampleRate) {
-        cerr << "WARNING: Transform sample rate " << transform.getSampleRate() << " does not match previously specified transform rate of " << m_sampleRate << " -- only a single rate is supported for each run" << endl;
-        cerr << "WARNING: Using previous rate of " << m_sampleRate << " for this transform as well" << endl;
-        transform.setSampleRate(m_sampleRate);
-    }
-
-    Plugin *plugin = 0;
-
-    // Remember what the original transform looked like, and index
-    // based on this -- because we may be about to fill in the zeros
-    // for step and block size, but we want any further copies with
-    // the same zeros to match this one
-    Transform originalTransform = transform;
-    
-    if (m_transformPluginMap.find(transform) == m_transformPluginMap.end()) {
-
-        // Test whether we already have a transform that is identical
-        // to this, except for the output requested and/or the summary
-        // type -- if so, they should share plugin instances (a vital
-        // optimisation)
-
-        for (TransformPluginMap::iterator i = m_transformPluginMap.begin();
-             i != m_transformPluginMap.end(); ++i) {
-            Transform test = i->first;
-            test.setOutput(transform.getOutput());
-            test.setSummaryType(transform.getSummaryType());
-            if (transform == test) {
-                cerr << "NOTE: Already have transform identical to this one (for \""
-                     << transform.getIdentifier().toStdString()
-                     << "\") in every detail except output identifier and/or "
-                     << "summary type; sharing its plugin instance" << endl;
-                plugin = i->second;
-                if (transform.getSummaryType() != Transform::NoSummary &&
-                    !dynamic_cast<PluginSummarisingAdapter *>(plugin)) {
-                    plugin = new PluginSummarisingAdapter(plugin);
-                    i->second = plugin;
-                }
-                break;
-            }
-        }
-
-        if (!plugin) {
-
-            TransformFactory *tf = TransformFactory::getInstance();
-
-            PluginBase *pb = tf->instantiatePluginFor(transform);
-            plugin = tf->downcastVampPlugin(pb);
-            if (!plugin) {
-                //!!! todo: handle non-Vamp plugins too, or make the main --list
-                // option print out only Vamp transforms
-                cerr << "ERROR: Failed to load plugin for transform \""
-                     << transform.getIdentifier().toStdString() << "\"" << endl;
-                delete pb;
-                return false;
-            }
-            
-            // We will provide the plugin with arbitrary step and
-            // block sizes (so that we can use the same read/write
-            // block size for all transforms), and to that end we use
-            // a PluginBufferingAdapter.  However, we need to know the
-            // underlying step size so that we can provide the right
-            // context for dense outputs.  (Although, don't forget
-            // that the PluginBufferingAdapter rewrites
-            // OneSamplePerStep outputs so as to use FixedSampleRate
-            // -- so it supplies the sample rate in the output
-            // feature.  I'm not sure whether we can easily use that.)
-
-            size_t pluginStepSize = plugin->getPreferredStepSize();
-            size_t pluginBlockSize = plugin->getPreferredBlockSize();
-
-            // adapt the plugin for buffering, channels, etc.
-            if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
-                plugin = new PluginInputDomainAdapter(plugin);
-            }
-
-            PluginBufferingAdapter *pba = new PluginBufferingAdapter(plugin);
-            plugin = pba;
-
-            if (transform.getStepSize() != 0) {
-                pba->setPluginStepSize(transform.getStepSize());
-            } else {
-                transform.setStepSize(pluginStepSize);
-            }
-
-            if (transform.getBlockSize() != 0) {
-                pba->setPluginBlockSize(transform.getBlockSize());
-            } else {
-                transform.setBlockSize(pluginBlockSize);
-            }
-
-            plugin = new PluginChannelAdapter(plugin);
-
-            if (!m_summaries.empty() ||
-                transform.getSummaryType() != Transform::NoSummary) {
-                PluginSummarisingAdapter *adapter =
-                    new PluginSummarisingAdapter(plugin);
-                adapter->setSummarySegmentBoundaries(m_boundaries);
-                plugin = adapter;
-            }
-
-            if (!plugin->initialise(m_channels, m_blockSize, m_blockSize)) {
-                cerr << "ERROR: Plugin initialise (channels = " << m_channels << ", stepSize = " << m_blockSize << ", blockSize = " << m_blockSize << ") failed." << endl;    
-                delete plugin;
-                return false;
-            }
-
-//            cerr << "Initialised plugin" << endl;
-
-            size_t actualStepSize = 0;
-            size_t actualBlockSize = 0;
-            pba->getActualStepAndBlockSizes(actualStepSize, actualBlockSize);
-            transform.setStepSize(actualStepSize);
-            transform.setBlockSize(actualBlockSize);
-
-            Plugin::OutputList outputs = plugin->getOutputDescriptors();
-            for (int i = 0; i < (int)outputs.size(); ++i) {
-
-//                cerr << "Newly initialised plugin output " << i << " has bin count " << outputs[i].binCount << endl;
-
-                m_pluginOutputs[plugin][outputs[i].identifier] = outputs[i];
-                m_pluginOutputIndices[outputs[i].identifier] = i;
-            }
-
-            cerr << "NOTE: Loaded and initialised plugin " << plugin
-                 << " for transform \""
-                 << transform.getIdentifier().toStdString() << "\"" << endl;
-        }
-
-        if (transform.getOutput() == "") {
-            transform.setOutput
-                (plugin->getOutputDescriptors()[0].identifier.c_str());
-        }
-
-        m_transformPluginMap[transform] = plugin;
-
-        if (!(originalTransform == transform)) {
-            m_transformPluginMap[originalTransform] = plugin;
-        }
-
-    } else {
-        
-        plugin = m_transformPluginMap[transform];
-    }
-
-    m_plugins[plugin][transform] = writers;
-
-    return true;
-}
-
-bool FeatureExtractionManager::addDefaultFeatureExtractor
-(TransformId transformId, const vector<FeatureWriter*> &writers)
-{
-    TransformFactory *tf = TransformFactory::getInstance();
-
-    if (m_sampleRate == 0) {
-        if (m_defaultSampleRate == 0) {
-            cerr << "ERROR: Default transform requested, but no default sample rate available" << endl;
-            return false;
-        } else {
-            cerr << "NOTE: Using default sample rate of " << m_defaultSampleRate << " for default transform" << endl;
-            m_sampleRate = m_defaultSampleRate;
-        }
-    }
-
-    Transform transform = tf->getDefaultTransformFor(transformId, m_sampleRate);
-
-    return addFeatureExtractor(transform, writers);
-}
-
-bool FeatureExtractionManager::addFeatureExtractorFromFile
-(QString transformXmlFile, const vector<FeatureWriter*> &writers)
-{
-    RDFTransformFactory factory
-        (QUrl::fromLocalFile(QFileInfo(transformXmlFile).absoluteFilePath())
-         .toString());
-    ProgressPrinter printer("Parsing transforms RDF file");
-    std::vector<Transform> transforms = factory.getTransforms(&printer);
-    if (!factory.isOK()) {
-        cerr << "WARNING: FeatureExtractionManager::addFeatureExtractorFromFile: Failed to parse transforms file: " << factory.getErrorString().toStdString() << endl;
-        if (factory.isRDF()) {
-            return false; // no point trying it as XML
-        }
-    }
-    if (!transforms.empty()) {
-        bool success = true;
-        for (int i = 0; i < (int)transforms.size(); ++i) {
-            if (!addFeatureExtractor(transforms[i], writers)) {
-                success = false;
-            }
-        }
-        return success;
-    }
-
-    QFile file(transformXmlFile);
-    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
-        cerr << "ERROR: Failed to open transform XML file \""
-             << transformXmlFile.toStdString() << "\" for reading" << endl;
-        return false;
-    }
-
-    QTextStream *qts = new QTextStream(&file);
-    QString qs = qts->readAll();
-    delete qts;
-    file.close();
-
-    Transform transform(qs);
-
-    return addFeatureExtractor(transform, writers);
-}
-
-void FeatureExtractionManager::extractFeatures(QString audioSource)
-{
-    if (m_plugins.empty()) return;
-
-    ProgressPrinter printer("Retrieving audio data...");
-
-    FileSource source(audioSource, &printer);
-    if (!source.isAvailable()) {
-        cerr << "ERROR: File or URL \"" << audioSource.toStdString()
-             << "\" could not be located" << endl;
-        exit(1);
-    }
-    
-    source.waitForData();
-    
-    if (QFileInfo(audioSource).suffix().toLower() == "m3u") {
-        PlaylistFileReader reader(source);
-        if (reader.isOK()) {
-            vector<QString> files = reader.load();
-            for (int i = 0; i < (int)files.size(); ++i) {
-                extractFeatures(files[i]);
-            }
-            return;
-        } else {
-            cerr << "ERROR: Playlist \"" << audioSource.toStdString()
-                 << "\" could not be opened" << endl;
-            exit(1);
-        }
-    }
-
-    if (m_sampleRate == 0) {
-        cerr << "ERROR: Internal error in FeatureExtractionManager::extractFeatures: Plugin list is non-empty, but no sample rate set" << endl;
-        exit(1);
-    }
-
-    AudioFileReader *reader =
-        AudioFileReaderFactory::createReader(source, m_sampleRate, &printer);
-    
-    if (!reader) {
-        cerr << "ERROR: File or URL \"" << audioSource.toStdString()
-             << "\" could not be opened" << endl;
-        exit(1);
-    }
-
-    size_t channels = reader->getChannelCount();
-    
-    cerr << "Opened " << channels << "-channel file or URL \"" << audioSource.toStdString() << "\"" << endl;
-
-    // reject file if it has too few channels, plugin will handle if it has too many
-    if ((int)channels < m_channels) {
-        //!!! should not be terminating here!
-        cerr << "ERROR: File or URL \"" << audioSource.toStdString() << "\" has less than " << m_channels << " channels" << endl;
-        exit(1);
-    }
-    
-    // allocate audio buffers
-    float **data = new float *[m_channels];
-    for (int c = 0; c < m_channels; ++c) {
-        data[c] = new float[m_blockSize];
-    }
-
-    size_t frameCount = reader->getFrameCount();
-    
-//    cerr << "file has " << frameCount << " frames" << endl;
-
-    for (PluginMap::iterator pi = m_plugins.begin();
-         pi != m_plugins.end(); ++pi) {
-
-        Plugin *plugin = pi->first;
-
-//        std::cerr << "Calling reset on " << plugin << std::endl;
-        plugin->reset();
-
-        for (TransformWriterMap::iterator ti = pi->second.begin();
-             ti != pi->second.end(); ++ti) {
-
-            const Transform &transform = ti->first;
-
-            //!!! we may want to set the start and duration times for extraction
-            // in the transform record (defaults of zero indicate extraction
-            // from the whole file)
-//            transform.setStartTime(RealTime::zeroTime);
-//            transform.setDuration
-//                (RealTime::frame2RealTime(reader->getFrameCount(), m_sampleRate));
-
-            string outputId = transform.getOutput().toStdString();
-            if (m_pluginOutputs[plugin].find(outputId) ==
-                m_pluginOutputs[plugin].end()) {
-                //!!! throw?
-                cerr << "WARNING: Nonexistent plugin output \"" << outputId << "\" requested for transform \""
-                     << transform.getIdentifier().toStdString() << "\", ignoring this transform"
-                     << endl;
-/*
-                cerr << "Known outputs for all plugins are as follows:" << endl;
-                for (PluginOutputMap::const_iterator k = m_pluginOutputs.begin();
-                     k != m_pluginOutputs.end(); ++k) {
-                    cerr << "Plugin " << k->first << ": ";
-                    if (k->second.empty()) {
-                        cerr << "(none)";
-                    }
-                    for (OutputMap::const_iterator i = k->second.begin();
-                         i != k->second.end(); ++i) {
-                        cerr << "\"" << i->first << "\" ";
-                    }
-                    cerr << endl;
-                }
-*/
-            }
-        }
-    }
-    
-    long startFrame = 0;
-    long endFrame = frameCount;
-
-/*!!! No -- there is no single transform to pull this stuff from --
- * the transforms may have various start and end times, need to be far
- * cleverer about this if we're going to support them
-
-    RealTime trStartRT = transform.getStartTime();
-    RealTime trDurationRT = transform.getDuration();
-
-    long trStart = RealTime::realTime2Frame(trStartRT, m_sampleRate);
-    long trDuration = RealTime::realTime2Frame(trDurationRT, m_sampleRate);
-
-    if (trStart == 0 || trStart < startFrame) {
-        trStart = startFrame;
-    }
-
-    if (trDuration == 0) {
-        trDuration = endFrame - trStart;
-    }
-    if (trStart + trDuration > endFrame) {
-        trDuration = endFrame - trStart;
-    }
-
-    startFrame = trStart;
-    endFrame = trStart + trDuration;
-*/
-    
-    for (PluginMap::iterator pi = m_plugins.begin();
-         pi != m_plugins.end(); ++pi) { 
-
-        for (TransformWriterMap::const_iterator ti = pi->second.begin();
-             ti != pi->second.end(); ++ti) {
-        
-            const vector<FeatureWriter *> &writers = ti->second;
-            
-            for (int j = 0; j < (int)writers.size(); ++j) {
-                FeatureWriter::TrackMetadata m;
-                m.title = reader->getTitle();
-                m.maker = reader->getMaker();
-                writers[j]->setTrackMetadata(audioSource, m);
-            }
-        }
-    }
-
-    ProgressPrinter extractionProgress("Extracting and writing features...");
-    int progress = 0;
-
-    for (long i = startFrame; i < endFrame; i += m_blockSize) {
-        
-        //!!! inefficient, although much of the inefficiency may be
-        // susceptible to optimisation
-        
-        SampleBlock frames;
-        reader->getInterleavedFrames(i, m_blockSize, frames);
-        
-        // We have to do our own channel handling here; we can't just
-        // leave it to the plugin adapter because the same plugin
-        // adapter may have to serve for input files with various
-        // numbers of channels (so the adapter is simply configured
-        // with a fixed channel count, generally 1).
-
-        int rc = reader->getChannelCount();
-
-        for (int j = 0; j < m_blockSize; ++j) {
-            for (int c = 0; c < m_channels; ++c) {
-                int index;
-                if (c < rc) {
-                    index = j * rc + c;
-                    data[c][j] = 0.f;
-                } else {
-                    index = j * rc + (c % rc);
-                }
-                if (index < (int)frames.size()) {
-                    data[c][j] += frames[index];
-                }
-            }
-        }    
-
-        Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime
-            (i, m_sampleRate);
-        
-        for (PluginMap::iterator pi = m_plugins.begin();
-             pi != m_plugins.end(); ++pi) {
-
-            Plugin *plugin = pi->first;
-            Plugin::FeatureSet featureSet = plugin->process(data, timestamp);
-
-            if (!m_summariesOnly) {
-                writeFeatures(audioSource, plugin, featureSet);
-            }
-        }
-
-        int pp = progress;
-        progress = ((i - startFrame) * 100) / (endFrame - startFrame);
-        if (progress > pp) extractionProgress.setProgress(progress);
-    }
-    
-    for (PluginMap::iterator pi = m_plugins.begin();
-         pi != m_plugins.end(); ++pi) { 
-
-        Plugin *plugin = pi->first;
-        Plugin::FeatureSet featureSet = plugin->getRemainingFeatures();
-
-        if (!m_summariesOnly) {
-            writeFeatures(audioSource, plugin, featureSet);
-        }
-
-        if (!m_summaries.empty()) {
-            PluginSummarisingAdapter *adapter =
-                dynamic_cast<PluginSummarisingAdapter *>(plugin);
-            if (!adapter) {
-                cerr << "WARNING: Summaries requested, but plugin is not a summarising adapter" << endl;
-            } else {
-                for (SummaryNameSet::const_iterator sni = m_summaries.begin();
-                     sni != m_summaries.end(); ++sni) {
-                    featureSet.clear();
-                    //!!! problem here -- we are requesting summaries
-                    //!!! for all outputs, but they in principle have
-                    //!!! different averaging requirements depending
-                    //!!! on whether their features have duration or
-                    //!!! not
-                    featureSet = adapter->getSummaryForAllOutputs
-                        (getSummaryType(*sni),
-                         PluginSummarisingAdapter::ContinuousTimeAverage);
-                    writeFeatures(audioSource, plugin, featureSet,//!!! *sni);
-                                  Transform::stringToSummaryType(sni->c_str()));
-                }
-            }
-        }
-
-        writeSummaries(audioSource, plugin);
-    }
-
-    finish();
-    
-    extractionProgress.setProgress(100);
-
-    TempDirectory::getInstance()->cleanup();
-}
-
-void
-FeatureExtractionManager::writeSummaries(QString audioSource, Plugin *plugin)
-{
-    // caller should have ensured plugin is in m_plugins
-    PluginMap::iterator pi = m_plugins.find(plugin);
-
-    for (TransformWriterMap::const_iterator ti = pi->second.begin();
-         ti != pi->second.end(); ++ti) {
-        
-        const Transform &transform = ti->first;
-        const vector<FeatureWriter *> &writers = ti->second;
-
-        Transform::SummaryType summaryType = transform.getSummaryType();
-        PluginSummarisingAdapter::SummaryType pType =
-            (PluginSummarisingAdapter::SummaryType)summaryType;
-
-        if (transform.getSummaryType() == Transform::NoSummary) {
-            continue;
-        }
-
-        PluginSummarisingAdapter *adapter =
-            dynamic_cast<PluginSummarisingAdapter *>(plugin);
-        if (!adapter) {
-            cerr << "FeatureExtractionManager::writeSummaries: INTERNAL ERROR: Summary requested for transform, but plugin is not a summarising adapter" << endl;
-            continue;
-        }
-
-        Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs
-            (pType, PluginSummarisingAdapter::ContinuousTimeAverage);
-
-//        cout << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl;
-
-        writeFeatures(audioSource, plugin, featureSet, summaryType);
-    }
-}
-
-void FeatureExtractionManager::writeFeatures(QString audioSource,
-                                             Plugin *plugin,
-                                             const Plugin::FeatureSet &features,
-                                             Transform::SummaryType summaryType)
-{
-    // caller should have ensured plugin is in m_plugins
-    PluginMap::iterator pi = m_plugins.find(plugin);
-
-    for (TransformWriterMap::const_iterator ti = pi->second.begin();
-         ti != pi->second.end(); ++ti) {
-        
-        const Transform &transform = ti->first;
-        const vector<FeatureWriter *> &writers = ti->second;
-        
-        if (transform.getSummaryType() != Transform::NoSummary &&
-            m_summaries.empty() &&
-            summaryType == Transform::NoSummary) {
-            continue;
-        }
-
-        if (transform.getSummaryType() != Transform::NoSummary &&
-            summaryType != Transform::NoSummary &&
-            transform.getSummaryType() != summaryType) {
-            continue;
-        }
-
-        string outputId = transform.getOutput().toStdString();
-
-        if (m_pluginOutputs[plugin].find(outputId) ==
-            m_pluginOutputs[plugin].end()) {
-            continue;
-        }
-        
-        const Plugin::OutputDescriptor &desc =
-            m_pluginOutputs[plugin][outputId];
-        
-        int outputIndex = m_pluginOutputIndices[outputId];
-        Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex);
-        if (fsi == features.end()) continue;
-
-        for (int j = 0; j < (int)writers.size(); ++j) {
-            writers[j]->write
-                (audioSource, transform, desc, fsi->second,
-                 Transform::summaryTypeToString(summaryType).toStdString());
-        }
-    }
-}
-
-void FeatureExtractionManager::finish()
-{
-    for (PluginMap::iterator pi = m_plugins.begin();
-         pi != m_plugins.end(); ++pi) {
-
-        for (TransformWriterMap::iterator ti = pi->second.begin();
-             ti != pi->second.end(); ++ti) {
-        
-            vector<FeatureWriter *> &writers = ti->second;
-
-            for (int i = 0; i < (int)writers.size(); ++i) {
-                writers[i]->flush();
-                writers[i]->finish();
-            }
-        }
-    }
-}
-
-void FeatureExtractionManager::print(Transform transform) const
-{
-    QString qs;
-    QTextStream qts(&qs);
-    transform.toXml(qts);
-    cerr << qs.toStdString() << endl;
-}
--- a/FeatureExtractionManager.h	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Annotator
-    A utility for batch feature extraction from audio files.
-    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
-    Copyright 2007-2008 QMUL.
-
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#ifndef _FEATURE_EXTRACTION_MANAGER_H_
-#define _FEATURE_EXTRACTION_MANAGER_H_
-
-#include <vector>
-#include <set>
-#include <string>
-
-#include <vamp-hostsdk/Plugin.h>
-#include <vamp-hostsdk/PluginSummarisingAdapter.h>
-#include <transform/Transform.h>
-
-using std::vector;
-using std::set;
-using std::string;
-using std::pair;
-using std::map;
-
-class FeatureWriter;
-
-class FeatureExtractionManager
-{
-public:
-    FeatureExtractionManager();
-    virtual ~FeatureExtractionManager();
-
-    void setChannels(int channels);
-    void setDefaultSampleRate(int sampleRate);
-
-    bool setSummaryTypes(const set<string> &summaryTypes,
-                         bool summariesOnly,
-                         const Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries &boundaries);
-
-    bool addFeatureExtractor(Transform transform,
-                             const vector<FeatureWriter*> &writers);
-
-    bool addFeatureExtractorFromFile(QString transformXmlFile,
-                                     const vector<FeatureWriter*> &writers);
-
-    bool addDefaultFeatureExtractor(TransformId transformId,
-                                    const vector<FeatureWriter*> &writers);
-
-    void extractFeatures(QString audioSource);
-
-private:
-    // A plugin may have many outputs, so we can have more than one
-    // transform requested for a single plugin.  The things we want to
-    // run in our process loop are plugins rather than their outputs,
-    // so we maintain a map from the plugins to the transforms desired
-    // of them and then iterate through this map
-
-    typedef map<Transform, vector<FeatureWriter *> > TransformWriterMap;
-    typedef map<Vamp::Plugin *, TransformWriterMap> PluginMap;
-    PluginMap m_plugins;
-        
-    // And a map back from transforms to their plugins.  Note that
-    // this is keyed by transform, not transform ID -- two differently
-    // configured transforms with the same ID must use different
-    // plugin instances.
-
-    typedef map<Transform, Vamp::Plugin *> TransformPluginMap;
-    TransformPluginMap m_transformPluginMap;
-
-    // Cache the plugin output descriptors, mapping from plugin to a
-    // map from output ID to output descriptor.
-    typedef map<string, Vamp::Plugin::OutputDescriptor> OutputMap;
-    typedef map<Vamp::Plugin *, OutputMap> PluginOutputMap;
-    PluginOutputMap m_pluginOutputs;
-
-    // Map from plugin output identifier to plugin output index
-    typedef map<string, int> OutputIndexMap;
-    OutputIndexMap m_pluginOutputIndices;
-
-    typedef set<std::string> SummaryNameSet;
-    SummaryNameSet m_summaries;
-    bool m_summariesOnly;
-    Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries m_boundaries;
-
-    void writeSummaries(QString audioSource, Vamp::Plugin *);
-
-    void writeFeatures(QString audioSource,
-                       Vamp::Plugin *,
-                       const Vamp::Plugin::FeatureSet &,
-                       Transform::SummaryType summaryType =
-                       Transform::NoSummary);
-    void finish();
-
-    int m_blockSize;
-    int m_defaultSampleRate;
-    int m_sampleRate;
-    int m_channels;
-    
-    void print(Transform transform) const;
-};
-
-#endif
--- a/FeatureWriterFactory.cpp	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Annotator
-    A utility for batch feature extraction from audio files.
-    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
-    Copyright 2007-2008 QMUL.
-
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-
-#include "FeatureWriterFactory.h"
-
-#include "DefaultFeatureWriter.h"
-#include "rdf/RDFFeatureWriter.h"
-#include "AudioDBFeatureWriter.h"
-#include "transform/CSVFeatureWriter.h"
-
-set<string>
-FeatureWriterFactory::getWriterTags()
-{
-    set<string> tags;
-    tags.insert("default");
-    tags.insert("rdf");
-    tags.insert("audiodb");
-    tags.insert("csv");
-    return tags;
-}
-
-FeatureWriter *
-FeatureWriterFactory::createWriter(string tag)
-{
-    if (tag == "default") {
-        return new DefaultFeatureWriter();
-    } else if (tag == "rdf") {
-        return new RDFFeatureWriter();
-    } else if (tag == "audiodb") {
-        return new AudioDBFeatureWriter();
-    } else if (tag == "csv") {
-        return new CSVFeatureWriter();
-    }
-
-    return 0;
-}
--- a/FeatureWriterFactory.h	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Annotator
-    A utility for batch feature extraction from audio files.
-    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
-    Copyright 2007-2008 QMUL.
-
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-
-#ifndef _FEATURE_WRITER_FACTORY_H_
-#define _FEATURE_WRITER_FACTORY_H_
-
-#include <set>
-#include <string>
-
-using std::set;
-using std::string;
-
-class FeatureWriter;
-
-class FeatureWriterFactory
-{
-public:
-    static set<string> getWriterTags();
-    static FeatureWriter *createWriter(string tag);
-};
-
-
-#endif
--- a/deploy_mac.sh	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-#!/bin/bash
-
-# this script should be executed from the directory that contains the app directory (application bundle)
-# it copies the required 3rd party libraries into the application bundle and corrects the library install names and references
-
-TARGETPATH="sonic-annotator.app/Contents/Frameworks/"
-
-mkdir "$TARGETPATH"
-
-QTPREFIX=/Library/Frameworks/
-QTFWKS="QtXml QtCore QtNetwork"
-
-# copy the dynamic libraries into the app bundle
-
-for FWK in $QTFWKS; do
-  cp ${QTPREFIX}${FWK}.framework/Versions/4/${FWK} "${TARGETPATH}"
-done
-
-# change the id's of the dylibs
-for FWK in $QTFWKS; do
-  install_name_tool -id @executable_path/../Frameworks/${FWK} "$TARGETPATH/$FWK"
-done
-
-# tell the linker to look for dylibs in the app bundle
-for FWK in $QTFWKS; do
-  install_name_tool -change ${FWK}.framework/Versions/4/${FWK} @executable_path/../Frameworks/${FWK} "sonic-annotator.app/Contents/MacOS/sonic-annotator"
-done
-
-# correct dependencies between QT dylibs
-for FWK in $QTFWKS; do
-  case $FWK in QtCore) continue;; esac
-  install_name_tool -change QtCore.framework/Versions/4/QtCore @executable_path/../Frameworks/QtCore "$TARGETPATH/${FWK}"
-done
-
--- a/main.cpp	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,735 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Annotator
-    A utility for batch feature extraction from audio files.
-    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
-    Copyright 2007-2008 QMUL.
-
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#include <vector>
-#include <string>
-#include <iostream>
-
-#include <QCoreApplication>
-#include <QSettings>
-#include <QStringList>
-#include <QString>
-#include <QFileInfo>
-#include <QDir>
-
-using std::cout;
-using std::cerr;
-using std::endl;
-using std::vector;
-using std::string;
-
-#include "base/Exceptions.h"
-#include "base/TempDirectory.h"
-
-#include "data/fileio/AudioFileReaderFactory.h"
-#include "data/fileio/PlaylistFileReader.h"
-
-#include "transform/Transform.h"
-#include "transform/TransformFactory.h"
-
-#include "FeatureExtractionManager.h"
-#include "transform/FeatureWriter.h"
-#include "FeatureWriterFactory.h"
-
-#include "rdf/RDFTransformFactory.h"
-
-#include <vamp-hostsdk/PluginSummarisingAdapter.h>
-
-#ifdef HAVE_FFTW3
-#include <fftw3.h>
-#endif
-
-// Desired options:
-//
-// * output preference:
-//   - all data in one file
-//   - one file per input file
-//   - one file per input file per transform
-//   - (any use for: one file per transform?)
-//
-// * output location:
-//   - same directory as input file
-//   - current directory
-//
-// * output filename: 
-//   - based on input (obvious choice for one file per input file modes)
-//   - specified on command line (obvious choice for all in one file mode)
-//
-// * output format: one or more of
-//   - RDF
-//   - AudioDB
-//   - Vamp Simple Host format
-//   - CSV
-//
-// * input handling:
-//   - run each transform on each input file separately
-//   - provide all input files to the same transform, one per channel
-//
-// * format-specific options:
-//   - RDF format: fancy/plain RDF
-//   - CSV format: separator, timestamp type
-//   note: do the output file/location also count as format-specific options?
-//   an output writer that wrote to a database would have different options...
-//
-// * debug level and progress output
-// 
-// * other potential options:
-//   - ignore version mismatches in Transform specifications
-//   - sample rate: force a given rate; use file rate instead of rate in
-//     Transform spec
-// 
-// * other potential instructions:
-//   - write out a skeleton Transform file for a specified plugin
-//   - write out skeleton RDF for a plugin library (i.e. do the job of
-//     RDF template_generator)
-//   - verify that RDF for a plugin library matches the plugin
-//
-// MAYBE:
-// * transform(s) to run:
-//   - supply transform file names on command line
-//   - use all transforms found in a given directory?
-//
-// MAYBE:
-// * input files to transform:
-//   - supply file names or URIs on command line
-//   - use all files in a given directory or tree
-
-static QString
-wrap(QString s, int len, int pfx = 0)
-{
-    QString ws;
-    QStringList sl(s.split(' '));
-    int i = 0, c = 0;
-    while (i < sl.size()) {
-        int wl = sl[i].length();
-        if (c + wl < len) {
-            if (c > 0) {
-                ws += ' ';
-                ++c;
-            }
-        } else {
-            if (c > 0) {
-                ws += '\n';
-                for (int j = 0; j < pfx; ++j) ws += ' ';
-                c = 0;
-            }
-        }
-        ws += sl[i];
-        c += wl;
-        ++i;
-    }
-    return ws;
-}
-
-void usage(QString myname)
-{
-    set<string> writers = FeatureWriterFactory::getWriterTags();
-        
-    cerr << endl;
-    cerr << "Sonic Annotator" << endl;
-    cerr << "A utility for batch feature extraction from audio files." << endl;
-    cerr << "Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London." << endl;
-    cerr << "Copyright 2007-2008 Queen Mary, University of London." << endl;
-    cerr << endl;
-    cerr << "This program is free software.  You may redistribute copies of it under the" << endl;
-    cerr << "terms of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>." << endl;
-    cerr << "This program is supplied with NO WARRANTY, to the extent permitted by law." << endl;
-    cerr << endl;
-    cerr << "  Usage: " << myname.toStdString()
-         << " [-mr] -t trans.xml [...] -w <writer> [...] <audio> [...]" << endl;
-    cerr << "         " << myname.toStdString()
-         << " [-mr] -T trans.txt [...] -w <writer> [...] <audio> [...]" << endl;
-    cerr << "         " << myname.toStdString()
-         << " -s <transform>" << endl;
-    cerr << "         " << myname.toStdString()
-         << " [-lh]" << endl;
-    cerr << endl;
-    cerr << "Where <audio> is an audio file or URL to use as input: either a local file" << endl;
-    cerr << "path, local \"file://\" URL, or remote \"http://\" or \"ftp://\" URL." << endl;
-    cerr << endl;
-
-    QString extensions = AudioFileReaderFactory::getKnownExtensions();
-    QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
-    if (!extlist.empty()) {
-        cerr << "The following audio file extensions are recognised:" << endl;
-        cerr << "  ";
-        int c = 2;
-        for (int i = 0; i < extlist.size(); ++i) {
-            QString ext = extlist[i];
-            if (ext.startsWith("*.")) ext = ext.right(ext.length()-2);
-            c += ext.length() + 2;
-            if (c >= 80) {
-                cerr << "\n  ";
-                c -= 78;
-            }
-            cerr << ext.toStdString();
-            if (i + 1 == extlist.size()) cerr << ".";
-            else cerr << ", ";
-        }
-        cerr << endl;
-    }
-
-    cerr << "Playlist files in M3U format are also supported." << endl;
-    cerr << endl;
-    cerr << "Transformation options:" << endl;
-    cerr << endl;
-    cerr << "  -t, --transform <T> Apply transform described in transform file <T> to" << endl;
-    cerr << "                      all input audio files.  You may supply this option" << endl;
-    cerr << "                      multiple times.  You must supply this option or -T at" << endl;
-    cerr << "                      least once for any work to be done.  Transform format" << endl;
-    cerr << "                      may be SV transform XML or Vamp transform RDF.  See" << endl;
-    cerr << "                      documentation for examples." << endl;
-    cerr << endl;
-    cerr << "  -T, --transforms <T> Apply all transforms described in transform files" << endl;
-    cerr << "                      whose names are listed in text file <T>.  You may supply" << endl;
-    cerr << "                      this option multiple times." << endl;
-    cerr << endl;
-    cerr << "  -d, --default <I>   Apply the default transform for transform id <I>.  This" << endl;
-    cerr << "                      is equivalent to generating a skeleton transform for this" << endl;
-    cerr << "                      id (using the -s option, below) and then applying that," << endl;
-    cerr << "                      unmodified, with the -t option in the normal way.  Note" << endl;
-    cerr << "                      that the results may vary as the implementation's default" << endl;
-    cerr << "                      processing parameters are not guaranteed.  Do not use" << endl;
-    cerr << "                      this in production systems.  You may supply this option" << endl;
-    cerr << "                      multiple times, and mix it with -t and -T." << endl;
-    cerr << endl;
-    cerr << "  -w, --writer <W>    Write output using writer type <W>." << endl;
-    cerr << "                      Supported writer types are: ";
-    for (set<string>::const_iterator i = writers.begin();
-         i != writers.end(); ) {
-        cerr << *i;
-        if (++i != writers.end()) cerr << ", ";
-        else cerr << ".";
-    }
-    cerr << endl;
-    cerr << "                      You may supply this option multiple times.  You must" << endl;
-    cerr << "                      supply this option at least once for any work to be done." << endl;
-    cerr << endl;
-    cerr << "  -S, --summary <S>   In addition to the result features, write summary feature" << endl;
-    cerr << "                      of summary type <S>." << endl;
-    cerr << "                      Supported summary types are: min, max, mean, median, mode," << endl;
-    cerr << "                      sum, variance, sd, count." << endl;
-    cerr << "                      You may supply this option multiple times." << endl;
-    cerr << endl;
-    cerr << "      --summary-only  Write only summary features; do not write the regular" << endl;
-    cerr << "                      result features." << endl;
-    cerr << endl;
-    cerr << "      --segments <A>,<B>[,...]" << endl;
-    cerr << "                      Summarise in segments, with segment boundaries" << endl;
-    cerr << "                      at A, B, ... seconds." << endl;
-    cerr << endl;
-
-/*!!! This feature not implemented yet (sniff)
-    cerr << "  -m, --multiplex     If multiple input audio files are given, use mono" << endl;
-    cerr << "                      mixdowns of all files as the input channels for a single" << endl;
-    cerr << "                      invocation of each transform, instead of running the" << endl;
-    cerr << "                      transform against all files separately." << endl;
-    cerr << endl;
-*/
-
-    cerr << "  -r, --recursive     If any of the <audio> arguments is found to be a local" << endl;
-    cerr << "                      directory, search the tree starting at that directory" << endl;
-    cerr << "                      for all supported audio files and take all of those as" << endl;
-    cerr << "                      input instead." << endl;
-    cerr << endl;
-    cerr << "Housekeeping options:" << endl;
-    cerr << endl;
-    cerr << "  -l, --list          List all known transform ids to standard output." << endl;
-    cerr << endl;
-    cerr << "  -s, --skeleton <I>  Generate a skeleton transform file for transform id <I>" << endl;
-    cerr << "                      and write it to standard output." << endl;
-    cerr << endl;
-    cerr << "  -h, --help          Show this help." << endl;
-
-    cerr << endl;
-    cerr << "If no -w (or --writer) options are supplied, either the -l -s or -h option (or" << endl;
-    cerr << "long equivalent) must be given instead." << endl;
-
-    for (set<string>::const_iterator i = writers.begin();
-         i != writers.end(); ++i) {
-        FeatureWriter *w = FeatureWriterFactory::createWriter(*i);
-        if (!w) {
-            cerr << "  (Internal error: failed to create writer of this type)" << endl;
-            continue;
-        }
-        FeatureWriter::ParameterList params = w->getSupportedParameters();
-        delete w;
-        if (params.empty()) {
-            continue;
-        }
-        cerr << endl;
-        cerr << "Additional options for writer type \"" << *i << "\":" << endl;
-        cerr << endl;
-        for (FeatureWriter::ParameterList::const_iterator j = params.begin();
-             j != params.end(); ++j) {
-            cerr << "  --" << *i << "-" << j->name << " ";
-            int spaceage = 16 - int(i->length()) - int(j->name.length());
-            if (j->hasArg) { cerr << "<X> "; spaceage -= 4; }
-            for (int k = 0; k < spaceage; ++k) cerr << " ";
-            QString s(j->description.c_str());
-            s = wrap(s, 56, 22);
-            cerr << s.toStdString() << endl;
-        }
-    }
-
-    cerr << endl;
-    exit(0);
-}
-
-void
-listTransforms()
-{
-    TransformList transforms =
-        TransformFactory::getInstance()->getAllTransformDescriptions();
-
-    for (TransformList::const_iterator iter = transforms.begin();
-         iter != transforms.end(); ++iter) {
-        const TransformDescription &transform = *iter;
-        if (transform.type == TransformDescription::Analysis) {
-            cout << transform.identifier.toStdString() << endl;
-        }
-    }
-}    
-
-void
-printSkeleton(QString id)
-{
-    Transform transform =
-        TransformFactory::getInstance()->getDefaultTransformFor(id);
-    cout << "@prefix xsd:      <http://www.w3.org/2001/XMLSchema#> ." << endl
-         << "@prefix vamp:     <http://purl.org/ontology/vamp/> ." << endl
-         << "@prefix :         <#> ." << endl << endl;
-    QString rdf = RDFTransformFactory::writeTransformToRDF
-        (transform, ":transform");
-    cout << rdf.toStdString();
-}    
-
-void
-findSourcesRecursive(QString dirname, QStringList &addTo, int &found)
-{
-    QDir dir(dirname);
-
-    QString printable = dir.dirName().left(20);
-    cerr << "\rScanning \"" << printable.toStdString() << "\"..."
-         << QString("                    ").left(20 - printable.length()).toStdString()
-         << " [" << found << " audio file(s)]";
-
-    QString extensions = AudioFileReaderFactory::getKnownExtensions();
-    QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
-
-    QStringList files = dir.entryList
-        (extlist, QDir::Files | QDir::Readable);
-    for (int i = 0; i < files.size(); ++i) {
-        addTo.push_back(dir.filePath(files[i]));
-        ++found;
-    }
-
-    QStringList subdirs = dir.entryList
-        (QStringList(), QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
-    for (int i = 0; i < subdirs.size(); ++i) {
-        findSourcesRecursive(dir.filePath(subdirs[i]), addTo, found);
-    }
-}
-
-
-int main(int argc, char **argv)
-{
-    QCoreApplication application(argc, argv);
-
-    QCoreApplication::setOrganizationName("QMUL");
-    QCoreApplication::setOrganizationDomain("qmul.ac.uk");
-    QCoreApplication::setApplicationName("Sonic Annotator");
-
-    QStringList args = application.arguments();
-    set<string> requestedWriterTags;
-    set<string> requestedTransformFiles;
-    set<string> requestedTransformListFiles;
-    set<string> requestedDefaultTransforms;
-    set<string> requestedSummaryTypes;
-//!!!    bool multiplex = false;
-    bool recursive = false;
-    bool list = false;
-    bool summaryOnly = false;
-    QString skeletonFor = "";
-    QString myname = args[0];
-    myname = QFileInfo(myname).baseName();
-    QStringList otherArgs;
-    Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries boundaries;
-
-    QString helpStr = myname + ": use -h or --help option for help";
-
-    for (int i = 1; i < args.size(); ++i) {
-
-        QString arg = args[i];
-        bool last = ((i + 1) == args.size());
-        
-        if (arg == "-h" || arg == "--help" || arg == "-?") {
-            usage(myname);
-        }
-
-        if (arg == "-w" || arg == "--writer") {
-            if (last || args[i+1].startsWith("-")) {
-                cerr << myname.toStdString() << ": argument expected for \""
-                     << arg.toStdString() << "\" option" << endl;
-                cerr << helpStr.toStdString() << endl;
-                exit(2);
-            } else {
-                string tag = args[++i].toStdString();
-                if (requestedWriterTags.find(tag) != requestedWriterTags.end()) {
-                    cerr << myname.toStdString() << ": NOTE: duplicate specification of writer type \"" << tag << "\" ignored" << endl;
-                } else {
-                    requestedWriterTags.insert(tag);
-                }
-                continue;
-            }
-        } else if (arg == "-t" || arg == "--transform") {
-            if (last || args[i+1].startsWith("-")) {
-                cerr << myname.toStdString() << ": argument expected for \""
-                     << arg.toStdString() << "\" option" << endl;
-                cerr << helpStr.toStdString() << endl;
-                exit(2);
-            } else {
-                string transform = args[++i].toStdString();
-                if (requestedTransformFiles.find(transform) !=
-                    requestedTransformFiles.end()) {
-                    cerr << myname.toStdString() << ": NOTE: duplicate specification of transform file \"" << transform << "\" ignored" << endl;
-                } else {
-                    requestedTransformFiles.insert(transform);
-                }
-                continue;
-            }
-        } else if (arg == "-T" || arg == "--transforms") {
-            if (last || args[i+1].startsWith("-")) {
-                cerr << myname.toStdString() << ": argument expected for \""
-                     << arg.toStdString() << "\" option" << endl;
-                cerr << helpStr.toStdString() << endl;
-                exit(2);
-            } else {
-                string transform = args[++i].toStdString();
-                if (requestedTransformListFiles.find(transform) !=
-                    requestedTransformListFiles.end()) {
-                    cerr << myname.toStdString() << ": NOTE: duplicate specification of transform list file \"" << transform << "\" ignored" << endl;
-                } else {
-                    requestedTransformListFiles.insert(transform);
-                }
-                continue;
-            }
-        } else if (arg == "-d" || arg == "--default") {
-            if (last || args[i+1].startsWith("-")) {
-                cerr << myname.toStdString() << ": argument expected for \""
-                     << arg.toStdString() << "\" option" << endl;
-                cerr << helpStr.toStdString() << endl;
-                exit(2);
-            } else {
-                string deft = args[++i].toStdString();
-                if (requestedDefaultTransforms.find(deft) !=
-                    requestedDefaultTransforms.end()) {
-                    cerr << myname.toStdString() << ": NOTE: duplicate specification of default transform \"" << deft << "\" ignored" << endl;
-                } else {
-                    requestedDefaultTransforms.insert(deft);
-                }
-                continue;
-            }
-        } else if (arg == "-S" || arg == "--summary") {
-            if (last || args[i+1].startsWith("-")) {
-                cerr << myname.toStdString() << ": argument expected for \""
-                     << arg.toStdString() << "\" option" << endl;
-                cerr << helpStr.toStdString() << endl;
-                exit(2);
-            } else {
-                string summary = args[++i].toStdString();
-                requestedSummaryTypes.insert(summary);
-                continue;
-            }
-        } else if (arg == "--summary-only") {
-            summaryOnly = true;
-            continue;
-        } else if (arg == "--segments") {
-            if (last) {
-                cerr << myname.toStdString() << ": argument expected for \""
-                     << arg.toStdString() << "\" option" << endl;
-                cerr << helpStr.toStdString() << endl;
-                exit(2);
-            } else {
-                string segmentSpec = args[++i].toStdString();
-                QStringList segmentStrs = QString(segmentSpec.c_str()).split(',');
-                for (int j = 0; j < segmentStrs.size(); ++j) {
-                    bool good = false;
-                    boundaries.insert(Vamp::RealTime::fromSeconds
-                                      (segmentStrs[j].toDouble(&good)));
-                    if (!good) {
-                        cerr << myname.toStdString() << ": segment boundaries must be numeric" << endl;
-                        cerr << helpStr.toStdString() << endl;
-                        exit(2);
-                    }
-                }
-            }
-/*!!!
-        } else if (arg == "-m" || arg == "--multiplex") {
-            multiplex = true;
-            cerr << myname.toStdString()
-                 << ": WARNING: Multiplex argument not yet implemented" << endl; //!!!
-            continue;
-*/
-        } else if (arg == "-r" || arg == "--recursive") {
-            recursive = true;
-            continue;
-        } else if (arg == "-l" || arg == "--list") {
-            list = true;
-            continue;
-        } else if (arg == "-s" || arg == "--skeleton") {
-            if (last || args[i+1].startsWith("-")) {
-                cerr << myname.toStdString() << ": usage: "
-                     << myname.toStdString() << " " << arg.toStdString()
-                     << " <transform>" << endl;
-                cerr << helpStr.toStdString() << endl;
-                exit(2);
-            } else {
-                skeletonFor = args[++i];
-                continue;
-            }
-        } else {
-            otherArgs.push_back(args[i]);
-        }
-    }
-
-    if (list) {
-        if (!requestedWriterTags.empty() || skeletonFor != "") {
-            cerr << helpStr.toStdString() << endl;
-            exit(2);
-        }
-        listTransforms();
-        exit(0);
-    }
-    if (skeletonFor != "") {
-        if (!requestedWriterTags.empty()) {
-            cerr << helpStr.toStdString() << endl;
-            exit(2);
-        }
-        printSkeleton(skeletonFor);
-        exit(0);
-    }
-
-    if (requestedTransformFiles.empty() &&
-        requestedTransformListFiles.empty() &&
-        requestedDefaultTransforms.empty()) {
-        cerr << myname.toStdString()
-             << ": no transform(s) specified" << endl;
-        cerr << helpStr.toStdString() << endl;
-        exit(2);
-    }
-
-    if (requestedWriterTags.empty()) {
-        cerr << myname.toStdString()
-             << ": no writer(s) specified" << endl;
-        cerr << helpStr.toStdString() << endl;
-        exit(2);
-    }
-
-    if (!boundaries.empty()) {
-        if (requestedSummaryTypes.empty()) {
-            cerr << myname.toStdString()
-                 << ": summary segment boundaries provided, but no summary type specified"
-                 << endl;
-            cerr << helpStr.toStdString() << endl;
-            exit(2);
-        }
-    }
-
-#ifdef HAVE_FFTW3
-    QSettings settings;
-    settings.beginGroup("FFTWisdom");
-    QString wisdom = settings.value("wisdom").toString();
-    if (wisdom != "") {
-        fftw_import_wisdom_from_string(wisdom.toLocal8Bit().data());
-    }
-    settings.endGroup();
-#endif
-
-    FeatureExtractionManager manager;
-
-    if (!requestedSummaryTypes.empty()) {
-        if (!manager.setSummaryTypes(requestedSummaryTypes,
-                                     summaryOnly,
-                                     boundaries)) {
-            cerr << myname.toStdString()
-                 << ": failed to set requested summary types" << endl;
-            exit(1);
-        }
-    }
-    
-    // the manager dictates the sample rate and number of channels
-    // to work at - files with too few channels are rejected,
-    // too many channels are handled as usual by the Vamp plugin
-
-    //!!! Review this: although we probably do want to fix the channel
-    // count here, we don't necessarily want to fix the rate: it's
-    // specified in the Transform file.
-
-    manager.setDefaultSampleRate(44100);
-    manager.setChannels(1);
-    
-    vector<FeatureWriter *> writers;
-
-    for (set<string>::const_iterator i = requestedWriterTags.begin();
-         i != requestedWriterTags.end(); ++i) {
-
-        FeatureWriter *writer = FeatureWriterFactory::createWriter(*i);
-
-        if (!writer) {
-            cerr << myname.toStdString() << ": unknown feature writer \""
-                 << *i << "\"" << endl;
-            cerr << helpStr.toStdString() << endl;
-            exit(2);
-        }
-
-        map<string, string> writerArgs;
-        FeatureWriter::ParameterList pl(writer->getSupportedParameters());
-
-        for (int k = 0; k < pl.size(); ++k) {
-            
-            string argbase = pl[k].name;
-            QString literal = QString("--%1-%2")
-                .arg(i->c_str()).arg(argbase.c_str());
-            
-            for (int j = 0; j < otherArgs.size(); ) {
-                
-                if (otherArgs[j] != literal) {
-                    ++j;
-                    continue;
-                }
-                    
-                otherArgs.removeAt(j);
-                    
-                if (pl[k].hasArg) {
-                    if (j < otherArgs.size()) {
-                        writerArgs[argbase] = otherArgs[j].toStdString();
-                        otherArgs.removeAt(j);
-                    } else {
-                        cerr << myname.toStdString() << ": "
-                             << "argument required for \""
-                             << literal.toStdString() << "\" option"
-                             << endl;
-                        cerr << helpStr.toStdString() << endl;
-                        exit(2);
-                    }
-                } else {
-                    writerArgs[argbase] = "";
-                }
-            }
-        }
-        
-        writer->setParameters(writerArgs);
-        
-        writers.push_back(writer);
-    }
-
-    for (int i = 0; i < otherArgs.size(); ++i) {
-        if (otherArgs[i].startsWith("-")) {
-            cerr << myname.toStdString() << ": unknown option \""
-                 << otherArgs[i].toStdString() << "\"" << endl;
-            cerr << helpStr.toStdString() << endl;
-            exit(2);
-        }
-    }
-
-    if (otherArgs.empty()) {
-        cerr << myname.toStdString() << ": no input(s) specified" << endl;
-        cerr << helpStr.toStdString() << endl;
-        exit(2);
-    }    
-
-    for (set<string>::const_iterator i = requestedTransformListFiles.begin();
-         i != requestedTransformListFiles.end(); ++i) {
-        PlaylistFileReader reader(i->c_str());
-        if (reader.isOK()) {
-            vector<QString> files = reader.load();
-            for (int j = 0; j < files.size(); ++j) {
-                requestedTransformFiles.insert(files[j].toStdString());
-            }
-        } else {
-            cerr << myname.toStdString() << ": failed to read template list file \"" << *i << "\"" << endl;
-            exit(2);
-        }
-    }
-
-    bool haveFeatureExtractor = false;
-    
-    for (set<string>::const_iterator i = requestedTransformFiles.begin();
-         i != requestedTransformFiles.end(); ++i) {
-        if (manager.addFeatureExtractorFromFile(i->c_str(), writers)) {
-            haveFeatureExtractor = true;
-        }
-    }
-
-    for (set<string>::const_iterator i = requestedDefaultTransforms.begin();
-         i != requestedDefaultTransforms.end(); ++i) {
-        if (manager.addDefaultFeatureExtractor(i->c_str(), writers)) {
-            haveFeatureExtractor = true;
-        }
-    }
-
-    if (!haveFeatureExtractor) {
-        cerr << myname.toStdString() << ": no feature extractors added" << endl;
-        exit(2);
-    }
-
-    QStringList sources;
-    if (!recursive) {
-        sources = otherArgs;
-    } else {
-        for (QStringList::const_iterator i = otherArgs.begin();
-             i != otherArgs.end(); ++i) {
-            if (QDir(*i).exists()) {
-                cerr << "Directory found and recursive flag set, scanning for audio files..." << endl;
-                int found = 0;
-                findSourcesRecursive(*i, sources, found);
-                cerr << "\rDone, found " << found << " supported audio file(s)                    " << endl;
-            } else {
-                sources.push_back(*i);
-            }
-        }
-    }
-
-    for (QStringList::const_iterator i = sources.begin();
-         i != sources.end(); ++i) {
-        std::cerr << "Extracting features for: \"" << i->toStdString() << "\"" << std::endl;
-        try {
-            manager.extractFeatures(*i);
-        } catch (FailedToOpenFile f) {
-            cerr << "ERROR: Failed to open output file for feature writer: "
-                 << f.what() << endl;
-            break;
-        }
-    }
-    
-    for (int i = 0; i < writers.size(); ++i) delete writers[i];
-
-#ifdef HAVE_FFTW3
-    settings.beginGroup("FFTWisdom");
-    char *cwisdom = fftw_export_wisdom_to_string();
-    if (cwisdom) {
-        settings.setValue("wisdom", cwisdom);
-        fftw_free(cwisdom);
-    }
-    settings.endGroup();
-#endif
-
-    TempDirectory::getInstance()->cleanup();
-    
-    return 0;
-}
-
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pull-svn.sh	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+svrepo=https://sv1.svn.sourceforge.net/svnroot/sv1/sonic-visualiser/trunk
+
+svn co $svrepo/audioio
+svn co $svrepo/base
+svn co $svrepo/data
+svn co $svrepo/plugin
+svn co $svrepo/rdf
+svn co $svrepo/system
+svn co $svrepo/transform
+
+
--- a/qm-keydetector.n3	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-@prefix rdfs:   <http://www.w3.org/2000/01/rdf-schema#>.
-@prefix xsd:    <http://www.w3.org/2001/XMLSchema#>.
-@prefix vamp:   <http://www.vamp-plugins.org/ontology/> .
-@prefix vampex: <http://www.vamp-plugins.org/examples/> .
-@prefix qvp:    <http://vamp-plugins.org/plugin/qm-vamp-plugins/>.
-@prefix owl:    <http://www.w3.org/2002/07/owl#> .
-@prefix dc:     <http://purl.org/dc/elements/1.1/> .
-@prefix af:     <http://purl.org/ontology/af/> .
-@prefix foaf:   <http://xmlns.com/foaf/0.1/> .
-@prefix cc:     <http://web.resource.org/cc/> .
-@prefix thisplug: <http://vamp-plugins.org/plugin/qm-vamp-plugins/qm-keydetector#> .
-@prefix :       <> .
-
-<>  a   vamp:PluginDescription ;
-    foaf:maker          <http://chrissutton.org/me> ;
-    foaf:primaryTopic   qvp:qm-keydetector .
-
-qvp:qm-keydetector a   vamp:Plugin ;
-    dc:title                "Key Detector" ;
-    dc:description          "";
-    foaf:maker              :katynoland, :chrislandone ; 
-#    cc:license              <http://creativecommons.org/licenses/BSD/> ; what is the license for QM Vamp plugins ?
-    vamp:identifier         "qm-keydetector" ; # The Vamp identifier for the plugin
-    vamp:vamp_API_version   vamp:version_v1.1b ; # Made up - this plugin doesn't actually specify it
-    owl:versionInfo         "2" ;
-    vamp:input_domain       vamp:TimeDomain ;
-
-    vamp:parameter_descriptor   thisplug:pd1 ;
-    vamp:parameter_descriptor   thisplug:pd2 ;
-    vamp:output_descriptor      thisplug:od1 ;
-    vamp:output_descriptor      thisplug:od2 ;
-    vamp:output_descriptor      thisplug:od3 .
-
-:katynoland a foaf:Person;
-    foaf:name "Katy Noland" .
-:chrislandone a foaf:Person;
-    foaf:name "Christian Landone" .
-    
-# Note : any need for these to have proper URIs ?
-thisplug:pd1    a       vamp:ParameterDescriptor ;
-    vamp:identifier     "tuning" ;
-    dc:title            "Tuning Frequency" ;
-    dc:format           "Hz" ;
-    vamp:minValue       420 ; #might be useful when interpreting plugin output
-    vamp:maxValue       460 ;
-    vamp:defaultValue   440 .
-
-thisplug:pd2    a       vamp:ParameterDescriptor ;
-    vamp:identifier     "length" ;
-    dc:title            "Window Length" ;
-    vamp:minValue       1 ; #might be useful when interpreting plugin output
-    vamp:maxValue       30 ;
-    vamp:defaultValue   10 .
-    
-thisplug:od1    a       vamp:OutputDescriptor ;
-    vamp:identifier     "tonic" ;
-    dc:title            "Tonic Pitch" ;
-    vamp:fixed_bin_count "true" ;
-    vamp:bin_count      1 ;
-    vamp:sample_type    vamp:OneSamplePerStep .
-
-
-thisplug:od2    a       vamp:OutputDescriptor ;
-    vamp:identifier     "mode" ;
-    dc:title            "Key Mode" ;
-    vamp:fixed_bin_count "true" ;
-    vamp:bin_count      1 ;
-    vamp:bin_names      "Major = 0, Minor = 1" ; # might need a rethink
-    vamp:sample_type    vamp:OneSamplePerStep .
-
-thisplug:od3    a       vamp:OutputDescriptor ;
-    vamp:identifier     "key" ;
-    dc:title            "Key" ;
-    vamp:fixed_bin_count "true" ;
-    vamp:bin_count      1 ;
-    vamp:sample_type    vamp:OneSamplePerStep ;
-    vamp:computes_feature_type  <http://purl.org/NET/c4dm/keys.owl#Key> .
--- a/runner.pro	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-
-TEMPLATE = app
-
-SV_UNIT_PACKAGES = vamp vamp-hostsdk samplerate mad id3tag oggz fishsound sndfile lrdf redland rasqal raptor
-
-#linux-g++:LIBS += -Wl,-Bstatic
-#linux-g++:DEFINES += BUILD_STATIC
-
-load(../sonic-visualiser/sv.prf)
-
-LIBPATH += /usr/local/lib
-
-CONFIG += sv qt thread warn_on stl rtti exceptions console
-QT += xml network
-QT -= gui
-
-# Using the "console" CONFIG flag above should ensure this happens for
-# normal Windows builds, but the console feature doesn't get picked up
-# in my local cross-compile setup because qmake itself doesn't know to
-# look for win32 features
-win32-x-g++:QMAKE_LFLAGS += -Wl,-subsystem,console
-
-# If you have compiled your Vamp plugin SDK with FFTW (using its
-# HAVE_FFTW3 flag), you can define the same flag here to ensure the
-# program saves and restores FFTW wisdom in its configuration properly
-#
-#DEFINES += HAVE_FFTW3
-
-TARGET = sonic-annotator
-
-DEPENDPATH += . ../sonic-visualiser i18n main
-INCLUDEPATH += . ../sonic-visualiser main
-LIBPATH = ../sonic-visualiser/audioio ../sonic-visualiser/data ../sonic-visualiser/plugin ../sonic-visualiser/rdf ../sonic-visualiser/transform ../sonic-visualiser/base ../sonic-visualiser/system $$LIBPATH
-
-QMAKE_CXXFLAGS_RELEASE += -fmessage-length=80 -fdiagnostics-show-location=every-line
-
-contains(DEFINES, BUILD_STATIC):LIBS -= -ljack
-
-#LIBS = -lsvaudioio -lsvdata -lsvtransform -lsvplugin -lsvrdf -lsvbase -lsvsystem  $$LIBS
-LIBS = -lsvdata -lsvtransform -lsvplugin -lsvrdf -lsvdata -lsvbase -lsvsystem  $$LIBS
-
-PRE_TARGETDEPS += ../sonic-visualiser/audioio/libsvaudioio.a \
-                  ../sonic-visualiser/data/libsvdata.a \
-                  ../sonic-visualiser/transform/libsvtransform.a \
-                  ../sonic-visualiser/plugin/libsvplugin.a \
-                  ../sonic-visualiser/rdf/libsvrdf.a \
-                  ../sonic-visualiser/base/libsvbase.a \
-                  ../sonic-visualiser/system/libsvsystem.a
-
-OBJECTS_DIR = tmp_obj
-MOC_DIR = tmp_moc
-
-# Input
-HEADERS += \
-	AudioDBFeatureWriter.h \
-        FeatureWriterFactory.h  \
-        DefaultFeatureWriter.h \
-        FeatureExtractionManager.h
-
-SOURCES += \
-	main.cpp \
-	DefaultFeatureWriter.cpp \
-	FeatureExtractionManager.cpp \
-        AudioDBFeatureWriter.cpp \
-        FeatureWriterFactory.cpp
-
-
-
-
-# Restore dynamic linkage, in case we went static earlier
-linux-g++:LIBS += -Wl,-Bdynamic -lpthread -ldl -lz
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/AudioDBFeatureWriter.cpp	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,214 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include <fstream>
+
+#include <QFileInfo>
+
+#include "AudioDBFeatureWriter.h"
+
+using namespace std;
+using namespace Vamp;
+
+string
+AudioDBFeatureWriter::catalogueIdParam = "catid";
+
+string
+AudioDBFeatureWriter::baseDirParam = "basedir";
+
+struct AudioDBFeatureWriter::TrackStream
+{
+    QString trackid;
+    ofstream* ofs;
+};
+
+AudioDBFeatureWriter::AudioDBFeatureWriter() : 
+    catalogueId("catalog"), baseDir("audiodb")
+{
+    
+}
+
+AudioDBFeatureWriter::~AudioDBFeatureWriter()
+{
+    // close all open files
+    for (map<string, TrackStream>::iterator iter = dbfiles.begin(); iter != dbfiles.end(); ++iter)
+    {
+        if (iter->second.ofs) {
+            iter->second.ofs->close();
+            delete iter->second.ofs;
+        }
+    }
+    
+    // TODO: error handling on close
+}
+
+AudioDBFeatureWriter::ParameterList
+AudioDBFeatureWriter::getSupportedParameters() const
+{
+    ParameterList pl;
+    Parameter p;
+
+    p.name = catalogueIdParam;
+    p.description = "Catalogue ID";
+    p.hasArg = true;
+    pl.push_back(p);
+
+    p.name = baseDirParam;
+    p.description = "Base output directory path";
+    p.hasArg = true;
+    pl.push_back(p);
+
+    return pl;
+}
+
+void
+AudioDBFeatureWriter::setParameters(map<string, string> &params)
+{
+    if (params.find(catalogueIdParam) != params.end()) {
+        setCatalogueId(params[catalogueIdParam]);
+        params.erase(catalogueIdParam);
+    }
+    if (params.find(baseDirParam) != params.end()) {
+        setBaseDirectory(params[baseDirParam]);
+        params.erase(baseDirParam);
+    }
+}
+
+void
+AudioDBFeatureWriter::setCatalogueId(const string &catid)
+{
+    catalogueId = catid;
+}
+
+void
+AudioDBFeatureWriter::setBaseDirectory(const string &base)
+{
+    baseDir = base;
+}
+
+void AudioDBFeatureWriter::write(QString trackid,
+                                 const Transform &transform,
+                                 const Vamp::Plugin::OutputDescriptor& output, 
+                                 const Vamp::Plugin::FeatureList& featureList,
+                                 std::string summaryType)
+{
+    //!!! use summaryType
+    if (summaryType != "") {
+        //!!! IMPLEMENT
+        cerr << "ERROR: AudioDBFeatureWriter::write: Writing summaries is not yet implemented!" << endl;
+        exit(1);
+    }
+
+
+    // binary output for FeatureSet
+    
+    // feature-dimension feature-1 feature-2 ...
+    // timestamp-1 timestamp-2 ...
+    
+    // audioDB has to write each feature to a different file
+    // assume a simple naming convention of
+    // <catalog-id>/<track-id>.<feature-id>
+    // with timestamps in a corresponding <catalog-id>/<track-id>.<feature-id>.timestamp file
+    // (start and end times in seconds for each frame -- somewhat optional)
+    
+    // the feature writer holds a map of open file descriptors
+    // the catalog-id is passed in to the feature writer's constructor    
+    
+    // NB -- all "floats" in the file should in fact be doubles
+
+    // TODO:
+    // - write feature end rather than start times, once end time is available in vamp
+    // - write a power file, probably by wrapping plugin in a PluginPowerAdapter :)
+    
+    if (output.binCount == 0)    // this kind of feature just outputs timestamps and labels, assume of no interest to audioDB
+        return;    
+        
+    for (int i = 0; i < featureList.size(); ++i)
+    {
+        // replace output files if necessary
+        if (replaceDBFile(trackid, output.identifier))
+        {                
+            // write the feature length for the next track feature record
+            // binCount has to be set
+            // - it can be zero, i.e. if the output is really a set of labels + timestamps
+            *dbfiles[output.identifier].ofs /*<< ios::binary*/ << output.binCount;
+            
+            cerr << "writing bin count " << output.binCount << " for " << output.identifier << endl;
+        }
+        
+        if (replaceDBFile(trackid, output.identifier + ".timestamp"))
+        {
+            // write the start time to the timestamp file
+            // as we want it for the first feature in the file
+            *dbfiles[output.identifier + ".timestamp"].ofs << featureList[i].timestamp.toString() << endl;
+        }
+
+        if (dbfiles[output.identifier].ofs) {
+            for (int j = 0; j < featureList[i].values.size(); ++j)
+                *dbfiles[output.identifier].ofs /*<< ios::binary*/ << featureList[i].values[j];
+        
+            // write the *end* time of each feature to the timestamp file
+            // NOT IMPLEMENTED YET
+//            *dbfiles[output.identifier + ".timestamp"].ofs << featureList[i].timestamp.toString() << endl;
+        }
+    }
+}
+
+bool AudioDBFeatureWriter::openDBFile(QString trackid, const string& identifier)
+{
+    QString trackBase = QFileInfo(trackid).fileName();
+    string filepath = baseDir + "/" + catalogueId  + "/"
+        + trackBase.toStdString() + "." + identifier;
+    cerr << "AudioDBFeatureWriter::openDBFile: filepath is \"" << filepath << "\"" << endl;
+    ofstream* ofs = new ofstream(filepath.c_str());
+    if (!*ofs)
+    {    
+        cerr << "ERROR AudioDBFeatureWriter::openDBFile(): can't open file " << filepath << endl;
+        return false;
+    }
+    TrackStream ts;
+    ts.trackid = trackid;
+    ts.ofs = ofs;
+    dbfiles[identifier] = ts;
+    return true;
+}
+
+// replace file if no file open for this track, else return false
+bool AudioDBFeatureWriter::replaceDBFile(QString trackid, 
+                                         const string& identifier)
+{
+    if (dbfiles.find(identifier) != dbfiles.end() && dbfiles[identifier].trackid == trackid)
+        return false;    // have an open file for this track
+    
+    if (dbfiles.find(identifier) != dbfiles.end() && dbfiles[identifier].trackid != trackid)
+    {
+        // close the current file
+        if (dbfiles[identifier].ofs) {
+            dbfiles[identifier].ofs->close();
+            delete dbfiles[identifier].ofs;
+            dbfiles[identifier].ofs = 0;
+        }
+    }
+    
+    // open a new file
+    if (!openDBFile(trackid, identifier)) {
+        dbfiles[identifier].ofs = 0;
+        return false; //!!! should throw an exception, otherwise we'll try to open the file again and again every time we want to write to it
+    }
+    
+    return true;
+}
+    
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/AudioDBFeatureWriter.h	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,61 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _AUDIO_DB_FEATURE_WRITER_H_
+#define _AUDIO_DB_FEATURE_WRITER_H_
+
+#include <string>
+#include <map>
+
+using std::string;
+using std::map;
+
+#include "transform/FeatureWriter.h"
+
+class AudioDBFeatureWriter : public FeatureWriter
+{
+public:
+    AudioDBFeatureWriter();
+    virtual ~AudioDBFeatureWriter();
+
+    virtual ParameterList getSupportedParameters() const;
+    virtual void setParameters(map<string, string> &params);
+
+    virtual void setCatalogueId(const string &);
+    virtual void setBaseDirectory(const string &);
+
+    virtual void write(QString trackid,
+                       const Transform &transform,
+                       const Vamp::Plugin::OutputDescriptor &output,
+                       const Vamp::Plugin::FeatureList &features,
+                       std::string summaryType = "");
+    
+    virtual void finish() { }
+
+private:
+    string catalogueId;
+    string baseDir;
+
+    static string catalogueIdParam;
+    static string baseDirParam;
+    
+    struct TrackStream;
+    map<string, TrackStream> dbfiles;
+    
+    bool openDBFile(QString trackid, const string& identifier);
+    bool replaceDBFile(QString trackid, const string& identifier);
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/DefaultFeatureWriter.cpp	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,77 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include <iostream>
+#include <map>
+
+using namespace std;
+
+#include "DefaultFeatureWriter.h"
+
+void DefaultFeatureWriter::write(QString trackid,
+                                 const Transform &transform,
+                                 const Vamp::Plugin::OutputDescriptor& output,
+                                 const Vamp::Plugin::FeatureList& featureList,
+                                 std::string summaryType)
+{
+    // generic XML output
+    
+    /*
+     
+     <feature>
+        <name>output.name</name>
+        <timestamp>feature.timestamp</timestamp>    
+        <values>output.binName[0]:feature.value[0]...</values>
+        <label>feature.label</label>
+     </feature>
+     
+     */
+    
+    for (int i = 0; i < featureList.size(); ++i)
+    {
+        if (summaryType == "") {
+            cout << "<feature>" << endl;
+        } else {
+            cout << "<summary type=\"" << summaryType << "\">" << endl;
+        }
+        cout << "\t<name>" << output.name << "</name>" << endl;
+        if (featureList[i].hasTimestamp) {
+            cout << "\t<timestamp>" << featureList[i].timestamp << "</timestamp>" << endl;    
+        }
+        if (featureList[i].hasDuration) {
+            cout << "\t<duration>" << featureList[i].duration << "</duration>" << endl;    
+        }
+        if (featureList[i].values.size() > 0)
+        {
+            cout << "\t<values>";
+            for (int j = 0; j < featureList[i].values.size(); ++j)
+            {
+                if (j > 0)
+                    cout << " ";
+                if (output.binNames.size() > 0)
+                    cout << output.binNames[j] << ":";
+                cout << featureList[i].values[j];
+            }
+            cout << "</values>" << endl;
+        }
+        if (featureList[i].label.length() > 0)
+            cout << "\t<label>" << featureList[i].label << "</label>" << endl;            
+        if (summaryType == "") {
+            cout << "</feature>" << endl;
+        } else {
+            cout << "</summary>" << endl;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/DefaultFeatureWriter.h	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,34 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _DEFAULT_FEATURE_WRITER_H_
+#define _DEFAULT_FEATURE_WRITER_H_
+
+
+#include "transform/FeatureWriter.h"
+
+class DefaultFeatureWriter : public FeatureWriter
+{
+public:
+    virtual ~DefaultFeatureWriter() { }
+    virtual void write(QString trackid,
+                       const Transform &transform,
+                       const Vamp::Plugin::OutputDescriptor &output,
+                       const Vamp::Plugin::FeatureList &features,
+                       std::string summaryType = "");
+    virtual void finish() { }
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/FeatureExtractionManager.cpp	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,702 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "FeatureExtractionManager.h"
+
+#include <vamp-hostsdk/PluginChannelAdapter.h>
+#include <vamp-hostsdk/PluginBufferingAdapter.h>
+#include <vamp-hostsdk/PluginInputDomainAdapter.h>
+#include <vamp-hostsdk/PluginSummarisingAdapter.h>
+#include <vamp-hostsdk/PluginLoader.h>
+
+#include <iostream>
+
+using namespace std;
+
+using Vamp::Plugin;
+using Vamp::PluginBase;
+using Vamp::HostExt::PluginLoader;
+using Vamp::HostExt::PluginChannelAdapter;
+using Vamp::HostExt::PluginBufferingAdapter;
+using Vamp::HostExt::PluginInputDomainAdapter;
+using Vamp::HostExt::PluginSummarisingAdapter;
+
+#include "data/fileio/FileSource.h"
+#include "data/fileio/AudioFileReader.h"
+#include "data/fileio/AudioFileReaderFactory.h"
+#include "data/fileio/PlaylistFileReader.h"
+#include "base/TempDirectory.h"
+#include "base/ProgressPrinter.h"
+#include "transform/TransformFactory.h"
+#include "rdf/RDFTransformFactory.h"
+#include "transform/FeatureWriter.h"
+
+#include <QTextStream>
+#include <QFile>
+#include <QFileInfo>
+
+FeatureExtractionManager::FeatureExtractionManager() :
+    m_summariesOnly(false),
+    // We can read using an arbitrary fixed block size --
+    // PluginBufferingAdapter handles this for us.  It's likely to be
+    // quicker to use larger sizes than smallish ones like 1024
+    m_blockSize(16384),
+    m_defaultSampleRate(0),
+    m_sampleRate(0),
+    m_channels(1)
+{
+}
+
+FeatureExtractionManager::~FeatureExtractionManager()
+{
+    for (PluginMap::iterator pi = m_plugins.begin();
+         pi != m_plugins.end(); ++pi) {
+        delete pi->first;
+    }
+}
+
+void FeatureExtractionManager::setChannels(int channels)
+{
+    m_channels = channels;
+}
+
+void FeatureExtractionManager::setDefaultSampleRate(int sampleRate)
+{
+    m_defaultSampleRate = sampleRate;
+}
+
+static PluginSummarisingAdapter::SummaryType
+getSummaryType(string name)
+{
+    if (name == "min")      return PluginSummarisingAdapter::Minimum;
+    if (name == "max")      return PluginSummarisingAdapter::Maximum;
+    if (name == "mean")     return PluginSummarisingAdapter::Mean;
+    if (name == "median")   return PluginSummarisingAdapter::Median;
+    if (name == "mode")     return PluginSummarisingAdapter::Mode;
+    if (name == "sum")      return PluginSummarisingAdapter::Sum;
+    if (name == "variance") return PluginSummarisingAdapter::Variance;
+    if (name == "sd")       return PluginSummarisingAdapter::StandardDeviation;
+    if (name == "count")    return PluginSummarisingAdapter::Count;
+    return PluginSummarisingAdapter::UnknownSummaryType;
+}
+
+bool FeatureExtractionManager::setSummaryTypes(const set<string> &names,
+                                               bool summariesOnly,
+                                               const PluginSummarisingAdapter::SegmentBoundaries &boundaries)
+{
+    for (SummaryNameSet::const_iterator i = names.begin();
+         i != names.end(); ++i) {
+        if (getSummaryType(*i) == PluginSummarisingAdapter::UnknownSummaryType) {
+            cerr << "ERROR: Unknown summary type \"" << *i << "\"" << endl;
+            return false;
+        }
+    }
+    m_summaries = names;
+    m_summariesOnly = summariesOnly;
+    m_boundaries = boundaries;
+    return true;
+}
+
+bool FeatureExtractionManager::addFeatureExtractor
+(Transform transform, const vector<FeatureWriter*> &writers)
+{
+    //!!! exceptions rather than return values?
+
+    if (transform.getSampleRate() == 0) {
+        if (m_sampleRate == 0) {
+            cerr << "NOTE: Transform does not specify a sample rate, using default rate of " << m_defaultSampleRate << endl;
+            transform.setSampleRate(m_defaultSampleRate);
+            m_sampleRate = m_defaultSampleRate;
+        } else {
+            cerr << "NOTE: Transform does not specify a sample rate, using previous transform's rate of " << m_sampleRate << endl;
+            transform.setSampleRate(m_sampleRate);
+        }
+    }
+
+    if (m_sampleRate == 0) {
+        m_sampleRate = transform.getSampleRate();
+    }
+
+    if (transform.getSampleRate() != m_sampleRate) {
+        cerr << "WARNING: Transform sample rate " << transform.getSampleRate() << " does not match previously specified transform rate of " << m_sampleRate << " -- only a single rate is supported for each run" << endl;
+        cerr << "WARNING: Using previous rate of " << m_sampleRate << " for this transform as well" << endl;
+        transform.setSampleRate(m_sampleRate);
+    }
+
+    Plugin *plugin = 0;
+
+    // Remember what the original transform looked like, and index
+    // based on this -- because we may be about to fill in the zeros
+    // for step and block size, but we want any further copies with
+    // the same zeros to match this one
+    Transform originalTransform = transform;
+    
+    if (m_transformPluginMap.find(transform) == m_transformPluginMap.end()) {
+
+        // Test whether we already have a transform that is identical
+        // to this, except for the output requested and/or the summary
+        // type -- if so, they should share plugin instances (a vital
+        // optimisation)
+
+        for (TransformPluginMap::iterator i = m_transformPluginMap.begin();
+             i != m_transformPluginMap.end(); ++i) {
+            Transform test = i->first;
+            test.setOutput(transform.getOutput());
+            test.setSummaryType(transform.getSummaryType());
+            if (transform == test) {
+                cerr << "NOTE: Already have transform identical to this one (for \""
+                     << transform.getIdentifier().toStdString()
+                     << "\") in every detail except output identifier and/or "
+                     << "summary type; sharing its plugin instance" << endl;
+                plugin = i->second;
+                if (transform.getSummaryType() != Transform::NoSummary &&
+                    !dynamic_cast<PluginSummarisingAdapter *>(plugin)) {
+                    plugin = new PluginSummarisingAdapter(plugin);
+                    i->second = plugin;
+                }
+                break;
+            }
+        }
+
+        if (!plugin) {
+
+            TransformFactory *tf = TransformFactory::getInstance();
+
+            PluginBase *pb = tf->instantiatePluginFor(transform);
+            plugin = tf->downcastVampPlugin(pb);
+            if (!plugin) {
+                //!!! todo: handle non-Vamp plugins too, or make the main --list
+                // option print out only Vamp transforms
+                cerr << "ERROR: Failed to load plugin for transform \""
+                     << transform.getIdentifier().toStdString() << "\"" << endl;
+                delete pb;
+                return false;
+            }
+            
+            // We will provide the plugin with arbitrary step and
+            // block sizes (so that we can use the same read/write
+            // block size for all transforms), and to that end we use
+            // a PluginBufferingAdapter.  However, we need to know the
+            // underlying step size so that we can provide the right
+            // context for dense outputs.  (Although, don't forget
+            // that the PluginBufferingAdapter rewrites
+            // OneSamplePerStep outputs so as to use FixedSampleRate
+            // -- so it supplies the sample rate in the output
+            // feature.  I'm not sure whether we can easily use that.)
+
+            size_t pluginStepSize = plugin->getPreferredStepSize();
+            size_t pluginBlockSize = plugin->getPreferredBlockSize();
+
+            // adapt the plugin for buffering, channels, etc.
+            if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
+                plugin = new PluginInputDomainAdapter(plugin);
+            }
+
+            PluginBufferingAdapter *pba = new PluginBufferingAdapter(plugin);
+            plugin = pba;
+
+            if (transform.getStepSize() != 0) {
+                pba->setPluginStepSize(transform.getStepSize());
+            } else {
+                transform.setStepSize(pluginStepSize);
+            }
+
+            if (transform.getBlockSize() != 0) {
+                pba->setPluginBlockSize(transform.getBlockSize());
+            } else {
+                transform.setBlockSize(pluginBlockSize);
+            }
+
+            plugin = new PluginChannelAdapter(plugin);
+
+            if (!m_summaries.empty() ||
+                transform.getSummaryType() != Transform::NoSummary) {
+                PluginSummarisingAdapter *adapter =
+                    new PluginSummarisingAdapter(plugin);
+                adapter->setSummarySegmentBoundaries(m_boundaries);
+                plugin = adapter;
+            }
+
+            if (!plugin->initialise(m_channels, m_blockSize, m_blockSize)) {
+                cerr << "ERROR: Plugin initialise (channels = " << m_channels << ", stepSize = " << m_blockSize << ", blockSize = " << m_blockSize << ") failed." << endl;    
+                delete plugin;
+                return false;
+            }
+
+//            cerr << "Initialised plugin" << endl;
+
+            size_t actualStepSize = 0;
+            size_t actualBlockSize = 0;
+            pba->getActualStepAndBlockSizes(actualStepSize, actualBlockSize);
+            transform.setStepSize(actualStepSize);
+            transform.setBlockSize(actualBlockSize);
+
+            Plugin::OutputList outputs = plugin->getOutputDescriptors();
+            for (int i = 0; i < (int)outputs.size(); ++i) {
+
+//                cerr << "Newly initialised plugin output " << i << " has bin count " << outputs[i].binCount << endl;
+
+                m_pluginOutputs[plugin][outputs[i].identifier] = outputs[i];
+                m_pluginOutputIndices[outputs[i].identifier] = i;
+            }
+
+            cerr << "NOTE: Loaded and initialised plugin " << plugin
+                 << " for transform \""
+                 << transform.getIdentifier().toStdString() << "\"" << endl;
+        }
+
+        if (transform.getOutput() == "") {
+            transform.setOutput
+                (plugin->getOutputDescriptors()[0].identifier.c_str());
+        }
+
+        m_transformPluginMap[transform] = plugin;
+
+        if (!(originalTransform == transform)) {
+            m_transformPluginMap[originalTransform] = plugin;
+        }
+
+    } else {
+        
+        plugin = m_transformPluginMap[transform];
+    }
+
+    m_plugins[plugin][transform] = writers;
+
+    return true;
+}
+
+bool FeatureExtractionManager::addDefaultFeatureExtractor
+(TransformId transformId, const vector<FeatureWriter*> &writers)
+{
+    TransformFactory *tf = TransformFactory::getInstance();
+
+    if (m_sampleRate == 0) {
+        if (m_defaultSampleRate == 0) {
+            cerr << "ERROR: Default transform requested, but no default sample rate available" << endl;
+            return false;
+        } else {
+            cerr << "NOTE: Using default sample rate of " << m_defaultSampleRate << " for default transform" << endl;
+            m_sampleRate = m_defaultSampleRate;
+        }
+    }
+
+    Transform transform = tf->getDefaultTransformFor(transformId, m_sampleRate);
+
+    return addFeatureExtractor(transform, writers);
+}
+
+bool FeatureExtractionManager::addFeatureExtractorFromFile
+(QString transformXmlFile, const vector<FeatureWriter*> &writers)
+{
+    RDFTransformFactory factory
+        (QUrl::fromLocalFile(QFileInfo(transformXmlFile).absoluteFilePath())
+         .toString());
+    ProgressPrinter printer("Parsing transforms RDF file");
+    std::vector<Transform> transforms = factory.getTransforms(&printer);
+    if (!factory.isOK()) {
+        cerr << "WARNING: FeatureExtractionManager::addFeatureExtractorFromFile: Failed to parse transforms file: " << factory.getErrorString().toStdString() << endl;
+        if (factory.isRDF()) {
+            return false; // no point trying it as XML
+        }
+    }
+    if (!transforms.empty()) {
+        bool success = true;
+        for (int i = 0; i < (int)transforms.size(); ++i) {
+            if (!addFeatureExtractor(transforms[i], writers)) {
+                success = false;
+            }
+        }
+        return success;
+    }
+
+    QFile file(transformXmlFile);
+    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+        cerr << "ERROR: Failed to open transform XML file \""
+             << transformXmlFile.toStdString() << "\" for reading" << endl;
+        return false;
+    }
+
+    QTextStream *qts = new QTextStream(&file);
+    QString qs = qts->readAll();
+    delete qts;
+    file.close();
+
+    Transform transform(qs);
+
+    return addFeatureExtractor(transform, writers);
+}
+
+void FeatureExtractionManager::extractFeatures(QString audioSource)
+{
+    if (m_plugins.empty()) return;
+
+    ProgressPrinter printer("Retrieving audio data...");
+
+    FileSource source(audioSource, &printer);
+    if (!source.isAvailable()) {
+        cerr << "ERROR: File or URL \"" << audioSource.toStdString()
+             << "\" could not be located" << endl;
+        exit(1);
+    }
+    
+    source.waitForData();
+    
+    if (QFileInfo(audioSource).suffix().toLower() == "m3u") {
+        PlaylistFileReader reader(source);
+        if (reader.isOK()) {
+            vector<QString> files = reader.load();
+            for (int i = 0; i < (int)files.size(); ++i) {
+                extractFeatures(files[i]);
+            }
+            return;
+        } else {
+            cerr << "ERROR: Playlist \"" << audioSource.toStdString()
+                 << "\" could not be opened" << endl;
+            exit(1);
+        }
+    }
+
+    if (m_sampleRate == 0) {
+        cerr << "ERROR: Internal error in FeatureExtractionManager::extractFeatures: Plugin list is non-empty, but no sample rate set" << endl;
+        exit(1);
+    }
+
+    AudioFileReader *reader =
+        AudioFileReaderFactory::createReader(source, m_sampleRate, &printer);
+    
+    if (!reader) {
+        cerr << "ERROR: File or URL \"" << audioSource.toStdString()
+             << "\" could not be opened" << endl;
+        exit(1);
+    }
+
+    size_t channels = reader->getChannelCount();
+    
+    cerr << "Opened " << channels << "-channel file or URL \"" << audioSource.toStdString() << "\"" << endl;
+
+    // reject file if it has too few channels, plugin will handle if it has too many
+    if ((int)channels < m_channels) {
+        //!!! should not be terminating here!
+        cerr << "ERROR: File or URL \"" << audioSource.toStdString() << "\" has less than " << m_channels << " channels" << endl;
+        exit(1);
+    }
+    
+    // allocate audio buffers
+    float **data = new float *[m_channels];
+    for (int c = 0; c < m_channels; ++c) {
+        data[c] = new float[m_blockSize];
+    }
+
+    size_t frameCount = reader->getFrameCount();
+    
+//    cerr << "file has " << frameCount << " frames" << endl;
+
+    for (PluginMap::iterator pi = m_plugins.begin();
+         pi != m_plugins.end(); ++pi) {
+
+        Plugin *plugin = pi->first;
+
+//        std::cerr << "Calling reset on " << plugin << std::endl;
+        plugin->reset();
+
+        for (TransformWriterMap::iterator ti = pi->second.begin();
+             ti != pi->second.end(); ++ti) {
+
+            const Transform &transform = ti->first;
+
+            //!!! we may want to set the start and duration times for extraction
+            // in the transform record (defaults of zero indicate extraction
+            // from the whole file)
+//            transform.setStartTime(RealTime::zeroTime);
+//            transform.setDuration
+//                (RealTime::frame2RealTime(reader->getFrameCount(), m_sampleRate));
+
+            string outputId = transform.getOutput().toStdString();
+            if (m_pluginOutputs[plugin].find(outputId) ==
+                m_pluginOutputs[plugin].end()) {
+                //!!! throw?
+                cerr << "WARNING: Nonexistent plugin output \"" << outputId << "\" requested for transform \""
+                     << transform.getIdentifier().toStdString() << "\", ignoring this transform"
+                     << endl;
+/*
+                cerr << "Known outputs for all plugins are as follows:" << endl;
+                for (PluginOutputMap::const_iterator k = m_pluginOutputs.begin();
+                     k != m_pluginOutputs.end(); ++k) {
+                    cerr << "Plugin " << k->first << ": ";
+                    if (k->second.empty()) {
+                        cerr << "(none)";
+                    }
+                    for (OutputMap::const_iterator i = k->second.begin();
+                         i != k->second.end(); ++i) {
+                        cerr << "\"" << i->first << "\" ";
+                    }
+                    cerr << endl;
+                }
+*/
+            }
+        }
+    }
+    
+    long startFrame = 0;
+    long endFrame = frameCount;
+
+/*!!! No -- there is no single transform to pull this stuff from --
+ * the transforms may have various start and end times, need to be far
+ * cleverer about this if we're going to support them
+
+    RealTime trStartRT = transform.getStartTime();
+    RealTime trDurationRT = transform.getDuration();
+
+    long trStart = RealTime::realTime2Frame(trStartRT, m_sampleRate);
+    long trDuration = RealTime::realTime2Frame(trDurationRT, m_sampleRate);
+
+    if (trStart == 0 || trStart < startFrame) {
+        trStart = startFrame;
+    }
+
+    if (trDuration == 0) {
+        trDuration = endFrame - trStart;
+    }
+    if (trStart + trDuration > endFrame) {
+        trDuration = endFrame - trStart;
+    }
+
+    startFrame = trStart;
+    endFrame = trStart + trDuration;
+*/
+    
+    for (PluginMap::iterator pi = m_plugins.begin();
+         pi != m_plugins.end(); ++pi) { 
+
+        for (TransformWriterMap::const_iterator ti = pi->second.begin();
+             ti != pi->second.end(); ++ti) {
+        
+            const vector<FeatureWriter *> &writers = ti->second;
+            
+            for (int j = 0; j < (int)writers.size(); ++j) {
+                FeatureWriter::TrackMetadata m;
+                m.title = reader->getTitle();
+                m.maker = reader->getMaker();
+                writers[j]->setTrackMetadata(audioSource, m);
+            }
+        }
+    }
+
+    ProgressPrinter extractionProgress("Extracting and writing features...");
+    int progress = 0;
+
+    for (long i = startFrame; i < endFrame; i += m_blockSize) {
+        
+        //!!! inefficient, although much of the inefficiency may be
+        // susceptible to optimisation
+        
+        SampleBlock frames;
+        reader->getInterleavedFrames(i, m_blockSize, frames);
+        
+        // We have to do our own channel handling here; we can't just
+        // leave it to the plugin adapter because the same plugin
+        // adapter may have to serve for input files with various
+        // numbers of channels (so the adapter is simply configured
+        // with a fixed channel count, generally 1).
+
+        int rc = reader->getChannelCount();
+
+        for (int j = 0; j < m_blockSize; ++j) {
+            for (int c = 0; c < m_channels; ++c) {
+                int index;
+                if (c < rc) {
+                    index = j * rc + c;
+                    data[c][j] = 0.f;
+                } else {
+                    index = j * rc + (c % rc);
+                }
+                if (index < (int)frames.size()) {
+                    data[c][j] += frames[index];
+                }
+            }
+        }    
+
+        Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime
+            (i, m_sampleRate);
+        
+        for (PluginMap::iterator pi = m_plugins.begin();
+             pi != m_plugins.end(); ++pi) {
+
+            Plugin *plugin = pi->first;
+            Plugin::FeatureSet featureSet = plugin->process(data, timestamp);
+
+            if (!m_summariesOnly) {
+                writeFeatures(audioSource, plugin, featureSet);
+            }
+        }
+
+        int pp = progress;
+        progress = ((i - startFrame) * 100) / (endFrame - startFrame);
+        if (progress > pp) extractionProgress.setProgress(progress);
+    }
+    
+    for (PluginMap::iterator pi = m_plugins.begin();
+         pi != m_plugins.end(); ++pi) { 
+
+        Plugin *plugin = pi->first;
+        Plugin::FeatureSet featureSet = plugin->getRemainingFeatures();
+
+        if (!m_summariesOnly) {
+            writeFeatures(audioSource, plugin, featureSet);
+        }
+
+        if (!m_summaries.empty()) {
+            PluginSummarisingAdapter *adapter =
+                dynamic_cast<PluginSummarisingAdapter *>(plugin);
+            if (!adapter) {
+                cerr << "WARNING: Summaries requested, but plugin is not a summarising adapter" << endl;
+            } else {
+                for (SummaryNameSet::const_iterator sni = m_summaries.begin();
+                     sni != m_summaries.end(); ++sni) {
+                    featureSet.clear();
+                    //!!! problem here -- we are requesting summaries
+                    //!!! for all outputs, but they in principle have
+                    //!!! different averaging requirements depending
+                    //!!! on whether their features have duration or
+                    //!!! not
+                    featureSet = adapter->getSummaryForAllOutputs
+                        (getSummaryType(*sni),
+                         PluginSummarisingAdapter::ContinuousTimeAverage);
+                    writeFeatures(audioSource, plugin, featureSet,//!!! *sni);
+                                  Transform::stringToSummaryType(sni->c_str()));
+                }
+            }
+        }
+
+        writeSummaries(audioSource, plugin);
+    }
+
+    finish();
+    
+    extractionProgress.setProgress(100);
+
+    TempDirectory::getInstance()->cleanup();
+}
+
+void
+FeatureExtractionManager::writeSummaries(QString audioSource, Plugin *plugin)
+{
+    // caller should have ensured plugin is in m_plugins
+    PluginMap::iterator pi = m_plugins.find(plugin);
+
+    for (TransformWriterMap::const_iterator ti = pi->second.begin();
+         ti != pi->second.end(); ++ti) {
+        
+        const Transform &transform = ti->first;
+        const vector<FeatureWriter *> &writers = ti->second;
+
+        Transform::SummaryType summaryType = transform.getSummaryType();
+        PluginSummarisingAdapter::SummaryType pType =
+            (PluginSummarisingAdapter::SummaryType)summaryType;
+
+        if (transform.getSummaryType() == Transform::NoSummary) {
+            continue;
+        }
+
+        PluginSummarisingAdapter *adapter =
+            dynamic_cast<PluginSummarisingAdapter *>(plugin);
+        if (!adapter) {
+            cerr << "FeatureExtractionManager::writeSummaries: INTERNAL ERROR: Summary requested for transform, but plugin is not a summarising adapter" << endl;
+            continue;
+        }
+
+        Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs
+            (pType, PluginSummarisingAdapter::ContinuousTimeAverage);
+
+//        cout << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl;
+
+        writeFeatures(audioSource, plugin, featureSet, summaryType);
+    }
+}
+
+void FeatureExtractionManager::writeFeatures(QString audioSource,
+                                             Plugin *plugin,
+                                             const Plugin::FeatureSet &features,
+                                             Transform::SummaryType summaryType)
+{
+    // caller should have ensured plugin is in m_plugins
+    PluginMap::iterator pi = m_plugins.find(plugin);
+
+    for (TransformWriterMap::const_iterator ti = pi->second.begin();
+         ti != pi->second.end(); ++ti) {
+        
+        const Transform &transform = ti->first;
+        const vector<FeatureWriter *> &writers = ti->second;
+        
+        if (transform.getSummaryType() != Transform::NoSummary &&
+            m_summaries.empty() &&
+            summaryType == Transform::NoSummary) {
+            continue;
+        }
+
+        if (transform.getSummaryType() != Transform::NoSummary &&
+            summaryType != Transform::NoSummary &&
+            transform.getSummaryType() != summaryType) {
+            continue;
+        }
+
+        string outputId = transform.getOutput().toStdString();
+
+        if (m_pluginOutputs[plugin].find(outputId) ==
+            m_pluginOutputs[plugin].end()) {
+            continue;
+        }
+        
+        const Plugin::OutputDescriptor &desc =
+            m_pluginOutputs[plugin][outputId];
+        
+        int outputIndex = m_pluginOutputIndices[outputId];
+        Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex);
+        if (fsi == features.end()) continue;
+
+        for (int j = 0; j < (int)writers.size(); ++j) {
+            writers[j]->write
+                (audioSource, transform, desc, fsi->second,
+                 Transform::summaryTypeToString(summaryType).toStdString());
+        }
+    }
+}
+
+void FeatureExtractionManager::finish()
+{
+    for (PluginMap::iterator pi = m_plugins.begin();
+         pi != m_plugins.end(); ++pi) {
+
+        for (TransformWriterMap::iterator ti = pi->second.begin();
+             ti != pi->second.end(); ++ti) {
+        
+            vector<FeatureWriter *> &writers = ti->second;
+
+            for (int i = 0; i < (int)writers.size(); ++i) {
+                writers[i]->flush();
+                writers[i]->finish();
+            }
+        }
+    }
+}
+
+void FeatureExtractionManager::print(Transform transform) const
+{
+    QString qs;
+    QTextStream qts(&qs);
+    transform.toXml(qts);
+    cerr << qs.toStdString() << endl;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/FeatureExtractionManager.h	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,110 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _FEATURE_EXTRACTION_MANAGER_H_
+#define _FEATURE_EXTRACTION_MANAGER_H_
+
+#include <vector>
+#include <set>
+#include <string>
+
+#include <vamp-hostsdk/Plugin.h>
+#include <vamp-hostsdk/PluginSummarisingAdapter.h>
+#include <transform/Transform.h>
+
+using std::vector;
+using std::set;
+using std::string;
+using std::pair;
+using std::map;
+
+class FeatureWriter;
+
+class FeatureExtractionManager
+{
+public:
+    FeatureExtractionManager();
+    virtual ~FeatureExtractionManager();
+
+    void setChannels(int channels);
+    void setDefaultSampleRate(int sampleRate);
+
+    bool setSummaryTypes(const set<string> &summaryTypes,
+                         bool summariesOnly,
+                         const Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries &boundaries);
+
+    bool addFeatureExtractor(Transform transform,
+                             const vector<FeatureWriter*> &writers);
+
+    bool addFeatureExtractorFromFile(QString transformXmlFile,
+                                     const vector<FeatureWriter*> &writers);
+
+    bool addDefaultFeatureExtractor(TransformId transformId,
+                                    const vector<FeatureWriter*> &writers);
+
+    void extractFeatures(QString audioSource);
+
+private:
+    // A plugin may have many outputs, so we can have more than one
+    // transform requested for a single plugin.  The things we want to
+    // run in our process loop are plugins rather than their outputs,
+    // so we maintain a map from the plugins to the transforms desired
+    // of them and then iterate through this map
+
+    typedef map<Transform, vector<FeatureWriter *> > TransformWriterMap;
+    typedef map<Vamp::Plugin *, TransformWriterMap> PluginMap;
+    PluginMap m_plugins;
+        
+    // And a map back from transforms to their plugins.  Note that
+    // this is keyed by transform, not transform ID -- two differently
+    // configured transforms with the same ID must use different
+    // plugin instances.
+
+    typedef map<Transform, Vamp::Plugin *> TransformPluginMap;
+    TransformPluginMap m_transformPluginMap;
+
+    // Cache the plugin output descriptors, mapping from plugin to a
+    // map from output ID to output descriptor.
+    typedef map<string, Vamp::Plugin::OutputDescriptor> OutputMap;
+    typedef map<Vamp::Plugin *, OutputMap> PluginOutputMap;
+    PluginOutputMap m_pluginOutputs;
+
+    // Map from plugin output identifier to plugin output index
+    typedef map<string, int> OutputIndexMap;
+    OutputIndexMap m_pluginOutputIndices;
+
+    typedef set<std::string> SummaryNameSet;
+    SummaryNameSet m_summaries;
+    bool m_summariesOnly;
+    Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries m_boundaries;
+
+    void writeSummaries(QString audioSource, Vamp::Plugin *);
+
+    void writeFeatures(QString audioSource,
+                       Vamp::Plugin *,
+                       const Vamp::Plugin::FeatureSet &,
+                       Transform::SummaryType summaryType =
+                       Transform::NoSummary);
+    void finish();
+
+    int m_blockSize;
+    int m_defaultSampleRate;
+    int m_sampleRate;
+    int m_channels;
+    
+    void print(Transform transform) const;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/FeatureWriterFactory.cpp	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,49 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+
+#include "FeatureWriterFactory.h"
+
+#include "DefaultFeatureWriter.h"
+#include "rdf/RDFFeatureWriter.h"
+#include "AudioDBFeatureWriter.h"
+#include "transform/CSVFeatureWriter.h"
+
+set<string>
+FeatureWriterFactory::getWriterTags()
+{
+    set<string> tags;
+    tags.insert("default");
+    tags.insert("rdf");
+    tags.insert("audiodb");
+    tags.insert("csv");
+    return tags;
+}
+
+FeatureWriter *
+FeatureWriterFactory::createWriter(string tag)
+{
+    if (tag == "default") {
+        return new DefaultFeatureWriter();
+    } else if (tag == "rdf") {
+        return new RDFFeatureWriter();
+    } else if (tag == "audiodb") {
+        return new AudioDBFeatureWriter();
+    } else if (tag == "csv") {
+        return new CSVFeatureWriter();
+    }
+
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/FeatureWriterFactory.h	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,36 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _FEATURE_WRITER_FACTORY_H_
+#define _FEATURE_WRITER_FACTORY_H_
+
+#include <set>
+#include <string>
+
+using std::set;
+using std::string;
+
+class FeatureWriter;
+
+class FeatureWriterFactory
+{
+public:
+    static set<string> getWriterTags();
+    static FeatureWriter *createWriter(string tag);
+};
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/main.cpp	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,735 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include <vector>
+#include <string>
+#include <iostream>
+
+#include <QCoreApplication>
+#include <QSettings>
+#include <QStringList>
+#include <QString>
+#include <QFileInfo>
+#include <QDir>
+
+using std::cout;
+using std::cerr;
+using std::endl;
+using std::vector;
+using std::string;
+
+#include "base/Exceptions.h"
+#include "base/TempDirectory.h"
+
+#include "data/fileio/AudioFileReaderFactory.h"
+#include "data/fileio/PlaylistFileReader.h"
+
+#include "transform/Transform.h"
+#include "transform/TransformFactory.h"
+
+#include "FeatureExtractionManager.h"
+#include "transform/FeatureWriter.h"
+#include "FeatureWriterFactory.h"
+
+#include "rdf/RDFTransformFactory.h"
+
+#include <vamp-hostsdk/PluginSummarisingAdapter.h>
+
+#ifdef HAVE_FFTW3
+#include <fftw3.h>
+#endif
+
+// Desired options:
+//
+// * output preference:
+//   - all data in one file
+//   - one file per input file
+//   - one file per input file per transform
+//   - (any use for: one file per transform?)
+//
+// * output location:
+//   - same directory as input file
+//   - current directory
+//
+// * output filename: 
+//   - based on input (obvious choice for one file per input file modes)
+//   - specified on command line (obvious choice for all in one file mode)
+//
+// * output format: one or more of
+//   - RDF
+//   - AudioDB
+//   - Vamp Simple Host format
+//   - CSV
+//
+// * input handling:
+//   - run each transform on each input file separately
+//   - provide all input files to the same transform, one per channel
+//
+// * format-specific options:
+//   - RDF format: fancy/plain RDF
+//   - CSV format: separator, timestamp type
+//   note: do the output file/location also count as format-specific options?
+//   an output writer that wrote to a database would have different options...
+//
+// * debug level and progress output
+// 
+// * other potential options:
+//   - ignore version mismatches in Transform specifications
+//   - sample rate: force a given rate; use file rate instead of rate in
+//     Transform spec
+// 
+// * other potential instructions:
+//   - write out a skeleton Transform file for a specified plugin
+//   - write out skeleton RDF for a plugin library (i.e. do the job of
+//     RDF template_generator)
+//   - verify that RDF for a plugin library matches the plugin
+//
+// MAYBE:
+// * transform(s) to run:
+//   - supply transform file names on command line
+//   - use all transforms found in a given directory?
+//
+// MAYBE:
+// * input files to transform:
+//   - supply file names or URIs on command line
+//   - use all files in a given directory or tree
+
+static QString
+wrap(QString s, int len, int pfx = 0)
+{
+    QString ws;
+    QStringList sl(s.split(' '));
+    int i = 0, c = 0;
+    while (i < sl.size()) {
+        int wl = sl[i].length();
+        if (c + wl < len) {
+            if (c > 0) {
+                ws += ' ';
+                ++c;
+            }
+        } else {
+            if (c > 0) {
+                ws += '\n';
+                for (int j = 0; j < pfx; ++j) ws += ' ';
+                c = 0;
+            }
+        }
+        ws += sl[i];
+        c += wl;
+        ++i;
+    }
+    return ws;
+}
+
+void usage(QString myname)
+{
+    set<string> writers = FeatureWriterFactory::getWriterTags();
+        
+    cerr << endl;
+    cerr << "Sonic Annotator" << endl;
+    cerr << "A utility for batch feature extraction from audio files." << endl;
+    cerr << "Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London." << endl;
+    cerr << "Copyright 2007-2008 Queen Mary, University of London." << endl;
+    cerr << endl;
+    cerr << "This program is free software.  You may redistribute copies of it under the" << endl;
+    cerr << "terms of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>." << endl;
+    cerr << "This program is supplied with NO WARRANTY, to the extent permitted by law." << endl;
+    cerr << endl;
+    cerr << "  Usage: " << myname.toStdString()
+         << " [-mr] -t trans.xml [...] -w <writer> [...] <audio> [...]" << endl;
+    cerr << "         " << myname.toStdString()
+         << " [-mr] -T trans.txt [...] -w <writer> [...] <audio> [...]" << endl;
+    cerr << "         " << myname.toStdString()
+         << " -s <transform>" << endl;
+    cerr << "         " << myname.toStdString()
+         << " [-lh]" << endl;
+    cerr << endl;
+    cerr << "Where <audio> is an audio file or URL to use as input: either a local file" << endl;
+    cerr << "path, local \"file://\" URL, or remote \"http://\" or \"ftp://\" URL." << endl;
+    cerr << endl;
+
+    QString extensions = AudioFileReaderFactory::getKnownExtensions();
+    QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
+    if (!extlist.empty()) {
+        cerr << "The following audio file extensions are recognised:" << endl;
+        cerr << "  ";
+        int c = 2;
+        for (int i = 0; i < extlist.size(); ++i) {
+            QString ext = extlist[i];
+            if (ext.startsWith("*.")) ext = ext.right(ext.length()-2);
+            c += ext.length() + 2;
+            if (c >= 80) {
+                cerr << "\n  ";
+                c -= 78;
+            }
+            cerr << ext.toStdString();
+            if (i + 1 == extlist.size()) cerr << ".";
+            else cerr << ", ";
+        }
+        cerr << endl;
+    }
+
+    cerr << "Playlist files in M3U format are also supported." << endl;
+    cerr << endl;
+    cerr << "Transformation options:" << endl;
+    cerr << endl;
+    cerr << "  -t, --transform <T> Apply transform described in transform file <T> to" << endl;
+    cerr << "                      all input audio files.  You may supply this option" << endl;
+    cerr << "                      multiple times.  You must supply this option or -T at" << endl;
+    cerr << "                      least once for any work to be done.  Transform format" << endl;
+    cerr << "                      may be SV transform XML or Vamp transform RDF.  See" << endl;
+    cerr << "                      documentation for examples." << endl;
+    cerr << endl;
+    cerr << "  -T, --transforms <T> Apply all transforms described in transform files" << endl;
+    cerr << "                      whose names are listed in text file <T>.  You may supply" << endl;
+    cerr << "                      this option multiple times." << endl;
+    cerr << endl;
+    cerr << "  -d, --default <I>   Apply the default transform for transform id <I>.  This" << endl;
+    cerr << "                      is equivalent to generating a skeleton transform for this" << endl;
+    cerr << "                      id (using the -s option, below) and then applying that," << endl;
+    cerr << "                      unmodified, with the -t option in the normal way.  Note" << endl;
+    cerr << "                      that the results may vary as the implementation's default" << endl;
+    cerr << "                      processing parameters are not guaranteed.  Do not use" << endl;
+    cerr << "                      this in production systems.  You may supply this option" << endl;
+    cerr << "                      multiple times, and mix it with -t and -T." << endl;
+    cerr << endl;
+    cerr << "  -w, --writer <W>    Write output using writer type <W>." << endl;
+    cerr << "                      Supported writer types are: ";
+    for (set<string>::const_iterator i = writers.begin();
+         i != writers.end(); ) {
+        cerr << *i;
+        if (++i != writers.end()) cerr << ", ";
+        else cerr << ".";
+    }
+    cerr << endl;
+    cerr << "                      You may supply this option multiple times.  You must" << endl;
+    cerr << "                      supply this option at least once for any work to be done." << endl;
+    cerr << endl;
+    cerr << "  -S, --summary <S>   In addition to the result features, write summary feature" << endl;
+    cerr << "                      of summary type <S>." << endl;
+    cerr << "                      Supported summary types are: min, max, mean, median, mode," << endl;
+    cerr << "                      sum, variance, sd, count." << endl;
+    cerr << "                      You may supply this option multiple times." << endl;
+    cerr << endl;
+    cerr << "      --summary-only  Write only summary features; do not write the regular" << endl;
+    cerr << "                      result features." << endl;
+    cerr << endl;
+    cerr << "      --segments <A>,<B>[,...]" << endl;
+    cerr << "                      Summarise in segments, with segment boundaries" << endl;
+    cerr << "                      at A, B, ... seconds." << endl;
+    cerr << endl;
+
+/*!!! This feature not implemented yet (sniff)
+    cerr << "  -m, --multiplex     If multiple input audio files are given, use mono" << endl;
+    cerr << "                      mixdowns of all files as the input channels for a single" << endl;
+    cerr << "                      invocation of each transform, instead of running the" << endl;
+    cerr << "                      transform against all files separately." << endl;
+    cerr << endl;
+*/
+
+    cerr << "  -r, --recursive     If any of the <audio> arguments is found to be a local" << endl;
+    cerr << "                      directory, search the tree starting at that directory" << endl;
+    cerr << "                      for all supported audio files and take all of those as" << endl;
+    cerr << "                      input instead." << endl;
+    cerr << endl;
+    cerr << "Housekeeping options:" << endl;
+    cerr << endl;
+    cerr << "  -l, --list          List all known transform ids to standard output." << endl;
+    cerr << endl;
+    cerr << "  -s, --skeleton <I>  Generate a skeleton transform file for transform id <I>" << endl;
+    cerr << "                      and write it to standard output." << endl;
+    cerr << endl;
+    cerr << "  -h, --help          Show this help." << endl;
+
+    cerr << endl;
+    cerr << "If no -w (or --writer) options are supplied, either the -l -s or -h option (or" << endl;
+    cerr << "long equivalent) must be given instead." << endl;
+
+    for (set<string>::const_iterator i = writers.begin();
+         i != writers.end(); ++i) {
+        FeatureWriter *w = FeatureWriterFactory::createWriter(*i);
+        if (!w) {
+            cerr << "  (Internal error: failed to create writer of this type)" << endl;
+            continue;
+        }
+        FeatureWriter::ParameterList params = w->getSupportedParameters();
+        delete w;
+        if (params.empty()) {
+            continue;
+        }
+        cerr << endl;
+        cerr << "Additional options for writer type \"" << *i << "\":" << endl;
+        cerr << endl;
+        for (FeatureWriter::ParameterList::const_iterator j = params.begin();
+             j != params.end(); ++j) {
+            cerr << "  --" << *i << "-" << j->name << " ";
+            int spaceage = 16 - int(i->length()) - int(j->name.length());
+            if (j->hasArg) { cerr << "<X> "; spaceage -= 4; }
+            for (int k = 0; k < spaceage; ++k) cerr << " ";
+            QString s(j->description.c_str());
+            s = wrap(s, 56, 22);
+            cerr << s.toStdString() << endl;
+        }
+    }
+
+    cerr << endl;
+    exit(0);
+}
+
+void
+listTransforms()
+{
+    TransformList transforms =
+        TransformFactory::getInstance()->getAllTransformDescriptions();
+
+    for (TransformList::const_iterator iter = transforms.begin();
+         iter != transforms.end(); ++iter) {
+        const TransformDescription &transform = *iter;
+        if (transform.type == TransformDescription::Analysis) {
+            cout << transform.identifier.toStdString() << endl;
+        }
+    }
+}    
+
+void
+printSkeleton(QString id)
+{
+    Transform transform =
+        TransformFactory::getInstance()->getDefaultTransformFor(id);
+    cout << "@prefix xsd:      <http://www.w3.org/2001/XMLSchema#> ." << endl
+         << "@prefix vamp:     <http://purl.org/ontology/vamp/> ." << endl
+         << "@prefix :         <#> ." << endl << endl;
+    QString rdf = RDFTransformFactory::writeTransformToRDF
+        (transform, ":transform");
+    cout << rdf.toStdString();
+}    
+
+void
+findSourcesRecursive(QString dirname, QStringList &addTo, int &found)
+{
+    QDir dir(dirname);
+
+    QString printable = dir.dirName().left(20);
+    cerr << "\rScanning \"" << printable.toStdString() << "\"..."
+         << QString("                    ").left(20 - printable.length()).toStdString()
+         << " [" << found << " audio file(s)]";
+
+    QString extensions = AudioFileReaderFactory::getKnownExtensions();
+    QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
+
+    QStringList files = dir.entryList
+        (extlist, QDir::Files | QDir::Readable);
+    for (int i = 0; i < files.size(); ++i) {
+        addTo.push_back(dir.filePath(files[i]));
+        ++found;
+    }
+
+    QStringList subdirs = dir.entryList
+        (QStringList(), QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
+    for (int i = 0; i < subdirs.size(); ++i) {
+        findSourcesRecursive(dir.filePath(subdirs[i]), addTo, found);
+    }
+}
+
+
+int main(int argc, char **argv)
+{
+    QCoreApplication application(argc, argv);
+
+    QCoreApplication::setOrganizationName("QMUL");
+    QCoreApplication::setOrganizationDomain("qmul.ac.uk");
+    QCoreApplication::setApplicationName("Sonic Annotator");
+
+    QStringList args = application.arguments();
+    set<string> requestedWriterTags;
+    set<string> requestedTransformFiles;
+    set<string> requestedTransformListFiles;
+    set<string> requestedDefaultTransforms;
+    set<string> requestedSummaryTypes;
+//!!!    bool multiplex = false;
+    bool recursive = false;
+    bool list = false;
+    bool summaryOnly = false;
+    QString skeletonFor = "";
+    QString myname = args[0];
+    myname = QFileInfo(myname).baseName();
+    QStringList otherArgs;
+    Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries boundaries;
+
+    QString helpStr = myname + ": use -h or --help option for help";
+
+    for (int i = 1; i < args.size(); ++i) {
+
+        QString arg = args[i];
+        bool last = ((i + 1) == args.size());
+        
+        if (arg == "-h" || arg == "--help" || arg == "-?") {
+            usage(myname);
+        }
+
+        if (arg == "-w" || arg == "--writer") {
+            if (last || args[i+1].startsWith("-")) {
+                cerr << myname.toStdString() << ": argument expected for \""
+                     << arg.toStdString() << "\" option" << endl;
+                cerr << helpStr.toStdString() << endl;
+                exit(2);
+            } else {
+                string tag = args[++i].toStdString();
+                if (requestedWriterTags.find(tag) != requestedWriterTags.end()) {
+                    cerr << myname.toStdString() << ": NOTE: duplicate specification of writer type \"" << tag << "\" ignored" << endl;
+                } else {
+                    requestedWriterTags.insert(tag);
+                }
+                continue;
+            }
+        } else if (arg == "-t" || arg == "--transform") {
+            if (last || args[i+1].startsWith("-")) {
+                cerr << myname.toStdString() << ": argument expected for \""
+                     << arg.toStdString() << "\" option" << endl;
+                cerr << helpStr.toStdString() << endl;
+                exit(2);
+            } else {
+                string transform = args[++i].toStdString();
+                if (requestedTransformFiles.find(transform) !=
+                    requestedTransformFiles.end()) {
+                    cerr << myname.toStdString() << ": NOTE: duplicate specification of transform file \"" << transform << "\" ignored" << endl;
+                } else {
+                    requestedTransformFiles.insert(transform);
+                }
+                continue;
+            }
+        } else if (arg == "-T" || arg == "--transforms") {
+            if (last || args[i+1].startsWith("-")) {
+                cerr << myname.toStdString() << ": argument expected for \""
+                     << arg.toStdString() << "\" option" << endl;
+                cerr << helpStr.toStdString() << endl;
+                exit(2);
+            } else {
+                string transform = args[++i].toStdString();
+                if (requestedTransformListFiles.find(transform) !=
+                    requestedTransformListFiles.end()) {
+                    cerr << myname.toStdString() << ": NOTE: duplicate specification of transform list file \"" << transform << "\" ignored" << endl;
+                } else {
+                    requestedTransformListFiles.insert(transform);
+                }
+                continue;
+            }
+        } else if (arg == "-d" || arg == "--default") {
+            if (last || args[i+1].startsWith("-")) {
+                cerr << myname.toStdString() << ": argument expected for \""
+                     << arg.toStdString() << "\" option" << endl;
+                cerr << helpStr.toStdString() << endl;
+                exit(2);
+            } else {
+                string deft = args[++i].toStdString();
+                if (requestedDefaultTransforms.find(deft) !=
+                    requestedDefaultTransforms.end()) {
+                    cerr << myname.toStdString() << ": NOTE: duplicate specification of default transform \"" << deft << "\" ignored" << endl;
+                } else {
+                    requestedDefaultTransforms.insert(deft);
+                }
+                continue;
+            }
+        } else if (arg == "-S" || arg == "--summary") {
+            if (last || args[i+1].startsWith("-")) {
+                cerr << myname.toStdString() << ": argument expected for \""
+                     << arg.toStdString() << "\" option" << endl;
+                cerr << helpStr.toStdString() << endl;
+                exit(2);
+            } else {
+                string summary = args[++i].toStdString();
+                requestedSummaryTypes.insert(summary);
+                continue;
+            }
+        } else if (arg == "--summary-only") {
+            summaryOnly = true;
+            continue;
+        } else if (arg == "--segments") {
+            if (last) {
+                cerr << myname.toStdString() << ": argument expected for \""
+                     << arg.toStdString() << "\" option" << endl;
+                cerr << helpStr.toStdString() << endl;
+                exit(2);
+            } else {
+                string segmentSpec = args[++i].toStdString();
+                QStringList segmentStrs = QString(segmentSpec.c_str()).split(',');
+                for (int j = 0; j < segmentStrs.size(); ++j) {
+                    bool good = false;
+                    boundaries.insert(Vamp::RealTime::fromSeconds
+                                      (segmentStrs[j].toDouble(&good)));
+                    if (!good) {
+                        cerr << myname.toStdString() << ": segment boundaries must be numeric" << endl;
+                        cerr << helpStr.toStdString() << endl;
+                        exit(2);
+                    }
+                }
+            }
+/*!!!
+        } else if (arg == "-m" || arg == "--multiplex") {
+            multiplex = true;
+            cerr << myname.toStdString()
+                 << ": WARNING: Multiplex argument not yet implemented" << endl; //!!!
+            continue;
+*/
+        } else if (arg == "-r" || arg == "--recursive") {
+            recursive = true;
+            continue;
+        } else if (arg == "-l" || arg == "--list") {
+            list = true;
+            continue;
+        } else if (arg == "-s" || arg == "--skeleton") {
+            if (last || args[i+1].startsWith("-")) {
+                cerr << myname.toStdString() << ": usage: "
+                     << myname.toStdString() << " " << arg.toStdString()
+                     << " <transform>" << endl;
+                cerr << helpStr.toStdString() << endl;
+                exit(2);
+            } else {
+                skeletonFor = args[++i];
+                continue;
+            }
+        } else {
+            otherArgs.push_back(args[i]);
+        }
+    }
+
+    if (list) {
+        if (!requestedWriterTags.empty() || skeletonFor != "") {
+            cerr << helpStr.toStdString() << endl;
+            exit(2);
+        }
+        listTransforms();
+        exit(0);
+    }
+    if (skeletonFor != "") {
+        if (!requestedWriterTags.empty()) {
+            cerr << helpStr.toStdString() << endl;
+            exit(2);
+        }
+        printSkeleton(skeletonFor);
+        exit(0);
+    }
+
+    if (requestedTransformFiles.empty() &&
+        requestedTransformListFiles.empty() &&
+        requestedDefaultTransforms.empty()) {
+        cerr << myname.toStdString()
+             << ": no transform(s) specified" << endl;
+        cerr << helpStr.toStdString() << endl;
+        exit(2);
+    }
+
+    if (requestedWriterTags.empty()) {
+        cerr << myname.toStdString()
+             << ": no writer(s) specified" << endl;
+        cerr << helpStr.toStdString() << endl;
+        exit(2);
+    }
+
+    if (!boundaries.empty()) {
+        if (requestedSummaryTypes.empty()) {
+            cerr << myname.toStdString()
+                 << ": summary segment boundaries provided, but no summary type specified"
+                 << endl;
+            cerr << helpStr.toStdString() << endl;
+            exit(2);
+        }
+    }
+
+#ifdef HAVE_FFTW3
+    QSettings settings;
+    settings.beginGroup("FFTWisdom");
+    QString wisdom = settings.value("wisdom").toString();
+    if (wisdom != "") {
+        fftw_import_wisdom_from_string(wisdom.toLocal8Bit().data());
+    }
+    settings.endGroup();
+#endif
+
+    FeatureExtractionManager manager;
+
+    if (!requestedSummaryTypes.empty()) {
+        if (!manager.setSummaryTypes(requestedSummaryTypes,
+                                     summaryOnly,
+                                     boundaries)) {
+            cerr << myname.toStdString()
+                 << ": failed to set requested summary types" << endl;
+            exit(1);
+        }
+    }
+    
+    // the manager dictates the sample rate and number of channels
+    // to work at - files with too few channels are rejected,
+    // too many channels are handled as usual by the Vamp plugin
+
+    //!!! Review this: although we probably do want to fix the channel
+    // count here, we don't necessarily want to fix the rate: it's
+    // specified in the Transform file.
+
+    manager.setDefaultSampleRate(44100);
+    manager.setChannels(1);
+    
+    vector<FeatureWriter *> writers;
+
+    for (set<string>::const_iterator i = requestedWriterTags.begin();
+         i != requestedWriterTags.end(); ++i) {
+
+        FeatureWriter *writer = FeatureWriterFactory::createWriter(*i);
+
+        if (!writer) {
+            cerr << myname.toStdString() << ": unknown feature writer \""
+                 << *i << "\"" << endl;
+            cerr << helpStr.toStdString() << endl;
+            exit(2);
+        }
+
+        map<string, string> writerArgs;
+        FeatureWriter::ParameterList pl(writer->getSupportedParameters());
+
+        for (int k = 0; k < pl.size(); ++k) {
+            
+            string argbase = pl[k].name;
+            QString literal = QString("--%1-%2")
+                .arg(i->c_str()).arg(argbase.c_str());
+            
+            for (int j = 0; j < otherArgs.size(); ) {
+                
+                if (otherArgs[j] != literal) {
+                    ++j;
+                    continue;
+                }
+                    
+                otherArgs.removeAt(j);
+                    
+                if (pl[k].hasArg) {
+                    if (j < otherArgs.size()) {
+                        writerArgs[argbase] = otherArgs[j].toStdString();
+                        otherArgs.removeAt(j);
+                    } else {
+                        cerr << myname.toStdString() << ": "
+                             << "argument required for \""
+                             << literal.toStdString() << "\" option"
+                             << endl;
+                        cerr << helpStr.toStdString() << endl;
+                        exit(2);
+                    }
+                } else {
+                    writerArgs[argbase] = "";
+                }
+            }
+        }
+        
+        writer->setParameters(writerArgs);
+        
+        writers.push_back(writer);
+    }
+
+    for (int i = 0; i < otherArgs.size(); ++i) {
+        if (otherArgs[i].startsWith("-")) {
+            cerr << myname.toStdString() << ": unknown option \""
+                 << otherArgs[i].toStdString() << "\"" << endl;
+            cerr << helpStr.toStdString() << endl;
+            exit(2);
+        }
+    }
+
+    if (otherArgs.empty()) {
+        cerr << myname.toStdString() << ": no input(s) specified" << endl;
+        cerr << helpStr.toStdString() << endl;
+        exit(2);
+    }    
+
+    for (set<string>::const_iterator i = requestedTransformListFiles.begin();
+         i != requestedTransformListFiles.end(); ++i) {
+        PlaylistFileReader reader(i->c_str());
+        if (reader.isOK()) {
+            vector<QString> files = reader.load();
+            for (int j = 0; j < files.size(); ++j) {
+                requestedTransformFiles.insert(files[j].toStdString());
+            }
+        } else {
+            cerr << myname.toStdString() << ": failed to read template list file \"" << *i << "\"" << endl;
+            exit(2);
+        }
+    }
+
+    bool haveFeatureExtractor = false;
+    
+    for (set<string>::const_iterator i = requestedTransformFiles.begin();
+         i != requestedTransformFiles.end(); ++i) {
+        if (manager.addFeatureExtractorFromFile(i->c_str(), writers)) {
+            haveFeatureExtractor = true;
+        }
+    }
+
+    for (set<string>::const_iterator i = requestedDefaultTransforms.begin();
+         i != requestedDefaultTransforms.end(); ++i) {
+        if (manager.addDefaultFeatureExtractor(i->c_str(), writers)) {
+            haveFeatureExtractor = true;
+        }
+    }
+
+    if (!haveFeatureExtractor) {
+        cerr << myname.toStdString() << ": no feature extractors added" << endl;
+        exit(2);
+    }
+
+    QStringList sources;
+    if (!recursive) {
+        sources = otherArgs;
+    } else {
+        for (QStringList::const_iterator i = otherArgs.begin();
+             i != otherArgs.end(); ++i) {
+            if (QDir(*i).exists()) {
+                cerr << "Directory found and recursive flag set, scanning for audio files..." << endl;
+                int found = 0;
+                findSourcesRecursive(*i, sources, found);
+                cerr << "\rDone, found " << found << " supported audio file(s)                    " << endl;
+            } else {
+                sources.push_back(*i);
+            }
+        }
+    }
+
+    for (QStringList::const_iterator i = sources.begin();
+         i != sources.end(); ++i) {
+        std::cerr << "Extracting features for: \"" << i->toStdString() << "\"" << std::endl;
+        try {
+            manager.extractFeatures(*i);
+        } catch (FailedToOpenFile f) {
+            cerr << "ERROR: Failed to open output file for feature writer: "
+                 << f.what() << endl;
+            break;
+        }
+    }
+    
+    for (int i = 0; i < writers.size(); ++i) delete writers[i];
+
+#ifdef HAVE_FFTW3
+    settings.beginGroup("FFTWisdom");
+    char *cwisdom = fftw_export_wisdom_to_string();
+    if (cwisdom) {
+        settings.setValue("wisdom", cwisdom);
+        fftw_free(cwisdom);
+    }
+    settings.endGroup();
+#endif
+
+    TempDirectory::getInstance()->cleanup();
+    
+    return 0;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/runner.pro	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,71 @@
+
+TEMPLATE = app
+
+SV_UNIT_PACKAGES = vamp vamp-hostsdk samplerate mad id3tag oggz fishsound sndfile lrdf redland rasqal raptor
+
+#linux-g++:LIBS += -Wl,-Bstatic
+#linux-g++:DEFINES += BUILD_STATIC
+
+load(../sonic-visualiser/sv.prf)
+
+LIBPATH += /usr/local/lib
+
+CONFIG += sv qt thread warn_on stl rtti exceptions console
+QT += xml network
+QT -= gui
+
+# Using the "console" CONFIG flag above should ensure this happens for
+# normal Windows builds, but the console feature doesn't get picked up
+# in my local cross-compile setup because qmake itself doesn't know to
+# look for win32 features
+win32-x-g++:QMAKE_LFLAGS += -Wl,-subsystem,console
+
+# If you have compiled your Vamp plugin SDK with FFTW (using its
+# HAVE_FFTW3 flag), you can define the same flag here to ensure the
+# program saves and restores FFTW wisdom in its configuration properly
+#
+#DEFINES += HAVE_FFTW3
+
+TARGET = sonic-annotator
+
+DEPENDPATH += . ../sonic-visualiser i18n main
+INCLUDEPATH += . ../sonic-visualiser main
+LIBPATH = ../sonic-visualiser/audioio ../sonic-visualiser/data ../sonic-visualiser/plugin ../sonic-visualiser/rdf ../sonic-visualiser/transform ../sonic-visualiser/base ../sonic-visualiser/system $$LIBPATH
+
+QMAKE_CXXFLAGS_RELEASE += -fmessage-length=80 -fdiagnostics-show-location=every-line
+
+contains(DEFINES, BUILD_STATIC):LIBS -= -ljack
+
+#LIBS = -lsvaudioio -lsvdata -lsvtransform -lsvplugin -lsvrdf -lsvbase -lsvsystem  $$LIBS
+LIBS = -lsvdata -lsvtransform -lsvplugin -lsvrdf -lsvdata -lsvbase -lsvsystem  $$LIBS
+
+PRE_TARGETDEPS += ../sonic-visualiser/audioio/libsvaudioio.a \
+                  ../sonic-visualiser/data/libsvdata.a \
+                  ../sonic-visualiser/transform/libsvtransform.a \
+                  ../sonic-visualiser/plugin/libsvplugin.a \
+                  ../sonic-visualiser/rdf/libsvrdf.a \
+                  ../sonic-visualiser/base/libsvbase.a \
+                  ../sonic-visualiser/system/libsvsystem.a
+
+OBJECTS_DIR = tmp_obj
+MOC_DIR = tmp_moc
+
+# Input
+HEADERS += \
+	AudioDBFeatureWriter.h \
+        FeatureWriterFactory.h  \
+        DefaultFeatureWriter.h \
+        FeatureExtractionManager.h
+
+SOURCES += \
+	main.cpp \
+	DefaultFeatureWriter.cpp \
+	FeatureExtractionManager.cpp \
+        AudioDBFeatureWriter.cpp \
+        FeatureWriterFactory.cpp
+
+
+
+
+# Restore dynamic linkage, in case we went static earlier
+linux-g++:LIBS += -Wl,-Bdynamic -lpthread -ldl -lz
--- a/test-queries/test-query	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-
-PREFIX vamp: <http://purl.org/ontology/vamp/>
-PREFIX examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>
-
-SELECT ?transform ?library ?library_id ?plugin
-FROM <file:///work/runner/transforms/percussiononsets.n3>
-FROM <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins>
-
-WHERE {
-      ?transform a vamp:Transform .
-      ?transform vamp:plugin ?plugin .
-      ?plugin a vamp:Plugin .
-      ?library a vamp:PluginLibrary .
-      ?library vamp:identifier ?library_id .
-      ?library vamp:available_plugin ?plugin .
-}
-
--- a/test-queries/test-query-dense-output	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-
-PREFIX vamp: <http://purl.org/ontology/vamp/>
-PREFIX mo: <http://purl.org/ontology/mo/>
-PREFIX af: <http://purl.org/ontology/af/>
-
-SELECT ?signal_source ?feature_signal_type ?value
-FROM <file:///share/music/wav/12-You Look So Fine.n3>
-
-WHERE {
-      ?signal mo:available_as ?signal_source .
-      ?signal a mo:Signal .
-      ?signal af:signal_feature ?feature .
-      ?feature a ?feature_signal_type.
-      ?feature af:value ?value .
-}
-
--- a/test-queries/test-query-plugin-for-transform	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-
-PREFIX vamp: <http://purl.org/ontology/vamp/>
-
-SELECT ?transform ?plugin ?step_size ?block_size ?window_type ?program ?sample_rate ?start ?duration ?param_id ?param_value
-FROM <file:///work/runner/transforms/percussiononsets.n3>
-
-WHERE {
-      ?transform a vamp:Transform .
-      ?transform vamp:plugin ?plugin .
-      OPTIONAL { ?transform vamp:step_size ?step_size } .
-      OPTIONAL { ?transform vamp:block_size ?block_size } .
-      OPTIONAL { ?transform vamp:window_type ?window_type } .
-      OPTIONAL { ?transform vamp:program ?program } .
-      OPTIONAL { ?transform vamp:sample_rate ?sample_rate } .
-      OPTIONAL { ?transform vamp:start ?start } .
-      OPTIONAL { ?transform vamp:duration ?duration } .
-      OPTIONAL {
-          ?transform vamp:parameter ?param .
-	  ?param vamp:identifier ?param_id .
-	  ?param vamp:value ?param_value
-      }
-}
-
--- a/test-queries/test-query-plugin-output-types	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-
-PREFIX vamp: <http://purl.org/ontology/vamp/>
-PREFIX examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>
-
-SELECT ?output_id ?output_type ?feature_type ?event_type ?unit
-FROM <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins>
-
-WHERE {
-      ?plugin a vamp:Plugin .
-      ?plugin vamp:identifier "percussiononsets" .
-      ?plugin vamp:output_descriptor ?output .
-      ?output vamp:identifier ?output_id .
-      ?output a ?output_type .
-      OPTIONAL { ?output vamp:computes_feature_type ?feature_type } .
-      OPTIONAL { ?output vamp:computes_event_type ?event_type } .
-      OPTIONAL { ?output vamp:unit ?unit } .
-}
-
--- a/test-queries/test-query-pluginid-for-rdf	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-
-PREFIX vamp: <http://purl.org/ontology/vamp/>
-PREFIX examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>
-
-SELECT ?plugin ?library_id ?plugin_id
-FROM <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins>
-
-WHERE {
-      ?plugin a vamp:Plugin .
-      ?plugin vamp:identifier ?plugin_id .
-      OPTIONAL { ?library a vamp:PluginLibrary .
-            ?library vamp:identifier ?library_id .
-	          ?library vamp:available_plugin ?plugin . }
-}
-
--- a/test-queries/test-query-transform-params	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-PREFIX vamp: <http://purl.org/ontology/vamp/>
-
-SELECT ?transform ?param_id ?param_value
-
-FROM <file:///work/runner/transforms/percussiononsets.n3>
-
-WHERE {
-   ?transform vamp:parameter_binding ?binding . 
-   ?binding vamp:parameter ?param .
-   ?param vamp:identifier ?param_id .
-   ?binding vamp:value ?param_value .
- }
-
--- a/test-queries/test-query-transforms	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-
-PREFIX vamp: <http://purl.org/ontology/vamp/>
-
-SELECT ?transform ?plugin ?output ?program
-       ?step_size ?block_size ?window_type
-       ?sample_rate ?start ?duration
-
-FROM <file:///work/runner/transforms/percussiononsets.n3>
-
-WHERE {
-  ?transform a vamp:Transform ;
-             vamp:plugin ?plugin .
-  OPTIONAL { ?transform vamp:output ?output } .
-  OPTIONAL { ?transform vamp:program ?program } .
-  OPTIONAL { ?transform vamp:step_size ?step_size } .
-  OPTIONAL { ?transform vamp:block_size ?block_size } .
-  OPTIONAL { ?transform vamp:window_type ?window_type } .
-  OPTIONAL { ?transform vamp:sample_rate ?sample_rate } .
-  OPTIONAL { ?transform vamp:start ?start } .
-  OPTIONAL { ?transform vamp:duration ?duration }
-}
--- a/test-queries/test-roqet	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-
-PREFIX : <file:///work/runner/transforms/test.n3#>
-
-SELECT ?result ?mand
-
-FROM <file:///work/runner/transforms/test.n3>
-
-WHERE {
-  ?result :mand ?mand .
-  OPTIONAL { ?result :opt1 ?opt1 } .
-  OPTIONAL { ?result :opt2 ?opt2 } .
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/misc-queries/test-query	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,17 @@
+
+PREFIX vamp: <http://purl.org/ontology/vamp/>
+PREFIX examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>
+
+SELECT ?transform ?library ?library_id ?plugin
+FROM <file:///work/runner/transforms/percussiononsets.n3>
+FROM <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins>
+
+WHERE {
+      ?transform a vamp:Transform .
+      ?transform vamp:plugin ?plugin .
+      ?plugin a vamp:Plugin .
+      ?library a vamp:PluginLibrary .
+      ?library vamp:identifier ?library_id .
+      ?library vamp:available_plugin ?plugin .
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/misc-queries/test-query-dense-output	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,16 @@
+
+PREFIX vamp: <http://purl.org/ontology/vamp/>
+PREFIX mo: <http://purl.org/ontology/mo/>
+PREFIX af: <http://purl.org/ontology/af/>
+
+SELECT ?signal_source ?feature_signal_type ?value
+FROM <file:///share/music/wav/12-You Look So Fine.n3>
+
+WHERE {
+      ?signal mo:available_as ?signal_source .
+      ?signal a mo:Signal .
+      ?signal af:signal_feature ?feature .
+      ?feature a ?feature_signal_type.
+      ?feature af:value ?value .
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/misc-queries/test-query-plugin-for-transform	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,23 @@
+
+PREFIX vamp: <http://purl.org/ontology/vamp/>
+
+SELECT ?transform ?plugin ?step_size ?block_size ?window_type ?program ?sample_rate ?start ?duration ?param_id ?param_value
+FROM <file:///work/runner/transforms/percussiononsets.n3>
+
+WHERE {
+      ?transform a vamp:Transform .
+      ?transform vamp:plugin ?plugin .
+      OPTIONAL { ?transform vamp:step_size ?step_size } .
+      OPTIONAL { ?transform vamp:block_size ?block_size } .
+      OPTIONAL { ?transform vamp:window_type ?window_type } .
+      OPTIONAL { ?transform vamp:program ?program } .
+      OPTIONAL { ?transform vamp:sample_rate ?sample_rate } .
+      OPTIONAL { ?transform vamp:start ?start } .
+      OPTIONAL { ?transform vamp:duration ?duration } .
+      OPTIONAL {
+          ?transform vamp:parameter ?param .
+	  ?param vamp:identifier ?param_id .
+	  ?param vamp:value ?param_value
+      }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/misc-queries/test-query-plugin-output-types	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,18 @@
+
+PREFIX vamp: <http://purl.org/ontology/vamp/>
+PREFIX examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>
+
+SELECT ?output_id ?output_type ?feature_type ?event_type ?unit
+FROM <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins>
+
+WHERE {
+      ?plugin a vamp:Plugin .
+      ?plugin vamp:identifier "percussiononsets" .
+      ?plugin vamp:output_descriptor ?output .
+      ?output vamp:identifier ?output_id .
+      ?output a ?output_type .
+      OPTIONAL { ?output vamp:computes_feature_type ?feature_type } .
+      OPTIONAL { ?output vamp:computes_event_type ?event_type } .
+      OPTIONAL { ?output vamp:unit ?unit } .
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/misc-queries/test-query-pluginid-for-rdf	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,15 @@
+
+PREFIX vamp: <http://purl.org/ontology/vamp/>
+PREFIX examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>
+
+SELECT ?plugin ?library_id ?plugin_id
+FROM <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins>
+
+WHERE {
+      ?plugin a vamp:Plugin .
+      ?plugin vamp:identifier ?plugin_id .
+      OPTIONAL { ?library a vamp:PluginLibrary .
+            ?library vamp:identifier ?library_id .
+	          ?library vamp:available_plugin ?plugin . }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/misc-queries/test-query-transform-params	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,13 @@
+PREFIX vamp: <http://purl.org/ontology/vamp/>
+
+SELECT ?transform ?param_id ?param_value
+
+FROM <file:///work/runner/transforms/percussiononsets.n3>
+
+WHERE {
+   ?transform vamp:parameter_binding ?binding . 
+   ?binding vamp:parameter ?param .
+   ?param vamp:identifier ?param_id .
+   ?binding vamp:value ?param_value .
+ }
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/misc-queries/test-query-transforms	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,21 @@
+
+PREFIX vamp: <http://purl.org/ontology/vamp/>
+
+SELECT ?transform ?plugin ?output ?program
+       ?step_size ?block_size ?window_type
+       ?sample_rate ?start ?duration
+
+FROM <file:///work/runner/transforms/percussiononsets.n3>
+
+WHERE {
+  ?transform a vamp:Transform ;
+             vamp:plugin ?plugin .
+  OPTIONAL { ?transform vamp:output ?output } .
+  OPTIONAL { ?transform vamp:program ?program } .
+  OPTIONAL { ?transform vamp:step_size ?step_size } .
+  OPTIONAL { ?transform vamp:block_size ?block_size } .
+  OPTIONAL { ?transform vamp:window_type ?window_type } .
+  OPTIONAL { ?transform vamp:sample_rate ?sample_rate } .
+  OPTIONAL { ?transform vamp:start ?start } .
+  OPTIONAL { ?transform vamp:duration ?duration }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/misc-queries/test-roqet	Thu Dec 11 10:26:12 2008 +0000
@@ -0,0 +1,13 @@
+
+PREFIX : <file:///work/runner/transforms/test.n3#>
+
+SELECT ?result ?mand
+
+FROM <file:///work/runner/transforms/test.n3>
+
+WHERE {
+  ?result :mand ?mand .
+  OPTIONAL { ?result :opt1 ?opt1 } .
+  OPTIONAL { ?result :opt2 ?opt2 } .
+}
+
--- a/vamp-example-plugins:percussiononsets.n3	Thu Dec 11 10:22:33 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-@prefix rdfs:   <http://www.w3.org/2000/01/rdf-schema#>.
-@prefix xsd:    <http://www.w3.org/2001/XMLSchema#>.
-@prefix vamp:   <http://www.vamp-plugins.org/ontology/> .
-@prefix vampex: <http://www.vamp-plugins.org/examples/> .
-@prefix owl:    <http://www.w3.org/2002/07/owl#> .
-@prefix dc:     <http://purl.org/dc/elements/1.1/> .
-@prefix af:     <http://purl.org/ontology/af/> .
-@prefix foaf:   <http://xmlns.com/foaf/0.1/> .
-@prefix cc:     <http://web.resource.org/cc/> .
-@prefix thisplug: <http://www.vamp-plugins.org/examples/percussiononsets#>.
-@prefix :       <> .
-
-<>  a   vamp:PluginDescription ;
-    foaf:maker          <http://chrissutton.org/me> ;
-    foaf:primaryTopic   vampex:percussiononsets .
-
-vampex:percussiononsets a   vamp:Plugin ;
-    dc:title                "Simple Percussion Onset Detector" ;
-    dc:description          "Detect percussive note onsets by identifying broadband energy rises";
-    foaf:maker              <http://www.all-day-breakfast.com/cannam> ; #we'll pretend this is his URI
-    cc:license              <http://creativecommons.org/licenses/BSD/> ;
-    vamp:identifier         "percussiononsets" ; # The Vamp identifier for the plugin
-    vamp:vamp_API_version   vamp:version_v1.1b ; # Made up - this plugin doesn't actually specify it
-    owl:versionInfo         "2" ;
-    vamp:input_domain       vamp:TimeDomain ; # Made up - this plugin doesn't actually specify it
-
-    vamp:parameter_descriptor   thisplug:pd1 ;
-    vamp:parameter_descriptor   thisplug:pd2 ;
-    vamp:output_descriptor      thisplug:od1 ;
-    vamp:output_descriptor      thisplug:od2 .
-
-
-thisplug:pd1    a       vamp:ParameterDescriptor ;
-    vamp:identifier     "threshold" ;
-    dc:title            "Energy Rise threshold" ;
-    dc:description      "Energy rise within a frequency bin necessary to count toward broadband total" ;
-    dc:format           "dB" ;
-    vamp:minValue       0 ; #might be useful when interpreting plugin output
-    vamp:maxValue       20 ;
-    vamp:defaultValue   3 .
-
-
-thisplug:pd2    a       vamp:ParameterDescriptor ;
-    vamp:identifier     "sensitivity" ;
-    dc:title            "Sensitivity" ;
-    dc:description      "Sensitivity of peak detector applied to broadband detection function" ;
-    dc:format           "%" ;
-    vamp:minValue       0 ; #might be useful when interpreting plugin output
-    vamp:maxValue       100 ;
-    vamp:defaultValue   40 .
-    
-thisplug:od1    a       vamp:OutputDescriptor ;
-    vamp:identifier     "onsets" ;
-    dc:title            "Onsets" ;
-    dc:description      "Percussive note onset locations" ;
-    dc:format           "" ;
-    vamp:fixed_bin_count "true" ;
-    vamp:bin_count      0 ;
-    vamp:sample_type    vamp:VariableSampleRate ;
-    vamp:computes_event_type    af:Onset . # af:Onset is pending some thought
-
-thisplug:od2    a       vamp:OutputDescriptor ;
-    vamp:identifier     "detectionfunction" ;
-    dc:title            "Detection Function" ;
-    dc:description      "Broadband energy rise detection function";
-    dc:format           "" ;
-    vamp:fixed_bin_count "true" ;
-    vamp:bin_count      1 ;
-    vamp:sample_type    vamp:OneSamplePerStep ;
-    vamp:computes_feature_type  af:OnsetDetectionFunction .