changeset 0:581b1b150a4d

* copy to sonic-annotator
author Chris Cannam
date Thu, 11 Dec 2008 10:22:33 +0000
parents
children 92911f967a16
files AudioDBFeatureWriter.cpp AudioDBFeatureWriter.h DefaultFeatureWriter.cpp DefaultFeatureWriter.h FeatureExtractionManager.cpp FeatureExtractionManager.h FeatureWriterFactory.cpp FeatureWriterFactory.h README deploy_mac.sh feature-description-example.n3 main.cpp qm-keydetector.n3 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/audio/3clicks.mp3 tests/audio/3clicks.ogg tests/audio/3clicks8.wav tests/audio/6clicks8.wav tests/expected/transforms-audioformat-percussiononsets-mp3.csv tests/expected/transforms-audioformat-percussiononsets-ogg.csv tests/expected/transforms-audioformat-percussiononsets-wav.csv tests/expected/transforms-basic-percussiononsets-no-parameters-default-output.csv tests/expected/transforms-basic-percussiononsets-no-parameters.csv tests/expected/transforms-basic-percussiononsets-set-parameters.csv tests/expected/transforms-basic-percussiononsets-set-sample-rate.csv tests/expected/transforms-basic-percussiononsets-set-step-and-block-size.csv tests/expected/transforms-basic-percussiononsets.csv tests/expected/transforms-basic-skeleton-1.csv tests/expected/transforms-summaries-percussiononsets-all-summaries-only.csv tests/expected/transforms-summaries-percussiononsets-from-rdf.csv tests/expected/transforms-summaries-percussiononsets-from-rdf.n3 tests/expected/transforms-summaries-percussiononsets-with-mean.csv tests/expected/transforms-summaries-percussiononsets.csv tests/test-as-advertised.sh tests/test-audioformat.sh tests/test-helpfulflags.sh tests/test-rdf-destinations.sh tests/test-rdf-writer.sh tests/test-summaries.sh tests/test-supportprogs.sh tests/test-transforms-basic.sh tests/test.sh tests/transforms/transforms-as-advertised-percussiononsets-detectionfunction.n3 tests/transforms/transforms-as-advertised-percussiononsets-onsets.n3 tests/transforms/transforms-audioformat-percussiononsets.n3 tests/transforms/transforms-basic-percussiononsets-no-parameters-default-output.n3 tests/transforms/transforms-basic-percussiononsets-no-parameters-default-output.xml tests/transforms/transforms-basic-percussiononsets-no-parameters.n3 tests/transforms/transforms-basic-percussiononsets-no-parameters.xml tests/transforms/transforms-basic-percussiononsets-set-parameters.n3 tests/transforms/transforms-basic-percussiononsets-set-parameters.xml tests/transforms/transforms-basic-percussiononsets-set-sample-rate.n3 tests/transforms/transforms-basic-percussiononsets-set-sample-rate.xml tests/transforms/transforms-basic-percussiononsets-set-step-and-block-size.n3 tests/transforms/transforms-basic-percussiononsets-set-step-and-block-size.xml tests/transforms/transforms-basic-percussiononsets.n3 tests/transforms/transforms-basic-percussiononsets.xml tests/transforms/transforms-nosummaries-percussiononsets-detectionfunction.n3 tests/transforms/transforms-rdf-writer-percussiononsets-detectionfunction.n3 tests/transforms/transforms-rdf-writer-percussiononsets-onsets.n3 tests/transforms/transforms-summaries-percussiononsets-detectionfunction.n3 transforms/chromagram.xml transforms/percussiononsets-detectionfunction.n3 transforms/percussiononsets-detectionfunction.xml transforms/percussiononsets.n3 transforms/percussiononsets.xml transforms/segmentation.n3 transforms/spectralcentroid.xml vamp-example-plugins:percussiononsets.n3
diffstat 77 files changed, 4719 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AudioDBFeatureWriter.cpp	Thu Dec 11 10:22:33 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/AudioDBFeatureWriter.h	Thu Dec 11 10:22:33 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/DefaultFeatureWriter.cpp	Thu Dec 11 10:22:33 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/DefaultFeatureWriter.h	Thu Dec 11 10:22:33 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/FeatureExtractionManager.cpp	Thu Dec 11 10:22:33 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/FeatureExtractionManager.h	Thu Dec 11 10:22:33 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/FeatureWriterFactory.cpp	Thu Dec 11 10:22:33 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/FeatureWriterFactory.h	Thu Dec 11 10:22:33 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/README	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,287 @@
+
+Sonic Annotator
+===============
+
+Sonic Annotator is a utility program for batch feature extraction from
+audio files.  It runs Vamp audio analysis plugins on audio files, and
+can write the result features in a selection of formats.
+
+
+A Quick Tutorial
+----------------
+
+To use Sonic Annotator, you need to tell it three things: what audio
+files to extract features from; what features to extract; and how and
+where to write the results.  You can also optionally tell it to
+summarise the features.
+
+
+1. What audio files to extract features from
+
+Sonic Annotator accepts a list of audio files on the command line.
+Any argument that is not understood as a supported command-line option
+will be taken to be the name of an audio file.  Any number of files
+may be listed.
+
+Several common audio file formats are supported, including MP3, Ogg,
+and a number of PCM formats such as WAV and AIFF.  AAC is supported on
+OS/X only, and only if not DRM protected.  WMA is not supported.
+
+File paths do not have to be local; you can also provide remote HTTP
+or FTP URLs for Sonic Annotator to retrieve.
+
+Sonic Annotator also accepts the names of playlist files (.m3u
+extension) and will process every file found in the playlist.
+
+Finally, you can provide a local directory path instead of a file,
+together with the -r (recursive) option, for Sonic Annotator to
+process every audio file found in that directory or any of its
+subdirectories.
+
+
+2. What features to extract
+
+Sonic Annotator applies "transforms" to its input audio files, where a
+transform (in this terminology) consists of a Vamp plugin together
+with a certain set of parameters and a specified execution context:
+step and block size, sample rate, etc.
+
+(See http://www.vamp-plugins.org/ for more information about Vamp
+plugins.)
+
+To use a particular transform, specify its filename on the command
+line with the -t option.
+
+Transforms are usually described in RDF, following the transform part
+of the Vamp plugin ontology (http://purl.org/ontology/vamp/).  A
+Transform may use any Vamp plugin that is currently installed and
+available on the system.  You can obtain a list of available plugin
+outputs by running Sonic Annotator with the -l option, and you can
+obtain a skeleton transform description for one of these plugins with
+the -s option.
+
+For example, if the example plugins from the Vamp plugin SDK are
+available and no other plugins are installed, you might have an
+exchange like this:
+
+  $ sonic-annotator -l
+  vamp:vamp-example-plugins:amplitudefollower:amplitude
+  vamp:vamp-example-plugins:fixedtempo:acf
+  vamp:vamp-example-plugins:fixedtempo:detectionfunction
+  vamp:vamp-example-plugins:fixedtempo:filtered_acf
+  vamp:vamp-example-plugins:fixedtempo:tempo
+  vamp:vamp-example-plugins:fixedtempo:candidates
+  vamp:vamp-example-plugins:percussiononsets:detectionfunction
+  vamp:vamp-example-plugins:percussiononsets:onsets
+  vamp:vamp-example-plugins:powerspectrum:powerspectrum
+  vamp:vamp-example-plugins:spectralcentroid:linearcentroid
+  vamp:vamp-example-plugins:spectralcentroid:logcentroid
+  vamp:vamp-example-plugins:zerocrossing:counts
+  vamp:vamp-example-plugins:zerocrossing:zerocrossings
+  $ sonic-annotator -s vamp:vamp-example-plugins:fixedtempo:tempo
+  @prefix xsd:      <http://www.w3.org/2001/XMLSchema#> .
+  @prefix vamp:     <http://purl.org/ontology/vamp/> .
+  @prefix :         <#> .
+
+  :transform a vamp:Transform ;
+      vamp:plugin <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#fixedtempo> ;
+      vamp:step_size "64"^^xsd:int ; 
+      vamp:block_size "256"^^xsd:int ; 
+      vamp:parameter_binding [
+          vamp:parameter [ vamp:identifier "maxbpm" ] ;
+          vamp:value "190"^^xsd:float ;
+      ] ;
+      vamp:parameter_binding [
+          vamp:parameter [ vamp:identifier "maxdflen" ] ;
+          vamp:value "10"^^xsd:float ;
+      ] ;
+      vamp:parameter_binding [
+          vamp:parameter [ vamp:identifier "minbpm" ] ;
+          vamp:value "50"^^xsd:float ;
+      ] ;
+      vamp:output <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#fixedtempo_output_tempo> .
+  $
+
+The output of -s is an RDF/Turtle document describing the default
+settings for the Tempo output of the Fixed Tempo Estimator plugin in
+the Vamp plugin SDK.
+
+(The exact format of the RDF printed may differ -- e.g. if the
+plugin's RDF description is not installed and so its "home" URI is not
+known -- but the result should be functionally equivalent to this.)
+
+You could run this transform by saving the RDF to a file and
+specifying that file with -t:
+
+  $ sonic-annotator -s vamp:vamp-example-plugins:fixedtempo:tempo > test.n3
+  $ sonic-annotator -t test.n3 audio.wav -w csv --csv-stdout
+  (... logging output on stderr, then ...)
+  "audio.wav",0.002902494,5.196916099,68.7916,"68.8 bpm"
+  $
+
+The single line of output above consists of the audio file name, the
+timestamp and duration for a single feature, the value of that feature
+(the estimated tempo of the given region of time from that file, in
+bpm -- the plugin in question performs a single tempo estimation and
+nothing else) and the feature's label.
+
+A quicker way to achieve the above is to use the -d (default) option
+to tell Sonic Annotator to use directly the default configuration for
+a named transform:
+
+  $ sonic-annotator -d vamp:vamp-example-plugins:fixedtempo:tempo audio.wav -w csv --csv-stdout
+  (... some log output on stderr, then ...)
+  "audio.wav",0.002902494,5.196916099,68.7916,"68.8 bpm"
+  $
+
+Although handy for experimentation, the -d option is inadvisable in
+any "production" situation because the plugin configuration is not
+guaranteed to be the same each time (for example if an updated version
+of a plugin changes some of its defaults).  It's better to save a
+well-defined transform to file and refer to that, even if it is simply
+the transform created by the skeleton option.
+
+To run more than one transform on the same audio files, just put more
+than one set of transform RDF descriptions in the same file, or give
+the -t option more than once with separate transform description
+files.  Remember that if you want to specify more than one transform
+in the same file, they will need to have distinct URIs (that is, the
+":transform" part of the example above, which may be any arbitrary
+name, must be distinct for each described transform).
+
+
+3. How and where to write the results
+
+Sonic Annotator supports various different output modules (and it is
+fairly easy for the developer to add new ones).  You have to choose at
+least one output module; use the -w (writer) option to do so.  Each
+module has its own set of parameters which can be adjusted on the
+command line, as well as its own default rules about where to write
+the results.
+
+The following writers are currently supported.  (Others exist, but are
+not properly implemented or not supported.)
+
+ * csv
+
+   Writes the results into comma-separated data files.
+
+   One file is created for each transform applied to each input audio
+   file, named after the input audio file and transform name with .csv
+   suffix and ":" replaced by "_" throughout, placed in the same
+   directory as the audio file.
+
+   To instruct Sonic Annotator to place the output files in another
+   location, use --csv-basedir with a directory name.
+
+   To write a single file with all data in it, use --csv-one-file.
+
+   To write all data to stdout instead of to a file, use --csv-stdout.
+
+   Sonic Annotator will not write to an output file that already
+   exists.  If you want to make it do this, use --csv-force to
+   overwrite or --csv-append to append to it.
+
+   The data generated consists of one line for each result feature,
+   containing the feature timestamp, feature duration if present, all
+   of the feature's bin values in order, followed by the feature's
+   label if present.  If the --csv-one-file or --csv-stdout option is
+   specified, then an additional column will appear before any of the
+   above, containing the audio file name from which the feature was
+   extracted, if it differs from that of the previous row.
+
+   The default column separator is a comma; you can specify a
+   different one with the --csv-separator option.
+
+ * rdf
+
+   Writes the results into RDF/Turtle documents following the Audio
+   Features ontology (http://purl.org/ontology/af/).
+
+   One file is created for each input audio file containing the
+   features extracted by all transforms applied to that file, named
+   after the input audio file with .n3 extension, placed in the same
+   directory as the audio file.
+
+   To instruct Sonic Annotator to place the output files in another
+   location, use --rdf-basedir with a directory name.
+
+   To write a single file with all data (from all input audio files)
+   in it, use --rdf-one-file.
+
+   To write one file for each transform applied to each input audio
+   file, named after the input audio file and transform name with .n3
+   suffix and ":" replaced by "_" throughout, use --rdf-many-files.
+
+   To write all data to stdout instead of to a file, use --rdf-stdout.
+
+   Sonic Annotator will not write to an output file that already
+   exists.  If you want to make it do this, use --rdf-force to
+   overwrite or --rdf-append to append to it.
+
+   Sonic Annotator will use plugin description RDF if available to
+   enhance its output (for example identifying note onset times as
+   note onset times, if the plugin's RDF says that is what it
+   produces, rather than writing them as plain events).  Best results
+   will be obtained if an RDF document is provided with your plugins
+   (for example, vamp-example-plugins.n3) and you have this installed
+   in the same location as the plugins.  To override this enhanced
+   output and write plain events for all features, use --rdf-plain.
+
+   The output RDF will include an available_as property linking the
+   results to the original audio signal URI.  By default, this will
+   point to the URI of the file or resource containing the audio that
+   Sonic Annotator processed, such as the file:/// location on disk.
+   To override this, for example to process a local copy of a file
+   while generating RDF that describes a copy of it available on a
+   network, you can use the --rdf-signal-uri option to specify an
+   alternative signal URI.
+
+
+4. Optionally, how to summarise the features
+
+Sonic Annotator can also calculate and write summaries of features,
+such as mean and median values.
+
+To obtain a summary as well as the feature results, just use the -S
+option, naming the type of summary you want (min, max, mean, median,
+mode, sum, variance, sd or count).  You can also tell it to produce
+only the summary, not the individual features, with --summary-only.
+
+Alternatively, you can specify a summary in a transform description.
+The following example tells Sonic Annotator to write both the times of
+note onsets estimated by the simple percussion onset detector example
+plugin, and the variance of the plugin's onset detection function.
+(It will only process the audio file and run the plugin once.)
+
+  @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+  @prefix vamp: <http://purl.org/ontology/vamp/>.
+  @prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+  @prefix : <#>.
+
+  :transform1 a vamp:Transform;
+     vamp:plugin examples:percussiononsets ;
+     vamp:output examples:percussiononsets_output_onsets .
+
+  :transform0 a vamp:Transform;
+     vamp:plugin examples:percussiononsets ;
+     vamp:output examples:percussiononsets_output_detectionfunction ;
+     vamp:summary_type "variance" .
+
+Sonic Annotator can also summarise in segments -- if you provide a
+comma-separated list of times as an argument to the --segments option,
+it will calculate one summary for each segment bounded by the times
+you provided.  For example,
+
+  $ sonic-annotator -d vamp:vamp-example-plugins:percussiononsets:detectionfunction -S variance --sumary-only --segments 1,2,3 -w csv --csv-stdout audio.wav
+  (... some log output on stderr, then ...)
+  ,0.000000000,1.000000000,variance,1723.99,"(variance, continuous-time average)"
+  ,1.000000000,1.000000000,variance,1981.75,"(variance, continuous-time average)"
+  ,2.000000000,1.000000000,variance,1248.79,"(variance, continuous-time average)"
+  ,3.000000000,7.031020407,variance,1030.06,"(variance, continuous-time average)"
+
+Here the first row contains a summary covering the time period from 0
+to 1 second, the second from 1 to 2 seconds, the third from 2 to 3
+seconds and the fourth from 3 seconds to the end of the (short) audio
+file.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy_mac.sh	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,34 @@
+#!/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
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/feature-description-example.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,82 @@
+
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix dc: <http://purl.org/dc/elements/1.1/> .
+@prefix mo: <http://purl.org/ontology/mo/> .
+@prefix af: <http://purl.org/ontology/af/> .
+@prefix event: <http://purl.org/NET/c4dm/event.owl#> .
+@prefix tl: <http://purl.org/NET/c4dm/timeline.owl#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix : <#> .
+
+
+# Describe the signal we're annotating, and associate it with a
+# timeline (the timeline is named as :base_timeline but never given
+# any attributes in this document).  Any feature that is associated
+# with the same timeline will be taken as derived from this signal.
+
+:audio_signal a mo:Signal ;
+        mo:available_as <file:///path/to/myfile.wav> ;
+        mo:time :signal_interval .
+
+:signal_interval a tl:Interval ;
+                 tl:onTimeLine :base_timeline ;
+                 tl:beginsAt "PT0"^^xsd:duration .
+
+
+# A sparse feature event.
+
+:event0 a af:Onset ;
+        event:time :time0 .
+
+:time0 a tl:Instant ;
+       tl:onTimeLine :base_timeline ;
+       tl:at "PT0.185759637S"^^xsd:duration .
+
+# Alternatively we could write that with a blank node.
+
+:event1 a af:Onset ;
+        event:time [
+                   a tl:Instant ;
+                   tl:onTimeLine :base_timeline ;
+                   tl:at "PT0.510839002S"^^xsd:duration ;
+                   ] .
+
+
+# For a dense feature, we need an appropriately sampled, windowed timeline.
+
+:feature_timeline a tl:DiscreteTimeLine .
+
+:feature_timeline_map a tl:UniformSamplingWindowingMap ;
+               tl:rangeTimeLine :feature_timeline ;
+               tl:domainTimeLine :base_timeline ;
+               tl:sampleRate "44100"^^xsd:int ;
+               tl:windowLength "1024"^^xsd:int ;
+               tl:hopSize "512"^^xsd:int .
+
+
+# ... and an interval, defined in terms of hops (the discrete steps of
+# the windowed timeline).
+
+:feature_interval a tl:Interval ;
+                  tl:onTimeLine :feature_timeline ;
+                  tl:beginsAt "0"^^xsd:int ;
+                  tl:duration "5634"^^xsd:int .
+
+
+# Then our feature is a signal that is on the interval we just
+# described.  We associate it explicitly with the original audio
+# signal, although presumably we could leave the association implicit,
+# to be derived from the relationships between timelines, just as it
+# is for the sparse features above.
+
+:audio_signal af:signal_feature :feature1 .
+
+:feature_signal_type rdfs:subClassOf af:DetectionFunction ;
+          dc:title "Detection Function from Simple Percussion Onset Detector" .
+
+:feature1 a :feature_signal_type ;
+          mo:time :feature_interval ;
+          af:dimensions "12 12345" ;
+          af:value "0 0 0 0 0 0 0 1 2 3 1 24 236 123213 (etc)" .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Thu Dec 11 10:22:33 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/qm-keydetector.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,77 @@
+@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> .
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner.pro	Thu Dec 11 10:22:33 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-queries/test-query	Thu Dec 11 10:22:33 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/test-queries/test-query-dense-output	Thu Dec 11 10:22:33 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/test-queries/test-query-plugin-for-transform	Thu Dec 11 10:22:33 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/test-queries/test-query-plugin-output-types	Thu Dec 11 10:22:33 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/test-queries/test-query-pluginid-for-rdf	Thu Dec 11 10:22:33 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/test-queries/test-query-transform-params	Thu Dec 11 10:22:33 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/test-queries/test-query-transforms	Thu Dec 11 10:22:33 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/test-queries/test-roqet	Thu Dec 11 10:22:33 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 } .
+}
+
Binary file tests/audio/3clicks.mp3 has changed
Binary file tests/audio/3clicks.ogg has changed
Binary file tests/audio/3clicks8.wav has changed
Binary file tests/audio/6clicks8.wav has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-audioformat-percussiononsets-mp3.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,4 @@
+0.058049886
+0.824308390
+1.706666666
+2.461315192
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-audioformat-percussiononsets-ogg.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,3 @@
+0.777868480
+1.648616780
+2.414875283
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-audioformat-percussiononsets-wav.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,3 @@
+0.777868480
+1.648616780
+2.414875283
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-basic-percussiononsets-no-parameters-default-output.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,3 @@
+0.777868480
+1.648616780
+2.414875283
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-basic-percussiononsets-no-parameters.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,3 @@
+0.777868480
+1.648616780
+2.414875283
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-basic-percussiononsets-set-parameters.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,13 @@
+0.162539682
+0.290249433
+0.394739229
+0.777868480
+1.648616780
+2.414875283
+3.041814058
+3.134693877
+3.157913832
+3.599092970
+3.831292517
+4.504671201
+4.748480725
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-basic-percussiononsets-set-sample-rate.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,3 @@
+0.766258503
+1.648616780
+2.414875283
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-basic-percussiononsets-set-step-and-block-size.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,3 @@
+0.780294784
+1.648662131
+2.415328797
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-basic-percussiononsets.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,3 @@
+0.777868480
+1.648616780
+2.414875283
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-basic-skeleton-1.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,3 @@
+0.777868480
+1.648616780
+2.414875283
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-summaries-percussiononsets-all-summaries-only.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,9 @@
+0.000000000,count,448
+0.000000000,max,503
+0.000000000,mean,162.123
+0.000000000,median,168
+0.000000000,min,0
+0.000000000,mode,0
+0.000000000,sd,46.82
+0.000000000,sum,72631
+0.000000000,variance,2192.12
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-summaries-percussiononsets-from-rdf.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,7 @@
+0.777868480
+1.648616780
+2.414875283
+0.000000000,mean,668.509
+0.000000000,mean,162.123
+0.000000000,median,168
+0.000000000,mode,0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-summaries-percussiononsets-from-rdf.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,159 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix dc: <http://purl.org/dc/elements/1.1/> .
+@prefix mo: <http://purl.org/ontology/mo/> .
+@prefix af: <http://purl.org/ontology/af/> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix event: <http://purl.org/NET/c4dm/event.owl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix tl: <http://purl.org/NET/c4dm/timeline.owl#> .
+@prefix vamp: <http://purl.org/ontology/vamp/> .
+
+:transform_0_onsets
+    vamp:output <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#percussiononsets_output_onsets> ;
+    vamp:plugin <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#percussiononsets> ;
+    vamp:sample_rate "44100"^^xsd:float ;
+    a vamp:Transform .
+
+<file:///work/runner/tests/audio/3clicks8.wav>
+    a mo:AudioFile .
+
+:signal_1
+    mo:available_as <file:///work/runner/tests/audio/3clicks8.wav> ;
+    mo:time [
+        tl:onTimeLine :signal_timeline_1 ;
+        a tl:Interval
+    ] ;
+    a mo:Signal .
+
+:event_2
+    event:time [
+        tl:at "PT0.777868480S"^^xsd:duration ;
+        tl:onTimeLine :signal_timeline_1 ;
+        a tl:Instant
+    ] ;
+    vamp:computed_by :transform_0_onsets ;
+    a af:Onset .
+
+:event_3
+    event:time [
+        tl:at "PT1.648616780S"^^xsd:duration ;
+        tl:onTimeLine :signal_timeline_1 ;
+        a tl:Instant
+    ] ;
+    vamp:computed_by :transform_0_onsets ;
+    a af:Onset .
+
+:event_4
+    event:time [
+        tl:at "PT2.414875283S"^^xsd:duration ;
+        tl:onTimeLine :signal_timeline_1 ;
+        a tl:Instant
+    ] ;
+    vamp:computed_by :transform_0_onsets ;
+    a af:Onset .
+
+:transform_5_detectionfunction
+    vamp:block_size "4096"^^xsd:int ;
+    vamp:output <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#percussiononsets_output_detectionfunction> ;
+    vamp:plugin <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#percussiononsets> ;
+    vamp:sample_rate "44100"^^xsd:float ;
+    vamp:step_size "2048"^^xsd:int ;
+    vamp:summary_type "mean" ;
+    a vamp:Transform .
+
+:event_type_6
+    dc:description "Broadband energy rise detection function" ;
+    dc:format "" ;
+    dc:title "Detection Function" ;
+    rdfs:subClassOf event:Event .
+
+:event_7
+    event:time [
+        tl:beginsAt "PT0.000000000S"^^xsd:duration ;
+        tl:duration "PT5.201269840S"^^xsd:duration ;
+        tl:onTimeLine :signal_timeline_1 ;
+        a tl:Interval
+    ] ;
+    af:feature "668.509" ;
+    vamp:computed_by :transform_5_detectionfunction ;
+    a :event_type_6 ;
+    rdfs:label "(mean value, continuous-time average)" .
+
+:transform_8_detectionfunction
+    vamp:block_size "1024"^^xsd:int ;
+    vamp:output <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#percussiononsets_output_detectionfunction> ;
+    vamp:plugin <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#percussiononsets> ;
+    vamp:sample_rate "44100"^^xsd:float ;
+    vamp:step_size "512"^^xsd:int ;
+    vamp:summary_type "mean" ;
+    a vamp:Transform .
+
+:event_type_9
+    dc:description "Broadband energy rise detection function" ;
+    dc:format "" ;
+    dc:title "Detection Function" ;
+    rdfs:subClassOf event:Event .
+
+:event_10
+    event:time [
+        tl:beginsAt "PT0.000000000S"^^xsd:duration ;
+        tl:duration "PT5.201269840S"^^xsd:duration ;
+        tl:onTimeLine :signal_timeline_1 ;
+        a tl:Interval
+    ] ;
+    af:feature "162.123" ;
+    vamp:computed_by :transform_8_detectionfunction ;
+    a :event_type_9 ;
+    rdfs:label "(mean value, continuous-time average)" .
+
+:transform_11_detectionfunction
+    vamp:output <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#percussiononsets_output_detectionfunction> ;
+    vamp:plugin <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#percussiononsets> ;
+    vamp:sample_rate "44100"^^xsd:float ;
+    vamp:summary_type "median" ;
+    a vamp:Transform .
+
+:event_type_12
+    dc:description "Broadband energy rise detection function" ;
+    dc:format "" ;
+    dc:title "Detection Function" ;
+    rdfs:subClassOf event:Event .
+
+:event_13
+    event:time [
+        tl:beginsAt "PT0.000000000S"^^xsd:duration ;
+        tl:duration "PT5.201269840S"^^xsd:duration ;
+        tl:onTimeLine :signal_timeline_1 ;
+        a tl:Interval
+    ] ;
+    af:feature "168" ;
+    vamp:computed_by :transform_11_detectionfunction ;
+    a :event_type_12 ;
+    rdfs:label "(median value, continuous-time average)" .
+
+:transform_14_detectionfunction
+    vamp:output <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#percussiononsets_output_detectionfunction> ;
+    vamp:plugin <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#percussiononsets> ;
+    vamp:sample_rate "44100"^^xsd:float ;
+    vamp:summary_type "mode" ;
+    a vamp:Transform .
+
+:event_type_15
+    dc:description "Broadband energy rise detection function" ;
+    dc:format "" ;
+    dc:title "Detection Function" ;
+    rdfs:subClassOf event:Event .
+
+:event_16
+    event:time [
+        tl:beginsAt "PT0.000000000S"^^xsd:duration ;
+        tl:duration "PT5.201269840S"^^xsd:duration ;
+        tl:onTimeLine :signal_timeline_1 ;
+        a tl:Interval
+    ] ;
+    af:feature "0" ;
+    vamp:computed_by :transform_14_detectionfunction ;
+    a :event_type_15 ;
+    rdfs:label "(modal value, continuous-time average)" .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-summaries-percussiononsets-with-mean.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,449 @@
+0.000000000,0
+0.011609977,170
+0.023219954,184
+0.034829931,166
+0.046439909,174
+0.058049886,179
+0.069659863,162
+0.081269841,157
+0.092879818,156
+0.104489795,182
+0.116099773,168
+0.127709750,168
+0.139319727,155
+0.150929705,200
+0.162539682,163
+0.174149659,178
+0.185759637,164
+0.197369614,166
+0.208979591,137
+0.220589569,155
+0.232199546,177
+0.243809523,145
+0.255419501,172
+0.267029478,155
+0.278639455,201
+0.290249433,166
+0.301859410,169
+0.313469387,158
+0.325079365,169
+0.336689342,155
+0.348299319,187
+0.359909297,177
+0.371519274,149
+0.383129251,196
+0.394739229,160
+0.406349206,174
+0.417959183,165
+0.429569160,149
+0.441179138,181
+0.452789115,180
+0.464399092,156
+0.476009070,163
+0.487619047,188
+0.499229024,173
+0.510839002,160
+0.522448979,168
+0.534058956,154
+0.545668934,155
+0.557278911,161
+0.568888888,163
+0.580498866,172
+0.592108843,168
+0.603718820,181
+0.615328798,167
+0.626938775,172
+0.638548752,158
+0.650158730,172
+0.661768707,167
+0.673378684,180
+0.684988662,188
+0.696598639,160
+0.708208616,166
+0.719818594,158
+0.731428571,176
+0.743038548,153
+0.754648526,173
+0.766258503,449
+0.777868480,30
+0.789478458,70
+0.801088435,181
+0.812698412,165
+0.824308390,174
+0.835918367,181
+0.847528344,179
+0.859138321,154
+0.870748299,169
+0.882358276,150
+0.893968253,189
+0.905578231,159
+0.917188208,172
+0.928798185,157
+0.940408163,167
+0.952018140,169
+0.963628117,155
+0.975238095,189
+0.986848072,178
+0.998458049,168
+1.010068027,169
+1.021678004,178
+1.033287981,157
+1.044897959,166
+1.056507936,162
+1.068117913,170
+1.079727891,186
+1.091337868,178
+1.102947845,178
+1.114557823,155
+1.126167800,173
+1.137777777,156
+1.149387755,158
+1.160997732,170
+1.172607709,167
+1.184217687,160
+1.195827664,180
+1.207437641,153
+1.219047619,154
+1.230657596,171
+1.242267573,159
+1.253877551,194
+1.265487528,170
+1.277097505,157
+1.288707482,184
+1.300317460,156
+1.311927437,173
+1.323537414,184
+1.335147392,168
+1.346757369,182
+1.358367346,147
+1.369977324,164
+1.381587301,163
+1.393197278,165
+1.404807256,181
+1.416417233,172
+1.428027210,169
+1.439637188,165
+1.451247165,166
+1.462857142,158
+1.474467120,162
+1.486077097,188
+1.497687074,176
+1.509297052,176
+1.520907029,159
+1.532517006,183
+1.544126984,178
+1.555736961,168
+1.567346938,171
+1.578956916,158
+1.590566893,153
+1.602176870,189
+1.613786848,171
+1.625396825,142
+1.637006802,503
+1.648616780,0
+1.660226757,30
+1.671836734,189
+1.683446712,146
+1.695056689,174
+1.706666666,171
+1.718276643,169
+1.729886621,164
+1.741496598,181
+1.753106575,180
+1.764716553,163
+1.776326530,174
+1.787936507,135
+1.799546485,193
+1.811156462,187
+1.822766439,176
+1.834376417,172
+1.845986394,155
+1.857596371,185
+1.869206349,167
+1.880816326,173
+1.892426303,162
+1.904036281,166
+1.915646258,167
+1.927256235,179
+1.938866213,170
+1.950476190,158
+1.962086167,168
+1.973696145,162
+1.985306122,182
+1.996916099,167
+2.008526077,185
+2.020136054,165
+2.031746031,184
+2.043356009,157
+2.054965986,167
+2.066575963,164
+2.078185941,160
+2.089795918,181
+2.101405895,165
+2.113015873,181
+2.124625850,161
+2.136235827,176
+2.147845804,177
+2.159455782,150
+2.171065759,185
+2.182675736,173
+2.194285714,162
+2.205895691,169
+2.217505668,171
+2.229115646,140
+2.240725623,196
+2.252335600,155
+2.263945578,171
+2.275555555,186
+2.287165532,157
+2.298775510,166
+2.310385487,169
+2.321995464,183
+2.333605442,186
+2.345215419,138
+2.356825396,162
+2.368435374,180
+2.380045351,165
+2.391655328,181
+2.403265306,444
+2.414875283,27
+2.426485260,150
+2.438095238,166
+2.449705215,177
+2.461315192,174
+2.472925170,165
+2.484535147,158
+2.496145124,178
+2.507755102,177
+2.519365079,172
+2.530975056,174
+2.542585034,168
+2.554195011,165
+2.565804988,168
+2.577414965,149
+2.589024943,171
+2.600634920,164
+2.612244897,163
+2.623854875,182
+2.635464852,162
+2.647074829,156
+2.658684807,173
+2.670294784,188
+2.681904761,166
+2.693514739,157
+2.705124716,170
+2.716734693,167
+2.728344671,191
+2.739954648,154
+2.751564625,175
+2.763174603,157
+2.774784580,169
+2.786394557,160
+2.798004535,163
+2.809614512,191
+2.821224489,158
+2.832834467,184
+2.844444444,164
+2.856054421,176
+2.867664399,182
+2.879274376,180
+2.890884353,138
+2.902494331,170
+2.914104308,172
+2.925714285,172
+2.937324263,168
+2.948934240,186
+2.960544217,171
+2.972154195,154
+2.983764172,185
+2.995374149,172
+3.006984126,164
+3.018594104,176
+3.030204081,195
+3.041814058,160
+3.053424036,124
+3.065034013,181
+3.076643990,170
+3.088253968,178
+3.099863945,165
+3.111473922,164
+3.123083900,194
+3.134693877,157
+3.146303854,205
+3.157913832,163
+3.169523809,159
+3.181133786,184
+3.192743764,145
+3.204353741,170
+3.215963718,155
+3.227573696,161
+3.239183673,164
+3.250793650,184
+3.262403628,175
+3.274013605,155
+3.285623582,164
+3.297233560,181
+3.308843537,182
+3.320453514,145
+3.332063492,177
+3.343673469,162
+3.355283446,153
+3.366893424,185
+3.378503401,174
+3.390113378,167
+3.401723356,187
+3.413333333,157
+3.424943310,170
+3.436553287,169
+3.448163265,166
+3.459773242,171
+3.471383219,165
+3.482993197,158
+3.494603174,181
+3.506213151,168
+3.517823129,175
+3.529433106,162
+3.541043083,167
+3.552653061,179
+3.564263038,167
+3.575873015,156
+3.587482993,209
+3.599092970,179
+3.610702947,159
+3.622312925,180
+3.633922902,152
+3.645532879,169
+3.657142857,189
+3.668752834,170
+3.680362811,156
+3.691972789,165
+3.703582766,179
+3.715192743,173
+3.726802721,163
+3.738412698,170
+3.750022675,181
+3.761632653,177
+3.773242630,181
+3.784852607,166
+3.796462585,153
+3.808072562,164
+3.819682539,209
+3.831292517,159
+3.842902494,194
+3.854512471,164
+3.866122448,184
+3.877732426,163
+3.889342403,173
+3.900952380,165
+3.912562358,164
+3.924172335,182
+3.935782312,170
+3.947392290,160
+3.959002267,173
+3.970612244,160
+3.982222222,174
+3.993832199,179
+4.005442176,179
+4.017052154,145
+4.028662131,196
+4.040272108,155
+4.051882086,170
+4.063492063,180
+4.075102040,156
+4.086712018,159
+4.098321995,182
+4.109931972,175
+4.121541950,173
+4.133151927,164
+4.144761904,186
+4.156371882,160
+4.167981859,175
+4.179591836,178
+4.191201814,159
+4.202811791,177
+4.214421768,181
+4.226031746,177
+4.237641723,163
+4.249251700,158
+4.260861678,188
+4.272471655,163
+4.284081632,158
+4.295691609,164
+4.307301587,164
+4.318911564,183
+4.330521541,160
+4.342131519,182
+4.353741496,172
+4.365351473,172
+4.376961451,179
+4.388571428,154
+4.400181405,166
+4.411791383,174
+4.423401360,156
+4.435011337,174
+4.446621315,169
+4.458231292,169
+4.469841269,178
+4.481451247,175
+4.493061224,193
+4.504671201,153
+4.516281179,160
+4.527891156,176
+4.539501133,167
+4.551111111,184
+4.562721088,163
+4.574331065,150
+4.585941043,156
+4.597551020,182
+4.609160997,168
+4.620770975,168
+4.632380952,152
+4.643990929,174
+4.655600907,186
+4.667210884,168
+4.678820861,173
+4.690430839,184
+4.702040816,169
+4.713650793,166
+4.725260770,164
+4.736870748,193
+4.748480725,155
+4.760090702,164
+4.771700680,181
+4.783310657,142
+4.794920634,183
+4.806530612,181
+4.818140589,185
+4.829750566,171
+4.841360544,184
+4.852970521,156
+4.864580498,162
+4.876190476,182
+4.887800453,161
+4.899410430,175
+4.911020408,168
+4.922630385,172
+4.934240362,151
+4.945850340,176
+4.957460317,171
+4.969070294,93
+4.980680272,0
+4.992290249,0
+5.003900226,0
+5.015510204,0
+5.027120181,0
+5.038730158,0
+5.050340136,0
+5.061950113,0
+5.073560090,0
+5.085170068,0
+5.096780045,0
+5.108390022,0
+5.120000000,0
+5.131609977,0
+5.143219954,0
+5.154829931,0
+5.166439909,0
+5.178049886,0
+5.189659863,0
+0.000000000,mean,162.123
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/expected/transforms-summaries-percussiononsets.csv	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,448 @@
+0.000000000,0
+0.011609977,170
+0.023219954,184
+0.034829931,166
+0.046439909,174
+0.058049886,179
+0.069659863,162
+0.081269841,157
+0.092879818,156
+0.104489795,182
+0.116099773,168
+0.127709750,168
+0.139319727,155
+0.150929705,200
+0.162539682,163
+0.174149659,178
+0.185759637,164
+0.197369614,166
+0.208979591,137
+0.220589569,155
+0.232199546,177
+0.243809523,145
+0.255419501,172
+0.267029478,155
+0.278639455,201
+0.290249433,166
+0.301859410,169
+0.313469387,158
+0.325079365,169
+0.336689342,155
+0.348299319,187
+0.359909297,177
+0.371519274,149
+0.383129251,196
+0.394739229,160
+0.406349206,174
+0.417959183,165
+0.429569160,149
+0.441179138,181
+0.452789115,180
+0.464399092,156
+0.476009070,163
+0.487619047,188
+0.499229024,173
+0.510839002,160
+0.522448979,168
+0.534058956,154
+0.545668934,155
+0.557278911,161
+0.568888888,163
+0.580498866,172
+0.592108843,168
+0.603718820,181
+0.615328798,167
+0.626938775,172
+0.638548752,158
+0.650158730,172
+0.661768707,167
+0.673378684,180
+0.684988662,188
+0.696598639,160
+0.708208616,166
+0.719818594,158
+0.731428571,176
+0.743038548,153
+0.754648526,173
+0.766258503,449
+0.777868480,30
+0.789478458,70
+0.801088435,181
+0.812698412,165
+0.824308390,174
+0.835918367,181
+0.847528344,179
+0.859138321,154
+0.870748299,169
+0.882358276,150
+0.893968253,189
+0.905578231,159
+0.917188208,172
+0.928798185,157
+0.940408163,167
+0.952018140,169
+0.963628117,155
+0.975238095,189
+0.986848072,178
+0.998458049,168
+1.010068027,169
+1.021678004,178
+1.033287981,157
+1.044897959,166
+1.056507936,162
+1.068117913,170
+1.079727891,186
+1.091337868,178
+1.102947845,178
+1.114557823,155
+1.126167800,173
+1.137777777,156
+1.149387755,158
+1.160997732,170
+1.172607709,167
+1.184217687,160
+1.195827664,180
+1.207437641,153
+1.219047619,154
+1.230657596,171
+1.242267573,159
+1.253877551,194
+1.265487528,170
+1.277097505,157
+1.288707482,184
+1.300317460,156
+1.311927437,173
+1.323537414,184
+1.335147392,168
+1.346757369,182
+1.358367346,147
+1.369977324,164
+1.381587301,163
+1.393197278,165
+1.404807256,181
+1.416417233,172
+1.428027210,169
+1.439637188,165
+1.451247165,166
+1.462857142,158
+1.474467120,162
+1.486077097,188
+1.497687074,176
+1.509297052,176
+1.520907029,159
+1.532517006,183
+1.544126984,178
+1.555736961,168
+1.567346938,171
+1.578956916,158
+1.590566893,153
+1.602176870,189
+1.613786848,171
+1.625396825,142
+1.637006802,503
+1.648616780,0
+1.660226757,30
+1.671836734,189
+1.683446712,146
+1.695056689,174
+1.706666666,171
+1.718276643,169
+1.729886621,164
+1.741496598,181
+1.753106575,180
+1.764716553,163
+1.776326530,174
+1.787936507,135
+1.799546485,193
+1.811156462,187
+1.822766439,176
+1.834376417,172
+1.845986394,155
+1.857596371,185
+1.869206349,167
+1.880816326,173
+1.892426303,162
+1.904036281,166
+1.915646258,167
+1.927256235,179
+1.938866213,170
+1.950476190,158
+1.962086167,168
+1.973696145,162
+1.985306122,182
+1.996916099,167
+2.008526077,185
+2.020136054,165
+2.031746031,184
+2.043356009,157
+2.054965986,167
+2.066575963,164
+2.078185941,160
+2.089795918,181
+2.101405895,165
+2.113015873,181
+2.124625850,161
+2.136235827,176
+2.147845804,177
+2.159455782,150
+2.171065759,185
+2.182675736,173
+2.194285714,162
+2.205895691,169
+2.217505668,171
+2.229115646,140
+2.240725623,196
+2.252335600,155
+2.263945578,171
+2.275555555,186
+2.287165532,157
+2.298775510,166
+2.310385487,169
+2.321995464,183
+2.333605442,186
+2.345215419,138
+2.356825396,162
+2.368435374,180
+2.380045351,165
+2.391655328,181
+2.403265306,444
+2.414875283,27
+2.426485260,150
+2.438095238,166
+2.449705215,177
+2.461315192,174
+2.472925170,165
+2.484535147,158
+2.496145124,178
+2.507755102,177
+2.519365079,172
+2.530975056,174
+2.542585034,168
+2.554195011,165
+2.565804988,168
+2.577414965,149
+2.589024943,171
+2.600634920,164
+2.612244897,163
+2.623854875,182
+2.635464852,162
+2.647074829,156
+2.658684807,173
+2.670294784,188
+2.681904761,166
+2.693514739,157
+2.705124716,170
+2.716734693,167
+2.728344671,191
+2.739954648,154
+2.751564625,175
+2.763174603,157
+2.774784580,169
+2.786394557,160
+2.798004535,163
+2.809614512,191
+2.821224489,158
+2.832834467,184
+2.844444444,164
+2.856054421,176
+2.867664399,182
+2.879274376,180
+2.890884353,138
+2.902494331,170
+2.914104308,172
+2.925714285,172
+2.937324263,168
+2.948934240,186
+2.960544217,171
+2.972154195,154
+2.983764172,185
+2.995374149,172
+3.006984126,164
+3.018594104,176
+3.030204081,195
+3.041814058,160
+3.053424036,124
+3.065034013,181
+3.076643990,170
+3.088253968,178
+3.099863945,165
+3.111473922,164
+3.123083900,194
+3.134693877,157
+3.146303854,205
+3.157913832,163
+3.169523809,159
+3.181133786,184
+3.192743764,145
+3.204353741,170
+3.215963718,155
+3.227573696,161
+3.239183673,164
+3.250793650,184
+3.262403628,175
+3.274013605,155
+3.285623582,164
+3.297233560,181
+3.308843537,182
+3.320453514,145
+3.332063492,177
+3.343673469,162
+3.355283446,153
+3.366893424,185
+3.378503401,174
+3.390113378,167
+3.401723356,187
+3.413333333,157
+3.424943310,170
+3.436553287,169
+3.448163265,166
+3.459773242,171
+3.471383219,165
+3.482993197,158
+3.494603174,181
+3.506213151,168
+3.517823129,175
+3.529433106,162
+3.541043083,167
+3.552653061,179
+3.564263038,167
+3.575873015,156
+3.587482993,209
+3.599092970,179
+3.610702947,159
+3.622312925,180
+3.633922902,152
+3.645532879,169
+3.657142857,189
+3.668752834,170
+3.680362811,156
+3.691972789,165
+3.703582766,179
+3.715192743,173
+3.726802721,163
+3.738412698,170
+3.750022675,181
+3.761632653,177
+3.773242630,181
+3.784852607,166
+3.796462585,153
+3.808072562,164
+3.819682539,209
+3.831292517,159
+3.842902494,194
+3.854512471,164
+3.866122448,184
+3.877732426,163
+3.889342403,173
+3.900952380,165
+3.912562358,164
+3.924172335,182
+3.935782312,170
+3.947392290,160
+3.959002267,173
+3.970612244,160
+3.982222222,174
+3.993832199,179
+4.005442176,179
+4.017052154,145
+4.028662131,196
+4.040272108,155
+4.051882086,170
+4.063492063,180
+4.075102040,156
+4.086712018,159
+4.098321995,182
+4.109931972,175
+4.121541950,173
+4.133151927,164
+4.144761904,186
+4.156371882,160
+4.167981859,175
+4.179591836,178
+4.191201814,159
+4.202811791,177
+4.214421768,181
+4.226031746,177
+4.237641723,163
+4.249251700,158
+4.260861678,188
+4.272471655,163
+4.284081632,158
+4.295691609,164
+4.307301587,164
+4.318911564,183
+4.330521541,160
+4.342131519,182
+4.353741496,172
+4.365351473,172
+4.376961451,179
+4.388571428,154
+4.400181405,166
+4.411791383,174
+4.423401360,156
+4.435011337,174
+4.446621315,169
+4.458231292,169
+4.469841269,178
+4.481451247,175
+4.493061224,193
+4.504671201,153
+4.516281179,160
+4.527891156,176
+4.539501133,167
+4.551111111,184
+4.562721088,163
+4.574331065,150
+4.585941043,156
+4.597551020,182
+4.609160997,168
+4.620770975,168
+4.632380952,152
+4.643990929,174
+4.655600907,186
+4.667210884,168
+4.678820861,173
+4.690430839,184
+4.702040816,169
+4.713650793,166
+4.725260770,164
+4.736870748,193
+4.748480725,155
+4.760090702,164
+4.771700680,181
+4.783310657,142
+4.794920634,183
+4.806530612,181
+4.818140589,185
+4.829750566,171
+4.841360544,184
+4.852970521,156
+4.864580498,162
+4.876190476,182
+4.887800453,161
+4.899410430,175
+4.911020408,168
+4.922630385,172
+4.934240362,151
+4.945850340,176
+4.957460317,171
+4.969070294,93
+4.980680272,0
+4.992290249,0
+5.003900226,0
+5.015510204,0
+5.027120181,0
+5.038730158,0
+5.050340136,0
+5.061950113,0
+5.073560090,0
+5.085170068,0
+5.096780045,0
+5.108390022,0
+5.120000000,0
+5.131609977,0
+5.143219954,0
+5.154829931,0
+5.166439909,0
+5.178049886,0
+5.189659863,0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-as-advertised.sh	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+mypath=`dirname $0`
+r=$mypath/../sonic-annotator
+
+infile=$mypath/audio/3clicks8.wav
+testplug=vamp:vamp-example-plugins:percussiononsets
+tmpdir=$mypath/tmp_1_$$.dir
+tmpwav=$tmpdir/test.wav
+
+trap "rm -rf $tmpdir" 0
+
+fail() {
+    echo "Test failed: $1"
+    exit 1
+}
+
+types=`\
+    $r --help 2>&1 | \
+    grep 'Supported writer types are:' | \
+    sed -e 's/^.*://' -e 's/[,\.]//g' \
+    `
+[ -n "$types" ] || \
+    fail "Fails to report sensible list of writers in help text?"
+
+onsets=$mypath/transforms/transforms-as-advertised-percussiononsets-onsets.n3
+df=$mypath/transforms/transforms-as-advertised-percussiononsets-detectionfunction.n3
+
+adbdir=$tmpdir/audiodb-test
+mkdir -p $adbdir
+
+for type in $types; do
+
+    mkdir -p $tmpdir
+    cp $infile $tmpwav
+
+    # Some of these are special cases:
+    #
+    # * The "default" writer type always prints to stdout instead of
+    # to a file.
+    #
+    # * The "audiodb" writer will not print any output for features
+    # that have no values (but are only point events).  I don't know
+    # how reasonable that is, but it's clearly intentional.  It also
+    # writes to a subdirectory $basedir/$catid/$trackid.$output
+
+    case $type in
+	audiodb) 
+	    $r -t $df -w $type $tmpwav --audiodb-basedir $tmpdir --audiodb-catid `basename $adbdir` 2>/dev/null || \
+		fail "Fails to run with reader type \"$type\" and default options"
+	    ;;
+	default) 
+	    $r -t $onsets -w $type $tmpwav > $tmpdir/test.out 2>/dev/null || \
+		fail "Fails to run with reader type \"$type\" and default options"
+	    ;;
+	*)
+	    $r -t $onsets -w $type $tmpwav 2>/dev/null || \
+		fail "Fails to run with reader type \"$type\" and default options"
+	    ;;
+    esac
+    newfiles=`ls $tmpdir | fgrep -v .wav`
+    if [ "$type" = audiodb ]; then newfiles=`ls $adbdir`; fi
+
+    [ -n "$newfiles" ] || \
+	fail "Fails to create output file for reader \"$type\" with default options"
+
+    case `echo $newfiles | wc -w` in
+	[2-9])
+	if [ "$type" != audiodb ]; then
+	    fail "Produces more than one output file for reader \"$type\" with default options"
+	fi
+	;;
+	1)
+	if [ "$type" = audiodb ]; then
+	    fail "Produces only one output file for reader \"$type\" with default options (expected two)"
+	fi
+	;;
+    esac
+
+    rm -r $tmpdir
+done
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-audioformat.sh	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+mypath=`dirname $0`
+r=$mypath/../sonic-annotator
+
+inbase=$mypath/audio/3clicks
+testplug=vamp:vamp-example-plugins:percussiononsets
+tmpfile1=$mypath/tmp_1_$$
+tmpfile2=$mypath/tmp_2_$$
+
+trap "rm -f $tmpfile1 $tmpfile2" 0
+
+fail() {
+    echo "Test failed: $1"
+    exit 1
+}
+
+for extension in wav ogg mp3 ; do
+
+    transform=$mypath/transforms/transforms-audioformat-percussiononsets.n3 
+    expected=$mypath/expected/transforms-audioformat-percussiononsets-$extension.csv
+
+    test -f $transform || \
+	fail "Internal error: no transforms file for suffix $suffix"
+
+    test -f $expected || \
+	fail "Internal error: no expected output file for suffix $suffix"
+
+    infile=$inbase.$extension
+    if [ "$extension" = "wav" ]; then infile=${inbase}8.$extension; fi
+
+    test -f $infile || \
+	fail "Internal error: no input audio file for extension $extension"
+
+    $r -t $transform -w csv --csv-stdout $infile > $tmpfile2 2>/dev/null || \
+	fail "Fails to run transform $transform against audio file $infile"
+
+    if [ "$extension" = "wav" ]; then
+	cmp -s $tmpfile2 $expected || \
+	    fail "Output mismatch for transform $transform with audio file $infile"
+    else
+	cmp -s $tmpfile2 $expected || \
+	    ( echo "NOTE: Output mismatch for transform $transform with audio file $infile" ; \
+	      echo "This may be the result of differences in the audio file decoder, so I am not" ; \
+	      echo "failing the test, but I recommend that you check the results." )
+    fi
+done
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-helpfulflags.sh	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+mypath=`dirname $0`
+r=$mypath/../sonic-annotator
+
+testplug=vamp:vamp-example-plugins:percussiononsets
+
+fail() {
+    echo "Test failed: $1"
+    exit 1
+}
+
+$r >/dev/null 2>&1 && \
+    fail "Return code 0 when run without args (should be a failure code)"
+
+$r 2>&1 >/dev/null | grep -q "for help" || \
+    fail "Improper response when run without args"
+
+$r --help 2>&1 | grep -q Copy || \
+    fail "Expected help not printed when run with --help"
+
+$r --list >/dev/null 2>&1 || \
+    fail "Fails to run with --list"
+
+$r --list 2>/dev/null | grep -q $testplug || \
+    fail "Fails to print $testplug in plugin list (if you haven't got it, install it -- it's needed for other tests)"
+
+$r --skeleton $testplug >/dev/null || \
+    fail "Fails to run with --skeleton $testplug"
+
+$r -s $testplug >/dev/null || \
+    fail "Fails to run with -s $testplug"
+
+$r --skeleton $testplug >/dev/null || \
+    fail "Fails to run with --skeleton $testplug"
+
+$r --skeleton $testplug | rapper -i turtle - test >/dev/null 2>&1 || \
+    fail "Invalid XML skeleton produced with --skeleton $testplug"
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rdf-destinations.sh	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,144 @@
+#!/bin/bash
+
+mypath=`dirname $0`
+r=$mypath/../sonic-annotator
+
+infile1=$mypath/audio/3clicks8.wav
+infile2=$mypath/audio/6clicks8.wav
+
+outfile1=$mypath/audio/3clicks8.n3
+outfile2=$mypath/audio/6clicks8.n3
+
+outfile3=$mypath/audio/3clicks8_vamp_vamp-example-plugins_percussiononsets_onsets.n3
+outfile4=$mypath/audio/3clicks8_vamp_vamp-example-plugins_percussiononsets_detectionfunction.n3
+outfile5=$mypath/audio/6clicks8_vamp_vamp-example-plugins_percussiononsets_onsets.n3
+outfile6=$mypath/audio/6clicks8_vamp_vamp-example-plugins_percussiononsets_detectionfunction.n3
+
+testplug=vamp:vamp-example-plugins:percussiononsets
+tmpttl=$mypath/tmp_1_$$.ttl
+
+trap "rm -f $tmpttl $outfile1 $outfile2 $outfile3 $outfile4 $outfile5 $outfile6" 0
+
+fail() {
+    echo "Test failed: $1"
+    exit 1
+}
+
+transformpfx=$mypath/transforms/transforms-rdf-writer-percussiononsets
+
+check_rdf() {
+    test -f $1 || \
+	fail "Fails to write output to expected location $1 for $2"
+    rapper -i turtle $1 >/dev/null 2>&1 || \
+	fail "Fails to produce parseable RDF/TTL for $2"
+    rapper -i turtle -c $1 2>&1 | egrep -q 'Parsing returned [1-9][0-9]+ triples' || \
+	fail "RDF output contains no triples (?) for $2"
+    rm -f $1
+}    
+
+
+ctx="onsets transform, one audio file, default RDF writer destination"
+
+rm -f $outfile1
+
+$r -t $transformpfx-onsets.n3 -w rdf $infile1 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_rdf $outfile1 "$ctx"
+
+
+ctx="onsets and df transforms, one audio file, default RDF writer destination"
+
+rm -f $outfile1
+
+$r -t $transformpfx-onsets.n3 -t $transformpfx-detectionfunction.n3 -w rdf $infile1 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_rdf $outfile1 "$ctx"
+
+
+ctx="onsets transform, two audio files, default RDF writer destination"
+
+rm -f $outfile1
+rm -f $outfile2
+
+$r -t $transformpfx-onsets.n3 -w rdf $infile1 $infile2 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_rdf $outfile1 "$ctx"
+check_rdf $outfile2 "$ctx"
+
+
+ctx="onsets transform, two audio files, one-file RDF writer"
+
+$r -t $transformpfx-onsets.n3 -w rdf --rdf-one-file $tmpttl $infile1 $infile2 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_rdf $tmpttl "$ctx"
+
+
+ctx="onsets transform, two audio files, stdout RDF writer"
+
+$r -t $transformpfx-onsets.n3 -w rdf --rdf-stdout $infile1 $infile2 2>/dev/null >$tmpttl || \
+    fail "Fails to run with $ctx"
+
+check_rdf $tmpttl "$ctx"
+
+
+ctx="onsets transform, one audio file, many-files RDF writer"
+
+rm -f $outfile3
+
+$r -t $transformpfx-onsets.n3 -w rdf --rdf-many-files $infile1 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_rdf $outfile3 "$ctx"
+
+
+ctx="onsets transform, two audio files, many-files RDF writer"
+
+rm -f $outfile3
+rm -f $outfile5
+
+$r -t $transformpfx-onsets.n3 -w rdf --rdf-many-files $infile1 $infile2 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_rdf $outfile3 "$ctx"
+check_rdf $outfile5 "$ctx"
+
+
+ctx="onsets and df transforms, two audio files, many-files RDF writer"
+
+rm -f $outfile3
+rm -f $outfile4
+rm -f $outfile5
+rm -f $outfile6
+
+$r -t $transformpfx-onsets.n3 -t $transformpfx-detectionfunction.n3 -w rdf --rdf-many-files $infile1 $infile2 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_rdf $outfile3 "$ctx"
+check_rdf $outfile4 "$ctx"
+check_rdf $outfile5 "$ctx"
+check_rdf $outfile6 "$ctx"
+
+
+ctx="existing output file and no --rdf-force"
+
+touch $outfile1
+
+$r -t $transformpfx-onsets.n3 -w rdf $infile1 2>/dev/null && \
+    fail "Fails by completing successfully when output file already exists (should refuse and bail out)"
+
+
+ctx="existing output file and --rdf-force"
+
+touch $outfile1
+
+$r -t $transformpfx-onsets.n3 -w rdf --rdf-force $infile1 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_rdf $outfile1 "$ctx"
+
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rdf-writer.sh	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+mypath=`dirname $0`
+r=$mypath/../sonic-annotator
+
+infile=$mypath/audio/3clicks8.wav
+testplug=vamp:vamp-example-plugins:percussiononsets
+tmpttl=$mypath/tmp_1_$$.ttl
+
+trap "rm -f $tmpttl" 0
+
+fail() {
+    echo "Test failed: $1"
+    exit 1
+}
+
+transformpfx=$mypath/transforms/transforms-rdf-writer-percussiononsets
+
+for rdfarg in "" "--rdf-plain" ; do
+
+    note=""
+    [ -n "$rdfarg" ] && note=" with $rdfarg"
+
+    rm -f $tmpttl
+
+    $r -t $transformpfx-onsets.n3 -w rdf $infile $rdfarg --rdf-one-file $tmpttl 2>/dev/null || \
+	fail "Fails to run with onsets transform and RDF writer$note"
+
+    rapper -i turtle $tmpttl >/dev/null 2>&1 || \
+	fail "Fails to produce parseable RDF/TTL for onsets transform$note"
+
+    rapper -i turtle -c $tmpttl 2>&1 | egrep -q 'Parsing returned [1-9][0-9]+ triples' ||
+	fail "RDF output contains no triples (?) for onsets transform$note"
+
+    rm -f $tmpttl
+    
+    $r -t $transformpfx-detectionfunction.n3 -w rdf $infile $rdfarg --rdf-one-file $tmpttl 2>/dev/null || \
+	fail "Fails to run with detectionfunction transform and RDF writer$note"
+
+    rapper -i turtle $tmpttl >/dev/null 2>&1 || \
+	fail "Fails to produce parseable RDF/TTL for detectionfunction transform$note"
+
+    rapper -i turtle -c $tmpttl 2>&1 | egrep -q 'Parsing returned [1-9][0-9]+ triples' ||
+	fail "RDF output contains no triples (?) for detectionfunction transform$note"
+
+    rm -f $tmpttl
+
+    $r -t $transformpfx-onsets.n3 -t $transformpfx-detectionfunction.n3 -w rdf $infile $rdfarg --rdf-one-file $tmpttl 2>/dev/null || \
+	fail "Fails to run with detectionfunction and onsets transforms together and RDF writer$note"
+
+    rapper -i turtle $tmpttl >/dev/null 2>&1 || \
+	fail "Fails to produce parseable RDF/TTL for detectionfunction and onsets transforms together$note"
+
+    rapper -i turtle -c $tmpttl 2>&1 | egrep -q 'Parsing returned [1-9][0-9]+ triples' ||
+	fail "RDF output contains no triples (?) for detectionfunction and onsets transforms together$note"
+
+done
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries.sh	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,72 @@
+#!/bin/bash
+
+mypath=`dirname $0`
+r=$mypath/../sonic-annotator
+
+infile=$mypath/audio/3clicks8.wav
+tmpfile=$mypath/tmp_1_$$
+tmpcanonical=$mypath/tmp_2_$$
+
+trap "rm -f $tmpfile $tmpcanonical" 0
+
+fail() {
+    echo "Test failed: $1"
+    if [ -n "$2" -a -n "$3" ]; then
+	echo "Output follows:"
+	echo "--"
+	cat $2
+	echo "--"
+	echo "Expected output follows:"
+	echo "--"
+	cat $3
+	echo "--"
+	echo "Diff:"
+	echo "--"
+	diff -u $2 $3
+	echo "--"
+    fi
+    exit 1
+}
+
+# transform to which we have to add summarisation on command line
+transform=$mypath/transforms/transforms-nosummaries-percussiononsets-detectionfunction.n3 
+expected=$mypath/expected/transforms-summaries-percussiononsets
+
+stransform=$mypath/transforms/transforms-summaries-percussiononsets-detectionfunction.n3 
+sexpected=$mypath/expected/transforms-summaries-percussiononsets-from-rdf
+
+$r -t $transform -w csv --csv-stdout $infile > $tmpfile 2>/dev/null || \
+    fail "Fails to run transform $transform"
+
+cmp -s $tmpfile ${expected}.csv || \
+    fail "Output mismatch for transform $transform" $tmpfile ${expected}.csv
+
+$r -t $transform -w csv --csv-stdout -S mean $infile > $tmpfile 2>/dev/null || \
+    fail "Fails to run transform $transform with summary type mean"
+
+cmp -s $tmpfile ${expected}-with-mean.csv || \
+    fail "Output mismatch for transform $transform with summary type mean" $tmpfile ${expected}-with-mean.csv
+
+$r -t $transform -w csv --csv-stdout -S min -S max -S mean -S median -S mode -S sum -S variance -S sd -S count --summary-only $infile > $tmpfile 2>/dev/null || \
+    fail "Fails to run transform $transform with all summary types and summary-only"
+
+cmp -s $tmpfile ${expected}-all-summaries-only.csv || \
+    fail "Output mismatch for transform $transform with all summary types and summary-only" $tmpfile ${expected}-all-summaries-only.csv
+
+$r -t $stransform -w csv --csv-stdout $infile > $tmpfile 2>/dev/null || \
+    fail "Fails to run transform $stransform with CSV output"
+
+cmp -s $tmpfile ${sexpected}.csv || \
+    fail "Output mismatch for transform $stransform" $tmpfile ${sexpected}.csv
+
+$r -t $stransform -w rdf --rdf-stdout $infile > $tmpfile 2>/dev/null || \
+    fail "Fails to run transform $stransform with RDF output"
+
+rapper -i turtle $tmpfile -o turtle 2>/dev/null | grep -v '^@prefix :' > $tmpcanonical ||
+    fail "Fails to produce parseable RDF/TTL for transform $stransform"
+
+cmp -s $tmpcanonical ${sexpected}.n3 || \
+    fail "Output mismatch for canonicalised version of transform $stransform" $tmpcanonical ${sexpected}.n3
+
+exit 0
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-supportprogs.sh	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+fail() {
+    echo "Test failed: $1"
+    exit 1
+}
+
+xmllint --version 2>/dev/null || \
+    fail "Can't find required xmllint program"
+
+rapper --version >/dev/null || \
+    fail "Can't find required rapper program"
+
+exit 0
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-transforms-basic.sh	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+mypath=`dirname $0`
+r=$mypath/../sonic-annotator
+
+infile=$mypath/audio/3clicks8.wav
+testplug=vamp:vamp-example-plugins:percussiononsets
+tmpfile1=$mypath/tmp_1_$$
+tmpfile2=$mypath/tmp_2_$$
+
+trap "rm -f $tmpfile1 $tmpfile2" 0
+
+fail() {
+    echo "Test failed: $1"
+    exit 1
+}
+
+$r --skeleton $testplug > $tmpfile1 2>/dev/null || \
+    fail "Fails to run with --skeleton $testplug"
+
+$r -t $tmpfile1 -w csv --csv-stdout $infile > $tmpfile2 2>/dev/null || \
+    fail "Fails to run with -t $tmpfile -w csv --csv-stdout $infile"
+
+cmp -s $tmpfile2 $mypath/expected/transforms-basic-skeleton-1.csv || \
+    fail "Output mismatch for transforms-basic-skeleton-1.csv"
+
+for suffix in \
+    -no-parameters-default-output \
+    -no-parameters \
+    "" \
+    -set-parameters \
+    -set-step-and-block-size \
+    -set-sample-rate \
+    ; do
+
+    for type in xml n3 ; do 
+
+	transform=$mypath/transforms/transforms-basic-percussiononsets$suffix.$type
+	expected=$mypath/expected/transforms-basic-percussiononsets$suffix.csv
+
+	test -f $transform || \
+	    fail "Internal error: no transforms file for suffix $suffix"
+
+	test -f $expected || \
+	    fail "Internal error: no expected output file for suffix $suffix"
+
+	$r -t $transform -w csv --csv-stdout $infile > $tmpfile2 2>/dev/null || \
+	    fail "Fails to run transform $transform"
+
+	cmp -s $tmpfile2 $expected || \
+	    fail "Output mismatch for transform $transform"
+    done
+done
+
+exit 0
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test.sh	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+mypath=`dirname $0`
+
+for x in \
+    supportprogs \
+    helpfulflags \
+    transforms-basic \
+    audioformat \
+    as-advertised \
+    rdf-writer \
+    rdf-destinations \
+    summaries \
+    ; do
+
+    echo -n "$x: "
+    if bash $mypath/test-$x.sh; then
+	echo test succeeded
+    else
+	echo "*** Test FAILED"
+	exit 1
+    fi
+
+done
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-as-advertised-percussiononsets-detectionfunction.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,11 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets;
+	vamp:output examples:percussiononsets_output_detectionfunction.
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-as-advertised-percussiononsets-onsets.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,10 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets.
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-audioformat-percussiononsets.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,10 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets.
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets-no-parameters-default-output.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,10 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets.
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets-no-parameters-default-output.xml	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,2 @@
+<transform id="vamp:vamp-example-plugins:percussiononsets"/>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets-no-parameters.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,11 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_onsets .
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets-no-parameters.xml	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,1 @@
+<transform id="vamp:vamp-example-plugins:percussiononsets:onsets"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets-set-parameters.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,22 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_onsets ;
+	vamp:step_size "512";
+ 	vamp:block_size "1024";
+	vamp:parameter_binding :param0 ;
+	vamp:parameter_binding :param1 .
+
+:param0 a vamp:ParameterBinding;
+	vamp:parameter [ vamp:identifier "sensitivity" ];
+	vamp:value "60".
+
+:param1 a vamp:ParameterBinding;
+	vamp:parameter examples:percussiononsets_param_threshold;
+	vamp:value "2.6".
+
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets-set-parameters.xml	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,7 @@
+<transform
+    id="vamp:vamp-example-plugins:percussiononsets:onsets"
+    stepSize="512"
+    blockSize="1024">
+  <parameter name="sensitivity" value="60"/>
+  <parameter name="threshold" value="2.6"/>
+</transform>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets-set-sample-rate.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,11 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_onsets ;
+	vamp:sample_rate "22050" .
+
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets-set-sample-rate.xml	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,4 @@
+<transform
+    id="vamp:vamp-example-plugins:percussiononsets:onsets"
+    sampleRate="22050">
+</transform>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets-set-step-and-block-size.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,11 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_onsets ;
+	vamp:step_size "345";
+ 	vamp:block_size "512".
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets-set-step-and-block-size.xml	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,5 @@
+<transform
+    id="vamp:vamp-example-plugins:percussiononsets:onsets"
+    stepSize="345"
+    blockSize="512"
+   />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,22 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_onsets ;
+	vamp:step_size "512";
+ 	vamp:block_size "1024";
+	vamp:parameter_binding :param0 ;
+	vamp:parameter_binding :param1 .
+
+:param0 a vamp:ParameterBinding;
+	vamp:parameter [ vamp:identifier "sensitivity" ];
+	vamp:value "40".
+
+:param1 a vamp:ParameterBinding;
+	vamp:parameter examples:percussiononsets_param_threshold;
+	vamp:value "3".
+
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-basic-percussiononsets.xml	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,13 @@
+<transform
+    id="vamp:vamp-example-plugins:percussiononsets:onsets"
+    pluginVersion="2"
+    program=""
+    stepSize="512"
+    blockSize="1024"
+    windowType="hanning"
+    startTime="0.000000000"
+    duration="0.000000000"
+    sampleRate="0">
+  <parameter name="sensitivity" value="40"/>
+  <parameter name="threshold" value="3"/>
+</transform>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-nosummaries-percussiononsets-detectionfunction.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,9 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_detectionfunction .
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-rdf-writer-percussiononsets-detectionfunction.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,11 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_detectionfunction .
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-rdf-writer-percussiononsets-onsets.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,10 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets.
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/transforms/transforms-summaries-percussiononsets-detectionfunction.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,32 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform; # 8
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_detectionfunction ;
+	vamp:summary_type "mean" .
+
+:transform1 a vamp:Transform; # 11
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_detectionfunction ;
+	vamp:summary_type "median" .
+
+:transform2 a vamp:Transform; # 0
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_onsets .
+
+:transform3 a vamp:Transform; # 14
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_detectionfunction ;
+	vamp:summary_type "mode" .
+
+:transform4 a vamp:Transform; # 23
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_detectionfunction ;
+	vamp:step_size 2048 ;
+	vamp:block_size 4096 ;
+	vamp:summary_type "mean" .
+
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transforms/chromagram.xml	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,7 @@
+<transform id="vamp:qm-vamp-plugins:qm-chromagram" program="" stepSize="0" blockSize="0" windowType="hanning" startTime="0.000000000" duration="0.000000000" sampleRate="0" >
+  <parameter name="bpo" value="12"/>
+  <parameter name="maxpitch" value="96"/>
+  <parameter name="minpitch" value="12"/>
+  <parameter name="normalized" value="1"/>
+  <parameter name="tuning" value="440"/>
+</transform>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transforms/percussiononsets-detectionfunction.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,23 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets;
+	vamp:output examples:percussiononsets_output_detectionfunction;
+#	vamp:step_size "256";
+# 	vamp:block_size "512";
+	vamp:window_type "Hanning";
+	vamp:parameter_binding :param0 ;
+	vamp:parameter_binding :param1 .
+
+:param0 a vamp:ParameterBinding;
+	vamp:parameter [ vamp:identifier "sensitivity" ];
+	vamp:value "60".
+
+:param1 a vamp:ParameterBinding;
+	vamp:parameter examples:percussiononsets_param_threshold;
+	vamp:value "2".
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transforms/percussiononsets-detectionfunction.xml	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,5 @@
+<transform id="vamp:vamp-example-plugins:percussiononsets:detectionfunction" program="" stepSize="512" blockSize="1024" windowType="hanning" startTime="0.000000000" duration="0.000000000" sampleRate="0" >
+  <parameter name="sensitivity" value="40"/>
+  <parameter name="threshold" value="3"/>
+</transform>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transforms/percussiononsets.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,24 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets;
+	vamp:output examples:percussiononsets_output_onsets;
+	vamp:step_size "256";
+ 	vamp:block_size "512";
+#	vamp:sample_rate "44100";
+	vamp:window_type "Hanning";
+	vamp:parameter_binding :param0 ;
+	vamp:parameter_binding :param1 .
+
+:param0 a vamp:ParameterBinding;
+	vamp:parameter [ vamp:identifier "sensitivity" ];
+	vamp:value "40".
+
+:param1 a vamp:ParameterBinding;
+	vamp:parameter examples:percussiononsets_param_threshold;
+	vamp:value "3".
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transforms/percussiononsets.xml	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,4 @@
+<transform id="vamp:vamp-example-plugins:percussiononsets" program="" stepSize="512" blockSize="1024" windowType="hanning" startTime="0.000000000" duration="0.000000000" sampleRate="0" >
+  <parameter name="sensitivity" value="40"/>
+  <parameter name="threshold" value="3"/>
+</transform>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transforms/segmentation.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,13 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix qmp: <http://vamp-plugins.org/rdf/plugins/qm-vamp-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin qmp:qm-segmenter;
+	vamp:output qmp:qm-segmenter_output_segmentation;
+	vamp:step_size "";
+ 	vamp:block_size "";
+	vamp:window_type "".
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transforms/spectralcentroid.xml	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,1 @@
+<transform id="vamp:vamp-example-plugins:spectralcentroid" program="" stepSize="512" blockSize="1024" windowType="hanning" startTime="0.000000000" duration="0.000000000" sampleRate="22050" />
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-example-plugins:percussiononsets.n3	Thu Dec 11 10:22:33 2008 +0000
@@ -0,0 +1,70 @@
+@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 .