changeset 439:beb2948baa77

* Merge revisions 1041 to 1130 from sv-rdf-import branch
author Chris Cannam
date Thu, 18 Sep 2008 12:09:32 +0000
parents 32c399d06374
children 5746c559af15
files base/ProgressPrinter.cpp base/ProgressPrinter.h base/ProgressReporter.h base/RealTime.cpp base/RealTime.h data/fileio/FileSource.cpp data/model/EditableDenseThreeDimensionalModel.cpp rdf/PluginRDFDescription.cpp rdf/PluginRDFDescription.h rdf/PluginRDFIndexer.cpp rdf/PluginRDFIndexer.h rdf/RDFImporter.cpp rdf/RDFImporter.h rdf/RDFTransformFactory.cpp rdf/RDFTransformFactory.h rdf/SimpleSPARQLQuery.cpp rdf/SimpleSPARQLQuery.h rdf/rdf.pro transform/TransformFactory.cpp
diffstat 19 files changed, 1871 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/base/ProgressPrinter.cpp	Thu Aug 07 16:06:59 2008 +0000
+++ b/base/ProgressPrinter.cpp	Thu Sep 18 12:09:32 2008 +0000
@@ -20,7 +20,8 @@
 ProgressPrinter::ProgressPrinter(QString message, QObject *parent) :
     ProgressReporter(parent),
     m_prefix(message),
-    m_lastProgress(0)
+    m_lastProgress(0),
+    m_definite(true)
 {
 }
 
@@ -32,6 +33,18 @@
 //    std::cerr << "(progress printer dtor)" << std::endl;
 }
 
+bool
+ProgressPrinter::isDefinite() const
+{
+    return m_definite;
+}
+
+void
+ProgressPrinter::setDefinite(bool definite)
+{
+    m_definite = definite;
+}
+
 void
 ProgressPrinter::setMessage(QString message)
 {
@@ -46,8 +59,12 @@
     else {
         std::cerr << "\r"
                   << m_prefix.toStdString() 
-                  << (m_prefix == "" ? "" : " ")
-                  << progress << "%";
+                  << (m_prefix == "" ? "" : " ");
+        if (m_definite) {
+            std::cerr << progress << "%";
+        } else {
+            std::cerr << "|/-\\"[progress % 4];
+        }
     }
     m_lastProgress = progress;
 }
--- a/base/ProgressPrinter.h	Thu Aug 07 16:06:59 2008 +0000
+++ b/base/ProgressPrinter.h	Thu Sep 18 12:09:32 2008 +0000
@@ -26,6 +26,11 @@
     ProgressPrinter(QString message, QObject *parent = 0);
     virtual ~ProgressPrinter();
     
+    virtual bool isDefinite() const;
+    virtual void setDefinite(bool definite);
+
+    virtual bool wasCancelled() const { return false; } // no mechanism
+
 public slots:
     virtual void setMessage(QString);
     virtual void setProgress(int);
@@ -33,6 +38,7 @@
 protected:
     QString m_prefix;
     int m_lastProgress;
+    bool m_definite;
 };
 
 #endif
--- a/base/ProgressReporter.h	Thu Aug 07 16:06:59 2008 +0000
+++ b/base/ProgressReporter.h	Thu Sep 18 12:09:32 2008 +0000
@@ -14,6 +14,7 @@
 */
 
 #ifndef _PROGRESS_REPORTER_H_
+#define _PROGRESS_REPORTER_H_
 
 #include <QObject>
 #include <QString>
@@ -26,6 +27,11 @@
     ProgressReporter(QObject *parent = 0);
     virtual ~ProgressReporter();
 
+    virtual bool isDefinite() const = 0;
+    virtual void setDefinite(bool definite) = 0; // default should be definite
+
+    virtual bool wasCancelled() const = 0;
+
 signals:
     void cancelled();
 
--- a/base/RealTime.cpp	Thu Aug 07 16:06:59 2008 +0000
+++ b/base/RealTime.cpp	Thu Sep 18 12:09:32 2008 +0000
@@ -79,6 +79,94 @@
     return RealTime(tv.tv_sec, tv.tv_usec * 1000);
 }
 
+RealTime
+RealTime::fromXsdDuration(std::string xsdd)
+{
+    RealTime t;
+
+    int year = 0, month = 0, day = 0, hour = 0, minute = 0;
+    double second = 0.0;
+
+    int i = 0;
+
+    const char *s = xsdd.c_str();
+    int len = xsdd.length();
+
+    bool negative = false, afterT = false;
+
+    int valstart = 0;
+
+    while (i < len) {
+
+        if (s[i] == '-') {
+            if (i == 0) negative = true;
+            ++i;
+            continue;
+        }
+
+        double value = 0.0;
+        char *eptr = 0;
+
+        if (isdigit(s[i]) || s[i] == '.') {
+            valstart = i;
+            value = strtod(&s[i], &eptr);
+            i = eptr - s;
+        }
+
+        if (i == len) break;
+
+        switch (s[i]) {
+        case 'Y': year = int(value + 0.1); break;
+        case 'D': day  = int(value + 0.1); break;
+        case 'H': hour = int(value + 0.1); break;
+        case 'M':
+            if (afterT) minute = int(value + 0.1);
+            else month = int(value + 0.1);
+            break;
+        case 'S':
+            second = value;
+            break;
+        case 'T': afterT = true; break;
+        };
+
+        ++i;
+    }
+
+    if (year > 0) {
+        std::cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero year.\nWith no origin and a limited data size, I will treat a year as exactly 31556952\nseconds and you should expect overflow and/or poor results." << std::endl;
+        t = t + RealTime(year * 31556952, 0);
+    }
+
+    if (month > 0) {
+        std::cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero month.\nWith no origin and a limited data size, I will treat a month as exactly 2629746\nseconds and you should expect overflow and/or poor results." << std::endl;
+        t = t + RealTime(month * 2629746, 0);
+    }
+
+    if (day > 0) {
+        t = t + RealTime(day * 86400, 0);
+    }
+
+    if (hour > 0) {
+        t = t + RealTime(hour * 3600, 0);
+    }
+
+    if (minute > 0) {
+        t = t + RealTime(minute * 60, 0);
+    }
+
+    t = t + fromSeconds(second);
+
+    return t;
+}
+
+double
+RealTime::toDouble() const
+{
+    double d = sec;
+    d += double(nsec) / double(ONE_BILLION);
+    return d;
+}
+
 std::ostream &operator<<(std::ostream &out, const RealTime &rt)
 {
     if (rt < RealTime::zeroTime) {
@@ -128,7 +216,6 @@
 RealTime::fromString(std::string s)
 {
     bool negative = false;
-    bool faulty = false;
     bool section = 0;
     std::string ssec, snsec;
 
--- a/base/RealTime.h	Thu Aug 07 16:06:59 2008 +0000
+++ b/base/RealTime.h	Thu Sep 18 12:09:32 2008 +0000
@@ -49,6 +49,9 @@
     static RealTime fromSeconds(double sec);
     static RealTime fromMilliseconds(int msec);
     static RealTime fromTimeval(const struct timeval &);
+    static RealTime fromXsdDuration(std::string xsdd);
+
+    double toDouble() const;
 
     RealTime &operator=(const RealTime &r) {
 	sec = r.sec; nsec = r.nsec; return *this;
--- a/data/fileio/FileSource.cpp	Thu Aug 07 16:06:59 2008 +0000
+++ b/data/fileio/FileSource.cpp	Thu Sep 18 12:09:32 2008 +0000
@@ -209,6 +209,8 @@
             m_localFilename = m_url.toString();
             literal = true;
         }
+        m_localFilename = QFileInfo(m_localFilename).absoluteFilePath();
+
 #ifdef DEBUG_FILE_SOURCE
         std::cerr << "FileSource::init: URL translates to local filename \""
                   << m_localFilename.toStdString() << "\"" << std::endl;
--- a/data/model/EditableDenseThreeDimensionalModel.cpp	Thu Aug 07 16:06:59 2008 +0000
+++ b/data/model/EditableDenseThreeDimensionalModel.cpp	Thu Sep 18 12:09:32 2008 +0000
@@ -178,6 +178,8 @@
 
     bool allChange = false;
 
+    if (values.size() > m_yBinCount) m_yBinCount = values.size();
+
     for (size_t i = 0; i < values.size(); ++i) {
         float value = values[i];
         if (std::isnan(value) || std::isinf(value)) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/PluginRDFDescription.cpp	Thu Sep 18 12:09:32 2008 +0000
@@ -0,0 +1,221 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 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 "PluginRDFDescription.h"
+
+#include "PluginRDFIndexer.h"
+#include "SimpleSPARQLQuery.h"
+
+#include "plugin/PluginIdentifier.h"
+
+#include <iostream>
+using std::cerr;
+using std::endl;
+
+PluginRDFDescription::PluginRDFDescription(QString pluginId) :
+    m_pluginId(pluginId),
+    m_haveDescription(false)
+{
+    PluginRDFIndexer *indexer = PluginRDFIndexer::getInstance();
+    QString url = indexer->getDescriptionURLForPluginId(pluginId);
+    if (url == "") {
+        cerr << "PluginRDFDescription: WARNING: No RDF description available for plugin ID \""
+             << pluginId.toStdString() << "\"" << endl;
+    } else {
+        if (!indexURL(url)) {
+            cerr << "PluginRDFDescription: ERROR: Failed to query RDF description for plugin ID \""
+                 << pluginId.toStdString() << "\"" << endl;
+        } else {
+            m_haveDescription = true;
+        }
+    }
+}
+
+PluginRDFDescription::~PluginRDFDescription()
+{
+}
+
+bool
+PluginRDFDescription::haveDescription() const
+{
+    return m_haveDescription;
+}
+
+PluginRDFDescription::OutputType
+PluginRDFDescription::getOutputType(QString outputId) const
+{
+    if (m_outputTypes.find(outputId) == m_outputTypes.end()) {
+        return OutputTypeUnknown;
+    }
+    return m_outputTypes.find(outputId)->second;
+}
+
+PluginRDFDescription::OutputDisposition
+PluginRDFDescription::getOutputDisposition(QString outputId) const
+{
+    if (m_outputDispositions.find(outputId) == m_outputDispositions.end()) {
+        return OutputDispositionUnknown;
+    }
+    return m_outputDispositions.find(outputId)->second;
+}
+
+QString
+PluginRDFDescription::getOutputFeatureTypeURI(QString outputId) const
+{
+    if (m_outputFeatureTypeURIMap.find(outputId) ==
+        m_outputFeatureTypeURIMap.end()) {
+        return "";
+    }
+    return m_outputFeatureTypeURIMap.find(outputId)->second;
+}
+
+QString
+PluginRDFDescription::getOutputEventTypeURI(QString outputId) const
+{
+    if (m_outputEventTypeURIMap.find(outputId) ==
+        m_outputEventTypeURIMap.end()) {
+        return "";
+    }
+    return m_outputEventTypeURIMap.find(outputId)->second;
+}
+
+QString
+PluginRDFDescription::getOutputUnit(QString outputId) const
+{
+    if (m_outputUnitMap.find(outputId) == m_outputUnitMap.end()) {
+        return "";
+    }
+    return m_outputUnitMap.find(outputId)->second;
+}
+
+bool
+PluginRDFDescription::indexURL(QString url) 
+{
+    QString type, soname, label;
+    PluginIdentifier::parseIdentifier(m_pluginId, type, soname, label);
+
+    SimpleSPARQLQuery query
+        (QString
+         (
+             " PREFIX vamp: <http://purl.org/ontology/vamp/> "
+
+             " SELECT ?output_id ?output_type ?feature_type ?event_type ?unit "
+             " FROM <%1> "
+
+             " WHERE { "
+
+             "   ?plugin a vamp:Plugin ; "
+             "           vamp:identifier \"%2\" ; "
+             "           vamp:output_descriptor ?output . "
+
+             "   ?output vamp:identifier ?output_id ; "
+             "           a ?output_type . "
+
+             "   OPTIONAL { "
+             "     ?output vamp:computes_feature_type ?feature_type "
+             "   } . "
+
+             "   OPTIONAL { "
+             "     ?output vamp:computes_event_type ?event_type "
+             "   } . "
+
+             "   OPTIONAL { "
+             "     ?output vamp:unit ?unit "
+             "   } . "
+
+             " } "
+             )
+         .arg(url)
+         .arg(label));
+
+    SimpleSPARQLQuery::ResultList results = query.execute();
+
+    if (!query.isOK()) {
+        cerr << "ERROR: PluginRDFDescription::indexURL: ERROR: Failed to query document at <"
+             << url.toStdString() << ">: "
+             << query.getErrorString().toStdString() << endl;
+        return false;
+    }
+
+    if (results.empty()) {
+        cerr << "ERROR: PluginRDFDescription::indexURL: NOTE: Document at <"
+             << url.toStdString()
+             << "> does not appear to describe any plugin outputs" << endl;
+        return false;
+    }
+
+    // Note that an output may appear more than once, if it inherits
+    // more than one type (e.g. DenseOutput and QuantizedOutput).  So
+    // these results must accumulate
+
+    for (int i = 0; i < results.size(); ++i) {
+
+        QString outputId = results[i]["output_id"].value;
+
+        if (m_outputTypes.find(outputId) == m_outputTypes.end()) {
+            m_outputTypes[outputId] = OutputTypeUnknown;
+        }
+
+        QString outputType = results[i]["output_type"].value;
+
+        if (outputType.contains("DenseOutput")) {
+            m_outputDispositions[outputId] = OutputDense;
+        } else if (outputType.contains("SparseOutput")) {
+            m_outputDispositions[outputId] = OutputSparse;
+        } else if (outputType.contains("TrackLevelOutput")) {
+            m_outputDispositions[outputId] = OutputTrackLevel;
+        }
+
+        if (results[i]["feature_type"].type == SimpleSPARQLQuery::URIValue) {
+
+            QString featureType = results[i]["feature_type"].value;
+
+            if (featureType != "") {
+                if (m_outputTypes[outputId] == OutputEvents) {
+                    m_outputTypes[outputId] = OutputFeaturesAndEvents;
+                } else {
+                    m_outputTypes[outputId] = OutputFeatures;
+                }
+                m_outputFeatureTypeURIMap[outputId] = featureType;
+            }
+        }
+
+        if (results[i]["event_type"].type == SimpleSPARQLQuery::URIValue) {
+
+            QString eventType = results[i]["event_type"].value;
+
+            if (eventType != "") {
+                if (m_outputTypes[outputId] == OutputFeatures) {
+                    m_outputTypes[outputId] = OutputFeaturesAndEvents;
+                } else {
+                    m_outputTypes[outputId] = OutputEvents;
+                }
+                m_outputEventTypeURIMap[outputId] = eventType;
+            }
+        }
+            
+        if (results[i]["unit"].type == SimpleSPARQLQuery::LiteralValue) {
+
+            QString unit = results[i]["unit"].value;
+            
+            if (unit != "") {
+                m_outputUnitMap[outputId] = unit;
+            }
+        }
+    }
+
+    return true;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/PluginRDFDescription.h	Thu Sep 18 12:09:32 2008 +0000
@@ -0,0 +1,70 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 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 _PLUGIN_RDF_DESCRIPTION_H_
+#define _PLUGIN_RDF_DESCRIPTION_H_
+
+#include <QString>
+#include <map>
+
+class FileSource;
+
+class PluginRDFDescription
+{
+public:
+    PluginRDFDescription() : m_haveDescription(false) { }
+    PluginRDFDescription(QString pluginId);
+    ~PluginRDFDescription();
+
+    enum OutputType
+    {
+        OutputTypeUnknown,
+        OutputFeatures,
+        OutputEvents,
+        OutputFeaturesAndEvents
+    };
+
+    enum OutputDisposition
+    {
+        OutputDispositionUnknown,
+        OutputSparse,
+        OutputDense,
+        OutputTrackLevel
+    };
+
+    bool haveDescription() const;
+    OutputType getOutputType(QString outputId) const;
+    OutputDisposition getOutputDisposition(QString outputId) const;
+    QString getOutputFeatureTypeURI(QString outputId) const;
+    QString getOutputEventTypeURI(QString outputId) const;
+    QString getOutputUnit(QString outputId) const;
+
+protected:    
+    typedef std::map<QString, OutputType> OutputTypeMap;
+    typedef std::map<QString, OutputDisposition> OutputDispositionMap;
+    typedef std::map<QString, QString> OutputStringMap;
+
+    QString m_pluginId;
+    bool m_haveDescription;
+    OutputTypeMap m_outputTypes;
+    OutputDispositionMap m_outputDispositions;
+    OutputStringMap m_outputFeatureTypeURIMap;
+    OutputStringMap m_outputEventTypeURIMap;
+    OutputStringMap m_outputUnitMap;
+    bool indexURL(QString url);
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/PluginRDFIndexer.cpp	Thu Sep 18 12:09:32 2008 +0000
@@ -0,0 +1,286 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 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 "PluginRDFIndexer.h"
+
+#include "SimpleSPARQLQuery.h"
+
+#include "data/fileio/FileSource.h"
+#include "plugin/PluginIdentifier.h"
+
+#include <vamp-sdk/PluginHostAdapter.h>
+
+#include <QFileInfo>
+#include <QDir>
+#include <QUrl>
+
+#include <iostream>
+using std::cerr;
+using std::endl;
+using std::vector;
+using std::string;
+using Vamp::PluginHostAdapter;
+
+PluginRDFIndexer *
+PluginRDFIndexer::m_instance = 0;
+
+PluginRDFIndexer *
+PluginRDFIndexer::getInstance() 
+{
+    if (!m_instance) m_instance = new PluginRDFIndexer();
+    return m_instance;
+}
+
+PluginRDFIndexer::PluginRDFIndexer()
+{
+    vector<string> paths = PluginHostAdapter::getPluginPath();
+
+    QStringList filters;
+    filters << "*.n3";
+    filters << "*.N3";
+    filters << "*.rdf";
+    filters << "*.RDF";
+
+    // Search each Vamp plugin path for a .rdf file that either has
+    // name "soname", "soname:label" or "soname/label" plus RDF
+    // extension.  Use that order of preference, and prefer n3 over
+    // rdf extension.
+
+    for (vector<string>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
+        
+        QDir dir(i->c_str());
+        if (!dir.exists()) continue;
+
+        QStringList entries = dir.entryList
+            (filters, QDir::Files | QDir::Readable);
+
+        for (QStringList::const_iterator j = entries.begin();
+             j != entries.end(); ++j) {
+            QFileInfo fi(dir.filePath(*j));
+            indexFile(fi.absoluteFilePath());
+        }
+
+        QStringList subdirs = dir.entryList
+            (QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Readable);
+
+        for (QStringList::const_iterator j = subdirs.begin();
+             j != subdirs.end(); ++j) {
+            QDir subdir(dir.filePath(*j));
+            if (subdir.exists()) {
+                entries = subdir.entryList
+                    (filters, QDir::Files | QDir::Readable);
+                for (QStringList::const_iterator k = entries.begin();
+                     k != entries.end(); ++k) {
+                    QFileInfo fi(subdir.filePath(*k));
+                    indexFile(fi.absoluteFilePath());
+                }
+            }
+        }
+    }
+}
+
+PluginRDFIndexer::~PluginRDFIndexer()
+{
+    while (!m_cache.empty()) {
+        delete *m_cache.begin();
+        m_cache.erase(m_cache.begin());
+    }
+}
+
+QString
+PluginRDFIndexer::getURIForPluginId(QString pluginId)
+{
+    if (m_idToUriMap.find(pluginId) == m_idToUriMap.end()) return "";
+    return m_idToUriMap[pluginId];
+}
+
+QString
+PluginRDFIndexer::getIdForPluginURI(QString uri)
+{
+    if (m_uriToIdMap.find(uri) == m_uriToIdMap.end()) {
+
+        // Haven't found this uri referenced in any document on the
+        // local filesystem; try resolving the pre-fragment part of
+        // the uri as a document URL and reading that if possible.
+
+        // Because we may want to refer to this document again, we
+        // cache it locally if it turns out to exist.
+
+        cerr << "PluginRDFIndexer::getIdForPluginURI: NOTE: Failed to find a local RDF document describing plugin <" << uri.toStdString() << ">: attempting to retrieve one remotely by guesswork" << endl;
+
+        QString baseUrl = QUrl(uri).toString(QUrl::RemoveFragment);
+
+        FileSource source(baseUrl);
+        if (source.isAvailable()) {
+            source.waitForData();
+            if (indexFile(source.getLocalFilename())) {
+                m_cache.insert(new FileSource(source));
+            }
+        }
+
+        if (m_uriToIdMap.find(uri) == m_uriToIdMap.end()) {
+            m_uriToIdMap[uri] = "";
+        }
+    }
+
+    return m_uriToIdMap[uri];
+}
+
+QString
+PluginRDFIndexer::getDescriptionURLForPluginId(QString pluginId)
+{
+    if (m_idToDescriptionMap.find(pluginId) == m_idToDescriptionMap.end()) return "";
+    return m_idToDescriptionMap[pluginId];
+}
+
+QString
+PluginRDFIndexer::getDescriptionURLForPluginURI(QString uri)
+{
+    QString id = getIdForPluginURI(uri);
+    if (id == "") return "";
+    return getDescriptionURLForPluginId(id);
+}
+
+bool
+PluginRDFIndexer::indexFile(QString filepath)
+{
+    QUrl url = QUrl::fromLocalFile(filepath);
+    QString urlString = url.toString();
+    return indexURL(urlString);
+}
+
+bool
+PluginRDFIndexer::indexURL(QString urlString)
+{
+//    cerr << "PluginRDFIndexer::indexURL: url = <" << urlString.toStdString() << ">" << endl;
+
+    SimpleSPARQLQuery query
+        (QString
+         (
+             " PREFIX vamp: <http://purl.org/ontology/vamp/> "
+
+             " SELECT ?plugin ?library_id ?plugin_id "
+             " FROM <%1> "
+
+             " WHERE { "
+             "   ?plugin a vamp:Plugin . "
+
+             // Make the identifier and library parts optional, so
+             // that we can check and report helpfully if one or both
+             // is absent instead of just getting no results
+
+             "   OPTIONAL { ?plugin vamp:identifier ?plugin_id } . "
+
+             "   OPTIONAL { "
+             "     ?library a vamp:PluginLibrary ; "
+             "              vamp:available_plugin ?plugin ; "
+             "              vamp:identifier ?library_id "
+             "   } "
+             " } "
+             )
+         .arg(urlString));
+
+    SimpleSPARQLQuery::ResultList results = query.execute();
+
+    if (!query.isOK()) {
+        cerr << "ERROR: PluginRDFIndexer::indexURL: ERROR: Failed to index document at <"
+             << urlString.toStdString() << ">: "
+             << query.getErrorString().toStdString() << endl;
+        return false;
+    }
+
+    if (results.empty()) {
+        cerr << "PluginRDFIndexer::indexURL: NOTE: Document at <"
+             << urlString.toStdString()
+             << "> does not describe any vamp:Plugin resources" << endl;
+        return false;
+    }
+
+    bool foundSomething = false;
+    bool addedSomething = false;
+
+    for (SimpleSPARQLQuery::ResultList::iterator i = results.begin();
+         i != results.end(); ++i) {
+
+        QString pluginUri = (*i)["plugin"].value;
+        QString soname = (*i)["library_id"].value;
+        QString identifier = (*i)["plugin_id"].value;
+
+        if (identifier == "") {
+            cerr << "PluginRDFIndexer::indexURL: NOTE: Document at <"
+                 << urlString.toStdString()
+                 << "> fails to define any vamp:identifier for plugin <"
+                 << pluginUri.toStdString() << ">"
+                 << endl;
+            continue;
+        }
+        if (soname == "") {
+            cerr << "PluginRDFIndexer::indexURL: NOTE: Document at <"
+                 << urlString.toStdString() << "> does not associate plugin <"
+                 << pluginUri.toStdString() << "> with any implementation library"
+                 << endl;
+            continue;
+        }
+/*
+        cerr << "PluginRDFIndexer::indexURL: Document for plugin \""
+             << soname.toStdString() << ":" << identifier.toStdString()
+             << "\" (uri <" << pluginUri.toStdString() << ">) is at url <"
+             << urlString.toStdString() << ">" << endl;
+*/
+        QString pluginId = PluginIdentifier::createIdentifier
+            ("vamp", soname, identifier);
+
+        foundSomething = true;
+
+        if (m_idToDescriptionMap.find(pluginId) != m_idToDescriptionMap.end()) {
+            cerr << "PluginRDFIndexer::indexURL: NOTE: Plugin id \""
+                 << pluginId.toStdString() << "\", described in document at <"
+                 << urlString.toStdString()
+                 << ">, has already been described in document <"
+                 << m_idToDescriptionMap[pluginId].toStdString()
+                 << ">: ignoring this new description" << endl;
+            continue;
+        }
+
+        m_idToDescriptionMap[pluginId] = urlString;
+        m_idToUriMap[pluginId] = pluginUri;
+
+        addedSomething = true;
+
+        if (pluginUri != "") {
+            if (m_uriToIdMap.find(pluginUri) != m_uriToIdMap.end()) {
+                cerr << "PluginRDFIndexer::indexURL: WARNING: Found multiple plugins with the same URI:" << endl;
+                cerr << "  1. Plugin id \"" << m_uriToIdMap[pluginUri].toStdString() << "\"" << endl;
+                cerr << "     described in <" << m_idToDescriptionMap[m_uriToIdMap[pluginUri]].toStdString() << ">" << endl;
+                cerr << "  2. Plugin id \"" << pluginId.toStdString() << "\"" << endl;
+                cerr << "     described in <" << urlString.toStdString() << ">" << endl;
+                cerr << "both claim URI <" << pluginUri.toStdString() << ">" << endl;
+            } else {
+                m_uriToIdMap[pluginUri] = pluginId;
+            }
+        }
+    }
+
+    if (!foundSomething) {
+        cerr << "PluginRDFIndexer::indexURL: NOTE: Document at <"
+             << urlString.toStdString()
+             << "> does not sufficiently describe any plugins" << endl;
+    }
+    
+    return addedSomething;
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/PluginRDFIndexer.h	Thu Sep 18 12:09:32 2008 +0000
@@ -0,0 +1,50 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 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 _PLUGIN_RDF_INDEXER_H_
+#define _PLUGIN_RDF_INDEXER_H_
+
+#include <QString>
+#include <map>
+#include <set>
+
+class FileSource;
+
+class PluginRDFIndexer
+{
+public:
+    static PluginRDFIndexer *getInstance();
+
+    QString getURIForPluginId(QString pluginId);
+    QString getIdForPluginURI(QString uri);
+    QString getDescriptionURLForPluginId(QString pluginId);
+    QString getDescriptionURLForPluginURI(QString uri);
+
+    ~PluginRDFIndexer();
+
+protected:
+    PluginRDFIndexer();
+    typedef std::map<QString, QString> StringMap;
+    StringMap m_uriToIdMap;
+    StringMap m_idToUriMap;
+    StringMap m_idToDescriptionMap;
+    bool indexFile(QString path);
+    bool indexURL(QString url);
+    std::set<FileSource *> m_cache;
+    static PluginRDFIndexer *m_instance;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/RDFImporter.cpp	Thu Sep 18 12:09:32 2008 +0000
@@ -0,0 +1,435 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 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 "RDFImporter.h"
+
+#include <map>
+#include <vector>
+
+#include <iostream>
+#include <cmath>
+
+#include "SimpleSPARQLQuery.h"
+
+#include "base/ProgressReporter.h"
+#include "base/RealTime.h"
+
+#include "data/model/SparseOneDimensionalModel.h"
+#include "data/model/SparseTimeValueModel.h"
+#include "data/model/EditableDenseThreeDimensionalModel.h"
+
+using std::cerr;
+using std::endl;
+
+class RDFImporterImpl
+{
+public:
+    RDFImporterImpl(QString url, int sampleRate);
+    virtual ~RDFImporterImpl();
+    
+    bool isOK();
+    QString getErrorString() const;
+
+    std::vector<Model *> getDataModels(ProgressReporter *);
+
+protected:
+    QString m_uristring;
+    QString m_errorString;
+    int m_sampleRate;
+
+    typedef std::vector<float> ValueList;
+    typedef std::map<RealTime, ValueList> TimeValueMap;
+    typedef std::map<QString, TimeValueMap> TypeTimeValueMap;
+    typedef std::map<QString, TypeTimeValueMap> SourceTypeTimeValueMap;
+
+    void extractStructure(const TimeValueMap &map, bool &sparse,
+                          int &minValueCount, int &maxValueCount);
+
+    void fillModel(SparseOneDimensionalModel *, const TimeValueMap &);
+    void fillModel(SparseTimeValueModel *, const TimeValueMap &);
+    void fillModel(EditableDenseThreeDimensionalModel *, const TimeValueMap &);
+};
+
+
+QString
+RDFImporter::getKnownExtensions()
+{
+    return "*.rdf *.n3 *.ttl";
+}
+
+RDFImporter::RDFImporter(QString url, int sampleRate) :
+    m_d(new RDFImporterImpl(url, sampleRate)) 
+{
+}
+
+RDFImporter::~RDFImporter()
+{
+    delete m_d;
+}
+
+bool
+RDFImporter::isOK()
+{
+    return m_d->isOK();
+}
+
+QString
+RDFImporter::getErrorString() const
+{
+    return m_d->getErrorString();
+}
+
+std::vector<Model *>
+RDFImporter::getDataModels(ProgressReporter *r)
+{
+    return m_d->getDataModels(r);
+}
+
+RDFImporterImpl::RDFImporterImpl(QString uri, int sampleRate) :
+    m_uristring(uri),
+    m_sampleRate(sampleRate)
+{
+}
+
+RDFImporterImpl::~RDFImporterImpl()
+{
+}
+
+bool
+RDFImporterImpl::isOK()
+{
+    return (m_errorString == "");
+}
+
+QString
+RDFImporterImpl::getErrorString() const
+{
+    return m_errorString;
+}
+
+std::vector<Model *>
+RDFImporterImpl::getDataModels(ProgressReporter *reporter)
+{
+    std::vector<Model *> models;
+
+    // Our query is intended to retrieve every thing that has a time,
+    // and every feature type and value associated with a thing that
+    // has a time.
+
+    // We will then need to refine this big bag of results into a set
+    // of data models.
+
+    // Results that have different source signals should go into
+    // different models.
+
+    // Results that have different feature types should go into
+    // different models.
+
+    // Results that are sparse should go into different models from
+    // those that are dense (we need to examine the timestamps to
+    // establish this -- if the timestamps are regular, the results
+    // are dense -- so we can't do it as we go along, only after
+    // collecting all results).
+
+    // Timed things that have features associated with them should not
+    // appear directly in any model -- their features should appear
+    // instead -- and these should be different models from those used
+    // for timed things that do not have features.
+
+    // As we load the results, we'll push them into a partially
+    // structured container that maps from source signal (URI as
+    // string) -> feature type (likewise) -> time -> list of values.
+    // If the source signal or feature type is unavailable, the empty
+    // string will do.
+
+    SourceTypeTimeValueMap m;
+
+    QString queryString = QString(
+
+        " PREFIX event: <http://purl.org/NET/c4dm/event.owl#>"
+        " PREFIX time: <http://purl.org/NET/c4dm/timeline.owl#>"
+        " PREFIX mo: <http://purl.org/ontology/mo/>"
+        " PREFIX af: <http://purl.org/ontology/af/>"
+
+        " SELECT ?signalSource ?time ?eventType ?value"
+        " FROM <%1>"
+
+        " WHERE {"
+        "   ?signal mo:available_as ?signalSource ."
+        "   ?signal mo:time ?interval ."
+        "   ?interval time:onTimeLine ?tl ."
+        "   ?t time:onTimeLine ?tl ."
+        "   ?t time:at ?time ."
+        "   ?timedThing event:time ?t ."
+        "   ?timedThing a ?eventType ."
+        "   OPTIONAL {"
+        "     ?timedThing af:hasFeature ?feature ."
+        "     ?feature af:value ?value"
+        "   }"
+        " }"
+
+        ).arg(m_uristring);
+
+    SimpleSPARQLQuery query(queryString);
+    query.setProgressReporter(reporter);
+
+    cerr << "Query will be: " << queryString.toStdString() << endl;
+
+    SimpleSPARQLQuery::ResultList results = query.execute();
+
+    if (!query.isOK()) {
+        m_errorString = query.getErrorString();
+        return models;
+    }
+
+    if (query.wasCancelled()) {
+        m_errorString = "Query cancelled";
+        return models;
+    }        
+
+    for (int i = 0; i < results.size(); ++i) {
+
+        QString source = results[i]["signalSource"].value;
+
+        QString timestring = results[i]["time"].value;
+        RealTime time;
+        time = RealTime::fromXsdDuration(timestring.toStdString());
+        cerr << "time = " << time.toString() << " (from xsd:duration \""
+             << timestring.toStdString() << "\")" << endl;
+
+        QString type = results[i]["eventType"].value;
+
+        QString valuestring = results[i]["value"].value;
+        float value = 0.f;
+        bool haveValue = false;
+        if (valuestring != "") {
+            value = valuestring.toFloat(&haveValue);
+            cerr << "value = " << value << endl;
+        }
+
+        if (haveValue) {
+            m[source][type][time].push_back(value);
+        } else if (m[source][type].find(time) == m[source][type].end()) {
+            m[source][type][time] = ValueList();
+        }
+    }
+
+    for (SourceTypeTimeValueMap::const_iterator mi = m.begin();
+         mi != m.end(); ++mi) {
+        
+        QString source = mi->first;
+
+        for (TypeTimeValueMap::const_iterator ttvi = mi->second.begin();
+             ttvi != mi->second.end(); ++ttvi) {
+            
+            QString type = ttvi->first;
+
+            // Now we need to work out what sort of model to use for
+            // this source/type combination.  Ultimately we'll
+            // hopefully be able to map directly from the type to the
+            // model on the basis of known structures for the types,
+            // but we also want to be able to handle untyped data
+            // according to its apparent structure so let's do that
+            // first.
+
+            bool sparse = false;
+            int minValueCount = 0, maxValueCount = 0;
+
+            extractStructure(ttvi->second, sparse, minValueCount, maxValueCount);
+    
+            cerr << "For source \"" << source.toStdString() << "\", type \""
+                 << type.toStdString() << "\" we have sparse = " << sparse
+                 << ", min value count = " << minValueCount << ", max = "
+                 << maxValueCount << endl;
+
+            // Model allocations:
+            //
+            // Sparse, no values: SparseOneDimensionalModel
+            //
+            // Sparse, always 1 value: SparseTimeValueModel
+            //
+            // Sparse, > 1 value: No standard model for this.  If
+            // there are always 2 values, perhaps hack it into
+            // NoteModel for now?  Or always use SparseTimeValueModel
+            // and discard all but the first value.
+            //
+            // Dense, no values: Meaningless; no suitable model
+            //
+            // Dense, > 0 values: EditableDenseThreeDimensionalModel
+            //
+            // These should just be our fallback positions; we want to
+            // be reading semantic data from the RDF in order to pick
+            // the right model directly
+
+            enum { SODM, STVM, EDTDM } modelType = SODM;
+
+            if (sparse) {
+                if (maxValueCount == 0) {
+                    modelType = SODM;
+                } else if (minValueCount == 1 && maxValueCount == 1) {
+                    modelType = STVM;
+                } else {
+                    cerr << "WARNING: No suitable model available for sparse data with between " << minValueCount << " and " << maxValueCount << " values" << endl;
+                    modelType = STVM;
+                }
+            } else {
+                if (maxValueCount == 0) {
+                    cerr << "WARNING: Dense data set with no values is not meaningful, skipping" << endl;
+                    continue;
+                } else {
+                    modelType = EDTDM;
+                }
+            }
+
+            //!!! set model name &c
+
+            if (modelType == SODM) {
+
+                SparseOneDimensionalModel *model = 
+                    new SparseOneDimensionalModel(m_sampleRate, 1, false);
+                
+                fillModel(model, ttvi->second);
+                models.push_back(model);
+
+            } else if (modelType == STVM) {
+
+                SparseTimeValueModel *model = 
+                    new SparseTimeValueModel(m_sampleRate, 1, false);
+                
+                fillModel(model, ttvi->second);
+                models.push_back(model);
+
+            } else {
+                
+                EditableDenseThreeDimensionalModel *model =
+                    new EditableDenseThreeDimensionalModel(m_sampleRate, 1, 0,
+                                                           false);
+
+                fillModel(model, ttvi->second);
+                models.push_back(model);
+            }
+        }
+    }
+
+
+    return models;
+}
+
+void
+RDFImporterImpl::extractStructure(const TimeValueMap &tvm,
+                                  bool &sparse,
+                                  int &minValueCount,
+                                  int &maxValueCount)
+{
+    // These are floats intentionally rather than RealTime --
+    // see logic for handling rounding error below
+    float firstTime = 0.f;
+    float timeStep = 0.f;
+    bool haveTimeStep = false;
+    
+    for (TimeValueMap::const_iterator tvi = tvm.begin(); tvi != tvm.end(); ++tvi) {
+        
+        RealTime time = tvi->first;
+        int valueCount = tvi->second.size();
+        
+        if (tvi == tvm.begin()) {
+            
+            minValueCount = valueCount;
+            maxValueCount = valueCount;
+            
+            firstTime = time.toDouble();
+            
+        } else {
+            
+            if (valueCount < minValueCount) minValueCount = valueCount;
+            if (valueCount > maxValueCount) maxValueCount = valueCount;
+            
+            if (!haveTimeStep) {
+                timeStep = time.toDouble() - firstTime;
+                if (timeStep == 0.f) sparse = true;
+                haveTimeStep = true;
+            } else if (!sparse) {
+                // test whether this time is within
+                // rounding-error range of being an integer
+                // multiple of some constant away from the
+                // first time
+                float timeAsFloat = time.toDouble();
+                int count = int((timeAsFloat - firstTime) / timeStep + 0.5);
+                float expected = firstTime + (timeStep * count);
+                if (fabsf(expected - timeAsFloat) > 1e-6) {
+                    cerr << "Event at " << timeAsFloat << " is not evenly spaced -- would expect it to be " << expected << " for a spacing of " << count << " * " << timeStep << endl;
+                    sparse = true;
+                }
+            }
+        }
+    }
+}
+
+void
+RDFImporterImpl::fillModel(SparseOneDimensionalModel *model,
+                           const TimeValueMap &tvm)
+{
+    //!!! labels &c not yet handled
+
+    for (TimeValueMap::const_iterator tvi = tvm.begin();
+         tvi != tvm.end(); ++tvi) {
+        
+        RealTime time = tvi->first;
+        long frame = RealTime::realTime2Frame(time, m_sampleRate);
+
+        SparseOneDimensionalModel::Point point(frame);
+
+        model->addPoint(point);
+    }
+}
+
+void
+RDFImporterImpl::fillModel(SparseTimeValueModel *model,
+                           const TimeValueMap &tvm)
+{
+    //!!! labels &c not yet handled
+
+    for (TimeValueMap::const_iterator tvi = tvm.begin();
+         tvi != tvm.end(); ++tvi) {
+        
+        RealTime time = tvi->first;
+        long frame = RealTime::realTime2Frame(time, m_sampleRate);
+
+        float value = 0.f;
+        if (!tvi->second.empty()) value = *tvi->second.begin();
+        
+        SparseTimeValueModel::Point point(frame, value, "");
+
+        model->addPoint(point);
+    }
+}
+
+void
+RDFImporterImpl::fillModel(EditableDenseThreeDimensionalModel *model,
+                           const TimeValueMap &tvm)
+{
+    //!!! labels &c not yet handled
+
+    //!!! start time offset not yet handled
+
+    size_t col = 0;
+
+    for (TimeValueMap::const_iterator tvi = tvm.begin();
+         tvi != tvm.end(); ++tvi) {
+        
+        model->setColumn(col++, tvi->second);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/RDFImporter.h	Thu Sep 18 12:09:32 2008 +0000
@@ -0,0 +1,52 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 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 _RDF_IMPORTER_H_
+#define _RDF_IMPORTER_H_
+
+#include <QObject>
+#include <QString>
+
+#include <vector>
+
+class Model;
+class RDFImporterImpl;
+class ProgressReporter;
+
+class RDFImporter : public QObject
+{
+    Q_OBJECT
+
+public:
+    /**
+     * Return the file extensions that we have data file readers for,
+     * in a format suitable for use with QFileDialog.  For example,
+     * "*.rdf *.n3".
+     */
+    static QString getKnownExtensions();
+
+    RDFImporter(QString url, int sampleRate);
+    virtual ~RDFImporter();
+
+    bool isOK();
+    QString getErrorString() const;
+
+    std::vector<Model *> getDataModels(ProgressReporter *reporter);
+
+protected:
+    RDFImporterImpl *m_d;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/RDFTransformFactory.cpp	Thu Sep 18 12:09:32 2008 +0000
@@ -0,0 +1,258 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 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 "RDFTransformFactory.h"
+
+#include <map>
+#include <vector>
+
+#include <redland.h>
+#include <rasqal.h>
+
+#include <iostream>
+#include <cmath>
+
+#include "SimpleSPARQLQuery.h"
+#include "PluginRDFIndexer.h"
+#include "base/ProgressReporter.h"
+
+#include "transform/TransformFactory.h"
+
+using std::cerr;
+using std::endl;
+
+typedef const unsigned char *STR; // redland's expected string type
+
+
+class RDFTransformFactoryImpl
+{
+public:
+    RDFTransformFactoryImpl(QString url);
+    virtual ~RDFTransformFactoryImpl();
+    
+    bool isOK();
+    QString getErrorString() const;
+
+    std::vector<Transform> getTransforms(ProgressReporter *);
+
+protected:
+    QString m_urlString;
+    QString m_errorString;
+};
+
+
+QString
+RDFTransformFactory::getKnownExtensions()
+{
+    return "*.rdf *.n3 *.ttl";
+}
+
+RDFTransformFactory::RDFTransformFactory(QString url) :
+    m_d(new RDFTransformFactoryImpl(url)) 
+{
+}
+
+RDFTransformFactory::~RDFTransformFactory()
+{
+    delete m_d;
+}
+
+bool
+RDFTransformFactory::isOK()
+{
+    return m_d->isOK();
+}
+
+QString
+RDFTransformFactory::getErrorString() const
+{
+    return m_d->getErrorString();
+}
+
+std::vector<Transform>
+RDFTransformFactory::getTransforms(ProgressReporter *r)
+{
+    return m_d->getTransforms(r);
+}
+
+RDFTransformFactoryImpl::RDFTransformFactoryImpl(QString url) :
+    m_urlString(url)
+{
+}
+
+RDFTransformFactoryImpl::~RDFTransformFactoryImpl()
+{
+}
+
+bool
+RDFTransformFactoryImpl::isOK()
+{
+    return (m_errorString == "");
+}
+
+QString
+RDFTransformFactoryImpl::getErrorString() const
+{
+    return m_errorString;
+}
+
+std::vector<Transform>
+RDFTransformFactoryImpl::getTransforms(ProgressReporter *reporter)
+{
+    std::vector<Transform> transforms;
+
+    SimpleSPARQLQuery query
+        (QString
+         (
+             " PREFIX vamp: <http://purl.org/ontology/vamp/> "
+
+             " SELECT ?transform ?plugin ?output ?program "
+             "        ?step_size ?block_size ?window_type "
+             "        ?sample_rate ?start ?duration "
+
+             " FROM <%1> "
+
+             " 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 } "
+             " } "
+             )
+         .arg(m_urlString));
+
+    SimpleSPARQLQuery::ResultList results = query.execute();
+
+    if (!query.isOK()) {
+        m_errorString = query.getErrorString();
+        return transforms;
+    }
+
+    if (query.wasCancelled()) {
+        m_errorString = "Query cancelled";
+        return transforms;
+    }
+
+    PluginRDFIndexer *indexer = PluginRDFIndexer::getInstance();
+
+    for (int i = 0; i < results.size(); ++i) {
+
+        SimpleSPARQLQuery::KeyValueMap &result = results[i];
+
+        QString transformUri = result["transform"].value;
+        QString pluginUri = result["plugin"].value;
+
+        QString pluginId = indexer->getIdForPluginURI(pluginUri);
+
+        if (pluginId == "") {
+            cerr << "RDFTransformFactory: WARNING: Unknown plugin <"
+                 << pluginUri.toStdString() << "> for transform <"
+                 << transformUri.toStdString() << ">" << endl;
+            continue;
+        }
+
+        Transform transform;
+        transform.setPluginIdentifier(pluginId);
+        
+        if (result["output"].type == SimpleSPARQLQuery::LiteralValue) {
+            transform.setOutput(result["output"].value);
+        }
+
+        if (result["program"].type == SimpleSPARQLQuery::LiteralValue) {
+            transform.setProgram(result["program"].value);
+        }
+        
+        if (result["step_size"].type == SimpleSPARQLQuery::LiteralValue) {
+            transform.setStepSize(result["step_size"].value.toUInt());
+        }
+        
+        if (result["block_size"].type == SimpleSPARQLQuery::LiteralValue) {
+            transform.setBlockSize(result["block_size"].value.toUInt());
+        }
+        
+        if (result["window_type"].type == SimpleSPARQLQuery::LiteralValue) {
+            cerr << "NOTE: can't handle window type yet (value is \""
+                 << result["window_type"].value.toStdString() << "\")" << endl;
+        }
+        
+        if (result["sample_rate"].type == SimpleSPARQLQuery::LiteralValue) {
+            transform.setStepSize(result["sample_rate"].value.toFloat());
+        }
+
+        if (result["start"].type == SimpleSPARQLQuery::LiteralValue) {
+            transform.setStartTime(RealTime::fromXsdDuration
+                                   (result["start"].value.toStdString()));
+        }
+
+        if (result["duration"].type == SimpleSPARQLQuery::LiteralValue) {
+            transform.setDuration(RealTime::fromXsdDuration
+                                  (result["duration"].value.toStdString()));
+        }
+
+        SimpleSPARQLQuery paramQuery
+            (QString
+             (
+                 " PREFIX vamp: <http://purl.org/ontology/vamp/> "
+
+                 " SELECT ?param_id ?param_value "
+
+                 " FROM <%1> "
+
+                 " WHERE { "
+                 "   <%2> vamp:parameter ?param . "
+                 "   ?param vamp:identifier ?param_id ; "
+                 "          vamp:value ?param_value "
+                 " } "
+                 )
+             .arg(m_urlString)
+             .arg(transformUri));
+        
+        SimpleSPARQLQuery::ResultList paramResults = paramQuery.execute();
+
+        if (!paramQuery.isOK()) {
+            m_errorString = paramQuery.getErrorString();
+            return transforms;
+        }
+
+        if (paramQuery.wasCancelled()) {
+            m_errorString = "Query cancelled";
+            return transforms;
+        }
+
+        for (int j = 0; j < paramResults.size(); ++j) {
+
+            QString paramId = paramResults[j]["param_id"].value;
+            QString paramValue = paramResults[j]["param_value"].value;
+
+            if (paramId == "" || paramValue == "") continue;
+
+            transform.setParameter(paramId, paramValue.toFloat());
+        }
+
+        cerr << "RDFTransformFactory: NOTE: Transform is: " << endl;
+        cerr << transform.toXmlString().toStdString() << endl;
+
+        transforms.push_back(transform);
+    }
+        
+    return transforms;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/RDFTransformFactory.h	Thu Sep 18 12:09:32 2008 +0000
@@ -0,0 +1,48 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 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 _RDF_TRANSFORM_FACTORY_H_
+#define _RDF_TRANSFORM_FACTORY_H_
+
+#include <QObject>
+#include <QString>
+
+#include <vector>
+
+#include "transform/Transform.h"
+
+class RDFTransformFactoryImpl;
+class ProgressReporter;
+
+class RDFTransformFactory : public QObject
+{
+    Q_OBJECT
+
+public:
+    static QString getKnownExtensions();
+
+    RDFTransformFactory(QString url);
+    virtual ~RDFTransformFactory();
+
+    bool isOK();
+    QString getErrorString() const;
+
+    std::vector<Transform> getTransforms(ProgressReporter *reporter);
+
+protected:
+    RDFTransformFactoryImpl *m_d;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/SimpleSPARQLQuery.cpp	Thu Sep 18 12:09:32 2008 +0000
@@ -0,0 +1,235 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 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 "SimpleSPARQLQuery.h"
+#include "base/ProgressReporter.h"
+
+#include <rasqal.h>
+
+#include <iostream>
+
+using std::cerr;
+using std::endl;
+
+class SimpleSPARQLQuery::Impl
+{
+public:
+    Impl(QString query);
+    ~Impl();
+
+    void setProgressReporter(ProgressReporter *reporter) { m_reporter = reporter; }
+    bool wasCancelled() const { return m_cancelled; }
+
+    ResultList execute();
+
+    bool isOK() const;
+    QString getErrorString() const;
+
+protected:
+    static void errorHandler(void *, raptor_locator *, const char *);
+
+    static bool m_initialised;
+    
+    QString m_query;
+    QString m_errorString;
+    ProgressReporter *m_reporter;
+    bool m_cancelled;
+};
+
+SimpleSPARQLQuery::SimpleSPARQLQuery(QString query) :
+    m_impl(new Impl(query)) { }
+
+SimpleSPARQLQuery::~SimpleSPARQLQuery() 
+{
+    delete m_impl;
+}
+
+void
+SimpleSPARQLQuery::setProgressReporter(ProgressReporter *reporter)
+{
+    m_impl->setProgressReporter(reporter);
+}
+
+bool
+SimpleSPARQLQuery::wasCancelled() const
+{
+    return m_impl->wasCancelled();
+}
+
+SimpleSPARQLQuery::ResultList
+SimpleSPARQLQuery::execute()
+{
+    return m_impl->execute();
+}
+
+bool
+SimpleSPARQLQuery::isOK() const
+{
+    return m_impl->isOK();
+}
+
+QString
+SimpleSPARQLQuery::getErrorString() const
+{
+    return m_impl->getErrorString();
+}
+
+bool
+SimpleSPARQLQuery::Impl::m_initialised = false;
+
+SimpleSPARQLQuery::Impl::Impl(QString query) :
+    m_query(query),
+    m_reporter(0),
+    m_cancelled(false)
+{
+    //!!! fortunately this global stuff goes away in future rasqal versions
+    if (!m_initialised) {
+        rasqal_init();
+    }
+}
+
+SimpleSPARQLQuery::Impl::~Impl()
+{
+//!!!    rasqal_finish();
+}
+
+bool
+SimpleSPARQLQuery::Impl::isOK() const
+{
+    return (m_errorString == "");
+}
+
+QString
+SimpleSPARQLQuery::Impl::getErrorString() const
+{
+    return m_errorString;
+}
+
+void
+SimpleSPARQLQuery::Impl::errorHandler(void *data, 
+                                      raptor_locator *locator,
+                                      const char *message) 
+{
+    SimpleSPARQLQuery::Impl *impl = (SimpleSPARQLQuery::Impl *)data;
+    
+//    char buffer[256];
+//    raptor_format_locator(buffer, 255, locator);
+//    impl->m_errorString = QString("%1 - %2").arg(buffer).arg(message);
+
+    impl->m_errorString = message;
+
+    cerr << "SimpleSPARQLQuery: ERROR: " << impl->m_errorString.toStdString() << endl;
+}
+
+SimpleSPARQLQuery::ResultList
+SimpleSPARQLQuery::Impl::execute()
+{
+    ResultList list;
+
+    rasqal_query *query = rasqal_new_query("sparql", NULL);
+    if (!query) {
+        m_errorString = "Failed to construct query";
+        cerr << "SimpleSPARQLQuery: ERROR: " << m_errorString.toStdString() << endl;
+        return list;
+    }
+
+    rasqal_query_set_error_handler(query, this, errorHandler);
+    rasqal_query_set_fatal_error_handler(query, this, errorHandler);
+
+    if (rasqal_query_prepare
+        (query, (const unsigned char *)m_query.toUtf8().data(), NULL)) {
+        cerr << "SimpleSPARQLQuery: Failed to prepare query" << endl;
+        rasqal_free_query(query);
+        return list;
+    }
+
+    rasqal_query_results *results = rasqal_query_execute(query);
+    
+//    cerr << "Query executed" << endl;
+
+    if (!results) {
+        cerr << "SimpleSPARQLQuery: RASQAL query failed" << endl;
+        rasqal_free_query(query);
+        return list;
+    }
+
+    if (!rasqal_query_results_is_bindings(results)) {
+        cerr << "SimpleSPARQLQuery: RASQAL query has wrong result type (not bindings)" << endl;
+        rasqal_free_query_results(results);
+        rasqal_free_query(query);
+        return list;
+    }
+    
+    int resultCount = 0;
+    int resultTotal = rasqal_query_results_get_count(results); // probably wrong
+    m_cancelled = false;
+
+    while (!rasqal_query_results_finished(results)) {
+
+        int count = rasqal_query_results_get_bindings_count(results);
+
+        KeyValueMap resultmap;
+
+        for (int i = 0; i < count; ++i) {
+
+            const unsigned char *name =
+                rasqal_query_results_get_binding_name(results, i);
+
+            rasqal_literal *literal =
+                rasqal_query_results_get_binding_value(results, i);
+
+            QString key = (const char *)name;
+
+            if (!literal) {
+                resultmap[key] = Value();
+                continue;
+            }
+
+            ValueType type = LiteralValue;
+            if (literal->type == RASQAL_LITERAL_URI) type = URIValue;
+            else if (literal->type == RASQAL_LITERAL_BLANK) type = BlankValue;
+
+            QString text = (const char *)rasqal_literal_as_string(literal);
+
+            resultmap[key] = Value(type, text);
+        }
+
+        list.push_back(resultmap);
+
+        rasqal_query_results_next(results);
+
+        resultCount++;
+
+        if (m_reporter) {
+            if (resultCount >= resultTotal) {
+                if (m_reporter->isDefinite()) m_reporter->setDefinite(false);
+                m_reporter->setProgress(resultCount);
+            } else {
+                m_reporter->setProgress((resultCount * 100) / resultTotal);
+            }
+
+            if (m_reporter->wasCancelled()) {
+                m_cancelled = true;
+                break;
+            }
+        }
+    }
+
+    rasqal_free_query_results(results);
+    rasqal_free_query(query);
+
+    return list;
+}
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/SimpleSPARQLQuery.h	Thu Sep 18 12:09:32 2008 +0000
@@ -0,0 +1,56 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 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 _SIMPLE_SPARQL_QUERY_H_
+#define _SIMPLE_SPARQL_QUERY_H_
+
+#include <QString>
+#include <map>
+#include <vector>
+
+class ProgressReporter;
+
+class SimpleSPARQLQuery
+{
+public:
+    enum ValueType { NoValue, URIValue, LiteralValue, BlankValue };
+
+    struct Value {
+        Value() : type(NoValue), value() { }
+        Value(ValueType t, QString v) : type(t), value(v) { }
+        ValueType type;
+        QString value;
+    };
+
+    typedef std::map<QString, Value> KeyValueMap;
+    typedef std::vector<KeyValueMap> ResultList;
+
+    SimpleSPARQLQuery(QString query);
+    ~SimpleSPARQLQuery();
+
+    void setProgressReporter(ProgressReporter *reporter);
+    bool wasCancelled() const;
+    
+    ResultList execute();
+
+    bool isOK() const;
+    QString getErrorString() const;
+
+protected:
+    class Impl;
+    Impl *m_impl;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/rdf.pro	Thu Sep 18 12:09:32 2008 +0000
@@ -0,0 +1,26 @@
+TEMPLATE = lib
+
+SV_UNIT_PACKAGES = redland
+load(../sv.prf)
+
+CONFIG += sv staticlib qt thread warn_on stl rtti exceptions
+
+TARGET = svrdf
+
+DEPENDPATH += . .. 
+INCLUDEPATH += . ..
+OBJECTS_DIR = tmp_obj
+MOC_DIR = tmp_moc
+
+# Input
+HEADERS += PluginRDFDescription.h \
+           PluginRDFIndexer.h \
+           RDFImporter.h \
+	   RDFTransformFactory.h \
+           SimpleSPARQLQuery.h
+SOURCES += PluginRDFDescription.cpp \
+           PluginRDFIndexer.cpp \
+           RDFImporter.cpp \
+           RDFTransformFactory.cpp \
+           SimpleSPARQLQuery.cpp
+
--- a/transform/TransformFactory.cpp	Thu Aug 07 16:06:59 2008 +0000
+++ b/transform/TransformFactory.cpp	Thu Sep 18 12:09:32 2008 +0000
@@ -484,14 +484,18 @@
         FeatureExtractionPluginFactory *factory = 
             FeatureExtractionPluginFactory::instanceFor(pluginId);
 
-        plugin = factory->instantiatePlugin(pluginId, rate);
+        if (factory) {
+            plugin = factory->instantiatePlugin(pluginId, rate);
+        }
 
     } else {
 
         RealTimePluginFactory *factory = 
             RealTimePluginFactory::instanceFor(pluginId);
-            
-        plugin = factory->instantiatePlugin(pluginId, 0, 0, rate, 1024, 1);
+
+        if (factory) {
+            plugin = factory->instantiatePlugin(pluginId, 0, 0, rate, 1024, 1);
+        }
     }
 
     return plugin;