changeset 1206:659372323b45 tony-2.0-integration

Merge latest SV 3.0 branch code
author Chris Cannam
date Fri, 19 Aug 2016 15:58:57 +0100
parents e94719f941ba (current diff) 35387a99c236 (diff)
children d60f9715ba78
files base/ResizeableBitset.h
diffstat 52 files changed, 1062 insertions(+), 767 deletions(-) [+]
line wrap: on
line diff
--- a/acinclude.m4	Tue Oct 20 12:54:06 2015 +0100
+++ b/acinclude.m4	Fri Aug 19 15:58:57 2016 +0100
@@ -69,6 +69,9 @@
    	AC_CHECK_PROG(QMAKE, qmake-qt5, $QTDIR/bin/qmake-qt5,,$QTDIR/bin/)
 fi
 if test x$QMAKE = x ; then
+   	AC_CHECK_PROG(QMAKE, qt5-qmake, $QTDIR/bin/qt5-qmake,,$QTDIR/bin/)
+fi
+if test x$QMAKE = x ; then
    	AC_CHECK_PROG(QMAKE, qmake, $QTDIR/bin/qmake,,$QTDIR/bin/)
 fi
 if test x$QMAKE = x ; then
@@ -78,6 +81,9 @@
    	AC_CHECK_PROG(QMAKE, qmake-qt5, qmake-qt5,,$PATH)
 fi
 if test x$QMAKE = x ; then
+   	AC_CHECK_PROG(QMAKE, qt5-qmake, qt5-qmake,,$PATH)
+fi
+if test x$QMAKE = x ; then
    	AC_CHECK_PROG(QMAKE, qmake, qmake,,$PATH)
 fi
 if test x$QMAKE = x ; then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/ColumnOp.h	Fri Aug 19 15:58:57 2016 +0100
@@ -0,0 +1,216 @@
+/* -*- 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 2006-2016 Chris Cannam and 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 COLUMN_OP_H
+#define COLUMN_OP_H
+
+#include "BaseTypes.h"
+
+#include <cmath>
+
+/**
+ * Display normalization types for columns in e.g. grid plots.
+ *
+ * Max1 means to normalize to max value = 1.0.
+ * Sum1 means to normalize to sum of values = 1.0.
+ *
+ * Hybrid means normalize to max = 1.0 and then multiply by
+ * log10 of the max value, to retain some difference between
+ * levels of neighbouring columns.
+ *
+ * Area normalization is handled separately.
+ */
+enum class ColumnNormalization {
+    None,
+    Max1,
+    Sum1,
+    Hybrid
+};
+
+/**
+ * Class containing static functions for simple operations on data
+ * columns, for use by display layers.
+ */
+class ColumnOp
+{
+public:
+    /** 
+     * Column type. 
+     */
+    typedef std::vector<float> Column;
+
+    /**
+     * Scale the given column using the given gain multiplier.
+     */
+    static Column applyGain(const Column &in, double gain) {
+	
+	if (gain == 1.0) {
+	    return in;
+	}
+	Column out;
+	out.reserve(in.size());
+	for (auto v: in) {
+	    out.push_back(float(v * gain));
+	}
+	return out;
+    }
+
+    /**
+     * Scale an FFT output by half the FFT size.
+     */
+    static Column fftScale(const Column &in, int fftSize) {
+        return applyGain(in, 2.0 / fftSize);
+    }
+
+    /**
+     * Determine whether an index points to a local peak.
+     */
+    static bool isPeak(const Column &in, int ix) {
+	
+	if (!in_range_for(in, ix-1)) return false;
+	if (!in_range_for(in, ix+1)) return false;
+	if (in[ix] < in[ix+1]) return false;
+	if (in[ix] < in[ix-1]) return false;
+	
+	return true;
+    }
+
+    /**
+     * Return a column containing only the local peak values (all
+     * others zero).
+     */
+    static Column peakPick(const Column &in) {
+	
+	std::vector<float> out(in.size(), 0.f);
+	for (int i = 0; in_range_for(in, i); ++i) {
+	    if (isPeak(in, i)) {
+		out[i] = in[i];
+	    }
+	}
+	
+	return out;
+    }
+
+    /**
+     * Return a column normalized from the input column according to
+     * the given normalization scheme.
+     */
+    static Column normalize(const Column &in, ColumnNormalization n) {
+
+	if (n == ColumnNormalization::None) {
+	    return in;
+	}
+
+        float scale = 1.f;
+        
+        if (n == ColumnNormalization::Sum1) {
+
+            float sum = 0.f;
+
+            for (auto v: in) {
+                sum += v;
+            }
+
+            if (sum != 0.f) {
+                scale = 1.f / sum;
+            }
+        } else {
+        
+            float max = *max_element(in.begin(), in.end());
+
+            if (n == ColumnNormalization::Max1) {
+                if (max != 0.f) {
+                    scale = 1.f / max;
+                }
+            } else if (n == ColumnNormalization::Hybrid) {
+                if (max > 0.f) {
+                    scale = log10f(max + 1.f) / max;
+                }
+            }
+        }
+
+        return applyGain(in, scale);
+    }
+
+    /**
+     * Distribute the given column into a target vector of a different
+     * size, optionally using linear interpolation. The binfory vector
+     * contains a mapping from y coordinate (i.e. index into the
+     * target vector) to bin (i.e. index into the source column).
+     */
+    static Column distribute(const Column &in,
+			     int h,
+			     const std::vector<double> &binfory,
+			     int minbin,
+			     bool interpolate) {
+
+	std::vector<float> out(h, 0.f);
+	int bins = int(in.size());
+
+	for (int y = 0; y < h; ++y) {
+        
+	    double sy0 = binfory[y] - minbin;
+	    double sy1 = sy0 + 1;
+	    if (y+1 < h) {
+		sy1 = binfory[y+1] - minbin;
+	    }
+        
+	    if (interpolate && fabs(sy1 - sy0) < 1.0) {
+            
+		double centre = (sy0 + sy1) / 2;
+		double dist = (centre - 0.5) - rint(centre - 0.5);
+		int bin = int(centre);
+
+		int other = (dist < 0 ? (bin-1) : (bin+1));
+
+		if (bin < 0) bin = 0;
+		if (bin >= bins) bin = bins-1;
+
+		if (other < 0 || other >= bins) {
+		    other = bin;
+		}
+
+		double prop = 1.0 - fabs(dist);
+
+		double v0 = in[bin];
+		double v1 = in[other];
+                
+		out[y] = float(prop * v0 + (1.0 - prop) * v1);
+
+	    } else { // not interpolating this one
+
+		int by0 = int(sy0 + 0.0001);
+		int by1 = int(sy1 + 0.0001);
+		if (by1 < by0 + 1) by1 = by0 + 1;
+                if (by1 >= bins) by1 = by1 - 1;
+                
+		for (int bin = by0; bin < by1; ++bin) {
+
+		    float value = in[bin];
+
+		    if (bin == by0 || value > out[y]) {
+			out[y] = value;
+		    }
+		}
+	    }
+	}
+
+	return out;
+    }
+
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/MagnitudeRange.h	Fri Aug 19 15:58:57 2016 +0100
@@ -0,0 +1,83 @@
+/* -*- 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 2006 Chris Cannam and 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 MAGNITUDE_RANGE_H
+#define MAGNITUDE_RANGE_H
+
+#include <vector>
+
+/**
+ * Maintain a min and max value, and update them when supplied a new
+ * data point.
+ */
+class MagnitudeRange
+{
+public:
+    MagnitudeRange() : m_min(0), m_max(0) { }
+    MagnitudeRange(float min, float max) : m_min(min), m_max(max) { }
+    
+    bool operator==(const MagnitudeRange &r) {
+	return r.m_min == m_min && r.m_max == m_max;
+    }
+    bool operator!=(const MagnitudeRange &r) {
+        return !(*this == r);
+    }
+    
+    bool isSet() const { return (m_min != 0.f || m_max != 0.f); }
+    void set(float min, float max) {
+	m_min = min;
+	m_max = max;
+	if (m_max < m_min) m_max = m_min;
+    }
+    bool sample(float f) {
+	bool changed = false;
+	if (isSet()) {
+	    if (f < m_min) { m_min = f; changed = true; }
+	    if (f > m_max) { m_max = f; changed = true; }
+	} else {
+	    m_max = m_min = f;
+	    changed = true;
+	}
+	return changed;
+    }
+    bool sample(const std::vector<float> &ff) {
+        bool changed = false;
+        for (auto f: ff) {
+            if (sample(f)) {
+                changed = true;
+            }
+        }
+        return changed;
+    }
+    bool sample(const MagnitudeRange &r) {
+	bool changed = false;
+	if (isSet()) {
+	    if (r.m_min < m_min) { m_min = r.m_min; changed = true; }
+	    if (r.m_max > m_max) { m_max = r.m_max; changed = true; }
+	} else {
+	    m_min = r.m_min;
+	    m_max = r.m_max;
+	    changed = true;
+	}
+	return changed;
+    }            
+    float getMin() const { return m_min; }
+    float getMax() const { return m_max; }
+private:
+    float m_min;
+    float m_max;
+};
+
+#endif
--- a/base/RangeMapper.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/base/RangeMapper.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -23,13 +23,15 @@
 
 LinearRangeMapper::LinearRangeMapper(int minpos, int maxpos,
 				     double minval, double maxval,
-                                     QString unit, bool inverted) :
+                                     QString unit, bool inverted,
+                                     std::map<int, QString> labels) :
     m_minpos(minpos),
     m_maxpos(maxpos),
     m_minval(minval),
     m_maxval(maxval),
     m_unit(unit),
-    m_inverted(inverted)
+    m_inverted(inverted),
+    m_labels(labels)
 {
     assert(m_maxval != m_minval);
     assert(m_maxpos != m_minpos);
@@ -70,9 +72,20 @@
     double value = m_minval +
         ((double(position - m_minpos) / double(m_maxpos - m_minpos))
          * (m_maxval - m_minval));
+//    cerr << "getValueForPositionUnclamped(" << position << "): minval " << m_minval << ", maxval " << m_maxval << ", value " << value << endl;
     return value;
 }
 
+QString
+LinearRangeMapper::getLabel(int position) const
+{
+    if (m_labels.find(position) != m_labels.end()) {
+        return m_labels.at(position);
+    } else {
+        return "";
+    }
+}
+
 LogRangeMapper::LogRangeMapper(int minpos, int maxpos,
                                double minval, double maxval,
                                QString unit, bool inverted) :
--- a/base/RangeMapper.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/base/RangeMapper.h	Fri Aug 19 15:58:57 2016 +0100
@@ -50,7 +50,7 @@
     virtual double getValueForPosition(int position) const = 0;
 
     /**
-     * Return the value mapped from the given positionq, without
+     * Return the value mapped from the given position, without
      * clamping. That is, whatever mapping function is in use will be
      * projected even outside the minimum and maximum extents of the
      * mapper's value range. (The mapping outside that range is not
@@ -62,6 +62,16 @@
      * Get the unit of the mapper's value range.
      */
     virtual QString getUnit() const { return ""; }
+
+    /**
+     * The mapper may optionally provide special labels for one or
+     * more individual positions (such as the minimum position, the
+     * default, or indeed all positions). These should be used in any
+     * display context in preference to just showing the numerical
+     * value for the position. If a position has such a label, return
+     * it here.
+     */
+    virtual QString getLabel(int /* position */) const { return ""; }
 };
 
 
@@ -76,7 +86,8 @@
      */
     LinearRangeMapper(int minpos, int maxpos,
                       double minval, double maxval,
-                      QString unit = "", bool inverted = false);
+                      QString unit = "", bool inverted = false,
+                      std::map<int, QString> labels = {});
     
     virtual int getPositionForValue(double value) const;
     virtual int getPositionForValueUnclamped(double value) const;
@@ -85,6 +96,7 @@
     virtual double getValueForPositionUnclamped(int position) const;
 
     virtual QString getUnit() const { return m_unit; }
+    virtual QString getLabel(int position) const;
 
 protected:
     int m_minpos;
@@ -93,6 +105,7 @@
     double m_maxval;
     QString m_unit;
     bool m_inverted;
+    std::map<int, QString> m_labels;
 };
 
 class LogRangeMapper : public RangeMapper
--- a/base/ResizeableBitset.h	Tue Oct 20 12:54:06 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-/* -*- 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 2006 Chris Cannam.
-    
-    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 _RESIZEABLE_BITMAP_H_
-#define _RESIZEABLE_BITMAP_H_
-
-#include <vector>
-#include <stdint.h>
-#include <stddef.h>
-#include <stdlib.h>
-
-class ResizeableBitset {
-
-public:
-    ResizeableBitset() : m_bits(0), m_size(0) {
-    }
-    ResizeableBitset(size_t size) : m_bits(new std::vector<uint8_t>), m_size(size) {
-        m_bits->assign((size >> 3) + 1, 0);
-    }
-    ResizeableBitset(const ResizeableBitset &b) {
-        m_bits = new std::vector<uint8_t>(*b.m_bits);
-    }
-    ResizeableBitset &operator=(const ResizeableBitset &b) {
-        if (&b != this) return *this;
-        delete m_bits;
-        m_bits = new std::vector<uint8_t>(*b.m_bits);
-        return *this;
-    }
-    ~ResizeableBitset() {
-        delete m_bits;
-    }
-    
-    void resize(size_t size) { // retaining existing data; not thread safe
-        size_t bytes = (size >> 3) + 1;
-        if (m_bits && bytes == m_bits->size()) return;
-        std::vector<uint8_t> *newbits = new std::vector<uint8_t>(bytes);
-        newbits->assign(bytes, 0);
-        if (m_bits) {
-            for (size_t i = 0; i < bytes && i < m_bits->size(); ++i) {
-                (*newbits)[i] = (*m_bits)[i];
-            }
-            delete m_bits;
-        }
-        m_bits = newbits;
-        m_size = size;
-    }
-    
-    bool get(size_t column) const {
-        return ((*m_bits)[column >> 3]) & (1u << (column & 0x07));
-    }
-    
-    void set(size_t column) {
-        size_t ix = (column >> 3);
-        uint8_t prior = (*m_bits)[ix];
-        uint8_t extra = ((1u << (column & 0x07)) & 0xff);
-        (*m_bits)[ix] = uint8_t(prior | extra);
-    }
-
-    void reset(size_t column) {
-        ((*m_bits)[column >> 3]) &= uint8_t((~(1u << (column & 0x07))) & 0xff);
-    }
-
-    void copy(size_t source, size_t dest) {
-        get(source) ? set(dest) : reset(dest);
-    }
-
-    bool isAllOff() const {
-        for (size_t i = 0; i < m_bits->size(); ++i) {
-            if ((*m_bits)[i]) return false;
-        }
-        return true;
-    }
-
-    bool isAllOn() const {
-        for (size_t i = 0; i + 1 < m_bits->size(); ++i) {
-            if ((*m_bits)[i] != 0xff) return false;
-        }
-        for (size_t i = (m_size / 8) * 8; i < m_size; ++i) {
-            if (!get(i)) return false;
-        }
-        return true;
-    }
-
-    size_t size() const {
-        return m_size;
-    }
-    
-private:
-    std::vector<uint8_t> *m_bits;
-    size_t m_size;
-};
-
-
-#endif
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/Strings.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -0,0 +1,28 @@
+/* -*- 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 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 "Strings.h"
+
+QString
+Strings::pi = QChar(0x3c0);
+
+QString
+Strings::minus_pi = "-" + pi;
+
+QString
+Strings::infinity = QChar(0x221e);
+
+QString
+Strings::minus_infinity = "-" + infinity;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/Strings.h	Fri Aug 19 15:58:57 2016 +0100
@@ -0,0 +1,30 @@
+/* -*- 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 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 SV_STRINGS_H
+#define SV_STRINGS_H
+
+#include <QString>
+
+class Strings
+{
+public:
+    static QString pi;
+    static QString minus_pi;
+
+    static QString infinity;
+    static QString minus_infinity;
+};
+
+#endif
--- a/base/test/test.pro	Tue Oct 20 12:54:06 2015 +0100
+++ b/base/test/test.pro	Fri Aug 19 15:58:57 2016 +0100
@@ -1,6 +1,8 @@
 
 TEMPLATE = app
 
+INCLUDEPATH += ../../../vamp-plugin-sdk
+
 LIBS += -L../.. -L../../../dataquay -L../../release -L../../../dataquay/release -lsvcore -ldataquay
 
 win32-g++ {
@@ -27,7 +29,7 @@
 
     DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG HAVE_PORTAUDIO
 
-    LIBS += -lbz2 -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0
+    LIBS += -lbz2 -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0
 
     win* {
         LIBS += -llo -lwinmm -lws2_32
--- a/config.pri.in	Tue Oct 20 12:54:06 2015 +0100
+++ b/config.pri.in	Fri Aug 19 15:58:57 2016 +0100
@@ -10,7 +10,7 @@
 QMAKE_CXXFLAGS += @CXXFLAGS@ 
 QMAKE_LFLAGS += @LDFLAGS@
 
-linux*:LIBS += -lasound
+linux*:LIBS += -lasound -ldl
 
 macx*:DEFINES += HAVE_COREAUDIO MACOSX_DEPLOYMENT_TARGET=1060
 
--- a/configure	Tue Oct 20 12:54:06 2015 +0100
+++ b/configure	Fri Aug 19 15:58:57 2016 +0100
@@ -650,10 +650,6 @@
 portaudio_2_0_CFLAGS
 liblo_LIBS
 liblo_CFLAGS
-vamphostsdk_LIBS
-vamphostsdk_CFLAGS
-vamp_LIBS
-vamp_CFLAGS
 samplerate_LIBS
 samplerate_CFLAGS
 sndfile_LIBS
@@ -754,10 +750,6 @@
 sndfile_LIBS
 samplerate_CFLAGS
 samplerate_LIBS
-vamp_CFLAGS
-vamp_LIBS
-vamphostsdk_CFLAGS
-vamphostsdk_LIBS
 liblo_CFLAGS
 liblo_LIBS
 portaudio_2_0_CFLAGS
@@ -1419,12 +1411,6 @@
               C compiler flags for samplerate, overriding pkg-config
   samplerate_LIBS
               linker flags for samplerate, overriding pkg-config
-  vamp_CFLAGS C compiler flags for vamp, overriding pkg-config
-  vamp_LIBS   linker flags for vamp, overriding pkg-config
-  vamphostsdk_CFLAGS
-              C compiler flags for vamphostsdk, overriding pkg-config
-  vamphostsdk_LIBS
-              linker flags for vamphostsdk, overriding pkg-config
   liblo_CFLAGS
               C compiler flags for liblo, overriding pkg-config
   liblo_LIBS  linker flags for liblo, overriding pkg-config
@@ -4129,6 +4115,45 @@
 
 fi
 if test x$QMAKE = x ; then
+   	# Extract the first word of "qt5-qmake", so it can be a program name with args.
+set dummy qt5-qmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_QMAKE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$QMAKE"; then
+  ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $QTDIR/bin/
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_QMAKE="$QTDIR/bin/qt5-qmake"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+QMAKE=$ac_cv_prog_QMAKE
+if test -n "$QMAKE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5
+$as_echo "$QMAKE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test x$QMAKE = x ; then
    	# Extract the first word of "qmake", so it can be a program name with args.
 set dummy qmake; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -4246,6 +4271,45 @@
 
 fi
 if test x$QMAKE = x ; then
+   	# Extract the first word of "qt5-qmake", so it can be a program name with args.
+set dummy qt5-qmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_QMAKE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$QMAKE"; then
+  ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_QMAKE="qt5-qmake"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+QMAKE=$ac_cv_prog_QMAKE
+if test -n "$QMAKE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5
+$as_echo "$QMAKE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test x$QMAKE = x ; then
    	# Extract the first word of "qmake", so it can be a program name with args.
 set dummy qmake; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -5140,308 +5204,6 @@
 fi
 
 
-SV_MODULE_MODULE=vamp
-SV_MODULE_VERSION_TEST="vamp >= 2.1"
-SV_MODULE_HEADER=vamp/vamp.h
-SV_MODULE_LIB=
-SV_MODULE_FUNC=
-SV_MODULE_HAVE=HAVE_$(echo vamp | tr 'a-z' 'A-Z')
-SV_MODULE_FAILED=1
-if test -n "$vamp_LIBS" ; then
-   { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
-$as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $vamp_CFLAGS"
-   LIBS="$LIBS $vamp_LIBS"
-   SV_MODULE_FAILED=""
-fi
-if test -z "$SV_MODULE_VERSION_TEST" ; then
-   SV_MODULE_VERSION_TEST=$SV_MODULE_MODULE
-fi
-if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
-
-pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for vamp" >&5
-$as_echo_n "checking for vamp... " >&6; }
-
-if test -n "$vamp_CFLAGS"; then
-    pkg_cv_vamp_CFLAGS="$vamp_CFLAGS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_vamp_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-if test -n "$vamp_LIBS"; then
-    pkg_cv_vamp_LIBS="$vamp_LIBS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_vamp_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-
-
-
-if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-
-if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
-        _pkg_short_errors_supported=yes
-else
-        _pkg_short_errors_supported=no
-fi
-        if test $_pkg_short_errors_supported = yes; then
-	        vamp_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        else
-	        vamp_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        fi
-	# Put the nasty error message in config.log where it belongs
-	echo "$vamp_PKG_ERRORS" >&5
-
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-else
-	vamp_CFLAGS=$pkg_cv_vamp_CFLAGS
-	vamp_LIBS=$pkg_cv_vamp_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $vamp_CFLAGS";LIBS="$LIBS $vamp_LIBS";SV_MODULE_FAILED=""
-fi
-fi
-if test -n "$SV_MODULE_FAILED"; then
-   as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh`
-ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  HAVES="$HAVES $SV_MODULE_HAVE"
-else
-  as_fn_error $? "Failed to find header $SV_MODULE_HEADER for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
-
-   if test -n "$SV_MODULE_LIB"; then
-     as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh`
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5
-$as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; }
-if eval \${$as_ac_Lib+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-l$SV_MODULE_LIB  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char $SV_MODULE_FUNC ();
-int
-main ()
-{
-return $SV_MODULE_FUNC ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_cxx_try_link "$LINENO"; then :
-  eval "$as_ac_Lib=yes"
-else
-  eval "$as_ac_Lib=no"
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-eval ac_res=\$$as_ac_Lib
-	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
-if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
-  LIBS="$LIBS -l$SV_MODULE_LIB"
-else
-  as_fn_error $? "Failed to find library $SV_MODULE_LIB for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
-   fi
-fi
-
-
-SV_MODULE_MODULE=vamphostsdk
-SV_MODULE_VERSION_TEST="vamp-hostsdk >= 2.5"
-SV_MODULE_HEADER=vamp-hostsdk/PluginLoader.h
-SV_MODULE_LIB=vamp-hostsdk
-SV_MODULE_FUNC=libvamphostsdk_v_2_5_present
-SV_MODULE_HAVE=HAVE_$(echo vamphostsdk | tr 'a-z' 'A-Z')
-SV_MODULE_FAILED=1
-if test -n "$vamphostsdk_LIBS" ; then
-   { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5
-$as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;}
-   CXXFLAGS="$CXXFLAGS $vamphostsdk_CFLAGS"
-   LIBS="$LIBS $vamphostsdk_LIBS"
-   SV_MODULE_FAILED=""
-fi
-if test -z "$SV_MODULE_VERSION_TEST" ; then
-   SV_MODULE_VERSION_TEST=$SV_MODULE_MODULE
-fi
-if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then
-
-pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for vamphostsdk" >&5
-$as_echo_n "checking for vamphostsdk... " >&6; }
-
-if test -n "$vamphostsdk_CFLAGS"; then
-    pkg_cv_vamphostsdk_CFLAGS="$vamphostsdk_CFLAGS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_vamphostsdk_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-if test -n "$vamphostsdk_LIBS"; then
-    pkg_cv_vamphostsdk_LIBS="$vamphostsdk_LIBS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_vamphostsdk_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-
-
-
-if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-
-if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
-        _pkg_short_errors_supported=yes
-else
-        _pkg_short_errors_supported=no
-fi
-        if test $_pkg_short_errors_supported = yes; then
-	        vamphostsdk_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        else
-	        vamphostsdk_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        fi
-	# Put the nasty error message in config.log where it belongs
-	echo "$vamphostsdk_PKG_ERRORS" >&5
-
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5
-$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;}
-else
-	vamphostsdk_CFLAGS=$pkg_cv_vamphostsdk_CFLAGS
-	vamphostsdk_LIBS=$pkg_cv_vamphostsdk_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $vamphostsdk_CFLAGS";LIBS="$LIBS $vamphostsdk_LIBS";SV_MODULE_FAILED=""
-fi
-fi
-if test -n "$SV_MODULE_FAILED"; then
-   as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh`
-ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  HAVES="$HAVES $SV_MODULE_HAVE"
-else
-  as_fn_error $? "Failed to find header $SV_MODULE_HEADER for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
-
-   if test -n "$SV_MODULE_LIB"; then
-     as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh`
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5
-$as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; }
-if eval \${$as_ac_Lib+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-l$SV_MODULE_LIB  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char $SV_MODULE_FUNC ();
-int
-main ()
-{
-return $SV_MODULE_FUNC ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_cxx_try_link "$LINENO"; then :
-  eval "$as_ac_Lib=yes"
-else
-  eval "$as_ac_Lib=no"
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-eval ac_res=\$$as_ac_Lib
-	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
-if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
-  LIBS="$LIBS -l$SV_MODULE_LIB"
-else
-  as_fn_error $? "Failed to find library $SV_MODULE_LIB for required module $SV_MODULE_MODULE" "$LINENO" 5
-fi
-
-   fi
-fi
-
-
 
 SV_MODULE_MODULE=liblo
 SV_MODULE_VERSION_TEST=""
--- a/configure.ac	Tue Oct 20 12:54:06 2015 +0100
+++ b/configure.ac	Fri Aug 19 15:58:57 2016 +0100
@@ -83,8 +83,6 @@
 SV_MODULE_REQUIRED([fftw3f],[fftw3f >= 3.0.0],[fftw3.h],[fftw3f],[fftwf_execute])
 SV_MODULE_REQUIRED([sndfile],[sndfile >= 1.0.16],[sndfile.h],[sndfile],[sf_open])
 SV_MODULE_REQUIRED([samplerate],[samplerate >= 0.1.2],[samplerate.h],[samplerate],[src_new])
-SV_MODULE_REQUIRED([vamp],[vamp >= 2.1],[vamp/vamp.h],[],[])
-SV_MODULE_REQUIRED([vamphostsdk],[vamp-hostsdk >= 2.5],[vamp-hostsdk/PluginLoader.h],[vamp-hostsdk],[libvamphostsdk_v_2_5_present])
 
 SV_MODULE_OPTIONAL([liblo],[],[lo/lo.h],[lo],[lo_address_new])
 SV_MODULE_OPTIONAL([portaudio],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported])
--- a/data/fileio/AudioFileReaderFactory.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/fileio/AudioFileReaderFactory.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -29,6 +29,8 @@
 #include <QFileInfo>
 #include <iostream>
 
+//#define DEBUG_AUDIO_FILE_READER_FACTORY 1
+
 QString
 AudioFileReaderFactory::getKnownExtensions()
 {
@@ -87,7 +89,9 @@
 {
     QString err;
 
-    SVDEBUG << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\"): Requested rate: " << targetRate << endl;
+#ifdef DEBUG_AUDIO_FILE_READER_FACTORY
+    cerr << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\"): Requested rate: " << targetRate << endl;
+#endif
 
     if (!source.isOK()) {
         cerr << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Failed to retrieve source (transmission error?): " << source.getErrorString() << endl;
@@ -95,7 +99,7 @@
     }
 
     if (!source.isAvailable()) {
-        SVDEBUG << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Source not found" << endl;
+        cerr << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Source not found" << endl;
         return 0;
     }
 
@@ -139,8 +143,10 @@
              (cacheMode == CodedAudioFileReader::CacheInMemory) ||
              (targetRate != 0 && fileRate != targetRate))) {
 
-            SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", in memory " << (cacheMode == CodedAudioFileReader::CacheInMemory) << ", creating decoding reader" << endl;
-
+#ifdef DEBUG_AUDIO_FILE_READER_FACTORY
+            cerr << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", in memory " << (cacheMode == CodedAudioFileReader::CacheInMemory) << ", creating decoding reader" << endl;
+#endif
+            
             delete reader;
             reader = new DecodingWavFileReader
                 (source,
@@ -208,7 +214,9 @@
          (cacheMode == CodedAudioFileReader::CacheInMemory) ||
          (targetRate != 0 && fileRate != targetRate))) {
 
-        SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", in memory " << (cacheMode == CodedAudioFileReader::CacheInMemory) << ", creating decoding reader" << endl;
+#ifdef DEBUG_AUDIO_FILE_READER_FACTORY
+        cerr << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", in memory " << (cacheMode == CodedAudioFileReader::CacheInMemory) << ", creating decoding reader" << endl;
+#endif
 
         delete reader;
         reader = new DecodingWavFileReader
--- a/data/fileio/CodedAudioFileReader.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/fileio/CodedAudioFileReader.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -146,11 +146,27 @@
             }
             fileInfo.samplerate = fileRate;
             fileInfo.channels = m_channelCount;
-            
-            // No point in writing 24-bit or float; generally this
-            // class is used for decoding files that have come from a
-            // 16 bit source or that decode to only 16 bits anyway.
-            fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
+
+            // Previously we were writing SF_FORMAT_PCM_16 and in a
+            // comment I wrote: "No point in writing 24-bit or float;
+            // generally this class is used for decoding files that
+            // have come from a 16 bit source or that decode to only
+            // 16 bits anyway." That was naive -- we want to preserve
+            // the original values to the same float precision that we
+            // use internally. Saving PCM_16 obviously doesn't
+            // preserve values for sources at bit depths greater than
+            // 16, but it also doesn't always do so for sources at bit
+            // depths less than 16.
+            //
+            // (This came to light with a bug in libsndfile 1.0.26,
+            // which always reports every file as non-seekable, so
+            // that coded readers were being used even for WAV
+            // files. This changed the values that came from PCM_8 WAV
+            // sources, breaking Sonic Annotator's output comparison
+            // tests.)
+            //
+            // So: now we write floats.
+            fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
     
             m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
                                           SFM_WRITE, &fileInfo);
--- a/data/fileio/CoreAudioFileReader.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/fileio/CoreAudioFileReader.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -57,7 +57,7 @@
 }
 
 CoreAudioFileReader::CoreAudioFileReader(FileSource source,
-                                         DecodeMode decodeMode,
+                                         DecodeMode /* decodeMode */,
                                          CacheMode mode,
                                          sv_samplerate_t targetRate,
                                          bool normalised,
@@ -180,7 +180,7 @@
         // buffers are interleaved unless specified otherwise
         addSamplesToDecodeCache((float *)m_d->buffer.mBuffers[0].mData, framesRead);
 
-        if (framesRead < m_d->blockSize) break;
+        if ((int)framesRead < m_d->blockSize) break;
     }
 
     finishDecodeCache();
@@ -196,7 +196,7 @@
 
     if (m_d->valid) {
         ExtAudioFileDispose(m_d->file);
-        delete[] m_d->buffer.mBuffers[0].mData;
+        delete[] (float *)(m_d->buffer.mBuffers[0].mData);
     }
 
     delete m_d;
--- a/data/fileio/MP3FileReader.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/fileio/MP3FileReader.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -32,6 +32,7 @@
 #ifdef HAVE_ID3TAG
 #include <id3tag.h>
 #endif
+
 //#define DEBUG_ID3TAG 1
 
 #include <QFileInfo>
@@ -178,7 +179,7 @@
     id3_tag *tag = id3_file_tag(file);
     if (!tag) {
 #ifdef DEBUG_ID3TAG
-        SVDEBUG << "MP3FileReader::loadTags: No ID3 tag found" << endl;
+        cerr << "MP3FileReader::loadTags: No ID3 tag found" << endl;
 #endif
         id3_file_close(file);
         return;
@@ -201,7 +202,7 @@
 
 #else
 #ifdef DEBUG_ID3TAG
-    SVDEBUG << "MP3FileReader::loadTags: ID3 tag support not compiled in"
+    cerr << "MP3FileReader::loadTags: ID3 tag support not compiled in"
               << endl;
 #endif
 #endif
@@ -216,20 +217,20 @@
     id3_frame *frame = id3_tag_findframe(tag, name, 0);
     if (!frame) {
 #ifdef DEBUG_ID3TAG
-        SVDEBUG << "MP3FileReader::loadTags: No \"" << name << "\" in ID3 tag" << endl;
+        cerr << "MP3FileReader::loadTags: No \"" << name << "\" in ID3 tag" << endl;
 #endif
         return "";
     }
         
     if (frame->nfields < 2) {
-        SVDEBUG << "MP3FileReader::loadTags: WARNING: Not enough fields (" << frame->nfields << ") for \"" << name << "\" in ID3 tag" << endl;
+        cerr << "MP3FileReader::loadTags: WARNING: Not enough fields (" << frame->nfields << ") for \"" << name << "\" in ID3 tag" << endl;
         return "";
     }
 
     unsigned int nstrings = id3_field_getnstrings(&frame->fields[1]);
     if (nstrings == 0) {
 #ifdef DEBUG_ID3TAG
-        SVDEBUG << "MP3FileReader::loadTags: No strings for \"" << name << "\" in ID3 tag" << endl;
+        cerr << "MP3FileReader::loadTags: No strings for \"" << name << "\" in ID3 tag" << endl;
 #endif
         return "";
     }
@@ -237,7 +238,7 @@
     id3_ucs4_t const *ustr = id3_field_getstrings(&frame->fields[1], 0);
     if (!ustr) {
 #ifdef DEBUG_ID3TAG
-        SVDEBUG << "MP3FileReader::loadTags: Invalid or absent data for \"" << name << "\" in ID3 tag" << endl;
+        cerr << "MP3FileReader::loadTags: Invalid or absent data for \"" << name << "\" in ID3 tag" << endl;
 #endif
         return "";
     }
@@ -252,7 +253,7 @@
     free(u8str);
 
 #ifdef DEBUG_ID3TAG
-	SVDEBUG << "MP3FileReader::loadTags: tag \"" << name << "\" -> \""
+	cerr << "MP3FileReader::loadTags: tag \"" << name << "\" -> \""
 	<< rv << "\"" << endl;
 #endif
 
--- a/data/fileio/MatrixFile.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/fileio/MatrixFile.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -176,8 +176,6 @@
 
     QMutexLocker locker(&m_createMutex);
 
-    delete m_setColumns;
-
     if (m_fileName != "") {
 
         if (--m_refcount[m_fileName] == 0) {
@@ -208,7 +206,7 @@
 
     assert(m_mode == WriteOnly);
 
-    m_setColumns = new ResizeableBitset(m_width);
+    m_setColumns.resize(m_width, false);
     
     off_t off = m_headerSize + (m_width * m_height * m_cellSize) + m_width;
 
@@ -316,7 +314,7 @@
 MatrixFile::haveSetColumnAt(int x) const
 {
     if (m_mode == WriteOnly) {
-        return m_setColumns->get(x);
+        return m_setColumns[x];
     }
 
     if (m_readyToReadColumn >= 0 &&
@@ -398,9 +396,10 @@
         throw FileOperationFailed(m_fileName, "write");
     }
 
-    m_setColumns->set(x);
+    m_setColumns[x] = true;
     if (m_autoClose) {
-        if (m_setColumns->isAllOn()) {
+        if (std::all_of(m_setColumns.begin(), m_setColumns.end(),
+                        [](bool c) { return c; })) {
 #ifdef DEBUG_MATRIX_FILE
             cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): All columns set: auto-closing" << endl;
 #endif
--- a/data/fileio/MatrixFile.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/fileio/MatrixFile.h	Fri Aug 19 15:58:57 2016 +0100
@@ -13,10 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _MATRIX_FILE_CACHE_H_
-#define _MATRIX_FILE_CACHE_H_
-
-#include "base/ResizeableBitset.h"
+#ifndef MATRIX_FILE_H
+#define MATRIX_FILE_H
 
 #include "FileReadThread.h"
 
@@ -91,7 +89,7 @@
     int     m_headerSize;
     QString m_fileName;
 
-    ResizeableBitset *m_setColumns; // only in writer
+    std::vector<bool> m_setColumns; // only populated in writer
     bool m_autoClose;
 
     // In reader: if this is >= 0, we can read that column directly
--- a/data/fileio/WavFileReader.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/fileio/WavFileReader.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -62,20 +62,26 @@
 
         m_seekable = (m_fileInfo.seekable != 0);
 
-        // Our m_seekable reports whether a file is rapidly seekable,
-        // so things like Ogg don't qualify. We cautiously report
-        // every file type of "at least" the historical period of Ogg
-        // or FLAC as non-seekable.
         int type = m_fileInfo.format & SF_FORMAT_TYPEMASK;
-//        cerr << "WavFileReader: format type is " << type << " (flac, ogg are " << SF_FORMAT_FLAC << ", " << SF_FORMAT_OGG << ")" << endl;
+        int subtype = m_fileInfo.format & SF_FORMAT_SUBMASK;
+
         if (type >= SF_FORMAT_FLAC || type >= SF_FORMAT_OGG) {
-//            cerr << "WavFileReader: Recording as non-seekable" << endl;
+            // Our m_seekable reports whether a file is rapidly
+            // seekable, so things like Ogg don't qualify. We
+            // cautiously report every file type of "at least" the
+            // historical period of Ogg or FLAC as non-seekable.
             m_seekable = false;
+        } else if (type == SF_FORMAT_WAV && subtype <= SF_FORMAT_DOUBLE) {
+            // libsndfile 1.0.26 has a bug (subsequently fixed in the
+            // repo) that causes all files to be reported as
+            // non-seekable. We know that certain common file types
+            // are definitely seekable so, again cautiously, identify
+            // and mark those (basically only non-adaptive WAVs).
+            m_seekable = true;
         }
     }
 
-//    cerr << "WavFileReader: Frame count " << m_frameCount << ", channel count " << m_channelCount << ", sample rate " << m_sampleRate << ", seekable " << m_seekable << endl;
-
+//    cerr << "WavFileReader: Filename " << m_path << ", frame count " << m_frameCount << ", channel count " << m_channelCount << ", sample rate " << m_sampleRate << ", format " << m_fileInfo.format << ", seekable " << m_fileInfo.seekable << " adjusted to " << m_seekable << endl;
 }
 
 WavFileReader::~WavFileReader()
--- a/data/fileio/test/test.pro	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/fileio/test/test.pro	Fri Aug 19 15:58:57 2016 +0100
@@ -1,6 +1,8 @@
 
 TEMPLATE = app
 
+INCLUDEPATH += ../../../../vamp-plugin-sdk
+
 LIBS += -L../../.. -L../../../../dataquay -L../../../release -L../../../../dataquay/release -lsvcore -ldataquay
 
 win32-g++ {
@@ -27,7 +29,7 @@
 
     DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG HAVE_PORTAUDIO
 
-    LIBS += -lbz2 -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0
+    LIBS += -lbz2 -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0
 
     win* {
         LIBS += -llo -lwinmm -lws2_32
@@ -36,6 +38,9 @@
         DEFINES += HAVE_COREAUDIO
         LIBS += -framework CoreAudio -framework CoreMidi -framework AudioUnit -framework AudioToolbox -framework CoreFoundation -framework CoreServices -framework Accelerate
     }
+    linux* {
+        LIBS += -ldl
+    }
 }
 
 CONFIG += qt thread warn_on stl rtti exceptions console c++11
--- a/data/model/Dense3DModelPeakCache.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/Dense3DModelPeakCache.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -17,13 +17,11 @@
 
 #include "base/Profiler.h"
 
-Dense3DModelPeakCache::Dense3DModelPeakCache(DenseThreeDimensionalModel *source,
+Dense3DModelPeakCache::Dense3DModelPeakCache(const DenseThreeDimensionalModel *source,
 					     int columnsPerPeak) :
     m_source(source),
-    m_resolution(columnsPerPeak)
+    m_columnsPerPeak(columnsPerPeak)
 {
-    m_coverage.resize(1); // otherwise it is simply invalid
-
     m_cache = new EditableDenseThreeDimensionalModel
         (source->getSampleRate(),
          getResolution(),
@@ -43,20 +41,6 @@
     delete m_cache;
 }
 
-bool
-Dense3DModelPeakCache::isColumnAvailable(int column) const
-{
-    if (!m_source) return false;
-    if (haveColumn(column)) return true;
-    for (int i = m_resolution; i > 0; ) {
-        --i;
-        if (!m_source->isColumnAvailable(column * m_resolution + i)) {
-            return false;
-        }
-    }
-    return true;
-}
-
 Dense3DModelPeakCache::Column
 Dense3DModelPeakCache::getColumn(int column) const
 {
@@ -81,9 +65,9 @@
     if (m_coverage.size() > 0) {
         // The last peak may have come from an incomplete read, which
         // may since have been filled, so reset it
-        m_coverage.reset(m_coverage.size()-1);
+        m_coverage[m_coverage.size()-1] = false;
     }
-    m_coverage.resize(getWidth()); // retaining data
+    m_coverage.resize(getWidth(), false); // retaining data
 }
 
 void
@@ -95,7 +79,7 @@
 bool
 Dense3DModelPeakCache::haveColumn(int column) const
 {
-    return column < (int)m_coverage.size() && m_coverage.get(column);
+    return in_range_for(m_coverage, column) && m_coverage[column];
 }
 
 void
@@ -103,26 +87,43 @@
 {
     Profiler profiler("Dense3DModelPeakCache::fillColumn");
 
-    if (column >= (int)m_coverage.size()) {
-        // see note in sourceModelChanged
-        if (m_coverage.size() > 0) m_coverage.reset(m_coverage.size()-1);
-        m_coverage.resize(column + 1);
+    if (!in_range_for(m_coverage, column)) {
+        if (m_coverage.size() > 0) {
+            // The last peak may have come from an incomplete read, which
+            // may since have been filled, so reset it
+            m_coverage[m_coverage.size()-1] = false;
+        }
+        m_coverage.resize(column + 1, false);
     }
 
+    int sourceWidth = m_source->getWidth();
+    
     Column peak;
-    for (int i = 0; i < int(m_resolution); ++i) {
-        Column here = m_source->getColumn(column * m_resolution + i);
+    int n = 0;
+    for (int i = 0; i < m_columnsPerPeak; ++i) {
+
+        int sourceColumn = column * m_columnsPerPeak + i;
+        if (sourceColumn >= sourceWidth) break;
+        
+        Column here = m_source->getColumn(sourceColumn);
+
+//        cerr << "Dense3DModelPeakCache::fillColumn(" << column << "): source col "
+//             << sourceColumn << " of " << sourceWidth
+//             << " returned " << here.size() << " elts" << endl;
+        
         if (i == 0) {
             peak = here;
+            n = int(peak.size());
         } else {
-            for (int j = 0; j < (int)peak.size() && j < (int)here.size(); ++j) {
-                if (here[j] > peak[j]) peak[j] = here[j];
+            int m = std::min(n, int(here.size()));
+            for (int j = 0; j < m; ++j) {
+                peak[j] = std::max(here[j], peak[j]);
             }
         }
     }
 
     m_cache->setColumn(column, peak);
-    m_coverage.set(column);
+    m_coverage[column] = true;
 }
 
 
--- a/data/model/Dense3DModelPeakCache.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/Dense3DModelPeakCache.h	Fri Aug 19 15:58:57 2016 +0100
@@ -13,19 +13,18 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _DENSE_3D_MODEL_PEAK_CACHE_H_
-#define _DENSE_3D_MODEL_PEAK_CACHE_H_
+#ifndef DENSE_3D_MODEL_PEAK_CACHE_H
+#define DENSE_3D_MODEL_PEAK_CACHE_H
 
 #include "DenseThreeDimensionalModel.h"
 #include "EditableDenseThreeDimensionalModel.h"
-#include "base/ResizeableBitset.h"
 
 class Dense3DModelPeakCache : public DenseThreeDimensionalModel
 {
     Q_OBJECT
 
 public:
-    Dense3DModelPeakCache(DenseThreeDimensionalModel *source,
+    Dense3DModelPeakCache(const DenseThreeDimensionalModel *source,
                           int columnsPerPeak);
     ~Dense3DModelPeakCache();
 
@@ -46,11 +45,20 @@
     }
 
     virtual int getResolution() const {
-        return m_source->getResolution() * m_resolution;
+        return m_source->getResolution() * m_columnsPerPeak;
     }
 
+    virtual int getColumnsPerPeak() const {
+        return m_columnsPerPeak;
+    }
+    
     virtual int getWidth() const {
-        return m_source->getWidth() / m_resolution + 1;
+        int sourceWidth = m_source->getWidth();
+        if ((sourceWidth % m_columnsPerPeak) == 0) {
+            return sourceWidth / m_columnsPerPeak;
+        } else {
+            return sourceWidth / m_columnsPerPeak + 1;
+        }
     }
 
     virtual int getHeight() const {
@@ -65,8 +73,6 @@
         return m_source->getMaximumLevel();
     }
 
-    virtual bool isColumnAvailable(int column) const;
-
     virtual Column getColumn(int column) const;
 
     virtual float getValueAt(int column, int n) const;
@@ -90,10 +96,11 @@
     void sourceModelAboutToBeDeleted();
 
 private:
-    DenseThreeDimensionalModel *m_source;
+    const DenseThreeDimensionalModel *m_source;
     mutable EditableDenseThreeDimensionalModel *m_cache;
-    mutable ResizeableBitset m_coverage;
-    int m_resolution;
+    mutable std::vector<bool> m_coverage; // must be bool, for space efficiency
+                                          // (vector of bool uses 1-bit elements)
+    int m_columnsPerPeak;
 
     bool haveColumn(int column) const;
     void fillColumn(int column) const;
--- a/data/model/DenseThreeDimensionalModel.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/DenseThreeDimensionalModel.h	Fri Aug 19 15:58:57 2016 +0100
@@ -18,6 +18,7 @@
 
 #include "Model.h"
 #include "TabularModel.h"
+#include "base/ColumnOp.h"
 #include "base/ZoomConstraint.h"
 #include "base/RealTime.h"
 
@@ -55,16 +56,7 @@
      */
     virtual float getMaximumLevel() const = 0;
 
-    /**
-     * Return true if there are data available for the given column.
-     * This should return true only if getColumn(column) would not
-     * have to do any substantial work to calculate its return values.
-     * If this function returns false, it may still be possible to
-     * retrieve the column, but its values may have to be calculated.
-     */
-    virtual bool isColumnAvailable(int column) const = 0;
-
-    typedef QVector<float> Column;
+    typedef ColumnOp::Column Column;
 
     /**
      * Get data from the given column of bin values.
@@ -153,12 +145,12 @@
     {
         switch (column) {
         case 0: {
-            RealTime rt = RealTime::frame2RealTime(row * getResolution(),
-                                                   getSampleRate());
+            RealTime rt = RealTime::frame2RealTime
+                (row * getResolution() + getStartFrame(), getSampleRate());
             return rt.toText().c_str();
         }
         case 1:
-            return int(row * getResolution());
+            return int(row * getResolution() + getStartFrame());
         default:
             return getValueAt(row, column - 2);
         }
@@ -172,10 +164,10 @@
     }
 
     virtual sv_frame_t getFrameForRow(int row) const {
-        return sv_frame_t(row) * getResolution();
+        return sv_frame_t(row) * getResolution() + getStartFrame();
     }
     virtual int getRowForFrame(sv_frame_t frame) const {
-        return int(frame / getResolution());
+        return int((frame - getStartFrame()) / getResolution());
     }
 
 protected:
--- a/data/model/EditableDenseThreeDimensionalModel.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/EditableDenseThreeDimensionalModel.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -96,7 +96,7 @@
 int
 EditableDenseThreeDimensionalModel::getWidth() const
 {
-    return m_data.size();
+    return int(m_data.size());
 }
 
 int
@@ -139,15 +139,15 @@
 EditableDenseThreeDimensionalModel::getColumn(int index) const
 {
     QReadLocker locker(&m_lock);
-    if (index < 0 || index >= m_data.size()) return Column();
-    return expandAndRetrieve(index);
+    if (in_range_for(m_data, index)) return expandAndRetrieve(index);
+    else return Column();
 }
 
 float
 EditableDenseThreeDimensionalModel::getValueAt(int index, int n) const
 {
     Column c = getColumn(index);
-    if (int(n) < c.size()) return c.at(n);
+    if (in_range_for(c, n)) return c.at(n);
     return m_minimum;
 }
 
@@ -157,7 +157,7 @@
 EditableDenseThreeDimensionalModel::truncateAndStore(int index,
                                                      const Column &values)
 {
-    assert(int(index) < m_data.size());
+    assert(in_range_for(m_data, index));
 
     //cout << "truncateAndStore(" << index << ", " << values.size() << ")" << endl;
 
@@ -169,7 +169,7 @@
     m_trunc[index] = 0;
     if (index == 0 ||
         m_compression == NoCompression ||
-        values.size() != int(m_yBinCount)) {
+        int(values.size()) != m_yBinCount) {
 //        given += values.size();
 //        stored += values.size();
         m_data[index] = values;
@@ -206,7 +206,7 @@
     Column p = expandAndRetrieve(index - tdist);
     int h = m_yBinCount;
 
-    if (p.size() == h && tdist <= maxdist) {
+    if (int(p.size()) == h && tdist <= maxdist) {
 
         int bcount = 0, tcount = 0;
         if (!known || !top) {
@@ -282,7 +282,7 @@
     int tdist = trunc;
     if (trunc < 0) { top = false; tdist = -trunc; }
     Column p = expandAndRetrieve(index - tdist);
-    int psize = p.size(), csize = c.size();
+    int psize = int(p.size()), csize = int(c.size());
     if (psize != m_yBinCount) {
         cerr << "WARNING: EditableDenseThreeDimensionalModel::expandAndRetrieve: Trying to expand from incorrectly sized column" << endl;
     }
@@ -291,10 +291,6 @@
             c.push_back(p.at(i));
         }
     } else {
-        // push_front is very slow on QVector -- but not enough to
-        // make it desirable to choose a different container, since
-        // QVector has all the other advantages for us.  easier to
-        // write the whole array out to a new vector
         Column cc(psize);
         for (int i = 0; i < psize - csize; ++i) {
             cc[i] = p.at(i);
@@ -313,7 +309,7 @@
 {
     QWriteLocker locker(&m_lock);
 
-    while (int(index) >= m_data.size()) {
+    while (index >= int(m_data.size())) {
 	m_data.push_back(Column());
         m_trunc.push_back(0);
     }
@@ -322,7 +318,7 @@
 
 //    if (values.size() > m_yBinCount) m_yBinCount = values.size();
 
-    for (int i = 0; i < values.size(); ++i) {
+    for (int i = 0; in_range_for(values, i); ++i) {
         float value = values[i];
         if (ISNAN(value) || ISINF(value)) {
             continue;
@@ -432,13 +428,13 @@
     
     for (int i = 0; i < 10; ++i) {
         int index = i * 10;
-        if (index < m_data.size()) {
+        if (in_range_for(m_data, index)) {
             const Column &c = m_data.at(index);
-            while (c.size() > int(sample.size())) {
+            while (c.size() > sample.size()) {
                 sample.push_back(0.0);
                 n.push_back(0);
             }
-            for (int j = 0; j < c.size(); ++j) {
+            for (int j = 0; in_range_for(c, j); ++j) {
                 sample[j] += c.at(j);
                 ++n[j];
             }
@@ -486,9 +482,9 @@
 {
     QReadLocker locker(&m_lock);
     QString s;
-    for (int i = 0; i < m_data.size(); ++i) {
+    for (int i = 0; in_range_for(m_data, i); ++i) {
         QStringList list;
-	for (int j = 0; j < m_data.at(i).size(); ++j) {
+	for (int j = 0; in_range_for(m_data.at(i), j); ++j) {
             list << QString("%1").arg(m_data.at(i).at(j));
         }
         s += list.join(delimiter) + "\n";
@@ -501,11 +497,11 @@
 {
     QReadLocker locker(&m_lock);
     QString s;
-    for (int i = 0; i < m_data.size(); ++i) {
+    for (int i = 0; in_range_for(m_data, i); ++i) {
         sv_frame_t fr = m_startFrame + i * m_resolution;
         if (fr >= f0 && fr < f1) {
             QStringList list;
-            for (int j = 0; j < m_data.at(i).size(); ++j) {
+            for (int j = 0; in_range_for(m_data.at(i), j); ++j) {
                 list << QString("%1").arg(m_data.at(i).at(j));
             }
             s += list.join(delimiter) + "\n";
--- a/data/model/EditableDenseThreeDimensionalModel.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/EditableDenseThreeDimensionalModel.h	Fri Aug 19 15:58:57 2016 +0100
@@ -105,11 +105,6 @@
     virtual void setMaximumLevel(float sz);
 
     /**
-     * Return true if there are data available for the given column.
-     */
-    virtual bool isColumnAvailable(int x) const { return x < getWidth(); }
-
-    /**
      * Get the set of bin values at the given column.
      */
     virtual Column getColumn(int x) const;
@@ -194,7 +189,7 @@
                        QString extraAttributes = "") const;
 
 protected:
-    typedef QVector<Column> ValueMatrix;
+    typedef std::vector<Column> ValueMatrix;
     ValueMatrix m_data;
 
     // m_trunc is used for simple compression.  If at least the top N
--- a/data/model/FFTModel.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/FFTModel.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -98,9 +98,21 @@
 {
     auto cplx = getFFTColumn(x);
     Column col;
-    col.reserve(int(cplx.size()));
+    col.reserve(cplx.size());
     for (auto c: cplx) col.push_back(abs(c));
-    return col;
+    return move(col);
+}
+
+FFTModel::Column
+FFTModel::getPhases(int x) const
+{
+    auto cplx = getFFTColumn(x);
+    Column col;
+    col.reserve(cplx.size());
+    for (auto c: cplx) {
+        col.push_back(arg(c));
+    }
+    return move(col);
 }
 
 float
@@ -116,7 +128,8 @@
 {
     Column col(getColumn(x));
     float max = 0.f;
-    for (int i = 0; i < col.size(); ++i) {
+    int n = int(col.size());
+    for (int i = 0; i < n; ++i) {
         if (col[i] > max) max = col[i];
     }
     return max;
@@ -138,13 +151,6 @@
 }
 
 bool
-FFTModel::isColumnAvailable(int) const
-{
-    //!!!
-    return true;
-}
-
-bool
 FFTModel::getMagnitudesAt(int x, float *values, int minbin, int count) const
 {
     if (count == 0) count = getHeight();
@@ -155,23 +161,6 @@
     return true;
 }
 
-float
-FFTModel::getNormalizedMagnitudesAt(int x, float *values, int minbin, int count) const
-{
-    if (!getMagnitudesAt(x, values, minbin, count)) return false;
-    if (count == 0) count = getHeight();
-    float max = 0.f;
-    for (int i = 0; i < count; ++i) {
-        if (values[i] > max) max = values[i];
-    }
-    if (max > 0.f) {
-        for (int i = 0; i < count; ++i) {
-            values[i] /= max;
-        }
-    }
-    return max;
-}
-
 bool
 FFTModel::getPhasesAt(int x, float *values, int minbin, int count) const
 {
@@ -313,7 +302,7 @@
     }
     m_cached.push_back(sc);
 
-    return col;
+    return move(col);
 }
 
 bool
@@ -354,7 +343,7 @@
 }
 
 FFTModel::PeakLocationSet
-FFTModel::getPeaks(PeakPickType type, int x, int ymin, int ymax)
+FFTModel::getPeaks(PeakPickType type, int x, int ymin, int ymax) const
 {
     Profiler profiler("FFTModel::getPeaks");
 
@@ -388,10 +377,11 @@
     }
 
     Column values = getColumn(x);
+    int nv = int(values.size());
 
     float mean = 0.f;
-    for (int i = 0; i < values.size(); ++i) mean += values[i];
-    if (values.size() > 0) mean = mean / float(values.size());
+    for (int i = 0; i < nv; ++i) mean += values[i];
+    if (nv > 0) mean = mean / float(values.size());
     
     // For peak picking we use a moving median window, picking the
     // highest value within each continuous region of values that
@@ -412,8 +402,8 @@
     else binmin = 0;
 
     int binmax;
-    if (ymax + halfWin < values.size()) binmax = ymax + halfWin;
-    else binmax = values.size()-1;
+    if (ymax + halfWin < nv) binmax = ymax + halfWin;
+    else binmax = nv - 1;
 
     int prevcentre = 0;
 
@@ -434,8 +424,8 @@
         int actualSize = int(window.size());
 
         if (type == MajorPitchAdaptivePeaks) {
-            if (ymax + halfWin < values.size()) binmax = ymax + halfWin;
-            else binmax = values.size()-1;
+            if (ymax + halfWin < nv) binmax = ymax + halfWin;
+            else binmax = nv - 1;
         }
 
         deque<float> sorted(window);
@@ -455,7 +445,7 @@
                 inrange.push_back(centrebin);
             }
 
-            if (centre <= median || centrebin+1 == values.size()) {
+            if (centre <= median || centrebin+1 == nv) {
                 if (!inrange.empty()) {
                     int peakbin = 0;
                     float peakval = 0.f;
@@ -501,7 +491,7 @@
 
 FFTModel::PeakSet
 FFTModel::getPeakFrequencies(PeakPickType type, int x,
-                             int ymin, int ymax)
+                             int ymin, int ymax) const
 {
     Profiler profiler("FFTModel::getPeakFrequencies");
 
--- a/data/model/FFTModel.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/FFTModel.h	Fri Aug 19 15:58:57 2016 +0100
@@ -76,6 +76,7 @@
     virtual float getMinimumLevel() const { return 0.f; } // Can't provide
     virtual float getMaximumLevel() const { return 1.f; } // Can't provide
     virtual Column getColumn(int x) const; // magnitudes
+    virtual Column getPhases(int x) const;
     virtual QString getBinName(int n) const;
     virtual bool shouldUseLogValueScale() const { return true; }
     virtual int getCompletion() const {
@@ -95,14 +96,14 @@
     int getWindowSize() const { return m_windowSize; }
     int getWindowIncrement() const { return m_windowIncrement; }
     int getFFTSize() const { return m_fftSize; }
+
+//!!! review which of these are ever actually called
     
     float getMagnitudeAt(int x, int y) const;
     float getMaximumMagnitudeAt(int x) const;
     float getPhaseAt(int x, int y) const;
     void getValuesAt(int x, int y, float &real, float &imaginary) const;
-    bool isColumnAvailable(int x) const;
     bool getMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) const;
-    float getNormalizedMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) const; // returns maximum of unnormalized magnitudes
     bool getPhasesAt(int x, float *values, int minbin = 0, int count = 0) const;
     bool getValuesAt(int x, float *reals, float *imaginaries, int minbin = 0, int count = 0) const;
 
@@ -128,13 +129,13 @@
      * ymax is zero, getHeight()-1 will be used.
      */
     virtual PeakLocationSet getPeaks(PeakPickType type, int x,
-                                     int ymin = 0, int ymax = 0);
+                                     int ymin = 0, int ymax = 0) const;
 
     /**
      * Return locations and estimated stable frequencies of peak bins.
      */
     virtual PeakSet getPeakFrequencies(PeakPickType type, int x,
-                                       int ymin = 0, int ymax = 0);
+                                       int ymin = 0, int ymax = 0) const;
 
     QString getTypeName() const { return tr("FFT"); }
 
--- a/data/model/ReadOnlyWaveFileModel.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/ReadOnlyWaveFileModel.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -193,7 +193,7 @@
     // playback or input to transforms.
 
 #ifdef DEBUG_WAVE_FILE_MODEL
-    cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << ", " << buffer << endl;
+    cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << endl;
 #endif
 
     int channels = getChannelCount();
@@ -252,7 +252,7 @@
     // playback or input to transforms.
 
 #ifdef DEBUG_WAVE_FILE_MODEL
-    cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << ", " << buffer << endl;
+    cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << endl;
 #endif
 
     int channels = getChannelCount();
@@ -322,7 +322,7 @@
 
 void
 ReadOnlyWaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count,
-                            RangeBlock &ranges, int &blockSize) const
+                                    RangeBlock &ranges, int &blockSize) const
 {
     ranges.clear();
     if (!isOK()) return;
@@ -403,14 +403,12 @@
         blockSize = roundedBlockSize;
 
 	sv_frame_t cacheBlock, div;
-        
-	if (cacheType == 0) {
-	    cacheBlock = (1 << m_zoomConstraint.getMinCachePower());
-            div = (1 << power) / cacheBlock;
-	} else {
-	    cacheBlock = sv_frame_t((1 << m_zoomConstraint.getMinCachePower()) * sqrt(2.) + 0.01);
-            div = sv_frame_t(((1 << power) * sqrt(2.) + 0.01) / double(cacheBlock));
+
+        cacheBlock = (sv_frame_t(1) << m_zoomConstraint.getMinCachePower());
+	if (cacheType == 1) {
+	    cacheBlock = sv_frame_t(double(cacheBlock) * sqrt(2.) + 0.01);
 	}
+        div = blockSize / cacheBlock;
 
 	sv_frame_t startIndex = start / cacheBlock;
 	sv_frame_t endIndex = (start + count) / cacheBlock;
@@ -425,7 +423,7 @@
 	for (i = 0; i <= endIndex - startIndex; ) {
         
 	    sv_frame_t index = (i + startIndex) * channels + channel;
-	    if (index >= (sv_frame_t)cache.size()) break;
+	    if (!in_range_for(cache, index)) break;
             
             const Range &range = cache[index];
             if (range.max() > max || got == 0) max = range.max();
@@ -448,7 +446,7 @@
     }
 
 #ifdef DEBUG_WAVE_FILE_MODEL
-    SVDEBUG << "returning " << ranges.size() << " ranges" << endl;
+    cerr << "returning " << ranges.size() << " ranges" << endl;
 #endif
     return;
 }
@@ -573,7 +571,7 @@
                                         sqrt(2.) + 0.01));
     
     sv_frame_t frame = 0;
-    const sv_frame_t readBlockSize = 16384;
+    const sv_frame_t readBlockSize = 32768;
     vector<float> block;
 
     if (!m_model.isOK()) return;
@@ -583,7 +581,9 @@
 
     if (updating) {
         while (channels == 0 && !m_model.m_exiting) {
-//            SVDEBUG << "ReadOnlyWaveFileModel::fill: Waiting for channels..." << endl;
+#ifdef DEBUG_WAVE_FILE_MODEL
+            cerr << "ReadOnlyWaveFileModel::fill: Waiting for channels..." << endl;
+#endif
             sleep(1);
             channels = m_model.getChannelCount();
         }
@@ -604,38 +604,38 @@
         updating = m_model.m_reader->isUpdating();
         m_frameCount = m_model.getFrameCount();
 
-//        SVDEBUG << "ReadOnlyWaveFileModel::fill: frame = " << frame << ", count = " << m_frameCount << endl;
+        m_model.m_mutex.lock();
 
         while (frame < m_frameCount) {
 
-//            SVDEBUG << "ReadOnlyWaveFileModel::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl;
+            m_model.m_mutex.unlock();
+
+#ifdef DEBUG_WAVE_FILE_MODEL
+            cerr << "ReadOnlyWaveFileModel::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl;
+#endif
 
             if (updating && (frame + readBlockSize > m_frameCount)) break;
 
             block = m_model.m_reader->getInterleavedFrames(frame, readBlockSize);
 
-//            cerr << "block is " << block.size() << endl;
+            sv_frame_t gotBlockSize = block.size() / channels;
 
-            for (sv_frame_t i = 0; i < readBlockSize; ++i) {
+            m_model.m_mutex.lock();
+
+            for (sv_frame_t i = 0; i < gotBlockSize; ++i) {
 		
-                if (channels * i + channels > (int)block.size()) break;
-
                 for (int ch = 0; ch < channels; ++ch) {
 
                     sv_frame_t index = channels * i + ch;
                     float sample = block[index];
                     
-                    for (int cacheType = 0; cacheType < 2; ++cacheType) { // cache type
-                        
+                    for (int cacheType = 0; cacheType < 2; ++cacheType) {
                         sv_frame_t rangeIndex = ch * 2 + cacheType;
                         range[rangeIndex].sample(sample);
                         means[rangeIndex] += fabsf(sample);
                     }
                 }
 
-                //!!! this looks like a ludicrous way to do synchronisation
-                QMutexLocker locker(&m_model.m_mutex);
-
                 for (int cacheType = 0; cacheType < 2; ++cacheType) {
 
                     if (++count[cacheType] == cacheBlockSize[cacheType]) {
@@ -655,18 +655,16 @@
                 
                 ++frame;
             }
-            
+
             if (m_model.m_exiting) break;
-            
             m_fillExtent = frame;
         }
 
-//        cerr << "ReadOnlyWaveFileModel: inner loop ended" << endl;
-
+        m_model.m_mutex.unlock();
+            
         first = false;
         if (m_model.m_exiting) break;
         if (updating) {
-//            cerr << "sleeping..." << endl;
             sleep(1);
         }
     }
--- a/data/model/test/test.pro	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/test/test.pro	Fri Aug 19 15:58:57 2016 +0100
@@ -1,6 +1,8 @@
 
 TEMPLATE = app
 
+INCLUDEPATH += ../../../../vamp-plugin-sdk
+
 LIBS += -L../../.. -L../../../../dataquay -L../../../release -L../../../../dataquay/release -lsvcore -ldataquay
 
 win32-g++ {
@@ -27,7 +29,7 @@
 
     DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG HAVE_PORTAUDIO
 
-    LIBS += -lbz2 -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0
+    LIBS += -lbz2 -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0
 
     win* {
         LIBS += -llo -lwinmm -lws2_32
--- a/plugin/DSSIPluginFactory.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/plugin/DSSIPluginFactory.h	Fri Aug 19 15:58:57 2016 +0100
@@ -48,6 +48,10 @@
     DSSIPluginFactory();
     friend class RealTimePluginFactory;
 
+    virtual PluginScan::PluginType getPluginType() const {
+        return PluginScan::DSSIPlugin;
+    }
+
     virtual std::vector<QString> getPluginPath();
 
     virtual std::vector<QString> getLRDFPath(QString &baseUri);
--- a/plugin/FeatureExtractionPluginFactory.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/plugin/FeatureExtractionPluginFactory.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -21,6 +21,8 @@
 
 #include "system/System.h"
 
+#include "PluginScan.h"
+
 #include <QDir>
 #include <QFile>
 #include <QFileInfo>
@@ -30,6 +32,8 @@
 
 #include "base/Profiler.h"
 
+using namespace std;
+
 //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
 
 class PluginDeletionNotifyAdapter : public Vamp::HostExt::PluginWrapper {
@@ -77,25 +81,25 @@
     return instance(type);
 }
 
-std::vector<QString>
+vector<QString>
 FeatureExtractionPluginFactory::getPluginPath()
 {
     if (!m_pluginPath.empty()) return m_pluginPath;
 
-    std::vector<std::string> p = Vamp::PluginHostAdapter::getPluginPath();
+    vector<string> p = Vamp::PluginHostAdapter::getPluginPath();
     for (size_t i = 0; i < p.size(); ++i) m_pluginPath.push_back(p[i].c_str());
     return m_pluginPath;
 }
 
-std::vector<QString>
+vector<QString>
 FeatureExtractionPluginFactory::getAllPluginIdentifiers()
 {
     FeatureExtractionPluginFactory *factory;
-    std::vector<QString> rv;
+    vector<QString> rv;
     
     factory = instance("vamp");
     if (factory) {
-	std::vector<QString> tmp = factory->getPluginIdentifiers();
+	vector<QString> tmp = factory->getPluginIdentifiers();
 	for (size_t i = 0; i < tmp.size(); ++i) {
 //            cerr << "identifier: " << tmp[i] << endl;
 	    rv.push_back(tmp[i]);
@@ -108,103 +112,84 @@
     return rv;
 }
 
-std::vector<QString>
+vector<QString>
 FeatureExtractionPluginFactory::getPluginIdentifiers()
 {
     Profiler profiler("FeatureExtractionPluginFactory::getPluginIdentifiers");
 
-    std::vector<QString> rv;
-    std::vector<QString> path = getPluginPath();
+    vector<QString> rv;
+
+    QStringList candidates = PluginScan::getInstance()->getCandidateLibrariesFor
+        (PluginScan::VampPlugin);
     
-    for (std::vector<QString>::iterator i = path.begin(); i != path.end(); ++i) {
+    for (QString soname : candidates) {
+
+        void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
+            
+        if (!libraryHandle) {
+            cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl;
+            continue;
+        }
+
+        VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
+            DLSYM(libraryHandle, "vampGetPluginDescriptor");
+
+        if (!fn) {
+            cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl;
+            if (DLCLOSE(libraryHandle) != 0) {
+                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
+            }
+            continue;
+        }
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: scanning directory " << i-<< endl;
+            cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl;
 #endif
 
-	QDir pluginDir(*i, PLUGIN_GLOB,
-                       QDir::Name | QDir::IgnoreCase,
-                       QDir::Files | QDir::Readable);
+        const VampPluginDescriptor *descriptor = 0;
+        int index = 0;
 
-	for (unsigned int j = 0; j < pluginDir.count(); ++j) {
+        map<string, int> known;
+        bool ok = true;
 
-            QString soname = pluginDir.filePath(pluginDir[j]);
+        while ((descriptor = fn(VAMP_API_VERSION, index))) {
 
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-            SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: trying potential library " << soname << endl;
-#endif
-
-            void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
-            
-            if (!libraryHandle) {
-                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl;
-                continue;
+            if (known.find(descriptor->identifier) != known.end()) {
+                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Plugin library "
+                     << soname
+                     << " returns the same plugin identifier \""
+                     << descriptor->identifier << "\" at indices "
+                     << known[descriptor->identifier] << " and "
+                     << index << endl;
+                    cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
+                ok = false;
+                break;
+            } else {
+                known[descriptor->identifier] = index;
             }
 
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-            SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: It's a library all right, checking for descriptor" << endl;
-#endif
+            ++index;
+        }
 
-            VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
-                DLSYM(libraryHandle, "vampGetPluginDescriptor");
+        if (ok) {
 
-            if (!fn) {
-                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl;
-                if (DLCLOSE(libraryHandle) != 0) {
-                    cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
-                }
-                continue;
-            }
-
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-            SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl;
-#endif
-
-            const VampPluginDescriptor *descriptor = 0;
-            int index = 0;
-
-            std::map<std::string, int> known;
-            bool ok = true;
+            index = 0;
 
             while ((descriptor = fn(VAMP_API_VERSION, index))) {
 
-                if (known.find(descriptor->identifier) != known.end()) {
-                    cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Plugin library "
-                              << soname
-                              << " returns the same plugin identifier \""
-                              << descriptor->identifier << "\" at indices "
-                              << known[descriptor->identifier] << " and "
-                              << index << endl;
-                    SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
-                    ok = false;
-                    break;
-                } else {
-                    known[descriptor->identifier] = index;
-                }
-
+                QString id = PluginIdentifier::createIdentifier
+                    ("vamp", soname, descriptor->identifier);
+                rv.push_back(id);
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+                cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl;
+#endif
                 ++index;
             }
-
-            if (ok) {
-
-                index = 0;
-
-                while ((descriptor = fn(VAMP_API_VERSION, index))) {
-
-                    QString id = PluginIdentifier::createIdentifier
-                        ("vamp", soname, descriptor->identifier);
-                    rv.push_back(id);
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-                    SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl;
-#endif
-                    ++index;
-                }
-            }
+        }
             
-            if (DLCLOSE(libraryHandle) != 0) {
-                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
-            }
-	}
+        if (DLCLOSE(libraryHandle) != 0) {
+            cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
+        }
     }
 
     generateTaxonomy();
@@ -218,7 +203,7 @@
     QString file = "";
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-    SVDEBUG << "FeatureExtractionPluginFactory::findPluginFile(\""
+    cerr << "FeatureExtractionPluginFactory::findPluginFile(\""
               << soname << "\", \"" << inDir << "\")"
               << endl;
 #endif
@@ -235,7 +220,7 @@
         if (QFileInfo(file).exists() && QFileInfo(file).isFile()) {
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-            SVDEBUG << "FeatureExtractionPluginFactory::findPluginFile: "
+            cerr << "FeatureExtractionPluginFactory::findPluginFile: "
                       << "found trivially at " << file << endl;
 #endif
 
@@ -247,7 +232,7 @@
             if (QFileInfo(file).baseName() == QFileInfo(soname).baseName()) {
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-                SVDEBUG << "FeatureExtractionPluginFactory::findPluginFile: "
+                cerr << "FeatureExtractionPluginFactory::findPluginFile: "
                           << "found \"" << soname << "\" at " << file << endl;
 #endif
 
@@ -256,7 +241,7 @@
         }
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        SVDEBUG << "FeatureExtractionPluginFactory::findPluginFile (with dir): "
+        cerr << "FeatureExtractionPluginFactory::findPluginFile (with dir): "
                   << "not found" << endl;
 #endif
 
@@ -268,7 +253,7 @@
 
         if (fi.isAbsolute() && fi.exists() && fi.isFile()) {
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-            SVDEBUG << "FeatureExtractionPluginFactory::findPluginFile: "
+            cerr << "FeatureExtractionPluginFactory::findPluginFile: "
                       << "found trivially at " << soname << endl;
 #endif
             return soname;
@@ -279,8 +264,8 @@
             if (file != "") return file;
         }
 
-        std::vector<QString> path = getPluginPath();
-        for (std::vector<QString>::iterator i = path.begin();
+        vector<QString> path = getPluginPath();
+        for (vector<QString>::iterator i = path.begin();
              i != path.end(); ++i) {
             if (*i != "") {
                 file = findPluginFile(soname, *i);
@@ -289,7 +274,7 @@
         }
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        SVDEBUG << "FeatureExtractionPluginFactory::findPluginFile: "
+        cerr << "FeatureExtractionPluginFactory::findPluginFile: "
                   << "not found" << endl;
 #endif
 
@@ -312,7 +297,9 @@
     QString type, soname, label;
     PluginIdentifier::parseIdentifier(identifier, type, soname, label);
     if (type != "vamp") {
-	SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl;
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl;
+#endif
 	return 0;
     }
 
@@ -324,7 +311,7 @@
     } else if (found != soname) {
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl;
+        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl;
         cerr << soname << " -> " << found << endl;
 #endif
 
@@ -343,7 +330,7 @@
         DLSYM(libraryHandle, "vampGetPluginDescriptor");
     
     if (!fn) {
-        SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl;
+        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl;
         goto done;
     }
 
@@ -375,7 +362,9 @@
         }
     }
 
-//    SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl;
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+    cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl;
+#endif
     
     return rv;
 }
@@ -385,7 +374,9 @@
 {
     void *handle = m_handleMap[plugin];
     if (handle) {
-//        SVDEBUG << "unloading library " << handle << " for plugin " << plugin << endl;
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+        cerr << "unloading library " << handle << " for plugin " << plugin << endl;
+#endif
         DLCLOSE(handle);
     }
     m_handleMap.erase(plugin);
@@ -400,8 +391,8 @@
 void
 FeatureExtractionPluginFactory::generateTaxonomy()
 {
-    std::vector<QString> pluginPath = getPluginPath();
-    std::vector<QString> path;
+    vector<QString> pluginPath = getPluginPath();
+    vector<QString> path;
 
     for (size_t i = 0; i < pluginPath.size(); ++i) {
 	if (pluginPath[i].contains("/lib/")) {
--- a/plugin/FeatureExtractionPluginFactory.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/plugin/FeatureExtractionPluginFactory.h	Fri Aug 19 15:58:57 2016 +0100
@@ -37,7 +37,7 @@
     virtual std::vector<QString> getPluginPath();
 
     virtual std::vector<QString> getPluginIdentifiers();
-
+    
     virtual QString findPluginFile(QString soname, QString inDir = "");
 
     // We don't set blockSize or channels on this -- they're
@@ -57,7 +57,7 @@
     friend class PluginDeletionNotifyAdapter;
     void pluginDeleted(Vamp::Plugin *);
     std::map<Vamp::Plugin *, void *> m_handleMap;
-
+    
     void generateTaxonomy();
 };
 
--- a/plugin/LADSPAPluginFactory.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/plugin/LADSPAPluginFactory.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -668,14 +668,11 @@
 
     generateFallbackCategories();
 
-    for (std::vector<QString>::iterator i = pathList.begin();
-	 i != pathList.end(); ++i) {
+    QStringList candidates =
+        PluginScan::getInstance()->getCandidateLibrariesFor(getPluginType());
 
-	QDir pluginDir(*i, PLUGIN_GLOB);
-
-	for (unsigned int j = 0; j < pluginDir.count(); ++j) {
-	    discoverPluginsFrom(QString("%1/%2").arg(*i).arg(pluginDir[j]));
-	}
+    for (QString c: candidates) {
+        discoverPluginsFrom(c);
     }
 }
 
--- a/plugin/LADSPAPluginFactory.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/plugin/LADSPAPluginFactory.h	Fri Aug 19 15:58:57 2016 +0100
@@ -24,6 +24,8 @@
 #include "RealTimePluginFactory.h"
 #include "api/ladspa.h"
 
+#include "PluginScan.h"
+
 #include <vector>
 #include <map>
 #include <set>
@@ -63,6 +65,10 @@
     LADSPAPluginFactory();
     friend class RealTimePluginFactory;
 
+    virtual PluginScan::PluginType getPluginType() const {
+        return PluginScan::LADSPAPlugin;
+    }
+
     virtual std::vector<QString> getPluginPath();
 
     virtual std::vector<QString> getLRDFPath(QString &baseUri);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/PluginScan.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -0,0 +1,112 @@
+/* -*- 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 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 "PluginScan.h"
+
+#include "base/Debug.h"
+
+#include "checker/knownplugins.h"
+
+#include <QMutex>
+#include <QCoreApplication>
+
+using std::string;
+
+class PluginScan::Logger : public PluginCandidates::LogCallback
+{
+protected:
+    void log(std::string message) {
+        SVDEBUG << "PluginScan: " << message;
+    }
+};
+
+PluginScan *PluginScan::getInstance()
+{
+    static QMutex mutex;
+    static PluginScan *m_instance = 0;
+    mutex.lock();
+    if (!m_instance) m_instance = new PluginScan();
+    mutex.unlock();
+    return m_instance;
+}
+
+PluginScan::PluginScan() : m_kp(0), m_succeeded(false), m_logger(new Logger) {
+}
+
+PluginScan::~PluginScan() {
+    delete m_kp;
+    delete m_logger;
+}
+
+void
+PluginScan::scan(QString helperExecutablePath)
+{
+    delete m_kp;
+    m_succeeded = false;
+    try {
+	m_kp = new KnownPlugins(helperExecutablePath.toStdString(), m_logger);
+	m_succeeded = true;
+    } catch (const std::exception &e) {
+	cerr << "ERROR: PluginScan::scan: " << e.what() << endl;
+	m_kp = 0;
+    }
+}
+
+QStringList
+PluginScan::getCandidateLibrariesFor(PluginType type) const
+{
+    KnownPlugins::PluginType kpt;
+    switch (type) {
+    case VampPlugin: kpt = KnownPlugins::VampPlugin; break;
+    case LADSPAPlugin: kpt = KnownPlugins::LADSPAPlugin; break;
+    case DSSIPlugin: kpt = KnownPlugins::DSSIPlugin; break;
+    default: throw std::logic_error("Inconsistency in plugin type enums");
+    }
+    
+    QStringList candidates;
+    if (!m_kp) return candidates;
+    auto c = m_kp->getCandidateLibrariesFor(kpt);
+    for (auto s: c) candidates.push_back(s.c_str());
+    return candidates;
+}
+
+QString
+PluginScan::getStartupFailureReport() const
+{
+    if (!m_succeeded) {
+	return QObject::tr("<b>Failed to scan for plugins</b>"
+			   "<p>Failed to scan for plugins at startup. Possibly "
+                           "the plugin checker helper program was not correctly "
+                           "installed alongside %1?</p>")
+            .arg(QCoreApplication::applicationName());
+    }
+    if (!m_kp) {
+	return QObject::tr("<b>Did not scan for plugins</b>"
+			   "<p>Apparently no scan for plugins was attempted "
+			   "(internal error?)</p>");
+    }
+
+    string report = m_kp->getFailureReport();
+    if (report == "") {
+	return QString(report.c_str());
+    }
+
+    return QObject::tr("<b>Failed to load plugins</b>"
+		       "<p>Failed to load one or more plugin libraries:</p>")
+	+ QString(report.c_str())
+        + QObject::tr("<p>These plugins may be incompatible with the system, "
+                      "and will be ignored during this run of %1.</p>")
+        .arg(QCoreApplication::applicationName());
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/PluginScan.h	Fri Aug 19 15:58:57 2016 +0100
@@ -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 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_SCAN_H
+#define PLUGIN_SCAN_H
+
+#include <QStringList>
+
+class KnownPlugins;
+
+class PluginScan
+{
+public:
+    static PluginScan *getInstance();
+
+    void scan(QString helperExecutablePath);
+
+    bool scanSucceeded() const;
+    
+    enum PluginType {
+	VampPlugin,
+	LADSPAPlugin,
+	DSSIPlugin
+    };
+    QStringList getCandidateLibrariesFor(PluginType) const;
+
+    QString getStartupFailureReport() const;
+
+private:
+    PluginScan();
+    ~PluginScan();
+    KnownPlugins *m_kp;
+    bool m_succeeded;
+
+    class Logger;
+    Logger *m_logger;
+};
+
+#endif
--- a/plugin/plugins/SamplePlayer.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/plugin/plugins/SamplePlayer.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -571,7 +571,7 @@
         if (m_concertA) {
             ratio *= *m_concertA / 440.f;
         }
-	if (m_basePitch && n != *m_basePitch) {
+	if (m_basePitch && float(n) != *m_basePitch) {
 	    ratio *= powf(1.059463094f, float(n) - *m_basePitch);
 	}
     }
--- a/rdf/RDFFeatureWriter.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/rdf/RDFFeatureWriter.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -352,7 +352,7 @@
 
     bool wantTrack = (userSpecifiedTrack ||
                       (m_userMakerUri != "") ||
-                      (m_metadata.find(trackId) != m_metadata.end()));
+                      haveTitleArtistMetadata(trackId));
 
 //    cerr << "wantTrack = " << wantTrack << " (userSpecifiedTrack = "
 //         << userSpecifiedTrack << ", m_userMakerUri = " << m_userMakerUri << ", have metadata = " << (m_metadata.find(trackId) != m_metadata.end()) << ")" << endl;
@@ -367,7 +367,7 @@
         // including a Track would be to assert that this was one,
         // which is the one thing we wouldn't know...
         TrackMetadata tm;
-        if (m_metadata.find(trackId) != m_metadata.end()) {
+        if (haveTitleArtistMetadata(trackId)) {
             tm = m_metadata[trackId];
         }
         stream << trackURI << " a mo:Track ";
--- a/rdf/RDFFeatureWriter.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/rdf/RDFFeatureWriter.h	Fri Aug 19 15:58:57 2016 +0100
@@ -70,6 +70,19 @@
     typedef map<QString, TrackMetadata> TrackMetadataMap;
     TrackMetadataMap m_metadata;
 
+    bool haveTitleArtistMetadata(QString trackId) const {
+        // Formerly in various places we used to test whether a track
+        // appeared in the metadata map at all, in order to determine
+        // whether it had any associated metadata. That won't work any
+        // more because metadata now includes duration, which can
+        // appear even if no title/artist are given and which is not
+        // something whose presence indicates the involvement of a
+        // "publication Track". So check for artist/title explicitly.
+        auto mitr = m_metadata.find(trackId);
+        if (mitr == m_metadata.end()) return false;
+        return (mitr->second.title != "" || mitr->second.maker != "");
+    }
+
     QString m_fixedEventTypeURI;
 
     virtual void reviewFileForAppending(QString filename);
--- a/rdf/RDFTransformFactory.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/rdf/RDFTransformFactory.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -129,7 +129,12 @@
         }
         m_store->import(qurl, BasicStore::ImportIgnoreDuplicates);
         m_isRDF = true;
-    } catch (...) { }
+    } catch (const std::exception &e) {
+        // The file is not RDF -- we report this by returning false
+        // from isRDF (because we have not reached the m_isRDF = true
+        // line above), but we also set the error string
+        m_errorString = e.what();
+    }
 }
 
 RDFTransformFactoryImpl::~RDFTransformFactoryImpl()
@@ -146,7 +151,7 @@
 bool
 RDFTransformFactoryImpl::isOK()
 {
-    return (m_errorString == "");
+    return m_isRDF && (m_errorString == "");
 }
 
 QString
@@ -160,6 +165,8 @@
 {
     std::vector<Transform> transforms;
 
+    if (!m_isRDF) return transforms;
+    
     std::map<QString, Transform> uriTransformMap;
 
     Nodes tnodes = m_store->match
--- a/rdf/RDFTransformFactory.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/rdf/RDFTransformFactory.h	Fri Aug 19 15:58:57 2016 +0100
@@ -36,8 +36,28 @@
     RDFTransformFactory(QString url);
     virtual ~RDFTransformFactory();
 
-    bool isRDF(); // true if the file was parseable and had transforms in it
-    bool isOK();  // true if the transforms could be completely constructed
+    /** isRDF() may be queried at any point after construction. It
+        returns true if the file was parseable as RDF.
+    */
+    bool isRDF();
+
+    /** isOK() may be queried at any point after getTransforms() has
+        been called. It is true if the file was parseable as RDF and
+        any transforms in it could be completely constructed.
+
+        Note that even if isOK() returns true, it is still possible
+        that the file did not define any transforms; in this case,
+        getTransforms() would have returned an empty list.
+
+        If isOK() is called before getTransforms() has been invoked to
+        query the file, it will return true iff isRDF() is true.
+    */
+    bool isOK();
+
+    /** Return any error string resulting from loading or querying the
+        file. This will be non-empty if isRDF() or isOK() returns
+        false.
+     */
     QString getErrorString() const;
 
     std::vector<Transform> getTransforms(ProgressReporter *reporter);
--- a/svcore.pro	Tue Oct 20 12:54:06 2015 +0100
+++ b/svcore.pro	Fri Aug 19 15:58:57 2016 +0100
@@ -1,6 +1,9 @@
 
 TEMPLATE = lib
 
+INCLUDEPATH += ../vamp-plugin-sdk
+DEFINES += HAVE_VAMP HAVE_VAMPHOSTSDK
+
 exists(config.pri) {
     include(config.pri)
 }
@@ -22,7 +25,7 @@
         LIBS += -L../sv-dependency-builds/osx/lib
     }
 
-    DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_LIBLO HAVE_MAD HAVE_ID3TAG 
+    DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_LIBLO HAVE_MAD HAVE_ID3TAG 
 
     macx* {
         DEFINES += HAVE_COREAUDIO
@@ -36,7 +39,7 @@
 TARGET = svcore
 
 DEPENDPATH += . data plugin plugin/api/alsa
-INCLUDEPATH += . data plugin plugin/api/alsa ../dataquay
+INCLUDEPATH += . data plugin plugin/api/alsa ../dataquay ../checker
 OBJECTS_DIR = o
 MOC_DIR = o
 
@@ -53,10 +56,12 @@
            base/AudioPlaySource.h \
            base/BaseTypes.h \
            base/Clipboard.h \
+           base/ColumnOp.h \
            base/Command.h \
            base/Debug.h \
            base/Exceptions.h \
            base/LogRange.h \
+           base/MagnitudeRange.h \
            base/Pitch.h \
            base/Playable.h \
            base/PlayParameterRepository.h \
@@ -70,7 +75,6 @@
            base/RealTime.h \
            base/RecentFiles.h \
            base/Resampler.h \
-           base/ResizeableBitset.h \
            base/ResourceFinder.h \
            base/RingBuffer.h \
            base/Scavenger.h \
@@ -78,6 +82,7 @@
            base/Serialiser.h \
            base/StorageAdviser.h \
            base/StringBits.h \
+           base/Strings.h \
            base/TempDirectory.h \
            base/TempWriteFile.h \
            base/TextMatcher.h \
@@ -110,6 +115,7 @@
            base/Serialiser.cpp \
            base/StorageAdviser.cpp \
            base/StringBits.cpp \
+           base/Strings.cpp \
            base/TempDirectory.cpp \
            base/TempWriteFile.cpp \
            base/TextMatcher.cpp \
@@ -221,7 +227,8 @@
            data/osc/OSCMessage.cpp \
            data/osc/OSCQueue.cpp 
 
-HEADERS += plugin/DSSIPluginFactory.h \
+HEADERS += plugin/PluginScan.h \
+           plugin/DSSIPluginFactory.h \
            plugin/DSSIPluginInstance.h \
            plugin/FeatureExtractionPluginFactory.h \
            plugin/LADSPAPluginFactory.h \
@@ -241,7 +248,8 @@
            plugin/api/alsa/sound/asequencer.h
 
 
-SOURCES += plugin/DSSIPluginFactory.cpp \
+SOURCES += plugin/PluginScan.cpp \
+           plugin/DSSIPluginFactory.cpp \
            plugin/DSSIPluginInstance.cpp \
            plugin/FeatureExtractionPluginFactory.cpp \
            plugin/LADSPAPluginFactory.cpp \
--- a/system/System.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/system/System.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -325,3 +325,4 @@
 double princarg(double a) { return mod(a + M_PI, -2 * M_PI) + M_PI; }
 float princargf(float a) { return float(princarg(a)); }
 
+
--- a/transform/CSVFeatureWriter.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/CSVFeatureWriter.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -37,7 +37,8 @@
     m_sampleTiming(false),
     m_endTimes(false),
     m_forceEnd(false),
-    m_omitFilename(false)
+    m_omitFilename(false),
+    m_digits(6)
 {
 }
 
@@ -82,6 +83,11 @@
     p.hasArg = false;
     pl.push_back(p);
 
+    p.name = "digits";
+    p.description = "Specify the number of significant digits to use when printing transform outputs. Outputs are represented internally using single-precision floating-point, so digits beyond the 8th or 9th place are usually meaningless. The default is 6.";
+    p.hasArg = true;
+    pl.push_back(p);
+
     return pl;
 }
 
@@ -93,10 +99,10 @@
     SVDEBUG << "CSVFeatureWriter::setParameters" << endl;
     for (map<string, string>::iterator i = params.begin();
          i != params.end(); ++i) {
-        cerr << i->first << " -> " << i->second << endl;
+        SVDEBUG << i->first << " -> " << i->second << endl;
         if (i->first == "separator") {
             m_separator = i->second.c_str();
-            cerr << "m_separator = " << m_separator << endl;
+            SVDEBUG << "m_separator = " << m_separator << endl;
             if (m_separator == "\\t") {
                 m_separator = QChar::Tabulation;
             }
@@ -108,6 +114,14 @@
             m_forceEnd = true;
         } else if (i->first == "omit-filename") {
             m_omitFilename = true;
+        } else if (i->first == "digits") {
+            int digits = atoi(i->second.c_str());
+            if (digits <= 0 || digits > 100) {
+                cerr << "CSVFeatureWriter: ERROR: Invalid or out-of-range value for number of significant digits: " << i->second << endl;
+                cerr << "CSVFeatureWriter: NOTE: Continuing with default settings" << endl;
+            } else {
+                m_digits = digits;
+            }
         }
     }
 }
@@ -262,7 +276,7 @@
     }
     
     for (unsigned int j = 0; j < f.values.size(); ++j) {
-        stream << m_separator << f.values[j];
+        stream << m_separator << QString("%1").arg(f.values[j], 0, 'g', m_digits);
     }
     
     if (f.label != "") {
--- a/transform/CSVFeatureWriter.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/CSVFeatureWriter.h	Fri Aug 19 15:58:57 2016 +0100
@@ -74,6 +74,8 @@
                       const Vamp::Plugin::Feature &f,
                       const Vamp::Plugin::Feature *optionalNextFeature,
                       std::string summaryType);
+
+    int m_digits;
 };
 
 #endif
--- a/transform/FeatureExtractionModelTransformer.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/FeatureExtractionModelTransformer.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -1007,8 +1007,7 @@
 	
     } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) {
 	
-	DenseThreeDimensionalModel::Column values =
-            DenseThreeDimensionalModel::Column::fromStdVector(feature.values);
+	DenseThreeDimensionalModel::Column values = feature.values;
 	
 	EditableDenseThreeDimensionalModel *model =
             getConformingOutput<EditableDenseThreeDimensionalModel>(n);
--- a/transform/FeatureWriter.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/FeatureWriter.h	Fri Aug 19 15:58:57 2016 +0100
@@ -60,6 +60,7 @@
     struct TrackMetadata {
         QString title;
         QString maker;
+        RealTime duration;
     };
     virtual void setTrackMetadata(QString /* trackid */, TrackMetadata) { }
 
--- a/transform/FileFeatureWriter.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/FileFeatureWriter.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -46,7 +46,7 @@
         } else if (m_support & SupportOneFileTotal) {
             m_singleFileName = QString("output.%1").arg(m_extension);
         } else {
-            SVDEBUG << "FileFeatureWriter::FileFeatureWriter: ERROR: Invalid support specification " << support << endl;
+            cerr << "FileFeatureWriter::FileFeatureWriter: ERROR: Invalid support specification " << support << endl;
         }
     }
 }
@@ -130,7 +130,7 @@
             if (m_support & SupportOneFilePerTrackTransform &&
                 m_support & SupportOneFilePerTrack) {
                 if (m_singleFileName != "") {
-                    SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both one-file and many-files parameters provided, ignoring many-files" << endl;
+                    cerr << "FileFeatureWriter::setParameters: WARNING: Both one-file and many-files parameters provided, ignoring many-files" << endl;
                 } else {
                     m_manyFiles = true;
                 }
@@ -144,7 +144,7 @@
                     // OneFilePerTrack), so we need to be able to
                     // override it
 //                    if (m_manyFiles) {
-//                        SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both many-files and one-file parameters provided, ignoring one-file" << endl;
+//                        cerr << "FileFeatureWriter::setParameters: WARNING: Both many-files and one-file parameters provided, ignoring one-file" << endl;
 //                    } else {
                         m_singleFileName = i->second.c_str();
 //                    }
@@ -153,7 +153,7 @@
         } else if (i->first == "stdout") {
             if (m_support & SupportStdOut) {
                 if (m_singleFileName != "") {
-                    SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both stdout and one-file provided, ignoring stdout" << endl;
+                    cerr << "FileFeatureWriter::setParameters: WARNING: Both stdout and one-file provided, ignoring stdout" << endl;
                 } else {
                     m_stdout = true;
                 }
@@ -173,7 +173,7 @@
     if (m_singleFileName != "") {
         if (QFileInfo(m_singleFileName).exists() && !(m_force || m_append)) {
             cerr << endl << "FileFeatureWriter: ERROR: Specified output file \"" << m_singleFileName << "\" exists and neither --" << getWriterTag() << "-force nor --" << getWriterTag() << "-append flag is specified -- not overwriting" << endl;
-            SVDEBUG << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
+            cerr << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
             return "";
         }
         return m_singleFileName;
@@ -220,7 +220,7 @@
 
     if (QFileInfo(filename).exists() && !(m_force || m_append)) {
         cerr << endl << "FileFeatureWriter: ERROR: Output file \"" << filename << "\" exists (for input file or URL \"" << trackId << "\" and transform \"" << transformId << "\") and neither --" << getWriterTag() << "-force nor --" << getWriterTag() << "-append is specified -- not overwriting" << endl;
-        SVDEBUG << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
+        cerr << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
         return "";
     }
     
--- a/transform/Transform.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/Transform.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -18,6 +18,7 @@
 #include "plugin/PluginIdentifier.h"
 
 #include "plugin/FeatureExtractionPluginFactory.h"
+#include "plugin/RealTimePluginFactory.h"
 
 #include <QXmlAttributes>
 
@@ -53,12 +54,8 @@
     int errorColumn;
 
     if (!doc.setContent(xml, false, &error, &errorLine, &errorColumn)) {
-        cerr << "Transform::Transform: Error in parsing XML: "
-                  << error << " at line " << errorLine
-                  << ", column " << errorColumn << endl;
-        cerr << "Input follows:" << endl;
-        cerr << xml << endl;
-        cerr << "Input ends." << endl;
+        m_errorString = QString("%1 at line %2, column %3")
+            .arg(error).arg(errorLine).arg(errorColumn);
         return;
     }
     
@@ -207,10 +204,10 @@
 {
     if (FeatureExtractionPluginFactory::instanceFor(getPluginIdentifier())) {
         return FeatureExtraction;
+    } else if (RealTimePluginFactory::instanceFor(getPluginIdentifier())) {
+        return RealTimeEffect;
     } else {
-        // We don't have an unknown/invalid return value, so always
-        // return this
-        return RealTimeEffect;
+        return UnknownType;
     }
 }
 
--- a/transform/Transform.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/Transform.h	Fri Aug 19 15:58:57 2016 +0100
@@ -46,7 +46,8 @@
 
     /**
      * Construct a Transform by parsing the given XML data string.
-     * This is the inverse of toXml.
+     * This is the inverse of toXml. If this fails, getErrorString()
+     * will return a non-empty string.
      */
     Transform(QString xml);
 
@@ -67,7 +68,7 @@
     void setIdentifier(TransformId id);
     TransformId getIdentifier() const;
 
-    enum Type { FeatureExtraction, RealTimeEffect };
+    enum Type { FeatureExtraction, RealTimeEffect, UnknownType };
 
     Type getType() const;
     QString getPluginIdentifier() const;
@@ -156,6 +157,8 @@
      */
     void setFromXmlAttributes(const QXmlAttributes &);
 
+    QString getErrorString() const { return m_errorString; }
+    
     static SummaryType stringToSummaryType(QString);
     static QString summaryTypeToString(SummaryType);
 
@@ -195,6 +198,7 @@
     RealTime m_startTime;
     RealTime m_duration;
     sv_samplerate_t m_sampleRate;
+    QString m_errorString;
 };
 
 typedef std::vector<Transform> Transforms;
--- a/transform/TransformFactory.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/TransformFactory.cpp	Fri Aug 19 15:58:57 2016 +0100
@@ -794,6 +794,9 @@
 
     if (t.getType() == Transform::FeatureExtraction) {
 
+//        cerr << "TransformFactory::instantiateDefaultPluginFor: identifier \""
+//             << identifier << "\" is a feature extraction transform" << endl;
+        
         FeatureExtractionPluginFactory *factory = 
             FeatureExtractionPluginFactory::instanceFor(pluginId);
 
@@ -801,7 +804,10 @@
             plugin = factory->instantiatePlugin(pluginId, rate);
         }
 
-    } else {
+    } else if (t.getType() == Transform::RealTimeEffect) {
+
+//        cerr << "TransformFactory::instantiateDefaultPluginFor: identifier \""
+//             << identifier << "\" is a real-time transform" << endl;
 
         RealTimePluginFactory *factory = 
             RealTimePluginFactory::instanceFor(pluginId);
@@ -809,6 +815,10 @@
         if (factory) {
             plugin = factory->instantiatePlugin(pluginId, 0, 0, rate, 1024, 1);
         }
+
+    } else {
+        cerr << "TransformFactory: ERROR: transform id \""
+             << identifier << "\" is of unknown type" << endl;
     }
 
     return plugin;
--- a/transform/TransformFactory.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/TransformFactory.h	Fri Aug 19 15:58:57 2016 +0100
@@ -195,7 +195,7 @@
      */
     void setParametersFromPluginConfigurationXml(Transform &transform,
                                                  QString xml);
-
+    
 protected:
     typedef std::map<TransformId, TransformDescription> TransformDescriptionMap;