changeset 160:2a399bed0184

Merge from branch "labfile"
author Chris Cannam
date Wed, 15 Oct 2014 11:23:05 +0100
parents 04945e74d314 (current diff) 237ccacbb85e (diff)
children 4b19d824a213 f4f770b4356b
files
diffstat 27 files changed, 672 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsubstate	Tue Oct 14 11:13:31 2014 +0100
+++ b/.hgsubstate	Wed Oct 15 11:23:05 2014 +0100
@@ -1,3 +1,3 @@
 d16f0fd6db6104d87882bc43788a3bb1b0f8c528 dataquay
 879bdc878826bebec67130326f99397c430419b1 sv-dependency-builds
-694301cc71ccb10c8cc5a729c9ffb861891589c2 svcore
+c2316a3bbb814eb70f817bd0ed30543f00a68352 svcore
--- a/runner.pro	Tue Oct 14 11:13:31 2014 +0100
+++ b/runner.pro	Wed Oct 15 11:23:05 2014 +0100
@@ -84,6 +84,7 @@
         runner/FeatureWriterFactory.h  \
         runner/DefaultFeatureWriter.h \
         runner/FeatureExtractionManager.h \
+        runner/LabFeatureWriter.h \
         runner/MIDIFeatureWriter.h \
         runner/MultiplexedReader.h
 
@@ -93,6 +94,7 @@
 	runner/FeatureExtractionManager.cpp \
         runner/AudioDBFeatureWriter.cpp \
         runner/FeatureWriterFactory.cpp \
+        runner/LabFeatureWriter.cpp \
         runner/MIDIFeatureWriter.cpp \
         runner/MultiplexedReader.cpp
 
--- a/runner/FeatureWriterFactory.cpp	Tue Oct 14 11:13:31 2014 +0100
+++ b/runner/FeatureWriterFactory.cpp	Wed Oct 15 11:23:05 2014 +0100
@@ -17,10 +17,13 @@
 #include "FeatureWriterFactory.h"
 
 #include "DefaultFeatureWriter.h"
+
 #include "rdf/RDFFeatureWriter.h"
+#include "transform/CSVFeatureWriter.h"
+
 #include "AudioDBFeatureWriter.h"
 #include "MIDIFeatureWriter.h"
-#include "transform/CSVFeatureWriter.h"
+#include "LabFeatureWriter.h"
 
 set<string>
 FeatureWriterFactory::getWriterTags()
@@ -30,6 +33,7 @@
     tags.insert("rdf");
     tags.insert("audiodb");
     tags.insert("csv");
+    tags.insert("lab");
     tags.insert("midi");
     return tags;
 }
@@ -45,6 +49,8 @@
         return new AudioDBFeatureWriter();
     } else if (tag == "csv") {
         return new CSVFeatureWriter();
+    } else if (tag == "lab") {
+        return new LabFeatureWriter();
     } else if (tag == "midi") {
         return new MIDIFeatureWriter();
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/LabFeatureWriter.cpp	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,178 @@
+/* -*- 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.
+
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "LabFeatureWriter.h"
+
+#include <iostream>
+
+#include <QRegExp>
+#include <QTextStream>
+
+using namespace std;
+using namespace Vamp;
+
+LabFeatureWriter::LabFeatureWriter() :
+    FileFeatureWriter(SupportOneFilePerTrackTransform |
+                      SupportStdOut,
+                      "lab"),
+    m_forceEnd(false)
+{
+}
+
+LabFeatureWriter::~LabFeatureWriter()
+{
+}
+
+string
+LabFeatureWriter::getDescription() const
+{
+    return "Write features in .lab, a tab-separated columnar format. The first column is always the feature start time in seconds. If the features have duration, the second column will be the feature end time in seconds. Remaining columns are the feature values (if any) and finally the feature label (if any). There is no identification of the audio file or the transform, so confusion will result if features from different audio or transforms are mixed. (For more control over the output, consider using the more general CSV writer.)";
+}
+
+LabFeatureWriter::ParameterList
+LabFeatureWriter::getSupportedParameters() const
+{
+    ParameterList pl = FileFeatureWriter::getSupportedParameters();
+
+    Parameter p;
+
+    p.name = "fill-ends";
+    p.description = "Include end times even for features without duration, by using the gap to the next feature instead.";
+    p.hasArg = false;
+    pl.push_back(p);
+
+    return pl;
+}
+
+void
+LabFeatureWriter::setParameters(map<string, string> &params)
+{
+    FileFeatureWriter::setParameters(params);
+
+    for (map<string, string>::iterator i = params.begin();
+         i != params.end(); ++i) {
+        if (i->first == "fill-ends") {
+            m_forceEnd = true;
+        }
+    }
+}
+
+void
+LabFeatureWriter::write(QString trackId,
+                        const Transform &transform,
+                        const Plugin::OutputDescriptor& ,
+                        const Plugin::FeatureList& features,
+                        std::string)
+{
+    // Select appropriate output file for our track/transform
+    // combination
+
+    TransformId transformId = transform.getIdentifier();
+
+    QTextStream *sptr = getOutputStream(trackId, transformId);
+    if (!sptr) {
+        throw FailedToOpenOutputStream(trackId, transformId);
+    }
+
+    QTextStream &stream = *sptr;
+
+    int n = features.size();
+
+    if (n == 0) return;
+
+    TrackTransformPair tt(trackId, transformId);
+
+    if (m_pending.find(tt) != m_pending.end()) {
+        writeFeature(stream, m_pending[tt], &features[0]);
+        m_pending.erase(tt);
+    }
+
+    if (m_forceEnd) {
+        // can't write final feature until we know its end time
+        --n;
+        m_pending[tt] = features[n];
+    }
+
+    for (int i = 0; i < n; ++i) {
+        writeFeature(stream, features[i], m_forceEnd ? &features[i+1] : 0);
+    }
+}
+
+void
+LabFeatureWriter::finish()
+{
+    for (PendingFeatures::const_iterator i = m_pending.begin();
+         i != m_pending.end(); ++i) {
+        TrackTransformPair tt = i->first;
+        Plugin::Feature f = i->second;
+        QTextStream *sptr = getOutputStream(tt.first, tt.second);
+        if (!sptr) {
+            throw FailedToOpenOutputStream(tt.first, tt.second);
+        }
+        QTextStream &stream = *sptr;
+        // final feature has its own time as end time (we can't
+        // reliably determine the end of audio file, and because of
+        // the nature of block processing, the feature could even
+        // start beyond that anyway)
+        writeFeature(stream, f, &f);
+    }
+
+    m_pending.clear();
+}
+
+void
+LabFeatureWriter::writeFeature(QTextStream &stream,
+                               const Plugin::Feature &f,
+                               const Plugin::Feature *optionalNextFeature)
+{
+    QString sep = "\t";
+
+    QString timestamp = f.timestamp.toString().c_str();
+    timestamp.replace(QRegExp("^ +"), "");
+    stream << timestamp;
+
+    Vamp::RealTime endTime;
+    bool haveEndTime = true;
+
+    if (f.hasDuration) {
+        endTime = f.timestamp + f.duration;
+    } else if (optionalNextFeature) {
+        endTime = optionalNextFeature->timestamp;
+    } else {
+        haveEndTime = false;
+    }
+
+    if (haveEndTime) {
+        QString e = endTime.toString().c_str();
+        e.replace(QRegExp("^ +"), "");
+        stream << sep << e;
+    }
+    
+    for (unsigned int j = 0; j < f.values.size(); ++j) {
+        stream << sep << f.values[j];
+    }
+    
+    if (f.label != "") {
+        stream << sep << "\"" << f.label.c_str() << "\"";
+    }
+    
+    stream << "\n";
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/LabFeatureWriter.h	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,69 @@
+/* -*- 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.
+
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _LAB_FEATURE_WRITER_H_
+#define _LAB_FEATURE_WRITER_H_
+
+#include <string>
+#include <map>
+#include <set>
+
+#include <QString>
+
+#include "transform/FileFeatureWriter.h"
+
+using std::string;
+using std::map;
+
+class QTextStream;
+class QFile;
+
+class LabFeatureWriter : public FileFeatureWriter
+{
+public:
+    LabFeatureWriter();
+    virtual ~LabFeatureWriter();
+
+    virtual string getDescription() const;
+
+    virtual ParameterList getSupportedParameters() const;
+    virtual void setParameters(map<string, string> &params);
+
+    virtual void write(QString trackid,
+                       const Transform &transform,
+                       const Vamp::Plugin::OutputDescriptor &output,
+                       const Vamp::Plugin::FeatureList &features,
+                       std::string summaryType = "");
+
+    virtual void finish();
+
+    virtual QString getWriterTag() const { return "lab"; }
+
+private:
+    bool m_forceEnd;
+    
+    typedef map<TrackTransformPair, Vamp::Plugin::Feature> PendingFeatures;
+    PendingFeatures m_pending;
+
+    void writeFeature(QTextStream &,
+                      const Vamp::Plugin::Feature &f,
+                      const Vamp::Plugin::Feature *optionalNextFeature);
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/curve-vsr-all.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav"@0@33075@0@"1 of 10: 0 at 0"
+@33075@66150@0.1@"2 of 10: 0.1 at 0.75"
+@66150@99225@0.2@"3 of 10: 0.2 at 1.5"
+@99225@132300@0.3@"4 of 10: 0.3 at 2.25"
+@132300@165375@0.4@"5 of 10: 0.4 at 3"
+@165375@198450@0.5@"6 of 10: 0.5 at 3.75"
+@198450@231525@0.6@"7 of 10: 0.6 at 4.5"
+@231525@264600@0.7@"8 of 10: 0.7 at 5.25"
+@264600@297675@0.8@"9 of 10: 0.8 at 6"
+@297675@297675@0.9@"10 of 10: 0.9 at 6.75"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/curve-vsr-end-times.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav",0.000000000,0,"1 of 10: 0 at 0"
+,0.750000000,0.1,"2 of 10: 0.1 at 0.75"
+,1.500000000,0.2,"3 of 10: 0.2 at 1.5"
+,2.250000000,0.3,"4 of 10: 0.3 at 2.25"
+,3.000000000,0.4,"5 of 10: 0.4 at 3"
+,3.750000000,0.5,"6 of 10: 0.5 at 3.75"
+,4.500000000,0.6,"7 of 10: 0.6 at 4.5"
+,5.250000000,0.7,"8 of 10: 0.7 at 5.25"
+,6.000000000,0.8,"9 of 10: 0.8 at 6"
+,6.750000000,0.9,"10 of 10: 0.9 at 6.75"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/curve-vsr-fill-ends.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav",0.000000000,0.750000000,0,"1 of 10: 0 at 0"
+,0.750000000,0.750000000,0.1,"2 of 10: 0.1 at 0.75"
+,1.500000000,0.750000000,0.2,"3 of 10: 0.2 at 1.5"
+,2.250000000,0.750000000,0.3,"4 of 10: 0.3 at 2.25"
+,3.000000000,0.750000000,0.4,"5 of 10: 0.4 at 3"
+,3.750000000,0.750000000,0.5,"6 of 10: 0.5 at 3.75"
+,4.500000000,0.750000000,0.6,"7 of 10: 0.6 at 4.5"
+,5.250000000,0.750000000,0.7,"8 of 10: 0.7 at 5.25"
+,6.000000000,0.750000000,0.8,"9 of 10: 0.8 at 6"
+,6.750000000,0.000000000,0.9,"10 of 10: 0.9 at 6.75"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/curve-vsr-no-flags.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav",0.000000000,0,"1 of 10: 0 at 0"
+,0.750000000,0.1,"2 of 10: 0.1 at 0.75"
+,1.500000000,0.2,"3 of 10: 0.2 at 1.5"
+,2.250000000,0.3,"4 of 10: 0.3 at 2.25"
+,3.000000000,0.4,"5 of 10: 0.4 at 3"
+,3.750000000,0.5,"6 of 10: 0.5 at 3.75"
+,4.500000000,0.6,"7 of 10: 0.6 at 4.5"
+,5.250000000,0.7,"8 of 10: 0.7 at 5.25"
+,6.000000000,0.8,"9 of 10: 0.8 at 6"
+,6.750000000,0.9,"10 of 10: 0.9 at 6.75"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/curve-vsr-sample-timing.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav",0,0,"1 of 10: 0 at 0"
+,33075,0.1,"2 of 10: 0.1 at 0.75"
+,66150,0.2,"3 of 10: 0.2 at 1.5"
+,99225,0.3,"4 of 10: 0.3 at 2.25"
+,132300,0.4,"5 of 10: 0.4 at 3"
+,165375,0.5,"6 of 10: 0.5 at 3.75"
+,198450,0.6,"7 of 10: 0.6 at 4.5"
+,231525,0.7,"8 of 10: 0.7 at 5.25"
+,264600,0.8,"9 of 10: 0.8 at 6"
+,297675,0.9,"10 of 10: 0.9 at 6.75"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/curve-vsr-separator.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav"@0.000000000@0@"1 of 10: 0 at 0"
+@0.750000000@0.1@"2 of 10: 0.1 at 0.75"
+@1.500000000@0.2@"3 of 10: 0.2 at 1.5"
+@2.250000000@0.3@"4 of 10: 0.3 at 2.25"
+@3.000000000@0.4@"5 of 10: 0.4 at 3"
+@3.750000000@0.5@"6 of 10: 0.5 at 3.75"
+@4.500000000@0.6@"7 of 10: 0.6 at 4.5"
+@5.250000000@0.7@"8 of 10: 0.7 at 5.25"
+@6.000000000@0.8@"9 of 10: 0.8 at 6"
+@6.750000000@0.9@"10 of 10: 0.9 at 6.75"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/notes-regions-all.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav"@0@77175@0@"1 of 10: 0 at 0 dur. 1.75"
+@44100@66150@0.1@"2 of 10: 0.1 at 1 dur. 0.5"
+@88200@165375@0.2@"3 of 10: 0.2 at 2 dur. 1.75"
+@132300@154350@0.3@"4 of 10: 0.3 at 3 dur. 0.5"
+@176400@253575@0.4@"5 of 10: 0.4 at 4 dur. 1.75"
+@220500@242550@0.5@"6 of 10: 0.5 at 5 dur. 0.5"
+@264600@341775@0.6@"7 of 10: 0.6 at 6 dur. 1.75"
+@308700@330750@0.7@"8 of 10: 0.7 at 7 dur. 0.5"
+@352800@429975@0.8@"9 of 10: 0.8 at 8 dur. 1.75"
+@396900@418950@0.9@"10 of 10: 0.9 at 9 dur. 0.5"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/notes-regions-end-times.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav",0.000000000,1.750000000,0,"1 of 10: 0 at 0 dur. 1.75"
+,1.000000000,1.500000000,0.1,"2 of 10: 0.1 at 1 dur. 0.5"
+,2.000000000,3.750000000,0.2,"3 of 10: 0.2 at 2 dur. 1.75"
+,3.000000000,3.500000000,0.3,"4 of 10: 0.3 at 3 dur. 0.5"
+,4.000000000,5.750000000,0.4,"5 of 10: 0.4 at 4 dur. 1.75"
+,5.000000000,5.500000000,0.5,"6 of 10: 0.5 at 5 dur. 0.5"
+,6.000000000,7.750000000,0.6,"7 of 10: 0.6 at 6 dur. 1.75"
+,7.000000000,7.500000000,0.7,"8 of 10: 0.7 at 7 dur. 0.5"
+,8.000000000,9.750000000,0.8,"9 of 10: 0.8 at 8 dur. 1.75"
+,9.000000000,9.500000000,0.9,"10 of 10: 0.9 at 9 dur. 0.5"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/notes-regions-fill-ends.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav",0.000000000,1.750000000,0,"1 of 10: 0 at 0 dur. 1.75"
+,1.000000000,0.500000000,0.1,"2 of 10: 0.1 at 1 dur. 0.5"
+,2.000000000,1.750000000,0.2,"3 of 10: 0.2 at 2 dur. 1.75"
+,3.000000000,0.500000000,0.3,"4 of 10: 0.3 at 3 dur. 0.5"
+,4.000000000,1.750000000,0.4,"5 of 10: 0.4 at 4 dur. 1.75"
+,5.000000000,0.500000000,0.5,"6 of 10: 0.5 at 5 dur. 0.5"
+,6.000000000,1.750000000,0.6,"7 of 10: 0.6 at 6 dur. 1.75"
+,7.000000000,0.500000000,0.7,"8 of 10: 0.7 at 7 dur. 0.5"
+,8.000000000,1.750000000,0.8,"9 of 10: 0.8 at 8 dur. 1.75"
+,9.000000000,0.500000000,0.9,"10 of 10: 0.9 at 9 dur. 0.5"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/notes-regions-no-flags.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav",0.000000000,1.750000000,0,"1 of 10: 0 at 0 dur. 1.75"
+,1.000000000,0.500000000,0.1,"2 of 10: 0.1 at 1 dur. 0.5"
+,2.000000000,1.750000000,0.2,"3 of 10: 0.2 at 2 dur. 1.75"
+,3.000000000,0.500000000,0.3,"4 of 10: 0.3 at 3 dur. 0.5"
+,4.000000000,1.750000000,0.4,"5 of 10: 0.4 at 4 dur. 1.75"
+,5.000000000,0.500000000,0.5,"6 of 10: 0.5 at 5 dur. 0.5"
+,6.000000000,1.750000000,0.6,"7 of 10: 0.6 at 6 dur. 1.75"
+,7.000000000,0.500000000,0.7,"8 of 10: 0.7 at 7 dur. 0.5"
+,8.000000000,1.750000000,0.8,"9 of 10: 0.8 at 8 dur. 1.75"
+,9.000000000,0.500000000,0.9,"10 of 10: 0.9 at 9 dur. 0.5"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/notes-regions-sample-timing.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav",0,77175,0,"1 of 10: 0 at 0 dur. 1.75"
+,44100,22050,0.1,"2 of 10: 0.1 at 1 dur. 0.5"
+,88200,77175,0.2,"3 of 10: 0.2 at 2 dur. 1.75"
+,132300,22050,0.3,"4 of 10: 0.3 at 3 dur. 0.5"
+,176400,77175,0.4,"5 of 10: 0.4 at 4 dur. 1.75"
+,220500,22050,0.5,"6 of 10: 0.5 at 5 dur. 0.5"
+,264600,77175,0.6,"7 of 10: 0.6 at 6 dur. 1.75"
+,308700,22050,0.7,"8 of 10: 0.7 at 7 dur. 0.5"
+,352800,77175,0.8,"9 of 10: 0.8 at 8 dur. 1.75"
+,396900,22050,0.9,"10 of 10: 0.9 at 9 dur. 0.5"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/expected/notes-regions-separator.csv	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+"./../audio/20sec-silence.wav"@0.000000000@1.750000000@0@"1 of 10: 0 at 0 dur. 1.75"
+@1.000000000@0.500000000@0.1@"2 of 10: 0.1 at 1 dur. 0.5"
+@2.000000000@1.750000000@0.2@"3 of 10: 0.2 at 2 dur. 1.75"
+@3.000000000@0.500000000@0.3@"4 of 10: 0.3 at 3 dur. 0.5"
+@4.000000000@1.750000000@0.4@"5 of 10: 0.4 at 4 dur. 1.75"
+@5.000000000@0.500000000@0.5@"6 of 10: 0.5 at 5 dur. 0.5"
+@6.000000000@1.750000000@0.6@"7 of 10: 0.6 at 6 dur. 1.75"
+@7.000000000@0.500000000@0.7@"8 of 10: 0.7 at 7 dur. 0.5"
+@8.000000000@1.750000000@0.8@"9 of 10: 0.8 at 8 dur. 1.75"
+@9.000000000@0.500000000@0.9@"10 of 10: 0.9 at 9 dur. 0.5"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-csv-writer/test-csv-writer.sh	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+. ../include.sh
+
+infile=$audiopath/20sec-silence.wav
+tmpcsv=$mypath/tmp_1_$$.csv
+
+trap "rm -f $tmpcsv" 0
+
+for output in notes-regions curve-vsr; do
+
+    flag=""
+
+    $r -d "$testplug:$output" -w csv --csv-stdout "$infile" 2>/dev/null > "$tmpcsv" || \
+	fail "Failed to run for plugin $testplug with output $output and no additional flags"
+
+    csvcompare "$tmpcsv" "$mypath/expected/$output-no-flags.csv" || \
+	faildiff "Output differs from expected for output $output and no additional flags" "$tmpcsv" "$mypath/expected/$output-no-flags.csv"
+
+    flag=sample-timing
+
+    $r -d "$testplug:$output" -w csv --csv-$flag --csv-stdout "$infile" 2>/dev/null > "$tmpcsv" || \
+	fail "Failed to run for plugin $testplug with output $output and $flag flag"
+
+    csvcompare "$tmpcsv" "$mypath/expected/$output-$flag.csv" || \
+	faildiff "Output differs from expected for output $output and $flag flag" "$tmpcsv" "$mypath/expected/$output-$flag.csv"
+
+    flag=fill-ends
+
+    $r -d "$testplug:$output" -w csv --csv-$flag --csv-stdout "$infile" 2>/dev/null > "$tmpcsv" || \
+	fail "Failed to run for plugin $testplug with output $output and $flag flag"
+
+    csvcompare "$tmpcsv" "$mypath/expected/$output-$flag.csv" || \
+	faildiff "Output differs from expected for output $output and $flag flag" "$tmpcsv" "$mypath/expected/$output-$flag.csv"
+
+    flag=end-times
+
+    $r -d "$testplug:$output" -w csv --csv-$flag --csv-stdout "$infile" 2>/dev/null > "$tmpcsv" || \
+	fail "Failed to run for plugin $testplug with output $output and $flag flag"
+
+    csvcompare "$tmpcsv" "$mypath/expected/$output-$flag.csv" || \
+	faildiff "Output differs from expected for output $output and $flag flag" "$tmpcsv" "$mypath/expected/$output-$flag.csv"
+
+    flag=separator
+
+    $r -d "$testplug:$output" -w csv --csv-$flag '@' --csv-stdout "$infile" 2>/dev/null > "$tmpcsv" || \
+	fail "Failed to run for plugin $testplug with output $output and $flag flag"
+
+    csvcompare "$tmpcsv" "$mypath/expected/$output-$flag.csv" || \
+	faildiff "Output differs from expected for output $output and $flag flag" "$tmpcsv" "$mypath/expected/$output-$flag.csv"
+
+    flag=all
+
+    $r -d "$testplug:$output" -w csv --csv-sample-timing --csv-fill-ends --csv-end-times --csv-separator '@' --csv-stdout "$infile" 2>/dev/null > "$tmpcsv" || \
+	fail "Failed to run for plugin $testplug with output $output and all flags"
+
+    csvcompare "$tmpcsv" "$mypath/expected/$output-$flag.csv" || \
+	faildiff "Output differs from expected for output $output and all flags" "$tmpcsv" "$mypath/expected/$output-$flag.csv"
+
+done
+
+exit 0
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lab-destinations/test-lab-destinations.sh	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,115 @@
+#!/bin/bash
+
+. ../include.sh
+
+infile1=$audiopath/3clicks8.wav
+infile2=$audiopath/6clicks8.wav
+
+outfile1=$audiopath/3clicks8_vamp_vamp-example-plugins_percussiononsets_onsets.lab
+outfile2=$audiopath/6clicks8_vamp_vamp-example-plugins_percussiononsets_onsets.lab
+
+infile1dot=$audiopath/3.clicks.8.wav
+outfile1dot=$audiopath/3.clicks.8_vamp_vamp-example-plugins_percussiononsets_onsets.lab
+
+outfile3=$audiopath/3clicks8_vamp_vamp-example-plugins_percussiononsets_onsets.lab
+outfile4=$audiopath/3clicks8_vamp_vamp-example-plugins_percussiononsets_detectionfunction.lab
+
+tmplab=$mypath/tmp_1_$$.lab
+
+trap "rm -f $tmplab $outfile1 $outfile2 $outfile3 $outfile4 $infile1dot $outfile1dot" 0
+
+transformdir=$mypath/transforms
+
+check_lab() {
+    test -f $1 || \
+	fail "Fails to write output to expected location $1 for $2"
+    # every line must contain the same number of tabs
+    formats=`awk -F'\t' '{ print NF; }' $1 | sort | uniq | wc | awk '{ print $1 }'`
+    if [ "$formats" != "1" ]; then
+	fail "Output is not consistently formatted tab-separated file for $2"
+    fi
+    rm -f $1
+}    
+
+
+ctx="onsets transform, one audio file, default LAB writer destination"
+
+rm -f $outfile1
+
+$r -t $transformdir/onsets.n3 -w lab $infile1 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_lab $outfile1 "$ctx"
+
+
+ctx="onsets transform, one audio file with dots in filename, default LAB writer destination"
+
+rm -f $outfile1
+
+cp $infile1 $infile1dot
+
+$r -t $transformdir/onsets.n3 -w lab $infile1dot 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_lab $outfile1dot "$ctx"
+
+rm -f $infile1dot $outfile1dot
+
+
+ctx="onsets and df transforms, one audio file, default LAB writer destination"
+
+rm -f $outfile1
+
+$r -t $transformdir/onsets.n3 -t $transformdir/detectionfunction.n3 -w lab $infile1 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_lab $outfile1 "$ctx"
+
+
+ctx="onsets transform, two audio files, default LAB writer destination"
+
+rm -f $outfile1
+rm -f $outfile2
+
+$r -t $transformdir/onsets.n3 -w lab $infile1 $infile2 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_lab $outfile1 "$ctx"
+check_lab $outfile2 "$ctx"
+
+
+ctx="onsets transform, two audio files, one-file LAB writer"
+
+# Writer should refuse to write results from more than one audio file
+# together (as the audio file is not identified)
+
+$r -t $transformdir/onsets.n3 -w lab --lab-one-file $tmplab $infile1 $infile2 2>/dev/null && \
+    fail "Fails by completing successfully with $ctx"
+
+
+ctx="onsets transform, two audio files, stdout LAB writer"
+
+$r -t $transformdir/onsets.n3 -w lab --lab-stdout $infile1 $infile2 2>/dev/null >$tmplab || \
+    fail "Fails to run with $ctx"
+
+check_lab $tmplab "$ctx"
+
+
+ctx="existing output file and no --lab-force"
+
+touch $outfile1
+
+$r -t $transformdir/onsets.n3 -w lab $infile1 2>/dev/null && \
+    fail "Fails by completing successfully when output file already exists (should refuse and bail out)"
+
+
+ctx="existing output file and --lab-force"
+
+touch $outfile1
+
+$r -t $transformdir/onsets.n3 -w lab --lab-force $infile1 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_lab $outfile1 "$ctx"
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lab-destinations/transforms/detectionfunction.n3	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,11 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_detectionfunction .
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lab-destinations/transforms/onsets.n3	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets.
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lab-writer/expected/curve-vsr-fill-ends.lab	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+0.000000000	0.750000000	0	"1 of 10: 0 at 0"
+0.750000000	1.500000000	0.1	"2 of 10: 0.1 at 0.75"
+1.500000000	2.250000000	0.2	"3 of 10: 0.2 at 1.5"
+2.250000000	3.000000000	0.3	"4 of 10: 0.3 at 2.25"
+3.000000000	3.750000000	0.4	"5 of 10: 0.4 at 3"
+3.750000000	4.500000000	0.5	"6 of 10: 0.5 at 3.75"
+4.500000000	5.250000000	0.6	"7 of 10: 0.6 at 4.5"
+5.250000000	6.000000000	0.7	"8 of 10: 0.7 at 5.25"
+6.000000000	6.750000000	0.8	"9 of 10: 0.8 at 6"
+6.750000000	6.750000000	0.9	"10 of 10: 0.9 at 6.75"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lab-writer/expected/curve-vsr-no-flags.lab	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+0.000000000	0	"1 of 10: 0 at 0"
+0.750000000	0.1	"2 of 10: 0.1 at 0.75"
+1.500000000	0.2	"3 of 10: 0.2 at 1.5"
+2.250000000	0.3	"4 of 10: 0.3 at 2.25"
+3.000000000	0.4	"5 of 10: 0.4 at 3"
+3.750000000	0.5	"6 of 10: 0.5 at 3.75"
+4.500000000	0.6	"7 of 10: 0.6 at 4.5"
+5.250000000	0.7	"8 of 10: 0.7 at 5.25"
+6.000000000	0.8	"9 of 10: 0.8 at 6"
+6.750000000	0.9	"10 of 10: 0.9 at 6.75"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lab-writer/expected/notes-regions-fill-ends.lab	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+0.000000000	1.750000000	0	"1 of 10: 0 at 0 dur. 1.75"
+1.000000000	1.500000000	0.1	"2 of 10: 0.1 at 1 dur. 0.5"
+2.000000000	3.750000000	0.2	"3 of 10: 0.2 at 2 dur. 1.75"
+3.000000000	3.500000000	0.3	"4 of 10: 0.3 at 3 dur. 0.5"
+4.000000000	5.750000000	0.4	"5 of 10: 0.4 at 4 dur. 1.75"
+5.000000000	5.500000000	0.5	"6 of 10: 0.5 at 5 dur. 0.5"
+6.000000000	7.750000000	0.6	"7 of 10: 0.6 at 6 dur. 1.75"
+7.000000000	7.500000000	0.7	"8 of 10: 0.7 at 7 dur. 0.5"
+8.000000000	9.750000000	0.8	"9 of 10: 0.8 at 8 dur. 1.75"
+9.000000000	9.500000000	0.9	"10 of 10: 0.9 at 9 dur. 0.5"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lab-writer/expected/notes-regions-no-flags.lab	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,10 @@
+0.000000000	1.750000000	0	"1 of 10: 0 at 0 dur. 1.75"
+1.000000000	1.500000000	0.1	"2 of 10: 0.1 at 1 dur. 0.5"
+2.000000000	3.750000000	0.2	"3 of 10: 0.2 at 2 dur. 1.75"
+3.000000000	3.500000000	0.3	"4 of 10: 0.3 at 3 dur. 0.5"
+4.000000000	5.750000000	0.4	"5 of 10: 0.4 at 4 dur. 1.75"
+5.000000000	5.500000000	0.5	"6 of 10: 0.5 at 5 dur. 0.5"
+6.000000000	7.750000000	0.6	"7 of 10: 0.6 at 6 dur. 1.75"
+7.000000000	7.500000000	0.7	"8 of 10: 0.7 at 7 dur. 0.5"
+8.000000000	9.750000000	0.8	"9 of 10: 0.8 at 8 dur. 1.75"
+9.000000000	9.500000000	0.9	"10 of 10: 0.9 at 9 dur. 0.5"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lab-writer/test-lab-writer.sh	Wed Oct 15 11:23:05 2014 +0100
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+. ../include.sh
+
+infile=$audiopath/20sec-silence.wav
+tmplab=$mypath/tmp_1_$$.lab
+
+trap "rm -f $tmplab" 0
+
+for output in notes-regions curve-vsr; do
+
+    flag=""
+
+    $r -d "$testplug:$output" -w lab --lab-stdout "$infile" 2>/dev/null > "$tmplab" || \
+	fail "Failed to run for plugin $testplug with output $output and no additional flags"
+
+    csvcompare "$tmplab" "$mypath/expected/$output-no-flags.lab" || \
+	faildiff "Output differs from expected for output $output and no additional flags" "$tmplab" "$mypath/expected/$output-no-flags.lab"
+
+    flag=fill-ends
+
+    $r -d "$testplug:$output" -w lab --lab-$flag --lab-stdout "$infile" 2>/dev/null > "$tmplab" || \
+	fail "Failed to run for plugin $testplug with output $output and $flag flag"
+
+    csvcompare "$tmplab" "$mypath/expected/$output-$flag.lab" || \
+	faildiff "Output differs from expected for output $output and $flag flag" "$tmplab" "$mypath/expected/$output-$flag.lab"
+done
+
+# Do it all over again, but using the CSV writer. The Lab writer is
+# actually redundant, it's equivalent to -w csv --csv-separator '\t'
+# --csv-end-times --csv-omit-filename
+
+for output in notes-regions curve-vsr; do
+
+    flag=""
+
+    $r -d "$testplug:$output" -w csv --csv-separator '\t' --csv-end-times --csv-omit-filename --csv-stdout "$infile" 2>/dev/null > "$tmplab" || \
+	fail "Failed to run for plugin $testplug and CSV writer with output $output and no additional flags"
+
+    csvcompare "$tmplab" "$mypath/expected/$output-no-flags.lab" || \
+	faildiff "Output differs from expected for CSV writer with output $output and no additional flags" "$tmplab" "$mypath/expected/$output-no-flags.lab"
+
+    flag=fill-ends
+
+    $r -d "$testplug:$output" -w csv --csv-separator '\t' --csv-end-times --csv-omit-filename --csv-$flag --csv-stdout "$infile" 2>/dev/null > "$tmplab" || \
+	fail "Failed to run for plugin $testplug and CSV writer with output $output and $flag flag"
+
+    csvcompare "$tmplab" "$mypath/expected/$output-$flag.lab" || \
+	faildiff "Output differs from expected for CSV writer with output $output and $flag flag" "$tmplab" "$mypath/expected/$output-$flag.lab"
+done
+
+exit 0
+
--- a/tests/test.sh	Tue Oct 14 11:13:31 2014 +0100
+++ b/tests/test.sh	Wed Oct 15 11:23:05 2014 +0100
@@ -11,7 +11,10 @@
     as-advertised \
     rdf-writer \
     rdf-destinations \
+    csv-writer \
     csv-destinations \
+    lab-writer \
+    lab-destinations \
     midi-destinations \
     summaries \
     multiple-audio \