changeset 440:5746c559af15

* Merge revisions 1131 to 1201 from sv-rdf-import branch
author Chris Cannam
date Thu, 18 Sep 2008 12:33:30 +0000
parents beb2948baa77
children 288f45533041
files data/fileio/AudioFileReaderFactory.cpp data/fileio/PlaylistFileReader.cpp data/fileio/PlaylistFileReader.h rdf/PluginRDFDescription.cpp rdf/PluginRDFDescription.h rdf/PluginRDFIndexer.cpp rdf/RDFImporter.cpp rdf/RDFTransformFactory.cpp rdf/SimpleSPARQLQuery.cpp rdf/SimpleSPARQLQuery.h rdf/rdf.pro
diffstat 11 files changed, 723 insertions(+), 199 deletions(-) [+]
line wrap: on
line diff
--- a/data/fileio/AudioFileReaderFactory.cpp	Thu Sep 18 12:09:32 2008 +0000
+++ b/data/fileio/AudioFileReaderFactory.cpp	Thu Sep 18 12:33:30 2008 +0000
@@ -104,6 +104,10 @@
                  ResamplingWavFileReader::CacheInTemporaryFile,
                  targetRate,
                  reporter);
+            if (!reader->isOK()) {
+                delete reader;
+                reader = 0;
+            }
         }
     }
     
@@ -119,6 +123,10 @@
                  OggVorbisFileReader::CacheInTemporaryFile,
                  targetRate,
                  reporter);
+            if (!reader->isOK()) {
+                delete reader;
+                reader = 0;
+            }
         }
     }
 #endif
@@ -135,6 +143,10 @@
                  MP3FileReader::CacheInTemporaryFile,
                  targetRate,
                  reporter);
+            if (!reader->isOK()) {
+                delete reader;
+                reader = 0;
+            }
         }
     }
 #endif
@@ -150,6 +162,99 @@
                  QuickTimeFileReader::CacheInTemporaryFile,
                  targetRate,
                  reporter);
+            if (!reader->isOK()) {
+                delete reader;
+                reader = 0;
+            }
+        }
+    }
+#endif
+
+    // If none of the readers claimed to support this file extension,
+    // perhaps the extension is missing or misleading.  Try again,
+    // ignoring it.  We have to be confident that the reader won't
+    // open just any old text file or whatever and pretend it's
+    // succeeded
+
+    if (!reader) {
+
+        reader = new WavFileReader(source);
+
+        if (targetRate != 0 &&
+            reader->isOK() &&
+            reader->getSampleRate() != targetRate) {
+
+            std::cerr << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", creating resampling reader" << std::endl;
+
+            delete reader;
+            reader = new ResamplingWavFileReader
+                (source,
+                 threading ?
+                 ResamplingWavFileReader::ResampleThreaded :
+                 ResamplingWavFileReader::ResampleAtOnce,
+                 ResamplingWavFileReader::CacheInTemporaryFile,
+                 targetRate,
+                 reporter);
+        }
+
+        if (!reader->isOK()) {
+            delete reader;
+            reader = 0;
+        }
+    }
+    
+#ifdef HAVE_OGGZ
+#ifdef HAVE_FISHSOUND
+    if (!reader) {
+        reader = new OggVorbisFileReader
+            (source,
+             threading ?
+             OggVorbisFileReader::DecodeThreaded :
+             OggVorbisFileReader::DecodeAtOnce,
+             OggVorbisFileReader::CacheInTemporaryFile,
+             targetRate,
+             reporter);
+
+        if (!reader->isOK()) {
+            delete reader;
+            reader = 0;
+        }
+    }
+#endif
+#endif
+
+#ifdef HAVE_MAD
+    if (!reader) {
+        reader = new MP3FileReader
+            (source,
+             threading ?
+             MP3FileReader::DecodeThreaded :
+             MP3FileReader::DecodeAtOnce,
+             MP3FileReader::CacheInTemporaryFile,
+             targetRate,
+             reporter);
+
+        if (!reader->isOK()) {
+            delete reader;
+            reader = 0;
+        }
+    }
+#endif
+
+#ifdef HAVE_QUICKTIME
+    if (!reader) {
+        reader = new QuickTimeFileReader
+            (source,
+             threading ?
+             QuickTimeFileReader::DecodeThreaded : 
+             QuickTimeFileReader::DecodeAtOnce,
+             QuickTimeFileReader::CacheInTemporaryFile,
+             targetRate,
+             reporter);
+
+        if (!reader->isOK()) {
+            delete reader;
+            reader = 0;
         }
     }
 #endif
--- a/data/fileio/PlaylistFileReader.cpp	Thu Sep 18 12:09:32 2008 +0000
+++ b/data/fileio/PlaylistFileReader.cpp	Thu Sep 18 12:33:30 2008 +0000
@@ -18,6 +18,10 @@
 #include <QFile>
 #include <QTextStream>
 #include <QStringList>
+#include <QFileInfo>
+#include <QDir>
+
+#include <iostream>
 
 PlaylistFileReader::PlaylistFileReader(QString path) :
     m_source(path),
@@ -56,7 +60,9 @@
 
     m_source.waitForData();
 
-    m_file = new QFile(m_source.getLocalFilename());
+    QString filename = m_source.getLocalFilename();
+
+    m_file = new QFile(filename);
     bool good = false;
 
     if (!m_file->exists()) {
@@ -69,6 +75,12 @@
 	good = true;
     }
 
+    if (good) {
+        if (!m_source.isRemote()) {
+            m_basedir = QFileInfo(filename).dir().canonicalPath();
+        }
+    }
+
     if (!good) {
 	delete m_file;
 	m_file = 0;
@@ -105,12 +117,31 @@
         QString chunk = in.readLine();
         QStringList lines = chunk.split('\r', QString::SkipEmptyParts);
         
-        for (size_t li = 0; li < lines.size(); ++li) {
+        for (int li = 0; li < lines.size(); ++li) {
 
             QString line = lines[li];
 
             if (line.startsWith("#")) continue;
 
+            // line is expected to be a URL or a file path.  If it
+            // appears to be a local relative file path, then we
+            // should check whether it can be resolved relative to the
+            // location of the playlist file and, if so, do so.
+
+            if (!FileSource::isRemote(line)) {
+                if (QFileInfo(line).isRelative() && m_basedir != "") {
+                    QString testpath = QDir(m_basedir).filePath(line);
+                    if (QFileInfo(testpath).exists() &&
+                        QFileInfo(testpath).isFile()) {
+                        std::cerr << "Path \"" << line.toStdString()
+                                  << "\" is relative, resolving to \""
+                                  << testpath.toStdString() << "\""
+                                  << std::endl;
+                        line = testpath;
+                    }
+                }
+            }
+
             playlist.push_back(line);
         }
     }
--- a/data/fileio/PlaylistFileReader.h	Thu Sep 18 12:09:32 2008 +0000
+++ b/data/fileio/PlaylistFileReader.h	Thu Sep 18 12:33:30 2008 +0000
@@ -45,6 +45,7 @@
 
     FileSource m_source;
     QFile *m_file;
+    QString m_basedir;
     QString m_error;
 };
 
--- a/rdf/PluginRDFDescription.cpp	Thu Sep 18 12:09:32 2008 +0000
+++ b/rdf/PluginRDFDescription.cpp	Thu Sep 18 12:33:30 2008 +0000
@@ -53,15 +53,6 @@
     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
 {
@@ -72,16 +63,6 @@
 }
 
 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) ==
@@ -92,6 +73,26 @@
 }
 
 QString
+PluginRDFDescription::getOutputFeatureAttributeURI(QString outputId) const
+{
+    if (m_outputFeatureAttributeURIMap.find(outputId) ==
+        m_outputFeatureAttributeURIMap.end()) {
+        return "";
+    }
+    return m_outputFeatureAttributeURIMap.find(outputId)->second;
+}
+
+QString
+PluginRDFDescription::getOutputSignalTypeURI(QString outputId) const
+{
+    if (m_outputSignalTypeURIMap.find(outputId) ==
+        m_outputSignalTypeURIMap.end()) {
+        return "";
+    }
+    return m_outputSignalTypeURIMap.find(outputId)->second;
+}
+
+QString
 PluginRDFDescription::getOutputUnit(QString outputId) const
 {
     if (m_outputUnitMap.find(outputId) == m_outputUnitMap.end()) {
@@ -111,27 +112,19 @@
          (
              " PREFIX vamp: <http://purl.org/ontology/vamp/> "
 
-             " SELECT ?output_id ?output_type ?feature_type ?event_type ?unit "
+             " SELECT ?output ?output_id ?output_type ?unit "
              " FROM <%1> "
 
              " WHERE { "
 
              "   ?plugin a vamp:Plugin ; "
              "           vamp:identifier \"%2\" ; "
-             "           vamp:output_descriptor ?output . "
+             "           vamp:output ?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 "
              "   } . "
 
@@ -152,7 +145,8 @@
     if (results.empty()) {
         cerr << "ERROR: PluginRDFDescription::indexURL: NOTE: Document at <"
              << url.toStdString()
-             << "> does not appear to describe any plugin outputs" << endl;
+             << "> does not appear to describe any outputs for plugin with id \""
+             << label.toStdString() << "\"" << endl;
         return false;
     }
 
@@ -162,12 +156,8 @@
 
     for (int i = 0; i < results.size(); ++i) {
 
+        QString outputUri = results[i]["output"].value;
         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")) {
@@ -177,34 +167,6 @@
         } 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) {
 
@@ -214,6 +176,35 @@
                 m_outputUnitMap[outputId] = unit;
             }
         }
+
+        QString queryTemplate = 
+            QString(" PREFIX vamp: <http://purl.org/ontology/vamp/> "
+                    " SELECT ?%3 FROM <%1> "
+                    " WHERE { <%2> vamp:computes_%3 ?%3 } ")
+            .arg(url).arg(outputUri);
+
+        SimpleSPARQLQuery::Value v;
+
+        v = SimpleSPARQLQuery::singleResultQuery
+            (queryTemplate.arg("event_type"), "event_type");
+
+        if (v.type == SimpleSPARQLQuery::URIValue && v.value != "") {
+            m_outputEventTypeURIMap[outputId] = v.value;
+        }
+
+        v = SimpleSPARQLQuery::singleResultQuery
+            (queryTemplate.arg("feature_attribute"), "feature_attribute");
+
+        if (v.type == SimpleSPARQLQuery::URIValue && v.value != "") {
+            m_outputFeatureAttributeURIMap[outputId] = v.value;
+        }
+
+        v = SimpleSPARQLQuery::singleResultQuery
+            (queryTemplate.arg("signal_type"), "signal_type");
+
+        if (v.type == SimpleSPARQLQuery::URIValue && v.value != "") {
+            m_outputSignalTypeURIMap[outputId] = v.value;
+        }
     }
 
     return true;
--- a/rdf/PluginRDFDescription.h	Thu Sep 18 12:09:32 2008 +0000
+++ b/rdf/PluginRDFDescription.h	Thu Sep 18 12:33:30 2008 +0000
@@ -28,14 +28,6 @@
     PluginRDFDescription(QString pluginId);
     ~PluginRDFDescription();
 
-    enum OutputType
-    {
-        OutputTypeUnknown,
-        OutputFeatures,
-        OutputEvents,
-        OutputFeaturesAndEvents
-    };
-
     enum OutputDisposition
     {
         OutputDispositionUnknown,
@@ -45,23 +37,22 @@
     };
 
     bool haveDescription() const;
-    OutputType getOutputType(QString outputId) const;
     OutputDisposition getOutputDisposition(QString outputId) const;
-    QString getOutputFeatureTypeURI(QString outputId) const;
     QString getOutputEventTypeURI(QString outputId) const;
+    QString getOutputFeatureAttributeURI(QString outputId) const;
+    QString getOutputSignalTypeURI(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_outputFeatureAttributeURIMap;
+    OutputStringMap m_outputSignalTypeURIMap;
     OutputStringMap m_outputUnitMap;
     bool indexURL(QString url);
 };
--- a/rdf/PluginRDFIndexer.cpp	Thu Sep 18 12:09:32 2008 +0000
+++ b/rdf/PluginRDFIndexer.cpp	Thu Sep 18 12:33:30 2008 +0000
@@ -181,7 +181,12 @@
              // 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 } . "
+             //!!! No -- because of rasqal's inability to correctly
+             // handle more than one OPTIONAL graph in a query, let's
+             // make identifier compulsory after all
+             //"   OPTIONAL { ?plugin vamp:identifier ?plugin_id } . "
+
+             "   ?plugin vamp:identifier ?plugin_id . "
 
              "   OPTIONAL { "
              "     ?library a vamp:PluginLibrary ; "
--- a/rdf/RDFImporter.cpp	Thu Sep 18 12:09:32 2008 +0000
+++ b/rdf/RDFImporter.cpp	Thu Sep 18 12:33:30 2008 +0000
@@ -54,6 +54,13 @@
     typedef std::map<QString, TimeValueMap> TypeTimeValueMap;
     typedef std::map<QString, TypeTimeValueMap> SourceTypeTimeValueMap;
 
+    void getDataModelsSparse(std::vector<Model *> &, ProgressReporter *);
+    void getDataModelsDense(std::vector<Model *> &, ProgressReporter *);
+
+    void getDenseFeatureProperties(QString featureUri,
+                                   int &sampleRate, int &windowLength,
+                                   int &hopSize, int &width, int &height);
+
     void extractStructure(const TimeValueMap &map, bool &sparse,
                           int &minValueCount, int &maxValueCount);
 
@@ -124,6 +131,229 @@
 {
     std::vector<Model *> models;
 
+    getDataModelsDense(models, reporter);
+
+    QString error;
+    if (!isOK()) error = m_errorString;
+    m_errorString = "";
+
+    getDataModelsSparse(models, reporter);
+
+    if (isOK()) m_errorString = error;
+
+    return models;
+}
+
+void
+RDFImporterImpl::getDataModelsDense(std::vector<Model *> &models,
+                                    ProgressReporter *reporter)
+{
+    SimpleSPARQLQuery query = SimpleSPARQLQuery
+        (QString
+         (
+             " PREFIX mo: <http://purl.org/ontology/mo/>"
+             " PREFIX af: <http://purl.org/ontology/af/>"
+             
+             " SELECT ?feature ?signal_source ?feature_signal_type ?value "
+             " FROM <%1> "
+             
+             " WHERE { "
+             
+             "   ?signal a mo:Signal ; "
+             "           mo:available_as ?signal_source ; "
+             "           af:signal_feature ?feature . "
+             
+             "   ?feature a ?feature_signal_type ; "
+             "            af:value ?value . "
+    
+             " } "
+             )
+         .arg(m_uristring));
+
+    SimpleSPARQLQuery::ResultList results = query.execute();
+
+    if (!query.isOK()) {
+        m_errorString = query.getErrorString();
+        return;
+    }
+
+    if (query.wasCancelled()) {
+        m_errorString = "Query cancelled";
+        return;
+    }        
+
+    for (int i = 0; i < results.size(); ++i) {
+
+        QString feature = results[i]["feature"].value;
+        QString source = results[i]["signal_source"].value;
+        QString type = results[i]["feature_signal_type"].value;
+        QString value = results[i]["value"].value;
+
+        int sampleRate = 0;
+        int windowLength = 0;
+        int hopSize = 0;
+        int width = 0;
+        int height = 0;
+        getDenseFeatureProperties
+            (feature, sampleRate, windowLength, hopSize, width, height);
+
+        if (sampleRate != 0 && sampleRate != m_sampleRate) {
+            cerr << "WARNING: Sample rate in dense feature description does not match our underlying rate -- using rate from feature description" << endl;
+        }
+        if (sampleRate == 0) sampleRate = m_sampleRate;
+
+        if (hopSize == 0) {
+            cerr << "WARNING: Dense feature description does not specify a hop size -- assuming 1" << endl;
+            hopSize = 1;
+        }
+
+        if (height == 0) {
+            cerr << "WARNING: Dense feature description does not specify feature signal dimensions -- assuming one-dimensional (height = 1)" << endl;
+            height = 1;
+        }
+
+        QStringList values = value.split(' ', QString::SkipEmptyParts);
+
+        if (values.empty()) {
+            cerr << "WARNING: Dense feature description does not specify any values!" << endl;
+            continue;
+        }
+
+        if (height == 1) {
+
+            SparseTimeValueModel *m = new SparseTimeValueModel
+                (sampleRate, hopSize, false);
+
+            for (int j = 0; j < values.size(); ++j) {
+                float f = values[j].toFloat();
+                SparseTimeValueModel::Point point(j * hopSize, f, "");
+                m->addPoint(point);
+            }
+        
+            models.push_back(m);
+
+        } else {
+
+            EditableDenseThreeDimensionalModel *m =
+                new EditableDenseThreeDimensionalModel(sampleRate, hopSize,
+                                                       height, false);
+            
+            EditableDenseThreeDimensionalModel::Column column;
+
+            int x = 0;
+
+            for (int j = 0; j < values.size(); ++j) {
+                if (j % height == 0 && !column.empty()) {
+                    m->setColumn(x++, column);
+                    column.clear();
+                }
+                column.push_back(values[j].toFloat());
+            }
+
+            if (!column.empty()) {
+                m->setColumn(x++, column);
+            }
+
+            models.push_back(m);
+        }
+    }
+}
+
+void
+RDFImporterImpl::getDenseFeatureProperties(QString featureUri,
+                                           int &sampleRate, int &windowLength,
+                                           int &hopSize, int &width, int &height)
+{
+    QString dimensionsQuery 
+        (
+            " PREFIX mo: <http://purl.org/ontology/mo/>"
+            " PREFIX af: <http://purl.org/ontology/af/>"
+            
+            " SELECT ?dimensions "
+            " FROM <%1> "
+
+            " WHERE { "
+
+            "   <%2> af:dimensions ?dimensions . "
+            
+            " } "
+            );
+
+    SimpleSPARQLQuery::Value dimensionsValue =
+        SimpleSPARQLQuery::singleResultQuery(dimensionsQuery
+                                             .arg(m_uristring).arg(featureUri),
+                                             "dimensions");
+
+    cerr << "Dimensions = \"" << dimensionsValue.value.toStdString() << "\""
+         << endl;
+
+    if (dimensionsValue.value != "") {
+        QStringList dl = dimensionsValue.value.split(" ");
+        if (dl.empty()) dl.push_back(dimensionsValue.value);
+        if (dl.size() > 0) height = dl[0].toInt();
+        if (dl.size() > 1) width = dl[1].toInt();
+    }
+
+    QString queryTemplate
+        (
+            " PREFIX mo: <http://purl.org/ontology/mo/>"
+            " PREFIX af: <http://purl.org/ontology/af/>"
+            " PREFIX tl: <http://purl.org/NET/c4dm/timeline.owl#>"
+
+            " SELECT ?%3 "
+            " FROM <%1> "
+            
+            " WHERE { "
+            
+            "   <%2> mo:time ?time . "
+            
+            "   ?time a tl:Interval ; "
+            "         tl:onTimeLine ?timeline . "
+
+            "   ?map tl:rangeTimeLine ?timeline . "
+
+            "   ?map tl:%3 ?%3 . "
+            
+            " } "
+            );
+
+    // Another laborious workaround for rasqal's failure to handle
+    // multiple optionals properly
+
+    SimpleSPARQLQuery::Value srValue = 
+        SimpleSPARQLQuery::singleResultQuery(queryTemplate
+                                             .arg(m_uristring).arg(featureUri)
+                                             .arg("sampleRate"),
+                                             "sampleRate");
+    if (srValue.value != "") {
+        sampleRate = srValue.value.toInt();
+    }
+
+    SimpleSPARQLQuery::Value hopValue = 
+        SimpleSPARQLQuery::singleResultQuery(queryTemplate
+                                             .arg(m_uristring).arg(featureUri)
+                                             .arg("hopSize"),
+                                             "hopSize");
+    if (srValue.value != "") {
+        hopSize = hopValue.value.toInt();
+    }
+
+    SimpleSPARQLQuery::Value winValue = 
+        SimpleSPARQLQuery::singleResultQuery(queryTemplate
+                                             .arg(m_uristring).arg(featureUri)
+                                             .arg("windowLength"),
+                                             "windowLength");
+    if (winValue.value != "") {
+        windowLength = winValue.value.toInt();
+    }
+
+    cerr << "sr = " << sampleRate << ", hop = " << hopSize << ", win = " << windowLength << endl;
+}
+
+void
+RDFImporterImpl::getDataModelsSparse(std::vector<Model *> &models,
+                                     ProgressReporter *reporter)
+{
     // 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.
@@ -163,20 +393,23 @@
         " PREFIX mo: <http://purl.org/ontology/mo/>"
         " PREFIX af: <http://purl.org/ontology/af/>"
 
-        " SELECT ?signalSource ?time ?eventType ?value"
+        " SELECT ?signal_source ?time ?event_type ?value"
         " FROM <%1>"
 
         " WHERE {"
-        "   ?signal mo:available_as ?signalSource ."
+
+        "   ?signal mo:available_as ?signal_source ."
+        "   ?signal a mo:Signal ."
+
         "   ?signal mo:time ?interval ."
         "   ?interval time:onTimeLine ?tl ."
         "   ?t time:onTimeLine ?tl ."
         "   ?t time:at ?time ."
-        "   ?timedThing event:time ?t ."
-        "   ?timedThing a ?eventType ."
+        "   ?timed_thing event:time ?t ."
+        "   ?timed_thing a ?event_type ."
+
         "   OPTIONAL {"
-        "     ?timedThing af:hasFeature ?feature ."
-        "     ?feature af:value ?value"
+        "     ?timed_thing af:feature ?value"
         "   }"
         " }"
 
@@ -191,17 +424,17 @@
 
     if (!query.isOK()) {
         m_errorString = query.getErrorString();
-        return models;
+        return;
     }
 
     if (query.wasCancelled()) {
         m_errorString = "Query cancelled";
-        return models;
+        return;
     }        
 
     for (int i = 0; i < results.size(); ++i) {
 
-        QString source = results[i]["signalSource"].value;
+        QString source = results[i]["signal_source"].value;
 
         QString timestring = results[i]["time"].value;
         RealTime time;
@@ -209,12 +442,13 @@
         cerr << "time = " << time.toString() << " (from xsd:duration \""
              << timestring.toStdString() << "\")" << endl;
 
-        QString type = results[i]["eventType"].value;
+        QString type = results[i]["event_type"].value;
 
         QString valuestring = results[i]["value"].value;
         float value = 0.f;
         bool haveValue = false;
         if (valuestring != "") {
+            //!!! no -- runner actually writes a "CSV literal"
             value = valuestring.toFloat(&haveValue);
             cerr << "value = " << value << endl;
         }
@@ -322,9 +556,6 @@
             }
         }
     }
-
-
-    return models;
 }
 
 void
--- a/rdf/RDFTransformFactory.cpp	Thu Sep 18 12:09:32 2008 +0000
+++ b/rdf/RDFTransformFactory.cpp	Thu Sep 18 12:33:30 2008 +0000
@@ -18,9 +18,6 @@
 #include <map>
 #include <vector>
 
-#include <redland.h>
-#include <rasqal.h>
-
 #include <iostream>
 #include <cmath>
 
@@ -50,6 +47,8 @@
 protected:
     QString m_urlString;
     QString m_errorString;
+    bool setOutput(Transform &, QString, QString);
+    bool setParameters(Transform &, QString, QString);
 };
 
 
@@ -113,140 +112,156 @@
 {
     std::vector<Transform> transforms;
 
-    SimpleSPARQLQuery query
-        (QString
-         (
-             " PREFIX vamp: <http://purl.org/ontology/vamp/> "
+    // We have to do this a very long way round, to work around
+    // rasqal's current inability to handle correctly more than one
+    // OPTIONAL graph in a query
 
-             " SELECT ?transform ?plugin ?output ?program "
-             "        ?step_size ?block_size ?window_type "
-             "        ?sample_rate ?start ?duration "
+    const char *optionals[] = {
+        "output",
+        "program",
+        "step_size",
+        "block_size",
+        "window_type",
+        "sample_rate",
+        "start", 
+        "duration"
+    };
 
-             " FROM <%1> "
+    std::map<QString, Transform> uriTransformMap;
 
-             " 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));
+    QString queryTemplate = 
+        " PREFIX vamp: <http://purl.org/ontology/vamp/> "
 
-    SimpleSPARQLQuery::ResultList results = query.execute();
+        " SELECT ?transform ?plugin %1 "
+        
+        " FROM <%2> "
 
-    if (!query.isOK()) {
-        m_errorString = query.getErrorString();
+        " WHERE { "
+        "   ?transform a vamp:Transform ; "
+        "              vamp:plugin ?plugin . "
+        "   %3 "
+        " } ";
+
+    SimpleSPARQLQuery transformsQuery
+        (queryTemplate.arg("").arg(m_urlString).arg(""));
+
+    SimpleSPARQLQuery::ResultList transformResults = transformsQuery.execute();
+
+    if (!transformsQuery.isOK()) {
+        m_errorString = transformsQuery.getErrorString();
         return transforms;
     }
 
-    if (query.wasCancelled()) {
-        m_errorString = "Query cancelled";
+    if (transformResults.empty()) {
+        cerr << "RDFTransformFactory: NOTE: No RDF/TTL transform descriptions found in document at <" << m_urlString.toStdString() << ">" << endl;
         return transforms;
     }
 
     PluginRDFIndexer *indexer = PluginRDFIndexer::getInstance();
 
-    for (int i = 0; i < results.size(); ++i) {
+    for (int i = 0; i < transformResults.size(); ++i) {
 
-        SimpleSPARQLQuery::KeyValueMap &result = results[i];
+        SimpleSPARQLQuery::KeyValueMap &result = transformResults[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() << ">, skipping this transform"
+                 << endl;
+            continue;
+        }
+
+        QString pluginDescriptionURL =
+            indexer->getDescriptionURLForPluginId(pluginId);
+        if (pluginDescriptionURL == "") {
+            cerr << "RDFTransformFactory: WARNING: No RDF description available for plugin <"
+                 << pluginUri.toStdString() << ">, skipping 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();
+        if (!setOutput(transform, transformUri, pluginDescriptionURL)) {
             return transforms;
         }
 
-        if (paramQuery.wasCancelled()) {
-            m_errorString = "Query cancelled";
+        if (!setParameters(transform, transformUri, pluginDescriptionURL)) {
             return transforms;
         }
 
-        for (int j = 0; j < paramResults.size(); ++j) {
+        uriTransformMap[transformUri] = transform;
+    }
 
-            QString paramId = paramResults[j]["param_id"].value;
-            QString paramValue = paramResults[j]["param_value"].value;
+    for (int i = 0; i < sizeof(optionals)/sizeof(optionals[0]); ++i) {
 
-            if (paramId == "" || paramValue == "") continue;
+        QString optional = optionals[i];
 
-            transform.setParameter(paramId, paramValue.toFloat());
+        SimpleSPARQLQuery query
+            (queryTemplate
+             .arg(QString("?%1").arg(optional))
+             .arg(m_urlString)
+             .arg(QString("?transform vamp:%1 ?%2")
+                  .arg(optionals[i]).arg(optional)));
+        
+        SimpleSPARQLQuery::ResultList results = query.execute();
+
+        if (!query.isOK()) {
+            m_errorString = query.getErrorString();
+            return transforms;
         }
 
+        if (results.empty()) continue;
+
+        for (int j = 0; j < results.size(); ++j) {
+
+            QString transformUri = results[j]["transform"].value;
+            
+            if (uriTransformMap.find(transformUri) == uriTransformMap.end()) {
+                cerr << "RDFTransformFactory: ERROR: Transform URI <"
+                     << transformUri.toStdString() << "> not found in internal map!" << endl;
+                continue;
+            }
+
+            Transform &transform = uriTransformMap[transformUri];
+            const SimpleSPARQLQuery::Value &v = results[j][optional];
+
+            if (v.type == SimpleSPARQLQuery::LiteralValue) {
+                
+                if (optional == "program") {
+                    transform.setProgram(v.value);
+                } else if (optional == "step_size") {
+                    transform.setStepSize(v.value.toUInt());
+                } else if (optional == "block_size") {
+                    transform.setBlockSize(v.value.toUInt());
+                } else if (optional == "window_type") {
+                    cerr << "NOTE: can't handle window type yet (value is \""
+                         << v.value.toStdString() << "\")" << endl;
+                } else if (optional == "sample_rate") {
+                    transform.setSampleRate(v.value.toFloat());
+                } else if (optional == "start") {
+                    transform.setStartTime
+                        (RealTime::fromXsdDuration(v.value.toStdString()));
+                } else if (optional == "duration") {
+                    transform.setDuration
+                        (RealTime::fromXsdDuration(v.value.toStdString()));
+                } else {
+                    cerr << "RDFTransformFactory: ERROR: Inconsistent optionals lists (unexpected optional \"" << optional.toStdString() << "\"" << endl;
+                }
+            }
+        }
+    }
+
+    for (std::map<QString, Transform>::iterator i = uriTransformMap.begin();
+         i != uriTransformMap.end(); ++i) {
+
+        Transform &transform = i->second;
+
         cerr << "RDFTransformFactory: NOTE: Transform is: " << endl;
         cerr << transform.toXmlString().toStdString() << endl;
 
@@ -256,3 +271,99 @@
     return transforms;
 }
 
+bool
+RDFTransformFactoryImpl::setOutput(Transform &transform,
+                                   QString transformUri,
+                                   QString pluginDescriptionURL)
+{
+    SimpleSPARQLQuery outputQuery
+        (QString
+         (
+             " PREFIX vamp: <http://purl.org/ontology/vamp/> "
+             
+             " SELECT ?output_id "
+             
+             " FROM <%1> "
+             " FROM <%2> "
+             
+             " WHERE { "
+             "   <%3> vamp:output ?output . "
+             "   ?output vamp:identifier ?output_id "
+             " } "
+             )
+         .arg(m_urlString)
+         .arg(pluginDescriptionURL)
+         .arg(transformUri));
+    
+    SimpleSPARQLQuery::ResultList outputResults = outputQuery.execute();
+    
+    if (!outputQuery.isOK()) {
+        m_errorString = outputQuery.getErrorString();
+        return false;
+    }
+    
+    if (outputQuery.wasCancelled()) {
+        m_errorString = "Query cancelled";
+        return false;
+    }
+    
+    for (int j = 0; j < outputResults.size(); ++j) {
+        QString outputId = outputResults[j]["output_id"].value;
+        transform.setOutput(outputId);
+    }
+
+    return true;
+}
+        
+
+bool
+RDFTransformFactoryImpl::setParameters(Transform &transform,
+                                       QString transformUri,
+                                       QString pluginDescriptionURL)
+{
+    SimpleSPARQLQuery paramQuery
+        (QString
+         (
+             " PREFIX vamp: <http://purl.org/ontology/vamp/> "
+             
+             " SELECT ?param_id ?param_value "
+             
+             " FROM <%1> "
+             " FROM <%2> "
+             
+             " WHERE { "
+             "   <%3> vamp:parameter_binding ?binding . "
+             "   ?binding vamp:parameter ?param ; "
+             "            vamp:value ?param_value . "
+             "   ?param vamp:identifier ?param_id "
+             " } "
+             )
+         .arg(m_urlString)
+         .arg(pluginDescriptionURL)
+         .arg(transformUri));
+    
+    SimpleSPARQLQuery::ResultList paramResults = paramQuery.execute();
+    
+    if (!paramQuery.isOK()) {
+        m_errorString = paramQuery.getErrorString();
+        return false;
+    }
+    
+    if (paramQuery.wasCancelled()) {
+        m_errorString = "Query cancelled";
+        return false;
+    }
+    
+    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());
+    }
+
+    return true;
+}
+
--- a/rdf/SimpleSPARQLQuery.cpp	Thu Sep 18 12:09:32 2008 +0000
+++ b/rdf/SimpleSPARQLQuery.cpp	Thu Sep 18 12:33:30 2008 +0000
@@ -16,13 +16,31 @@
 #include "SimpleSPARQLQuery.h"
 #include "base/ProgressReporter.h"
 
+#ifdef USE_NEW_RASQAL_API
+#include <rasqal/rasqal.h>
+#else
 #include <rasqal.h>
+#endif
 
 #include <iostream>
 
 using std::cerr;
 using std::endl;
 
+#ifdef USE_NEW_RASQAL_API
+class WrasqalWorldWrapper // wrong but wromantic, etc
+{
+public:
+    WrasqalWorldWrapper() : m_world(rasqal_new_world()) { }
+    ~WrasqalWorldWrapper() { rasqal_free_world(m_world); }
+
+    rasqal_world *getWorld() const { return m_world; }
+
+private:
+    rasqal_world *m_world;
+};
+#endif
+
 class SimpleSPARQLQuery::Impl
 {
 public:
@@ -40,7 +58,11 @@
 protected:
     static void errorHandler(void *, raptor_locator *, const char *);
 
+#ifdef USE_NEW_RASQAL_API
+    static WrasqalWorldWrapper m_www;
+#else
     static bool m_initialised;
+#endif
     
     QString m_query;
     QString m_errorString;
@@ -48,6 +70,14 @@
     bool m_cancelled;
 };
 
+#ifdef USE_NEW_RASQAL_API
+WrasqalWorldWrapper
+SimpleSPARQLQuery::Impl::m_www;
+#else
+bool
+SimpleSPARQLQuery::Impl::m_initialised = false;
+#endif
+
 SimpleSPARQLQuery::SimpleSPARQLQuery(QString query) :
     m_impl(new Impl(query)) { }
 
@@ -86,23 +116,15 @@
     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
@@ -138,7 +160,15 @@
 {
     ResultList list;
 
+#ifdef USE_NEW_RASQAL_API
+    rasqal_query *query = rasqal_new_query(m_www.getWorld(), "sparql", NULL);
+#else
+    if (!m_initialised) {
+        m_initialised = true;
+        rasqal_init();
+    }
     rasqal_query *query = rasqal_new_query("sparql", NULL);
+#endif
     if (!query) {
         m_errorString = "Failed to construct query";
         cerr << "SimpleSPARQLQuery: ERROR: " << m_errorString.toStdString() << endl;
@@ -232,4 +262,28 @@
 
     return list;
 }
-    
+
+SimpleSPARQLQuery::Value
+SimpleSPARQLQuery::singleResultQuery(QString query, QString binding)
+{
+    SimpleSPARQLQuery q(query);
+    ResultList results = q.execute();
+    if (!q.isOK()) {
+        cerr << "SimpleSPARQLQuery::singleResultQuery: ERROR: "
+             << q.getErrorString().toStdString() << endl;
+        return Value();
+    }
+    if (results.empty()) {
+        return Value();
+    }
+    for (int i = 0; i < results.size(); ++i) {
+        if (results[i].find(binding) != results[i].end() &&
+            results[i][binding].type != NoValue) {
+            return results[i][binding];
+        }
+    }
+    return Value();
+}
+
+
+
--- a/rdf/SimpleSPARQLQuery.h	Thu Sep 18 12:09:32 2008 +0000
+++ b/rdf/SimpleSPARQLQuery.h	Thu Sep 18 12:33:30 2008 +0000
@@ -48,6 +48,10 @@
     bool isOK() const;
     QString getErrorString() const;
 
+    // Do a query and return the value for the given binding, from the
+    // first result that has a value for it
+    static Value singleResultQuery(QString query, QString binding);
+
 protected:
     class Impl;
     Impl *m_impl;
--- a/rdf/rdf.pro	Thu Sep 18 12:09:32 2008 +0000
+++ b/rdf/rdf.pro	Thu Sep 18 12:33:30 2008 +0000
@@ -1,6 +1,6 @@
 TEMPLATE = lib
 
-SV_UNIT_PACKAGES = redland
+SV_UNIT_PACKAGES = rasqal raptor
 load(../sv.prf)
 
 CONFIG += sv staticlib qt thread warn_on stl rtti exceptions