changeset 1183:aa61e73cf580 3.0-plus-imaf

Merge branches 3.0-integration and imaf_enc to 3.0-plus-imaf
author Chris Cannam
date Wed, 20 Apr 2016 12:06:28 +0100
parents a1f410f895d3 (diff) c475940aba79 (current diff)
children
files data/fileio/FileFinder.h
diffstat 219 files changed, 9452 insertions(+), 8482 deletions(-) [+]
line wrap: on
line diff
--- a/INSTALL.txt	Tue Jul 14 15:04:46 2015 +0100
+++ b/INSTALL.txt	Wed Apr 20 12:06:28 2016 +0100
@@ -16,7 +16,6 @@
 
 REQUIRED	Qt v4.4 or newer	http://qt.nokia.com/
 REQUIRED	Vamp Plugin SDK	v2.x	http://www.vamp-plugins.org/
-REQUIRED	Rubber Band Library	http://www.breakfastquay.com/rubberband/
 REQUIRED	libsndfile		http://www.mega-nerd.com/libsndfile/
 REQUIRED	libsamplerate		http://www.mega-nerd.com/SRC/
 REQUIRED	FFTW3 			http://www.fftw.org/
--- a/acinclude.m4	Tue Jul 14 15:04:46 2015 +0100
+++ b/acinclude.m4	Wed Apr 20 12:06:28 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
@@ -112,3 +118,146 @@
 
 ])
 
+# From autoconf archive:
+
+# ============================================================================
+#  http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html
+# ============================================================================
+#
+# SYNOPSIS
+#
+#   AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional])
+#
+# DESCRIPTION
+#
+#   Check for baseline language coverage in the compiler for the C++11
+#   standard; if necessary, add switches to CXXFLAGS to enable support.
+#
+#   The first argument, if specified, indicates whether you insist on an
+#   extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g.
+#   -std=c++11).  If neither is specified, you get whatever works, with
+#   preference for an extended mode.
+#
+#   The second argument, if specified 'mandatory' or if left unspecified,
+#   indicates that baseline C++11 support is required and that the macro
+#   should error out if no mode with that support is found.  If specified
+#   'optional', then configuration proceeds regardless, after defining
+#   HAVE_CXX11 if and only if a supporting mode is found.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com>
+#   Copyright (c) 2012 Zack Weinberg <zackw@panix.com>
+#   Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu>
+#   Copyright (c) 2014 Alexey Sokolov <sokolov@google.com>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[
+  template <typename T>
+    struct check
+    {
+      static_assert(sizeof(int) <= sizeof(T), "not big enough");
+    };
+
+    struct Base {
+    virtual void f() {}
+    };
+    struct Child : public Base {
+    virtual void f() override {}
+    };
+
+    typedef check<check<bool>> right_angle_brackets;
+
+    int a;
+    decltype(a) b;
+
+    typedef check<int> check_type;
+    check_type c;
+    check_type&& cr = static_cast<check_type&&>(c);
+
+    auto d = a;
+    auto l = [](){};
+]])
+
+AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl
+  m4_if([$1], [], [],
+        [$1], [ext], [],
+        [$1], [noext], [],
+        [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl
+  m4_if([$2], [], [ax_cxx_compile_cxx11_required=true],
+        [$2], [mandatory], [ax_cxx_compile_cxx11_required=true],
+        [$2], [optional], [ax_cxx_compile_cxx11_required=false],
+        [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])])
+  AC_LANG_PUSH([C++])dnl
+  ac_success=no
+  AC_CACHE_CHECK(whether $CXX supports C++11 features by default,
+  ax_cv_cxx_compile_cxx11,
+  [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+    [ax_cv_cxx_compile_cxx11=yes],
+    [ax_cv_cxx_compile_cxx11=no])])
+  if test x$ax_cv_cxx_compile_cxx11 = xyes; then
+    ac_success=yes
+  fi
+
+  m4_if([$1], [noext], [], [dnl
+  if test x$ac_success = xno; then
+    for switch in -std=gnu++11 -std=gnu++0x; do
+      cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch])
+      AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch,
+                     $cachevar,
+        [ac_save_CXXFLAGS="$CXXFLAGS"
+         CXXFLAGS="$CXXFLAGS $switch"
+         AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+          [eval $cachevar=yes],
+          [eval $cachevar=no])
+         CXXFLAGS="$ac_save_CXXFLAGS"])
+      if eval test x\$$cachevar = xyes; then
+        CXXFLAGS="$CXXFLAGS $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi])
+
+  m4_if([$1], [ext], [], [dnl
+  if test x$ac_success = xno; then
+    for switch in -std=c++11 -std=c++0x; do
+      cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch])
+      AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch,
+                     $cachevar,
+        [ac_save_CXXFLAGS="$CXXFLAGS"
+         CXXFLAGS="$CXXFLAGS $switch"
+         AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+          [eval $cachevar=yes],
+          [eval $cachevar=no])
+         CXXFLAGS="$ac_save_CXXFLAGS"])
+      if eval test x\$$cachevar = xyes; then
+        CXXFLAGS="$CXXFLAGS $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi])
+  AC_LANG_POP([C++])
+  if test x$ax_cxx_compile_cxx11_required = xtrue; then
+    if test x$ac_success = xno; then
+      AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.])
+    fi
+  else
+    if test x$ac_success = xno; then
+      HAVE_CXX11=0
+      AC_MSG_NOTICE([No compiler with C++11 support was found])
+    else
+      HAVE_CXX11=1
+      AC_DEFINE(HAVE_CXX11,1,
+                [define if the compiler supports basic C++11 syntax])
+    fi
+
+    AC_SUBST(HAVE_CXX11)
+  fi
+])
+
--- a/base/AudioLevel.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/AudioLevel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -26,52 +26,52 @@
 #include <cassert>
 #include "system/System.h"
 
-const float AudioLevel::DB_FLOOR = -1000.f;
+const double AudioLevel::DB_FLOOR = -1000.;
 
 struct FaderDescription
 {
-    FaderDescription(float _minDb, float _maxDb, float _zeroPoint) :
+    FaderDescription(double _minDb, double _maxDb, double _zeroPoint) :
 	minDb(_minDb), maxDb(_maxDb), zeroPoint(_zeroPoint) { }
 
-    float minDb;
-    float maxDb;
-    float zeroPoint; // as fraction of total throw
+    double minDb;
+    double maxDb;
+    double zeroPoint; // as fraction of total throw
 };
 
 static const FaderDescription faderTypes[] = {
-    FaderDescription(-40.f,  +6.f, 0.75f), // short
-    FaderDescription(-70.f, +10.f, 0.80f), // long
-    FaderDescription(-70.f,   0.f, 1.00f), // IEC268
-    FaderDescription(-70.f, +10.f, 0.80f), // IEC268 long
-    FaderDescription(-40.f,   0.f, 1.00f), // preview
+    FaderDescription(-40.,  +6., 0.75), // short
+    FaderDescription(-70., +10., 0.80), // long
+    FaderDescription(-70.,   0., 1.00), // IEC268
+    FaderDescription(-70., +10., 0.80), // IEC268 long
+    FaderDescription(-40.,   0., 1.00), // preview
 };
 
-//typedef std::vector<float> LevelList;
+//typedef std::vector<double> LevelList;
 //static std::map<int, LevelList> previewLevelCache;
 //static const LevelList &getPreviewLevelCache(int levels);
 
-float
-AudioLevel::multiplier_to_dB(float multiplier)
+double
+AudioLevel::multiplier_to_dB(double multiplier)
 {
-    if (multiplier == 0.f) return DB_FLOOR;
-    else if (multiplier < 0.f) return multiplier_to_dB(-multiplier);
-    float dB = 10 * log10f(multiplier);
+    if (multiplier == 0.) return DB_FLOOR;
+    else if (multiplier < 0.) return multiplier_to_dB(-multiplier);
+    double dB = 10 * log10(multiplier);
     return dB;
 }
 
-float
-AudioLevel::dB_to_multiplier(float dB)
+double
+AudioLevel::dB_to_multiplier(double dB)
 {
-    if (dB == DB_FLOOR) return 0.f;
-    float m = powf(10.f, dB / 10.f);
+    if (dB == DB_FLOOR) return 0.;
+    double m = pow(10., dB / 10.);
     return m;
 }
 
 /* IEC 60-268-18 fader levels.  Thanks to Steve Harris. */
 
-static float iec_dB_to_fader(float db)
+static double iec_dB_to_fader(double db)
 {
-    float def = 0.0f; // Meter deflection %age
+    double def = 0.0f; // Meter deflection %age
 
     if (db < -70.0f) {
         def = 0.0f;
@@ -92,9 +92,9 @@
     return def;
 }
 
-static float iec_fader_to_dB(float def)  // Meter deflection %age
+static double iec_fader_to_dB(double def)  // Meter deflection %age
 {
-    float db = 0.0f;
+    double db = 0.0f;
 
     if (def >= 50.0f) {
 	db = (def - 50.0f) / 2.5f - 20.0f;
@@ -113,16 +113,16 @@
     return db;
 }
 
-float
+double
 AudioLevel::fader_to_dB(int level, int maxLevel, FaderType type)
 {
     if (level == 0) return DB_FLOOR;
 
     if (type == IEC268Meter || type == IEC268LongMeter) {
 
-	float maxPercent = iec_dB_to_fader(faderTypes[type].maxDb);
-	float percent = float(level) * maxPercent / float(maxLevel);
-	float dB = iec_fader_to_dB(percent);
+	double maxPercent = iec_dB_to_fader(faderTypes[type].maxDb);
+	double percent = double(level) * maxPercent / double(maxLevel);
+	double dB = iec_fader_to_dB(percent);
 	return dB;
 
     } else { // scale proportional to sqrt(fabs(dB))
@@ -131,27 +131,27 @@
     
 	if (level >= zeroLevel) {
 	    
-	    float value = level - zeroLevel;
-	    float scale = float(maxLevel - zeroLevel) /
-		sqrtf(faderTypes[type].maxDb);
+	    double value = level - zeroLevel;
+	    double scale = (maxLevel - zeroLevel) /
+		sqrt(faderTypes[type].maxDb);
 	    value /= scale;
-	    float dB = powf(value, 2.f);
+	    double dB = pow(value, 2.);
 	    return dB;
 	    
 	} else {
 	    
-	    float value = zeroLevel - level;
-	    float scale = zeroLevel / sqrtf(0.f - faderTypes[type].minDb);
+	    double value = zeroLevel - level;
+	    double scale = zeroLevel / sqrt(0. - faderTypes[type].minDb);
 	    value /= scale;
-	    float dB = powf(value, 2.f);
-	    return 0.f - dB;
+	    double dB = pow(value, 2.);
+	    return 0. - dB;
 	}
     }
 }
 
 
 int
-AudioLevel::dB_to_fader(float dB, int maxLevel, FaderType type)
+AudioLevel::dB_to_fader(double dB, int maxLevel, FaderType type)
 {
     if (dB == DB_FLOOR) return 0;
 
@@ -162,8 +162,8 @@
 	// result not as a percentage, but as a scale between 0 and
 	// whatever the "percentage" for our (possibly >0dB) max dB is.
 	
-	float maxPercent = iec_dB_to_fader(faderTypes[type].maxDb);
-	float percent = iec_dB_to_fader(dB);
+	double maxPercent = iec_dB_to_fader(faderTypes[type].maxDb);
+	double percent = iec_dB_to_fader(dB);
 	int faderLevel = int((maxLevel * percent) / maxPercent + 0.01f);
 	
 	if (faderLevel < 0) faderLevel = 0;
@@ -174,16 +174,16 @@
 
 	int zeroLevel = int(maxLevel * faderTypes[type].zeroPoint);
 
-	if (dB >= 0.f) {
+	if (dB >= 0.) {
 	    
-            if (faderTypes[type].maxDb <= 0.f) {
+            if (faderTypes[type].maxDb <= 0.) {
                 
                 return maxLevel;
 
             } else {
 
-                float value = sqrtf(dB);
-                float scale = (maxLevel - zeroLevel) / sqrtf(faderTypes[type].maxDb);
+                double value = sqrt(dB);
+                double scale = (maxLevel - zeroLevel) / sqrt(faderTypes[type].maxDb);
                 value *= scale;
                 int level = int(value + 0.01f) + zeroLevel;
                 if (level > maxLevel) level = maxLevel;
@@ -192,9 +192,9 @@
 	    
 	} else {
 
-	    dB = 0.f - dB;
-	    float value = sqrtf(dB);
-	    float scale = zeroLevel / sqrtf(0.f - faderTypes[type].minDb);
+	    dB = 0. - dB;
+	    double value = sqrt(dB);
+	    double scale = zeroLevel / sqrt(0. - faderTypes[type].minDb);
 	    value *= scale;
 	    int level = zeroLevel - int(value + 0.01f);
 	    if (level < 0) level = 0;
@@ -204,18 +204,18 @@
 }
 
 	
-float
+double
 AudioLevel::fader_to_multiplier(int level, int maxLevel, FaderType type)
 {
-    if (level == 0) return 0.f;
+    if (level == 0) return 0.;
     return dB_to_multiplier(fader_to_dB(level, maxLevel, type));
 }
 
 int
-AudioLevel::multiplier_to_fader(float multiplier, int maxLevel, FaderType type)
+AudioLevel::multiplier_to_fader(double multiplier, int maxLevel, FaderType type)
 {
-    if (multiplier == 0.f) return 0;
-    float dB = multiplier_to_dB(multiplier);
+    if (multiplier == 0.) return 0;
+    double dB = multiplier_to_dB(multiplier);
     int fader = dB_to_fader(dB, maxLevel, type);
     return fader;
 }
@@ -227,7 +227,7 @@
     LevelList &ll = previewLevelCache[levels];
     if (ll.empty()) {
 	for (int i = 0; i <= levels; ++i) {
-	    float m = AudioLevel::fader_to_multiplier
+	    double m = AudioLevel::fader_to_multiplier
 		(i + levels/4, levels + levels/4, AudioLevel::PreviewLevel);
 	    if (levels == 1) m /= 100; // noise
 	    ll.push_back(m);
@@ -238,14 +238,14 @@
 */
 
 int
-AudioLevel::multiplier_to_preview(float m, int levels)
+AudioLevel::multiplier_to_preview(double m, int levels)
 {
     assert(levels > 0);
     return multiplier_to_fader(m, levels, PreviewLevel);
 
     /* The original multiplier_to_preview which follows is not thread-safe.
 
-    if (m < 0.f) return -multiplier_to_preview(-m, levels);
+    if (m < 0.) return -multiplier_to_preview(-m, levels);
 
     const LevelList &ll = getPreviewLevelCache(levels);
     int result = -1;
@@ -277,7 +277,7 @@
     */
 }
 
-float
+double
 AudioLevel::preview_to_multiplier(int level, int levels)
 {
     assert(levels > 0);
--- a/base/AudioLevel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/AudioLevel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -35,7 +35,7 @@
 {
 public:
 
-    static const float DB_FLOOR;
+    static const double DB_FLOOR;
 
     enum FaderType {
 	     ShortFader = 0, // -40 -> +6  dB
@@ -45,19 +45,19 @@
 	   PreviewLevel = 4
     };
 
-    static float multiplier_to_dB(float multiplier);
-    static float dB_to_multiplier(float dB);
+    static double multiplier_to_dB(double multiplier);
+    static double dB_to_multiplier(double dB);
 
-    static float fader_to_dB(int level, int maxLevel, FaderType type);
-    static int   dB_to_fader(float dB, int maxFaderLevel, FaderType type);
+    static double fader_to_dB(int level, int maxLevel, FaderType type);
+    static int    dB_to_fader(double dB, int maxFaderLevel, FaderType type);
 
-    static float fader_to_multiplier(int level, int maxLevel, FaderType type);
-    static int   multiplier_to_fader(float multiplier, int maxFaderLevel,
+    static double fader_to_multiplier(int level, int maxLevel, FaderType type);
+    static int    multiplier_to_fader(double multiplier, int maxFaderLevel,
 				     FaderType type);
 
     // fast if "levels" doesn't change often -- for audio segment previews
-    static int   multiplier_to_preview(float multiplier, int levels);
-    static float preview_to_multiplier(int level, int levels);
+    static int    multiplier_to_preview(double multiplier, int levels);
+    static double preview_to_multiplier(int level, int levels);
 };
 
 
--- a/base/AudioPlaySource.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/AudioPlaySource.h	Wed Apr 20 12:06:28 2016 +0100
@@ -16,6 +16,8 @@
 #ifndef _AUDIO_PLAY_SOURCE_H_
 #define _AUDIO_PLAY_SOURCE_H_
 
+#include "BaseTypes.h"
+
 struct Auditionable {
     virtual ~Auditionable() { }
 };
@@ -37,7 +39,7 @@
      * Start playing from the given frame.  If playback is already
      * under way, reseek to the given frame and continue.
      */
-    virtual void play(size_t startFrame) = 0;
+    virtual void play(sv_frame_t startFrame) = 0;
 
     /**
      * Stop playback.
@@ -53,7 +55,7 @@
      * Return the frame number that is currently expected to be coming
      * out of the speakers.  (i.e. compensating for playback latency.)
      */
-    virtual size_t getCurrentPlayingFrame() = 0;
+    virtual sv_frame_t getCurrentPlayingFrame() = 0;
 
     /**
      * Return the current (or thereabouts) output levels in the range
@@ -65,14 +67,14 @@
      * Return the sample rate of the source material -- any material
      * that wants to play at a different rate will sound wrong.
      */
-    virtual size_t getSourceSampleRate() const = 0;
+    virtual sv_samplerate_t getSourceSampleRate() const = 0;
 
     /**
      * Return the sample rate set by the target audio device (or the
      * source sample rate if the target hasn't set one).  If the
      * source and target sample rates differ, resampling will occur.
      */
-    virtual size_t getTargetSampleRate() const = 0;
+    virtual sv_samplerate_t getTargetSampleRate() const = 0;
 
     /**
      * Get the block size of the target audio device.  This may be an
@@ -80,7 +82,7 @@
      * size; the source should behave itself even if this value turns
      * out to be inaccurate.
      */
-    virtual size_t getTargetBlockSize() const = 0;
+    virtual int getTargetBlockSize() const = 0;
 
     /**
      * Get the number of channels of audio that will be provided
@@ -88,7 +90,7 @@
      * count: for example, a mono source will provide 2 channels
      * after pan.
      */
-    virtual size_t getTargetChannelCount() const = 0;
+    virtual int getTargetChannelCount() const = 0;
 
     /**
      * Set a plugin or other subclass of Auditionable as an
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/BaseTypes.h	Wed Apr 20 12:06:28 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 BASE_TYPES_H
+#define BASE_TYPES_H
+
+#include <cstdint>
+
+/** Frame index, the unit of our time axis. This is signed because the
+    axis conceptually extends below zero: zero represents the start of
+    the main loaded audio model, not the start of time; a windowed
+    transform could legitimately produce results before then. We also
+    use this for frame counts, simply to avoid error-prone arithmetic
+    between signed and unsigned types.
+*/
+typedef int64_t sv_frame_t;
+
+/** Check whether an integer index is in range for a container,
+    avoiding overflows and signed/unsigned comparison warnings.
+*/
+template<typename T, typename C>
+bool in_range_for(const C &container, T i)
+{
+    if (i < 0) return false;
+    if (sizeof(T) > sizeof(typename C::size_type)) {
+	return i < static_cast<T>(container.size());
+    } else {
+	return static_cast<typename C::size_type>(i) < container.size();
+    }
+}
+
+/** Sample rate. We have to deal with sample rates provided as float
+    or (unsigned) int types, so we might as well have a type that can
+    represent both. Storage size isn't an issue anyway.
+*/
+typedef double sv_samplerate_t;
+
+#endif
+
--- a/base/Clipboard.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Clipboard.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -15,11 +15,13 @@
 
 #include "Clipboard.h"
 
-Clipboard::Point::Point(long frame, QString label) :
+Clipboard::Point::Point(sv_frame_t frame, QString label) :
     m_haveFrame(true),
     m_frame(frame),
     m_haveValue(false),
+    m_value(0),
     m_haveDuration(false),
+    m_duration(0),
     m_haveLabel(true),
     m_label(label),
     m_haveLevel(false),
@@ -29,12 +31,13 @@
 {
 }
 
-Clipboard::Point::Point(long frame, float value, QString label) :
+Clipboard::Point::Point(sv_frame_t frame, float value, QString label) :
     m_haveFrame(true),
     m_frame(frame),
     m_haveValue(true),
     m_value(value),
     m_haveDuration(false),
+    m_duration(0),
     m_haveLabel(true),
     m_label(label),
     m_haveLevel(false),
@@ -44,7 +47,7 @@
 {
 }
 
-Clipboard::Point::Point(long frame, float value, size_t duration, QString label) :
+Clipboard::Point::Point(sv_frame_t frame, float value, sv_frame_t duration, QString label) :
     m_haveFrame(true),
     m_frame(frame),
     m_haveValue(true),
@@ -60,7 +63,7 @@
 {
 }
 
-Clipboard::Point::Point(long frame, float value, size_t duration, float level, QString label) :
+Clipboard::Point::Point(sv_frame_t frame, float value, sv_frame_t duration, float level, QString label) :
     m_haveFrame(true),
     m_frame(frame),
     m_haveValue(true),
@@ -117,12 +120,21 @@
     return m_haveFrame;
 }
 
-long
+sv_frame_t
 Clipboard::Point::getFrame() const
 {
     return m_frame;
 }
 
+Clipboard::Point
+Clipboard::Point::withFrame(sv_frame_t frame) const
+{
+    Point p(*this);
+    p.m_haveFrame = true;
+    p.m_frame = frame;
+    return p;
+}
+
 bool
 Clipboard::Point::haveValue() const
 {
@@ -135,18 +147,36 @@
     return m_value;
 }
 
+Clipboard::Point
+Clipboard::Point::withValue(float value) const
+{
+    Point p(*this);
+    p.m_haveValue = true;
+    p.m_value = value;
+    return p;
+}
+
 bool
 Clipboard::Point::haveDuration() const
 {
     return m_haveDuration;
 }
 
-size_t
+sv_frame_t
 Clipboard::Point::getDuration() const
 {
     return m_duration;
 }
 
+Clipboard::Point
+Clipboard::Point::withDuration(sv_frame_t duration) const
+{
+    Point p(*this);
+    p.m_haveDuration = true;
+    p.m_duration = duration;
+    return p;
+}
+
 bool
 Clipboard::Point::haveLabel() const
 {
@@ -159,6 +189,15 @@
     return m_label;
 }
 
+Clipboard::Point
+Clipboard::Point::withLabel(QString label) const
+{
+    Point p(*this);
+    p.m_haveLabel = true;
+    p.m_label = label;
+    return p;
+}
+
 bool
 Clipboard::Point::haveLevel() const
 {
@@ -171,6 +210,15 @@
     return m_level;
 }
 
+Clipboard::Point
+Clipboard::Point::withLevel(float level) const
+{
+    Point p(*this);
+    p.m_haveLevel = true;
+    p.m_level = level;
+    return p;
+}
+
 bool
 Clipboard::Point::haveReferenceFrame() const
 {
@@ -183,14 +231,14 @@
     return m_haveReferenceFrame && (m_referenceFrame != m_frame);
 }
 
-long
+sv_frame_t
 Clipboard::Point::getReferenceFrame() const
 {
     return m_referenceFrame;
 }
 
 void
-Clipboard::Point::setReferenceFrame(long f) 
+Clipboard::Point::setReferenceFrame(sv_frame_t f) 
 {
     m_haveReferenceFrame = true;
     m_referenceFrame = f;
--- a/base/Clipboard.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Clipboard.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,53 +19,60 @@
 #include <QString>
 #include <vector>
 
+#include "BaseTypes.h"
+
 class Clipboard
 {
 public:
     class Point
     {
     public:
-        Point(long frame, QString label);
-        Point(long frame, float value, QString label);
-        Point(long frame, float value, size_t duration, QString label);
-        Point(long frame, float value, size_t duration, float level, QString label);
+        Point(sv_frame_t frame, QString label);
+        Point(sv_frame_t frame, float value, QString label);
+        Point(sv_frame_t frame, float value, sv_frame_t duration, QString label);
+        Point(sv_frame_t frame, float value, sv_frame_t duration, float level, QString label);
         Point(const Point &point);
         Point &operator=(const Point &point);
 
         bool haveFrame() const;
-        long getFrame() const;
+        sv_frame_t getFrame() const;
+        Point withFrame(sv_frame_t frame) const;
 
         bool haveValue() const;
         float getValue() const;
+        Point withValue(float value) const;
         
         bool haveDuration() const;
-        size_t getDuration() const;
+        sv_frame_t getDuration() const;
+        Point withDuration(sv_frame_t duration) const;
         
         bool haveLabel() const;
         QString getLabel() const;
+        Point withLabel(QString label) const;
 
         bool haveLevel() const;
         float getLevel() const;
+        Point withLevel(float level) const;
 
         bool haveReferenceFrame() const;
         bool referenceFrameDiffers() const; // from point frame
 
-        long getReferenceFrame() const;
-        void setReferenceFrame(long);
+        sv_frame_t getReferenceFrame() const;
+        void setReferenceFrame(sv_frame_t);
 
     private:
         bool m_haveFrame;
-        long m_frame;
+        sv_frame_t m_frame;
         bool m_haveValue;
         float m_value;
         bool m_haveDuration;
-        size_t m_duration;
+        sv_frame_t m_duration;
         bool m_haveLabel;
         QString m_label;
         bool m_haveLevel;
         float m_level;
         bool m_haveReferenceFrame;
-        long m_referenceFrame;
+        sv_frame_t m_referenceFrame;
     };
 
     Clipboard();
--- a/base/Command.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Command.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -95,6 +95,6 @@
 BundleCommand::getName() const
 {
     if (m_commands.size() == 1) return m_name;
-    return tr("%1 (%n change(s))", "", m_commands.size()).arg(m_name);
+    return tr("%1 (%n change(s))", "", int(m_commands.size())).arg(m_name);
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/DataExportOptions.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,27 @@
+/* -*- 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 DATA_EXPORT_OPTIONS_H
+#define DATA_EXPORT_OPTIONS_H
+
+enum DataExportOption
+{
+    DataExportDefaults = 0x0,
+    DataExportFillGaps = 0x1,
+    DataExportOmitLevels = 0x2,
+};
+
+typedef int DataExportOptions;
+
+#endif
--- a/base/Debug.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Debug.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -16,53 +16,57 @@
 #include "Debug.h"
 #include "ResourceFinder.h"
 
-#include <QString>
+#include <QMutex>
+#include <QDir>
 #include <QUrl>
-#include <QMutex>
-#include <QMutexLocker>
-#include <QFile>
-#include <QDir>
 #include <QCoreApplication>
-#include <QDateTime>
 
-#include <cstdio>
+#ifndef NDEBUG
 
-QDebug &
-getSVDebug()
-{
-    static QFile *logFile = 0;
-    static QDebug *debug = 0;
-    static QMutex mutex;
-    static char *prefix;
+static SVDebug *debug = 0;
+static QMutex mutex;
+
+SVDebug &getSVDebug() {
     mutex.lock();
     if (!debug) {
-        prefix = new char[20];
-        sprintf(prefix, "[%lu]", (unsigned long)QCoreApplication::applicationPid());
-	QString pfx = ResourceFinder().getUserResourcePrefix();
-	QDir logdir(QString("%1/%2").arg(pfx).arg("log"));
-	if (!logdir.exists()) logdir.mkpath(logdir.path());
-        logFile = new QFile(logdir.path() + "/debug.log");
-        if (logFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
-            QDebug(QtDebugMsg) << (const char *)prefix
-                               << "Opened debug log file "
-                               << logFile->fileName();
-            debug = new QDebug(logFile);
-        } else {
-            QDebug(QtWarningMsg) << (const char *)prefix
-                                 << "Failed to open debug log file "
-                                 << logFile->fileName()
-                                 << " for writing, using console debug instead";
-            debug = new QDebug(QtDebugMsg);
-            delete logFile;
-            logFile = 0;
-        }
-        *debug << endl << (const char *)prefix << "Log started at "
-               << QDateTime::currentDateTime().toString();
+        debug = new SVDebug();
     }
     mutex.unlock();
+    return *debug;
+}
 
-    QDebug &dref = *debug;
-    return dref << endl << (const char *)prefix;
+SVDebug::SVDebug() :
+    m_prefix(0),
+    m_ok(false),
+    m_eol(false)
+{
+    QString pfx = ResourceFinder().getUserResourcePrefix();
+    QDir logdir(QString("%1/%2").arg(pfx).arg("log"));
+
+    m_prefix = strdup(QString("[%1]")
+                      .arg(QCoreApplication::applicationPid())
+                      .toLatin1().data());
+
+    //!!! what to do if mkpath fails?
+    if (!logdir.exists()) logdir.mkpath(logdir.path());
+
+    QString fileName = logdir.path() + "/sv-debug.log";
+
+    m_stream.open(fileName.toLocal8Bit().data(), std::ios_base::out);
+
+    if (!m_stream) {
+        QDebug(QtWarningMsg) << (const char *)m_prefix
+                             << "Failed to open debug log file "
+                             << fileName << " for writing";
+    } else {
+        cerr << m_prefix << ": Log file is " << fileName << endl;
+        m_ok = true;
+    }
+}
+
+SVDebug::~SVDebug()
+{
+    m_stream.close();
 }
 
 QDebug &
@@ -72,6 +76,8 @@
     return dbg;
 }
 
+#endif
+
 std::ostream &
 operator<<(std::ostream &target, const QString &str)
 {
--- a/base/Debug.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Debug.h	Wed Apr 20 12:06:28 2016 +0100
@@ -13,14 +13,17 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _DEBUG_H_
-#define _DEBUG_H_
+#ifndef SV_DEBUG_H
+#define SV_DEBUG_H
 
 #include <QDebug>
 #include <QTextStream>
 
+#include "RealTime.h"
+
 #include <string>
 #include <iostream>
+#include <fstream>
 
 class QString;
 class QUrl;
@@ -35,19 +38,40 @@
 
 #ifndef NDEBUG
 
-extern QDebug &getSVDebug();
+class SVDebug {
+public:
+    SVDebug();
+    ~SVDebug();
+
+    template <typename T>
+    inline SVDebug &operator<<(const T &t) {
+        if (m_ok) {
+            if (m_eol) {
+                m_stream << m_prefix << " ";
+            }
+            m_stream << t;
+            m_eol = false;
+        }
+        return *this;
+    }
+
+    inline SVDebug &operator<<(QTextStreamFunction) {
+        m_stream << std::endl;
+        m_eol = true;
+        return *this;
+    }
+
+private:
+    std::fstream m_stream;
+    char *m_prefix;
+    bool m_ok;
+    bool m_eol;
+};
+
+extern SVDebug &getSVDebug();
 
 #define SVDEBUG getSVDebug()
 
-template <typename T>
-inline QDebug &operator<<(QDebug &d, const T &t) {
-    QString s;
-    QTextStream ts(&s);
-    ts << t;
-    d << s;
-    return d;
-}
-
 #else
 
 class NoDebug
--- a/base/FrameTimer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/FrameTimer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -16,6 +16,8 @@
 #ifndef _FRAME_TIMER_H_
 #define _FRAME_TIMER_H_
 
+#include "BaseTypes.h"
+
 /**
  * A trivial interface for things that permit retrieving "the current
  * frame".  Implementations of this interface are used, for example,
@@ -25,7 +27,7 @@
 class FrameTimer
 {
 public:
-    virtual unsigned long getFrame() const = 0;
+    virtual sv_frame_t getFrame() const = 0;
 };
 
 #endif
--- a/base/LogRange.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/LogRange.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -21,88 +21,89 @@
 #include <cmath>
 
 void
-LogRange::mapRange(float &min, float &max, float logthresh)
+LogRange::mapRange(double &min, double &max, double logthresh)
 {
     if (min > max) std::swap(min, max);
     if (max == min) max = min + 1;
 
-//    SVDEBUG << "LogRange::mapRange: min = " << min << ", max = " << max << endl;
-
+//    cerr << "LogRange::mapRange: min = " << min << ", max = " << max << endl;
+    
     if (min >= 0.f) {
 
-        max = log10f(max); // we know max != 0
+        max = log10(max); // we know max != 0
 
         if (min == 0.f) min = std::min(logthresh, max);
-        else min = log10f(min);
+        else min = log10(min);
 
-//        SVDEBUG << "LogRange::mapRange: positive: min = " << min << ", max = " << max << endl;
+//        cerr << "LogRange::mapRange: positive: min = " << min << ", max = " << max << endl;
 
     } else if (max <= 0.f) {
         
-        min = log10f(-min); // we know min != 0
+        min = log10(-min); // we know min != 0
         
         if (max == 0.f) max = std::min(logthresh, min);
-        else max = log10f(-max);
+        else max = log10(-max);
         
         std::swap(min, max);
         
-//        SVDEBUG << "LogRange::mapRange: negative: min = " << min << ", max = " << max << endl;
+//        cerr << "LogRange::mapRange: negative: min = " << min << ", max = " << max << endl;
 
     } else {
         
         // min < 0 and max > 0
         
-        max = log10f(std::max(max, -min));
+        max = log10(std::max(max, -min));
         min = std::min(logthresh, max);
 
-//        SVDEBUG << "LogRange::mapRange: spanning: min = " << min << ", max = " << max << endl;
+//        cerr << "LogRange::mapRange: spanning: min = " << min << ", max = " << max << endl;
     }
 
     if (min == max) min = max - 1;
 }        
 
-float
-LogRange::map(float value, float thresh)
+double
+LogRange::map(double value, double thresh)
 {
     if (value == 0.f) return thresh;
-    return log10f(fabsf(value));
+    return log10(fabs(value));
 }
 
-float
-LogRange::unmap(float value)
+double
+LogRange::unmap(double value)
 {
-    return powf(10.0, value);
+    return pow(10.0, value);
 }
 
-static float
-sd(const std::vector<float> &values, size_t start, size_t n)
+static double
+sd(const std::vector<double> &values, int start, int n)
 {
-    float sum = 0.f, mean = 0.f, variance = 0.f;
-    for (size_t i = 0; i < n; ++i) {
+    double sum = 0.f, mean = 0.f, variance = 0.f;
+    for (int i = 0; i < n; ++i) {
         sum += values[start + i];
     }
     mean = sum / n;
-    for (size_t i = 0; i < n; ++i) {
-        float diff = values[start + i] - mean;
+    for (int i = 0; i < n; ++i) {
+        double diff = values[start + i] - mean;
         variance += diff * diff;
     }
     variance = variance / n;
-    return sqrtf(variance);
+    return sqrt(variance);
 }
 
 bool
-LogRange::useLogScale(std::vector<float> values)
+LogRange::useLogScale(std::vector<double> values)
 {
     // Principle: Partition the data into two sets around the median;
     // calculate the standard deviation of each set; if the two SDs
     // are very different, it's likely that a log scale would be good.
 
-    if (values.size() < 4) return false;
+    int n = int(values.size());
+    if (n < 4) return false;
     std::sort(values.begin(), values.end());
-    size_t mi = values.size() / 2;
+    int mi = n / 2;
 
-    float sd0 = sd(values, 0, mi);
-    float sd1 = sd(values, mi, values.size() - mi);
+    double sd0 = sd(values, 0, mi);
+    double sd1 = sd(values, mi, n - mi);
 
     SVDEBUG << "LogRange::useLogScale: sd0 = "
               << sd0 << ", sd1 = " << sd1 << endl;
@@ -111,7 +112,7 @@
 
     // I wonder what method of determining "one sd much bigger than
     // the other" would be appropriate here...
-    if (std::max(sd0, sd1) / std::min(sd0, sd1) > 10.f) return true;
+    if (std::max(sd0, sd1) / std::min(sd0, sd1) > 10.) return true;
     else return false;
 }
     
--- a/base/LogRange.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/LogRange.h	Wed Apr 20 12:06:28 2016 +0100
@@ -28,27 +28,27 @@
      * extents of the logarithmic range.  thresh is the minimum value
      * for the log range, to be used if the linear range spans zero.
      */
-    static void mapRange(float &min, float &max, float thresh = -10);
+    static void mapRange(double &min, double &max, double thresh = -10);
 
     /**
      * Map a value onto a logarithmic range.  This just means taking
      * the base-10 log of the absolute value, or using the threshold
      * value if the absolute value is zero.
      */
-    static float map(float value, float thresh = -10);
+    static double map(double value, double thresh = -10);
 
     /**
      * Map a value from the logarithmic range back again.  This just
      * means taking the value'th power of ten.
      */
-    static float unmap(float value);
+    static double unmap(double value);
 
     /**
      * Estimate whether a set of values would be more properly shown
      * using a logarithmic than a linear scale.  This is only ever
      * going to be a rough guess.
      */
-    static bool useLogScale(std::vector<float> values);
+    static bool useLogScale(std::vector<double> values);
 
 };
 
--- a/base/Pitch.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Pitch.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -19,45 +19,49 @@
 
 #include <cmath>
 
-float
+double
 Pitch::getFrequencyForPitch(int midiPitch,
-			    float centsOffset,
-			    float concertA)
+			    double centsOffset,
+			    double concertA)
 {
     if (concertA <= 0.0) {
         concertA = Preferences::getInstance()->getTuningFrequency();
     }
-    float p = float(midiPitch) + (centsOffset / 100);
-    return concertA * powf(2.0, (p - 69.0) / 12.0);
+    double p = double(midiPitch) + (centsOffset / 100);
+    return concertA * pow(2.0, (p - 69.0) / 12.0);
 }
 
 int
-Pitch::getPitchForFrequency(float frequency,
-			    float *centsOffsetReturn,
-			    float concertA)
+Pitch::getPitchForFrequency(double frequency,
+			    double *centsOffsetReturn,
+			    double concertA)
 {
     if (concertA <= 0.0) {
         concertA = Preferences::getInstance()->getTuningFrequency();
     }
-    float p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0;
+    double p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0;
 
-    int midiPitch = int(p + 0.00001);
-    float centsOffset = (p - midiPitch) * 100.0;
+    int midiPitch = int(round(p));
+    double centsOffset = (p - midiPitch) * 100.0;
 
     if (centsOffset >= 50.0) {
 	midiPitch = midiPitch + 1;
 	centsOffset = -(100.0 - centsOffset);
     }
+    if (centsOffset < -50.0) {
+	midiPitch = midiPitch - 1;
+	centsOffset = (100.0 + centsOffset);
+    }
     
     if (centsOffsetReturn) *centsOffsetReturn = centsOffset;
     return midiPitch;
 }
 
 int
-Pitch::getPitchForFrequencyDifference(float frequencyA,
-                                      float frequencyB,
-                                      float *centsOffsetReturn,
-                                      float concertA)
+Pitch::getPitchForFrequencyDifference(double frequencyA,
+                                      double frequencyB,
+                                      double *centsOffsetReturn,
+                                      double concertA)
 {
     if (concertA <= 0.0) {
         concertA = Preferences::getInstance()->getTuningFrequency();
@@ -67,13 +71,13 @@
         std::swap(frequencyA, frequencyB);
     }
 
-    float pA = 12.0 * (log(frequencyA / (concertA / 2.0)) / log(2.0)) + 57.0;
-    float pB = 12.0 * (log(frequencyB / (concertA / 2.0)) / log(2.0)) + 57.0;
+    double pA = 12.0 * (log(frequencyA / (concertA / 2.0)) / log(2.0)) + 57.0;
+    double pB = 12.0 * (log(frequencyB / (concertA / 2.0)) / log(2.0)) + 57.0;
 
-    float p = pB - pA;
+    double p = pB - pA;
 
     int midiPitch = int(p + 0.00001);
-    float centsOffset = (p - midiPitch) * 100.0;
+    double centsOffset = (p - midiPitch) * 100.0;
 
     if (centsOffset >= 50.0) {
 	midiPitch = midiPitch + 1;
@@ -96,12 +100,24 @@
     "Ab%1", "A%1",  "Bb%1", "B%1"
 };
 
-QString
-Pitch::getPitchLabel(int midiPitch,
-		     float centsOffset,
-		     bool useFlats)
+int
+Pitch::getPitchForNoteAndOctave(int note, int octave)
 {
-    int octave = -2;
+    int baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote();
+    return (octave - baseOctave) * 12 + note;
+}
+
+void
+Pitch::getNoteAndOctaveForPitch(int midiPitch, int &note, int &octave)
+{
+    int baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote();
+
+    octave = baseOctave;
+
+    // Note, this only gets the right octave number at octave
+    // boundaries because Cb is enharmonic with B (not B#) and B# is
+    // enharmonic with C (not Cb). So neither B# nor Cb will be
+    // spelled from a MIDI pitch + flats flag in isolation.
 
     if (midiPitch < 0) {
 	while (midiPitch < 0) {
@@ -109,47 +125,58 @@
 	    --octave;
 	}
     } else {
-	octave = midiPitch / 12 - 2;
+	octave = midiPitch / 12 + baseOctave;
     }
 
-    QString plain = (useFlats ? flatNotes : notes)[midiPitch % 12].arg(octave);
+    note = midiPitch % 12;
+}
 
-    int ic = lrintf(centsOffset);
+QString
+Pitch::getPitchLabel(int midiPitch,
+		     double centsOffset,
+		     bool useFlats)
+{
+    int note, octave;
+    getNoteAndOctaveForPitch(midiPitch, note, octave);
+
+    QString plain = (useFlats ? flatNotes : notes)[note].arg(octave);
+
+    long ic = lrint(centsOffset);
     if (ic == 0) return plain;
     else if (ic > 0) return QString("%1+%2c").arg(plain).arg(ic);
     else return QString("%1%2c").arg(plain).arg(ic);
 }
 
 QString
-Pitch::getPitchLabelForFrequency(float frequency,
-				 float concertA,
+Pitch::getPitchLabelForFrequency(double frequency,
+				 double concertA,
 				 bool useFlats)
 {
     if (concertA <= 0.0) {
         concertA = Preferences::getInstance()->getTuningFrequency();
     }
-    float centsOffset = 0.0;
+    double centsOffset = 0.0;
     int midiPitch = getPitchForFrequency(frequency, &centsOffset, concertA);
     return getPitchLabel(midiPitch, centsOffset, useFlats);
 }
 
 QString
-Pitch::getLabelForPitchRange(int semis, float cents)
+Pitch::getLabelForPitchRange(int semis, double cents)
 {
     if (semis > 0) {
-        while (cents < 0.f) {
+        while (cents < 0.0) {
             --semis;
-            cents += 100.f;
+            cents += 100.0;
         }
     }
     if (semis < 0) {
-        while (cents > 0.f) {
+        while (cents > 0.0) {
             ++semis;
-            cents -= 100.f;
+            cents -= 100.0;
         }
     }
 
-    int ic = lrintf(cents);
+    long ic = lrint(cents);
 
     if (ic == 0) {
         if (semis >= 12) {
@@ -175,10 +202,10 @@
 }
 
 bool
-Pitch::isFrequencyInMidiRange(float frequency,
-                              float concertA)
+Pitch::isFrequencyInMidiRange(double frequency,
+                              double concertA)
 {
-    float centsOffset = 0.0;
+    double centsOffset = 0.0;
     int midiPitch = getPitchForFrequency(frequency, &centsOffset, concertA);
     return (midiPitch >= 0 && midiPitch < 128);
 }
--- a/base/Pitch.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Pitch.h	Wed Apr 20 12:06:28 2016 +0100
@@ -30,9 +30,9 @@
      * for the A at MIDI pitch 69; otherwise use the tuning frequency
      * specified in the application preferences (default 440Hz).
      */
-    static float getFrequencyForPitch(int midiPitch,
-				      float centsOffset = 0,
-				      float concertA = 0.0);
+    static double getFrequencyForPitch(int midiPitch,
+				      double centsOffset = 0,
+				      double concertA = 0.0);
 
     /**
      * Return the nearest MIDI pitch to the given frequency.
@@ -46,9 +46,22 @@
      * for the A at MIDI pitch 69; otherwise use the tuning frequency
      * specified in the application preferences (default 440Hz).
      */
-    static int getPitchForFrequency(float frequency,
-				    float *centsOffsetReturn = 0,
-				    float concertA = 0.0);
+    static int getPitchForFrequency(double frequency,
+				    double *centsOffsetReturn = 0,
+				    double concertA = 0.0);
+
+    /**
+     * Compatibility version of getPitchForFrequency accepting float
+     * pointer argument.
+     */
+    static int getPitchForFrequency(double frequency,
+				    float *centsOffsetReturn,
+				    double concertA = 0.0) {
+        double c;
+        int p = getPitchForFrequency(frequency, &c, concertA);
+        if (centsOffsetReturn) *centsOffsetReturn = float(c);
+        return p;
+    }
 
     /**
      * Return the nearest MIDI pitch range to the given frequency
@@ -64,16 +77,48 @@
      * for the A at MIDI pitch 69; otherwise use the tuning frequency
      * specified in the application preferences (default 440Hz).
      */
-    static int getPitchForFrequencyDifference(float frequencyA,
-                                              float frequencyB,
-                                              float *centsOffsetReturn = 0,
-                                              float concertA = 0.0);
+    static int getPitchForFrequencyDifference(double frequencyA,
+                                              double frequencyB,
+                                              double *centsOffsetReturn = 0,
+                                              double concertA = 0.0);
+
+    /**
+     * Compatibility version of getPitchForFrequencyDifference
+     * accepting float pointer argument.
+     */
+    static int getPitchForFrequencyDifference(double frequencyA,
+                                              double frequencyB,
+                                              float *centsOffsetReturn,
+                                              double concertA = 0.0) {
+        double c;
+        int p = getPitchForFrequencyDifference(frequencyA, frequencyB,
+                                               &c, concertA);
+        if (centsOffsetReturn) *centsOffsetReturn = float(c);
+        return p;
+    }
+    
+    /**
+     * Return the MIDI pitch for the given note number (0-12 where 0
+     * is C) and octave number. The octave numbering system is based
+     * on the application preferences (default is C4 = middle C,
+     * though in previous SV releases that was C3).
+     */
+    static int getPitchForNoteAndOctave(int note, int octave);
+    
+    /**
+     * Return the note number (0-12 where 0 is C) and octave number
+     * for the given MIDI pitch. The octave numbering system is based
+     * on the application preferences (default is C4 = middle C,
+     * though in previous SV releases that was C3).
+     */
+    static void getNoteAndOctaveForPitch(int midiPitch, int &note, int &octave);
 
     /**
      * Return a string describing the given MIDI pitch, with optional
-     * cents offset.  This consists of the note name, octave number
-     * (with MIDI pitch 0 having octave number -2), and optional
-     * cents.
+     * cents offset.  This consists of the note name, octave number,
+     * and optional cents. The octave numbering system is based on the
+     * application preferences (default is C4 = middle C, though in
+     * previous SV releases that was C3).
      *
      * For example, "A#3" (A# in octave 3) or "C2-12c" (C in octave 2,
      * minus 12 cents).
@@ -82,9 +127,9 @@
      * e.g. Bb3 instead of A#3.
      */
     static QString getPitchLabel(int midiPitch,
-				 float centsOffset = 0,
+				 double centsOffset = 0,
 				 bool useFlats = false);
-
+    
     /**
      * Return a string describing the nearest MIDI pitch to the given
      * frequency, with cents offset.
@@ -96,15 +141,15 @@
      * If useFlats is true, spell notes with flats instead of sharps,
      * e.g. Bb3 instead of A#3.
      */
-    static QString getPitchLabelForFrequency(float frequency,
-					     float concertA = 0.0,
+    static QString getPitchLabelForFrequency(double frequency,
+					     double concertA = 0.0,
 					     bool useFlats = false);
 
     /**
      * Return a string describing the given pitch range in octaves,
      * semitones and cents.  This is in the form e.g. "1'2+4c".
      */
-    static QString getLabelForPitchRange(int semis, float cents = 0);
+    static QString getLabelForPitchRange(int semis, double cents = 0);
 
     /**
      * Return true if the given frequency falls within the range of
@@ -117,8 +162,8 @@
      * for the A at MIDI pitch 69; otherwise use the tuning frequency
      * specified in the application preferences (default 440Hz).
      */
-    static bool isFrequencyInMidiRange(float frequency,
-                                       float concertA = 0.0);
+    static bool isFrequencyInMidiRange(double frequency,
+                                       double concertA = 0.0);
 };
 
 
--- a/base/PlayParameterRepository.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/PlayParameterRepository.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -35,36 +35,32 @@
 void
 PlayParameterRepository::addPlayable(const Playable *playable)
 {
-//    cerr << "PlayParameterRepository:addPlayable " << playable <<  endl;
+//    cerr << "PlayParameterRepository:addPlayable playable = " << playable <<  endl;
 
     if (!getPlayParameters(playable)) {
 
 	// Give all playables the same type of play parameters for the
 	// moment
 
-//	    cerr << "PlayParameterRepository: Adding play parameters for " << playable << endl;
+//        cerr << "PlayParameterRepository:addPlayable: Adding play parameters for " << playable << endl;
 
         PlayParameters *params = new PlayParameters;
         m_playParameters[playable] = params;
 
-        params->setPlayPluginId
-            (playable->getDefaultPlayPluginId());
-        
-        params->setPlayPluginConfiguration
-            (playable->getDefaultPlayPluginConfiguration());
+        params->setPlayClipId
+            (playable->getDefaultPlayClipId());
+
+        params->setPlayAudible
+            (playable->getDefaultPlayAudible());
         
         connect(params, SIGNAL(playParametersChanged()),
                 this, SLOT(playParametersChanged()));
         
-        connect(params, SIGNAL(playPluginIdChanged(QString)),
-                this, SLOT(playPluginIdChanged(QString)));
+        connect(params, SIGNAL(playClipIdChanged(QString)),
+                this, SLOT(playClipIdChanged(QString)));
 
-        connect(params, SIGNAL(playPluginConfigurationChanged(QString)),
-                this, SLOT(playPluginConfigurationChanged(QString)));
-        
-//            cerr << "Connected play parameters " << params << " for playable "
-//                      << playable << " to this " << this << endl;
-
+//        cerr << "Connected play parameters " << params << " for playable "
+//                     << playable << " to this " << this << endl;
     }
 }    
 
@@ -108,27 +104,13 @@
 }
 
 void
-PlayParameterRepository::playPluginIdChanged(QString id)
+PlayParameterRepository::playClipIdChanged(QString id)
 {
     PlayParameters *params = dynamic_cast<PlayParameters *>(sender());
     for (PlayableParameterMap::iterator i = m_playParameters.begin();
          i != m_playParameters.end(); ++i) {
         if (i->second == params) {
-            emit playPluginIdChanged(i->first, id);
-            return;
-        }
-    }
-}
-
-void
-PlayParameterRepository::playPluginConfigurationChanged(QString config)
-{
-    PlayParameters *params = dynamic_cast<PlayParameters *>(sender());
-//    SVDEBUG << "PlayParameterRepository::playPluginConfigurationChanged" << endl;
-    for (PlayableParameterMap::iterator i = m_playParameters.begin();
-         i != m_playParameters.end(); ++i) {
-        if (i->second == params) {
-            emit playPluginConfigurationChanged(i->first, config);
+            emit playClipIdChanged(i->first, id);
             return;
         }
     }
@@ -176,15 +158,9 @@
 }
 
 void
-PlayParameterRepository::EditCommand::setPlayPluginId(QString id)
+PlayParameterRepository::EditCommand::setPlayClipId(QString id)
 {
-    m_to.setPlayPluginId(id);
-}
-
-void
-PlayParameterRepository::EditCommand::setPlayPluginConfiguration(QString conf)
-{
-    m_to.setPlayPluginConfiguration(conf);
+    m_to.setPlayClipId(id);
 }
 
 void
@@ -222,13 +198,8 @@
         if (++changed > 1) return multiname;
     }
 
-    if (m_to.getPlayPluginId() != m_from.getPlayPluginId()) {
-        name = tr("Change Playback Plugin");
-        if (++changed > 1) return multiname;
-    }
-
-    if (m_to.getPlayPluginConfiguration() != m_from.getPlayPluginConfiguration()) {
-        name = tr("Configure Playback Plugin");
+    if (m_to.getPlayClipId() != m_from.getPlayClipId()) {
+        name = tr("Change Playback Sample");
         if (++changed > 1) return multiname;
     }
 
--- a/base/PlayParameterRepository.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/PlayParameterRepository.h	Wed Apr 20 12:06:28 2016 +0100
@@ -51,8 +51,7 @@
         void setPlayAudible(bool);
         void setPlayPan(float);
         void setPlayGain(float);
-        void setPlayPluginId(QString);
-        void setPlayPluginConfiguration(QString);
+        void setPlayClipId(QString);
         void execute();
         void unexecute();
         QString getName() const;
@@ -65,13 +64,11 @@
 
 signals:
     void playParametersChanged(PlayParameters *);
-    void playPluginIdChanged(const Playable *, QString);
-    void playPluginConfigurationChanged(const Playable *, QString);
+    void playClipIdChanged(const Playable *, QString);
 
 protected slots:
     void playParametersChanged();
-    void playPluginIdChanged(QString);
-    void playPluginConfigurationChanged(QString);
+    void playClipIdChanged(QString);
 
 protected:
     typedef std::map<const Playable *, PlayParameters *> PlayableParameterMap;
--- a/base/PlayParameters.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/PlayParameters.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -43,15 +43,9 @@
         changed = true;
     }
 
-    if (m_playPluginId != pp->getPlayPluginId()) {
-        m_playPluginId = pp->getPlayPluginId();
-        emit playPluginIdChanged(m_playPluginId);
-        changed = true;
-    }
-    
-    if (m_playPluginConfiguration != pp->getPlayPluginConfiguration()) {
-        m_playPluginConfiguration = pp->getPlayPluginConfiguration();
-        emit playPluginConfigurationChanged(m_playPluginConfiguration);
+    if (m_playClipId != pp->getPlayClipId()) {
+        m_playClipId = pp->getPlayClipId();
+        emit playClipIdChanged(m_playClipId);
         changed = true;
     }
 
@@ -64,18 +58,24 @@
                       QString extraAttributes) const
 {
     stream << indent;
-    stream << QString("<playparameters mute=\"%1\" pan=\"%2\" gain=\"%3\" pluginId=\"%4\" %6")
+    stream << QString("<playparameters mute=\"%1\" pan=\"%2\" gain=\"%3\" clipId=\"%4\" %6")
         .arg(m_playMuted ? "true" : "false")
         .arg(m_playPan)
         .arg(m_playGain)
-        .arg(m_playPluginId)
+        .arg(m_playClipId)
         .arg(extraAttributes);
-    if (m_playPluginConfiguration != "") {
-        stream << ">\n  " << indent << m_playPluginConfiguration
-               << "\n" << indent << "</playparameters>\n";
-    } else {
-        stream << "/>\n";
+
+    stream << ">\n";
+
+    if (m_playClipId != "") {
+        // for backward compatibility
+        stream << indent << "  ";
+        stream << QString("<plugin identifier=\"%1\" program=\"%2\"/>\n")
+            .arg("sample_player")
+            .arg(m_playClipId);
     }
+
+    stream << indent << "</playparameters>\n";
 }
 
 void
@@ -118,24 +118,11 @@
 }
 
 void
-PlayParameters::setPlayPluginId(QString id)
+PlayParameters::setPlayClipId(QString id)
 {
-    if (m_playPluginId != id) {
-        m_playPluginId = id;
-        emit playPluginIdChanged(id);
+    if (m_playClipId != id) {
+        m_playClipId = id;
+        emit playClipIdChanged(id);
         emit playParametersChanged();
     }
 }
-
-void
-PlayParameters::setPlayPluginConfiguration(QString configuration)
-{
-    if (m_playPluginConfiguration != configuration) {
-        m_playPluginConfiguration = configuration;
-//        cerr << "PlayParameters(" << this << "): setPlayPluginConfiguration to \"" << configuration << "\"" << endl;
-        emit playPluginConfigurationChanged(configuration);
-        emit playParametersChanged();
-    }
-}
-
-
--- a/base/PlayParameters.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/PlayParameters.h	Wed Apr 20 12:06:28 2016 +0100
@@ -32,8 +32,7 @@
     virtual float getPlayPan() const { return m_playPan; } // -1.0 -> 1.0
     virtual float getPlayGain() const { return m_playGain; }
 
-    virtual QString getPlayPluginId() const { return m_playPluginId; } 
-    virtual QString getPlayPluginConfiguration() const { return m_playPluginConfiguration; }
+    virtual QString getPlayClipId() const { return m_playClipId; }
 
     virtual void copyFrom(const PlayParameters *);
 
@@ -46,8 +45,7 @@
     virtual void setPlayAudible(bool nonMuted);
     virtual void setPlayPan(float pan);
     virtual void setPlayGain(float gain);
-    virtual void setPlayPluginId(QString id);
-    virtual void setPlayPluginConfiguration(QString configuration);
+    virtual void setPlayClipId(QString id);
 
 signals:
     void playParametersChanged();
@@ -55,15 +53,13 @@
     void playAudibleChanged(bool);
     void playPanChanged(float);
     void playGainChanged(float);
-    void playPluginIdChanged(QString);
-    void playPluginConfigurationChanged(QString);
+    void playClipIdChanged(QString);
 
 protected:
     bool m_playMuted;
     float m_playPan;
     float m_playGain;
-    QString m_playPluginId;
-    QString m_playPluginConfiguration;
+    QString m_playClipId;
 
 private:
     PlayParameters(const PlayParameters &);
--- a/base/Playable.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Playable.h	Wed Apr 20 12:06:28 2016 +0100
@@ -24,8 +24,8 @@
     virtual ~Playable() { }
     
     virtual bool canPlay() const { return false; }
-    virtual QString getDefaultPlayPluginId() const { return ""; }
-    virtual QString getDefaultPlayPluginConfiguration() const { return ""; }
+    virtual QString getDefaultPlayClipId() const { return ""; }
+    virtual bool getDefaultPlayAudible() const { return true; }
 };
 
 #endif
--- a/base/Preferences.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Preferences.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -43,10 +43,14 @@
     m_resampleQuality(1),
     m_omitRecentTemps(true),
     m_tempDirRoot(""),
+    m_fixedSampleRate(0),
     m_resampleOnLoad(false),
+    m_normaliseAudio(false),
     m_viewFontSize(10),
     m_backgroundMode(BackgroundFromTheme),
     m_timeToTextMode(TimeToTextMs),
+    m_showHMS(true),
+    m_octave(4),
     m_showSplash(true)
 {
     QSettings settings;
@@ -55,17 +59,21 @@
         (settings.value("spectrogram-y-smoothing", int(m_spectrogramSmoothing)).toInt());
     m_spectrogramXSmoothing = SpectrogramXSmoothing
         (settings.value("spectrogram-x-smoothing", int(m_spectrogramXSmoothing)).toInt());
-    m_tuningFrequency = settings.value("tuning-frequency", 440.f).toDouble();
+    m_tuningFrequency = settings.value("tuning-frequency", 440.).toDouble();
     m_propertyBoxLayout = PropertyBoxLayout
         (settings.value("property-box-layout", int(VerticallyStacked)).toInt());
     m_windowType = WindowType
         (settings.value("window-type", int(HanningWindow)).toInt());
     m_resampleQuality = settings.value("resample-quality", 1).toInt();
+    m_fixedSampleRate = settings.value("fixed-sample-rate", 0).toDouble();
     m_resampleOnLoad = settings.value("resample-on-load", false).toBool();
+    m_normaliseAudio = settings.value("normalise-audio", false).toBool();
     m_backgroundMode = BackgroundMode
         (settings.value("background-mode", int(BackgroundFromTheme)).toInt());
     m_timeToTextMode = TimeToTextMode
         (settings.value("time-to-text-mode", int(TimeToTextMs)).toInt());
+    m_showHMS = (settings.value("show-hours-minutes-seconds", true)).toBool(); 
+    m_octave = (settings.value("octave-of-middle-c", 4)).toInt();
     m_viewFontSize = settings.value("view-font-size", 10).toInt();
     m_showSplash = settings.value("show-splash", true).toBool();
     settings.endGroup();
@@ -91,9 +99,13 @@
     props.push_back("Resample Quality");
     props.push_back("Omit Temporaries from Recent Files");
     props.push_back("Resample On Load");
+    props.push_back("Normalise Audio");
+    props.push_back("Fixed Sample Rate");
     props.push_back("Temporary Directory Root");
     props.push_back("Background Mode");
     props.push_back("Time To Text Mode");
+    props.push_back("Show Hours And Minutes");
+    props.push_back("Octave Numbering System");
     props.push_back("View Font Size");
     props.push_back("Show Splash Screen");
     return props;
@@ -120,12 +132,18 @@
     if (name == "Resample Quality") {
         return tr("Playback resampler type");
     }
+    if (name == "Normalise Audio") {
+        return tr("Normalise audio signal when reading from audio file");
+    }
     if (name == "Omit Temporaries from Recent Files") {
         return tr("Omit temporaries from Recent Files menu");
     }
     if (name == "Resample On Load") {
         return tr("Resample mismatching files on import");
     }
+    if (name == "Fixed Sample Rate") {
+        return tr("Single fixed sample rate to resample all files to");
+    }
     if (name == "Temporary Directory Root") {
         return tr("Location for cache file directory");
     }
@@ -133,7 +151,13 @@
         return tr("Background colour preference");
     }
     if (name == "Time To Text Mode") {
-        return tr("Time display format");
+        return tr("Time display precision");
+    }
+    if (name == "Show Hours And Minutes") {
+        return tr("Use hours:minutes:seconds format");
+    }
+    if (name == "Octave Numbering System") {
+        return tr("Label middle C as");
     }
     if (name == "View Font Size") {
         return tr("Font size for text overlays");
@@ -165,12 +189,18 @@
     if (name == "Resample Quality") {
         return ValueProperty;
     }
+    if (name == "Normalise Audio") {
+        return ToggleProperty;
+    }
     if (name == "Omit Temporaries from Recent Files") {
         return ToggleProperty;
     }
     if (name == "Resample On Load") {
         return ToggleProperty;
     }
+    if (name == "Fixed Sample Rate") {
+        return ValueProperty;
+    }
     if (name == "Temporary Directory Root") {
         // It's an arbitrary string, we don't have a set of values for this
         return InvalidProperty;
@@ -181,6 +211,12 @@
     if (name == "Time To Text Mode") {
         return ValueProperty;
     }
+    if (name == "Show Hours And Minutes") {
+        return ToggleProperty;
+    }
+    if (name == "Octave Numbering System") {
+        return ValueProperty;
+    }
     if (name == "View Font Size") {
         return RangeProperty;
     }
@@ -232,6 +268,7 @@
 
     if (name == "Omit Temporaries from Recent Files") {
         if (deflt) *deflt = 1;
+        return m_omitRecentTemps ? 1 : 0;
     }
 
     if (name == "Background Mode") {
@@ -248,6 +285,21 @@
         return int(m_timeToTextMode);
     }        
 
+    if (name == "Show Hours And Minutes") {
+        if (deflt) *deflt = 1;
+        return m_showHMS ? 1 : 0;
+    }
+    
+    if (name == "Octave Numbering System") {
+        // we don't support arbitrary octaves in the gui, because we
+        // want to be able to label what the octave system comes
+        // from. so we support 0, 3, 4 and 5.
+        if (min) *min = 0;
+        if (max) *max = 3;
+        if (deflt) *deflt = 2;
+        return int(getSystemWithMiddleCInOctave(m_octave));
+    }
+
     if (name == "View Font Size") {
         if (min) *min = 3;
         if (max) *max = 48;
@@ -257,6 +309,7 @@
 
     if (name == "Show Splash Screen") {
         if (deflt) *deflt = 1;
+        return m_showSplash ? 1 : 0;
     }
 
     return 0;
@@ -322,6 +375,14 @@
         case TimeToText60Frame: return tr("60 FPS");
         }
     }
+    if (name == "Octave Numbering System") {
+        switch (value) {
+        case C0_Centre: return tr("C0 - middle of octave scale");
+        case C3_Logic: return tr("C3 - common MIDI sequencer convention");
+        case C4_ASA: return tr("C4 - ASA American standard");
+        case C5_Sonar: return tr("C5 - used in Cakewalk and others");
+        }
+    }
             
     return "";
 }
@@ -359,6 +420,11 @@
         setBackgroundMode(BackgroundMode(value));
     } else if (name == "Time To Text Mode") {
         setTimeToTextMode(TimeToTextMode(value));
+    } else if (name == "Show Hours And Minutes") {
+        setShowHMS(value ? true : false);
+    } else if (name == "Octave Numbering System") {
+        setOctaveOfMiddleC(getOctaveOfMiddleCInSystem
+                           (OctaveNumberingSystem(value)));
     } else if (name == "View Font Size") {
         setViewFontSize(value);
     } else if (name == "Show Splash Screen") {
@@ -401,7 +467,7 @@
 }
 
 void
-Preferences::setTuningFrequency(float freq)
+Preferences::setTuningFrequency(double freq)
 {
     if (m_tuningFrequency != freq) {
         m_tuningFrequency = freq;
@@ -495,6 +561,32 @@
 }
 
 void
+Preferences::setFixedSampleRate(sv_samplerate_t rate)
+{
+    if (m_fixedSampleRate != rate) {
+        m_fixedSampleRate = rate;
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        settings.setValue("fixed-sample-rate", rate);
+        settings.endGroup();
+        emit propertyChanged("Fixed Sample Rate");
+    }
+}
+
+void
+Preferences::setNormaliseAudio(bool norm)
+{
+    if (m_normaliseAudio != norm) {
+        m_normaliseAudio = norm;
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        settings.setValue("normalise-audio", norm);
+        settings.endGroup();
+        emit propertyChanged("Normalise Audio");
+    }
+}
+
+void
 Preferences::setBackgroundMode(BackgroundMode mode)
 {
     if (m_backgroundMode != mode) {
@@ -525,6 +617,60 @@
 }
 
 void
+Preferences::setShowHMS(bool show)
+{
+    if (m_showHMS != show) {
+
+        m_showHMS = show;
+
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        settings.setValue("show-hours-minutes-seconds", show);
+        settings.endGroup();
+        emit propertyChanged("Show Hours And Minutes");
+    }
+}
+
+void
+Preferences::setOctaveOfMiddleC(int oct)
+{
+    if (m_octave != oct) {
+
+        m_octave = oct;
+
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        settings.setValue("octave-of-middle-c", int(oct));
+        settings.endGroup();
+        emit propertyChanged("Octave Numbering System");
+    }
+}
+
+int
+Preferences::getOctaveOfMiddleCInSystem(OctaveNumberingSystem s)
+{
+    switch (s) {
+    case C0_Centre: return 0;
+    case C3_Logic: return 3;
+    case C4_ASA: return 4;
+    case C5_Sonar: return 5;
+    default: return 4;
+    }
+}
+
+Preferences::OctaveNumberingSystem
+Preferences::getSystemWithMiddleCInOctave(int o)
+{
+    switch (o) {
+    case 0: return C0_Centre;
+    case 3: return C3_Logic;
+    case 4: return C4_ASA;
+    case 5: return C5_Sonar;
+    default: return C4_ASA;
+    }
+}
+
+void
 Preferences::setViewFontSize(int size)
 {
     if (m_viewFontSize != size) {
--- a/base/Preferences.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Preferences.h	Wed Apr 20 12:06:28 2016 +0100
@@ -49,7 +49,7 @@
 
     SpectrogramSmoothing getSpectrogramSmoothing() const { return m_spectrogramSmoothing; }
     SpectrogramXSmoothing getSpectrogramXSmoothing() const { return m_spectrogramXSmoothing; }
-    float getTuningFrequency() const { return m_tuningFrequency; }
+    double getTuningFrequency() const { return m_tuningFrequency; }
     WindowType getWindowType() const { return m_windowType; }
     int getResampleQuality() const { return m_resampleQuality; }
 
@@ -66,8 +66,15 @@
 
     QString getTemporaryDirectoryRoot() const { return m_tempDirRoot; }
 
+    /// If we should always resample audio to the same rate, return it; otherwise (the normal case) return 0
+    sv_samplerate_t getFixedSampleRate() const { return m_fixedSampleRate; }
+
+    /// True if we should resample second or subsequent audio file to match first audio file's rate
     bool getResampleOnLoad() const { return m_resampleOnLoad; }    
     
+    /// True if audio files should be loaded with normalisation (max == 1)
+    bool getNormaliseAudio() const { return m_normaliseAudio; }
+
     enum BackgroundMode {
         BackgroundFromTheme,
         DarkBackground,
@@ -86,6 +93,16 @@
     };
     TimeToTextMode getTimeToTextMode() const { return m_timeToTextMode; }
 
+    bool getShowHMS() const { return m_showHMS; }
+    
+    int getOctaveOfMiddleC() const {
+        // weed out unsupported octaves
+        return getOctaveOfMiddleCInSystem(getSystemWithMiddleCInOctave(m_octave));
+    }
+    int getOctaveOfLowestMIDINote() const {
+        return getOctaveOfMiddleC() - 5;
+    }
+    
     bool getShowSplash() const { return m_showSplash; }
 
 public slots:
@@ -93,15 +110,19 @@
 
     void setSpectrogramSmoothing(SpectrogramSmoothing smoothing);
     void setSpectrogramXSmoothing(SpectrogramXSmoothing smoothing);
-    void setTuningFrequency(float freq);
+    void setTuningFrequency(double freq);
     void setPropertyBoxLayout(PropertyBoxLayout layout);
     void setWindowType(WindowType type);
     void setResampleQuality(int quality);
     void setOmitTempsFromRecentFiles(bool omit);
     void setTemporaryDirectoryRoot(QString tempDirRoot);
+    void setFixedSampleRate(sv_samplerate_t);
     void setResampleOnLoad(bool);
+    void setNormaliseAudio(bool);
     void setBackgroundMode(BackgroundMode mode);
     void setTimeToTextMode(TimeToTextMode mode);
+    void setShowHMS(bool show);
+    void setOctaveOfMiddleC(int oct);
     void setViewFontSize(int size);
     void setShowSplash(bool);
 
@@ -111,18 +132,35 @@
 
     static Preferences *m_instance;
 
+    // We don't support arbitrary octaves in the gui, because we want
+    // to be able to label what the octave system comes from. These
+    // are the ones we support. (But we save and load as octave
+    // numbers, so as not to make the prefs format really confusing)
+    enum OctaveNumberingSystem {
+        C0_Centre,
+        C3_Logic,
+        C4_ASA,
+        C5_Sonar
+    };
+    static int getOctaveOfMiddleCInSystem(OctaveNumberingSystem s);
+    static OctaveNumberingSystem getSystemWithMiddleCInOctave(int o);
+
     SpectrogramSmoothing m_spectrogramSmoothing;
     SpectrogramXSmoothing m_spectrogramXSmoothing;
-    float m_tuningFrequency;
+    double m_tuningFrequency;
     PropertyBoxLayout m_propertyBoxLayout;
     WindowType m_windowType;
     int m_resampleQuality;
     bool m_omitRecentTemps;
     QString m_tempDirRoot;
+    sv_samplerate_t m_fixedSampleRate;
     bool m_resampleOnLoad;
+    bool m_normaliseAudio;
     int m_viewFontSize;
     BackgroundMode m_backgroundMode;
     TimeToTextMode m_timeToTextMode;
+    bool m_showHMS;
+    int m_octave;
     bool m_showSplash;
 };
 
--- a/base/Profiler.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Profiler.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -105,7 +105,7 @@
         fprintf(stderr, "\tCPU:  \t%.9g ms/call \t[%d ms total]\n",
                 (((double)pp.second.first * 1000.0 /
 		  (double)pp.first) / CLOCKS_PER_SEC),
-                int((pp.second.first * 1000.0) / CLOCKS_PER_SEC));
+                int((double(pp.second.first) * 1000.0) / CLOCKS_PER_SEC));
 
         fprintf(stderr, "\tReal: \t%s ms      \t[%s ms total]\n",
                 ((pp.second.second / pp.first) * 1000).toString().c_str(),
@@ -118,7 +118,7 @@
 
         fprintf(stderr, "\tWorst:\t%s ms/call \t[%d ms CPU]\n",
                 (wc.second * 1000).toString().c_str(),
-                int((wc.first * 1000.0) / CLOCKS_PER_SEC));
+                int((double(wc.first) * 1000.0) / CLOCKS_PER_SEC));
     }
 
     typedef std::multimap<RealTime, const char *> TimeRMap;
--- a/base/PropertyContainer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/PropertyContainer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -54,7 +54,13 @@
 }
 
 QString
-PropertyContainer::getPropertyValueLabel(const PropertyName &name, int value) const
+PropertyContainer::getPropertyValueLabel(const PropertyName &, int) const
+{
+    return QString();
+}
+
+QString
+PropertyContainer::getPropertyValueIconName(const PropertyName &, int) const
 {
     return QString();
 }
--- a/base/PropertyContainer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/PropertyContainer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -91,6 +91,13 @@
 					  int value) const;
 
     /**
+     * If the given property is a ValueProperty, return the icon to be
+     * used for the given value for that property, if any.
+     */
+    virtual QString getPropertyValueIconName(const PropertyName &,
+                                             int value) const;
+
+    /**
      * If the given property is a RangeProperty, return a new
      * RangeMapper object mapping its integer range onto an underlying
      * floating point value range for human-intelligible display, if
--- a/base/RangeMapper.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/RangeMapper.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -22,7 +22,7 @@
 #include <iostream>
 
 LinearRangeMapper::LinearRangeMapper(int minpos, int maxpos,
-				     float minval, float maxval,
+				     double minval, double maxval,
                                      QString unit, bool inverted) :
     m_minpos(minpos),
     m_maxpos(maxpos),
@@ -36,35 +36,45 @@
 }
 
 int
-LinearRangeMapper::getPositionForValue(float value) const
+LinearRangeMapper::getPositionForValue(double value) const
+{
+    int position = getPositionForValueUnclamped(value);
+    if (position < m_minpos) position = m_minpos;
+    if (position > m_maxpos) position = m_maxpos;
+    return position;
+}
+
+int
+LinearRangeMapper::getPositionForValueUnclamped(double value) const
 {
     int position = m_minpos +
-        lrintf(((value - m_minval) / (m_maxval - m_minval))
-               * (m_maxpos - m_minpos));
-    if (position < m_minpos) position = m_minpos;
-    if (position > m_maxpos) position = m_maxpos;
-//    SVDEBUG << "LinearRangeMapper::getPositionForValue: " << value << " -> "
-//              << position << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", minval " << m_minval << ", maxval " << m_maxval << ")" << endl;
-    if (m_inverted) return m_maxpos - position;
+        int(lrint(((value - m_minval) / (m_maxval - m_minval))
+                  * (m_maxpos - m_minpos)));
+    if (m_inverted) return m_maxpos - (position - m_minpos);
     else return position;
 }
 
-float
+double
 LinearRangeMapper::getValueForPosition(int position) const
 {
-    if (m_inverted) position = m_maxpos - position;
-    float value = m_minval +
-        ((float(position - m_minpos) / float(m_maxpos - m_minpos))
+    if (position < m_minpos) position = m_minpos;
+    if (position > m_maxpos) position = m_maxpos;
+    double value = getValueForPositionUnclamped(position);
+    return value;
+}
+
+double
+LinearRangeMapper::getValueForPositionUnclamped(int position) const
+{
+    if (m_inverted) position = m_maxpos - (position - m_minpos);
+    double value = m_minval +
+        ((double(position - m_minpos) / double(m_maxpos - m_minpos))
          * (m_maxval - m_minval));
-    if (value < m_minval) value = m_minval;
-    if (value > m_maxval) value = m_maxval;
-//    SVDEBUG << "LinearRangeMapper::getValueForPosition: " << position << " -> "
-//              << value << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", minval " << m_minval << ", maxval " << m_maxval << ")" << endl;
     return value;
 }
 
 LogRangeMapper::LogRangeMapper(int minpos, int maxpos,
-                               float minval, float maxval,
+                               double minval, double maxval,
                                QString unit, bool inverted) :
     m_minpos(minpos),
     m_maxpos(maxpos),
@@ -73,55 +83,259 @@
 {
     convertMinMax(minpos, maxpos, minval, maxval, m_minlog, m_ratio);
 
-    cerr << "LogRangeMapper: minpos " << minpos << ", maxpos "
-              << maxpos << ", minval " << minval << ", maxval "
-              << maxval << ", minlog " << m_minlog << ", ratio " << m_ratio
-              << ", unit " << unit << endl;
+//    cerr << "LogRangeMapper: minpos " << minpos << ", maxpos "
+//              << maxpos << ", minval " << minval << ", maxval "
+//              << maxval << ", minlog " << m_minlog << ", ratio " << m_ratio
+//              << ", unit " << unit << endl;
 
     assert(m_maxpos != m_minpos);
 
     m_maxlog = (m_maxpos - m_minpos) / m_ratio + m_minlog;
+
+//    cerr << "LogRangeMapper: maxlog = " << m_maxlog << endl;
 }
 
 void
 LogRangeMapper::convertMinMax(int minpos, int maxpos,
-                              float minval, float maxval,
-                              float &minlog, float &ratio)
+                              double minval, double maxval,
+                              double &minlog, double &ratio)
 {
-    static float thresh = powf(10, -10);
+    static double thresh = powf(10, -10);
     if (minval < thresh) minval = thresh;
-    minlog = log10f(minval);
-    ratio = (maxpos - minpos) / (log10f(maxval) - minlog);
+    minlog = log10(minval);
+    ratio = (maxpos - minpos) / (log10(maxval) - minlog);
 }
 
 void
-LogRangeMapper::convertRatioMinLog(float ratio, float minlog,
+LogRangeMapper::convertRatioMinLog(double ratio, double minlog,
                                    int minpos, int maxpos,
-                                   float &minval, float &maxval)
+                                   double &minval, double &maxval)
 {
-    minval = powf(10, minlog);
-    maxval = powf(10, (maxpos - minpos) / ratio + minlog);
+    minval = pow(10, minlog);
+    maxval = pow(10, (maxpos - minpos) / ratio + minlog);
 }
 
 int
-LogRangeMapper::getPositionForValue(float value) const
+LogRangeMapper::getPositionForValue(double value) const
 {
-    int position = (log10(value) - m_minlog) * m_ratio + m_minpos;
+    int position = getPositionForValueUnclamped(value);
     if (position < m_minpos) position = m_minpos;
     if (position > m_maxpos) position = m_maxpos;
-//    SVDEBUG << "LogRangeMapper::getPositionForValue: " << value << " -> "
-//              << position << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << endl;
-    if (m_inverted) return m_maxpos - position;
+    return position;
+}
+
+int
+LogRangeMapper::getPositionForValueUnclamped(double value) const
+{
+    static double thresh = pow(10, -10);
+    if (value < thresh) value = thresh;
+    int position = int(lrint((log10(value) - m_minlog) * m_ratio)) + m_minpos;
+    if (m_inverted) return m_maxpos - (position - m_minpos);
     else return position;
 }
 
-float
+double
 LogRangeMapper::getValueForPosition(int position) const
 {
-    if (m_inverted) position = m_maxpos - position;
-    float value = powf(10, (position - m_minpos) / m_ratio + m_minlog);
-//    SVDEBUG << "LogRangeMapper::getValueForPosition: " << position << " -> "
-//              << value << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << endl;
+    if (position < m_minpos) position = m_minpos;
+    if (position > m_maxpos) position = m_maxpos;
+    double value = getValueForPositionUnclamped(position);
     return value;
 }
 
+double
+LogRangeMapper::getValueForPositionUnclamped(int position) const
+{
+    if (m_inverted) position = m_maxpos - (position - m_minpos);
+    double value = pow(10, (position - m_minpos) / m_ratio + m_minlog);
+    return value;
+}
+
+InterpolatingRangeMapper::InterpolatingRangeMapper(CoordMap pointMappings,
+                                                   QString unit) :
+    m_mappings(pointMappings),
+    m_unit(unit)
+{
+    for (CoordMap::const_iterator i = m_mappings.begin(); 
+         i != m_mappings.end(); ++i) {
+        m_reverse[i->second] = i->first;
+    }
+}
+
+int
+InterpolatingRangeMapper::getPositionForValue(double value) const
+{
+    int pos = getPositionForValueUnclamped(value);
+    CoordMap::const_iterator i = m_mappings.begin();
+    if (pos < i->second) pos = i->second;
+    i = m_mappings.end(); --i;
+    if (pos > i->second) pos = i->second;
+    return pos;
+}
+
+int
+InterpolatingRangeMapper::getPositionForValueUnclamped(double value) const
+{
+    double p = interpolate(&m_mappings, value);
+    return int(lrint(p));
+}
+
+double
+InterpolatingRangeMapper::getValueForPosition(int position) const
+{
+    double val = getValueForPositionUnclamped(position);
+    CoordMap::const_iterator i = m_mappings.begin();
+    if (val < i->first) val = i->first;
+    i = m_mappings.end(); --i;
+    if (val > i->first) val = i->first;
+    return val;
+}
+
+double
+InterpolatingRangeMapper::getValueForPositionUnclamped(int position) const
+{
+    return interpolate(&m_reverse, position);
+}
+
+template <typename T>
+double
+InterpolatingRangeMapper::interpolate(T *mapping, double value) const
+{
+    // lower_bound: first element which does not compare less than value
+    typename T::const_iterator i =
+        mapping->lower_bound(typename T::key_type(value));
+
+    if (i == mapping->begin()) {
+        // value is less than or equal to first element, so use the
+        // gradient from first to second and extend it
+        ++i;
+    }
+
+    if (i == mapping->end()) {
+        // value is off the end, so use the gradient from penultimate
+        // to ultimate and extend it
+        --i;
+    }
+
+    typename T::const_iterator j = i;
+    --j;
+
+    double gradient = double(i->second - j->second) / double(i->first - j->first);
+
+    return j->second + (value - j->first) * gradient;
+}
+
+AutoRangeMapper::AutoRangeMapper(CoordMap pointMappings,
+                                 QString unit) :
+    m_mappings(pointMappings),
+    m_unit(unit)
+{
+    m_type = chooseMappingTypeFor(m_mappings);
+
+    CoordMap::const_iterator first = m_mappings.begin();
+    CoordMap::const_iterator last = m_mappings.end();
+    --last;
+
+    switch (m_type) {
+    case StraightLine:
+        m_mapper = new LinearRangeMapper(first->second, last->second,
+                                         first->first, last->first,
+                                         unit, false);
+        break;
+    case Logarithmic:
+        m_mapper = new LogRangeMapper(first->second, last->second,
+                                      first->first, last->first,
+                                      unit, false);
+        break;
+    case Interpolating:
+        m_mapper = new InterpolatingRangeMapper(m_mappings, unit);
+        break;
+    }
+}
+
+AutoRangeMapper::~AutoRangeMapper()
+{
+    delete m_mapper;
+}
+
+AutoRangeMapper::MappingType
+AutoRangeMapper::chooseMappingTypeFor(const CoordMap &mappings)
+{
+    // how do we work out whether a linear/log mapping is "close enough"?
+
+    CoordMap::const_iterator first = mappings.begin();
+    CoordMap::const_iterator last = mappings.end();
+    --last;
+
+    LinearRangeMapper linm(first->second, last->second,
+                           first->first, last->first,
+                           "", false);
+
+    bool inadequate = false;
+
+    for (CoordMap::const_iterator i = mappings.begin();
+         i != mappings.end(); ++i) {
+        int candidate = linm.getPositionForValue(i->first);
+        int diff = candidate - i->second;
+        if (diff < 0) diff = -diff;
+        if (diff > 1) {
+//            cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
+//                 << ", straight-line mapping inadequate" << endl;
+            inadequate = true;
+            break;
+        }
+    }
+
+    if (!inadequate) {
+        return StraightLine;
+    }
+
+    LogRangeMapper logm(first->second, last->second,
+                        first->first, last->first,
+                        "", false);
+
+    inadequate = false;
+
+    for (CoordMap::const_iterator i = mappings.begin();
+         i != mappings.end(); ++i) {
+        int candidate = logm.getPositionForValue(i->first);
+        int diff = candidate - i->second;
+        if (diff < 0) diff = -diff;
+        if (diff > 1) {
+//            cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
+//                 << ", log mapping inadequate" << endl;
+            inadequate = true;
+            break;
+        }
+    }
+
+    if (!inadequate) {
+        return Logarithmic;
+    }
+
+    return Interpolating;
+}
+
+int
+AutoRangeMapper::getPositionForValue(double value) const
+{
+    return m_mapper->getPositionForValue(value);
+}
+
+double
+AutoRangeMapper::getValueForPosition(int position) const
+{
+    return m_mapper->getValueForPosition(position);
+}
+
+int
+AutoRangeMapper::getPositionForValueUnclamped(double value) const
+{
+    return m_mapper->getPositionForValueUnclamped(value);
+}
+
+double
+AutoRangeMapper::getValueForPositionUnclamped(int position) const
+{
+    return m_mapper->getValueForPositionUnclamped(position);
+}
--- a/base/RangeMapper.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/RangeMapper.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,14 +19,48 @@
 #include <QString>
 
 #include "Debug.h"
-
+#include <map>
 
 class RangeMapper 
 {
 public:
     virtual ~RangeMapper() { }
-    virtual int getPositionForValue(float value) const = 0;
-    virtual float getValueForPosition(int position) const = 0;
+
+    /**
+     * Return the position that maps to the given value, rounding to
+     * the nearest position and clamping to the minimum and maximum
+     * extents of the mapper's positional range.
+     */
+    virtual int getPositionForValue(double value) const = 0;
+
+    /**
+     * Return the position that maps to the given value, rounding to
+     * the nearest 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 positional
+     * range. (The mapping outside that range is not guaranteed to be
+     * exact, except if the mapper is a linear one.)
+     */
+    virtual int getPositionForValueUnclamped(double value) const = 0;
+
+    /**
+     * Return the value mapped from the given position, clamping to
+     * the minimum and maximum extents of the mapper's value range.
+     */
+    virtual double getValueForPosition(int position) const = 0;
+
+    /**
+     * Return the value mapped from the given positionq, 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
+     * guaranteed to be exact, except if the mapper is a linear one.)
+     */
+    virtual double getValueForPositionUnclamped(int position) const = 0;
+
+    /**
+     * Get the unit of the mapper's value range.
+     */
     virtual QString getUnit() const { return ""; }
 };
 
@@ -34,54 +68,188 @@
 class LinearRangeMapper : public RangeMapper
 {
 public:
+    /**
+     * Map values in range minval->maxval linearly into integer range
+     * minpos->maxpos. minval and minpos must be less than maxval and
+     * maxpos respectively. If inverted is true, the range will be
+     * mapped "backwards" (minval to maxpos and maxval to minpos).
+     */
     LinearRangeMapper(int minpos, int maxpos,
-                      float minval, float maxval,
+                      double minval, double maxval,
                       QString unit = "", bool inverted = false);
     
-    virtual int getPositionForValue(float value) const;
-    virtual float getValueForPosition(int position) const;
+    virtual int getPositionForValue(double value) const;
+    virtual int getPositionForValueUnclamped(double value) const;
+
+    virtual double getValueForPosition(int position) const;
+    virtual double getValueForPositionUnclamped(int position) const;
 
     virtual QString getUnit() const { return m_unit; }
 
 protected:
     int m_minpos;
     int m_maxpos;
-    float m_minval;
-    float m_maxval;
+    double m_minval;
+    double m_maxval;
     QString m_unit;
     bool m_inverted;
 };
 
-
 class LogRangeMapper : public RangeMapper
 {
 public:
+    /**
+     * Map values in range minval->maxval into integer range
+     * minpos->maxpos such that logs of the values are mapped
+     * linearly. minval must be greater than zero, and minval and
+     * minpos must be less than maxval and maxpos respectively. If
+     * inverted is true, the range will be mapped "backwards" (minval
+     * to maxpos and maxval to minpos).
+     */
     LogRangeMapper(int minpos, int maxpos,
-                   float minval, float maxval,
+                   double minval, double maxval,
                    QString m_unit = "", bool inverted = false);
 
-    static void convertRatioMinLog(float ratio, float minlog,
+    static void convertRatioMinLog(double ratio, double minlog,
                                    int minpos, int maxpos,
-                                   float &minval, float &maxval);
+                                   double &minval, double &maxval);
 
     static void convertMinMax(int minpos, int maxpos,
-                              float minval, float maxval,
-                              float &ratio, float &minlog);
+                              double minval, double maxval,
+                              double &ratio, double &minlog);
 
-    virtual int getPositionForValue(float value) const;
-    virtual float getValueForPosition(int position) const;
+    virtual int getPositionForValue(double value) const;
+    virtual int getPositionForValueUnclamped(double value) const;
+
+    virtual double getValueForPosition(int position) const;
+    virtual double getValueForPositionUnclamped(int position) const;
 
     virtual QString getUnit() const { return m_unit; }
 
 protected:
     int m_minpos;
     int m_maxpos;
-    float m_ratio;
-    float m_minlog;
-    float m_maxlog;
+    double m_ratio;
+    double m_minlog;
+    double m_maxlog;
     QString m_unit;
     bool m_inverted;
 };
 
+class InterpolatingRangeMapper : public RangeMapper
+{
+public:
+    typedef std::map<double, int> CoordMap;
+
+    /**
+     * Given a series of (value, position) coordinate mappings,
+     * construct a range mapper that maps arbitrary values, in the
+     * range between minimum and maximum of the provided values, onto
+     * coordinates using linear interpolation between the supplied
+     * points.
+     *
+     *!!! todo: Cubic -- more generally useful than linear interpolation
+     *!!! todo: inverted flag
+     *
+     * The set of provided mappings must contain at least two
+     * coordinates.
+     *
+     * It is expected that the values and positions in the coordinate
+     * mappings will both be monotonically increasing (i.e. no
+     * inflections in the mapping curve). Behaviour is undefined if
+     * this is not the case.
+     */
+    InterpolatingRangeMapper(CoordMap pointMappings,
+                             QString unit);
+
+    virtual int getPositionForValue(double value) const;
+    virtual int getPositionForValueUnclamped(double value) const;
+
+    virtual double getValueForPosition(int position) const;
+    virtual double getValueForPositionUnclamped(int position) const;
+
+    virtual QString getUnit() const { return m_unit; }
+
+protected:
+    CoordMap m_mappings;
+    std::map<int, double> m_reverse;
+    QString m_unit;
+
+    template <typename T>
+    double interpolate(T *mapping, double v) const;
+};
+
+class AutoRangeMapper : public RangeMapper
+{
+public:
+    enum MappingType {
+        Interpolating,
+        StraightLine,
+        Logarithmic,
+    };
+
+    typedef std::map<double, int> CoordMap;
+
+    /**
+     * Given a series of (value, position) coordinate mappings,
+     * construct a range mapper that maps arbitrary values, in the
+     * range between minimum and maximum of the provided values, onto
+     * coordinates. 
+     *
+     * The mapping used may be
+     * 
+     *    Interpolating -- an InterpolatingRangeMapper will be used
+     * 
+     *    StraightLine -- a LinearRangeMapper from the minimum to
+     *    maximum value coordinates will be used, ignoring all other
+     *    supplied coordinate mappings
+     * 
+     *    Logarithmic -- a LogRangeMapper from the minimum to
+     *    maximum value coordinates will be used, ignoring all other
+     *    supplied coordinate mappings
+     *
+     * The mapping will be chosen automatically by looking at the
+     * supplied coordinates. If the supplied coordinates fall on a
+     * straight line, a StraightLine mapping will be used; if they
+     * fall on a log curve, a Logarithmic mapping will be used;
+     * otherwise an Interpolating mapping will be used.
+     *
+     *!!! todo: inverted flag
+     *
+     * The set of provided mappings must contain at least two
+     * coordinates, or at least three if the points are not supposed
+     * to be in a straight line.
+     *
+     * It is expected that the values and positions in the coordinate
+     * mappings will both be monotonically increasing (i.e. no
+     * inflections in the mapping curve). Behaviour is undefined if
+     * this is not the case.
+     */
+    AutoRangeMapper(CoordMap pointMappings,
+                    QString unit);
+
+    ~AutoRangeMapper();
+
+    /**
+     * Return the mapping type in use.
+     */
+    MappingType getType() const { return m_type; }
+
+    virtual int getPositionForValue(double value) const;
+    virtual int getPositionForValueUnclamped(double value) const;
+
+    virtual double getValueForPosition(int position) const;
+    virtual double getValueForPositionUnclamped(int position) const;
+
+    virtual QString getUnit() const { return m_unit; }
+
+protected:
+    MappingType m_type;
+    CoordMap m_mappings;
+    QString m_unit;
+    RangeMapper *m_mapper;
+
+    MappingType chooseMappingTypeFor(const CoordMap &);
+};
 
 #endif
--- a/base/RealTime.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/RealTime.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -59,7 +59,11 @@
 RealTime
 RealTime::fromSeconds(double sec)
 {
-    return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5));
+    if (sec >= 0) {
+        return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5));
+    } else {
+        return -fromSeconds(-sec);
+    }
 }
 
 RealTime
@@ -71,7 +75,7 @@
 RealTime
 RealTime::fromTimeval(const struct timeval &tv)
 {
-    return RealTime(tv.tv_sec, tv.tv_usec * 1000);
+    return RealTime(int(tv.tv_sec), int(tv.tv_usec * 1000));
 }
 
 RealTime
@@ -85,12 +89,10 @@
     int i = 0;
 
     const char *s = xsdd.c_str();
-    int len = xsdd.length();
+    int len = int(xsdd.length());
 
     bool negative = false, afterT = false;
 
-    int valstart = 0;
-
     while (i < len) {
 
         if (s[i] == '-') {
@@ -103,9 +105,8 @@
         char *eptr = 0;
 
         if (isdigit(s[i]) || s[i] == '.') {
-            valstart = i;
             value = strtod(&s[i], &eptr);
-            i = eptr - s;
+            i = int(eptr - s);
         }
 
         if (i == len) break;
@@ -151,7 +152,11 @@
 
     t = t + fromSeconds(second);
 
-    return t;
+    if (negative) {
+        return -t;
+    } else {
+        return t;
+    }
 }
 
 double
@@ -207,7 +212,7 @@
 RealTime::fromString(std::string s)
 {
     bool negative = false;
-    bool section = 0;
+    int section = 0;
     std::string ssec, snsec;
 
     for (size_t i = 0; i < s.length(); ++i) {
@@ -253,7 +258,10 @@
     if (*this < RealTime::zeroTime) return "-" + (-*this).toText(fixedDp);
 
     Preferences *p = Preferences::getInstance();
+    bool hms = true;
+    
     if (p) {
+        hms = p->getShowHMS();
         int fps = 0;
         switch (p->getTimeToTextMode()) {
         case Preferences::TimeToTextMs: break;
@@ -264,24 +272,45 @@
         case Preferences::TimeToText50Frame: fps = 50; break;
         case Preferences::TimeToText60Frame: fps = 60; break;
         }
-        if (fps != 0) return toFrameText(fps);
+        if (fps != 0) return toFrameText(fps, hms);
     }
 
+    return toMSText(fixedDp, hms);
+}
+
+static void
+writeSecPart(std::stringstream &out, bool hms, int sec)
+{
+    if (hms) {
+        if (sec >= 3600) {
+            out << (sec / 3600) << ":";
+        }
+
+        if (sec >= 60) {
+            int minutes = (sec % 3600) / 60;
+            if (sec >= 3600 && minutes < 10) out << "0";
+            out << minutes << ":";
+        }
+
+        if (sec >= 10) {
+            out << ((sec % 60) / 10);
+        }
+
+        out << (sec % 10);
+
+    } else {
+        out << sec;
+    }
+}
+
+std::string
+RealTime::toMSText(bool fixedDp, bool hms) const
+{
+    if (*this < RealTime::zeroTime) return "-" + (-*this).toMSText(fixedDp, hms);
+
     std::stringstream out;
 
-    if (sec >= 3600) {
-	out << (sec / 3600) << ":";
-    }
-
-    if (sec >= 60) {
-	out << (sec % 3600) / 60 << ":";
-    }
-
-    if (sec >= 10) {
-	out << ((sec % 60) / 10);
-    }
-
-    out << (sec % 10);
+    writeSecPart(out, hms, sec);
     
     int ms = msec();
 
@@ -310,27 +339,18 @@
 }
 
 std::string
-RealTime::toFrameText(int fps) const
+RealTime::toFrameText(int fps, bool hms) const
 {
-    if (*this < RealTime::zeroTime) return "-" + (-*this).toFrameText(fps);
+    if (*this < RealTime::zeroTime) return "-" + (-*this).toFrameText(fps, hms);
 
     std::stringstream out;
 
-    if (sec >= 3600) {
-	out << (sec / 3600) << ":";
-    }
+    writeSecPart(out, hms, sec);
 
-    if (sec >= 60) {
-	out << (sec % 3600) / 60 << ":";
-    }
-
-    if (sec >= 10) {
-	out << ((sec % 60) / 10);
-    }
-
-    out << (sec % 10);
-    
-    int f = nsec / (ONE_BILLION / fps);
+    // avoid rounding error if fps does not divide into ONE_BILLION
+    int64_t fbig = nsec;
+    fbig *= fps;
+    int f = int(fbig / ONE_BILLION);
 
     int div = 1;
     int n = fps - 1;
@@ -362,19 +382,7 @@
 
     std::stringstream out;
 
-    if (sec >= 3600) {
-	out << (sec / 3600) << ":";
-    }
-
-    if (sec >= 60) {
-	out << (sec % 3600) / 60 << ":";
-    }
-
-    if (sec >= 10) {
-	out << ((sec % 60) / 10);
-    }
-
-    out << (sec % 10);
+    writeSecPart(out, true, sec);
     
     if (sec < 60) {
         out << "s";
@@ -437,24 +445,36 @@
     else return lTotal/rTotal;
 }
 
-long
-RealTime::realTime2Frame(const RealTime &time, unsigned int sampleRate)
+static RealTime
+frame2RealTime_i(sv_frame_t frame, sv_frame_t iSampleRate)
+{
+    if (frame < 0) return -frame2RealTime_i(-frame, iSampleRate);
+
+    RealTime rt;
+    sv_frame_t sec = frame / iSampleRate;
+    rt.sec = int(sec);
+    frame -= sec * iSampleRate;
+    rt.nsec = (int)(((double(frame) * 1000000.0) / double(iSampleRate)) * 1000.0);
+    return rt;
+}
+
+sv_frame_t
+RealTime::realTime2Frame(const RealTime &time, sv_samplerate_t sampleRate)
 {
     if (time < zeroTime) return -realTime2Frame(-time, sampleRate);
     double s = time.sec + double(time.nsec + 1) / 1000000000.0;
-    return long(s * double(sampleRate));
+    return sv_frame_t(s * sampleRate);
 }
 
 RealTime
-RealTime::frame2RealTime(long frame, unsigned int sampleRate)
+RealTime::frame2RealTime(sv_frame_t frame, sv_samplerate_t sampleRate)
 {
-    if (frame < 0) return -frame2RealTime(-frame, sampleRate);
+    if (sampleRate == double(int(sampleRate))) {
+        return frame2RealTime_i(frame, int(sampleRate));
+    }
 
-    RealTime rt;
-    rt.sec = frame / long(sampleRate);
-    frame -= rt.sec * long(sampleRate);
-    rt.nsec = (int)(((double(frame) * 1000000.0) / long(sampleRate)) * 1000.0);
-    return rt;
+    double sec = double(frame) / sampleRate;
+    return fromSeconds(sec);
 }
 
 const RealTime RealTime::zeroTime(0,0);
--- a/base/RealTime.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/RealTime.h	Wed Apr 20 12:06:28 2016 +0100
@@ -18,15 +18,18 @@
    This file copyright 2000-2006 Chris Cannam.
 */
 
-#ifndef _REAL_TIME_H_
-#define _REAL_TIME_H_
+#ifndef SV_REAL_TIME_H
+#define SV_REAL_TIME_H
+
+#include "BaseTypes.h"
 
 #include <iostream>
 #include <string>
 
+#include <vamp-hostsdk/RealTime.h>
+
 struct timeval;
 
-
 /**
  * RealTime represents time values to nanosecond precision
  * with accurate arithmetic and frame-rate conversion functions.
@@ -46,12 +49,16 @@
     RealTime(const RealTime &r) :
 	sec(r.sec), nsec(r.nsec) { }
 
+    RealTime(const Vamp::RealTime &r) :
+	sec(r.sec), nsec(r.nsec) { }
+
     static RealTime fromSeconds(double sec);
     static RealTime fromMilliseconds(int msec);
     static RealTime fromTimeval(const struct timeval &);
     static RealTime fromXsdDuration(std::string xsdd);
 
     double toDouble() const;
+    Vamp::RealTime toVampRealTime() const { return Vamp::RealTime(sec, nsec); }
 
     RealTime &operator=(const RealTime &r) {
 	sec = r.sec; nsec = r.nsec; return *this;
@@ -121,23 +128,55 @@
     static RealTime fromString(std::string);
 
     /**
-     * Return a user-readable string to the nearest millisecond, in a
-     * form like HH:MM:SS.mmm
+     * Return a user-readable string to the nearest millisecond,
+     * typically in a form like HH:MM:SS.mmm. The exact format will
+     * depend on the application preferences for time display
+     * precision and hours:minutes:seconds format -- this function
+     * simply dispatches to toMSText or toFrameText with appropriate
+     * arguments depending on the preferences.
+     *
+     * If fixedDp is true, the result will be padded to 3 dp,
+     * i.e. millisecond resolution, even if the number of milliseconds
+     * is a multiple of 10.
      */
     std::string toText(bool fixedDp = false) const;
 
+    /** 
+     * Return a user-readable string to the nearest millisecond.
+     *
+     * If fixedDp is true, the result will be padded to 3 dp,
+     * i.e. millisecond resolution, even if the number of milliseconds
+     * is a multiple of 10.
+     *
+     * If hms is true, results may be returned in the form
+     * HH:MM:SS.mmm (if the time is large enough). If hms is false,
+     * the result will always be a (fractional) number of seconds.
+     *
+     * Unlike toText, this function does not depend on the application
+     * preferences.
+     */
+    std::string toMSText(bool fixedDp, bool hms) const;
+    
     /**
      * Return a user-readable string in which seconds are divided into
      * frames (presumably at a lower frame rate than audio rate,
      * e.g. 24 or 25 video frames), in a form like HH:MM:SS:FF.  fps
      * gives the number of frames per second, and must be integral
      * (29.97 not supported).
+     *
+     * Unlike toText, this function does not depend on the application
+     * preferences.
      */
-    std::string toFrameText(int fps) const;
+    std::string toFrameText(int fps, bool hms) const;
 
     /**
-     * Return a user-readable string to the nearest second, in a form
-     * like "6s" (for less than a minute) or "2:21" (for more).
+     * Return a user-readable string to the nearest second, in H:M:S
+     * form. Does not include milliseconds or frames. The result will
+     * be suffixed "s" if it contains only seconds (no hours or
+     * minutes).
+     *
+     * Unlike toText, this function does not depend on the application
+     * preferences.
      */
     std::string toSecText() const;
 
@@ -149,12 +188,12 @@
     /**
      * Convert a RealTime into a sample frame at the given sample rate.
      */
-    static long realTime2Frame(const RealTime &r, unsigned int sampleRate);
+    static sv_frame_t realTime2Frame(const RealTime &r, sv_samplerate_t sampleRate);
 
     /**
      * Convert a sample frame at the given sample rate into a RealTime.
      */
-    static RealTime frame2RealTime(long frame, unsigned int sampleRate);
+    static RealTime frame2RealTime(sv_frame_t frame, sv_samplerate_t sampleRate);
 
     static const RealTime zeroTime;
 };
--- a/base/RecentFiles.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/RecentFiles.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -21,7 +21,7 @@
 #include <QSettings>
 #include <QRegExp>
 
-RecentFiles::RecentFiles(QString settingsGroup, size_t maxCount) :
+RecentFiles::RecentFiles(QString settingsGroup, int maxCount) :
     m_settingsGroup(settingsGroup),
     m_maxCount(maxCount)
 {
@@ -40,7 +40,7 @@
     QSettings settings;
     settings.beginGroup(m_settingsGroup);
 
-    for (size_t i = 0; i < 100; ++i) {
+    for (int i = 0; i < 100; ++i) {
         QString key = QString("recent-%1").arg(i);
         QString name = settings.value(key, "").toString();
         if (name == "") break;
@@ -57,10 +57,10 @@
     QSettings settings;
     settings.beginGroup(m_settingsGroup);
 
-    for (size_t i = 0; i < m_maxCount; ++i) {
+    for (int i = 0; i < m_maxCount; ++i) {
         QString key = QString("recent-%1").arg(i);
         QString name = "";
-        if (i < m_names.size()) name = m_names[i];
+        if (i < (int)m_names.size()) name = m_names[i];
         settings.setValue(key, name);
     }
 
@@ -70,7 +70,7 @@
 void
 RecentFiles::truncateAndWrite()
 {
-    while (m_names.size() > m_maxCount) {
+    while (int(m_names.size()) > m_maxCount) {
         m_names.pop_back();
     }
     write();
@@ -80,8 +80,8 @@
 RecentFiles::getRecent() const
 {
     std::vector<QString> names;
-    for (size_t i = 0; i < m_maxCount; ++i) {
-        if (i < m_names.size()) {
+    for (int i = 0; i < m_maxCount; ++i) {
+        if (i < (int)m_names.size()) {
             names.push_back(m_names[i]);
         }
     }
@@ -92,7 +92,7 @@
 RecentFiles::add(QString name)
 {
     bool have = false;
-    for (size_t i = 0; i < m_names.size(); ++i) {
+    for (int i = 0; i < int(m_names.size()); ++i) {
         if (m_names[i] == name) {
             have = true;
             break;
@@ -104,7 +104,7 @@
     } else {
         std::deque<QString> newnames;
         newnames.push_back(name);
-        for (size_t i = 0; i < m_names.size(); ++i) {
+        for (int i = 0; i < int(m_names.size()); ++i) {
             if (m_names[i] == name) continue;
             newnames.push_back(m_names[i]);
         }
--- a/base/RecentFiles.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/RecentFiles.h	Wed Apr 20 12:06:28 2016 +0100
@@ -37,7 +37,7 @@
      * given QSettings group and truncates when the given count of
      * strings is reached.
      */
-    RecentFiles(QString settingsGroup = "RecentFiles", size_t maxCount = 10);
+    RecentFiles(QString settingsGroup = "RecentFiles", int maxCount = 10);
 
     virtual ~RecentFiles();
 
@@ -68,7 +68,7 @@
 
 protected:
     QString m_settingsGroup;
-    size_t m_maxCount;
+    int m_maxCount;
 
     std::deque<QString> m_names;
 
--- a/base/Resampler.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Resampler.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -31,16 +31,16 @@
 class Resampler::D
 {
 public:
-    D(Quality quality, size_t channels, size_t chunkSize);
+    D(Quality quality, int channels, sv_frame_t chunkSize);
     ~D();
 
-    size_t resample(float **in, float **out,
-                    size_t incount, float ratio,
-                    bool final);
+    sv_frame_t resample(float **in, float **out,
+                 sv_frame_t incount, double ratio,
+                 bool final);
 
-    size_t resampleInterleaved(float *in, float *out,
-                               size_t incount, float ratio,
-                               bool final);
+    sv_frame_t resampleInterleaved(float *in, float *out,
+                            sv_frame_t incount, double ratio,
+                            bool final);
 
     void reset();
 
@@ -48,12 +48,12 @@
     SRC_STATE *m_src;
     float *m_iin;
     float *m_iout;
-    size_t m_channels;
-    size_t m_iinsize;
-    size_t m_ioutsize;
+    int m_channels;
+    sv_frame_t m_iinsize;
+    sv_frame_t m_ioutsize;
 };
 
-Resampler::D::D(Quality quality, size_t channels, size_t chunkSize) :
+Resampler::D::D(Quality quality, int channels, sv_frame_t chunkSize) :
     m_src(0),
     m_iin(0),
     m_iout(0),
@@ -89,16 +89,16 @@
     }
 }
 
-size_t
+sv_frame_t
 Resampler::D::resample(float **in, float **out,
-                       size_t incount, float ratio,
+                       sv_frame_t incount, double ratio,
                        bool final)
 {
     if (m_channels == 1) {
         return resampleInterleaved(*in, *out, incount, ratio, final);
     }
 
-    size_t outcount = lrintf(ceilf(incount * ratio));
+    sv_frame_t outcount = lrint(ceil(double(incount) * ratio));
 
     if (incount * m_channels > m_iinsize) {
         m_iinsize = incount * m_channels;
@@ -108,16 +108,16 @@
         m_ioutsize = outcount * m_channels;
         m_iout = (float *)realloc(m_iout, m_ioutsize * sizeof(float));
     }
-    for (size_t i = 0; i < incount; ++i) {
-        for (size_t c = 0; c < m_channels; ++c) {
+    for (sv_frame_t i = 0; i < incount; ++i) {
+        for (int c = 0; c < m_channels; ++c) {
             m_iin[i * m_channels + c] = in[c][i];
         }
     }
     
-    size_t gen = resampleInterleaved(m_iin, m_iout, incount, ratio, final);
+    sv_frame_t gen = resampleInterleaved(m_iin, m_iout, incount, ratio, final);
 
-    for (size_t i = 0; i < gen; ++i) {
-        for (size_t c = 0; c < m_channels; ++c) {
+    for (sv_frame_t i = 0; i < gen; ++i) {
+        for (int c = 0; c < m_channels; ++c) {
             out[c][i] = m_iout[i * m_channels + c];
         }
     }
@@ -125,14 +125,14 @@
     return gen;
 }
 
-size_t
+sv_frame_t
 Resampler::D::resampleInterleaved(float *in, float *out,
-                                  size_t incount, float ratio,
+                                  sv_frame_t incount, double ratio,
                                   bool final)
 {
     SRC_DATA data;
 
-    size_t outcount = lrintf(ceilf(incount * ratio));
+    sv_frame_t outcount = lrint(ceil(double(incount) * ratio));
 
     data.data_in = in;
     data.data_out = out;
@@ -143,7 +143,11 @@
 
     int err = src_process(m_src, &data);
 
-    //!!! check err, respond appropriately
+    if (err) {
+        cerr << "Resampler: ERROR: src_process returned error: " <<
+            src_strerror(err) << endl;
+        return 0;
+    }
 
     if (data.input_frames_used != incount) {
         cerr << "Resampler: NOTE: input_frames_used == " << data.input_frames_used << " (while incount = " << incount << ")" << endl;
@@ -158,7 +162,7 @@
     src_reset(m_src);
 }
 
-Resampler::Resampler(Quality quality, size_t channels, size_t chunkSize)
+Resampler::Resampler(Quality quality, int channels, sv_frame_t chunkSize)
 {
     m_d = new D(quality, channels, chunkSize);
 }
@@ -168,18 +172,18 @@
     delete m_d;
 }
 
-size_t 
+sv_frame_t 
 Resampler::resample(float **in, float **out,
-                    size_t incount, float ratio,
+                    sv_frame_t incount, double ratio,
                     bool final)
 {
     return m_d->resample(in, out, incount, ratio, final);
 }
 
-size_t 
+sv_frame_t 
 Resampler::resampleInterleaved(float *in, float *out,
-                    size_t incount, float ratio,
-                    bool final)
+                               sv_frame_t incount, double ratio,
+                               bool final)
 {
     return m_d->resampleInterleaved(in, out, incount, ratio, final);
 }
--- a/base/Resampler.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Resampler.h	Wed Apr 20 12:06:28 2016 +0100
@@ -21,6 +21,8 @@
 #ifndef _RESAMPLER_H_
 #define _RESAMPLER_H_
 
+#include "BaseTypes.h"
+
 #include <sys/types.h>
 
 class Resampler
@@ -28,16 +30,16 @@
 public:
     enum Quality { Best, FastestTolerable, Fastest };
 
-    Resampler(Quality quality, size_t channels, size_t chunkSize = 0);
+    Resampler(Quality quality, int channels, sv_frame_t chunkSize = 0);
     ~Resampler();
 
-    size_t resample(float **in, float **out,
-                    size_t incount, float ratio,
-                    bool final = false);
+    sv_frame_t resample(float **in, float **out,
+                        sv_frame_t incount, double ratio,
+                        bool final = false);
 
-    size_t resampleInterleaved(float *in, float *out,
-                               size_t incount, float ratio,
-                               bool final = false);
+    sv_frame_t resampleInterleaved(float *in, float *out,
+                                   sv_frame_t incount, double ratio,
+                                   bool final = false);
 
     void reset();
 
--- a/base/ResizeableBitset.h	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +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) {
-    }
-    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) {
-        ((*m_bits)[column >> 3]) |=  (uint8_t(1) << (column & 0x07));
-    }
-
-    void reset(size_t column) {
-        ((*m_bits)[column >> 3]) &= ~(uint8_t(1) << (column & 0x07));
-    }
-
-    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
-
--- a/base/ResourceFinder.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/ResourceFinder.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -27,6 +27,10 @@
 #include <QProcess>
 #include <QCoreApplication>
 
+#if QT_VERSION >= 0x050000
+#include <QStandardPaths>
+#endif
+
 #include <cstdlib>
 #include <iostream>
 
@@ -88,10 +92,12 @@
     return list;
 }
 
-QString
-ResourceFinder::getUserResourcePrefix()
+static QString
+getOldStyleUserResourcePrefix()
 {
 #ifdef Q_OS_WIN32
+    // This is awkward and does not work correctly for non-ASCII home
+    // directory names, hence getNewStyleUserResourcePrefix() below
     char *homedrive = getenv("HOMEDRIVE");
     char *homepath = getenv("HOMEPATH");
     QString home;
@@ -114,7 +120,84 @@
         .arg(home)
         .arg(qApp->applicationName());
 #endif
-#endif    
+#endif
+}
+
+static QString
+getNewStyleUserResourcePrefix()
+{
+#if QT_VERSION >= 0x050000
+    // This is expected to be much more reliable than
+    // getOldStyleUserResourcePrefix(), but it returns a different
+    // directory because it includes the organisation name (which is
+    // fair enough). Hence migrateOldStyleResources() which moves
+    // across any resources found in the old-style path the first time
+    // we look for the new-style one
+    return QStandardPaths::writableLocation(QStandardPaths::DataLocation);
+#else
+    return getOldStyleUserResourcePrefix();
+#endif
+}
+
+static void
+migrateOldStyleResources()
+{
+    QString oldPath = getOldStyleUserResourcePrefix();
+    QString newPath = getNewStyleUserResourcePrefix();
+    
+    if (oldPath != newPath &&
+        QDir(oldPath).exists() &&
+        !QDir(newPath).exists()) {
+
+        QDir d(oldPath);
+        
+        if (!d.mkpath(newPath)) {
+            cerr << "WARNING: Failed to create new-style resource path \""
+                 << newPath << "\" to migrate old resources to" << endl;
+            return;
+        }
+
+        QDir target(newPath);
+
+        bool success = true;
+
+        QStringList entries
+            (d.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot));
+
+        foreach (QString entry, entries) {
+            if (d.rename(entry, target.filePath(entry))) {
+                cerr << "NOTE: Successfully moved resource \""
+                     << entry << "\" from old resource path to new" << endl;
+            } else {
+                cerr << "WARNING: Failed to move old resource \""
+                     << entry << "\" from old location \""
+                     << oldPath << "\" to new location \""
+                     << newPath << "\"" << endl;
+                success = false;
+            }
+        }
+
+        if (success) {
+            if (!d.rmdir(oldPath)) {
+                cerr << "WARNING: Failed to remove old resource path \""
+                     << oldPath << "\" after migrating " << entries.size()
+                     << " resource(s) to new path \"" << newPath
+                     << "\" (directory not empty?)" << endl;
+            } else {
+                cerr << "NOTE: Successfully moved " << entries.size()
+                     << " resource(s) from old resource "
+                     << "path \"" << oldPath << "\" to new path \""
+                     << newPath << "\"" << endl;
+            }
+        }
+    }
+}
+
+QString
+ResourceFinder::getUserResourcePrefix()
+{
+    migrateOldStyleResources();
+    return getNewStyleUserResourcePrefix();
 }
 
 QStringList
@@ -218,7 +301,7 @@
         QString save = QString("%1%2").arg(user).arg(resourceCat);
         QDir saveDir(save);
         if (!saveDir.exists()) {
-            if (!userDir.mkpath(save)) {
+            if (!saveDir.mkpath(save)) {
                 cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << save << "\"" << endl;
                 return "";
             }
--- a/base/RingBuffer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/RingBuffer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -55,7 +55,7 @@
      * power of two, this means n should ideally be some power of two
      * minus one.
      */
-    RingBuffer(size_t n);
+    RingBuffer(int n);
 
     virtual ~RingBuffer();
 
@@ -63,7 +63,7 @@
      * Return the total capacity of the ring buffer in samples.
      * (This is the argument n passed to the constructor.)
      */
-    size_t getSize() const;
+    int getSize() const;
 
     /**
      * Return a new ring buffer (allocated with "new" -- caller must
@@ -74,7 +74,7 @@
      * inconsistent.  If this buffer's data will not fit in the new
      * size, the contents are undefined.
      */
-    RingBuffer<T, N> *resized(size_t newSize) const;
+    RingBuffer<T, N> *resized(int newSize) const;
 
     /**
      * Lock the ring buffer into physical memory.  Returns true
@@ -92,19 +92,19 @@
      * Return the amount of data available for reading by reader R, in
      * samples.
      */
-    size_t getReadSpace(int R = 0) const;
+    int getReadSpace(int R = 0) const;
 
     /**
      * Return the amount of space available for writing, in samples.
      */
-    size_t getWriteSpace() const;
+    int getWriteSpace() const;
 
     /**
      * Read n samples from the buffer, for reader R.  If fewer than n
      * are available, the remainder will be zeroed out.  Returns the
      * number of samples actually read.
      */
-    size_t read(T *destination, size_t n, int R = 0);
+    int read(T *destination, int n, int R = 0);
 
     /**
      * Read n samples from the buffer, for reader R, adding them to
@@ -112,7 +112,7 @@
      * will be left alone.  Returns the number of samples actually
      * read.
      */
-    size_t readAdding(T *destination, size_t n, int R = 0);
+    int readAdding(T *destination, int n, int R = 0);
 
     /**
      * Read one sample from the buffer, for reader R.  If no sample is
@@ -130,7 +130,7 @@
      * n are available, the remainder will be zeroed out.  Returns the
      * number of samples actually read.
      */
-    size_t peek(T *destination, size_t n, int R = 0) const;
+    int peek(T *destination, int n, int R = 0) const;
 
     /**
      * Read one sample from the buffer, if available, without
@@ -146,29 +146,29 @@
      * samples).  Returns the number of samples actually available for
      * discarding.
      */
-    size_t skip(size_t n, int R = 0);
+    int skip(int n, int R = 0);
 
     /**
      * Write n samples to the buffer.  If insufficient space is
      * available, not all samples may actually be written.  Returns
      * the number of samples actually written.
      */
-    size_t write(const T *source, size_t n);
+    int write(const T *source, int n);
 
     /**
      * Write n zero-value samples to the buffer.  If insufficient
      * space is available, not all zeros may actually be written.
      * Returns the number of zeroes actually written.
      */
-    size_t zero(size_t n);
+    int zero(int n);
 
 protected:
-    T      *m_buffer;
-    bool    m_mlocked;
-    size_t  m_writer;
-    size_t *m_readers;
-    size_t  m_size;
-    size_t  m_spare;
+    T   *m_buffer;
+    bool m_mlocked;
+    int  m_writer;
+    int *m_readers;
+    int  m_size;
+    int  m_spare;
 
 private:
     RingBuffer(const RingBuffer &); // not provided
@@ -176,11 +176,11 @@
 };
 
 template <typename T, int N>
-RingBuffer<T, N>::RingBuffer(size_t n) :
+RingBuffer<T, N>::RingBuffer(int n) :
     m_buffer(new T[n + 1]),
     m_mlocked(false),
     m_writer(0),
-    m_readers(new size_t[N]),
+    m_readers(new int[N]),
     m_size(n + 1)
 {
 #ifdef DEBUG_RINGBUFFER
@@ -216,7 +216,7 @@
 }
 
 template <typename T, int N>
-size_t
+int
 RingBuffer<T, N>::getSize() const
 {
 #ifdef DEBUG_RINGBUFFER
@@ -228,7 +228,7 @@
 
 template <typename T, int N>
 RingBuffer<T, N> *
-RingBuffer<T, N>::resized(size_t newSize) const
+RingBuffer<T, N>::resized(int newSize) const
 {
 #ifdef DEBUG_RINGBUFFER
     std::cerr << "RingBuffer<T," << N << ">[" << this << "]::resized(" << newSize << ")" << std::endl;
@@ -270,12 +270,12 @@
 }
 
 template <typename T, int N>
-size_t
+int
 RingBuffer<T, N>::getReadSpace(int R) const
 {
-    size_t writer = m_writer;
-    size_t reader = m_readers[R];
-    size_t space = 0;
+    int writer = m_writer;
+    int reader = m_readers[R];
+    int space = 0;
 
     if (writer > reader) space = writer - reader;
     else space = ((writer + m_size) - reader) % m_size;
@@ -288,17 +288,17 @@
 }
 
 template <typename T, int N>
-size_t
+int
 RingBuffer<T, N>::getWriteSpace() const
 {
-    size_t space = 0;
+    int space = 0;
     for (int i = 0; i < N; ++i) {
-	size_t here = (m_readers[i] + m_size - m_writer - 1) % m_size;
+	int here = (m_readers[i] + m_size - m_writer - 1) % m_size;
 	if (i == 0 || here < space) space = here;
     }
 
 #ifdef DEBUG_RINGBUFFER
-    size_t rs(getReadSpace()), rp(m_readers[0]);
+    int rs(getReadSpace()), rp(m_readers[0]);
 
     std::cerr << "RingBuffer: write space " << space << ", read space "
 	      << rs << ", total " << (space + rs) << ", m_size " << m_size << std::endl;
@@ -313,14 +313,14 @@
 }
 
 template <typename T, int N>
-size_t
-RingBuffer<T, N>::read(T *destination, size_t n, int R)
+int
+RingBuffer<T, N>::read(T *destination, int n, int R)
 {
 #ifdef DEBUG_RINGBUFFER
     std::cerr << "RingBuffer<T," << N << ">[" << this << "]::read(dest, " << n << ", " << R << ")" << std::endl;
 #endif
 
-    size_t available = getReadSpace(R);
+    int available = getReadSpace(R);
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
 	std::cerr << "WARNING: Only " << available << " samples available"
@@ -331,7 +331,7 @@
     }
     if (n == 0) return n;
 
-    size_t here = m_size - m_readers[R];
+    int here = m_size - m_readers[R];
     if (here >= n) {
 	memcpy(destination, m_buffer + m_readers[R], n * sizeof(T));
     } else {
@@ -350,14 +350,14 @@
 }
 
 template <typename T, int N>
-size_t
-RingBuffer<T, N>::readAdding(T *destination, size_t n, int R)
+int
+RingBuffer<T, N>::readAdding(T *destination, int n, int R)
 {
 #ifdef DEBUG_RINGBUFFER
     std::cerr << "RingBuffer<T," << N << ">[" << this << "]::readAdding(dest, " << n << ", " << R << ")" << std::endl;
 #endif
 
-    size_t available = getReadSpace(R);
+    int available = getReadSpace(R);
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
 	std::cerr << "WARNING: Only " << available << " samples available"
@@ -367,17 +367,17 @@
     }
     if (n == 0) return n;
 
-    size_t here = m_size - m_readers[R];
+    int here = m_size - m_readers[R];
 
     if (here >= n) {
-	for (size_t i = 0; i < n; ++i) {
+	for (int i = 0; i < n; ++i) {
 	    destination[i] += (m_buffer + m_readers[R])[i];
 	}
     } else {
-	for (size_t i = 0; i < here; ++i) {
+	for (int i = 0; i < here; ++i) {
 	    destination[i] += (m_buffer + m_readers[R])[i];
 	}
-	for (size_t i = 0; i < (n - here); ++i) {
+	for (int i = 0; i < (n - here); ++i) {
 	    destination[i + here] += m_buffer[i];
 	}
     }
@@ -411,14 +411,14 @@
 }
 
 template <typename T, int N>
-size_t
-RingBuffer<T, N>::peek(T *destination, size_t n, int R) const
+int
+RingBuffer<T, N>::peek(T *destination, int n, int R) const
 {
 #ifdef DEBUG_RINGBUFFER
     std::cerr << "RingBuffer<T," << N << ">[" << this << "]::peek(dest, " << n << ", " << R << ")" << std::endl;
 #endif
 
-    size_t available = getReadSpace(R);
+    int available = getReadSpace(R);
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
 	std::cerr << "WARNING: Only " << available << " samples available"
@@ -429,7 +429,7 @@
     }
     if (n == 0) return n;
 
-    size_t here = m_size - m_readers[R];
+    int here = m_size - m_readers[R];
     if (here >= n) {
 	memcpy(destination, m_buffer + m_readers[R], n * sizeof(T));
     } else {
@@ -466,14 +466,14 @@
 }
 
 template <typename T, int N>
-size_t
-RingBuffer<T, N>::skip(size_t n, int R)
+int
+RingBuffer<T, N>::skip(int n, int R)
 {
 #ifdef DEBUG_RINGBUFFER
     std::cerr << "RingBuffer<T," << N << ">[" << this << "]::skip(" << n << ", " << R << ")" << std::endl;
 #endif
 
-    size_t available = getReadSpace(R);
+    int available = getReadSpace(R);
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
 	std::cerr << "WARNING: Only " << available << " samples available"
@@ -487,14 +487,14 @@
 }
 
 template <typename T, int N>
-size_t
-RingBuffer<T, N>::write(const T *source, size_t n)
+int
+RingBuffer<T, N>::write(const T *source, int n)
 {
 #ifdef DEBUG_RINGBUFFER
     std::cerr << "RingBuffer<T," << N << ">[" << this << "]::write(" << n << ")" << std::endl;
 #endif
 
-    size_t available = getWriteSpace();
+    int available = getWriteSpace();
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
 	std::cerr << "WARNING: Only room for " << available << " samples"
@@ -504,7 +504,7 @@
     }
     if (n == 0) return n;
 
-    size_t here = m_size - m_writer;
+    int here = m_size - m_writer;
     if (here >= n) {
 	memcpy(m_buffer + m_writer, source, n * sizeof(T));
     } else {
@@ -523,14 +523,14 @@
 }
 
 template <typename T, int N>
-size_t
-RingBuffer<T, N>::zero(size_t n)
+int
+RingBuffer<T, N>::zero(int n)
 {
 #ifdef DEBUG_RINGBUFFER
     std::cerr << "RingBuffer<T," << N << ">[" << this << "]::zero(" << n << ")" << std::endl;
 #endif
 
-    size_t available = getWriteSpace();
+    int available = getWriteSpace();
     if (n > available) {
 #ifdef DEBUG_RINGBUFFER
 	std::cerr << "WARNING: Only room for " << available << " samples"
@@ -540,7 +540,7 @@
     }
     if (n == 0) return n;
 
-    size_t here = m_size - m_writer;
+    int here = m_size - m_writer;
     if (here >= n) {
 	memset(m_buffer + m_writer, 0, n * sizeof(T));
     } else {
--- a/base/Scavenger.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Scavenger.h	Wed Apr 20 12:06:28 2016 +0100
@@ -61,17 +61,17 @@
     void scavenge(bool clearNow = false);
 
 protected:
-    typedef std::pair<T *, int> ObjectTimePair;
+    typedef std::pair<T *, time_t> ObjectTimePair;
     typedef std::vector<ObjectTimePair> ObjectTimeList;
     ObjectTimeList m_objects;
-    int m_sec;
+    time_t m_sec;
 
     typedef std::list<T *> ObjectList;
     ObjectList m_excess;
-    int m_lastExcess;
+    time_t m_lastExcess;
     QMutex m_excessMutex;
     void pushExcess(T *);
-    void clearExcess(int);
+    void clearExcess(time_t);
 
     unsigned int m_claimed;
     unsigned int m_scavenged;
@@ -129,7 +129,7 @@
 
     struct timeval tv;
     (void)gettimeofday(&tv, 0);
-    int sec = tv.tv_sec;
+    time_t sec = tv.tv_sec;
 
     for (size_t i = 0; i < m_objects.size(); ++i) {
 	ObjectTimePair &pair = m_objects[i];
@@ -156,7 +156,7 @@
     
     struct timeval tv;
     (void)gettimeofday(&tv, 0);
-    int sec = tv.tv_sec;
+    time_t sec = tv.tv_sec;
 
     for (size_t i = 0; i < m_objects.size(); ++i) {
 	ObjectTimePair &pair = m_objects[i];
@@ -188,7 +188,7 @@
 
 template <typename T>
 void
-Scavenger<T>::clearExcess(int sec)
+Scavenger<T>::clearExcess(time_t sec)
 {
     m_excessMutex.lock();
     for (typename ObjectList::iterator i = m_excess.begin();
--- a/base/Selection.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Selection.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -22,12 +22,12 @@
 {
 }
 
-Selection::Selection(size_t startFrame, size_t endFrame) :
+Selection::Selection(sv_frame_t startFrame, sv_frame_t endFrame) :
     m_startFrame(startFrame),
     m_endFrame(endFrame)
 {
     if (m_startFrame > m_endFrame) {
-	size_t tmp = m_endFrame;
+	sv_frame_t tmp = m_endFrame;
 	m_endFrame = m_startFrame;
 	m_startFrame = tmp;
     }
@@ -59,20 +59,20 @@
     return m_startFrame == m_endFrame;
 }
 
-size_t
+sv_frame_t
 Selection::getStartFrame() const
 {
     return m_startFrame;
 }
 
-size_t
+sv_frame_t
 Selection::getEndFrame() const
 {
     return m_endFrame;
 }
 
 bool
-Selection::contains(size_t frame) const
+Selection::contains(sv_frame_t frame) const
 {
     return (frame >= m_startFrame) && (frame < m_endFrame);
 }
@@ -174,7 +174,7 @@
 }
 
 void
-MultiSelection::getExtents(size_t &startFrame, size_t &endFrame) const
+MultiSelection::getExtents(sv_frame_t &startFrame, sv_frame_t &endFrame) const
 {
     startFrame = 0;
     endFrame = 0;
@@ -193,7 +193,7 @@
 }
 
 Selection
-MultiSelection::getContainingSelection(size_t frame, bool defaultToFollowing) const
+MultiSelection::getContainingSelection(sv_frame_t frame, bool defaultToFollowing) const
 {
     // This scales very badly with the number of selections, but it's
     // more efficient for very small numbers of selections than a more
--- a/base/Selection.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Selection.h	Wed Apr 20 12:06:28 2016 +0100
@@ -20,27 +20,43 @@
 #include <set>
 
 #include "XmlExportable.h"
+#include "BaseTypes.h"
 
+/**
+ * A selection object simply represents a range in time, via start and
+ * end frame.
+ *
+ * The end frame is the index of the frame just *after* the end of the
+ * selection. For example a selection of length 10 frames starting at
+ * time 0 will have start frame 0 and end frame 10. This will be
+ * contiguous with (rather than overlapping with) a selection that
+ * starts at frame 10.
+ *
+ * Any selection with equal start and end frames is empty,
+ * representing "no selection". All empty selections are equal under
+ * the comparison operators. The default constructor makes an empty
+ * selection with start and end frames equal to zero.
+ */
 class Selection
 {
 public:
     Selection();
-    Selection(size_t startFrame, size_t endFrame);
+    Selection(sv_frame_t startFrame, sv_frame_t endFrame);
     Selection(const Selection &);
     Selection &operator=(const Selection &);
     virtual ~Selection();
 
     bool isEmpty() const;
-    size_t getStartFrame() const;
-    size_t getEndFrame() const;
-    bool contains(size_t frame) const;
+    sv_frame_t getStartFrame() const;
+    sv_frame_t getEndFrame() const;
+    bool contains(sv_frame_t frame) const;
 
     bool operator<(const Selection &) const;
     bool operator==(const Selection &) const;
     
 protected:
-    size_t m_startFrame;
-    size_t m_endFrame;
+    sv_frame_t m_startFrame;
+    sv_frame_t m_endFrame;
 };
 
 class MultiSelection : public XmlExportable
@@ -57,7 +73,7 @@
     void removeSelection(const Selection &selection);
     void clearSelections();
 
-    void getExtents(size_t &startFrame, size_t &endFrame) const;
+    void getExtents(sv_frame_t &startFrame, sv_frame_t &endFrame) const;
 
     /**
      * Return the selection that contains a given frame.
@@ -65,7 +81,7 @@
      * selected area, return the next selection after the given frame.
      * Return the empty selection if no appropriate selection is found.
      */
-    Selection getContainingSelection(size_t frame, bool defaultToFollowing) const;
+    Selection getContainingSelection(sv_frame_t frame, bool defaultToFollowing) const;
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
--- a/base/StorageAdviser.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/StorageAdviser.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -24,21 +24,21 @@
 
 //#define DEBUG_STORAGE_ADVISER 1
 
-long StorageAdviser::m_discPlanned = 0;
-long StorageAdviser::m_memoryPlanned = 0;
+size_t StorageAdviser::m_discPlanned = 0;
+size_t StorageAdviser::m_memoryPlanned = 0;
 
 StorageAdviser::Recommendation
 StorageAdviser::m_baseRecommendation = StorageAdviser::NoRecommendation;
 
 StorageAdviser::Recommendation
 StorageAdviser::recommend(Criteria criteria,
-			  int minimumSize,
-			  int maximumSize)
+			  size_t minimumSize,
+			  size_t maximumSize)
 {
 #ifdef DEBUG_STORAGE_ADVISER
-    SVDEBUG << "StorageAdviser::recommend: Criteria " << criteria 
-              << ", minimumSize " << minimumSize
-              << ", maximumSize " << maximumSize << endl;
+    cerr << "StorageAdviser::recommend: Criteria " << criteria 
+         << ", minimumSize " << minimumSize
+         << ", maximumSize " << maximumSize << endl;
 #endif
 
     if (m_baseRecommendation != NoRecommendation) {
@@ -52,17 +52,17 @@
         cerr << "StorageAdviser::recommend: ERROR: Failed to get temporary directory path: " << e.what() << endl;
         return Recommendation(UseMemory | ConserveSpace);
     }
-    int discFree = GetDiscSpaceMBAvailable(path.toLocal8Bit());
-    int memoryFree, memoryTotal;
+    ssize_t discFree = GetDiscSpaceMBAvailable(path.toLocal8Bit());
+    ssize_t memoryFree, memoryTotal;
     GetRealMemoryMBAvailable(memoryFree, memoryTotal);
 
-    if (discFree > m_discPlanned / 1024 + 1) {
+    if (discFree > ssize_t(m_discPlanned / 1024 + 1)) {
         discFree -= m_discPlanned / 1024 + 1;
     } else if (discFree > 0) { // can also be -1 for unknown
         discFree = 0;
     }
 
-    if (memoryFree > m_memoryPlanned / 1024 + 1) {
+    if (memoryFree > ssize_t(m_memoryPlanned / 1024 + 1)) {
         memoryFree -= m_memoryPlanned / 1024 + 1;
     } else if (memoryFree > 0) { // can also be -1 for unknown
         memoryFree = 0;
@@ -87,11 +87,11 @@
     StorageStatus memoryStatus = Unknown;
     StorageStatus discStatus = Unknown;
 
-    int minmb = minimumSize / 1024 + 1;
-    int maxmb = maximumSize / 1024 + 1;
+    ssize_t minmb = ssize_t(minimumSize / 1024 + 1);
+    ssize_t maxmb = ssize_t(maximumSize / 1024 + 1);
 
     if (memoryFree == -1) memoryStatus = Unknown;
-    else if (memoryFree < memoryTotal / 3) memoryStatus = Insufficient;
+    else if (memoryFree < memoryTotal / 3 && memoryFree < 512) memoryStatus = Insufficient;
     else if (minmb > (memoryFree * 3) / 4) memoryStatus = Insufficient;
     else if (maxmb > (memoryFree * 3) / 4) memoryStatus = Marginal;
     else if (minmb > (memoryFree / 3)) memoryStatus = Marginal;
@@ -181,11 +181,15 @@
         }
     }
 
+#ifdef DEBUG_STORAGE_ADVISER
+    cerr << "StorageAdviser: returning recommendation " << recommendation << endl;
+#endif
+    
     return Recommendation(recommendation);
 }
 
 void
-StorageAdviser::notifyPlannedAllocation(AllocationArea area, int size)
+StorageAdviser::notifyPlannedAllocation(AllocationArea area, size_t size)
 {
     if (area == MemoryAllocation) m_memoryPlanned += size;
     else if (area == DiscAllocation) m_discPlanned += size;
@@ -194,7 +198,7 @@
 }
 
 void
-StorageAdviser::notifyDoneAllocation(AllocationArea area, int size)
+StorageAdviser::notifyDoneAllocation(AllocationArea area, size_t size)
 {
     if (area == MemoryAllocation) {
         if (m_memoryPlanned > size) m_memoryPlanned -= size;
--- a/base/StorageAdviser.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/StorageAdviser.h	Wed Apr 20 12:06:28 2016 +0100
@@ -17,6 +17,8 @@
 #ifndef _STORAGE_ADVISER_H_
 #define _STORAGE_ADVISER_H_
 
+#include <cstdlib>
+
 /**
  * A utility class designed to help decide whether to store cache data
  * (for example FFT outputs) in memory or on disk in the TempDirectory.
@@ -57,8 +59,8 @@
      * be nowhere the minimum amount of data can be stored.
      */
     static Recommendation recommend(Criteria criteria,
-                                    int minimumSize,
-                                    int maximumSize);
+                                    size_t minimumSize,
+                                    size_t maximumSize);
 
     enum AllocationArea {
         MemoryAllocation,
@@ -69,14 +71,14 @@
      * Specify that we are planning to use a given amount of storage
      * (in kilobytes), but haven't allocated it yet.
      */
-    static void notifyPlannedAllocation(AllocationArea area, int size);
+    static void notifyPlannedAllocation(AllocationArea area, size_t size);
 
     /**
      * Specify that we have now allocated, or abandoned the allocation
      * of, the given amount (in kilobytes) of a storage area that was
      * previously notified using notifyPlannedAllocation.
      */
-    static void notifyDoneAllocation(AllocationArea area, int size);
+    static void notifyDoneAllocation(AllocationArea area, size_t size);
 
     /**
      * Force all subsequent recommendations to use the (perhaps
@@ -86,8 +88,8 @@
     static void setFixedRecommendation(Recommendation recommendation);
 
 private:
-    static long m_discPlanned;
-    static long m_memoryPlanned;
+    static size_t m_discPlanned;
+    static size_t m_memoryPlanned;
     static Recommendation m_baseRecommendation;
 };
 
--- a/base/StringBits.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/StringBits.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -20,6 +20,10 @@
 
 #include "StringBits.h"
 
+#include "Debug.h"
+
+using namespace std;
+
 double
 StringBits::stringToDoubleLocaleFree(QString s, bool *ok)
 {
@@ -73,6 +77,11 @@
     QStringList tokens;
     QString tok;
 
+    // sep -> just seen a field separator (or start of line)
+    // unq -> in an unquoted field
+    // q1  -> in a single-quoted field
+    // q2  -> in a double-quoted field
+
     enum { sep, unq, q1, q2 } mode = sep;
 
     for (int i = 0; i < s.length(); ++i) {
@@ -83,14 +92,14 @@
 	    switch (mode) {
 	    case sep: mode = q1; break;
 	    case unq: case q2: tok += c; break;
-	    case q1: mode = sep; tokens << tok; tok = ""; break;
+	    case q1: mode = unq; break;
 	    }
 
 	} else if (c == '"') {
 	    switch (mode) {
 	    case sep: mode = q2; break;
 	    case unq: case q1: tok += c; break;
-	    case q2: mode = sep; tokens << tok; tok = ""; break;
+	    case q2: mode = unq; break;
 	    }
 
 	} else if (c == separator || (separator == ' ' && c.isSpace())) {
@@ -105,98 +114,31 @@
 		c = s[i];
 		switch (mode) {
 		case sep: mode = unq; tok += c; break;
-		default: tok += c; break;
+                case unq: case q1: case q2: tok += c; break;
 		}
 	    }
 
 	} else {
 	    switch (mode) {
 	    case sep: mode = unq; tok += c; break;
-	    default: tok += c; break;
+            case unq: case q1: case q2: tok += c; break;
 	    }
 	}
     }
 
-    if (tok != "" || mode != sep) tokens << tok;
+    if (tok != "" || mode != sep) {
+        if (mode == q1) {
+            tokens << ("'" + tok);  // turns out it wasn't quoted after all
+        } else if (mode == q2) {
+            tokens << ("\"" + tok);
+        } else {
+            tokens << tok;
+        }
+    }
+
     return tokens;
 }
 
-/*
-
-void testSplit()
-{
-    QStringList tests;
-    tests << "a b c d";
-    tests << "a \"b c\" d";
-    tests << "a 'b c' d";
-    tests << "a \"b c\\\" d\"";
-    tests << "a 'b c\\' d'";
-    tests << "a \"b c' d\"";
-    tests << "a 'b c\" d'";
-    tests << "aa 'bb cc\" dd'";
-    tests << "a'a 'bb' \\\"cc\" dd\\\"";
-    tests << "  a'a \\\'	 'bb'	 \'	\\\"cc\" ' dd\\\" '";
-
-    for (int j = 0; j < tests.size(); ++j) {
-	cout << endl;
-	cout << tests[j] << endl;
-	cout << "->" << endl << "(";
-	QStringList l = splitQuoted(tests[j], ' ');
-	for (int i = 0; i < l.size(); ++i) {
-	    if (i > 0) cout << ";";
-	    cout << l[i];
-	}
-	cout << ")" << endl;
-    }
-}
-
-*/
-
-/* 
-   Results:
-
-a b c d
-->     
-(a;b;c;d)
-
-a "b c" d
-->       
-(a;b c;d)
-
-a 'b c' d
-->       
-(a;b c;d)
-
-a "b c\" d"
-->         
-(a;b c" d) 
-
-a 'b c\' d'
-->         
-(a;b c' d) 
-
-a "b c' d"
-->        
-(a;b c' d)
-
-a 'b c" d'
-->        
-(a;b c" d)
-
-aa 'bb cc" dd'
-->            
-(aa;bb cc" dd)
-
-a'a 'bb' \"cc" dd\"
-->                 
-(a'a;bb;"cc";dd")  
-
-  a'a \'         'bb'    '      \"cc" ' dd\" '
-->                                            
-(a'a;';bb;      "cc" ;dd";)
-
-*/
-
 QStringList
 StringBits::split(QString line, QChar separator, bool quoted)
 {
--- a/base/TempDirectory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/TempDirectory.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -110,7 +110,7 @@
 
     QString suffix;
     int padlen = 6, attempts = 100;
-    unsigned int r = time(0) ^ getpid();
+    unsigned int r = (unsigned int)(time(0) ^ getpid());
 
     for (int i = 0; i < padlen; ++i) {
         suffix += "X";
--- a/base/ViewManagerBase.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/ViewManagerBase.h	Wed Apr 20 12:06:28 2016 +0100
@@ -37,15 +37,15 @@
 
     virtual void setAudioPlaySource(AudioPlaySource *source) = 0;
 
-    virtual size_t alignPlaybackFrameToReference(size_t) const = 0;
-    virtual size_t alignReferenceToPlaybackFrame(size_t) const = 0;
+    virtual sv_frame_t alignPlaybackFrameToReference(sv_frame_t) const = 0;
+    virtual sv_frame_t alignReferenceToPlaybackFrame(sv_frame_t) const = 0;
 
     virtual const MultiSelection &getSelection() const = 0;
     virtual const MultiSelection::SelectionList &getSelections() const = 0;
-    virtual size_t constrainFrameToSelection(size_t frame) const = 0;
+    virtual sv_frame_t constrainFrameToSelection(sv_frame_t frame) const = 0;
 
     virtual Selection getContainingSelection
-    (size_t frame, bool defaultToFollowing) const = 0;
+    (sv_frame_t frame, bool defaultToFollowing) const = 0;
 
     virtual bool getPlayLoopMode() const = 0;
     virtual bool getPlaySelectionMode() const = 0;
--- a/base/Window.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/Window.h	Wed Apr 20 12:06:28 2016 +0100
@@ -22,7 +22,7 @@
 #include <map>
 #include <cstdlib>
 
-#include <cstdlib>
+#include "system/System.h"
 
 enum WindowType {
     RectangularWindow,
@@ -47,7 +47,7 @@
      * than symmetrical. (A window of size N is equivalent to a
      * symmetrical window of size N+1 with the final element missing.)
      */
-    Window(WindowType type, size_t size) : m_type(type), m_size(size) { encache(); }
+    Window(WindowType type, int size) : m_type(type), m_size(size) { encache(); }
     Window(const Window &w) : m_type(w.m_type), m_size(w.m_size) { encache(); }
     Window &operator=(const Window &w) {
 	if (&w == this) return *this;
@@ -60,14 +60,14 @@
     
     void cut(T *src) const { cut(src, src); }
     void cut(T *src, T *dst) const {
-	for (size_t i = 0; i < m_size; ++i) dst[i] = src[i] * m_cache[i];
+	for (int i = 0; i < m_size; ++i) dst[i] = src[i] * m_cache[i];
     }
 
     T getArea() { return m_area; }
-    T getValue(size_t i) { return m_cache[i]; }
+    T getValue(int i) { return m_cache[i]; }
 
     WindowType getType() const { return m_type; }
-    size_t getSize() const { return m_size; }
+    int getSize() const { return m_size; }
 
     // The names used by these functions are un-translated, for use in
     // e.g. XML I/O.  Use Preferences::getPropertyValueLabel if you
@@ -77,18 +77,18 @@
 
 protected:
     WindowType m_type;
-    size_t m_size;
+    int m_size;
     T *m_cache;
     T m_area;
     
     void encache();
-    void cosinewin(T *, T, T, T, T);
+    void cosinewin(T *, double, double, double, double);
 };
 
 template <typename T>
 void Window<T>::encache()
 {
-    int n = int(m_size);
+    const int n = m_size;
     T *mult = new T[n];
     int i;
     for (i = 0; i < n; ++i) mult[i] = 1.0;
@@ -97,14 +97,14 @@
 		
     case RectangularWindow:
 	for (i = 0; i < n; ++i) {
-	    mult[i] *= 0.5;
+	    mult[i] *= T(0.5);
 	}
 	break;
 	    
     case BartlettWindow:
 	for (i = 0; i < n/2; ++i) {
-	    mult[i] *= (i / T(n/2));
-	    mult[i + n/2] *= (1.0 - (i / T(n/2)));
+	    mult[i] *= T(i) / T(n/2);
+	    mult[i + n/2] *= T(1.0) - T(i) / T(n/2);
 	}
 	break;
 	    
@@ -122,7 +122,7 @@
 	    
     case GaussianWindow:
 	for (i = 0; i < n; ++i) {
-            mult[i] *= pow(2, - pow((i - (n-1)/2.0) / ((n-1)/2.0 / 3), 2));
+            mult[i] *= T(pow(2, - pow((i - (n-1)/2.0) / ((n-1)/2.0 / 3), 2)));
 	}
 	break;
 	    
@@ -130,13 +130,13 @@
     {
         int N = n-1;
         for (i = 0; i < N/4; ++i) {
-            T m = 2 * pow(1.0 - (T(N)/2 - i) / (T(N)/2), 3);
+            T m = T(2 * pow(1.0 - (T(N)/2 - T(i)) / (T(N)/2), 3));
             mult[i] *= m;
             mult[N-i] *= m;
         }
         for (i = N/4; i <= N/2; ++i) {
             int wn = i - N/2;
-            T m = 1.0 - 6 * pow(wn / (T(N)/2), 2) * (1.0 - abs(wn) / (T(N)/2));
+            T m = T(1.0 - 6 * pow(T(wn) / (T(N)/2), 2) * (1.0 - T(abs(wn)) / (T(N)/2)));
             mult[i] *= m;
             mult[N-i] *= m;
         }            
@@ -158,18 +158,18 @@
     for (int i = 0; i < n; ++i) {
         m_area += m_cache[i];
     }
-    m_area /= n;
+    m_area /= T(n);
 }
 
 template <typename T>
-void Window<T>::cosinewin(T *mult, T a0, T a1, T a2, T a3)
+void Window<T>::cosinewin(T *mult, double a0, double a1, double a2, double a3)
 {
-    int n = int(m_size);
+    const int n = m_size;
     for (int i = 0; i < n; ++i) {
-        mult[i] *= (a0
-                    - a1 * cos((2 * M_PI * i) / n)
-                    + a2 * cos((4 * M_PI * i) / n)
-                    - a3 * cos((6 * M_PI * i) / n));
+        mult[i] *= T(a0
+                     - a1 * cos((2 * M_PI * i) / n)
+                     + a2 * cos((4 * M_PI * i) / n)
+                     - a3 * cos((6 * M_PI * i) / n));
     }
 }
 
--- a/base/ZoomConstraint.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/base/ZoomConstraint.h	Wed Apr 20 12:06:28 2016 +0100
@@ -48,7 +48,7 @@
      * summaries at powers-of-two block sizes, return 1024 or 2048
      * depending on the rounding direction supplied.
      */
-    virtual size_t getNearestBlockSize(size_t requestedBlockSize,
+    virtual int getNearestBlockSize(int requestedBlockSize,
 				       RoundingDirection = RoundNearest)
 	const
     {
@@ -58,8 +58,11 @@
 
     /**
      * Return the maximum zoom level within range for this constraint.
+     * This is quite large -- individual views will probably want to
+     * limit how far a user might reasonably zoom out based on other
+     * factors such as the duration of the file.
      */
-    virtual size_t getMaxZoomLevel() const { return 262144; }
+    virtual int getMaxZoomLevel() const { return 4194304; } // 2^22, arbitrarily
 };
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/TestPitch.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,108 @@
+/* -*- 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 TEST_PITCH_H
+#define TEST_PITCH_H
+
+#include "../Pitch.h"
+#include "../Preferences.h"
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+
+using namespace std;
+
+class TestPitch : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void init() {
+	Preferences::getInstance()->setOctaveOfMiddleC(4);
+	Preferences::getInstance()->setTuningFrequency(440);
+    }
+
+    void pitchLabel()
+    {
+	QCOMPARE(Pitch::getPitchLabel(60, 0, false), QString("C4"));
+	QCOMPARE(Pitch::getPitchLabel(69, 0, false), QString("A4"));
+	QCOMPARE(Pitch::getPitchLabel(61, 0, false), QString("C#4"));
+	QCOMPARE(Pitch::getPitchLabel(61, 0, true), QString("Db4"));
+	QCOMPARE(Pitch::getPitchLabel(59, 0, false), QString("B3"));
+	QCOMPARE(Pitch::getPitchLabel(59, 0, true), QString("B3"));
+	QCOMPARE(Pitch::getPitchLabel(0, 0, false), QString("C-1"));
+
+	QCOMPARE(Pitch::getPitchLabel(60, -40, false), QString("C4-40c"));
+	QCOMPARE(Pitch::getPitchLabel(60, 40, false), QString("C4+40c"));
+	QCOMPARE(Pitch::getPitchLabel(58, 4, false), QString("A#3+4c"));
+
+	Preferences::getInstance()->setOctaveOfMiddleC(3);
+
+	QCOMPARE(Pitch::getPitchLabel(60, 0, false), QString("C3"));
+	QCOMPARE(Pitch::getPitchLabel(69, 0, false), QString("A3"));
+	QCOMPARE(Pitch::getPitchLabel(61, 0, false), QString("C#3"));
+	QCOMPARE(Pitch::getPitchLabel(61, 0, true), QString("Db3"));
+	QCOMPARE(Pitch::getPitchLabel(59, 0, false), QString("B2"));
+	QCOMPARE(Pitch::getPitchLabel(59, 0, true), QString("B2"));
+	QCOMPARE(Pitch::getPitchLabel(0, 0, false), QString("C-2"));
+
+	QCOMPARE(Pitch::getPitchLabel(60, -40, false), QString("C3-40c"));
+	QCOMPARE(Pitch::getPitchLabel(60, 40, false), QString("C3+40c"));
+	QCOMPARE(Pitch::getPitchLabel(58, 4, false), QString("A#2+4c"));
+    }
+
+    void pitchLabelForFrequency()
+    {
+	QCOMPARE(Pitch::getPitchLabelForFrequency(440, 440, false), QString("A4"));
+	QCOMPARE(Pitch::getPitchLabelForFrequency(440, 220, false), QString("A5"));
+	QCOMPARE(Pitch::getPitchLabelForFrequency(261.63, 440, false), QString("C4"));
+    }
+
+#define MIDDLE_C 261.6255653005986
+
+    void frequencyForPitch()
+    {
+	QCOMPARE(Pitch::getFrequencyForPitch(60, 0), MIDDLE_C);
+	QCOMPARE(Pitch::getFrequencyForPitch(69, 0), 440.0);
+	QCOMPARE(Pitch::getFrequencyForPitch(60, 0, 220), MIDDLE_C / 2.0);
+	QCOMPARE(Pitch::getFrequencyForPitch(69, 0, 220), 220.0);
+    }
+
+    void pitchForFrequency()
+    {
+	double centsOffset = 0.0;
+	QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, &centsOffset), 60);
+	QCOMPARE(centsOffset, 0.0);
+	QCOMPARE(Pitch::getPitchForFrequency(261.0, &centsOffset), 60);
+	QCOMPARE(int(centsOffset), -4);
+	QCOMPARE(Pitch::getPitchForFrequency(440.0, &centsOffset), 69);
+	QCOMPARE(centsOffset, 0.0);
+    }
+
+    void pitchForFrequencyF()
+    {
+	float centsOffset = 0.f;
+	QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, &centsOffset), 60);
+	QCOMPARE(centsOffset, 0.f);
+	QCOMPARE(Pitch::getPitchForFrequency(261.0, &centsOffset), 60);
+	QCOMPARE(int(centsOffset), -4);
+	QCOMPARE(Pitch::getPitchForFrequency(440.0, &centsOffset), 69);
+	QCOMPARE(centsOffset, 0.f);
+    }
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/TestRangeMapper.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,284 @@
+/* -*- 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 TEST_RANGE_MAPPER_H
+#define TEST_RANGE_MAPPER_H
+
+#include "../RangeMapper.h"
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+
+using namespace std;
+
+class TestRangeMapper : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void linearUpForward()
+    {
+	LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", false);
+	QCOMPARE(rm.getUnit(), QString("x"));
+	QCOMPARE(rm.getPositionForValue(0.5), 1);
+	QCOMPARE(rm.getPositionForValue(4.0), 8);
+	QCOMPARE(rm.getPositionForValue(3.0), 6);
+	QCOMPARE(rm.getPositionForValue(3.1), 6);
+	QCOMPARE(rm.getPositionForValue(3.4), 7);
+	QCOMPARE(rm.getPositionForValue(0.2), 1);
+	QCOMPARE(rm.getPositionForValue(-12), 1);
+	QCOMPARE(rm.getPositionForValue(6.1), 8);
+	QCOMPARE(rm.getPositionForValueUnclamped(3.0), 6);
+	QCOMPARE(rm.getPositionForValueUnclamped(0.2), 0);
+	QCOMPARE(rm.getPositionForValueUnclamped(-12), -24);
+	QCOMPARE(rm.getPositionForValueUnclamped(6.1), 12);
+    }
+
+    void linearDownForward()
+    {
+	LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", true);
+	QCOMPARE(rm.getUnit(), QString("x"));
+	QCOMPARE(rm.getPositionForValue(0.5), 8);
+	QCOMPARE(rm.getPositionForValue(4.0), 1);
+	QCOMPARE(rm.getPositionForValue(3.0), 3);
+	QCOMPARE(rm.getPositionForValue(3.1), 3);
+	QCOMPARE(rm.getPositionForValue(3.4), 2);
+	QCOMPARE(rm.getPositionForValue(0.2), 8);
+	QCOMPARE(rm.getPositionForValue(-12), 8);
+	QCOMPARE(rm.getPositionForValue(6.1), 1);
+	QCOMPARE(rm.getPositionForValueUnclamped(3.0), 3);
+	QCOMPARE(rm.getPositionForValueUnclamped(0.2), 9);
+	QCOMPARE(rm.getPositionForValueUnclamped(-12), 33);
+	QCOMPARE(rm.getPositionForValueUnclamped(6.1), -3);
+    }
+
+    void linearUpBackward()
+    {
+	LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", false);
+	QCOMPARE(rm.getUnit(), QString("x"));
+	QCOMPARE(rm.getValueForPosition(1), 0.5);
+	QCOMPARE(rm.getValueForPosition(8), 4.0);
+	QCOMPARE(rm.getValueForPosition(6), 3.0);
+	QCOMPARE(rm.getValueForPosition(7), 3.5);
+	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1));
+	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8));
+	QCOMPARE(rm.getValueForPositionUnclamped(6), 3.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(0), 0.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(-24), -12.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(12), 6.0);
+    }
+
+    void linearDownBackward()
+    {
+	LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", true);
+	QCOMPARE(rm.getUnit(), QString("x"));
+	QCOMPARE(rm.getValueForPosition(8), 0.5);
+	QCOMPARE(rm.getValueForPosition(1), 4.0);
+	QCOMPARE(rm.getValueForPosition(3), 3.0);
+	QCOMPARE(rm.getValueForPosition(2), 3.5);
+	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1));
+	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8));
+	QCOMPARE(rm.getValueForPositionUnclamped(3), 3.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(9), 0.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(33), -12.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(-3), 6.0);
+    }
+
+    void logUpForward()
+    {
+	LogRangeMapper rm(3, 7, 10, 100000, "x", false);
+	QCOMPARE(rm.getUnit(), QString("x"));
+	QCOMPARE(rm.getPositionForValue(10.0), 3);
+	QCOMPARE(rm.getPositionForValue(100000.0), 7);
+	QCOMPARE(rm.getPositionForValue(1.0), 3);
+	QCOMPARE(rm.getPositionForValue(1000000.0), 7);
+	QCOMPARE(rm.getPositionForValue(1000.0), 5);
+	QCOMPARE(rm.getPositionForValue(900.0), 5);
+	QCOMPARE(rm.getPositionForValue(20000), 6);
+	QCOMPARE(rm.getPositionForValueUnclamped(1.0), 2);
+	QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 8);
+	QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5);
+    }
+
+    void logDownForward()
+    {
+	LogRangeMapper rm(3, 7, 10, 100000, "x", true);
+	QCOMPARE(rm.getUnit(), QString("x"));
+	QCOMPARE(rm.getPositionForValue(10.0), 7);
+	QCOMPARE(rm.getPositionForValue(100000.0), 3);
+	QCOMPARE(rm.getPositionForValue(1.0), 7);
+	QCOMPARE(rm.getPositionForValue(1000000.0), 3);
+	QCOMPARE(rm.getPositionForValue(1000.0), 5);
+	QCOMPARE(rm.getPositionForValue(900.0), 5);
+	QCOMPARE(rm.getPositionForValue(20000), 4);
+	QCOMPARE(rm.getPositionForValueUnclamped(1.0), 8);
+	QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 2);
+	QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5);
+    }
+
+    void logUpBackward()
+    {
+	LogRangeMapper rm(3, 7, 10, 100000, "x", false);
+	QCOMPARE(rm.getUnit(), QString("x"));
+	QCOMPARE(rm.getValueForPosition(3), 10.0);
+	QCOMPARE(rm.getValueForPosition(7), 100000.0);
+	QCOMPARE(rm.getValueForPosition(5), 1000.0);
+	QCOMPARE(rm.getValueForPosition(6), 10000.0);
+	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3));
+	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7));
+	QCOMPARE(rm.getValueForPositionUnclamped(2), 1.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(8), 1000000.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(5), 1000.0);
+    }
+
+    void logDownBackward()
+    {
+	LogRangeMapper rm(3, 7, 10, 100000, "x", true);
+	QCOMPARE(rm.getUnit(), QString("x"));
+	QCOMPARE(rm.getValueForPosition(7), 10.0);
+	QCOMPARE(rm.getValueForPosition(3), 100000.0);
+	QCOMPARE(rm.getValueForPosition(5), 1000.0);
+	QCOMPARE(rm.getValueForPosition(4), 10000.0);
+	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3));
+	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7));
+	QCOMPARE(rm.getValueForPositionUnclamped(8), 1.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(2), 1000000.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(5), 1000.0);
+    }
+
+    void interpolatingForward()
+    {
+	InterpolatingRangeMapper::CoordMap mappings;
+	mappings[1] = 10;
+	mappings[3] = 30;
+	mappings[5] = 70;
+	InterpolatingRangeMapper rm(mappings, "x");
+	QCOMPARE(rm.getUnit(), QString("x"));
+	QCOMPARE(rm.getPositionForValue(1.0), 10);
+	QCOMPARE(rm.getPositionForValue(0.0), 10);
+	QCOMPARE(rm.getPositionForValue(5.0), 70);
+	QCOMPARE(rm.getPositionForValue(6.0), 70);
+	QCOMPARE(rm.getPositionForValue(3.0), 30);
+	QCOMPARE(rm.getPositionForValue(2.5), 25);
+	QCOMPARE(rm.getPositionForValue(4.5), 60);
+	QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0);
+	QCOMPARE(rm.getPositionForValueUnclamped(2.5), 25);
+	QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90);
+    }
+
+    void interpolatingBackward()
+    {
+	InterpolatingRangeMapper::CoordMap mappings;
+	mappings[1] = 10;
+	mappings[3] = 30;
+	mappings[5] = 70;
+	InterpolatingRangeMapper rm(mappings, "x");
+	QCOMPARE(rm.getUnit(), QString("x"));
+	QCOMPARE(rm.getValueForPosition(10), 1.0);
+	QCOMPARE(rm.getValueForPosition(9), 1.0);
+	QCOMPARE(rm.getValueForPosition(70), 5.0);
+	QCOMPARE(rm.getValueForPosition(80), 5.0);
+	QCOMPARE(rm.getValueForPosition(30), 3.0);
+	QCOMPARE(rm.getValueForPosition(25), 2.5);
+	QCOMPARE(rm.getValueForPosition(60), 4.5);
+    }
+
+    void autoLinearForward()
+    {
+	AutoRangeMapper::CoordMap mappings;
+	mappings[0.5] = 1;
+	mappings[4.0] = 8;
+	AutoRangeMapper rm1(mappings, "x");
+	QCOMPARE(rm1.getUnit(), QString("x"));
+	QCOMPARE(rm1.getType(), AutoRangeMapper::StraightLine);
+	QCOMPARE(rm1.getPositionForValue(0.1), 1);
+	QCOMPARE(rm1.getPositionForValue(0.5), 1);
+	QCOMPARE(rm1.getPositionForValue(4.0), 8);
+	QCOMPARE(rm1.getPositionForValue(4.5), 8);
+	QCOMPARE(rm1.getPositionForValue(3.0), 6);
+	QCOMPARE(rm1.getPositionForValue(3.1), 6);
+	QCOMPARE(rm1.getPositionForValueUnclamped(0.1), 0);
+	QCOMPARE(rm1.getPositionForValueUnclamped(3.1), 6);
+	QCOMPARE(rm1.getPositionForValueUnclamped(4.5), 9);
+	mappings[3.0] = 6;
+	mappings[3.5] = 7;
+	AutoRangeMapper rm2(mappings, "x");
+	QCOMPARE(rm2.getUnit(), QString("x"));
+	QCOMPARE(rm2.getType(), AutoRangeMapper::StraightLine);
+	QCOMPARE(rm2.getPositionForValue(0.5), 1);
+	QCOMPARE(rm2.getPositionForValue(4.0), 8);
+	QCOMPARE(rm2.getPositionForValue(3.0), 6);
+	QCOMPARE(rm2.getPositionForValue(3.1), 6);
+    }
+
+    void autoLogForward()
+    {
+	AutoRangeMapper::CoordMap mappings;
+	mappings[10] = 3;
+	mappings[1000] = 5;
+	mappings[100000] = 7;
+	AutoRangeMapper rm1(mappings, "x");
+	QCOMPARE(rm1.getUnit(), QString("x"));
+	QCOMPARE(rm1.getType(), AutoRangeMapper::Logarithmic);
+	QCOMPARE(rm1.getPositionForValue(10.0), 3);
+	QCOMPARE(rm1.getPositionForValue(100000.0), 7);
+	QCOMPARE(rm1.getPositionForValue(1.0), 3);
+	QCOMPARE(rm1.getPositionForValue(1000000.0), 7);
+	QCOMPARE(rm1.getPositionForValue(1000.0), 5);
+	QCOMPARE(rm1.getPositionForValue(900.0), 5);
+	QCOMPARE(rm1.getPositionForValue(20000), 6);
+	QCOMPARE(rm1.getPositionForValueUnclamped(1.0), 2);
+	QCOMPARE(rm1.getPositionForValueUnclamped(900.0), 5);
+	QCOMPARE(rm1.getPositionForValueUnclamped(1000000.0), 8);
+	mappings[100] = 4;
+	AutoRangeMapper rm2(mappings, "x");
+	QCOMPARE(rm2.getUnit(), QString("x"));
+	QCOMPARE(rm2.getType(), AutoRangeMapper::Logarithmic);
+	QCOMPARE(rm2.getPositionForValue(10.0), 3);
+	QCOMPARE(rm2.getPositionForValue(100000.0), 7);
+	QCOMPARE(rm2.getPositionForValue(1.0), 3);
+	QCOMPARE(rm2.getPositionForValue(1000000.0), 7);
+	QCOMPARE(rm2.getPositionForValue(1000.0), 5);
+	QCOMPARE(rm2.getPositionForValue(900.0), 5);
+	QCOMPARE(rm2.getPositionForValue(20000), 6);
+    }
+
+    void autoInterpolatingForward()
+    {
+	AutoRangeMapper::CoordMap mappings;
+	mappings[1] = 10;
+	mappings[3] = 30;
+	mappings[5] = 70;
+	AutoRangeMapper rm(mappings, "x");
+	QCOMPARE(rm.getUnit(), QString("x"));
+	QCOMPARE(rm.getType(), AutoRangeMapper::Interpolating);
+	QCOMPARE(rm.getPositionForValue(1.0), 10);
+	QCOMPARE(rm.getPositionForValue(0.0), 10);
+	QCOMPARE(rm.getPositionForValue(5.0), 70);
+	QCOMPARE(rm.getPositionForValue(6.0), 70);
+	QCOMPARE(rm.getPositionForValue(3.0), 30);
+	QCOMPARE(rm.getPositionForValue(2.5), 25);
+	QCOMPARE(rm.getPositionForValue(4.5), 60);
+	QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0);
+	QCOMPARE(rm.getPositionForValueUnclamped(5.0), 70);
+	QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90);
+    }
+};
+
+#endif
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/TestRealTime.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,417 @@
+/* -*- 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 TEST_REALTIME_H
+#define TEST_REALTIME_H
+
+#include "../RealTime.h"
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+
+using namespace std;
+
+class TestRealTime : public QObject
+{
+    Q_OBJECT
+
+    void compareTexts(string s, const char *e) {
+        QCOMPARE(QString(s.c_str()), QString(e));
+    }
+
+private slots:
+
+#define ONE_MILLION 1000000
+#define ONE_BILLION 1000000000
+    
+    void zero()
+    {
+	QCOMPARE(RealTime(0, 0), RealTime::zeroTime);
+	QCOMPARE(RealTime(0, 0).sec, 0);
+	QCOMPARE(RealTime(0, 0).nsec, 0);
+	QCOMPARE(RealTime(0, 0).msec(), 0);
+	QCOMPARE(RealTime(0, 0).usec(), 0);
+    }
+
+    void ctor()
+    {
+	QCOMPARE(RealTime(0, 0), RealTime(0, 0));
+
+	// wraparounds
+	QCOMPARE(RealTime(0, ONE_BILLION/2), RealTime(1, -ONE_BILLION/2));
+	QCOMPARE(RealTime(0, -ONE_BILLION/2), RealTime(-1, ONE_BILLION/2));
+
+	QCOMPARE(RealTime(1, ONE_BILLION), RealTime(2, 0));
+	QCOMPARE(RealTime(1, -ONE_BILLION), RealTime(0, 0));
+	QCOMPARE(RealTime(-1, ONE_BILLION), RealTime(0, 0));
+	QCOMPARE(RealTime(-1, -ONE_BILLION), RealTime(-2, 0));
+
+	QCOMPARE(RealTime(2, -ONE_BILLION*2), RealTime(0, 0));
+	QCOMPARE(RealTime(2, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2));
+
+	QCOMPARE(RealTime(-2, ONE_BILLION*2), RealTime(0, 0));
+	QCOMPARE(RealTime(-2, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2));
+	
+	QCOMPARE(RealTime(0, 1).sec, 0);
+	QCOMPARE(RealTime(0, 1).nsec, 1);
+	QCOMPARE(RealTime(0, -1).sec, 0);
+	QCOMPARE(RealTime(0, -1).nsec, -1);
+	QCOMPARE(RealTime(1, -1).sec, 0);
+	QCOMPARE(RealTime(1, -1).nsec, ONE_BILLION-1);
+	QCOMPARE(RealTime(-1, 1).sec, 0);
+	QCOMPARE(RealTime(-1, 1).nsec, -ONE_BILLION+1);
+	QCOMPARE(RealTime(-1, -1).sec, -1);
+	QCOMPARE(RealTime(-1, -1).nsec, -1);
+	
+	QCOMPARE(RealTime(2, -ONE_BILLION*2).sec, 0);
+	QCOMPARE(RealTime(2, -ONE_BILLION*2).nsec, 0);
+	QCOMPARE(RealTime(2, -ONE_BILLION/2).sec, 1);
+	QCOMPARE(RealTime(2, -ONE_BILLION/2).nsec, ONE_BILLION/2);
+
+	QCOMPARE(RealTime(-2, ONE_BILLION*2).sec, 0);
+	QCOMPARE(RealTime(-2, ONE_BILLION*2).nsec, 0);
+	QCOMPARE(RealTime(-2, ONE_BILLION/2).sec, -1);
+	QCOMPARE(RealTime(-2, ONE_BILLION/2).nsec, -ONE_BILLION/2);
+    }
+    
+    void fromSeconds()
+    {
+	QCOMPARE(RealTime::fromSeconds(0), RealTime(0, 0));
+
+	QCOMPARE(RealTime::fromSeconds(0.5).sec, 0);
+	QCOMPARE(RealTime::fromSeconds(0.5).nsec, ONE_BILLION/2);
+	QCOMPARE(RealTime::fromSeconds(0.5).usec(), ONE_MILLION/2);
+	QCOMPARE(RealTime::fromSeconds(0.5).msec(), 500);
+	
+	QCOMPARE(RealTime::fromSeconds(0.5), RealTime(0, ONE_BILLION/2));
+	QCOMPARE(RealTime::fromSeconds(1), RealTime(1, 0));
+	QCOMPARE(RealTime::fromSeconds(1.5), RealTime(1, ONE_BILLION/2));
+
+	QCOMPARE(RealTime::fromSeconds(-0.5).sec, 0);
+	QCOMPARE(RealTime::fromSeconds(-0.5).nsec, -ONE_BILLION/2);
+	QCOMPARE(RealTime::fromSeconds(-0.5).usec(), -ONE_MILLION/2);
+	QCOMPARE(RealTime::fromSeconds(-0.5).msec(), -500);
+	
+	QCOMPARE(RealTime::fromSeconds(-1.5).sec, -1);
+	QCOMPARE(RealTime::fromSeconds(-1.5).nsec, -ONE_BILLION/2);
+	QCOMPARE(RealTime::fromSeconds(-1.5).usec(), -ONE_MILLION/2);
+	QCOMPARE(RealTime::fromSeconds(-1.5).msec(), -500);
+	
+	QCOMPARE(RealTime::fromSeconds(-0.5), RealTime(0, -ONE_BILLION/2));
+	QCOMPARE(RealTime::fromSeconds(-1), RealTime(-1, 0));
+	QCOMPARE(RealTime::fromSeconds(-1.5), RealTime(-1, -ONE_BILLION/2));
+    }
+
+    void fromMilliseconds()
+    {
+	QCOMPARE(RealTime::fromMilliseconds(0), RealTime(0, 0));
+	QCOMPARE(RealTime::fromMilliseconds(500), RealTime(0, ONE_BILLION/2));
+	QCOMPARE(RealTime::fromMilliseconds(1000), RealTime(1, 0));
+	QCOMPARE(RealTime::fromMilliseconds(1500), RealTime(1, ONE_BILLION/2));
+
+    	QCOMPARE(RealTime::fromMilliseconds(-0), RealTime(0, 0));
+	QCOMPARE(RealTime::fromMilliseconds(-500), RealTime(0, -ONE_BILLION/2));
+	QCOMPARE(RealTime::fromMilliseconds(-1000), RealTime(-1, 0));
+	QCOMPARE(RealTime::fromMilliseconds(-1500), RealTime(-1, -ONE_BILLION/2));
+    }
+    
+    void fromTimeval()
+    {
+	struct timeval tv;
+
+	tv.tv_sec = 0; tv.tv_usec = 0;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, 0));
+	tv.tv_sec = 0; tv.tv_usec = ONE_MILLION/2;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, ONE_BILLION/2));
+	tv.tv_sec = 1; tv.tv_usec = 0;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, 0));
+	tv.tv_sec = 1; tv.tv_usec = ONE_MILLION/2;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, ONE_BILLION/2));
+
+	tv.tv_sec = 0; tv.tv_usec = -ONE_MILLION/2;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, -ONE_BILLION/2));
+	tv.tv_sec = -1; tv.tv_usec = 0;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, 0));
+	tv.tv_sec = -1; tv.tv_usec = -ONE_MILLION/2;
+	QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, -ONE_BILLION/2));
+    }
+
+    void fromXsdDuration()
+    {
+	QCOMPARE(RealTime::fromXsdDuration("PT0"), RealTime::zeroTime);
+	QCOMPARE(RealTime::fromXsdDuration("PT0S"), RealTime::zeroTime);
+	QCOMPARE(RealTime::fromXsdDuration("PT10S"), RealTime(10, 0));
+	QCOMPARE(RealTime::fromXsdDuration("PT10.5S"), RealTime(10, ONE_BILLION/2));
+	QCOMPARE(RealTime::fromXsdDuration("PT1.5S").sec, 1);
+	QCOMPARE(RealTime::fromXsdDuration("PT1.5S").msec(), 500);
+	QCOMPARE(RealTime::fromXsdDuration("-PT1.5S").sec, -1);
+	QCOMPARE(RealTime::fromXsdDuration("-PT1.5S").msec(), -500);
+	QCOMPARE(RealTime::fromXsdDuration("PT1M30.5S"), RealTime(90, ONE_BILLION/2));
+	QCOMPARE(RealTime::fromXsdDuration("PT1H2M30.5S"), RealTime(3750, ONE_BILLION/2));
+    }
+
+    void toDouble()
+    {
+	QCOMPARE(RealTime(0, 0).toDouble(), 0.0);
+	QCOMPARE(RealTime(0, ONE_BILLION/2).toDouble(), 0.5);
+	QCOMPARE(RealTime(1, 0).toDouble(), 1.0);
+	QCOMPARE(RealTime(1, ONE_BILLION/2).toDouble(), 1.5);
+
+	QCOMPARE(RealTime(0, -ONE_BILLION/2).toDouble(), -0.5);
+	QCOMPARE(RealTime(-1, 0).toDouble(), -1.0);
+	QCOMPARE(RealTime(-1, -ONE_BILLION/2).toDouble(), -1.5);
+    }
+
+    void assign()
+    {
+	RealTime r;
+	r = RealTime(0, 0);
+	QCOMPARE(r, RealTime::zeroTime);
+	r = RealTime(0, ONE_BILLION/2);
+	QCOMPARE(r.toDouble(), 0.5);
+	r = RealTime(-1, -ONE_BILLION/2);
+	QCOMPARE(r.toDouble(), -1.5);
+    }
+
+    void plus()
+    {
+	QCOMPARE(RealTime(0, 0) + RealTime(0, 0), RealTime(0, 0));
+
+	QCOMPARE(RealTime(0, 0) + RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2));
+	QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(0, ONE_BILLION/2), RealTime(1, 0));
+	QCOMPARE(RealTime(1, 0) + RealTime(0, ONE_BILLION/2), RealTime(1, ONE_BILLION/2));
+
+	QCOMPARE(RealTime(0, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2));
+	QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(-1, 0));
+	QCOMPARE(RealTime(-1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2));
+
+    	QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2));
+	QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, 0));
+	QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2));
+
+	QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(-1, 0), RealTime(0, -ONE_BILLION/2));
+	QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(1, 0), RealTime(0, ONE_BILLION/2));
+    }
+    
+    void minus()
+    {
+	QCOMPARE(RealTime(0, 0) - RealTime(0, 0), RealTime(0, 0));
+
+	QCOMPARE(RealTime(0, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, -ONE_BILLION/2));
+	QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(0, ONE_BILLION/2), RealTime(0, 0));
+	QCOMPARE(RealTime(1, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2));
+
+	QCOMPARE(RealTime(0, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2));
+	QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(0, 0));
+	QCOMPARE(RealTime(-1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2));
+
+    	QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2));
+	QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, 0));
+	QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, ONE_BILLION/2));
+
+	QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(-1, 0), RealTime(1, ONE_BILLION/2));
+	QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(1, 0), RealTime(-1, -ONE_BILLION/2));
+    }
+
+    void negate()
+    {
+	QCOMPARE(-RealTime(0, 0), RealTime(0, 0));
+	QCOMPARE(-RealTime(1, 0), RealTime(-1, 0));
+	QCOMPARE(-RealTime(1, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2));
+	QCOMPARE(-RealTime(-1, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2));
+    }
+
+    void compare()
+    {
+	int sec, nsec;
+	for (sec = -2; sec <= 2; sec += 2) {
+	    for (nsec = -1; nsec <= 1; nsec += 1) {
+		QCOMPARE(RealTime(sec, nsec) < RealTime(sec, nsec), false);
+		QCOMPARE(RealTime(sec, nsec) > RealTime(sec, nsec), false);
+		QCOMPARE(RealTime(sec, nsec) == RealTime(sec, nsec), true);
+		QCOMPARE(RealTime(sec, nsec) != RealTime(sec, nsec), false);
+		QCOMPARE(RealTime(sec, nsec) <= RealTime(sec, nsec), true);
+		QCOMPARE(RealTime(sec, nsec) >= RealTime(sec, nsec), true);
+	    }
+	}
+	RealTime prev(-3, 0);
+	for (sec = -2; sec <= 2; sec += 2) {
+	    for (nsec = -1; nsec <= 1; nsec += 1) {
+
+		RealTime curr(sec, nsec);
+
+		QCOMPARE(prev < curr, true);
+		QCOMPARE(prev > curr, false);
+		QCOMPARE(prev == curr, false);
+		QCOMPARE(prev != curr, true);
+		QCOMPARE(prev <= curr, true);
+		QCOMPARE(prev >= curr, false);
+
+		QCOMPARE(curr < prev, false);
+		QCOMPARE(curr > prev, true);
+		QCOMPARE(curr == prev, false);
+		QCOMPARE(curr != prev, true);
+		QCOMPARE(curr <= prev, false);
+		QCOMPARE(curr >= prev, true);
+
+		prev = curr;
+	    }
+	}
+    }
+
+    void frame()
+    {
+        int frames[] = {
+            0, 1, 2047, 2048, 6656, 32767, 32768, 44100, 44101, 999999999
+        };
+        int n = sizeof(frames)/sizeof(frames[0]);
+
+        int rates[] = {
+            1, 2, 8000, 22050, 44100, 44101, 192000
+        };
+        int m = sizeof(rates)/sizeof(rates[0]);
+
+        for (int i = 0; i < n; ++i) {
+            sv_frame_t frame = frames[i];
+            for (int j = 0; j < m; ++j) {
+                int rate = rates[j];
+
+                RealTime rt = RealTime::frame2RealTime(frame, rate);
+                sv_frame_t conv = RealTime::realTime2Frame(rt, rate);
+                QCOMPARE(frame, conv);
+
+                rt = RealTime::frame2RealTime(-frame, rate);
+                conv = RealTime::realTime2Frame(rt, rate);
+                QCOMPARE(-frame, conv);
+            }
+        }
+    }
+    
+    void toText()
+    {
+        // we want to use QStrings, because then the Qt test library
+        // will print out any conflicts. The compareTexts function
+        // does this for us
+
+        int halfSec = ONE_BILLION/2; // nsec
+        
+        RealTime rt = RealTime(0, 0);
+        compareTexts(rt.toMSText(false, false), "0");
+        compareTexts(rt.toMSText(true, false), "0.000");
+        compareTexts(rt.toMSText(false, true), "0");
+        compareTexts(rt.toMSText(true, true), "0.000");
+        compareTexts(rt.toFrameText(24, false), "0:00");
+        compareTexts(rt.toFrameText(24, true), "0:00");
+        compareTexts(rt.toSecText(), "0s");
+
+        rt = RealTime(1, halfSec);
+        compareTexts(rt.toMSText(false, false), "1.5");
+        compareTexts(rt.toMSText(true, false), "1.500");
+        compareTexts(rt.toMSText(false, true), "1.5");
+        compareTexts(rt.toMSText(true, true), "1.500");
+        compareTexts(rt.toFrameText(24, false), "1:12");
+        compareTexts(rt.toFrameText(24, true), "1:12");
+        compareTexts(rt.toFrameText(25, false), "1:12");
+        compareTexts(rt.toFrameText(25, true), "1:12");
+        compareTexts(rt.toSecText(), "1s");
+
+        rt = RealTime::fromSeconds(-1.5);
+        compareTexts(rt.toMSText(false, false), "-1.5");
+        compareTexts(rt.toMSText(true, false), "-1.500");
+        compareTexts(rt.toMSText(false, true), "-1.5");
+        compareTexts(rt.toMSText(true, true), "-1.500");
+        compareTexts(rt.toFrameText(24, false), "-1:12");
+        compareTexts(rt.toFrameText(24, true), "-1:12");
+        compareTexts(rt.toSecText(), "-1s");
+
+        rt = RealTime(1, 1000);
+        compareTexts(rt.toMSText(false, false), "1");
+        compareTexts(rt.toFrameText(24, false), "1:00");
+        compareTexts(rt.toFrameText(ONE_MILLION, false), "1:000001");
+        compareTexts(rt.toSecText(), "1s");
+
+        rt = RealTime(1, 100000);
+        compareTexts(rt.toFrameText(ONE_MILLION, false), "1:000100");
+        compareTexts(rt.toSecText(), "1s");
+
+        rt = RealTime::fromSeconds(60);
+        compareTexts(rt.toMSText(false, false), "60");
+        compareTexts(rt.toMSText(true, false), "60.000");
+        compareTexts(rt.toMSText(false, true), "1:00");
+        compareTexts(rt.toMSText(true, true), "1:00.000");
+        compareTexts(rt.toFrameText(24, false), "60:00");
+        compareTexts(rt.toFrameText(24, true), "1:00:00");
+        compareTexts(rt.toSecText(), "1:00");
+
+        rt = RealTime::fromSeconds(61.05);
+        compareTexts(rt.toMSText(false, false), "61.05");
+        compareTexts(rt.toMSText(true, false), "61.050");
+        compareTexts(rt.toMSText(false, true), "1:01.05");
+        compareTexts(rt.toMSText(true, true), "1:01.050");
+        compareTexts(rt.toFrameText(24, false), "61:01");
+        compareTexts(rt.toFrameText(24, true), "1:01:01");
+        compareTexts(rt.toSecText(), "1:01");
+        
+        rt = RealTime::fromSeconds(601.05);
+        compareTexts(rt.toMSText(false, false), "601.05");
+        compareTexts(rt.toMSText(true, false), "601.050");
+        compareTexts(rt.toMSText(false, true), "10:01.05");
+        compareTexts(rt.toMSText(true, true), "10:01.050");
+        compareTexts(rt.toFrameText(24, false), "601:01");
+        compareTexts(rt.toFrameText(24, true), "10:01:01");
+        compareTexts(rt.toSecText(), "10:01");
+        
+        rt = RealTime::fromSeconds(3600);
+        compareTexts(rt.toMSText(false, false), "3600");
+        compareTexts(rt.toMSText(true, false), "3600.000");
+        compareTexts(rt.toMSText(false, true), "1:00:00");
+        compareTexts(rt.toMSText(true, true), "1:00:00.000");
+        compareTexts(rt.toFrameText(24, false), "3600:00");
+        compareTexts(rt.toFrameText(24, true), "1:00:00:00");
+        compareTexts(rt.toSecText(), "1:00:00");
+
+        // For practical reasons our time display always rounds down
+        rt = RealTime(3599, ONE_BILLION-1);
+        compareTexts(rt.toMSText(false, false), "3599.999");
+        compareTexts(rt.toMSText(true, false), "3599.999");
+        compareTexts(rt.toMSText(false, true), "59:59.999");
+        compareTexts(rt.toMSText(true, true), "59:59.999");
+        compareTexts(rt.toFrameText(24, false), "3599:23");
+        compareTexts(rt.toFrameText(24, true), "59:59:23");
+        compareTexts(rt.toSecText(), "59:59");
+
+        rt = RealTime::fromSeconds(3600 * 4 + 60 * 5 + 3 + 0.01);
+        compareTexts(rt.toMSText(false, false), "14703.01");
+        compareTexts(rt.toMSText(true, false), "14703.010");
+        compareTexts(rt.toMSText(false, true), "4:05:03.01");
+        compareTexts(rt.toMSText(true, true), "4:05:03.010");
+        compareTexts(rt.toFrameText(24, false), "14703:00");
+        compareTexts(rt.toFrameText(24, true), "4:05:03:00");
+        compareTexts(rt.toSecText(), "4:05:03");
+
+        rt = RealTime::fromSeconds(-(3600 * 4 + 60 * 5 + 3 + 0.01));
+        compareTexts(rt.toMSText(false, false), "-14703.01");
+        compareTexts(rt.toMSText(true, false), "-14703.010");
+        compareTexts(rt.toMSText(false, true), "-4:05:03.01");
+        compareTexts(rt.toMSText(true, true), "-4:05:03.010");
+        compareTexts(rt.toFrameText(24, false), "-14703:00");
+        compareTexts(rt.toFrameText(24, true), "-4:05:03:00");
+        compareTexts(rt.toSecText(), "-4:05:03");
+    }
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/TestStringBits.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,203 @@
+/* -*- 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 TEST_STRINGBITS_H
+#define TEST_STRINGBITS_H
+
+#include "../StringBits.h"
+
+#include <QObject>
+#include <QStringList>
+#include <QtTest>
+
+#include <iostream>
+
+using namespace std;
+
+class TestStringBits : public QObject
+{
+    Q_OBJECT
+
+private:
+    void testSplitQuoted(QString in, QStringList out) {
+        // Only suitable where the output strings do not have
+        // consecutive spaces in them
+        QCOMPARE(StringBits::splitQuoted(in, ' '), out);
+        QString in2(in);
+        in2.replace(' ', ',');
+        QStringList out2;
+        foreach (QString o, out) {
+            out2 << o.replace(' ', ',');
+        }
+        QCOMPARE(StringBits::splitQuoted(in2, ','), out2);
+    }
+
+private slots:
+    void simple() {
+        QString in = "a b c d";
+        QStringList out;     
+        out << "a" << "b" << "c" << "d";
+        testSplitQuoted(in, out);
+    }
+
+    void dquoted() {
+        QString in = "a \"b c\" d";
+        QStringList out;       
+        out << "a" << "b c" << "d";
+        testSplitQuoted(in, out);
+    }
+
+    void drunon() {
+        QString in = "a \"b c\"d e";
+        QStringList out;       
+        out << "a" << "b cd" << "e";
+        testSplitQuoted(in, out);
+    }
+
+    void squoted() {
+        QString in = "a 'b c' d";
+        QStringList out;       
+        out << "a" << "b c" << "d";
+        testSplitQuoted(in, out);
+    }
+
+    void srunon() {
+        QString in = "a 'b c'd e";
+        QStringList out;       
+        out << "a" << "b cd" << "e";
+        testSplitQuoted(in, out);
+    }
+
+    void dempty() {
+        QString in = "\"\" \"\" \"\"";
+        QStringList out;       
+        out << "" << "" << "";
+        testSplitQuoted(in, out);
+    }
+
+    void sempty() {
+        QString in = "'' '' ''";
+        QStringList out;       
+        out << "" << "" << "";
+        testSplitQuoted(in, out);
+    }
+
+    void descaped() {
+        QString in = "a \"b c\\\" d\"";
+        QStringList out;         
+        out << "a" << "b c\" d"; 
+        testSplitQuoted(in, out);
+    }
+
+    void sescaped() {
+        QString in = "a 'b c\\' d'";
+        QStringList out;         
+        out << "a" << "b c' d"; 
+        testSplitQuoted(in, out);
+    }
+
+    void dnested() {
+        QString in = "a \"b c' d\"";
+        QStringList out;        
+        out << "a" << "b c' d";
+        testSplitQuoted(in, out);
+    }
+
+    void snested() {
+        QString in = "a 'b c\" d'";
+        QStringList out;        
+        out << "a" << "b c\" d";
+        testSplitQuoted(in, out);
+    }
+
+    void snested2() {
+        QString in = "aa 'bb cc\" dd'";
+        QStringList out;            
+        out << "aa" << "bb cc\" dd";
+        testSplitQuoted(in, out);
+    }
+
+    void qquoted() {
+        QString in = "a'a 'bb' \\\"cc\" dd\\\"";
+        QStringList out;                 
+        out << "a'a" << "bb" << "\"cc\"" << "dd\"";  
+        testSplitQuoted(in, out);
+    }
+
+    void multispace() {
+        QString in = "  a'a \\'         'bb'    '      \\\"cc\" ' dd\\\" '";
+        QStringList out;                                            
+        out << "a'a" << "'" << "bb" << "      \"cc\" " << "dd\"" << "'";
+        QCOMPARE(StringBits::splitQuoted(in, ' '), out);
+
+        QString in2 = ",,a'a,\\',,,,,,,,,'bb',,,,',,,,,,\\\"cc\",',dd\\\",'";
+        QStringList out2;
+        out2 << "" << "" << "a'a" << "'" << "" << "" << "" << "" << "" << ""
+             << "" << "" << "bb" << "" << "" << "" << ",,,,,,\"cc\","
+             << "dd\"" << "'";
+        QCOMPARE(StringBits::splitQuoted(in2, ','), out2);
+    }
+};
+
+#endif
+
+/* r928
+Config: Using QtTest library 5.3.2, Qt 5.3.2
+PASS   : TestStringBits::initTestCase()
+PASS   : TestStringBits::simple()
+PASS   : TestStringBits::dquoted()
+PASS   : TestStringBits::squoted()
+PASS   : TestStringBits::descaped()
+FAIL!  : TestStringBits::sescaped() Compared lists have different sizes.
+   Actual   (StringBits::splitQuoted(in, ' ')) size: 3
+   Expected (out) size: 2
+   Loc: [o/../TestStringBits.h(65)]
+PASS   : TestStringBits::dnested()
+PASS   : TestStringBits::snested()
+PASS   : TestStringBits::snested2()
+PASS   : TestStringBits::qquoted()
+FAIL!  : TestStringBits::multispace() Compared lists differ at index 1.
+   Actual   (StringBits::splitQuoted(in, ' ')): "         "
+   Expected (out): "'"
+   Loc: [o/../TestStringBits.h(100)]
+FAIL!  : TestStringBits::qcommas() Compared lists have different sizes.
+   Actual   (StringBits::splitQuoted(in, ',')) size: 4
+   Expected (out) size: 3
+   Loc: [o/../TestStringBits.h(107)]
+PASS   : TestStringBits::cleanupTestCase()
+Totals: 10 passed, 3 failed, 0 skipped
+*/
+
+/*curr
+PASS   : TestStringBits::initTestCase()
+PASS   : TestStringBits::simple()
+PASS   : TestStringBits::dquoted()
+PASS   : TestStringBits::squoted()
+PASS   : TestStringBits::descaped()
+FAIL!  : TestStringBits::sescaped() Compared lists have different sizes.
+   Actual   (StringBits::splitQuoted(in, ' ')) size: 3
+   Expected (out) size: 2
+   Loc: [o/../TestStringBits.h(65)]
+PASS   : TestStringBits::dnested()
+PASS   : TestStringBits::snested()
+PASS   : TestStringBits::snested2()
+PASS   : TestStringBits::qquoted()
+FAIL!  : TestStringBits::multispace() Compared lists have different sizes.
+   Actual   (StringBits::splitQuoted(in, ' ')) size: 5
+   Expected (out) size: 6
+   Loc: [o/../TestStringBits.h(100)]
+PASS   : TestStringBits::qcommas()
+PASS   : TestStringBits::cleanupTestCase()
+Totals: 11 passed, 2 failed, 0 skipped
+*/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/main.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,59 @@
+/* -*- 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 "TestRangeMapper.h"
+#include "TestPitch.h"
+#include "TestRealTime.h"
+#include "TestStringBits.h"
+
+#include <QtTest>
+
+#include <iostream>
+
+int main(int argc, char *argv[])
+{
+    int good = 0, bad = 0;
+
+    QCoreApplication app(argc, argv);
+    app.setOrganizationName("Sonic Visualiser");
+    app.setApplicationName("test-svcore-base");
+
+    {
+	TestRangeMapper t;
+	if (QTest::qExec(&t, argc, argv) == 0) ++good;
+	else ++bad;
+    }
+    {
+	TestPitch t;
+	if (QTest::qExec(&t, argc, argv) == 0) ++good;
+	else ++bad;
+    }
+    {
+	TestRealTime t;
+	if (QTest::qExec(&t, argc, argv) == 0) ++good;
+	else ++bad;
+    }
+    {
+	TestStringBits t;
+	if (QTest::qExec(&t, argc, argv) == 0) ++good;
+	else ++bad;
+    }
+
+    if (bad > 0) {
+	cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl;
+	return 1;
+    } else {
+	cerr << "All tests passed" << endl;
+	return 0;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/test.pro	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,72 @@
+
+TEMPLATE = app
+
+LIBS += -L../.. -L../../../dataquay -L../../release -L../../../dataquay/release -lsvcore -ldataquay
+
+win32-g++ {
+    INCLUDEPATH += ../../../sv-dependency-builds/win32-mingw/include
+    LIBS += -L../../../sv-dependency-builds/win32-mingw/lib
+}
+win32-msvc* {
+    INCLUDEPATH += ../../../sv-dependency-builds/win32-msvc/include
+    LIBS += -L../../../sv-dependency-builds/win32-msvc/lib
+}
+mac* {
+    INCLUDEPATH += ../../../sv-dependency-builds/osx/include
+    LIBS += -L../../../sv-dependency-builds/osx/lib
+}
+
+exists(../../config.pri) {
+    include(../../config.pri)
+}
+
+!exists(../../config.pri) {
+
+    CONFIG += release
+    DEFINES += NDEBUG BUILD_RELEASE NO_TIMING
+
+    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
+
+    win* {
+        LIBS += -llo -lwinmm -lws2_32
+    }
+    macx* {
+        DEFINES += HAVE_COREAUDIO
+        LIBS += -framework CoreAudio -framework CoreMidi -framework AudioUnit -framework AudioToolbox -framework CoreFoundation -framework CoreServices -framework Accelerate
+    }
+}
+
+CONFIG += qt thread warn_on stl rtti exceptions console c++11
+QT += network xml testlib
+QT -= gui
+
+TARGET = svcore-base-test
+
+DEPENDPATH += ../..
+INCLUDEPATH += ../..
+OBJECTS_DIR = o
+MOC_DIR = o
+
+HEADERS += TestRangeMapper.h TestPitch.h TestRealTime.h TestStringBits.h
+SOURCES += main.cpp
+
+win* {
+//PRE_TARGETDEPS += ../../svcore.lib
+}
+!win* {
+PRE_TARGETDEPS += ../../libsvcore.a
+}
+
+!win32 {
+    !macx* {
+        QMAKE_POST_LINK=./$${TARGET}
+    }
+    macx* {
+        QMAKE_POST_LINK=./$${TARGET}.app/Contents/MacOS/$${TARGET}
+    }
+}
+
+win32:QMAKE_POST_LINK=./release/$${TARGET}.exe
+
--- a/config.pri.in	Tue Jul 14 15:04:46 2015 +0100
+++ b/config.pri.in	Wed Apr 20 12:06:28 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 Jul 14 15:04:46 2015 +0100
+++ b/configure	Wed Apr 20 12:06:28 2016 +0100
@@ -650,12 +650,6 @@
 portaudio_2_0_CFLAGS
 liblo_LIBS
 liblo_CFLAGS
-rubberband_LIBS
-rubberband_CFLAGS
-vamphostsdk_LIBS
-vamphostsdk_CFLAGS
-vamp_LIBS
-vamp_CFLAGS
 samplerate_LIBS
 samplerate_CFLAGS
 sndfile_LIBS
@@ -673,6 +667,7 @@
 EGREP
 GREP
 CXXCPP
+HAVE_CXX11
 MKDIR_P
 INSTALL_DATA
 INSTALL_SCRIPT
@@ -755,12 +750,6 @@
 sndfile_LIBS
 samplerate_CFLAGS
 samplerate_LIBS
-vamp_CFLAGS
-vamp_LIBS
-vamphostsdk_CFLAGS
-vamphostsdk_LIBS
-rubberband_CFLAGS
-rubberband_LIBS
 liblo_CFLAGS
 liblo_LIBS
 portaudio_2_0_CFLAGS
@@ -1422,16 +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
-  rubberband_CFLAGS
-              C compiler flags for rubberband, overriding pkg-config
-  rubberband_LIBS
-              linker flags for rubberband, overriding pkg-config
   liblo_CFLAGS
               C compiler flags for liblo, overriding pkg-config
   liblo_LIBS  linker flags for liblo, overriding pkg-config
@@ -3449,6 +3428,146 @@
 $as_echo "$MKDIR_P" >&6; }
 
 
+# We are daringly making use of C++11 now
+
+    ax_cxx_compile_cxx11_required=true
+  ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+  ac_success=no
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features by default" >&5
+$as_echo_n "checking whether $CXX supports C++11 features by default... " >&6; }
+if ${ax_cv_cxx_compile_cxx11+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+  template <typename T>
+    struct check
+    {
+      static_assert(sizeof(int) <= sizeof(T), "not big enough");
+    };
+
+    struct Base {
+    virtual void f() {}
+    };
+    struct Child : public Base {
+    virtual void f() override {}
+    };
+
+    typedef check<check<bool>> right_angle_brackets;
+
+    int a;
+    decltype(a) b;
+
+    typedef check<int> check_type;
+    check_type c;
+    check_type&& cr = static_cast<check_type&&>(c);
+
+    auto d = a;
+    auto l = [](){};
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ax_cv_cxx_compile_cxx11=yes
+else
+  ax_cv_cxx_compile_cxx11=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_cxx_compile_cxx11" >&5
+$as_echo "$ax_cv_cxx_compile_cxx11" >&6; }
+  if test x$ax_cv_cxx_compile_cxx11 = xyes; then
+    ac_success=yes
+  fi
+
+
+
+    if test x$ac_success = xno; then
+    for switch in -std=c++11 -std=c++0x; do
+      cachevar=`$as_echo "ax_cv_cxx_compile_cxx11_$switch" | $as_tr_sh`
+      { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features with $switch" >&5
+$as_echo_n "checking whether $CXX supports C++11 features with $switch... " >&6; }
+if eval \${$cachevar+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_CXXFLAGS="$CXXFLAGS"
+         CXXFLAGS="$CXXFLAGS $switch"
+         cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+  template <typename T>
+    struct check
+    {
+      static_assert(sizeof(int) <= sizeof(T), "not big enough");
+    };
+
+    struct Base {
+    virtual void f() {}
+    };
+    struct Child : public Base {
+    virtual void f() override {}
+    };
+
+    typedef check<check<bool>> right_angle_brackets;
+
+    int a;
+    decltype(a) b;
+
+    typedef check<int> check_type;
+    check_type c;
+    check_type&& cr = static_cast<check_type&&>(c);
+
+    auto d = a;
+    auto l = [](){};
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  eval $cachevar=yes
+else
+  eval $cachevar=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+         CXXFLAGS="$ac_save_CXXFLAGS"
+fi
+eval ac_res=\$$cachevar
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+      if eval test x\$$cachevar = xyes; then
+        CXXFLAGS="$CXXFLAGS $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi
+  ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+  if test x$ax_cxx_compile_cxx11_required = xtrue; then
+    if test x$ac_success = xno; then
+      as_fn_error $? "*** A compiler with support for C++11 language features is required." "$LINENO" 5
+    fi
+  else
+    if test x$ac_success = xno; then
+      HAVE_CXX11=0
+      { $as_echo "$as_me:${as_lineno-$LINENO}: No compiler with C++11 support was found" >&5
+$as_echo "$as_me: No compiler with C++11 support was found" >&6;}
+    else
+      HAVE_CXX11=1
+
+$as_echo "#define HAVE_CXX11 1" >>confdefs.h
+
+    fi
+
+
+  fi
+
 
 ac_ext=cpp
 ac_cpp='$CXXCPP $CPPFLAGS'
@@ -3996,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
@@ -4113,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
@@ -4202,9 +4399,10 @@
 CXXFLAGS_MINIMAL="$AUTOCONF_CXXFLAGS"
 
 if test "x$GCC" = "xyes"; then
-        CXXFLAGS_DEBUG="-Wall -Woverloaded-virtual -Wextra -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -g -pipe"
-   	CXXFLAGS_RELEASE="-g0 -O2 -Wall -pipe"
-   	CXXFLAGS_MINIMAL="-g0 -O0"
+   	CXXFLAGS_ANY="-Wall -Wextra -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe"
+        CXXFLAGS_DEBUG="$CXXFLAGS_ANY -Werror -g"
+   	CXXFLAGS_RELEASE="$CXXFLAGS_ANY -g0 -O2"
+   	CXXFLAGS_MINIMAL="$CXXFLAGS_ANY -g0 -O0"
 fi
 
 CXXFLAGS_BUILD="$CXXFLAGS_RELEASE"
@@ -5006,459 +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=rubberband
-SV_MODULE_VERSION_TEST="rubberband"
-SV_MODULE_HEADER=rubberband/RubberBandStretcher.h
-SV_MODULE_LIB=rubberband
-SV_MODULE_FUNC=rubberband_new
-SV_MODULE_HAVE=HAVE_$(echo rubberband | tr 'a-z' 'A-Z')
-SV_MODULE_FAILED=1
-if test -n "$rubberband_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 $rubberband_CFLAGS"
-   LIBS="$LIBS $rubberband_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 rubberband" >&5
-$as_echo_n "checking for rubberband... " >&6; }
-
-if test -n "$rubberband_CFLAGS"; then
-    pkg_cv_rubberband_CFLAGS="$rubberband_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_rubberband_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 "$rubberband_LIBS"; then
-    pkg_cv_rubberband_LIBS="$rubberband_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_rubberband_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
-	        rubberband_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1`
-        else
-	        rubberband_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 "$rubberband_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
-	rubberband_CFLAGS=$pkg_cv_rubberband_CFLAGS
-	rubberband_LIBS=$pkg_cv_rubberband_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-	HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS";LIBS="$LIBS $rubberband_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 Jul 14 15:04:46 2015 +0100
+++ b/configure.ac	Wed Apr 20 12:06:28 2016 +0100
@@ -25,6 +25,9 @@
 AC_PROG_INSTALL
 AC_PROG_MKDIR_P
 
+# We are daringly making use of C++11 now
+AX_CXX_COMPILE_STDCXX_11(noext)
+
 AC_HEADER_STDC
 
 # These are the flags Autoconf guesses for us; we use them later if
@@ -50,9 +53,10 @@
 CXXFLAGS_MINIMAL="$AUTOCONF_CXXFLAGS"
 
 if test "x$GCC" = "xyes"; then
-        CXXFLAGS_DEBUG="-Wall -Woverloaded-virtual -Wextra -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -g -pipe"
-   	CXXFLAGS_RELEASE="-g0 -O2 -Wall -pipe"
-   	CXXFLAGS_MINIMAL="-g0 -O0"
+   	CXXFLAGS_ANY="-Wall -Wextra -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe"
+        CXXFLAGS_DEBUG="$CXXFLAGS_ANY -Werror -g"
+   	CXXFLAGS_RELEASE="$CXXFLAGS_ANY -g0 -O2"
+   	CXXFLAGS_MINIMAL="$CXXFLAGS_ANY -g0 -O0"
 fi
 
 CXXFLAGS_BUILD="$CXXFLAGS_RELEASE"
@@ -79,12 +83,9 @@
 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_REQUIRED([rubberband],[rubberband],[rubberband/RubberBandStretcher.h],[rubberband],[rubberband_new])
 
 SV_MODULE_OPTIONAL([liblo],[],[lo/lo.h],[lo],[lo_address_new])
-SV_MODULE_OPTIONAL([portaudio_2_0],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported])
+SV_MODULE_OPTIONAL([portaudio],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported])
 SV_MODULE_OPTIONAL([JACK],[jack >= 0.100],[jack/jack.h],[jack],[jack_client_open])
 SV_MODULE_OPTIONAL([libpulse],[libpulse >= 0.9],[pulse/pulseaudio.h],[pulse],[pa_stream_new])
 SV_MODULE_OPTIONAL([lrdf],[lrdf >= 0.2],[lrdf.h],[lrdf],[lrdf_init])
--- a/data/fft/FFTCacheReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +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-2009 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 _FFT_CACHE_READER_H_
-#define _FFT_CACHE_READER_H_
-
-#include "FFTCacheStorageType.h"
-#include <stddef.h>
-
-class FFTCacheReader
-{
-public:
-    virtual ~FFTCacheReader() { }
-
-    virtual size_t getWidth() const = 0;
-    virtual size_t getHeight() const = 0;
-	
-    virtual float getMagnitudeAt(size_t x, size_t y) const = 0;
-    virtual float getNormalizedMagnitudeAt(size_t x, size_t y) const = 0;
-    virtual float getMaximumMagnitudeAt(size_t x) const = 0;
-    virtual float getPhaseAt(size_t x, size_t y) const = 0;
-
-    virtual void getValuesAt(size_t x, size_t y, float &real, float &imag) const = 0;
-    virtual void getMagnitudesAt(size_t x, float *values, size_t minbin, size_t count, size_t step) const = 0;
-
-    virtual bool haveSetColumnAt(size_t x) const = 0;
-
-    virtual FFTCache::StorageType getStorageType() const = 0;
-};
-
-#endif
--- a/data/fft/FFTCacheStorageType.h	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +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-2009 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 _FFT_CACHE_STORAGE_TYPE_H_
-#define _FFT_CACHE_STORAGE_TYPE_H_
-
-namespace FFTCache {
-enum StorageType { //!!! dup
-    Compact, // 16 bits normalized polar
-    Rectangular, // floating point real+imag
-    Polar // floating point mag+phase
-};
-}
-
-#endif
--- a/data/fft/FFTCacheWriter.h	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +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-2009 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 _FFT_CACHE_WRITER_H_
-#define _FFT_CACHE_WRITER_H_
-
-#include <stddef.h>
-
-class FFTCacheWriter
-{
-public:
-    virtual ~FFTCacheWriter() { }
-
-    virtual size_t getWidth() const = 0;
-    virtual size_t getHeight() const = 0;
-
-    virtual void setColumnAt(size_t x, float *mags, float *phases, float factor) = 0;
-    virtual void setColumnAt(size_t x, float *reals, float *imags) = 0;
-
-    virtual bool haveSetColumnAt(size_t x) const = 0;
-
-    virtual void allColumnsWritten() = 0; // notify cache to close
-
-    virtual FFTCache::StorageType getStorageType() const = 0;
-};
-
-#endif
-
--- a/data/fft/FFTDataServer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1573 +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 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.
-*/
-
-#include "FFTDataServer.h"
-
-#include "FFTFileCacheReader.h"
-#include "FFTFileCacheWriter.h"
-#include "FFTMemoryCache.h"
-
-#include "model/DenseTimeValueModel.h"
-
-#include "system/System.h"
-
-#include "base/StorageAdviser.h"
-#include "base/Exceptions.h"
-#include "base/Profiler.h"
-#include "base/Thread.h" // for debug mutex locker
-
-#include <QWriteLocker>
-
-//#define DEBUG_FFT_SERVER 1
-//#define DEBUG_FFT_SERVER_FILL 1
-
-#ifdef DEBUG_FFT_SERVER_FILL
-#ifndef DEBUG_FFT_SERVER
-#define DEBUG_FFT_SERVER 1
-#endif
-#endif
-
-
-FFTDataServer::ServerMap FFTDataServer::m_servers;
-FFTDataServer::ServerQueue FFTDataServer::m_releasedServers;
-QMutex FFTDataServer::m_serverMapMutex;
-
-FFTDataServer *
-FFTDataServer::getInstance(const DenseTimeValueModel *model,
-                           int channel,
-                           WindowType windowType,
-                           size_t windowSize,
-                           size_t windowIncrement,
-                           size_t fftSize,
-                           bool polar,
-                           StorageAdviser::Criteria criteria,
-                           size_t fillFromColumn)
-{
-    QString n = generateFileBasename(model,
-                                     channel,
-                                     windowType,
-                                     windowSize,
-                                     windowIncrement,
-                                     fftSize,
-                                     polar);
-
-    FFTDataServer *server = 0;
-    
-    MutexLocker locker(&m_serverMapMutex, "FFTDataServer::getInstance::m_serverMapMutex");
-
-    if ((server = findServer(n))) {
-        return server;
-    }
-
-    QString npn = generateFileBasename(model,
-                                       channel,
-                                       windowType,
-                                       windowSize,
-                                       windowIncrement,
-                                       fftSize,
-                                       !polar);
-
-    if ((server = findServer(npn))) {
-        return server;
-    }
-
-    try {
-        server = new FFTDataServer(n,
-                                   model,
-                                   channel,
-                                   windowType,
-                                   windowSize,
-                                   windowIncrement,
-                                   fftSize,
-                                   polar,
-                                   criteria,
-                                   fillFromColumn);
-    } catch (InsufficientDiscSpace) {
-        delete server;
-        server = 0;
-    }
-
-    if (server) {
-        m_servers[n] = ServerCountPair(server, 1);
-    }
-
-    return server;
-}
-
-FFTDataServer *
-FFTDataServer::getFuzzyInstance(const DenseTimeValueModel *model,
-                                int channel,
-                                WindowType windowType,
-                                size_t windowSize,
-                                size_t windowIncrement,
-                                size_t fftSize,
-                                bool polar,
-                                StorageAdviser::Criteria criteria,
-                                size_t fillFromColumn)
-{
-    // Fuzzy matching:
-    // 
-    // -- if we're asked for polar and have non-polar, use it (and
-    // vice versa).  This one is vital, and we do it for non-fuzzy as
-    // well (above).
-    //
-    // -- if we're asked for an instance with a given fft size and we
-    // have one already with a multiple of that fft size but the same
-    // window size and type (and model), we can draw the results from
-    // it (e.g. the 1st, 2nd, 3rd etc bins of a 512-sample FFT are the
-    // same as the the 1st, 5th, 9th etc of a 2048-sample FFT of the
-    // same window plus zero padding).
-    //
-    // -- if we're asked for an instance with a given window type and
-    // size and fft size and we have one already the same but with a
-    // smaller increment, we can draw the results from it (provided
-    // our increment is a multiple of its)
-    //
-    // The FFTModel knows how to interpret these things.  In
-    // both cases we require that the larger one is a power-of-two
-    // multiple of the smaller (e.g. even though in principle you can
-    // draw the results at increment 256 from those at increment 768
-    // or 1536, the model doesn't support this).
-
-    {
-        MutexLocker locker(&m_serverMapMutex, "FFTDataServer::getFuzzyInstance::m_serverMapMutex");
-
-        ServerMap::iterator best = m_servers.end();
-        int bestdist = -1;
-    
-        for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
-
-            FFTDataServer *server = i->second.first;
-
-            if (server->getModel() == model &&
-                (server->getChannel() == channel || model->getChannelCount() == 1) &&
-                server->getWindowType() == windowType &&
-                server->getWindowSize() == windowSize &&
-                server->getWindowIncrement() <= windowIncrement &&
-                server->getFFTSize() >= fftSize) {
-                
-                if ((windowIncrement % server->getWindowIncrement()) != 0) continue;
-                int ratio = windowIncrement / server->getWindowIncrement();
-                bool poweroftwo = true;
-                while (ratio > 1) {
-                    if (ratio & 0x1) {
-                        poweroftwo = false;
-                        break;
-                    }
-                    ratio >>= 1;
-                }
-                if (!poweroftwo) continue;
-
-                if ((server->getFFTSize() % fftSize) != 0) continue;
-                ratio = server->getFFTSize() / fftSize;
-                while (ratio > 1) {
-                    if (ratio & 0x1) {
-                        poweroftwo = false;
-                        break;
-                    }
-                    ratio >>= 1;
-                }
-                if (!poweroftwo) continue;
-                
-                int distance = 0;
-                
-                if (server->getPolar() != polar) distance += 1;
-                
-                distance += ((windowIncrement / server->getWindowIncrement()) - 1) * 15;
-                distance += ((server->getFFTSize() / fftSize) - 1) * 10;
-                
-                if (server->getFillCompletion() < 50) distance += 100;
-
-#ifdef DEBUG_FFT_SERVER
-                SVDEBUG << "FFTDataServer::getFuzzyInstance: Distance for server " << server << " is " << distance << ", best is " << bestdist << endl;
-#endif
-                
-                if (bestdist == -1 || distance < bestdist) {
-                    bestdist = distance;
-                    best = i;
-                }
-            }
-        }
-
-        if (bestdist >= 0) {
-            FFTDataServer *server = best->second.first;
-#ifdef DEBUG_FFT_SERVER
-            SVDEBUG << "FFTDataServer::getFuzzyInstance: We like server " << server << " (with distance " << bestdist << ")" << endl;
-#endif
-            claimInstance(server, false);
-            return server;
-        }
-    }
-
-    // Nothing found, make a new one
-
-    return getInstance(model,
-                       channel,
-                       windowType,
-                       windowSize,
-                       windowIncrement,
-                       fftSize,
-                       polar,
-                       criteria,
-                       fillFromColumn);
-}
-
-FFTDataServer *
-FFTDataServer::findServer(QString n)
-{    
-#ifdef DEBUG_FFT_SERVER
-    SVDEBUG << "FFTDataServer::findServer(\"" << n << "\")" << endl;
-#endif
-
-    if (m_servers.find(n) != m_servers.end()) {
-
-        FFTDataServer *server = m_servers[n].first;
-
-#ifdef DEBUG_FFT_SERVER
-        SVDEBUG << "FFTDataServer::findServer(\"" << n << "\"): found " << server << endl;
-#endif
-
-        claimInstance(server, false);
-
-        return server;
-    }
-
-#ifdef DEBUG_FFT_SERVER
-        SVDEBUG << "FFTDataServer::findServer(\"" << n << "\"): not found" << endl;
-#endif
-
-    return 0;
-}
-
-void
-FFTDataServer::claimInstance(FFTDataServer *server)
-{
-    claimInstance(server, true);
-}
-
-void
-FFTDataServer::claimInstance(FFTDataServer *server, bool needLock)
-{
-    MutexLocker locker(needLock ? &m_serverMapMutex : 0,
-                       "FFTDataServer::claimInstance::m_serverMapMutex");
-
-#ifdef DEBUG_FFT_SERVER
-    SVDEBUG << "FFTDataServer::claimInstance(" << server << ")" << endl;
-#endif
-
-    for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
-        if (i->second.first == server) {
-
-            for (ServerQueue::iterator j = m_releasedServers.begin();
-                 j != m_releasedServers.end(); ++j) {
-
-                if (*j == server) {
-#ifdef DEBUG_FFT_SERVER
-    SVDEBUG << "FFTDataServer::claimInstance: found in released server list, removing from it" << endl;
-#endif
-                    m_releasedServers.erase(j);
-                    break;
-                }
-            }
-
-            ++i->second.second;
-
-#ifdef DEBUG_FFT_SERVER
-            SVDEBUG << "FFTDataServer::claimInstance: new refcount is " << i->second.second << endl;
-#endif
-
-            return;
-        }
-    }
-    
-    cerr << "ERROR: FFTDataServer::claimInstance: instance "
-              << server << " unknown!" << endl;
-}
-
-void
-FFTDataServer::releaseInstance(FFTDataServer *server)
-{
-    releaseInstance(server, true);
-}
-
-void
-FFTDataServer::releaseInstance(FFTDataServer *server, bool needLock)
-{    
-    MutexLocker locker(needLock ? &m_serverMapMutex : 0,
-                       "FFTDataServer::releaseInstance::m_serverMapMutex");
-
-#ifdef DEBUG_FFT_SERVER
-    SVDEBUG << "FFTDataServer::releaseInstance(" << server << ")" << endl;
-#endif
-
-    // -- if ref count > 0, decrement and return
-    // -- if the instance hasn't been used at all, delete it immediately 
-    // -- if fewer than N instances (N = e.g. 3) remain with zero refcounts,
-    //    leave them hanging around
-    // -- if N instances with zero refcounts remain, delete the one that
-    //    was last released first
-    // -- if we run out of disk space when allocating an instance, go back
-    //    and delete the spare N instances before trying again
-    // -- have an additional method to indicate that a model has been
-    //    destroyed, so that we can delete all of its fft server instances
-
-    for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
-        if (i->second.first == server) {
-            if (i->second.second == 0) {
-                cerr << "ERROR: FFTDataServer::releaseInstance("
-                          << server << "): instance not allocated" << endl;
-            } else if (--i->second.second == 0) {
-/*!!!
-                if (server->m_lastUsedCache == -1) { // never used
-#ifdef DEBUG_FFT_SERVER
-                    SVDEBUG << "FFTDataServer::releaseInstance: instance "
-                              << server << " has never been used, erasing"
-                              << endl;
-#endif
-                    delete server;
-                    m_servers.erase(i);
-                } else {
-*/
-#ifdef DEBUG_FFT_SERVER
-                    SVDEBUG << "FFTDataServer::releaseInstance: instance "
-                              << server << " no longer in use, marking for possible collection"
-                              << endl;
-#endif
-                    bool found = false;
-                    for (ServerQueue::iterator j = m_releasedServers.begin();
-                         j != m_releasedServers.end(); ++j) {
-                        if (*j == server) {
-                            cerr << "ERROR: FFTDataServer::releaseInstance("
-                                      << server << "): server is already in "
-                                      << "released servers list" << endl;
-                            found = true;
-                        }
-                    }
-                    if (!found) m_releasedServers.push_back(server);
-                    server->suspend();
-                    purgeLimbo();
-//!!!                }
-            } else {
-#ifdef DEBUG_FFT_SERVER
-                    SVDEBUG << "FFTDataServer::releaseInstance: instance "
-                              << server << " now has refcount " << i->second.second
-                              << endl;
-#endif
-            }
-            return;
-        }
-    }
-
-    cerr << "ERROR: FFTDataServer::releaseInstance(" << server << "): "
-              << "instance not found" << endl;
-}
-
-void
-FFTDataServer::purgeLimbo(int maxSize)
-{
-#ifdef DEBUG_FFT_SERVER
-    SVDEBUG << "FFTDataServer::purgeLimbo(" << maxSize << "): "
-              << m_releasedServers.size() << " candidates" << endl;
-#endif
-
-    while (int(m_releasedServers.size()) > maxSize) {
-
-        FFTDataServer *server = *m_releasedServers.begin();
-
-        bool found = false;
-
-#ifdef DEBUG_FFT_SERVER
-        SVDEBUG << "FFTDataServer::purgeLimbo: considering candidate "
-                  << server << endl;
-#endif
-
-        for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
-
-            if (i->second.first == server) {
-                found = true;
-                if (i->second.second > 0) {
-                    cerr << "ERROR: FFTDataServer::purgeLimbo: Server "
-                              << server << " is in released queue, but still has non-zero refcount "
-                              << i->second.second << endl;
-                    // ... so don't delete it
-                    break;
-                }
-#ifdef DEBUG_FFT_SERVER
-                SVDEBUG << "FFTDataServer::purgeLimbo: looks OK, erasing it"
-                          << endl;
-#endif
-
-                m_servers.erase(i);
-                delete server;
-                break;
-            }
-        }
-
-        if (!found) {
-            cerr << "ERROR: FFTDataServer::purgeLimbo: Server "
-                      << server << " is in released queue, but not in server map!"
-                      << endl;
-            delete server;
-        }
-
-        m_releasedServers.pop_front();
-    }
-
-#ifdef DEBUG_FFT_SERVER
-    SVDEBUG << "FFTDataServer::purgeLimbo(" << maxSize << "): "
-              << m_releasedServers.size() << " remain" << endl;
-#endif
-
-}
-
-void
-FFTDataServer::modelAboutToBeDeleted(Model *model)
-{
-    MutexLocker locker(&m_serverMapMutex,
-                       "FFTDataServer::modelAboutToBeDeleted::m_serverMapMutex");
-
-#ifdef DEBUG_FFT_SERVER
-    SVDEBUG << "FFTDataServer::modelAboutToBeDeleted(" << model << ")"
-              << endl;
-#endif
-
-    for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
-        
-        FFTDataServer *server = i->second.first;
-
-        if (server->getModel() == model) {
-
-#ifdef DEBUG_FFT_SERVER
-            SVDEBUG << "FFTDataServer::modelAboutToBeDeleted: server is "
-                      << server << endl;
-#endif
-
-            if (i->second.second > 0) {
-                cerr << "WARNING: FFTDataServer::modelAboutToBeDeleted: Model " << model << " (\"" << model->objectName() << "\") is about to be deleted, but is still being referred to by FFT server " << server << " with non-zero refcount " << i->second.second << endl;
-                server->suspendWrites();
-                return;
-            }
-            for (ServerQueue::iterator j = m_releasedServers.begin();
-                 j != m_releasedServers.end(); ++j) {
-                if (*j == server) {
-#ifdef DEBUG_FFT_SERVER
-                    SVDEBUG << "FFTDataServer::modelAboutToBeDeleted: erasing from released servers" << endl;
-#endif
-                    m_releasedServers.erase(j);
-                    break;
-                }
-            }
-#ifdef DEBUG_FFT_SERVER
-            SVDEBUG << "FFTDataServer::modelAboutToBeDeleted: erasing server" << endl;
-#endif
-            m_servers.erase(i);
-            delete server;
-            return;
-        }
-    }
-}
-
-FFTDataServer::FFTDataServer(QString fileBaseName,
-                             const DenseTimeValueModel *model,
-                             int channel,
-			     WindowType windowType,
-			     size_t windowSize,
-			     size_t windowIncrement,
-			     size_t fftSize,
-                             bool polar,
-                             StorageAdviser::Criteria criteria,
-                             size_t fillFromColumn) :
-    m_fileBaseName(fileBaseName),
-    m_model(model),
-    m_channel(channel),
-    m_windower(windowType, windowSize),
-    m_windowSize(windowSize),
-    m_windowIncrement(windowIncrement),
-    m_fftSize(fftSize),
-    m_polar(polar),
-    m_width(0),
-    m_height(0),
-    m_cacheWidth(0),
-    m_cacheWidthPower(0),
-    m_cacheWidthMask(0),
-    m_criteria(criteria),
-    m_fftInput(0),
-    m_exiting(false),
-    m_suspended(true), //!!! or false?
-    m_fillThread(0)
-{
-#ifdef DEBUG_FFT_SERVER
-    cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::FFTDataServer" << endl;
-#endif
-
-    //!!! end is not correct until model finished reading -- what to do???
-
-    size_t start = m_model->getStartFrame();
-    size_t end = m_model->getEndFrame();
-
-    m_width = (end - start) / m_windowIncrement + 1;
-    m_height = m_fftSize / 2 + 1; // DC == 0, Nyquist == fftsize/2
-
-#ifdef DEBUG_FFT_SERVER 
-    cerr << "FFTDataServer(" << this << "): dimensions are "
-              << m_width << "x" << m_height << endl;
-#endif
-
-    size_t maxCacheSize = 20 * 1024 * 1024;
-    size_t columnSize = m_height * sizeof(fftsample) * 2 + sizeof(fftsample);
-    if (m_width * columnSize < maxCacheSize * 2) m_cacheWidth = m_width;
-    else m_cacheWidth = maxCacheSize / columnSize;
-    
-#ifdef DEBUG_FFT_SERVER
-    cerr << "FFTDataServer(" << this << "): cache width nominal "
-              << m_cacheWidth << ", actual ";
-#endif
-    
-    int bits = 0;
-    while (m_cacheWidth > 1) { m_cacheWidth >>= 1; ++bits; }
-    m_cacheWidthPower = bits + 1;
-    m_cacheWidth = 2;
-    while (bits) { m_cacheWidth <<= 1; --bits; }
-    m_cacheWidthMask = m_cacheWidth - 1;
-
-#ifdef DEBUG_FFT_SERVER
-    cerr << m_cacheWidth << " (power " << m_cacheWidthPower << ", mask "
-              << m_cacheWidthMask << ")" << endl;
-#endif
-
-    if (m_criteria == StorageAdviser::NoCriteria) {
-
-        // assume "spectrogram" criteria for polar ffts, and "feature
-        // extraction" criteria for rectangular ones.
-
-        if (m_polar) {
-            m_criteria = StorageAdviser::Criteria
-                (StorageAdviser::SpeedCritical |
-                 StorageAdviser::LongRetentionLikely);
-        } else {
-            m_criteria = StorageAdviser::Criteria
-                (StorageAdviser::PrecisionCritical);
-        }
-    }
-
-    for (size_t i = 0; i <= m_width / m_cacheWidth; ++i) {
-        m_caches.push_back(0);
-    }
-
-    m_fftInput = (fftsample *)
-        fftf_malloc(fftSize * sizeof(fftsample));
-
-    m_fftOutput = (fftf_complex *)
-        fftf_malloc((fftSize/2 + 1) * sizeof(fftf_complex));
-
-    m_workbuffer = (float *)
-        fftf_malloc((fftSize+2) * sizeof(float));
-
-    m_fftPlan = fftf_plan_dft_r2c_1d(m_fftSize,
-                                     m_fftInput,
-                                     m_fftOutput,
-                                     FFTW_MEASURE);
-
-    if (!m_fftPlan) {
-        cerr << "ERROR: fftf_plan_dft_r2c_1d(" << m_windowSize << ") failed!" << endl;
-        throw(0);
-    }
-
-    m_fillThread = new FillThread(*this, fillFromColumn);
-}
-
-FFTDataServer::~FFTDataServer()
-{
-#ifdef DEBUG_FFT_SERVER
-    cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::~FFTDataServer()" << endl;
-#endif
-
-    m_suspended = false;
-    m_exiting = true;
-    m_condition.wakeAll();
-    if (m_fillThread) {
-        m_fillThread->wait();
-        delete m_fillThread;
-    }
-
-//    MutexLocker locker(&m_writeMutex,
-//                       "FFTDataServer::~FFTDataServer::m_writeMutex");
-
-    QMutexLocker mlocker(&m_fftBuffersLock);
-    QWriteLocker wlocker(&m_cacheVectorLock);
-
-    for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) {
-        if (*i) {
-            delete *i;
-        }
-    }
-
-    deleteProcessingData();
-}
-
-void
-FFTDataServer::deleteProcessingData()
-{
-#ifdef DEBUG_FFT_SERVER
-    cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): deleteProcessingData" << endl;
-#endif
-    if (m_fftInput) {
-        fftf_destroy_plan(m_fftPlan);
-        fftf_free(m_fftInput);
-        fftf_free(m_fftOutput);
-        fftf_free(m_workbuffer);
-    }
-    m_fftInput = 0;
-}
-
-void
-FFTDataServer::suspend()
-{
-#ifdef DEBUG_FFT_SERVER
-    cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspend" << endl;
-#endif
-    Profiler profiler("FFTDataServer::suspend", false);
-
-    QMutexLocker locker(&m_fftBuffersLock);
-    m_suspended = true;
-}
-
-void
-FFTDataServer::suspendWrites()
-{
-#ifdef DEBUG_FFT_SERVER
-    cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspendWrites" << endl;
-#endif
-    Profiler profiler("FFTDataServer::suspendWrites", false);
-
-    m_suspended = true;
-}
-
-void
-FFTDataServer::resume()
-{
-#ifdef DEBUG_FFT_SERVER
-    cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): resume" << endl;
-#endif
-    Profiler profiler("FFTDataServer::resume", false);
-
-    m_suspended = false;
-    if (m_fillThread) {
-        if (m_fillThread->isFinished()) {
-            delete m_fillThread;
-            m_fillThread = 0;
-            deleteProcessingData();
-        } else if (!m_fillThread->isRunning()) {
-            m_fillThread->start();
-        } else {
-            m_condition.wakeAll();
-        }
-    }
-}
-
-void
-FFTDataServer::getStorageAdvice(size_t w, size_t h,
-                                bool &memoryCache, bool &compactCache)
-{
-    int cells = w * h;
-    int minimumSize = (cells / 1024) * sizeof(uint16_t); // kb
-    int maximumSize = (cells / 1024) * sizeof(float); // kb
-
-    // We don't have a compact rectangular representation, and compact
-    // of course is never precision-critical
-
-    bool canCompact = true;
-    if ((m_criteria & StorageAdviser::PrecisionCritical) || !m_polar) {
-        canCompact = false;
-        minimumSize = maximumSize; // don't use compact
-    }
-    
-    StorageAdviser::Recommendation recommendation;
-
-    try {
-
-        recommendation =
-            StorageAdviser::recommend(m_criteria, minimumSize, maximumSize);
-
-    } catch (InsufficientDiscSpace s) {
-
-        // Delete any unused servers we may have been leaving around
-        // in case we wanted them again
-
-        purgeLimbo(0);
-
-        // This time we don't catch InsufficientDiscSpace -- we
-        // haven't allocated anything yet and can safely let the
-        // exception out to indicate to the caller that we can't
-        // handle it.
-
-        recommendation =
-            StorageAdviser::recommend(m_criteria, minimumSize, maximumSize);
-    }
-
-//    cerr << "Recommendation was: " << recommendation << endl;
-
-    memoryCache = false;
-
-    if ((recommendation & StorageAdviser::UseMemory) ||
-        (recommendation & StorageAdviser::PreferMemory)) {
-        memoryCache = true;
-    }
-
-    compactCache = canCompact &&
-        (recommendation & StorageAdviser::ConserveSpace);
-
-#ifdef DEBUG_FFT_SERVER
-    cerr << "FFTDataServer: memory cache = " << memoryCache << ", compact cache = " << compactCache << endl;
-    
-    cerr << "Width " << w << " of " << m_width << ", height " << h << ", size " << w * h << endl;
-#endif
-}
-
-bool
-FFTDataServer::makeCache(int c)
-{
-    // Creating the cache could take a significant amount of time.  We
-    // don't want to block readers on m_cacheVectorLock while this is
-    // happening, but we do want to block any further calls to
-    // makeCache.  So we use this lock solely to serialise this
-    // particular function -- it isn't used anywhere else.
-
-    QMutexLocker locker(&m_cacheCreationMutex);
-
-    m_cacheVectorLock.lockForRead();
-    if (m_caches[c]) {
-        // someone else must have created the cache between our
-        // testing for it and taking the mutex
-        m_cacheVectorLock.unlock();
-        return true;
-    }
-    m_cacheVectorLock.unlock();
-
-    // Now m_cacheCreationMutex is held, but m_cacheVectorLock is not
-    // -- readers can proceed, but callers to this function will block
-
-    CacheBlock *cb = new CacheBlock;
-
-    QString name = QString("%1-%2").arg(m_fileBaseName).arg(c);
-
-    size_t width = m_cacheWidth;
-    if (c * m_cacheWidth + width > m_width) {
-        width = m_width - c * m_cacheWidth;
-    }
-
-    bool memoryCache = false;
-    bool compactCache = false;
-
-    getStorageAdvice(width, m_height, memoryCache, compactCache);
-
-    bool success = false;
-
-    if (memoryCache) {
-
-        try {
-
-            cb->memoryCache = new FFTMemoryCache
-                (compactCache ? FFTCache::Compact :
-                      m_polar ? FFTCache::Polar :
-                                FFTCache::Rectangular,
-                 width, m_height);
-
-            success = true;
-
-        } catch (std::bad_alloc) {
-
-            delete cb->memoryCache;
-            cb->memoryCache = 0;
-            
-            cerr << "WARNING: Memory allocation failed when creating"
-                      << " FFT memory cache no. " << c << " of " << width 
-                      << "x" << m_height << " (of total width " << m_width
-                      << "): falling back to disc cache" << endl;
-
-            memoryCache = false;
-        }
-    }
-
-    if (!memoryCache) {
-
-        try {
-        
-            cb->fileCacheWriter = new FFTFileCacheWriter
-                (name,
-                 compactCache ? FFTCache::Compact :
-                      m_polar ? FFTCache::Polar :
-                                FFTCache::Rectangular,
-                 width, m_height);
-
-            success = true;
-
-        } catch (std::exception &e) {
-
-            delete cb->fileCacheWriter;
-            cb->fileCacheWriter = 0;
-            
-            cerr << "ERROR: Failed to construct disc cache for FFT data: "
-                      << e.what() << endl;
-
-            throw;
-        }
-    }
-
-    m_cacheVectorLock.lockForWrite();
-
-    m_caches[c] = cb;
-
-    m_cacheVectorLock.unlock();
-
-    return success;
-}
- 
-bool
-FFTDataServer::makeCacheReader(int c)
-{
-    // preconditions: m_caches[c] exists and contains a file writer;
-    // m_cacheVectorLock is not locked by this thread
-#ifdef DEBUG_FFT_SERVER
-    SVDEBUG << "FFTDataServer::makeCacheReader(" << c << ")" << endl;
-#endif
-
-    QThread *me = QThread::currentThread();
-    QWriteLocker locker(&m_cacheVectorLock);
-    CacheBlock *cb(m_caches.at(c));
-    if (!cb || !cb->fileCacheWriter) return false;
-
-    try {
-        
-        cb->fileCacheReader[me] = new FFTFileCacheReader(cb->fileCacheWriter);
-
-    } catch (std::exception &e) {
-
-        delete cb->fileCacheReader[me];
-        cb->fileCacheReader.erase(me);
-            
-        cerr << "ERROR: Failed to construct disc cache reader for FFT data: "
-                  << e.what() << endl;
-        return false;
-    }
-
-    // erase a reader that looks like it may no longer going to be
-    // used by this thread for a while (leaving alone the current
-    // and previous cache readers)
-    int deleteCandidate = c - 2;
-    if (deleteCandidate < 0) deleteCandidate = c + 2;
-    if (deleteCandidate >= m_caches.size()) {
-        return true;
-    }
-
-    cb = m_caches.at(deleteCandidate);
-    if (cb && cb->fileCacheReader.find(me) != cb->fileCacheReader.end()) {
-#ifdef DEBUG_FFT_SERVER
-        SVDEBUG << "FFTDataServer::makeCacheReader: Deleting probably unpopular reader " << deleteCandidate << " for this thread (as I create reader " << c << ")" << endl;
-#endif
-        delete cb->fileCacheReader[me];
-        cb->fileCacheReader.erase(me);
-    }
-            
-    return true;
-}
-       
-float
-FFTDataServer::getMagnitudeAt(size_t x, size_t y)
-{
-    Profiler profiler("FFTDataServer::getMagnitudeAt", false);
-
-    if (x >= m_width || y >= m_height) return 0;
-
-    float val = 0;
-
-    try {
-        size_t col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return 0;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getMagnitudeAt: filling");
-#ifdef DEBUG_FFT_SERVER
-            SVDEBUG << "FFTDataServer::getMagnitudeAt: calling fillColumn(" 
-                  << x << ")" << endl;
-#endif
-            fillColumn(x);
-        }
-
-        val = cache->getMagnitudeAt(col, y);
-
-    } catch (std::exception &e) {
-        m_error = e.what();
-    }
-
-    return val;
-}
-
-bool
-FFTDataServer::getMagnitudesAt(size_t x, float *values, size_t minbin, size_t count, size_t step)
-{
-    Profiler profiler("FFTDataServer::getMagnitudesAt", false);
-
-    if (x >= m_width) return false;
-
-    if (minbin >= m_height) minbin = m_height - 1;
-    if (count == 0) count = (m_height - minbin) / step;
-    else if (minbin + count * step > m_height) {
-        count = (m_height - minbin) / step;
-    }
-
-    try {
-        size_t col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return false;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getMagnitudesAt: filling");
-            fillColumn(x);
-        }
-
-        cache->getMagnitudesAt(col, values, minbin, count, step);
-
-    } catch (std::exception &e) {
-        m_error = e.what();
-        return false;
-    }
-
-    return true;
-}
-
-float
-FFTDataServer::getNormalizedMagnitudeAt(size_t x, size_t y)
-{
-    Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt", false);
-
-    if (x >= m_width || y >= m_height) return 0;
-
-    float val = 0;
-
-    try {
-
-        size_t col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return 0;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt: filling");
-            fillColumn(x);
-        }
-        val = cache->getNormalizedMagnitudeAt(col, y);
-
-    } catch (std::exception &e) {
-        m_error = e.what();
-    }
-
-    return val;
-}
-
-bool
-FFTDataServer::getNormalizedMagnitudesAt(size_t x, float *values, size_t minbin, size_t count, size_t step)
-{
-    Profiler profiler("FFTDataServer::getNormalizedMagnitudesAt", false);
-
-    if (x >= m_width) return false;
-
-    if (minbin >= m_height) minbin = m_height - 1;
-    if (count == 0) count = (m_height - minbin) / step;
-    else if (minbin + count * step > m_height) {
-        count = (m_height - minbin) / step;
-    }
-
-    try {
-
-        size_t col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return false;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getNormalizedMagnitudesAt: filling");
-            fillColumn(x);
-        }
-        
-        for (size_t i = 0; i < count; ++i) {
-            values[i] = cache->getNormalizedMagnitudeAt(col, i * step + minbin);
-        }
-        
-    } catch (std::exception &e) {
-        m_error = e.what();
-        return false;
-    }
-
-    return true;
-}
-
-float
-FFTDataServer::getMaximumMagnitudeAt(size_t x)
-{
-    Profiler profiler("FFTDataServer::getMaximumMagnitudeAt", false);
-
-    if (x >= m_width) return 0;
-
-    float val = 0;
-
-    try {
-
-        size_t col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return 0;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getMaximumMagnitudeAt: filling");
-            fillColumn(x);
-        }
-        val = cache->getMaximumMagnitudeAt(col);
-
-    } catch (std::exception &e) {
-        m_error = e.what();
-    }
-
-    return val;
-}
-
-float
-FFTDataServer::getPhaseAt(size_t x, size_t y)
-{
-    Profiler profiler("FFTDataServer::getPhaseAt", false);
-
-    if (x >= m_width || y >= m_height) return 0;
-
-    float val = 0;
-
-    try {
-
-        size_t col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return 0;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getPhaseAt: filling");
-            fillColumn(x);
-        }
-        val = cache->getPhaseAt(col, y);
-
-    } catch (std::exception &e) {
-        m_error = e.what();
-    }
-
-    return val;
-}
-
-bool
-FFTDataServer::getPhasesAt(size_t x, float *values, size_t minbin, size_t count, size_t step)
-{
-    Profiler profiler("FFTDataServer::getPhasesAt", false);
-
-    if (x >= m_width) return false;
-
-    if (minbin >= m_height) minbin = m_height - 1;
-    if (count == 0) count = (m_height - minbin) / step;
-    else if (minbin + count * step > m_height) {
-        count = (m_height - minbin) / step;
-    }
-
-    try {
-
-        size_t col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return false;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getPhasesAt: filling");
-            fillColumn(x);
-        }
-        
-        for (size_t i = 0; i < count; ++i) {
-            values[i] = cache->getPhaseAt(col, i * step + minbin);
-        }
-
-    } catch (std::exception &e) {
-        m_error = e.what();
-        return false;
-    }
-
-    return true;
-}
-
-void
-FFTDataServer::getValuesAt(size_t x, size_t y, float &real, float &imaginary)
-{
-    Profiler profiler("FFTDataServer::getValuesAt", false);
-
-    if (x >= m_width || y >= m_height) {
-        real = 0;
-        imaginary = 0;
-        return;
-    }
-
-    try {
-
-        size_t col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-
-        if (!cache) {
-            real = 0;
-            imaginary = 0;
-            return;
-        }
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getValuesAt: filling");
-#ifdef DEBUG_FFT_SERVER
-            SVDEBUG << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << endl;
-#endif
-            fillColumn(x);
-        }        
-
-        cache->getValuesAt(col, y, real, imaginary);
-
-    } catch (std::exception &e) {
-        m_error = e.what();
-    }
-}
-
-bool
-FFTDataServer::getValuesAt(size_t x, float *reals, float *imaginaries, size_t minbin, size_t count, size_t step)
-{
-    Profiler profiler("FFTDataServer::getValuesAt", false);
-
-    if (x >= m_width) return false;
-
-    if (minbin >= m_height) minbin = m_height - 1;
-    if (count == 0) count = (m_height - minbin) / step;
-    else if (minbin + count * step > m_height) {
-        count = (m_height - minbin) / step;
-    }
-
-    try {
-
-        size_t col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return false;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getValuesAt: filling");
-            fillColumn(x);
-        }
-
-        for (size_t i = 0; i < count; ++i) {
-            cache->getValuesAt(col, i * step + minbin, reals[i], imaginaries[i]);
-        }
-
-    } catch (std::exception &e) {
-        m_error = e.what();
-        return false;
-    }
-
-    return true;
-}
-
-bool
-FFTDataServer::isColumnReady(size_t x)
-{
-    Profiler profiler("FFTDataServer::isColumnReady", false);
-
-    if (x >= m_width) return true;
-
-    if (!haveCache(x)) {
-/*!!!
-        if (m_lastUsedCache == -1) {
-            if (m_suspended) {
-                SVDEBUG << "FFTDataServer::isColumnReady(" << x << "): no cache, calling resume" << endl;
-                resume();
-            }
-            m_fillThread->start();
-        }
-*/
-        return false;
-    }
-
-    try {
-
-        size_t col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return true;
-
-        return cache->haveSetColumnAt(col);
-
-    } catch (std::exception &e) {
-        m_error = e.what();
-        return false;
-    }
-}    
-
-void
-FFTDataServer::fillColumn(size_t x)
-{
-    Profiler profiler("FFTDataServer::fillColumn", false);
-
-    if (!m_model->isReady()) {
-        cerr << "WARNING: FFTDataServer::fillColumn(" 
-                  << x << "): model not yet ready" << endl;
-        return;
-    }
-/*
-    if (!m_fftInput) {
-        cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): "
-                  << "input has already been completed and discarded?"
-                  << endl;
-        return;
-    }
-*/
-    if (x >= m_width) {
-        cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): "
-                  << "x > width (" << x << " > " << m_width << ")"
-                  << endl;
-        return;
-    }
-
-    size_t col;
-#ifdef DEBUG_FFT_SERVER_FILL
-    cout << "FFTDataServer::fillColumn(" << x << ")" << endl;
-#endif
-    FFTCacheWriter *cache = getCacheWriter(x, col);
-    if (!cache) return;
-
-    int winsize = m_windowSize;
-    int fftsize = m_fftSize;
-    int hs = fftsize/2;
-
-    int pfx = 0;
-    int off = (fftsize - winsize) / 2;
-
-    int startFrame = m_windowIncrement * x;
-    int endFrame = startFrame + m_windowSize;
-
-    startFrame -= winsize / 2;
-    endFrame   -= winsize / 2;
-
-#ifdef DEBUG_FFT_SERVER_FILL
-    SVDEBUG << "FFTDataServer::fillColumn: requesting frames "
-              << startFrame + pfx << " -> " << endFrame << " ( = "
-              << endFrame - (startFrame + pfx) << ") at index "
-              << off + pfx << " in buffer of size " << m_fftSize
-              << " with window size " << m_windowSize 
-              << " from channel " << m_channel << endl;
-#endif
-
-    QMutexLocker locker(&m_fftBuffersLock);
-
-    // We may have been called from a function that wanted to obtain a
-    // column using an FFTCacheReader.  Before calling us, it checked
-    // whether the column was available already, and the reader
-    // reported that it wasn't.  Now we test again, with the mutex
-    // held, to avoid a race condition in case another thread has
-    // called fillColumn at the same time.
-    if (cache->haveSetColumnAt(x & m_cacheWidthMask)) {
-        return;
-    }
-
-    if (!m_fftInput) {
-        cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): "
-                  << "input has already been completed and discarded?"
-                  << endl;
-        return;
-    }
-
-    for (int i = 0; i < off; ++i) {
-        m_fftInput[i] = 0.0;
-    }
-
-    for (int i = 0; i < off; ++i) {
-        m_fftInput[fftsize - i - 1] = 0.0;
-    }
-
-    if (startFrame < 0) {
-	pfx = -startFrame;
-	for (int i = 0; i < pfx; ++i) {
-	    m_fftInput[off + i] = 0.0;
-	}
-    }
-
-    int count = 0;
-    if (endFrame > startFrame + pfx) count = endFrame - (startFrame + pfx);
-
-    int got = m_model->getData(m_channel, startFrame + pfx,
-                               count, m_fftInput + off + pfx);
-
-    while (got + pfx < winsize) {
-	m_fftInput[off + got + pfx] = 0.0;
-	++got;
-    }
-
-    if (m_channel == -1) {
-	int channels = m_model->getChannelCount();
-	if (channels > 1) {
-	    for (int i = 0; i < winsize; ++i) {
-		m_fftInput[off + i] /= channels;
-	    }
-	}
-    }
-
-    m_windower.cut(m_fftInput + off);
-
-    for (int i = 0; i < hs; ++i) {
-	fftsample temp = m_fftInput[i];
-	m_fftInput[i] = m_fftInput[i + hs];
-	m_fftInput[i + hs] = temp;
-    }
-
-    fftf_execute(m_fftPlan);
-
-    float factor = 0.f;
-
-    if (cache->getStorageType() == FFTCache::Compact ||
-        cache->getStorageType() == FFTCache::Polar) {
-
-        for (int i = 0; i <= hs; ++i) {
-            fftsample real = m_fftOutput[i][0];
-            fftsample imag = m_fftOutput[i][1];
-            float mag = sqrtf(real * real + imag * imag);
-            m_workbuffer[i] = mag;
-            m_workbuffer[i + hs + 1] = atan2f(imag, real);
-            if (mag > factor) factor = mag;
-        }
-
-    } else {
-
-        for (int i = 0; i <= hs; ++i) {
-            m_workbuffer[i] = m_fftOutput[i][0];
-            m_workbuffer[i + hs + 1] = m_fftOutput[i][1];
-        }
-    }
-
-    Profiler subprof("FFTDataServer::fillColumn: set to cache");
-
-    if (cache->getStorageType() == FFTCache::Compact ||
-        cache->getStorageType() == FFTCache::Polar) {
-            
-        cache->setColumnAt(col,
-                           m_workbuffer,
-                           m_workbuffer + hs + 1,
-                           factor);
-
-    } else {
-
-        cache->setColumnAt(col,
-                           m_workbuffer,
-                           m_workbuffer + hs + 1);
-    }
-
-    if (m_suspended) {
-//        SVDEBUG << "FFTDataServer::fillColumn(" << x << "): calling resume" << endl;
-//        resume();
-    }
-}    
-
-void
-FFTDataServer::fillComplete()
-{
-    for (int i = 0; i < int(m_caches.size()); ++i) {
-        if (!m_caches[i]) continue;
-        if (m_caches[i]->memoryCache) {
-            m_caches[i]->memoryCache->allColumnsWritten();
-        }
-        if (m_caches[i]->fileCacheWriter) {
-            m_caches[i]->fileCacheWriter->allColumnsWritten();
-        }
-    }
-}
-
-QString
-FFTDataServer::getError() const
-{
-    if (m_error != "") return m_error;
-    else if (m_fillThread) return m_fillThread->getError();
-    else return "";
-}
-
-size_t
-FFTDataServer::getFillCompletion() const 
-{
-    if (m_fillThread) return m_fillThread->getCompletion();
-    else return 100;
-}
-
-size_t
-FFTDataServer::getFillExtent() const
-{
-    if (m_fillThread) return m_fillThread->getExtent();
-    else return m_model->getEndFrame();
-}
-
-QString
-FFTDataServer::generateFileBasename() const
-{
-    return generateFileBasename(m_model, m_channel, m_windower.getType(),
-                                m_windowSize, m_windowIncrement, m_fftSize,
-                                m_polar);
-}
-
-QString
-FFTDataServer::generateFileBasename(const DenseTimeValueModel *model,
-                                    int channel,
-                                    WindowType windowType,
-                                    size_t windowSize,
-                                    size_t windowIncrement,
-                                    size_t fftSize,
-                                    bool polar)
-{
-    char buffer[200];
-
-    sprintf(buffer, "%u-%u-%u-%u-%u-%u%s",
-            (unsigned int)XmlExportable::getObjectExportId(model),
-            (unsigned int)(channel + 1),
-            (unsigned int)windowType,
-            (unsigned int)windowSize,
-            (unsigned int)windowIncrement,
-            (unsigned int)fftSize,
-            polar ? "-p" : "-r");
-
-    return buffer;
-}
-
-void
-FFTDataServer::FillThread::run()
-{
-#ifdef DEBUG_FFT_SERVER_FILL
-    SVDEBUG << "FFTDataServer::FillThread::run()" << endl;
-#endif
-    
-    m_extent = 0;
-    m_completion = 0;
-    
-    while (!m_server.m_model->isReady() && !m_server.m_exiting) {
-#ifdef DEBUG_FFT_SERVER_FILL
-        SVDEBUG << "FFTDataServer::FillThread::run(): waiting for model " << m_server.m_model << " to be ready" << endl;
-#endif
-        sleep(1);
-    }
-    if (m_server.m_exiting) return;
-
-    size_t start = m_server.m_model->getStartFrame();
-    size_t end = m_server.m_model->getEndFrame();
-    size_t remainingEnd = end;
-
-    int counter = 0;
-    int updateAt = 1;
-    int maxUpdateAt = (end / m_server.m_windowIncrement) / 20;
-    if (maxUpdateAt < 100) maxUpdateAt = 100;
-
-    if (m_fillFrom > start) {
-
-        for (size_t f = m_fillFrom; f < end; f += m_server.m_windowIncrement) {
-	    
-            try {
-                m_server.fillColumn(int((f - start) / m_server.m_windowIncrement));
-            } catch (std::exception &e) {
-                SVDEBUG << "FFTDataServer::FillThread::run: exception: " << e.what() << endl;
-                m_error = e.what();
-                m_server.fillComplete();
-                m_completion = 100;
-                m_extent = end;
-                return;
-            }
-
-            if (m_server.m_exiting) return;
-
-            while (m_server.m_suspended) {
-#ifdef DEBUG_FFT_SERVER
-                cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << endl;
-#endif
-                MutexLocker locker(&m_server.m_fftBuffersLock,
-                                   "FFTDataServer::run::m_fftBuffersLock [1]");
-                if (m_server.m_suspended && !m_server.m_exiting) {
-                    m_server.m_condition.wait(&m_server.m_fftBuffersLock, 10000);
-                }
-#ifdef DEBUG_FFT_SERVER
-                cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): waited" << endl;
-#endif
-                if (m_server.m_exiting) return;
-            }
-
-            if (++counter == updateAt) {
-                m_extent = f;
-                m_completion = size_t(100 * fabsf(float(f - m_fillFrom) /
-                                                  float(end - start)));
-                counter = 0;
-                if (updateAt < maxUpdateAt) {
-                    updateAt *= 2;
-                    if (updateAt > maxUpdateAt) updateAt = maxUpdateAt;
-                }
-            }
-        }
-
-        remainingEnd = m_fillFrom;
-        if (remainingEnd > start) --remainingEnd;
-        else remainingEnd = start;
-    }
-
-    size_t baseCompletion = m_completion;
-
-    for (size_t f = start; f < remainingEnd; f += m_server.m_windowIncrement) {
-
-        try {
-            m_server.fillColumn(int((f - start) / m_server.m_windowIncrement));
-        } catch (std::exception &e) {
-            SVDEBUG << "FFTDataServer::FillThread::run: exception: " << e.what() << endl;
-            m_error = e.what();
-            m_server.fillComplete();
-            m_completion = 100;
-            m_extent = end;
-            return;
-        }
-
-        if (m_server.m_exiting) return;
-
-        while (m_server.m_suspended) {
-#ifdef DEBUG_FFT_SERVER
-            cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << endl;
-#endif
-            {
-                MutexLocker locker(&m_server.m_fftBuffersLock,
-                                   "FFTDataServer::run::m_fftBuffersLock [2]");
-                if (m_server.m_suspended && !m_server.m_exiting) {
-                    m_server.m_condition.wait(&m_server.m_fftBuffersLock, 10000);
-                }
-            }
-            if (m_server.m_exiting) return;
-        }
-		    
-        if (++counter == updateAt) {
-            m_extent = f;
-            m_completion = baseCompletion +
-                size_t(100 * fabsf(float(f - start) /
-                                   float(end - start)));
-            counter = 0;
-            if (updateAt < maxUpdateAt) {
-                updateAt *= 2;
-                if (updateAt > maxUpdateAt) updateAt = maxUpdateAt;
-            }
-        }
-    }
-
-    m_server.fillComplete();
-    m_completion = 100;
-    m_extent = end;
-
-#ifdef DEBUG_FFT_SERVER
-    SVDEBUG << "FFTDataServer::FillThread::run exiting" << endl;
-#endif
-}
-
--- a/data/fft/FFTDataServer.h	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,294 +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 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 _FFT_DATA_SERVER_H_
-#define _FFT_DATA_SERVER_H_
-
-#include "base/Window.h"
-#include "base/Thread.h"
-#include "base/StorageAdviser.h"
-
-#include "FFTapi.h"
-#include "FFTFileCacheReader.h"
-#include "FFTFileCacheWriter.h"
-#include "FFTMemoryCache.h"
-
-#include <QMutex>
-#include <QReadWriteLock>
-#include <QReadLocker>
-#include <QWaitCondition>
-#include <QString>
-
-#include <vector>
-#include <deque>
-
-class DenseTimeValueModel;
-class Model;
-
-class FFTDataServer
-{
-public:
-    static FFTDataServer *getInstance(const DenseTimeValueModel *model,
-                                      int channel,
-                                      WindowType windowType,
-                                      size_t windowSize,
-                                      size_t windowIncrement,
-                                      size_t fftSize,
-                                      bool polar,
-                                      StorageAdviser::Criteria criteria =
-                                          StorageAdviser::NoCriteria,
-                                      size_t fillFromColumn = 0);
-
-    static FFTDataServer *getFuzzyInstance(const DenseTimeValueModel *model,
-                                           int channel,
-                                           WindowType windowType,
-                                           size_t windowSize,
-                                           size_t windowIncrement,
-                                           size_t fftSize,
-                                           bool polar,
-                                           StorageAdviser::Criteria criteria =
-                                               StorageAdviser::NoCriteria,
-                                           size_t fillFromColumn = 0);
-
-    static void claimInstance(FFTDataServer *);
-    static void releaseInstance(FFTDataServer *);
-
-    static void modelAboutToBeDeleted(Model *);
-
-    const DenseTimeValueModel *getModel() const { return m_model; }
-    int        getChannel() const { return m_channel; }
-    WindowType getWindowType() const { return m_windower.getType(); }
-    size_t     getWindowSize() const { return m_windowSize; }
-    size_t     getWindowIncrement() const { return m_windowIncrement; }
-    size_t     getFFTSize() const { return m_fftSize; }
-    bool       getPolar() const { return m_polar; }
-
-    size_t     getWidth() const  { return m_width;  }
-    size_t     getHeight() const { return m_height; }
-
-    float      getMagnitudeAt(size_t x, size_t y);
-    float      getNormalizedMagnitudeAt(size_t x, size_t y);
-    float      getMaximumMagnitudeAt(size_t x);
-    float      getPhaseAt(size_t x, size_t y);
-    void       getValuesAt(size_t x, size_t y, float &real, float &imaginary);
-    bool       isColumnReady(size_t x);
-
-    bool       getMagnitudesAt(size_t x, float *values, size_t minbin = 0, size_t count = 0, size_t step = 1);
-    bool       getNormalizedMagnitudesAt(size_t x, float *values, size_t minbin = 0, size_t count = 0, size_t step = 1);
-    bool       getPhasesAt(size_t x, float *values, size_t minbin = 0, size_t count = 0, size_t step = 1);
-    bool       getValuesAt(size_t x, float *reals, float *imaginaries, size_t minbin = 0, size_t count = 0, size_t step = 1);
-
-    void       suspend();
-    void       suspendWrites();
-    void       resume(); // also happens automatically if new data needed
-
-    // Convenience functions:
-
-    bool isLocalPeak(size_t x, size_t y) {
-        float mag = getMagnitudeAt(x, y);
-        if (y > 0 && mag < getMagnitudeAt(x, y - 1)) return false;
-        if (y < getHeight()-1 && mag < getMagnitudeAt(x, y + 1)) return false;
-        return true;
-    }
-    bool isOverThreshold(size_t x, size_t y, float threshold) {
-        return getMagnitudeAt(x, y) > threshold;
-    }
-
-    QString getError() const;
-    size_t getFillCompletion() const;
-    size_t getFillExtent() const;
-
-private:
-    FFTDataServer(QString fileBaseName,
-                  const DenseTimeValueModel *model,
-                  int channel,
-                  WindowType windowType,
-                  size_t windowSize,
-                  size_t windowIncrement,
-                  size_t fftSize,
-                  bool polar,
-                  StorageAdviser::Criteria criteria,
-                  size_t fillFromColumn = 0);
-
-    virtual ~FFTDataServer();
-
-    FFTDataServer(const FFTDataServer &); // not implemented
-    FFTDataServer &operator=(const FFTDataServer &); // not implemented
-
-    typedef float fftsample;
-
-    QString m_fileBaseName;
-    const DenseTimeValueModel *m_model;
-    int m_channel;
-
-    Window<fftsample> m_windower;
-
-    size_t m_windowSize;
-    size_t m_windowIncrement;
-    size_t m_fftSize;
-    bool m_polar;
-
-    size_t m_width;
-    size_t m_height;
-    size_t m_cacheWidth;
-    size_t m_cacheWidthPower;
-    size_t m_cacheWidthMask;
-
-    struct CacheBlock {
-        FFTMemoryCache *memoryCache;
-        typedef std::map<QThread *, FFTFileCacheReader *> ThreadReaderMap;
-        ThreadReaderMap fileCacheReader;
-        FFTFileCacheWriter *fileCacheWriter;
-        CacheBlock() : memoryCache(0), fileCacheWriter(0) { }
-        ~CacheBlock() {
-            delete memoryCache; 
-            while (!fileCacheReader.empty()) {
-                delete fileCacheReader.begin()->second;
-                fileCacheReader.erase(fileCacheReader.begin());
-            }
-            delete fileCacheWriter;
-        }
-    };
-
-    typedef std::vector<CacheBlock *> CacheVector;
-    CacheVector m_caches;
-    QReadWriteLock m_cacheVectorLock; // locks cache lookup, not use
-    QMutex m_cacheCreationMutex; // solely to serialise makeCache() calls
-
-    FFTCacheReader *getCacheReader(size_t x, size_t &col) {
-        Profiler profiler("FFTDataServer::getCacheReader");
-        col = x & m_cacheWidthMask;
-        int c = x >> m_cacheWidthPower;
-        m_cacheVectorLock.lockForRead();
-        CacheBlock *cb(m_caches.at(c));
-        if (cb) {
-            if (cb->memoryCache) {
-                m_cacheVectorLock.unlock();
-                return cb->memoryCache;
-            }
-            if (cb->fileCacheWriter) {
-                QThread *me = QThread::currentThread();
-                CacheBlock::ThreadReaderMap &map = cb->fileCacheReader;
-                if (map.find(me) == map.end()) {
-                    m_cacheVectorLock.unlock();
-                    if (!makeCacheReader(c)) return 0;
-                    return getCacheReader(x, col);
-                }
-                FFTCacheReader *reader = cb->fileCacheReader[me];
-                m_cacheVectorLock.unlock();
-                return reader;
-            }
-            // if cb exists but cb->fileCacheWriter doesn't, creation
-            // must have failed: don't try again
-            m_cacheVectorLock.unlock();
-            return 0;
-        }
-        m_cacheVectorLock.unlock();
-        if (!makeCache(c)) return 0;
-        return getCacheReader(x, col);
-    }
-    
-    FFTCacheWriter *getCacheWriter(size_t x, size_t &col) {
-        Profiler profiler("FFTDataServer::getCacheWriter");
-        col = x & m_cacheWidthMask;
-        int c = x >> m_cacheWidthPower;
-        {
-            QReadLocker locker(&m_cacheVectorLock);
-            CacheBlock *cb(m_caches.at(c));
-            if (cb) {
-                if (cb->memoryCache) return cb->memoryCache;
-                if (cb->fileCacheWriter) return cb->fileCacheWriter;
-                // if cb exists, creation must have failed: don't try again
-                return 0;
-            }
-        }
-        if (!makeCache(c)) return 0;
-        return getCacheWriter(x, col);
-    }
-
-    bool haveCache(size_t x) {
-        int c = x >> m_cacheWidthPower;
-        return (m_caches.at(c) != 0);
-    }
-    
-    bool makeCache(int c);
-    bool makeCacheReader(int c);
-    
-    StorageAdviser::Criteria m_criteria;
-
-    void getStorageAdvice(size_t w, size_t h, bool &memory, bool &compact);
-        
-    QMutex m_fftBuffersLock;
-    QWaitCondition m_condition;
-
-    fftsample *m_fftInput;
-    fftf_complex *m_fftOutput;
-    float *m_workbuffer;
-    fftf_plan m_fftPlan;
-
-    class FillThread : public Thread
-    {
-    public:
-        FillThread(FFTDataServer &server, size_t fillFromColumn) :
-            m_server(server), m_extent(0), m_completion(0),
-            m_fillFrom(fillFromColumn) { }
-
-        size_t getExtent() const { return m_extent; }
-        size_t getCompletion() const { return m_completion ? m_completion : 1; }
-        QString getError() const { return m_error; }
-        virtual void run();
-
-    protected:
-        FFTDataServer &m_server;
-        size_t m_extent;
-        size_t m_completion;
-        size_t m_fillFrom;
-        QString m_error;
-    };
-
-    bool m_exiting;
-    bool m_suspended;
-    FillThread *m_fillThread;
-    QString m_error;
-
-    void deleteProcessingData();
-    void fillColumn(size_t x);
-    void fillComplete();
-
-    QString generateFileBasename() const;
-    static QString generateFileBasename(const DenseTimeValueModel *model,
-                                        int channel,
-                                        WindowType windowType,
-                                        size_t windowSize,
-                                        size_t windowIncrement,
-                                        size_t fftSize,
-                                        bool polar);
-
-    typedef std::pair<FFTDataServer *, int> ServerCountPair;
-    typedef std::map<QString, ServerCountPair> ServerMap;
-    typedef std::deque<FFTDataServer *> ServerQueue;
-
-    static ServerMap m_servers;
-    static ServerQueue m_releasedServers; // these are still in m_servers as well, with zero refcount
-    static QMutex m_serverMapMutex;
-    static FFTDataServer *findServer(QString); // call with serverMapMutex held
-    static void purgeLimbo(int maxSize = 3); // call with serverMapMutex held
-
-    static void claimInstance(FFTDataServer *, bool needLock);
-    static void releaseInstance(FFTDataServer *, bool needLock);
-
-};
-
-#endif
--- a/data/fft/FFTFileCacheReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,276 +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-2009 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.
-*/
-
-#include "FFTFileCacheReader.h"
-#include "FFTFileCacheWriter.h"
-
-#include "fileio/MatrixFile.h"
-
-#include "base/Profiler.h"
-#include "base/Thread.h"
-#include "base/Exceptions.h"
-
-#include <iostream>
-
-
-// The underlying matrix has height (m_height * 2 + 1).  In each
-// column we store magnitude at [0], [2] etc and phase at [1], [3]
-// etc, and then store the normalization factor (maximum magnitude) at
-// [m_height * 2].  In compact mode, the factor takes two cells.
-
-FFTFileCacheReader::FFTFileCacheReader(FFTFileCacheWriter *writer) :
-    m_readbuf(0),
-    m_readbufCol(0),
-    m_readbufWidth(0),
-    m_readbufGood(false),
-    m_storageType(writer->getStorageType()),
-    m_factorSize(m_storageType == FFTCache::Compact ? 2 : 1),
-    m_mfc(new MatrixFile
-          (writer->getFileBase(),
-           MatrixFile::ReadOnly,
-           m_storageType == FFTCache::Compact ? sizeof(uint16_t) : sizeof(float),
-           writer->getWidth(),
-           writer->getHeight() * 2 + m_factorSize))
-{
-//    cerr << "FFTFileCacheReader: storage type is " << (storageType == FFTCache::Compact ? "Compact" : storageType == Polar ? "Polar" : "Rectangular") << endl;
-}
-
-FFTFileCacheReader::~FFTFileCacheReader()
-{
-    if (m_readbuf) delete[] m_readbuf;
-    delete m_mfc;
-}
-
-size_t
-FFTFileCacheReader::getWidth() const
-{
-    return m_mfc->getWidth();
-}
-
-size_t
-FFTFileCacheReader::getHeight() const
-{
-    size_t mh = m_mfc->getHeight();
-    if (mh > m_factorSize) return (mh - m_factorSize) / 2;
-    else return 0;
-}
-
-float
-FFTFileCacheReader::getMagnitudeAt(size_t x, size_t y) const
-{
-    Profiler profiler("FFTFileCacheReader::getMagnitudeAt", false);
-
-    float value = 0.f;
-
-    switch (m_storageType) {
-
-    case FFTCache::Compact:
-        value = (getFromReadBufCompactUnsigned(x, y * 2) / 65535.0)
-            * getNormalizationFactor(x);
-        break;
-
-    case FFTCache::Rectangular:
-    {
-        float real, imag;
-        getValuesAt(x, y, real, imag);
-        value = sqrtf(real * real + imag * imag);
-        break;
-    }
-
-    case FFTCache::Polar:
-        value = getFromReadBufStandard(x, y * 2);
-        break;
-    }
-
-    return value;
-}
-
-float
-FFTFileCacheReader::getNormalizedMagnitudeAt(size_t x, size_t y) const
-{
-    float value = 0.f;
-
-    switch (m_storageType) {
-
-    case FFTCache::Compact:
-        value = getFromReadBufCompactUnsigned(x, y * 2) / 65535.0;
-        break;
-
-    default:
-    {
-        float mag = getMagnitudeAt(x, y);
-        float factor = getNormalizationFactor(x);
-        if (factor != 0) value = mag / factor;
-        else value = 0.f;
-        break;
-    }
-    }
-
-    return value;
-}
-
-float
-FFTFileCacheReader::getMaximumMagnitudeAt(size_t x) const
-{
-    return getNormalizationFactor(x);
-}
-
-float
-FFTFileCacheReader::getPhaseAt(size_t x, size_t y) const
-{
-    float value = 0.f;
-    
-    switch (m_storageType) {
-
-    case FFTCache::Compact:
-        value = (getFromReadBufCompactSigned(x, y * 2 + 1) / 32767.0) * M_PI;
-        break;
-
-    case FFTCache::Rectangular:
-    {
-        float real, imag;
-        getValuesAt(x, y, real, imag);
-        value = atan2f(imag, real);
-        break;
-    }
-
-    case FFTCache::Polar:
-        value = getFromReadBufStandard(x, y * 2 + 1);
-        break;
-    }
-
-    return value;
-}
-
-void
-FFTFileCacheReader::getValuesAt(size_t x, size_t y, float &real, float &imag) const
-{
-//    SVDEBUG << "FFTFileCacheReader::getValuesAt(" << x << "," << y << ")" << endl;
-
-    switch (m_storageType) {
-
-    case FFTCache::Rectangular:
-        real = getFromReadBufStandard(x, y * 2);
-        imag = getFromReadBufStandard(x, y * 2 + 1);
-        return;
-
-    default:
-        float mag = getMagnitudeAt(x, y);
-        float phase = getPhaseAt(x, y);
-        real = mag * cosf(phase);
-        imag = mag * sinf(phase);
-        return;
-    }
-}
-
-void
-FFTFileCacheReader::getMagnitudesAt(size_t x, float *values, size_t minbin, size_t count, size_t step) const
-{
-    Profiler profiler("FFTFileCacheReader::getMagnitudesAt");
-
-    switch (m_storageType) {
-
-    case FFTCache::Compact:
-        for (size_t i = 0; i < count; ++i) {
-            size_t y = minbin + i * step;
-            values[i] = (getFromReadBufCompactUnsigned(x, y * 2) / 65535.0)
-                * getNormalizationFactor(x);
-        }
-        break;
-
-    case FFTCache::Rectangular:
-    {
-        float real, imag;
-        for (size_t i = 0; i < count; ++i) {
-            size_t y = minbin + i * step;
-            real = getFromReadBufStandard(x, y * 2);
-            imag = getFromReadBufStandard(x, y * 2 + 1);
-            values[i] = sqrtf(real * real + imag * imag);
-        }
-        break;
-    }
-
-    case FFTCache::Polar:
-        for (size_t i = 0; i < count; ++i) {
-            size_t y = minbin + i * step;
-            values[i] = getFromReadBufStandard(x, y * 2);
-        }
-        break;
-    }
-}
-
-bool
-FFTFileCacheReader::haveSetColumnAt(size_t x) const
-{
-    if (m_readbuf && m_readbufGood &&
-        (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) {
-//        SVDEBUG << "FFTFileCacheReader::haveSetColumnAt: short-circuiting; we know about this one" << endl;
-        return true;
-    }
-    return m_mfc->haveSetColumnAt(x);
-}
-
-size_t
-FFTFileCacheReader::getCacheSize(size_t width, size_t height,
-                                 FFTCache::StorageType type)
-{
-    return (height * 2 + (type == FFTCache::Compact ? 2 : 1)) * width *
-        (type == FFTCache::Compact ? sizeof(uint16_t) : sizeof(float)) +
-        2 * sizeof(size_t); // matrix file header size
-}
-
-void
-FFTFileCacheReader::populateReadBuf(size_t x) const
-{
-    Profiler profiler("FFTFileCacheReader::populateReadBuf", false);
-
-//    SVDEBUG << "FFTFileCacheReader::populateReadBuf(" << x << ")" << endl;
-
-    if (!m_readbuf) {
-        m_readbuf = new char[m_mfc->getHeight() * 2 * m_mfc->getCellSize()];
-    }
-
-    m_readbufGood = false;
-
-    try {
-        bool good = false;
-        if (m_mfc->haveSetColumnAt(x)) {
-            // If the column is not available, we have no obligation
-            // to do anything with the readbuf -- we can cheerfully
-            // return garbage.  It's the responsibility of the caller
-            // to check haveSetColumnAt before trusting any retrieved
-            // data.  However, we do record whether the data in the
-            // readbuf is good or not, because we can use that to
-            // return an immediate result for haveSetColumnAt if the
-            // column is right.
-            good = true;
-            m_mfc->getColumnAt(x, m_readbuf);
-        }
-        if (m_mfc->haveSetColumnAt(x + 1)) {
-            m_mfc->getColumnAt
-                (x + 1, m_readbuf + m_mfc->getCellSize() * m_mfc->getHeight());
-            m_readbufWidth = 2;
-        } else {
-            m_readbufWidth = 1;
-        }
-        m_readbufGood = good;
-    } catch (FileReadFailed f) {
-        cerr << "ERROR: FFTFileCacheReader::populateReadBuf: File read failed: "
-                  << f.what() << endl;
-        memset(m_readbuf, 0, m_mfc->getHeight() * 2 * m_mfc->getCellSize());
-    }
-    m_readbufCol = x;
-}
-
--- a/data/fft/FFTFileCacheReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +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-2009 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 _FFT_FILE_CACHE_READER_H_
-#define _FFT_FILE_CACHE_READER_H_
-
-#include "data/fileio/MatrixFile.h"
-#include "FFTCacheReader.h"
-#include "FFTCacheStorageType.h"
-
-class FFTFileCacheWriter;
-
-class FFTFileCacheReader : public FFTCacheReader
-{
-public:
-    FFTFileCacheReader(FFTFileCacheWriter *);
-    ~FFTFileCacheReader();
-
-    size_t getWidth() const;
-    size_t getHeight() const;
-	
-    float getMagnitudeAt(size_t x, size_t y) const;
-    float getNormalizedMagnitudeAt(size_t x, size_t y) const;
-    float getMaximumMagnitudeAt(size_t x) const;
-    float getPhaseAt(size_t x, size_t y) const;
-
-    void getValuesAt(size_t x, size_t y, float &real, float &imag) const;
-    void getMagnitudesAt(size_t x, float *values, size_t minbin, size_t count, size_t step) const;
-
-    bool haveSetColumnAt(size_t x) const;
-
-    static size_t getCacheSize(size_t width, size_t height,
-                               FFTCache::StorageType type);
-
-    FFTCache::StorageType getStorageType() const { return m_storageType; }
-
-protected:
-    mutable char *m_readbuf;
-    mutable size_t m_readbufCol;
-    mutable size_t m_readbufWidth;
-    mutable bool m_readbufGood;
-
-    float getFromReadBufStandard(size_t x, size_t y) const {
-        float v;
-        if (m_readbuf &&
-            (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) {
-            v = ((float *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y];
-            return v;
-        } else {
-            populateReadBuf(x);
-            v = getFromReadBufStandard(x, y);
-            return v;
-        }
-    }
-
-    float getFromReadBufCompactUnsigned(size_t x, size_t y) const {
-        float v;
-        if (m_readbuf &&
-            (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) {
-            v = ((uint16_t *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y];
-            return v;
-        } else {
-            populateReadBuf(x);
-            v = getFromReadBufCompactUnsigned(x, y);
-            return v;
-        }
-    }
-
-    float getFromReadBufCompactSigned(size_t x, size_t y) const {
-        float v;
-        if (m_readbuf &&
-            (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) {
-            v = ((int16_t *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y];
-            return v;
-        } else {
-            populateReadBuf(x);
-            v = getFromReadBufCompactSigned(x, y);
-            return v;
-        }
-    }
-
-    void populateReadBuf(size_t x) const;
-
-    float getNormalizationFactor(size_t col) const {
-        size_t h = m_mfc->getHeight();
-        if (h < m_factorSize) return 0;
-        if (m_storageType != FFTCache::Compact) {
-            return getFromReadBufStandard(col, h - 1);
-        } else {
-            union {
-                float f;
-                uint16_t u[2];
-            } factor;
-            if (!m_readbuf ||
-                !(m_readbufCol == col ||
-                  (m_readbufWidth > 1 && m_readbufCol+1 == col))) {
-                populateReadBuf(col);
-            }
-            size_t ix = (col - m_readbufCol) * m_mfc->getHeight() + h;
-            factor.u[0] = ((uint16_t *)m_readbuf)[ix - 2];
-            factor.u[1] = ((uint16_t *)m_readbuf)[ix - 1];
-            return factor.f;
-        }
-    }
- 
-    FFTCache::StorageType m_storageType;
-    size_t m_factorSize;
-    MatrixFile *m_mfc;
-};
-
-#endif
--- a/data/fft/FFTFileCacheWriter.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,195 +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-2009 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.
-*/
-
-#include "FFTFileCacheWriter.h"
-
-#include "fileio/MatrixFile.h"
-
-#include "base/Profiler.h"
-#include "base/Thread.h"
-#include "base/Exceptions.h"
-
-#include <iostream>
-
-//#define DEBUG_FFT_FILE_CACHE_WRITER 1
-
-
-// The underlying matrix has height (m_height * 2 + 1).  In each
-// column we store magnitude at [0], [2] etc and phase at [1], [3]
-// etc, and then store the normalization factor (maximum magnitude) at
-// [m_height * 2].  In compact mode, the factor takes two cells.
-
-FFTFileCacheWriter::FFTFileCacheWriter(QString fileBase,
-                                       FFTCache::StorageType storageType,
-                                       size_t width, size_t height) :
-    m_writebuf(0),
-    m_fileBase(fileBase),
-    m_storageType(storageType),
-    m_factorSize(storageType == FFTCache::Compact ? 2 : 1),
-    m_mfc(new MatrixFile
-          (fileBase, MatrixFile::WriteOnly, 
-           storageType == FFTCache::Compact ? sizeof(uint16_t) : sizeof(float),
-           width, height * 2 + m_factorSize))
-{
-#ifdef DEBUG_FFT_FILE_CACHE_WRITER
-    cerr << "FFTFileCacheWriter: storage type is " << (storageType == FFTCache::Compact ? "Compact" : storageType == FFTCache::Polar ? "Polar" : "Rectangular") << ", size " << width << "x" << height << endl;
-#endif
-    m_mfc->setAutoClose(true);
-    m_writebuf = new char[(height * 2 + m_factorSize) * m_mfc->getCellSize()];
-}
-
-FFTFileCacheWriter::~FFTFileCacheWriter()
-{
-    if (m_writebuf) delete[] m_writebuf;
-    delete m_mfc;
-}
-
-QString
-FFTFileCacheWriter::getFileBase() const
-{
-    return m_fileBase;
-}
-
-size_t
-FFTFileCacheWriter::getWidth() const
-{
-    return m_mfc->getWidth();
-}
-
-size_t
-FFTFileCacheWriter::getHeight() const
-{
-    size_t mh = m_mfc->getHeight();
-    if (mh > m_factorSize) return (mh - m_factorSize) / 2;
-    else return 0;
-}
-
-bool
-FFTFileCacheWriter::haveSetColumnAt(size_t x) const
-{
-    return m_mfc->haveSetColumnAt(x);
-}
-
-void
-FFTFileCacheWriter::setColumnAt(size_t x, float *mags, float *phases, float factor)
-{
-    size_t h = getHeight();
-
-    switch (m_storageType) {
-
-    case FFTCache::Compact:
-        for (size_t y = 0; y < h; ++y) {
-            ((uint16_t *)m_writebuf)[y * 2] = uint16_t((mags[y] / factor) * 65535.0);
-            ((uint16_t *)m_writebuf)[y * 2 + 1] = uint16_t(int16_t((phases[y] * 32767) / M_PI));
-        }
-        break;
-
-    case FFTCache::Rectangular:
-        for (size_t y = 0; y < h; ++y) {
-            ((float *)m_writebuf)[y * 2] = mags[y] * cosf(phases[y]);
-            ((float *)m_writebuf)[y * 2 + 1] = mags[y] * sinf(phases[y]);
-        }
-        break;
-
-    case FFTCache::Polar:
-        for (size_t y = 0; y < h; ++y) {
-            ((float *)m_writebuf)[y * 2] = mags[y];
-            ((float *)m_writebuf)[y * 2 + 1] = phases[y];
-        }
-        break;
-    }
-
-    static float maxFactor = 0;
-    if (factor > maxFactor) maxFactor = factor;
-#ifdef DEBUG_FFT_FILE_CACHE_WRITER
-    cerr << "Column " << x << ": normalization factor: " << factor << ", max " << maxFactor << " (height " << getHeight() << ")" << endl;
-#endif
-
-    setNormalizationFactorToWritebuf(factor);
-
-    m_mfc->setColumnAt(x, m_writebuf);
-}
-
-void
-FFTFileCacheWriter::setColumnAt(size_t x, float *real, float *imag)
-{
-    size_t h = getHeight();
-
-    float factor = 0.0f;
-
-    switch (m_storageType) {
-
-    case FFTCache::Compact:
-        for (size_t y = 0; y < h; ++y) {
-            float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]);
-            if (mag > factor) factor = mag;
-        }
-        for (size_t y = 0; y < h; ++y) {
-            float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]);
-            float phase = atan2f(imag[y], real[y]);
-            ((uint16_t *)m_writebuf)[y * 2] = uint16_t((mag / factor) * 65535.0);
-            ((uint16_t *)m_writebuf)[y * 2 + 1] = uint16_t(int16_t((phase * 32767) / M_PI));
-        }
-        break;
-
-    case FFTCache::Rectangular:
-        for (size_t y = 0; y < h; ++y) {
-            ((float *)m_writebuf)[y * 2] = real[y];
-            ((float *)m_writebuf)[y * 2 + 1] = imag[y];
-            float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]);
-            if (mag > factor) factor = mag;
-        }
-        break;
-
-    case FFTCache::Polar:
-        for (size_t y = 0; y < h; ++y) {
-            float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]);
-            if (mag > factor) factor = mag;
-            ((float *)m_writebuf)[y * 2] = mag;
-            float phase = atan2f(imag[y], real[y]);
-            ((float *)m_writebuf)[y * 2 + 1] = phase;
-        }
-        break;
-    }
-
-    static float maxFactor = 0;
-    if (factor > maxFactor) maxFactor = factor;
-#ifdef DEBUG_FFT_FILE_CACHE_WRITER
-    cerr << "[RI] Column " << x << ": normalization factor: " << factor << ", max " << maxFactor << " (height " << getHeight() << ")" << endl;
-#endif
-
-    setNormalizationFactorToWritebuf(factor);
-
-    m_mfc->setColumnAt(x, m_writebuf);
-}
-
-size_t
-FFTFileCacheWriter::getCacheSize(size_t width, size_t height,
-                                 FFTCache::StorageType type)
-{
-    return (height * 2 + (type == FFTCache::Compact ? 2 : 1)) * width *
-        (type == FFTCache::Compact ? sizeof(uint16_t) : sizeof(float)) +
-        2 * sizeof(size_t); // matrix file header size
-}
-
-void
-FFTFileCacheWriter::allColumnsWritten()
-{
-#ifdef DEBUG_FFT_FILE_CACHE_WRITER
-    SVDEBUG << "FFTFileCacheWriter::allColumnsWritten" << endl;
-#endif
-    m_mfc->close();
-}
-
--- a/data/fft/FFTFileCacheWriter.h	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +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-2009 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 _FFT_FILE_CACHE_WRITER_H_
-#define _FFT_FILE_CACHE_WRITER_H_
-
-#include "FFTCacheStorageType.h"
-#include "FFTCacheWriter.h"
-#include "data/fileio/MatrixFile.h"
-
-class FFTFileCacheWriter : public FFTCacheWriter
-{
-public:
-    FFTFileCacheWriter(QString fileBase,
-                       FFTCache::StorageType storageType,
-                       size_t width, size_t height);
-    ~FFTFileCacheWriter();
-
-    size_t getWidth() const;
-    size_t getHeight() const;
-
-    void setColumnAt(size_t x, float *mags, float *phases, float factor);
-    void setColumnAt(size_t x, float *reals, float *imags);
-
-    static size_t getCacheSize(size_t width, size_t height,
-                               FFTCache::StorageType type);
-
-    bool haveSetColumnAt(size_t x) const;
-
-    void allColumnsWritten();
-
-    QString getFileBase() const;
-    FFTCache::StorageType getStorageType() const { return m_storageType; }
-
-protected:
-    char *m_writebuf;
-
-    void setNormalizationFactorToWritebuf(float newfactor) {
-        size_t h = m_mfc->getHeight();
-        if (h < m_factorSize) return;
-        if (m_storageType != FFTCache::Compact) {
-            ((float *)m_writebuf)[h - 1] = newfactor;
-        } else {
-            union {
-                float f;
-                uint16_t u[2];
-            } factor;
-            factor.f = newfactor;
-            ((uint16_t *)m_writebuf)[h - 2] = factor.u[0];
-            ((uint16_t *)m_writebuf)[h - 1] = factor.u[1];
-        }
-    }            
-
-    QString m_fileBase;
-    FFTCache::StorageType m_storageType;
-    size_t m_factorSize;
-    MatrixFile *m_mfc;
-};
-
-#endif
--- a/data/fft/FFTMemoryCache.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,216 +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.
-*/
-
-#include "FFTMemoryCache.h"
-#include "system/System.h"
-
-#include <iostream>
-#include <cstdlib>
-
-//#define DEBUG_FFT_MEMORY_CACHE 1
-
-FFTMemoryCache::FFTMemoryCache(FFTCache::StorageType storageType,
-                               size_t width, size_t height) :
-    m_width(width),
-    m_height(height),
-    m_magnitude(0),
-    m_phase(0),
-    m_fmagnitude(0),
-    m_fphase(0),
-    m_freal(0),
-    m_fimag(0),
-    m_factor(0),
-    m_storageType(storageType)
-{
-#ifdef DEBUG_FFT_MEMORY_CACHE
-    cerr << "FFTMemoryCache[" << this << "]::FFTMemoryCache (type "
-              << m_storageType << "), size " << m_width << "x" << m_height << endl;
-#endif
-
-    initialise();
-}
-
-FFTMemoryCache::~FFTMemoryCache()
-{
-#ifdef DEBUG_FFT_MEMORY_CACHE
-    cerr << "FFTMemoryCache[" << this << "]::~FFTMemoryCache" << endl;
-#endif
-
-    for (size_t i = 0; i < m_width; ++i) {
-	if (m_magnitude && m_magnitude[i]) free(m_magnitude[i]);
-	if (m_phase && m_phase[i]) free(m_phase[i]);
-	if (m_fmagnitude && m_fmagnitude[i]) free(m_fmagnitude[i]);
-	if (m_fphase && m_fphase[i]) free(m_fphase[i]);
-        if (m_freal && m_freal[i]) free(m_freal[i]);
-        if (m_fimag && m_fimag[i]) free(m_fimag[i]);
-    }
-
-    if (m_magnitude) free(m_magnitude);
-    if (m_phase) free(m_phase);
-    if (m_fmagnitude) free(m_fmagnitude);
-    if (m_fphase) free(m_fphase);
-    if (m_freal) free(m_freal);
-    if (m_fimag) free(m_fimag);
-    if (m_factor) free(m_factor);
-}
-
-void
-FFTMemoryCache::initialise()
-{
-    Profiler profiler("FFTMemoryCache::initialise");
-
-    size_t width = m_width, height = m_height;
-
-#ifdef DEBUG_FFT_MEMORY_CACHE
-    cerr << "FFTMemoryCache[" << this << "]::initialise(" << width << "x" << height << " = " << width*height << ")" << endl;
-#endif
-
-    if (m_storageType == FFTCache::Compact) {
-        initialise(m_magnitude);
-        initialise(m_phase);
-    } else if (m_storageType == FFTCache::Polar) {
-        initialise(m_fmagnitude);
-        initialise(m_fphase);
-    } else {
-        initialise(m_freal);
-        initialise(m_fimag);
-    }
-
-    m_colset.resize(width);
-
-    m_factor = (float *)realloc(m_factor, width * sizeof(float));
-
-    m_width = width;
-    m_height = height;
-
-#ifdef DEBUG_FFT_MEMORY_CACHE
-    cerr << "done, width = " << m_width << " height = " << m_height << endl;
-#endif
-}
-
-void
-FFTMemoryCache::initialise(uint16_t **&array)
-{
-    array = (uint16_t **)malloc(m_width * sizeof(uint16_t *));
-    if (!array) throw std::bad_alloc();
-    MUNLOCK(array, m_width * sizeof(uint16_t *));
-
-    for (size_t i = 0; i < m_width; ++i) {
-	array[i] = (uint16_t *)malloc(m_height * sizeof(uint16_t));
-	if (!array[i]) throw std::bad_alloc();
-	MUNLOCK(array[i], m_height * sizeof(uint16_t));
-    }
-}
-
-void
-FFTMemoryCache::initialise(float **&array)
-{
-    array = (float **)malloc(m_width * sizeof(float *));
-    if (!array) throw std::bad_alloc();
-    MUNLOCK(array, m_width * sizeof(float *));
-
-    for (size_t i = 0; i < m_width; ++i) {
-	array[i] = (float *)malloc(m_height * sizeof(float));
-	if (!array[i]) throw std::bad_alloc();
-	MUNLOCK(array[i], m_height * sizeof(float));
-    }
-}
-
-void
-FFTMemoryCache::setColumnAt(size_t x, float *mags, float *phases, float factor)
-{
-    Profiler profiler("FFTMemoryCache::setColumnAt: from polar");
-
-    setNormalizationFactor(x, factor);
-
-    if (m_storageType == FFTCache::Rectangular) {
-        Profiler subprof("FFTMemoryCache::setColumnAt: polar to cart");
-        for (size_t y = 0; y < m_height; ++y) {
-            m_freal[x][y] = mags[y] * cosf(phases[y]);
-            m_fimag[x][y] = mags[y] * sinf(phases[y]);
-        }
-    } else {
-        for (size_t y = 0; y < m_height; ++y) {
-            setMagnitudeAt(x, y, mags[y]);
-            setPhaseAt(x, y, phases[y]);
-        }
-    }
-
-    m_colsetLock.lockForWrite();
-    m_colset.set(x);
-    m_colsetLock.unlock();
-}
-
-void
-FFTMemoryCache::setColumnAt(size_t x, float *reals, float *imags)
-{
-    Profiler profiler("FFTMemoryCache::setColumnAt: from cart");
-
-    float max = 0.0;
-
-    switch (m_storageType) {
-
-    case FFTCache::Rectangular:
-        for (size_t y = 0; y < m_height; ++y) {
-            m_freal[x][y] = reals[y];
-            m_fimag[x][y] = imags[y];
-            float mag = sqrtf(reals[y] * reals[y] + imags[y] * imags[y]);
-            if (mag > max) max = mag;
-        }
-        break;
-
-    case FFTCache::Compact:
-    case FFTCache::Polar:
-    {
-        Profiler subprof("FFTMemoryCache::setColumnAt: cart to polar");
-        for (size_t y = 0; y < m_height; ++y) {
-            float mag = sqrtf(reals[y] * reals[y] + imags[y] * imags[y]);
-            float phase = atan2f(imags[y], reals[y]);
-            reals[y] = mag;
-            imags[y] = phase;
-            if (mag > max) max = mag;
-        }
-        break;
-    }
-    };
-
-    if (m_storageType == FFTCache::Rectangular) {
-        m_factor[x] = max;
-        m_colsetLock.lockForWrite();
-        m_colset.set(x);
-        m_colsetLock.unlock();
-    } else {
-        setColumnAt(x, reals, imags, max);
-    }
-}
-
-size_t
-FFTMemoryCache::getCacheSize(size_t width, size_t height, FFTCache::StorageType type)
-{
-    size_t sz = 0;
-
-    switch (type) {
-
-    case FFTCache::Compact:
-        sz = (height * 2 + 1) * width * sizeof(uint16_t);
-
-    case FFTCache::Polar:
-    case FFTCache::Rectangular:
-        sz = (height * 2 + 1) * width * sizeof(float);
-    }
-
-    return sz;
-}
-
--- a/data/fft/FFTMemoryCache.h	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,186 +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 _FFT_MEMORY_CACHE_H_
-#define _FFT_MEMORY_CACHE_H_
-
-#include "FFTCacheReader.h"
-#include "FFTCacheWriter.h"
-#include "FFTCacheStorageType.h"
-#include "base/ResizeableBitset.h"
-#include "base/Profiler.h"
-
-#include <QReadWriteLock>
-
-/**
- * In-memory FFT cache.  For this we want to cache magnitude with
- * enough resolution to have gain applied afterwards and determine
- * whether something is a peak or not, and also cache phase rather
- * than only phase-adjusted frequency so that we don't have to
- * recalculate if switching between phase and magnitude displays.  At
- * the same time, we don't want to take up too much memory.  It's not
- * expected to be accurate enough to be used as input for DSP or
- * resynthesis code.
- *
- * This implies probably 16 bits for a normalized magnitude and at
- * most 16 bits for phase.
- *
- * Each column's magnitudes are expected to be stored normalized
- * to [0,1] with respect to the column, so the normalization
- * factor should be calculated before all values in a column, and
- * set appropriately.
- */
-
-class FFTMemoryCache : public FFTCacheReader, public FFTCacheWriter
-{
-public:
-    FFTMemoryCache(FFTCache::StorageType storageType,
-                   size_t width, size_t height);
-    ~FFTMemoryCache();
-	
-    size_t getWidth() const { return m_width; }
-    size_t getHeight() const { return m_height; }
-	
-    float getMagnitudeAt(size_t x, size_t y) const {
-        if (m_storageType == FFTCache::Rectangular) {
-            Profiler profiler("FFTMemoryCache::getMagnitudeAt: cart to polar");
-            return sqrtf(m_freal[x][y] * m_freal[x][y] +
-                         m_fimag[x][y] * m_fimag[x][y]);
-        } else {
-            return getNormalizedMagnitudeAt(x, y) * m_factor[x];
-        }
-    }
-    
-    float getNormalizedMagnitudeAt(size_t x, size_t y) const {
-        if (m_storageType == FFTCache::Rectangular) return getMagnitudeAt(x, y) / m_factor[x];
-        else if (m_storageType == FFTCache::Polar) return m_fmagnitude[x][y];
-        else return float(m_magnitude[x][y]) / 65535.0;
-    }
-    
-    float getMaximumMagnitudeAt(size_t x) const {
-        return m_factor[x];
-    }
-    
-    float getPhaseAt(size_t x, size_t y) const {
-        if (m_storageType == FFTCache::Rectangular) {
-            Profiler profiler("FFTMemoryCache::getValuesAt: cart to polar");
-            return atan2f(m_fimag[x][y], m_freal[x][y]);
-        } else if (m_storageType == FFTCache::Polar) {
-            return m_fphase[x][y];
-        } else {
-            int16_t i = (int16_t)m_phase[x][y];
-            return (float(i) / 32767.0) * M_PI;
-        }
-    }
-    
-    void getValuesAt(size_t x, size_t y, float &real, float &imag) const {
-        if (m_storageType == FFTCache::Rectangular) {
-            real = m_freal[x][y];
-            imag = m_fimag[x][y];
-        } else {
-            Profiler profiler("FFTMemoryCache::getValuesAt: polar to cart");
-            float mag = getMagnitudeAt(x, y);
-            float phase = getPhaseAt(x, y);
-            real = mag * cosf(phase);
-            imag = mag * sinf(phase);
-        }
-    }
-
-    void getMagnitudesAt(size_t x, float *values, size_t minbin, size_t count, size_t step) const
-    {
-        if (m_storageType == FFTCache::Rectangular) {
-            for (size_t i = 0; i < count; ++i) {
-                size_t y = i * step + minbin;
-                values[i] = sqrtf(m_freal[x][y] * m_freal[x][y] +
-                                  m_fimag[x][y] * m_fimag[x][y]);
-            }
-        } else if (m_storageType == FFTCache::Polar) {
-            for (size_t i = 0; i < count; ++i) {
-                size_t y = i * step + minbin;
-                values[i] = m_fmagnitude[x][y] * m_factor[x];
-            }
-        } else {
-            for (size_t i = 0; i < count; ++i) {
-                size_t y = i * step + minbin;
-                values[i] = (float(m_magnitude[x][y]) * m_factor[x]) / 65535.0;
-            }
-        }
-    }
-
-    bool haveSetColumnAt(size_t x) const {
-        m_colsetLock.lockForRead();
-        bool have = m_colset.get(x);
-        m_colsetLock.unlock();
-        return have;
-    }
-
-    void setColumnAt(size_t x, float *mags, float *phases, float factor);
-
-    void setColumnAt(size_t x, float *reals, float *imags);
-
-    void allColumnsWritten() { } 
-
-    static size_t getCacheSize(size_t width, size_t height,
-                               FFTCache::StorageType type);
-
-    FFTCache::StorageType getStorageType() const { return m_storageType; }
-
-private:
-    size_t m_width;
-    size_t m_height;
-    uint16_t **m_magnitude;
-    uint16_t **m_phase;
-    float **m_fmagnitude;
-    float **m_fphase;
-    float **m_freal;
-    float **m_fimag;
-    float *m_factor;
-    FFTCache::StorageType m_storageType;
-    ResizeableBitset m_colset;
-    mutable QReadWriteLock m_colsetLock;
-
-    void initialise();
-
-    void setNormalizationFactor(size_t x, float factor) {
-        if (x < m_width) m_factor[x] = factor;
-    }
-    
-    void setMagnitudeAt(size_t x, size_t y, float mag) {
-         // norm factor must already be set
-        setNormalizedMagnitudeAt(x, y, mag / m_factor[x]);
-    }
-    
-    void setNormalizedMagnitudeAt(size_t x, size_t y, float norm) {
-        if (x < m_width && y < m_height) {
-            if (m_storageType == FFTCache::Polar) m_fmagnitude[x][y] = norm;
-            else m_magnitude[x][y] = uint16_t(norm * 65535.0);
-        }
-    }
-    
-    void setPhaseAt(size_t x, size_t y, float phase) {
-        // phase in range -pi -> pi
-        if (x < m_width && y < m_height) {
-            if (m_storageType == FFTCache::Polar) m_fphase[x][y] = phase;
-            else m_phase[x][y] = uint16_t(int16_t((phase * 32767) / M_PI));
-        }
-    }
-
-    void initialise(uint16_t **&);
-    void initialise(float **&);
-};
-
-
-#endif
-
--- a/data/fft/FFTapi.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fft/FFTapi.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -16,6 +16,8 @@
 
 #include "FFTapi.h"
 
+std::mutex FFTForward::m_mutex;
+
 #ifndef HAVE_FFTW3F
 
 #include <cmath>
--- a/data/fft/FFTapi.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fft/FFTapi.h	Wed Apr 20 12:06:28 2016 +0100
@@ -50,5 +50,54 @@
 
 #endif
 
+#include <vector>
+#include <complex>
+#include <mutex>
+
+class FFTForward // with fft shift but not window
+{
+    static std::mutex m_mutex;
+    
+public:
+    FFTForward(int size) :
+        m_size(size)
+    {
+        std::lock_guard<std::mutex> lock(m_mutex);
+        m_input = (float *)fftf_malloc(size * sizeof(float));
+        m_output = (fftf_complex *)fftf_malloc((size/2 + 1) * sizeof(fftf_complex));
+        m_plan = fftf_plan_dft_r2c_1d(size, m_input, m_output, FFTW_ESTIMATE);
+    }
+
+    ~FFTForward() {
+        std::lock_guard<std::mutex> lock(m_mutex);
+        fftf_destroy_plan(m_plan);
+        fftf_free(m_input);
+        fftf_free(m_output);
+    }
+
+    std::vector<std::complex<float> > process(const std::vector<float> &in) const {
+        const int hs = m_size/2;
+        for (int i = 0; i < hs; ++i) {
+            m_input[i] = in[i + hs];
+        }
+        for (int i = 0; i < hs; ++i) {
+            m_input[i + hs] = in[i];
+        }
+        fftf_execute(m_plan);
+        std::vector<std::complex<float> > result;
+        result.reserve(hs + 1);
+        for (int i = 0; i <= hs; ++i) {
+            result.push_back({ m_output[i][0], m_output[i][1] });
+        }
+        return result;
+    }
+
+private:
+    int m_size;
+    float *m_input;
+    fftf_complex *m_output;
+    fftf_plan m_plan;
+};
+
 #endif
 
--- a/data/fileio/AudioFileReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/AudioFileReader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -15,26 +15,26 @@
 
 #include "AudioFileReader.h"
 
-void
-AudioFileReader::getDeInterleavedFrames(size_t start, size_t count,
-                                        std::vector<SampleBlock> &frames) const
+using std::vector;
+
+vector<vector<float>>
+AudioFileReader::getDeInterleavedFrames(sv_frame_t start, sv_frame_t count) const
 {
-    SampleBlock interleaved;
-    getInterleavedFrames(start, count, interleaved);
+    vector<float> interleaved = getInterleavedFrames(start, count);
     
-    size_t channels = getChannelCount();
-    size_t rc = interleaved.size() / channels;
+    int channels = getChannelCount();
+    if (channels == 1) return { interleaved };
+    
+    sv_frame_t rc = interleaved.size() / channels;
 
-    frames.clear();
-
-    for (size_t c = 0; c < channels; ++c) {
-        frames.push_back(SampleBlock());
+    vector<vector<float>> frames(channels, vector<float>(rc, 0.f));
+    
+    for (int c = 0; c < channels; ++c) {
+        for (sv_frame_t i = 0; i < rc; ++i) {
+            frames[c][i] = interleaved[i * channels + c];
+        }
     }
 
-    for (size_t i = 0; i < rc; ++i) {
-        for (size_t c = 0; c < channels; ++c) {
-            frames[c].push_back(interleaved[i * channels + c]);
-        }
-    }
+    return frames;
 }
 
--- a/data/fileio/AudioFileReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/AudioFileReader.h	Wed Apr 20 12:06:28 2016 +0100
@@ -18,13 +18,12 @@
 
 #include <QString>
 
+#include "base/BaseTypes.h"
 #include "FileSource.h"
 
 #include <vector>
 #include <map>
 
-typedef std::vector<float> SampleBlock;
-
 class AudioFileReader : public QObject
 {
     Q_OBJECT
@@ -36,11 +35,11 @@
 
     virtual QString getError() const { return ""; }
 
-    size_t getFrameCount() const { return m_frameCount; }
-    size_t getChannelCount() const { return m_channelCount; }
-    size_t getSampleRate() const { return m_sampleRate; }
+    sv_frame_t getFrameCount() const { return m_frameCount; }
+    int getChannelCount() const { return m_channelCount; }
+    sv_samplerate_t getSampleRate() const { return m_sampleRate; }
 
-    virtual size_t getNativeRate() const { return m_sampleRate; } // if resampled
+    virtual sv_samplerate_t getNativeRate() const { return m_sampleRate; } // if resampled
 
     /**
      * Return the location of the audio data in the reader (as passed
@@ -62,6 +61,16 @@
      */
     virtual QString getMaker() const { return ""; }
 
+    /**
+     * Return the local file path of the audio data. This is the
+     * location most likely to contain readable audio data: it may be
+     * in a different place or format from the originally specified
+     * location, for example if the file has been retrieved and
+     * decoded. In some cases there may be no local file path, and
+     * this will return "" if there is none.
+     */
+    virtual QString getLocalFilename() const { return ""; }
+    
     typedef std::map<QString, QString> TagMap;
     virtual TagMap getTags() const { return TagMap(); }
 
@@ -74,15 +83,14 @@
 
     /** 
      * Return interleaved samples for count frames from index start.
-     * The resulting sample block will contain count *
-     * getChannelCount() samples (or fewer if end of file is reached).
+     * The resulting vector will contain count * getChannelCount()
+     * samples (or fewer if end of file is reached).
      *
      * The subclass implementations of this function must be
      * thread-safe -- that is, safe to call from multiple threads with
      * different arguments on the same object at the same time.
      */
-    virtual void getInterleavedFrames(size_t start, size_t count,
-				      SampleBlock &frames) const = 0;
+    virtual std::vector<float> getInterleavedFrames(sv_frame_t start, sv_frame_t count) const = 0;
 
     /**
      * Return de-interleaved samples for count frames from index
@@ -91,8 +99,7 @@
      * will contain getChannelCount() sample blocks of count samples
      * each (or fewer if end of file is reached).
      */
-    virtual void getDeInterleavedFrames(size_t start, size_t count,
-                                        std::vector<SampleBlock> &frames) const;
+    virtual std::vector<std::vector<float> > getDeInterleavedFrames(sv_frame_t start, sv_frame_t count) const;
 
     // only subclasses that do not know exactly how long the audio
     // file is until it's been completely decoded should implement this
@@ -104,9 +111,9 @@
     void frameCountChanged();
     
 protected:
-    size_t m_frameCount;
-    size_t m_channelCount;
-    size_t m_sampleRate;
+    sv_frame_t m_frameCount;
+    int m_channelCount;
+    sv_samplerate_t m_sampleRate;
 };
 
 #endif
--- a/data/fileio/AudioFileReaderFactory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/AudioFileReaderFactory.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -21,11 +21,16 @@
 #include "MP3FileReader.h"
 #include "QuickTimeFileReader.h"
 #include "CoreAudioFileReader.h"
+#include "AudioFileSizeEstimator.h"
+
+#include "base/StorageAdviser.h"
 
 #include <QString>
 #include <QFileInfo>
 #include <iostream>
 
+//#define DEBUG_AUDIO_FILE_READER_FACTORY 1
+
 QString
 AudioFileReaderFactory::getKnownExtensions()
 {
@@ -58,26 +63,35 @@
 }
 
 AudioFileReader *
-AudioFileReaderFactory::createReader(FileSource source, size_t targetRate,
+AudioFileReaderFactory::createReader(FileSource source, 
+                                     sv_samplerate_t targetRate,
+                                     bool normalised,
                                      ProgressReporter *reporter)
 {
-    return create(source, targetRate, false, reporter);
+    return create(source, targetRate, normalised, false, reporter);
 }
 
 AudioFileReader *
-AudioFileReaderFactory::createThreadingReader(FileSource source, size_t targetRate,
+AudioFileReaderFactory::createThreadingReader(FileSource source, 
+                                              sv_samplerate_t targetRate,
+                                              bool normalised,
                                               ProgressReporter *reporter)
 {
-    return create(source, targetRate, true, reporter);
+    return create(source, targetRate, normalised, true, reporter);
 }
 
 AudioFileReader *
-AudioFileReaderFactory::create(FileSource source, size_t targetRate, bool threading,
+AudioFileReaderFactory::create(FileSource source, 
+                               sv_samplerate_t targetRate, 
+                               bool normalised,
+                               bool threading,
                                ProgressReporter *reporter)
 {
     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;
@@ -85,173 +99,142 @@
     }
 
     if (!source.isAvailable()) {
-        SVDEBUG << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Source not found" << endl;
+        cerr << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Source not found" << endl;
         return 0;
     }
 
     AudioFileReader *reader = 0;
 
+    sv_frame_t estimatedSamples = 
+        AudioFileSizeEstimator::estimate(source, targetRate);
+    
+    CodedAudioFileReader::CacheMode cacheMode =
+        CodedAudioFileReader::CacheInTemporaryFile;
+
+    if (estimatedSamples > 0) {
+        size_t kb = (estimatedSamples * sizeof(float)) / 1024;
+        StorageAdviser::Recommendation rec =
+            StorageAdviser::recommend(StorageAdviser::SpeedCritical, kb, kb);
+        if (rec == StorageAdviser::UseMemory ||
+            rec == StorageAdviser::PreferMemory) {
+            cacheMode = CodedAudioFileReader::CacheInMemory;
+        }
+    }
+    
+    CodedAudioFileReader::DecodeMode decodeMode =
+        (threading ?
+         CodedAudioFileReader::DecodeThreaded :
+         CodedAudioFileReader::DecodeAtOnce);
+    
     // Try to construct a preferred reader based on the extension or
     // MIME type.
 
+#define CHECK(reader) if (!reader->isOK()) { delete reader; reader = 0; }
+
     if (WavFileReader::supports(source)) {
 
         reader = new WavFileReader(source);
 
-        int fileRate = reader->getSampleRate();
+        sv_samplerate_t fileRate = reader->getSampleRate();
 
         if (reader->isOK() &&
             (!reader->isQuicklySeekable() ||
+             normalised ||
+             (cacheMode == CodedAudioFileReader::CacheInMemory) ||
              (targetRate != 0 && fileRate != targetRate))) {
 
-            SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", seekable " << reader->isQuicklySeekable() << ", 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,
-                 threading ?
-                 DecodingWavFileReader::ResampleThreaded :
-                 DecodingWavFileReader::ResampleAtOnce,
-                 DecodingWavFileReader::CacheInTemporaryFile,
+                 decodeMode, cacheMode,
                  targetRate ? targetRate : fileRate,
+                 normalised,
                  reporter);
-            if (!reader->isOK()) {
-                delete reader;
-                reader = 0;
-            }
+            CHECK(reader);
         }
     }
     
 #ifdef HAVE_OGGZ
 #ifdef HAVE_FISHSOUND
-    if (!reader) {
-        if (OggVorbisFileReader::supports(source)) {
-            reader = new OggVorbisFileReader
-                (source,
-                 threading ?
-                 OggVorbisFileReader::DecodeThreaded :
-                 OggVorbisFileReader::DecodeAtOnce,
-                 OggVorbisFileReader::CacheInTemporaryFile,
-                 targetRate,
-                 reporter);
-            if (!reader->isOK()) {
-                delete reader;
-                reader = 0;
-            }
-        }
+    if (!reader && OggVorbisFileReader::supports(source)) {
+        reader = new OggVorbisFileReader
+            (source, decodeMode, cacheMode, targetRate, normalised, reporter);
+        CHECK(reader);
     }
 #endif
 #endif
 
 #ifdef HAVE_MAD
-    if (!reader) {
-        if (MP3FileReader::supports(source)) {
-            reader = new MP3FileReader
-                (source,
-                 threading ?
-                 MP3FileReader::DecodeThreaded :
-                 MP3FileReader::DecodeAtOnce,
-                 MP3FileReader::CacheInTemporaryFile,
-                 targetRate,
-                 reporter);
-            if (!reader->isOK()) {
-                delete reader;
-                reader = 0;
-            }
-        }
+    if (!reader && MP3FileReader::supports(source)) {
+        reader = new MP3FileReader
+            (source, decodeMode, cacheMode, targetRate, normalised, reporter);
+        CHECK(reader);
     }
 #endif
 
 #ifdef HAVE_QUICKTIME
-    if (!reader) {
-        if (QuickTimeFileReader::supports(source)) {
-            reader = new QuickTimeFileReader
-                (source,
-                 threading ?
-                 QuickTimeFileReader::DecodeThreaded : 
-                 QuickTimeFileReader::DecodeAtOnce,
-                 QuickTimeFileReader::CacheInTemporaryFile,
-                 targetRate,
-                 reporter);
-            if (!reader->isOK()) {
-                delete reader;
-                reader = 0;
-            }
-        }
+    if (!reader && QuickTimeFileReader::supports(source)) {
+        reader = new QuickTimeFileReader
+            (source, decodeMode, cacheMode, targetRate, normalised, reporter);
+        CHECK(reader);
     }
 #endif
 
 #ifdef HAVE_COREAUDIO
-    if (!reader) {
-        if (CoreAudioFileReader::supports(source)) {
-            reader = new CoreAudioFileReader
-                (source,
-                 threading ?
-                 CoreAudioFileReader::DecodeThreaded :
-                 CoreAudioFileReader::DecodeAtOnce,
-                 CoreAudioFileReader::CacheInTemporaryFile,
-                 targetRate,
-                 reporter);
-            if (!reader->isOK()) {
-                delete reader;
-                reader = 0;
-            }
-        }
+    if (!reader && CoreAudioFileReader::supports(source)) {
+        reader = new CoreAudioFileReader
+            (source, decodeMode, cacheMode, targetRate, normalised, reporter);
+        CHECK(reader);
     }
 #endif
 
-
+    if (reader) {
+        // The happy case: a reader recognised the file extension &
+        // succeeded in opening the file
+        return reader;
+    }
+    
     // If none of the readers claimed to support this file extension,
     // perhaps the extension is missing or misleading.  Try again,
     // ignoring it.  We have to be confident that the reader won't
     // open just any old text file or whatever and pretend it's
     // succeeded
 
-    if (!reader) {
+    reader = new WavFileReader(source);
 
-        reader = new WavFileReader(source);
+    sv_samplerate_t fileRate = reader->getSampleRate();
 
-        int fileRate = reader->getSampleRate();
+    if (reader->isOK() &&
+        (!reader->isQuicklySeekable() ||
+         normalised ||
+         (cacheMode == CodedAudioFileReader::CacheInMemory) ||
+         (targetRate != 0 && fileRate != targetRate))) {
 
-        if (reader->isOK() &&
-            (!reader->isQuicklySeekable() ||
-             (targetRate != 0 && fileRate != targetRate))) {
+#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
 
-            SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl;
+        delete reader;
+        reader = new DecodingWavFileReader
+            (source,
+             decodeMode, cacheMode,
+             targetRate ? targetRate : fileRate,
+             normalised,
+             reporter);
+    }
 
-            delete reader;
-            reader = new DecodingWavFileReader
-                (source,
-                 threading ?
-                 DecodingWavFileReader::ResampleThreaded :
-                 DecodingWavFileReader::ResampleAtOnce,
-                 DecodingWavFileReader::CacheInTemporaryFile,
-                 targetRate ? targetRate : fileRate,
-                 reporter);
-        }
-
-        if (!reader->isOK()) {
-            delete reader;
-            reader = 0;
-        }
-    }
+    CHECK(reader);
     
 #ifdef HAVE_OGGZ
 #ifdef HAVE_FISHSOUND
     if (!reader) {
         reader = new OggVorbisFileReader
-            (source,
-             threading ?
-             OggVorbisFileReader::DecodeThreaded :
-             OggVorbisFileReader::DecodeAtOnce,
-             OggVorbisFileReader::CacheInTemporaryFile,
-             targetRate,
-             reporter);
-
-        if (!reader->isOK()) {
-            delete reader;
-            reader = 0;
-        }
+            (source, decodeMode, cacheMode, targetRate, normalised, reporter);
+        CHECK(reader);
     }
 #endif
 #endif
@@ -259,76 +242,35 @@
 #ifdef HAVE_MAD
     if (!reader) {
         reader = new MP3FileReader
-            (source,
-             threading ?
-             MP3FileReader::DecodeThreaded :
-             MP3FileReader::DecodeAtOnce,
-             MP3FileReader::CacheInTemporaryFile,
-             targetRate,
-             reporter);
-
-        if (!reader->isOK()) {
-            delete reader;
-            reader = 0;
-        }
+            (source, decodeMode, cacheMode, targetRate, normalised, reporter);
+        CHECK(reader);
     }
 #endif
 
 #ifdef HAVE_QUICKTIME
     if (!reader) {
         reader = new QuickTimeFileReader
-            (source,
-             threading ?
-             QuickTimeFileReader::DecodeThreaded : 
-             QuickTimeFileReader::DecodeAtOnce,
-             QuickTimeFileReader::CacheInTemporaryFile,
-             targetRate,
-             reporter);
-
-        if (!reader->isOK()) {
-            delete reader;
-            reader = 0;
-        }
+            (source, decodeMode, cacheMode, targetRate, normalised, reporter);
+        CHECK(reader);
     }
 #endif
 
 #ifdef HAVE_COREAUDIO
     if (!reader) {
         reader = new CoreAudioFileReader
-            (source,
-             threading ?
-             CoreAudioFileReader::DecodeThreaded :
-             CoreAudioFileReader::DecodeAtOnce,
-             CoreAudioFileReader::CacheInTemporaryFile,
-             targetRate,
-             reporter);
-
-        if (!reader->isOK()) {
-            delete reader;
-            reader = 0;
-        }
+            (source, decodeMode, cacheMode, targetRate, normalised, reporter);
+        CHECK(reader);
     }
 #endif
 
-    if (reader) {
-        if (reader->isOK()) {
-            SVDEBUG << "AudioFileReaderFactory: Reader is OK" << endl;
-            return reader;
-        }
-        cerr << "AudioFileReaderFactory: Preferred reader for "
-                  << "url \"" << source.getLocation()
-                  << "\" (content type \""
-                  << source.getContentType() << "\") failed";
-
-        if (reader->getError() != "") {
-            cerr << ": \"" << reader->getError() << "\"";
-        }
-        cerr << endl;
-        delete reader;
-        reader = 0;
+    if (!reader) {
+        cerr << "AudioFileReaderFactory::Failed to create a reader for "
+             << "url \"" << source.getLocation()
+             << "\" (content type \""
+             << source.getContentType() << "\")" << endl;
+        return nullptr;
     }
-
-    cerr << "AudioFileReaderFactory: No reader" << endl;
+    
     return reader;
 }
 
--- a/data/fileio/AudioFileReaderFactory.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/AudioFileReaderFactory.h	Wed Apr 20 12:06:28 2016 +0100
@@ -13,12 +13,13 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _AUDIO_FILE_READER_FACTORY_H_
-#define _AUDIO_FILE_READER_FACTORY_H_
+#ifndef AUDIO_FILE_READER_FACTORY_H
+#define AUDIO_FILE_READER_FACTORY_H
 
 #include <QString>
 
 #include "FileSource.h"
+#include "base/BaseTypes.h"
 
 class AudioFileReader;
 class ProgressReporter;
@@ -43,6 +44,9 @@
      * if you want to find out whether the file is being resampled
      * or not.
      *
+     * If normalised is true, the file data will be normalised to
+     * abs(max) == 1.0. Otherwise the file will not be normalised.
+     *
      * If a ProgressReporter is provided, it will be updated with
      * progress status.  Caller retains ownership of the reporter
      * object.
@@ -50,7 +54,8 @@
      * Caller owns the returned object and must delete it after use.
      */
     static AudioFileReader *createReader(FileSource source,
-                                         size_t targetRate = 0,
+                                         sv_samplerate_t targetRate = 0,
+                                         bool normalised = false,
                                          ProgressReporter *reporter = 0);
 
     /**
@@ -65,6 +70,9 @@
      * if you want to find out whether the file is being resampled
      * or not.
      *
+     * If normalised is true, the file data will be normalised to
+     * abs(max) == 1.0. Otherwise the file will not be normalised.
+     *
      * If a ProgressReporter is provided, it will be updated with
      * progress status.  This will only be meaningful if threading
      * mode is not used because the file reader in use does not
@@ -75,12 +83,14 @@
      * Caller owns the returned object and must delete it after use.
      */
     static AudioFileReader *createThreadingReader(FileSource source,
-                                                  size_t targetRate = 0,
+                                                  sv_samplerate_t targetRate = 0,
+                                                  bool normalised = false,
                                                   ProgressReporter *reporter = 0);
 
 protected:
     static AudioFileReader *create(FileSource source,
-                                   size_t targetRate,
+                                   sv_samplerate_t targetRate,
+                                   bool normalised,
                                    bool threading,
                                    ProgressReporter *reporter);
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/AudioFileSizeEstimator.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,108 @@
+/* -*- 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 "AudioFileSizeEstimator.h"
+
+#include "WavFileReader.h"
+
+#include <QFile>
+
+//#define DEBUG_AUDIO_FILE_SIZE_ESTIMATOR 1
+
+sv_frame_t
+AudioFileSizeEstimator::estimate(FileSource source,
+				 sv_samplerate_t targetRate)
+{
+    sv_frame_t estimate = 0;
+    
+    // Most of our file readers don't know the sample count until
+    // after they've finished decoding. This is an exception:
+
+    WavFileReader *reader = new WavFileReader(source);
+    if (reader->isOK() &&
+	reader->getChannelCount() > 0 &&
+	reader->getFrameCount() > 0) {
+	sv_frame_t samples =
+	    reader->getFrameCount() * reader->getChannelCount();
+	sv_samplerate_t rate = reader->getSampleRate();
+	if (targetRate != 0.0 && targetRate != rate) {
+	    samples = sv_frame_t(double(samples) * targetRate / rate);
+	}
+	delete reader;
+	estimate = samples;
+    }
+
+    if (estimate == 0) {
+
+	// The remainder just makes an estimate based on the file size
+	// and extension. We don't even know its sample rate at this
+	// point, so the following is a wild guess.
+	
+	double rateRatio = 1.0;
+	if (targetRate != 0.0) {
+	    rateRatio = targetRate / 44100.0;
+	}
+    
+	QString extension = source.getExtension();
+
+	source.waitForData();
+	if (!source.isOK()) return 0;
+
+	sv_frame_t sz = 0;
+	{
+	    QFile f(source.getLocalFilename());
+	    if (f.open(QFile::ReadOnly)) {
+#ifdef DEBUG_AUDIO_FILE_SIZE_ESTIMATOR
+		cerr << "opened file, size is "  << f.size() << endl;
+#endif
+		sz = f.size();
+		f.close();
+	    }
+	}
+
+	if (extension == "ogg" || extension == "oga" ||
+	    extension == "m4a" || extension == "mp3" ||
+	    extension == "wma") {
+
+	    // Usually a lossy file. Compression ratios can vary
+	    // dramatically, but don't usually exceed about 20x compared
+	    // to 16-bit PCM (e.g. a 128kbps mp3 has 11x ratio over WAV at
+	    // 44.1kHz). We can estimate the number of samples to be file
+	    // size x 20, divided by 2 as we're comparing with 16-bit PCM.
+
+	    estimate = sv_frame_t(double(sz) * 10 * rateRatio);
+	}
+
+	if (extension == "flac") {
+	
+	    // FLAC usually takes up a bit more than half the space of
+	    // 16-bit PCM. So the number of 16-bit samples is roughly the
+	    // same as the file size in bytes. As above, let's be
+	    // conservative.
+
+	    estimate = sv_frame_t(double(sz) * 1.2 * rateRatio);
+	}
+
+#ifdef DEBUG_AUDIO_FILE_SIZE_ESTIMATOR
+	cerr << "AudioFileSizeEstimator: for extension " << extension << ", estimate = " << estimate << endl;
+#endif
+    }
+
+#ifdef DEBUG_AUDIO_FILE_SIZE_ESTIMATOR
+    cerr << "estimate = " << estimate << endl;
+#endif
+    
+    return estimate;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/AudioFileSizeEstimator.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,49 @@
+/* -*- 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 AUDIO_FILE_SIZE_ESTIMATOR_H
+#define AUDIO_FILE_SIZE_ESTIMATOR_H
+
+#include "base/BaseTypes.h"
+#include "data/fileio/FileSource.h"
+
+/**
+ * Estimate the number of samples in an audio file. For many
+ * compressed files this returns only a very approximate estimate,
+ * based on a rough estimate of compression ratio. Initially we're
+ * only aiming for a conservative estimate for purposes like "will
+ * this file fit in memory?" (and if unsure, say no).
+ */
+class AudioFileSizeEstimator
+{
+public:
+    /**
+     * Return an estimate of the number of samples (across all
+     * channels) in the given audio file, once it has been decoded and
+     * (if applicable) resampled to the given rate.
+     *
+     * This function is intended to be reasonably fast -- it may open
+     * the file, but it should not do any decoding. (However, if the
+     * file source is remote, it will probably be downloaded in its
+     * entirety before anything can be estimated.)
+     *
+     * The returned value is an estimate, and is deliberately usually
+     * on the high side. If the estimator has no idea at all, this
+     * will return 0.
+     */
+    static sv_frame_t estimate(FileSource source,
+			       sv_samplerate_t targetRate = 0);
+};
+
+#endif
--- a/data/fileio/BZipFileDevice.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/BZipFileDevice.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -178,7 +178,7 @@
     if (m_atEnd) return 0;
 
     int bzError = BZ_OK;
-    int read = BZ2_bzRead(&bzError, m_bzFile, data, maxSize);
+    int read = BZ2_bzRead(&bzError, m_bzFile, data, int(maxSize));
 
 //    SVDEBUG << "BZipFileDevice::readData: requested " << maxSize << ", read " << read << endl;
 
@@ -201,7 +201,7 @@
 BZipFileDevice::writeData(const char *data, qint64 maxSize)
 {
     int bzError = BZ_OK;
-    BZ2_bzWrite(&bzError, m_bzFile, (void *)data, maxSize);
+    BZ2_bzWrite(&bzError, m_bzFile, (void *)data, int(maxSize));
 
 //    SVDEBUG << "BZipFileDevice::writeData: " << maxSize << " to write" << endl;
 
--- a/data/fileio/CSVFileReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/CSVFileReader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -22,9 +22,11 @@
 #include "model/SparseTimeValueModel.h"
 #include "model/EditableDenseThreeDimensionalModel.h"
 #include "model/RegionModel.h"
+#include "model/NoteModel.h"
 #include "DataFileReaderFactory.h"
 
 #include <QFile>
+#include <QFileInfo>
 #include <QString>
 #include <QRegExp>
 #include <QStringList>
@@ -33,45 +35,60 @@
 #include <iostream>
 #include <map>
 
+using namespace std;
+
 CSVFileReader::CSVFileReader(QString path, CSVFormat format,
-                             size_t mainModelSampleRate) :
+                             sv_samplerate_t mainModelSampleRate) :
     m_format(format),
-    m_file(0),
+    m_device(0),
+    m_ownDevice(true),
     m_warnings(0),
     m_mainModelSampleRate(mainModelSampleRate)
 {
-    m_file = new QFile(path);
+    QFile *file = new QFile(path);
     bool good = false;
     
-    if (!m_file->exists()) {
+    if (!file->exists()) {
 	m_error = QFile::tr("File \"%1\" does not exist").arg(path);
-    } else if (!m_file->open(QIODevice::ReadOnly | QIODevice::Text)) {
+    } else if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) {
 	m_error = QFile::tr("Failed to open file \"%1\"").arg(path);
     } else {
 	good = true;
     }
 
-    if (!good) {
-	delete m_file;
-	m_file = 0;
+    if (good) {
+        m_device = file;
+        m_filename = QFileInfo(path).fileName();
+    } else {
+	delete file;
     }
 }
 
+CSVFileReader::CSVFileReader(QIODevice *device, CSVFormat format,
+                             sv_samplerate_t mainModelSampleRate) :
+    m_format(format),
+    m_device(device),
+    m_ownDevice(false),
+    m_warnings(0),
+    m_mainModelSampleRate(mainModelSampleRate)
+{
+}
+
 CSVFileReader::~CSVFileReader()
 {
-    SVDEBUG << "CSVFileReader::~CSVFileReader: file is " << m_file << endl;
+    SVDEBUG << "CSVFileReader::~CSVFileReader: device is " << m_device << endl;
 
-    if (m_file) {
-        SVDEBUG << "CSVFileReader::CSVFileReader: Closing file" << endl;
-        m_file->close();
+    if (m_device && m_ownDevice) {
+        SVDEBUG << "CSVFileReader::CSVFileReader: Closing device" << endl;
+        m_device->close();
+        delete m_device;
     }
-    delete m_file;
 }
 
 bool
 CSVFileReader::isOK() const
 {
-    return (m_file != 0);
+    return (m_device != 0);
 }
 
 QString
@@ -80,16 +97,17 @@
     return m_error;
 }
 
-size_t
-CSVFileReader::convertTimeValue(QString s, int lineno, size_t sampleRate,
-                                size_t windowSize) const
+sv_frame_t
+CSVFileReader::convertTimeValue(QString s, int lineno,
+                                sv_samplerate_t sampleRate,
+                                int windowSize) const
 {
     QRegExp nonNumericRx("[^0-9eE.,+-]");
-    unsigned int warnLimit = 10;
+    int warnLimit = 10;
 
     CSVFormat::TimeUnits timeUnits = m_format.getTimeUnits();
 
-    size_t calculatedFrame = 0;
+    sv_frame_t calculatedFrame = 0;
 
     bool ok = false;
     QString numeric = s;
@@ -99,7 +117,13 @@
 
         double time = numeric.toDouble(&ok);
         if (!ok) time = StringBits::stringToDoubleLocaleFree(numeric, &ok);
-        calculatedFrame = int(time * sampleRate + 0.5);
+        calculatedFrame = sv_frame_t(time * sampleRate + 0.5);
+    
+    } else if (timeUnits == CSVFormat::TimeMilliseconds) {
+
+        double time = numeric.toDouble(&ok);
+        if (!ok) time = StringBits::stringToDoubleLocaleFree(numeric, &ok);
+        calculatedFrame = sv_frame_t((time / 1000.0) * sampleRate + 0.5);
         
     } else {
         
@@ -129,13 +153,13 @@
 Model *
 CSVFileReader::load() const
 {
-    if (!m_file) return 0;
+    if (!m_device) return 0;
 
     CSVFormat::ModelType modelType = m_format.getModelType();
     CSVFormat::TimingType timingType = m_format.getTimingType();
     CSVFormat::TimeUnits timeUnits = m_format.getTimeUnits();
-    size_t sampleRate = m_format.getSampleRate();
-    size_t windowSize = m_format.getWindowSize();
+    sv_samplerate_t sampleRate = m_format.getSampleRate();
+    int windowSize = m_format.getWindowSize();
     QChar separator = m_format.getSeparator();
     bool allowQuoting = m_format.getAllowQuoting();
 
@@ -148,7 +172,8 @@
         } else {
             windowSize = 1;
         }
-	if (timeUnits == CSVFormat::TimeSeconds) {
+	if (timeUnits == CSVFormat::TimeSeconds ||
+            timeUnits == CSVFormat::TimeMilliseconds) {
 	    sampleRate = m_mainModelSampleRate;
 	}
     }
@@ -156,28 +181,29 @@
     SparseOneDimensionalModel *model1 = 0;
     SparseTimeValueModel *model2 = 0;
     RegionModel *model2a = 0;
+    NoteModel *model2b = 0;
     EditableDenseThreeDimensionalModel *model3 = 0;
     Model *model = 0;
 
-    QTextStream in(m_file);
-    in.seek(0);
+    QTextStream in(m_device);
 
     unsigned int warnings = 0, warnLimit = 10;
     unsigned int lineno = 0;
 
     float min = 0.0, max = 0.0;
 
-    size_t frameNo = 0;
-    size_t duration = 0;
-    size_t endFrame = 0;
+    sv_frame_t frameNo = 0;
+    sv_frame_t duration = 0;
+    sv_frame_t endFrame = 0;
 
     bool haveAnyValue = false;
     bool haveEndTime = false;
+    bool pitchLooksLikeMIDI = true;
 
-    size_t startFrame = 0; // for calculation of dense model resolution
+    sv_frame_t startFrame = 0; // for calculation of dense model resolution
     bool firstEverValue = true;
 
-    std::map<QString, int> labelCountMap;
+    map<QString, int> labelCountMap;
     
     int valueColumns = 0;
     for (int i = 0; i < m_format.getColumnCount(); ++i) {
@@ -202,10 +228,10 @@
         QString chunk = in.readLine();
         QStringList lines = chunk.split('\r', QString::SkipEmptyParts);
         
-        for (size_t li = 0; li < lines.size(); ++li) {
+        for (int li = 0; li < lines.size(); ++li) {
 
             QString line = lines[li];
-
+            
             if (line.startsWith("#")) continue;
 
             QStringList list = StringBits::split(line, separator, allowQuoting);
@@ -228,6 +254,11 @@
                     model = model2a;
                     break;
 		
+                case CSVFormat::TwoDimensionalModelWithDurationAndPitch:
+                    model2b = new NoteModel(sampleRate, windowSize, false);
+                    model = model2b;
+                    break;
+		
                 case CSVFormat::ThreeDimensionalModel:
                     model3 = new EditableDenseThreeDimensionalModel
                         (sampleRate,
@@ -237,9 +268,16 @@
                     model = model3;
                     break;
                 }
+
+                if (model) {
+                    if (m_filename != "") {
+                        model->setObjectName(m_filename);
+                    }
+                }
             }
 
             float value = 0.f;
+            float pitch = 0.f;
             QString label = "";
 
             duration = 0.f;
@@ -274,13 +312,21 @@
                     haveAnyValue = true;
                     break;
 
+                case CSVFormat::ColumnPitch:
+                    pitch = s.toFloat();
+                    if (pitch < 0.f || pitch > 127.f) {
+                        pitchLooksLikeMIDI = false;
+                    }
+                    break;
+
                 case CSVFormat::ColumnLabel:
                     label = s;
-                    ++labelCountMap[label];
                     break;
                 }
             }
 
+            ++labelCountMap[label];
+            
             if (haveEndTime) { // ... calculate duration now all cols read
                 if (endFrame > frameNo) {
                     duration = endFrame - frameNo;
@@ -302,6 +348,12 @@
                 RegionModel::Point point(frameNo, value, duration, label);
                 model2a->addPoint(point);
 
+            } else if (modelType == CSVFormat::TwoDimensionalModelWithDurationAndPitch) {
+
+                float level = ((value >= 0.f && value <= 1.f) ? value : 1.f);
+                NoteModel::Point point(frameNo, pitch, duration, level, label);
+                model2b->addPoint(point);
+
             } else if (modelType == CSVFormat::ThreeDimensionalModel) {
 
                 DenseThreeDimensionalModel::Column values;
@@ -325,7 +377,7 @@
                         model3->setStartFrame(startFrame);
                     } else if (lineno == 1 &&
                                timingType == CSVFormat::ExplicitTiming) {
-                        model3->setResolution(frameNo - startFrame);
+                        model3->setResolution(int(frameNo - startFrame));
                     }
                     
                     firstEverValue = false;
@@ -364,43 +416,64 @@
             // assign values for regions based on label frequency; we
             // have this in our labelCountMap, sort of
 
-            std::map<int, std::map<QString, float> > countLabelValueMap;
-            for (std::map<QString, int>::iterator i = labelCountMap.begin();
+            map<int, map<QString, float> > countLabelValueMap;
+            for (map<QString, int>::iterator i = labelCountMap.begin();
                  i != labelCountMap.end(); ++i) {
-                countLabelValueMap[i->second][i->first] = 0.f;
+                countLabelValueMap[i->second][i->first] = -1.f;
             }
 
             float v = 0.f;
-            for (std::map<int, std::map<QString, float> >::iterator i =
+            for (map<int, map<QString, float> >::iterator i =
                      countLabelValueMap.end(); i != countLabelValueMap.begin(); ) {
                 --i;
-                for (std::map<QString, float>::iterator j = i->second.begin();
+                cerr << "count -> " << i->first << endl;
+                for (map<QString, float>::iterator j = i->second.begin();
                      j != i->second.end(); ++j) {
                     j->second = v;
+                    cerr << "label -> " << j->first << ", value " << v << endl;
                     v = v + 1.f;
                 }
             }
 
-            std::map<RegionModel::Point, RegionModel::Point,
+            map<RegionModel::Point, RegionModel::Point,
                 RegionModel::Point::Comparator> pointMap;
             for (RegionModel::PointList::const_iterator i =
                      model2a->getPoints().begin();
                  i != model2a->getPoints().end(); ++i) {
                 RegionModel::Point p(*i);
-                v = countLabelValueMap[labelCountMap[p.label]][p.label];
+                int count = labelCountMap[p.label];
+                v = countLabelValueMap[count][p.label];
+                cerr << "mapping from label \"" << p.label << "\" (count " << count << ") to value " << v << endl;
                 RegionModel::Point pp(p.frame, v, p.duration, p.label);
                 pointMap[p] = pp;
             }
 
-            for (std::map<RegionModel::Point, RegionModel::Point>::iterator i = 
+            for (map<RegionModel::Point, RegionModel::Point>::iterator i = 
                      pointMap.begin(); i != pointMap.end(); ++i) {
-                model2a->deletePoint(i->first);
-                model2a->addPoint(i->second);
+                // There could be duplicate regions; if so replace
+                // them all -- but we need to check we're not
+                // replacing a region by itself (or else this will
+                // never terminate)
+                if (i->first.value == i->second.value) {
+                    continue;
+                }
+                while (model2a->containsPoint(i->first)) {
+                    model2a->deletePoint(i->first);
+                    model2a->addPoint(i->second);
+                }
             }
         }
     }
                 
-    if (modelType == CSVFormat::ThreeDimensionalModel) {
+    if (model2b) {
+        if (pitchLooksLikeMIDI) {
+            model2b->setScaleUnits("MIDI Pitch");
+        } else {
+            model2b->setScaleUnits("Hz");
+        }
+    }
+
+    if (model3) {
 	model3->setMinimumLevel(min);
 	model3->setMaximumLevel(max);
     }
--- a/data/fileio/CSVFileReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/CSVFileReader.h	Wed Apr 20 12:06:28 2016 +0100
@@ -20,30 +20,49 @@
 
 #include "CSVFormat.h"
 
+#include "base/BaseTypes.h"
+
 #include <QList>
 #include <QStringList>
+#include <QIODevice>
 
 class QFile;
 
 class CSVFileReader : public DataFileReader
 {
 public:
-    CSVFileReader(QString path, CSVFormat format, size_t mainModelSampleRate);
+    /**
+     * Construct a CSVFileReader to read the CSV file at the given
+     * path, with the given format.
+     */
+    CSVFileReader(QString path, CSVFormat format, sv_samplerate_t mainModelSampleRate);
+
+    /**
+     * Construct a CSVFileReader to read from the given
+     * QIODevice. Caller retains ownership of the QIODevice: the
+     * CSVFileReader will not close or delete it and it must outlive
+     * the CSVFileReader.
+     */
+    CSVFileReader(QIODevice *device, CSVFormat format, sv_samplerate_t mainModelSampleRate);
+
     virtual ~CSVFileReader();
 
     virtual bool isOK() const;
     virtual QString getError() const;
+
     virtual Model *load() const;
 
 protected:
     CSVFormat m_format;
-    QFile *m_file;
+    QIODevice *m_device;
+    bool m_ownDevice;
+    QString m_filename;
     QString m_error;
     mutable int m_warnings;
-    size_t m_mainModelSampleRate;
+    sv_samplerate_t m_mainModelSampleRate;
 
-    size_t convertTimeValue(QString, int lineno, size_t sampleRate,
-                            size_t windowSize) const;
+    sv_frame_t convertTimeValue(QString, int lineno, sv_samplerate_t sampleRate,
+                                int windowSize) const;
 };
 
 
--- a/data/fileio/CSVFileWriter.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/CSVFileWriter.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -28,11 +28,15 @@
 #include <QFile>
 #include <QTextStream>
 
-CSVFileWriter::CSVFileWriter(QString path, Model *model, QString delimiter) :
+CSVFileWriter::CSVFileWriter(QString path,
+                             Model *model,
+                             QString delimiter,
+                             DataExportOptions options) :
     m_path(path),
     m_model(model),
     m_error(""),
-    m_delimiter(delimiter)
+    m_delimiter(delimiter),
+    m_options(options)
 {
 }
 
@@ -66,7 +70,8 @@
         }
     
         QTextStream out(&file);
-        out << m_model->toDelimitedDataString(m_delimiter);
+        out << m_model->toDelimitedDataStringWithOptions
+            (m_delimiter, m_options);
 
         file.close();
         temp.moveToTarget();
@@ -95,8 +100,9 @@
                  selection->getSelections().begin();
              i != selection->getSelections().end(); ++i) {
 	
-            size_t f0(i->getStartFrame()), f1(i->getEndFrame());
-            out << m_model->toDelimitedDataString(m_delimiter, f0, f1);
+            sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame());
+            out << m_model->toDelimitedDataStringSubsetWithOptions
+                (m_delimiter, m_options, f0, f1);
         }
 
         file.close();
--- a/data/fileio/CSVFileWriter.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/CSVFileWriter.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,6 +19,8 @@
 #include <QObject>
 #include <QString>
 
+#include "base/DataExportOptions.h"
+
 class Model;
 class MultiSelection;
 
@@ -27,7 +29,10 @@
     Q_OBJECT
 
 public:
-    CSVFileWriter(QString path, Model *model, QString delimiter = ",");
+    CSVFileWriter(QString path,
+                  Model *model,
+                  QString delimiter = ",",
+                  DataExportOptions options = DataExportDefaults);
     virtual ~CSVFileWriter();
 
     virtual bool isOK() const;
@@ -41,6 +46,7 @@
     Model *m_model;
     QString m_error;
     QString m_delimiter;
+    DataExportOptions m_options;
 };
 
 #endif
--- a/data/fileio/CSVFormat.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/CSVFormat.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -66,7 +66,7 @@
         QString chunk = in.readLine();
         QStringList lines = chunk.split('\r', QString::SkipEmptyParts);
 
-        for (size_t li = 0; li < lines.size(); ++li) {
+        for (int li = 0; li < lines.size(); ++li) {
 
             QString line = lines[li];
             if (line.startsWith("#") || line == "") continue;
@@ -86,7 +86,7 @@
 CSVFormat::guessSeparator(QString line)
 {
     char candidates[] = { ',', '\t', ' ', '|', '/', ':' };
-    for (int i = 0; i < sizeof(candidates)/sizeof(candidates[0]); ++i) {
+    for (int i = 0; i < int(sizeof(candidates)/sizeof(candidates[0])); ++i) {
         if (StringBits::split(line, candidates[i], m_allowQuoting).size() >= 2) {
             m_separator = candidates[i];
             return;
@@ -103,14 +103,14 @@
     QStringList list = StringBits::split(line, m_separator[0], m_allowQuoting);
 
     int cols = list.size();
-    if (lineno == 0 || (cols < m_columnCount)) m_columnCount = cols;
+    if (lineno == 0 || (cols > m_columnCount)) m_columnCount = cols;
     if (cols != m_columnCount) m_variableColumnCount = true;
 
     // All columns are regarded as having these qualities until we see
     // something that indicates otherwise:
 
     ColumnQualities defaultQualities =
-        ColumnNumeric | ColumnIntegral | ColumnIncreasing;
+        ColumnNumeric | ColumnIntegral | ColumnIncreasing | ColumnNearEmpty;
     
     for (int i = 0; i < cols; ++i) {
 	    
@@ -128,7 +128,12 @@
         bool integral   = (qualities & ColumnIntegral);
         bool increasing = (qualities & ColumnIncreasing);
         bool large      = (qualities & ColumnLarge); // this one defaults to off
+        bool emptyish   = (qualities & ColumnNearEmpty);
 
+        if (lineno > 1 && s.trimmed() != "") {
+            emptyish = false;
+        }
+        
         float value = 0.f;
 
         //!!! how to take into account headers?
@@ -166,7 +171,8 @@
             (numeric    ? ColumnNumeric : 0) |
             (integral   ? ColumnIntegral : 0) |
             (increasing ? ColumnIncreasing : 0) |
-            (large      ? ColumnLarge : 0);
+            (large      ? ColumnLarge : 0) |
+            (emptyish   ? ColumnNearEmpty : 0);
     }
 
     if (lineno < 10) {
@@ -190,11 +196,31 @@
     m_timeUnits = CSVFormat::TimeWindows;
 	
     int timingColumnCount = 0;
+
+    // if our first column has zero or one entries in it and the rest
+    // have more, then we'll default to ignoring the first column and
+    // counting the next one as primary. (e.g. Sonic Annotator output
+    // with filename at start of first column.)
+
+    int primaryColumnNo = 0;
+
+    if (m_columnCount >= 2) {
+        if ( (m_columnQualities[0] & ColumnNearEmpty) &&
+            !(m_columnQualities[1] & ColumnNearEmpty)) {
+            primaryColumnNo = 1;
+        }
+    }
     
     for (int i = 0; i < m_columnCount; ++i) {
         
         ColumnPurpose purpose = ColumnUnknown;
-        bool primary = (i == 0);
+
+        if (i < primaryColumnNo) {
+            setColumnPurpose(i, purpose);
+            continue;
+        }
+        
+        bool primary = (i == primaryColumnNo);
 
         ColumnQualities qualities = m_columnQualities[i];
 
--- a/data/fileio/CSVFormat.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/CSVFormat.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,6 +19,8 @@
 #include <QString>
 #include <QStringList>
 
+#include "base/BaseTypes.h"
+
 class CSVFormat
 {
 public:
@@ -26,6 +28,7 @@
 	OneDimensionalModel,
 	TwoDimensionalModel,
         TwoDimensionalModelWithDuration,
+        TwoDimensionalModelWithDurationAndPitch,
 	ThreeDimensionalModel
     };
     
@@ -36,8 +39,9 @@
 
     enum TimeUnits {
 	TimeSeconds,
+        TimeMilliseconds,
 	TimeAudioFrames,
-	TimeWindows
+	TimeWindows,
     };
 
     enum ColumnPurpose {
@@ -46,14 +50,16 @@
         ColumnEndTime,
         ColumnDuration,
         ColumnValue,
+        ColumnPitch,
         ColumnLabel
     };
 
     enum ColumnQuality {
-        ColumnNumeric    = 0x1,
-        ColumnIntegral   = 0x2,
-        ColumnIncreasing = 0x4,
-        ColumnLarge      = 0x8
+        ColumnNumeric    = 1,
+        ColumnIntegral   = 2,
+        ColumnIncreasing = 4,
+        ColumnLarge      = 8,
+        ColumnNearEmpty  = 16,
     };
     typedef unsigned int ColumnQualities;
 
@@ -84,8 +90,8 @@
     ModelType    getModelType()     const { return m_modelType;     }
     TimingType   getTimingType()    const { return m_timingType;    }
     TimeUnits    getTimeUnits()     const { return m_timeUnits;     }
-    size_t       getSampleRate()    const { return m_sampleRate;    }
-    size_t       getWindowSize()    const { return m_windowSize;    }
+    sv_samplerate_t getSampleRate() const { return m_sampleRate;    }
+    int          getWindowSize()    const { return m_windowSize;    }
     int          getColumnCount()   const { return m_columnCount;   }
     bool         getAllowQuoting()  const { return m_allowQuoting;  }
     QChar        getSeparator()     const { 
@@ -97,8 +103,8 @@
     void setTimingType(TimingType t)      { m_timingType   = t; }
     void setTimeUnits(TimeUnits t)        { m_timeUnits    = t; }
     void setSeparator(QChar s)            { m_separator    = s; }
-    void setSampleRate(size_t r)          { m_sampleRate   = r; }
-    void setWindowSize(size_t s)          { m_windowSize   = s; }
+    void setSampleRate(sv_samplerate_t r) { m_sampleRate   = r; }
+    void setWindowSize(int s)             { m_windowSize   = s; }
     void setColumnCount(int c)            { m_columnCount  = c; }
     void setAllowQuoting(bool q)          { m_allowQuoting = q; }
 
@@ -121,8 +127,8 @@
     TimingType   m_timingType;
     TimeUnits    m_timeUnits;
     QString      m_separator;
-    size_t       m_sampleRate;
-    size_t       m_windowSize;
+    sv_samplerate_t m_sampleRate;
+    int          m_windowSize;
 
     int          m_columnCount;
     bool         m_variableColumnCount;
--- a/data/fileio/CodedAudioFileReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/CodedAudioFileReader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -21,14 +21,18 @@
 #include "base/Profiler.h"
 #include "base/Serialiser.h"
 #include "base/Resampler.h"
+#include "base/StorageAdviser.h"
 
 #include <stdint.h>
 #include <iostream>
 #include <QDir>
 #include <QMutexLocker>
 
+using namespace std;
+
 CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode,
-                                           size_t targetRate) :
+                                           sv_samplerate_t targetRate,
+                                           bool normalised) :
     m_cacheMode(cacheMode),
     m_initialised(false),
     m_serialiser(0),
@@ -40,9 +44,12 @@
     m_cacheWriteBufferSize(16384),
     m_resampler(0),
     m_resampleBuffer(0),
-    m_fileFrameCount(0)
+    m_fileFrameCount(0),
+    m_normalised(normalised),
+    m_max(0.f),
+    m_gain(1.f)
 {
-    SVDEBUG << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << endl;
+    SVDEBUG << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << ", normalised = " << normalised << endl;
 
     m_frameCount = 0;
     m_sampleRate = targetRate;
@@ -53,7 +60,7 @@
     QMutexLocker locker(&m_cacheMutex);
 
     endSerialised();
-
+    
     if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr);
 
     SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file reader" << endl;
@@ -69,6 +76,12 @@
 
     delete m_resampler;
     delete[] m_resampleBuffer;
+
+    if (!m_data.empty()) {
+        StorageAdviser::notifyDoneAllocation
+            (StorageAdviser::MemoryAllocation,
+             (m_data.size() * sizeof(float)) / 1024);
+    }
 }
 
 void
@@ -109,9 +122,9 @@
         m_resampler = new Resampler(Resampler::FastestTolerable,
                                     m_channelCount,
                                     m_cacheWriteBufferSize);
-        float ratio = float(m_sampleRate) / float(m_fileRate);
+        double ratio = m_sampleRate / m_fileRate;
         m_resampleBuffer = new float
-            [lrintf(ceilf(m_cacheWriteBufferSize * m_channelCount * ratio + 1))];
+            [lrint(ceil(double(m_cacheWriteBufferSize) * m_channelCount * ratio + 1))];
     }
 
     m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount];
@@ -125,13 +138,35 @@
                                            .arg((intptr_t)this));
 
             SF_INFO fileInfo;
-            fileInfo.samplerate = m_sampleRate;
+            int fileRate = int(round(m_sampleRate));
+            if (m_sampleRate != sv_samplerate_t(fileRate)) {
+                cerr << "CodedAudioFileReader: WARNING: Non-integer sample rate "
+                     << m_sampleRate << " presented for writing, rounding to " << fileRate
+                     << endl;
+            }
+            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);
@@ -172,15 +207,15 @@
 }
 
 void
-CodedAudioFileReader::addSamplesToDecodeCache(float **samples, size_t nframes)
+CodedAudioFileReader::addSamplesToDecodeCache(float **samples, sv_frame_t nframes)
 {
     QMutexLocker locker(&m_cacheMutex);
 
     if (!m_initialised) return;
 
-    for (size_t i = 0; i < nframes; ++i) {
+    for (sv_frame_t i = 0; i < nframes; ++i) {
         
-        for (size_t c = 0; c < m_channelCount; ++c) {
+        for (int c = 0; c < m_channelCount; ++c) {
 
             float sample = samples[c][i];
         
@@ -202,15 +237,15 @@
 }
 
 void
-CodedAudioFileReader::addSamplesToDecodeCache(float *samples, size_t nframes)
+CodedAudioFileReader::addSamplesToDecodeCache(float *samples, sv_frame_t nframes)
 {
     QMutexLocker locker(&m_cacheMutex);
 
     if (!m_initialised) return;
 
-    for (size_t i = 0; i < nframes; ++i) {
+    for (sv_frame_t i = 0; i < nframes; ++i) {
         
-        for (size_t c = 0; c < m_channelCount; ++c) {
+        for (int c = 0; c < m_channelCount; ++c) {
 
             float sample = samples[i * m_channelCount + c];
         
@@ -232,15 +267,13 @@
 }
 
 void
-CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples)
+CodedAudioFileReader::addSamplesToDecodeCache(const vector<float> &samples)
 {
     QMutexLocker locker(&m_cacheMutex);
 
     if (!m_initialised) return;
 
-    for (size_t i = 0; i < samples.size(); ++i) {
-
-        float sample = samples[i];
+    for (float sample: samples) {
         
         m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
 
@@ -270,11 +303,9 @@
         return;
     }
 
-//    if (m_cacheWriteBufferIndex > 0) {
-        pushBuffer(m_cacheWriteBuffer,
-                   m_cacheWriteBufferIndex / m_channelCount,
-                   true);
-//    }        
+    pushBuffer(m_cacheWriteBuffer,
+               m_cacheWriteBufferIndex / m_channelCount,
+               true);
 
     delete[] m_cacheWriteBuffer;
     m_cacheWriteBuffer = 0;
@@ -286,23 +317,30 @@
     m_resampler = 0;
 
     if (m_cacheMode == CacheInTemporaryFile) {
+
         sf_close(m_cacheFileWritePtr);
         m_cacheFileWritePtr = 0;
         if (m_cacheFileReader) m_cacheFileReader->updateFrameCount();
+
+    } else {
+        // I know, I know, we already allocated it...
+        StorageAdviser::notifyPlannedAllocation
+            (StorageAdviser::MemoryAllocation,
+             (m_data.size() * sizeof(float)) / 1024);
     }
 }
 
 void
-CodedAudioFileReader::pushBuffer(float *buffer, size_t sz, bool final)
+CodedAudioFileReader::pushBuffer(float *buffer, sv_frame_t sz, bool final)
 {
     m_fileFrameCount += sz;
 
-    float ratio = 1.f;
+    double ratio = 1.0;
     if (m_resampler && m_fileRate != 0) {
-        ratio = float(m_sampleRate) / float(m_fileRate);
+        ratio = m_sampleRate / m_fileRate;
     }
         
-    if (ratio != 1.f) {
+    if (ratio != 1.0) {
         pushBufferResampling(buffer, sz, ratio, final);
     } else {
         pushBufferNonResampling(buffer, sz);
@@ -310,16 +348,26 @@
 }
 
 void
-CodedAudioFileReader::pushBufferNonResampling(float *buffer, size_t sz)
+CodedAudioFileReader::pushBufferNonResampling(float *buffer, sv_frame_t sz)
 {
-    float max = 1.0;
-    size_t count = sz * m_channelCount;
+    float clip = 1.0;
+    sv_frame_t count = sz * m_channelCount;
 
-    for (size_t i = 0; i < count; ++i) {
-        if (buffer[i] >  max) buffer[i] =  max;
-    }
-    for (size_t i = 0; i < count; ++i) {
-        if (buffer[i] < -max) buffer[i] = -max;
+    if (m_normalised) {
+        for (sv_frame_t i = 0; i < count; ++i) {
+            float v = fabsf(buffer[i]);
+            if (v > m_max) {
+                m_max = v;
+                m_gain = 1.f / m_max;
+            }
+        }
+    } else {
+        for (sv_frame_t i = 0; i < count; ++i) {
+            if (buffer[i] >  clip) buffer[i] =  clip;
+        }
+        for (sv_frame_t i = 0; i < count; ++i) {
+            if (buffer[i] < -clip) buffer[i] = -clip;
+        }
     }
 
     m_frameCount += sz;
@@ -335,25 +383,22 @@
         break;
 
     case CacheInMemory:
-        m_dataLock.lockForWrite();
-        for (size_t s = 0; s < count; ++s) {
-            m_data.push_back(buffer[s]);
-        }
-	MUNLOCK_SAMPLEBLOCK(m_data);
+        m_dataLock.lock();
+        m_data.insert(m_data.end(), buffer, buffer + count);
         m_dataLock.unlock();
         break;
     }
 }
 
 void
-CodedAudioFileReader::pushBufferResampling(float *buffer, size_t sz,
-                                           float ratio, bool final)
+CodedAudioFileReader::pushBufferResampling(float *buffer, sv_frame_t sz,
+                                           double ratio, bool final)
 {
     SVDEBUG << "pushBufferResampling: ratio = " << ratio << ", sz = " << sz << ", final = " << final << endl;
 
     if (sz > 0) {
 
-        size_t out = m_resampler->resampleInterleaved
+        sv_frame_t out = m_resampler->resampleInterleaved
             (buffer,
              m_resampleBuffer,
              sz,
@@ -365,27 +410,27 @@
 
     if (final) {
 
-        size_t padFrames = 1;
-        if (m_frameCount / ratio < m_fileFrameCount) {
-            padFrames = m_fileFrameCount - (m_frameCount / ratio) + 1;
+        sv_frame_t padFrames = 1;
+        if (double(m_frameCount) / ratio < double(m_fileFrameCount)) {
+            padFrames = m_fileFrameCount - sv_frame_t(double(m_frameCount) / ratio) + 1;
         }
 
-        size_t padSamples = padFrames * m_channelCount;
+        sv_frame_t padSamples = padFrames * m_channelCount;
 
-        SVDEBUG << "frameCount = " << m_frameCount << ", equivFileFrames = " << m_frameCount / ratio << ", m_fileFrameCount = " << m_fileFrameCount << ", padFrames= " << padFrames << ", padSamples = " << padSamples << endl;
+        SVDEBUG << "frameCount = " << m_frameCount << ", equivFileFrames = " << double(m_frameCount) / ratio << ", m_fileFrameCount = " << m_fileFrameCount << ", padFrames= " << padFrames << ", padSamples = " << padSamples << endl;
 
         float *padding = new float[padSamples];
-        for (int i = 0; i < padSamples; ++i) padding[i] = 0.f;
+        for (sv_frame_t i = 0; i < padSamples; ++i) padding[i] = 0.f;
 
-        size_t out = m_resampler->resampleInterleaved
+        sv_frame_t out = m_resampler->resampleInterleaved
             (padding,
              m_resampleBuffer,
              padFrames,
              ratio,
              true);
 
-        if (m_frameCount + out > int(m_fileFrameCount * ratio)) {
-            out = int(m_fileFrameCount * ratio) - m_frameCount;
+        if (m_frameCount + out > sv_frame_t(double(m_fileFrameCount) * ratio)) {
+            out = sv_frame_t(double(m_fileFrameCount) * ratio) - m_frameCount;
         }
 
         pushBufferNonResampling(m_resampleBuffer, out);
@@ -393,9 +438,8 @@
     }
 }
 
-void
-CodedAudioFileReader::getInterleavedFrames(size_t start, size_t count,
-                                           SampleBlock &frames) const
+vector<float>
+CodedAudioFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
 {
     // Lock is only required in CacheInMemory mode (the cache file
     // reader is expected to be thread safe and manage its own
@@ -403,34 +447,44 @@
 
     if (!m_initialised) {
         SVDEBUG << "CodedAudioFileReader::getInterleavedFrames: not initialised" << endl;
-        return;
+        return {};
     }
 
+    vector<float> frames;
+    
     switch (m_cacheMode) {
 
     case CacheInTemporaryFile:
         if (m_cacheFileReader) {
-            m_cacheFileReader->getInterleavedFrames(start, count, frames);
+            frames = m_cacheFileReader->getInterleavedFrames(start, count);
         }
         break;
 
     case CacheInMemory:
     {
-        frames.clear();
-        if (!isOK()) return;
-        if (count == 0) return;
-        frames.reserve(count * m_channelCount);
+        if (!isOK()) return {};
+        if (count == 0) return {};
 
-        size_t idx = start * m_channelCount;
-        size_t i = 0;
+        sv_frame_t ix0 = start * m_channelCount;
+        sv_frame_t ix1 = ix0 + (count * m_channelCount);
 
-        m_dataLock.lockForRead();
-        while (i < count * m_channelCount && idx < m_data.size()) {
-            frames.push_back(m_data[idx]);
-            ++idx;
-        }
+
+        // This lock used to be a QReadWriteLock, but it appears that
+        // its lock mechanism is significantly slower than QMutex so
+        // it's not a good idea in cases like this where we don't
+        // really have threads taking a long time to read concurrently
+        m_dataLock.lock();
+        sv_frame_t n = sv_frame_t(m_data.size());
+        if (ix1 > n) ix1 = n;
+        frames = vector<float>(m_data.begin() + ix0, m_data.begin() + ix1);
         m_dataLock.unlock();
     }
     }
+
+    if (m_normalised) {
+        for (auto &f: frames) f *= m_gain;
+    }
+
+    return frames;
 }
 
--- a/data/fileio/CodedAudioFileReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/CodedAudioFileReader.h	Wed Apr 20 12:06:28 2016 +0100
@@ -38,11 +38,17 @@
         CacheInMemory
     };
 
-    virtual void getInterleavedFrames(size_t start, size_t count,
-				      SampleBlock &frames) const;
+    enum DecodeMode {
+        DecodeAtOnce, // decode the file on construction, with progress 
+        DecodeThreaded // decode in a background thread after construction
+    };
 
-    virtual size_t getNativeRate() const { return m_fileRate; }
+    virtual std::vector<float> getInterleavedFrames(sv_frame_t start, sv_frame_t count) const;
 
+    virtual sv_samplerate_t getNativeRate() const { return m_fileRate; }
+
+    virtual QString getLocalFilename() const { return m_cacheFileName; }
+    
     /// Intermediate cache means all CodedAudioFileReaders are quickly seekable
     virtual bool isQuicklySeekable() const { return true; }
 
@@ -50,14 +56,16 @@
     void progress(int);
 
 protected:
-    CodedAudioFileReader(CacheMode cacheMode, size_t targetRate);
+    CodedAudioFileReader(CacheMode cacheMode, 
+                         sv_samplerate_t targetRate,
+                         bool normalised);
 
     void initialiseDecodeCache(); // samplerate, channels must have been set
 
     // may throw InsufficientDiscSpace:
-    void addSamplesToDecodeCache(float **samples, size_t nframes);
-    void addSamplesToDecodeCache(float *samplesInterleaved, size_t nframes);
-    void addSamplesToDecodeCache(const SampleBlock &interleaved);
+    void addSamplesToDecodeCache(float **samples, sv_frame_t nframes);
+    void addSamplesToDecodeCache(float *samplesInterleaved, sv_frame_t nframes);
+    void addSamplesToDecodeCache(const std::vector<float> &interleaved);
 
     // may throw InsufficientDiscSpace:
     void finishDecodeCache();
@@ -68,29 +76,33 @@
     void endSerialised();
 
 private:
-    void pushBuffer(float *interleaved, size_t sz, bool final);
-    void pushBufferResampling(float *interleaved, size_t sz, float ratio, bool final);
-    void pushBufferNonResampling(float *interleaved, size_t sz);
+    void pushBuffer(float *interleaved, sv_frame_t sz, bool final);
+    void pushBufferResampling(float *interleaved, sv_frame_t sz, double ratio, bool final);
+    void pushBufferNonResampling(float *interleaved, sv_frame_t sz);
 
 protected:
     QMutex m_cacheMutex;
     CacheMode m_cacheMode;
-    SampleBlock m_data;
-    mutable QReadWriteLock m_dataLock;
+    std::vector<float> m_data;
+    mutable QMutex m_dataLock;
     bool m_initialised;
     Serialiser *m_serialiser;
-    size_t m_fileRate;
+    sv_samplerate_t m_fileRate;
 
     QString m_cacheFileName;
     SNDFILE *m_cacheFileWritePtr;
     WavFileReader *m_cacheFileReader;
     float *m_cacheWriteBuffer;
-    size_t m_cacheWriteBufferIndex;
-    size_t m_cacheWriteBufferSize; // frames
+    sv_frame_t m_cacheWriteBufferIndex;
+    sv_frame_t m_cacheWriteBufferSize; // frames
 
     Resampler *m_resampler;
     float *m_resampleBuffer;
-    size_t m_fileFrameCount;
+    sv_frame_t m_fileFrameCount;
+
+    bool m_normalised;
+    float m_max;
+    float m_gain;
 };
 
 #endif
--- a/data/fileio/CoreAudioFileReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/CoreAudioFileReader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -57,11 +57,12 @@
 }
 
 CoreAudioFileReader::CoreAudioFileReader(FileSource source,
-                                         DecodeMode decodeMode,
+                                         DecodeMode /* decodeMode */,
                                          CacheMode mode,
-                                         size_t targetRate,
+                                         sv_samplerate_t targetRate,
+                                         bool normalised,
                                          ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate),
+    CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_d(new D),
@@ -89,16 +90,16 @@
 
     //!!! how do we find out if the file open fails because of DRM protection?
 
-#if (MACOSX_DEPLOYMENT_TARGET <= 1040 && MAC_OS_X_VERSION_MIN_REQUIRED <= 1040)
-    FSRef fsref;
-    if (!CFURLGetFSRef(url, &fsref)) { // returns Boolean, not error code
-        m_error = "CoreAudioReadStream: Error looking up FS ref (file not found?)";
-        return;
-    }
-    m_d->err = ExtAudioFileOpen(&fsref, &m_d->file);
-#else
+//#if (MACOSX_DEPLOYMENT_TARGET <= 1040 && MAC_OS_X_VERSION_MIN_REQUIRED <= 1040)
+//    FSRef fsref;
+//    if (!CFURLGetFSRef(url, &fsref)) { // returns Boolean, not error code
+//        m_error = "CoreAudioReadStream: Error looking up FS ref (file not found?)";
+//        return;
+//    }
+//    m_d->err = ExtAudioFileOpen(&fsref, &m_d->file);
+//#else
     m_d->err = ExtAudioFileOpenURL(url, &m_d->file);
-#endif
+//#endif
 
     CFRelease(url);
 
@@ -179,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();
@@ -195,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/CoreAudioFileReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/CoreAudioFileReader.h	Wed Apr 20 12:06:28 2016 +0100
@@ -31,16 +31,12 @@
     Q_OBJECT
 
 public:
-    enum DecodeMode {
-        DecodeAtOnce, // decode the file on construction, with progress
-        DecodeThreaded // decode in a background thread after construction
-    };
-
     CoreAudioFileReader(FileSource source,
                         DecodeMode decodeMode,
                         CacheMode cacheMode,
-                        size_t targetRate = 0,
-                        ProgressReporter *reporter = 0);
+                        sv_samplerate_t targetRate = 0,
+                        bool normalised = false,
+                        ProgressReporter *reporter = nullptr);
     virtual ~CoreAudioFileReader();
 
     virtual QString getError() const { return m_error; }
--- a/data/fileio/DataFileReaderFactory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/DataFileReaderFactory.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -32,7 +32,7 @@
                                     bool csv,
                                     MIDIFileImportPreferenceAcquirer *acquirer,
                                     CSVFormat format,
-                                    size_t mainModelSampleRate)
+                                    sv_samplerate_t mainModelSampleRate)
 {
     QString err;
 
@@ -58,7 +58,7 @@
 DataFileReader *
 DataFileReaderFactory::createReader(QString path,
                                     MIDIFileImportPreferenceAcquirer *acquirer,
-                                    size_t mainModelSampleRate)
+                                    sv_samplerate_t mainModelSampleRate)
 {
     DataFileReader *reader = createReader
         (path, false, acquirer, CSVFormat(), mainModelSampleRate);
@@ -74,7 +74,7 @@
 Model *
 DataFileReaderFactory::load(QString path,
                             MIDIFileImportPreferenceAcquirer *acquirer,
-                            size_t mainModelSampleRate)
+                            sv_samplerate_t mainModelSampleRate)
 {
     DataFileReader *reader = createReader(path,
                                           acquirer,
@@ -94,7 +94,7 @@
 Model *
 DataFileReaderFactory::loadNonCSV(QString path,
                                   MIDIFileImportPreferenceAcquirer *acquirer,
-                                  size_t mainModelSampleRate)
+                                  sv_samplerate_t mainModelSampleRate)
 {
     DataFileReader *reader = createReader(path, false,
                                           acquirer,
@@ -114,7 +114,7 @@
 
 Model *
 DataFileReaderFactory::loadCSV(QString path, CSVFormat format,
-                               size_t mainModelSampleRate)
+                               sv_samplerate_t mainModelSampleRate)
 {
     DataFileReader *reader = createReader(path, true, 0, format,
                                           mainModelSampleRate);
--- a/data/fileio/DataFileReaderFactory.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/DataFileReaderFactory.h	Wed Apr 20 12:06:28 2016 +0100
@@ -48,7 +48,7 @@
      */
     static DataFileReader *createReader(QString path,
                                         MIDIFileImportPreferenceAcquirer *,
-					size_t mainModelSampleRate);
+					sv_samplerate_t mainModelSampleRate);
 
     /**
      * Read the given path, if a suitable reader is available.
@@ -60,7 +60,7 @@
      */
     static Model *load(QString path,
                        MIDIFileImportPreferenceAcquirer *acquirer,
-                       size_t mainModelSampleRate);
+                       sv_samplerate_t mainModelSampleRate);
 
     /**
      * Read the given path, if a suitable reader is available.
@@ -69,7 +69,7 @@
      */
     static Model *loadNonCSV(QString path,
                              MIDIFileImportPreferenceAcquirer *acquirer,
-                             size_t mainModelSampleRate);
+                             sv_samplerate_t mainModelSampleRate);
 
     /**
      * Read the given path using the CSV reader with the given format.
@@ -77,13 +77,13 @@
      */
     static Model *loadCSV(QString path,
                           CSVFormat format,
-                          size_t mainModelSampleRate);
+                          sv_samplerate_t mainModelSampleRate);
 
 protected:
     static DataFileReader *createReader(QString path, bool csv,
                                         MIDIFileImportPreferenceAcquirer *,
                                         CSVFormat format,
-					size_t mainModelSampleRate);
+					sv_samplerate_t mainModelSampleRate);
 };
 
 #endif
--- a/data/fileio/DecodingWavFileReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/DecodingWavFileReader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -21,12 +21,15 @@
 
 #include <QFileInfo>
 
+using namespace std;
+
 DecodingWavFileReader::DecodingWavFileReader(FileSource source,
-						 ResampleMode resampleMode,
-						 CacheMode mode,
-						 size_t targetRate,
-                                                 ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate),
+                                             DecodeMode decodeMode,
+                                             CacheMode mode,
+                                             sv_samplerate_t targetRate,
+                                             bool normalised,
+                                             ProgressReporter *reporter) :
+    CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_cancelled(false),
@@ -55,7 +58,7 @@
 
     initialiseDecodeCache();
 
-    if (resampleMode == ResampleAtOnce) {
+    if (decodeMode == DecodeAtOnce) {
 
         if (m_reporter) {
             connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
@@ -63,17 +66,17 @@
                 (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
         }
 
-        size_t blockSize = 16384;
-        size_t total = m_original->getFrameCount();
+        sv_frame_t blockSize = 16384;
+        sv_frame_t total = m_original->getFrameCount();
 
-        SampleBlock block;
+        vector<float> block;
 
-        for (size_t i = 0; i < total; i += blockSize) {
+        for (sv_frame_t i = 0; i < total; i += blockSize) {
 
-            size_t count = blockSize;
+            sv_frame_t count = blockSize;
             if (i + count > total) count = total - i;
 
-            m_original->getInterleavedFrames(i, count, block);
+            block = m_original->getInterleavedFrames(i, count);
             addBlock(block);
 
             if (m_cancelled) break;
@@ -120,17 +123,17 @@
         m_reader->startSerialised("DecodingWavFileReader::Decode");
     }
 
-    size_t blockSize = 16384;
-    size_t total = m_reader->m_original->getFrameCount();
+    sv_frame_t blockSize = 16384;
+    sv_frame_t total = m_reader->m_original->getFrameCount();
     
-    SampleBlock block;
+    vector<float> block;
     
-    for (size_t i = 0; i < total; i += blockSize) {
+    for (sv_frame_t i = 0; i < total; i += blockSize) {
         
-        size_t count = blockSize;
+        sv_frame_t count = blockSize;
         if (i + count > total) count = total - i;
         
-        m_reader->m_original->getInterleavedFrames(i, count, block);
+        block = m_reader->m_original->getInterleavedFrames(i, count);
         m_reader->addBlock(block);
 
         if (m_reader->m_cancelled) break;
@@ -146,16 +149,16 @@
 } 
 
 void
-DecodingWavFileReader::addBlock(const SampleBlock &frames)
+DecodingWavFileReader::addBlock(const vector<float> &frames)
 {
     addSamplesToDecodeCache(frames);
 
     m_processed += frames.size();
 
-    float ratio = float(m_sampleRate) / float(m_fileRate);
+    double ratio = double(m_sampleRate) / double(m_fileRate);
 
-    int progress = lrint((float(m_processed) * ratio * 100) /
-                         float(m_original->getFrameCount()));
+    int progress = int(lrint((double(m_processed) * ratio * 100) /
+                             double(m_original->getFrameCount())));
 
     if (progress > 99) progress = 99;
     m_completion = progress;
@@ -166,7 +169,7 @@
 }
 
 void
-DecodingWavFileReader::getSupportedExtensions(std::set<QString> &extensions)
+DecodingWavFileReader::getSupportedExtensions(set<QString> &extensions)
 {
     WavFileReader::getSupportedExtensions(extensions);
 }
--- a/data/fileio/DecodingWavFileReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/DecodingWavFileReader.h	Wed Apr 20 12:06:28 2016 +0100
@@ -29,16 +29,12 @@
 {
     Q_OBJECT
 public:
-    enum ResampleMode {
-        ResampleAtOnce, // resample the file on construction, with progress dialog
-        ResampleThreaded // resample in a background thread after construction
-    };
-
     DecodingWavFileReader(FileSource source,
-                            ResampleMode resampleMode,
-                            CacheMode cacheMode,
-                            size_t targetRate = 0,
-                            ProgressReporter *reporter = 0);
+                          DecodeMode decodeMode, // determines when to resample
+                          CacheMode cacheMode,
+                          sv_samplerate_t targetRate = 0,
+                          bool normalised = false,
+                          ProgressReporter *reporter = 0);
     virtual ~DecodingWavFileReader();
 
     virtual QString getError() const { return m_error; }
@@ -62,13 +58,13 @@
     QString m_path;
     QString m_error;
     bool m_cancelled;
-    size_t m_processed;
+    sv_frame_t m_processed;
     int m_completion;
 
     WavFileReader *m_original;
     ProgressReporter *m_reporter;
 
-    void addBlock(const SampleBlock &frames);
+    void addBlock(const std::vector<float> &frames);
     
     class DecodeThread : public Thread
     {
--- a/data/fileio/FFTFuzzyAdapter.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +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.
-*/
-
-#include "FFTFuzzyAdapter.h"
-
-#include <cassert>
-
-FFTFuzzyAdapter::FFTFuzzyAdapter(const DenseTimeValueModel *model,
-				 int channel,
-				 WindowType windowType,
-				 size_t windowSize,
-				 size_t windowIncrement,
-				 size_t fftSize,
-				 bool polar,
-				 size_t fillFromColumn) :
-    m_server(0),
-    m_xshift(0),
-    m_yshift(0)
-{
-    m_server = FFTDataServer::getFuzzyInstance(model,
-                                               channel,
-                                               windowType,
-                                               windowSize,
-                                               windowIncrement,
-                                               fftSize,
-                                               polar,
-                                               fillFromColumn);
-
-    size_t xratio = windowIncrement / m_server->getWindowIncrement();
-    size_t yratio = m_server->getFFTSize() / fftSize;
-
-    while (xratio > 1) {
-        if (xratio & 0x1) {
-            cerr << "ERROR: FFTFuzzyAdapter: Window increment ratio "
-                      << windowIncrement << " / "
-                      << m_server->getWindowIncrement()
-                      << " must be a power of two" << endl;
-            assert(!(xratio & 0x1));
-        }
-        ++m_xshift;
-        xratio >>= 1;
-    }
-
-    while (yratio > 1) {
-        if (yratio & 0x1) {
-            cerr << "ERROR: FFTFuzzyAdapter: FFT size ratio "
-                      << m_server->getFFTSize() << " / " << fftSize
-                      << " must be a power of two" << endl;
-            assert(!(yratio & 0x1));
-        }
-        ++m_yshift;
-        yratio >>= 1;
-    }
-}
-
-FFTFuzzyAdapter::~FFTFuzzyAdapter()
-{
-    FFTDataServer::releaseInstance(m_server);
-}
-
--- a/data/fileio/FFTFuzzyAdapter.h	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +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 _FFT_FUZZY_ADAPTER_H_
-#define _FFT_FUZZY_ADAPTER_H_
-
-#include "FFTDataServer.h"
-
-class FFTFuzzyAdapter
-{
-public:
-    FFTFuzzyAdapter(const DenseTimeValueModel *model,
-                    int channel,
-                    WindowType windowType,
-                    size_t windowSize,
-                    size_t windowIncrement,
-                    size_t fftSize,
-                    bool polar,
-                    size_t fillFromColumn = 0);
-    ~FFTFuzzyAdapter();
-
-    size_t getWidth() const {
-        return m_server->getWidth() >> m_xshift;
-    }
-    size_t getHeight() const {
-        return m_server->getHeight() >> m_yshift;
-    }
-    float getMagnitudeAt(size_t x, size_t y) {
-        return m_server->getMagnitudeAt(x << m_xshift, y << m_yshift);
-    }
-    float getNormalizedMagnitudeAt(size_t x, size_t y) {
-        return m_server->getNormalizedMagnitudeAt(x << m_xshift, y << m_yshift);
-    }
-    float getMaximumMagnitudeAt(size_t x) {
-        return m_server->getMaximumMagnitudeAt(x << m_xshift);
-    }
-    float getPhaseAt(size_t x, size_t y) {
-        return m_server->getPhaseAt(x << m_xshift, y << m_yshift);
-    }
-    void getValuesAt(size_t x, size_t y, float &real, float &imaginary) {
-        m_server->getValuesAt(x << m_xshift, y << m_yshift, real, imaginary);
-    }
-    bool isColumnReady(size_t x) {
-        return m_server->isColumnReady(x << m_xshift);
-    }
-    bool isLocalPeak(size_t x, size_t y) {
-        float mag = getMagnitudeAt(x, y);
-        if (y > 0 && mag < getMagnitudeAt(x, y - 1)) return false;
-        if (y < getHeight() - 1 && mag < getMagnitudeAt(x, y + 1)) return false;
-        return true;
-    }
-    bool isOverThreshold(size_t x, size_t y, float threshold) {
-        return getMagnitudeAt(x, y) > threshold;
-    }
-
-    size_t getFillCompletion() const { return m_server->getFillCompletion(); }
-    size_t getFillExtent() const { return m_server->getFillExtent(); }
-
-private:
-    FFTFuzzyAdapter(const FFTFuzzyAdapter &); // not implemented
-    FFTFuzzyAdapter &operator=(const FFTFuzzyAdapter &); // not implemented
-
-    FFTDataServer *m_server;
-    int m_xshift;
-    int m_yshift;
-};
-
-#endif
--- a/data/fileio/FileFinder.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/FileFinder.h	Wed Apr 20 12:06:28 2016 +0100
@@ -30,7 +30,9 @@
         ImageFile,
         AnyFile,
         CSVFile,
-        IMAFile
+        LayerFileNonSV,
+        LayerFileNoMidiNonSV,
+        IMAFile,
     };
 
     virtual QString getOpenFileName(FileType type, QString fallbackLocation = "") = 0;
--- a/data/fileio/FileSource.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/FileSource.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -51,8 +51,11 @@
 
 #ifdef DEBUG_FILE_SOURCE
 static int extantCount = 0;
+static int threadCount = 0;
 static std::map<QString, int> urlExtantCountMap;
+static QMutex countMutex;
 static void incCount(QString url) {
+    QMutexLocker locker(&countMutex);
     ++extantCount;
     if (urlExtantCountMap.find(url) == urlExtantCountMap.end()) {
         urlExtantCountMap[url] = 1;
@@ -62,10 +65,26 @@
     cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << endl;
 }
 static void decCount(QString url) {
+    QMutexLocker locker(&countMutex);
     --extantCount;
     --urlExtantCountMap[url];
     cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << endl;
 }
+void
+FileSource::debugReport()
+{
+    QMutexLocker locker(&countMutex);
+    cerr << "\nFileSource::debugReport: Have " << extantCount << " FileSource object(s) extant across " << threadCount << " thread(s)" << endl;
+    cerr << "URLs by extant count:" << endl;
+    cerr << "Count | URL" << endl;
+    for (std::map<QString, int>::const_iterator i = urlExtantCountMap.begin();
+         i != urlExtantCountMap.end(); ++i) {
+        cerr << i->second << " | " << i->first << endl;
+    }
+    cerr << "FileSource::debugReport done\n" << endl;
+}
+#else
+void FileSource::debugReport() { }
 #endif
 
 static QThreadStorage<QNetworkAccessManager *> nms;
@@ -78,6 +97,7 @@
     m_reply(0),
     m_preferredContentType(preferredContentType),
     m_ok(false),
+    m_cancelled(false),
     m_lastStatus(0),
     m_resource(fileOrUrl.startsWith(':')),
     m_remote(isRemote(fileOrUrl)),
@@ -167,6 +187,7 @@
     m_localFile(0),
     m_reply(0),
     m_ok(false),
+    m_cancelled(false),
     m_lastStatus(0),
     m_resource(false),
     m_remote(isRemote(url.toString())),
@@ -199,6 +220,7 @@
     m_localFile(0),
     m_reply(0),
     m_ok(rf.m_ok),
+    m_cancelled(rf.m_cancelled),
     m_lastStatus(rf.m_lastStatus),
     m_resource(rf.m_resource),
     m_remote(rf.m_remote),
@@ -265,13 +287,6 @@
 void
 FileSource::init()
 {
-    { // check we have a QNetworkAccessManager
-        QMutexLocker locker(&m_mapMutex);
-        if (!nms.hasLocalData()) {
-            nms.setLocalData(new QNetworkAccessManager());
-        }
-    }
-
     if (isResource()) {
 #ifdef DEBUG_FILE_SOURCE
         cerr << "FileSource::init: Is a resource" << endl;
@@ -460,6 +475,16 @@
              QString("%1, */*").arg(m_preferredContentType).toLatin1());
     }
 
+    { // check we have a QNetworkAccessManager
+        QMutexLocker locker(&m_mapMutex);
+        if (!nms.hasLocalData()) {
+#ifdef DEBUG_FILE_SOURCE
+            ++threadCount;
+#endif
+            nms.setLocalData(new QNetworkAccessManager());
+        }
+    }
+
     m_reply = nms.localData()->get(req);
 
     connect(m_reply, SIGNAL(readyRead()),
@@ -484,8 +509,12 @@
     m_done = true;
     if (m_reply) {
         QNetworkReply *r = m_reply;
+        disconnect(r, 0, this, 0);
         m_reply = 0;
-        r->abort();
+        // Can only call abort() when there are no errors.
+        if (r->error() == QNetworkReply::NoError) {
+            r->abort();
+        }
         r->deleteLater();
     }
     if (m_localFile) {
@@ -518,11 +547,14 @@
 {
     waitForStatus();
     bool available = true;
-    if (!m_ok) available = false;
-    else available = (m_lastStatus / 100 == 2);
+    if (!m_ok) {
+        available = false;
+    } else {
+        // http 2xx status codes mean success
+        available = (m_lastStatus / 100 == 2);
+    }
 #ifdef DEBUG_FILE_SOURCE
-    cerr << "FileSource::isAvailable: " << (available ? "yes" : "no")
-              << endl;
+    cerr << "FileSource::isAvailable: " << (available ? "yes" : "no") << endl;
 #endif
     return available;
 }
@@ -565,6 +597,12 @@
 }
 
 bool
+FileSource::wasCancelled() const
+{
+    return m_cancelled;
+}
+
+bool
 FileSource::isResource() const
 {
     return m_resource;
@@ -634,9 +672,12 @@
         return;
     }
 
+    // Handle http transfer status codes.
+
     int status =
         m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
 
+    // If this is a redirection (3xx) code, do the redirect
     if (status / 100 == 3) {
         QString location = m_reply->header
             (QNetworkRequest::LocationHeader).toString();
@@ -665,6 +706,8 @@
     }
 
     m_lastStatus = status;
+
+    // 400 and up are failures, get the error string
     if (m_lastStatus / 100 >= 4) {
         m_errorString = QString("%1 %2")
             .arg(status)
@@ -699,6 +742,7 @@
     cleanup();
 
     m_ok = false;
+    m_cancelled = true;
     m_errorString = tr("Download cancelled");
 }
 
@@ -713,6 +757,13 @@
 
     if (m_done) return;
 
+    QString scheme = m_url.scheme().toLower();
+    // For ftp transfers, replyFinished() will be called on success.
+    // metaDataChanged() is never called for ftp transfers.
+    if (scheme == "ftp") {
+        m_lastStatus = 200;  // http ok
+    }
+
     bool error = (m_lastStatus / 100 >= 4);
 
     cleanup();
@@ -745,7 +796,11 @@
 FileSource::replyFailed(QNetworkReply::NetworkError)
 {
     emit progress(100);
-    m_errorString = m_reply->errorString();
+    if (!m_reply) {
+        cerr << "WARNING: FileSource::replyFailed() called without a reply object being known to us" << endl;
+    } else {
+        m_errorString = m_reply->errorString();
+    }
     m_ok = false;
     m_done = true;
     cleanup();
@@ -835,7 +890,7 @@
 #ifdef DEBUG_FILE_SOURCE
         cerr << "FileSource::createCacheFile: ERROR: Failed to create temporary directory: " << f.what() << endl;
 #endif
-        return "";
+        return false;
     }
 
     QString filepart = m_url.path().section('/', -1, -1,
@@ -893,7 +948,7 @@
                       << m_url.toString() << "\" (or file already exists)" << endl;
 #endif
 
-            return "";
+            return false;
         }
     }
 
--- a/data/fileio/FileSource.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/FileSource.h	Wed Apr 20 12:06:28 2016 +0100
@@ -101,10 +101,10 @@
     void waitForData();
 
     /**
-     * Return true if the FileSource object is valid and no error
-     * occurred in looking up the file or remote URL.  Non-existence
-     * of the file or URL is not an error -- call isAvailable() to
-     * test for that.
+     * Return true if the FileSource object is valid and neither error
+     * nor cancellation occurred while retrieving the file or remote
+     * URL.  Non-existence of the file or URL is not an error -- call
+     * isAvailable() to test for that.
      */
     bool isOK() const;
 
@@ -122,6 +122,14 @@
     bool isDone() const;
 
     /**
+     * Return true if the operation was cancelled by the user through
+     * the ProgressReporter interface. Note that the cancelled()
+     * signal will have been emitted, and isOK() will also return
+     * false in this case.
+     */
+    bool wasCancelled() const;
+
+    /**
      * Return true if this FileSource is referring to a QRC resource.
      */
     bool isResource() const;
@@ -159,7 +167,8 @@
     QString getContentType() const;
 
     /**
-     * Return the file extension for this file, if any.
+     * Return the file extension for this file, if any. The returned
+     * extension is always lower-case.
      */
     QString getExtension() const;
 
@@ -187,6 +196,13 @@
      */
     static bool canHandleScheme(QUrl url);
 
+    /**
+     * Print some stats, if FileSource was compiled with debugging.
+     * It's safe to leave a call to this function in release code, as
+     * long as FileSource itself is compiled with release flags.
+     */
+    static void debugReport();
+    
 signals:
     /**
      * Emitted during URL retrieval, when the retrieval progress
@@ -227,6 +243,7 @@
     QString m_contentType;
     QString m_preferredContentType;
     bool m_ok;
+    bool m_cancelled;
     int m_lastStatus;
     bool m_resource;
     bool m_remote;
--- a/data/fileio/MIDIFileReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/MIDIFileReader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -36,6 +36,7 @@
 #include "model/NoteModel.h"
 
 #include <QString>
+#include <QFileInfo>
 
 #include <sstream>
 
@@ -57,7 +58,7 @@
 
 MIDIFileReader::MIDIFileReader(QString path,
                                MIDIFileImportPreferenceAcquirer *acquirer,
-			       size_t mainModelSampleRate) :
+			       sv_samplerate_t mainModelSampleRate) :
     m_smpte(false),
     m_timingDivision(0),
     m_fps(0),
@@ -300,7 +301,9 @@
 	// Set file size so we can count it off
 	//
 	m_midiFile->seekg(0, ios::end);
-	m_fileSize = m_midiFile->tellg();
+        std::streamoff off = m_midiFile->tellg();
+	m_fileSize = 0;
+        if (off > 0) m_fileSize = off;
 	m_midiFile->seekg(0, ios::beg);
 
 	// Parse the MIDI header first.  The first 14 bytes of the file.
@@ -439,9 +442,9 @@
     MIDIByte midiByte, metaEventCode, data1, data2;
     MIDIByte eventCode = 0x80;
     string metaMessage;
-    unsigned int messageLength;
-    unsigned long deltaTime;
-    unsigned long accumulatedTime = 0;
+    long messageLength;
+    long deltaTime;
+    long accumulatedTime = 0;
 
     // The trackNum passed in to this method is the default track for
     // all events provided they're all on the same channel.  If we find
@@ -629,13 +632,6 @@
                 m_midiComposition[trackNum].push_back(midiEvent);
                 break;
 
-            case MIDI_END_OF_EXCLUSIVE:
-#ifdef MIDI_DEBUG
-                SVDEBUG << "MIDIFileReader::parseTrack() - "
-                          << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl;
-#endif
-                break;
-
             default:
 #ifdef MIDI_DEBUG
                 SVDEBUG << "MIDIFileReader::parseTrack()" 
@@ -896,7 +892,7 @@
 
     if (tracksToLoad.empty()) return 0;
 
-    size_t n = tracksToLoad.size(), count = 0;
+    int n = int(tracksToLoad.size()), count = 0;
     Model *model = 0;
 
     for (std::set<unsigned int>::iterator i = tracksToLoad.begin();
@@ -939,14 +935,14 @@
     if (!model) {
 	model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false);
 	model->setValueQuantization(1.0);
+        model->setObjectName(QFileInfo(m_path).fileName());
     }
 
     const MIDITrack &track = m_midiComposition.find(trackToLoad)->second;
 
-    size_t totalEvents = track.size();
-    size_t count = 0;
+    int totalEvents = int(track.size());
+    int count = 0;
 
-    bool minorKey = false;
     bool sharpKey = true;
 
     for (MIDITrack::const_iterator i = track.begin(); i != track.end(); ++i) {
@@ -968,7 +964,7 @@
 	    switch((*i)->getMetaEventCode()) {
 
 	    case MIDI_KEY_SIGNATURE:
-		minorKey = (int((*i)->getMetaMessage()[1]) != 0);
+		// minorKey = (int((*i)->getMetaMessage()[1]) != 0);
 		sharpKey = (int((*i)->getMetaMessage()[0]) >= 0);
 		break;
 
--- a/data/fileio/MIDIFileReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/MIDIFileReader.h	Wed Apr 20 12:06:28 2016 +0100
@@ -62,7 +62,7 @@
 public:
     MIDIFileReader(QString path,
                    MIDIFileImportPreferenceAcquirer *pref,
-                   size_t mainModelSampleRate);
+                   sv_samplerate_t mainModelSampleRate);
     virtual ~MIDIFileReader();
 
     virtual bool isOK() const;
@@ -128,7 +128,7 @@
     std::ifstream         *m_midiFile;
     size_t                 m_fileSize;
     QString                m_error;
-    size_t                 m_mainModelSampleRate;
+    sv_samplerate_t        m_mainModelSampleRate;
 
     MIDIFileImportPreferenceAcquirer *m_acquirer;
 };
--- a/data/fileio/MIDIFileWriter.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/MIDIFileWriter.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -23,28 +23,31 @@
 #include "MIDIFileWriter.h"
 
 #include "data/midi/MIDIEvent.h"
-
-#include "model/NoteModel.h"
+#include "model/NoteData.h"
 
 #include "base/Pitch.h"
 
+#include <QCoreApplication>
+
 #include <algorithm>
 #include <fstream>
 
+//#define DEBUG_MIDI_FILE_WRITER 1
+
 using std::ofstream;
 using std::string;
 using std::ios;
 
 using namespace MIDIConstants;
 
-MIDIFileWriter::MIDIFileWriter(QString path, NoteModel *model, float tempo) :
+MIDIFileWriter::MIDIFileWriter(QString path, const NoteExportable *exportable,
+                               sv_samplerate_t sampleRate, float tempo) :
     m_path(path),
-    m_model(model),
-    m_modelUsesHz(false),
-    m_tempo(tempo)
+    m_exportable(exportable),
+    m_sampleRate(sampleRate),
+    m_tempo(tempo),
+    m_midiFile(0)
 {
-    if (model->getScaleUnits().toLower() == "hz") m_modelUsesHz = true;
-
     if (!convert()) {
         m_error = "Conversion from model to internal MIDI format failed";
     }
@@ -90,8 +93,8 @@
     MIDIByte upper;
     MIDIByte lower;
 
-    upper = (number & 0xFF00) >> 8;
-    lower = (number & 0x00FF);
+    upper = MIDIByte((number & 0xFF00) >> 8);
+    lower = MIDIByte( number & 0x00FF);
 
     string rv;
     rv += upper;
@@ -107,10 +110,10 @@
     MIDIByte upper2;
     MIDIByte lower2;
 
-    upper1 = (number & 0xff000000) >> 24;
-    lower1 = (number & 0x00ff0000) >> 16;
-    upper2 = (number & 0x0000ff00) >> 8;
-    lower2 = (number & 0x000000ff);
+    upper1 = MIDIByte((number & 0xff000000) >> 24);
+    lower1 = MIDIByte((number & 0x00ff0000) >> 16);
+    upper2 = MIDIByte((number & 0x0000ff00) >> 8);
+    lower2 = MIDIByte((number & 0x000000ff));
 
     string rv;
     rv += upper1;
@@ -318,16 +321,12 @@
     m_numberOfTracks = 1;
 
     int track = 0;
-    int midiChannel = 0;
 
     MIDIEvent *event;
 
-    event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
-                          "Exported from Sonic Visualiser");
-    m_midiComposition[track].push_back(event);
-
-    event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
-                          "http://www.sonicvisualiser.org/");
+    event = new MIDIEvent
+        (0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
+         ("Exported from " + qApp->applicationName()).toStdString());
     m_midiComposition[track].push_back(event);
 
     long tempoValue = long(60000000.0 / m_tempo + 0.01);
@@ -342,42 +341,32 @@
 
     // Omit time signature
 
-    const NoteModel::PointList &notes =
-        static_cast<SparseModel<Note> *>(m_model)->getPoints();
+    NoteList notes = m_exportable->getNotes();
 
-    for (NoteModel::PointList::const_iterator i = notes.begin();
-         i != notes.end(); ++i) {
+    for (NoteList::const_iterator i = notes.begin(); i != notes.end(); ++i) {
 
-        long frame = i->frame;
-        float value = i->value;
-        size_t duration = i->duration;
-
-        int pitch;
-
-        if (m_modelUsesHz) {
-            pitch = Pitch::getPitchForFrequency(value);
-        } else {
-            pitch = lrintf(value);
-        }
+        sv_frame_t frame = i->start;
+        sv_frame_t duration = i->duration;
+        int pitch = i->midiPitch;
+        int velocity = i->velocity;
+        int channel = i->channel;
 
         if (pitch < 0) pitch = 0;
         if (pitch > 127) pitch = 127;
 
+        if (channel < 0) channel = 0;
+        if (channel > 15) channel = 0;
+
         // Convert frame to MIDI time
 
-        double seconds = double(frame) / double(m_model->getSampleRate());
+        double seconds = double(frame) / m_sampleRate;
         double quarters = (seconds * m_tempo) / 60.0;
-        unsigned long midiTime = lrint(quarters * m_timingDivision);
-
-        int velocity = 100;
-        if (i->level > 0.f && i->level <= 1.f) {
-            velocity = lrintf(i->level * 127.f);
-        }
+        unsigned long midiTime = int(quarters * m_timingDivision + 0.5);
 
         // Get the sounding time for the matching NOTE_OFF
-        seconds = double(frame + duration) / double(m_model->getSampleRate());
+        seconds = double(frame + duration) / m_sampleRate;
         quarters = (seconds * m_tempo) / 60.0;
-        unsigned long endTime = lrint(quarters * m_timingDivision);
+        unsigned long endTime = int(quarters * m_timingDivision + 0.5);
 
         // At this point all the notes we insert have absolute times
         // in the delta time fields.  We resolve these into delta
@@ -385,17 +374,21 @@
         // in place).
 
         event = new MIDIEvent(midiTime,
-                              MIDI_NOTE_ON | midiChannel,
+                              MIDI_NOTE_ON | channel,
                               pitch,
                               velocity);
         m_midiComposition[track].push_back(event);
 
         event = new MIDIEvent(endTime,
-                              MIDI_NOTE_OFF | midiChannel,
+                              MIDI_NOTE_OFF | channel,
                               pitch,
                               127); // loudest silence you can muster
 
         m_midiComposition[track].push_back(event);
+
+#ifdef DEBUG_MIDI_FILE_WRITER
+        cerr << "midiTime = " << midiTime << ", endTime = " << endTime << endl;
+#endif
     }
     
     // Now gnash through the MIDI events and turn the absolute times
@@ -416,6 +409,9 @@
         for (MIDITrack::iterator it = m_midiComposition[i].begin();
              it != m_midiComposition[i].end(); it++) {
             unsigned long deltaTime = (*it)->getTime() - lastMidiTime;
+#ifdef DEBUG_MIDI_FILE_WRITER
+            cerr << "time = " << (*it)->getTime() << ", lastMidiTime = " << lastMidiTime << ", deltaTime = " << deltaTime << endl;
+#endif
             lastMidiTime = (*it)->getTime();
             (*it)->setTime(deltaTime);
         }
--- a/data/fileio/MIDIFileWriter.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/MIDIFileWriter.h	Wed Apr 20 12:06:28 2016 +0100
@@ -24,6 +24,7 @@
 #define _MIDI_FILE_WRITER_H_
 
 #include "base/RealTime.h"
+#include "base/BaseTypes.h"
 
 #include <QString>
 
@@ -32,7 +33,7 @@
 #include <fstream>
 
 class MIDIEvent;
-class NoteModel;
+class NoteExportable;
 
 /**
  * Write a MIDI file.  This includes file write code for generic
@@ -43,7 +44,10 @@
 class MIDIFileWriter 
 {
 public:
-    MIDIFileWriter(QString path, NoteModel *model, float tempo = 120.f);
+    MIDIFileWriter(QString path, 
+                   const NoteExportable *exportable, 
+                   sv_samplerate_t sampleRate, // used to convert exportable sample timings
+                   float tempo = 120.f);
     virtual ~MIDIFileWriter();
 
     virtual bool isOK() const;
@@ -74,18 +78,18 @@
     
     bool convert();
 
-    QString             m_path;
-    NoteModel          *m_model;
-    bool                m_modelUsesHz;
-    float               m_tempo;
-    int                 m_timingDivision;   // pulses per quarter note
-    MIDIFileFormatType  m_format;
-    unsigned int        m_numberOfTracks;
+    QString               m_path;
+    const NoteExportable *m_exportable;
+    sv_samplerate_t       m_sampleRate;
+    float                 m_tempo;
+    int                   m_timingDivision;   // pulses per quarter note
+    MIDIFileFormatType    m_format;
+    unsigned int          m_numberOfTracks;
 
-    MIDIComposition     m_midiComposition;
+    MIDIComposition       m_midiComposition;
 
-    std::ofstream      *m_midiFile;
-    QString             m_error;
+    std::ofstream        *m_midiFile;
+    QString               m_error;
 };
 
 #endif
--- a/data/fileio/MP3FileReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/MP3FileReader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -32,14 +32,16 @@
 #ifdef HAVE_ID3TAG
 #include <id3tag.h>
 #endif
+
 //#define DEBUG_ID3TAG 1
 
 #include <QFileInfo>
 
 MP3FileReader::MP3FileReader(FileSource source, DecodeMode decodeMode, 
-                             CacheMode mode, size_t targetRate,
+                             CacheMode mode, sv_samplerate_t targetRate,
+                             bool normalised,
                              ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate),
+    CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_decodeThread(0)
@@ -62,6 +64,10 @@
 
     m_fileSize = stat.st_size;
 
+    m_filebuffer = 0;
+    m_samplebuffer = 0;
+    m_samplebuffersize = 0;
+
     int fd = -1;
     if ((fd = ::open(m_path.toLocal8Bit().data(), O_RDONLY
 #ifdef _WIN32
@@ -72,10 +78,6 @@
 	return;
     }	
 
-    m_filebuffer = 0;
-    m_samplebuffer = 0;
-    m_samplebuffersize = 0;
-
     try {
         m_filebuffer = new unsigned char[m_fileSize];
     } catch (...) {
@@ -85,7 +87,7 @@
     }
     
     ssize_t sz = 0;
-    size_t offset = 0;
+    ssize_t offset = 0;
     while (offset < m_fileSize) {
         sz = ::read(fd, m_filebuffer + offset, m_fileSize - offset);
         if (sz < 0) {
@@ -177,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;
@@ -200,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
@@ -215,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 "";
     }
@@ -236,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 "";
     }
@@ -251,7 +253,7 @@
     free(u8str);
 
 #ifdef DEBUG_ID3TAG
-	SVDEBUG << "MP3FileReader::loadTags: tag \"" << name << "\" -> \""
+	cerr << "MP3FileReader::loadTags: tag \"" << name << "\" -> \""
 	<< rv << "\"" << endl;
 #endif
 
@@ -274,7 +276,7 @@
     m_reader->m_filebuffer = 0;
 
     if (m_reader->m_samplebuffer) {
-        for (size_t c = 0; c < m_reader->m_channelCount; ++c) {
+        for (int c = 0; c < m_reader->m_channelCount; ++c) {
             delete[] m_reader->m_samplebuffer[c];
         }
         delete[] m_reader->m_samplebuffer;
@@ -290,7 +292,7 @@
 } 
 
 bool
-MP3FileReader::decode(void *mm, size_t sz)
+MP3FileReader::decode(void *mm, sv_frame_t sz)
 {
     DecoderData data;
     struct mad_decoder decoder;
@@ -319,7 +321,7 @@
 
 #ifdef HAVE_ID3TAG
     if (length > ID3_TAG_QUERYSIZE) {
-        int taglen = id3_tag_query(start, ID3_TAG_QUERYSIZE);
+        ssize_t taglen = id3_tag_query(start, ID3_TAG_QUERYSIZE);
         if (taglen > 0) {
 //            cerr << "ID3 tag length to skip: " << taglen << endl;
             start += taglen;
@@ -351,7 +353,7 @@
     int frames = pcm->length;
 
     if (header) {
-        m_bitrateNum += header->bitrate;
+        m_bitrateNum = m_bitrateNum + double(header->bitrate);
         m_bitrateDenom ++;
     }
 
@@ -391,7 +393,7 @@
         initialiseDecodeCache();
     }
 
-    if (m_samplebuffersize < frames) {
+    if (int(m_samplebuffersize) < frames) {
         if (!m_samplebuffer) {
             m_samplebuffer = new float *[channels];
             for (int c = 0; c < channels; ++c) {
@@ -427,11 +429,11 @@
 }
 
 enum mad_flow
-MP3FileReader::error(void *dp,
-		     struct mad_stream *stream,
+MP3FileReader::error(void * /* dp */,
+		     struct mad_stream * /* stream */,
 		     struct mad_frame *)
 {
-    DecoderData *data = (DecoderData *)dp;
+//    DecoderData *data = (DecoderData *)dp;
 
 //    fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %lu\n",
 //	    stream->error, mad_stream_errorstr(stream),
--- a/data/fileio/MP3FileReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/MP3FileReader.h	Wed Apr 20 12:06:28 2016 +0100
@@ -32,15 +32,11 @@
     Q_OBJECT
 
 public:
-    enum DecodeMode {
-        DecodeAtOnce, // decode the file on construction, with progress
-        DecodeThreaded // decode in a background thread after construction
-    };
-
     MP3FileReader(FileSource source,
                   DecodeMode decodeMode,
                   CacheMode cacheMode,
-                  size_t targetRate = 0,
+                  sv_samplerate_t targetRate = 0,
+                  bool normalised = false,
                   ProgressReporter *reporter = 0);
     virtual ~MP3FileReader();
 
@@ -72,15 +68,15 @@
     QString m_title;
     QString m_maker;
     TagMap m_tags;
-    size_t m_fileSize;
+    sv_frame_t m_fileSize;
     double m_bitrateNum;
-    size_t m_bitrateDenom;
+    int m_bitrateDenom;
     int m_completion;
     bool m_done;
 
     unsigned char *m_filebuffer;
     float **m_samplebuffer;
-    size_t m_samplebuffersize;
+    int m_samplebuffersize;
 
     ProgressReporter *m_reporter;
     bool m_cancelled;
@@ -92,7 +88,7 @@
 	MP3FileReader *reader;
     };
 
-    bool decode(void *mm, size_t sz);
+    bool decode(void *mm, sv_frame_t sz);
     enum mad_flow accept(struct mad_header const *, struct mad_pcm *);
 
     static enum mad_flow input(void *, struct mad_stream *);
--- a/data/fileio/MatchFileReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,183 +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 2007 QMUL.
-    
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#include "MatchFileReader.h"
-
-#include <QFile>
-#include <QTextStream>
-
-#include <cmath>
-#include <iostream>
-
-Alignment::Alignment() :
-    thisHopTime(0.0),
-    refHopTime(0.0)
-{
-}
-
-double
-Alignment::fromReference(double t) const
-{
-    int ri = lrint(t / refHopTime);
-    int index = search(refIndex, ri);
-    return thisIndex[index] * thisHopTime;
-}
-
-double
-Alignment::toReference(double t) const
-{
-    int ti = lrint(t / thisHopTime);
-    int index = search(thisIndex, ti);
-    return refIndex[index] * refHopTime;
-}
-
-int
-Alignment::search(const FrameArray &arr, int val) const
-{
-    int len = arr.size();
-    int max = len - 1;
-    int min = 0;
-    while (max > min) {
-        int mid = (max + min) / 2;
-        if (val > arr[mid]) {
-            min = mid + 1;
-        } else {
-            max = mid;
-        }
-    } // max = MIN_j (arr[j] >= val)   i.e. the first equal or next highest
-    while ((max + 1 < len) && (arr[max + 1] == val)) {
-        max++;
-    }
-    return (min + max) / 2;
-}
-
-MatchFileReader::MatchFileReader(QString path) :
-    m_file(0)
-{
-    m_file = new QFile(path);
-    bool good = false;
-    
-    if (!m_file->exists()) {
-	m_error = QFile::tr("File \"%1\" does not exist").arg(path);
-    } else if (!m_file->open(QIODevice::ReadOnly | QIODevice::Text)) {
-	m_error = QFile::tr("Failed to open file \"%1\"").arg(path);
-    } else {
-	good = true;
-    }
-
-    if (!good) {
-	delete m_file;
-	m_file = 0;
-    }
-}
-
-MatchFileReader::~MatchFileReader()
-{
-    if (m_file) {
-        SVDEBUG << "MatchFileReader::MatchFileReader: Closing file" << endl;
-        m_file->close();
-    }
-    delete m_file;
-}
-
-bool
-MatchFileReader::isOK() const
-{
-    return (m_file != 0);
-}
-
-QString
-MatchFileReader::getError() const
-{
-    return m_error;
-}
-
-Alignment
-MatchFileReader::load() const
-{
-    Alignment alignment;
-
-    if (!m_file) return alignment;
-
-    QTextStream in(m_file);
-
-/*
-File: /home/studio/match-test/mahler-3-boulez-5.wav
-Marks: -1
-FixedPoints: true 0
-0
-0
-0
-0
-File: /home/studio/match-test/mahler-3-haitink-5.wav
-Marks: 0
-FixedPoints: true 0
-0.02
-0.02
-12836
-*/
-
-    int fileCount = 0;
-    int state = 0;
-    int count = 0;
-
-    while (!in.atEnd()) {
-
-        QString line = in.readLine().trimmed();
-        if (line.startsWith("File: ")) {
-            ++fileCount;
-            continue;
-        }
-        if (fileCount != 2) continue;
-        if (line.startsWith("Marks:") || line.startsWith("FixedPoints:")) {
-            continue;
-        }
-
-        switch (state) {
-        case 0:
-            alignment.thisHopTime = line.toDouble();
-            break;
-        case 1:
-            alignment.refHopTime = line.toDouble();
-            break;
-        case 2: 
-            count = line.toInt();
-            break;
-        case 3:
-            alignment.thisIndex.push_back(line.toInt());
-            break;
-        case 4:
-            alignment.refIndex.push_back(line.toInt());
-            break;
-        }
-
-        if (state < 3) ++state;
-        else if (state == 3 && alignment.thisIndex.size() == count) ++state;
-    }
-
-    if (alignment.thisHopTime == 0.0) {
-        cerr << "ERROR in Match file: this hop time == 0, using 0.01 instead" << endl;
-        alignment.thisHopTime = 0.01;
-    }
-
-    if (alignment.refHopTime == 0.0) {
-        cerr << "ERROR in Match file: ref hop time == 0, using 0.01 instead" << endl;
-        alignment.refHopTime = 0.01;
-    }
-
-    cerr << "MatchFileReader: this hop = " << alignment.thisHopTime << ", ref hop = " << alignment.refHopTime << ", this index count = " << alignment.thisIndex.size() << ", ref index count = " << alignment.refIndex.size() << endl;
-
-    return alignment;
-}
--- a/data/fileio/MatchFileReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +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 2007 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 _MATCH_FILE_READER_H_
-#define _MATCH_FILE_READER_H_
-
-#include <vector>
-#include <QString>
-#include "base/Debug.h"
-
-class QFile;
-class Model;
-
-class Alignment
-{
-public:
-    Alignment();
-
-    typedef std::vector<int> FrameArray;
-
-    double thisHopTime;
-    double refHopTime;
-
-    FrameArray thisIndex;
-    FrameArray refIndex;
-
-    double fromReference(double) const;
-    double toReference(double) const;
-
-    //!!! blah
-    void setMainModel(Model *m) { m_mainModel = m; }
-    bool isMainModel(Model *m) const { return m == m_mainModel; }
-
-    int search(const FrameArray &arr, int val) const;
-
-protected:
-    Model *m_mainModel;
-};
-
-class MatchFileReader
-{
-public:
-    MatchFileReader(QString path);
-    virtual ~MatchFileReader();
-
-    virtual bool isOK() const;
-    virtual QString getError() const;
-    virtual Alignment load() const;
-
-protected:
-    QFile *m_file;
-    QString m_error;
-};
-
-#endif
-
--- a/data/fileio/MatrixFile.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/MatrixFile.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -52,7 +52,7 @@
 static size_t openCount = 0;
 
 MatrixFile::MatrixFile(QString fileBase, Mode mode,
-                       size_t cellSize, size_t width, size_t height) :
+                       int cellSize, int width, int height) :
     m_fd(-1),
     m_mode(mode),
     m_flags(0),
@@ -60,7 +60,7 @@
     m_cellSize(cellSize),
     m_width(width),
     m_height(height),
-    m_headerSize(2 * sizeof(size_t)),
+    m_headerSize(2 * sizeof(int)),
     m_setColumns(0),
     m_autoClose(false),
     m_readyToReadColumn(-1)
@@ -89,6 +89,16 @@
         throw FileOperationFailed(fileName, "create");
     }
 
+    // Use floating-point here to avoid integer overflow. We can be
+    // approximate so long as we are on the cautious side
+    if ((double(m_width) * m_height) * m_cellSize + m_headerSize + m_width >=
+        pow(2, 31) - 10.0) { // bit of slack there
+        cerr << "ERROR: MatrixFile::MatrixFile: width " << m_width
+             << " is too large for height " << m_height << " and cell size "
+             << m_cellSize << " (should be using multiple files)" << endl;
+        throw FileOperationFailed(fileName, "size");
+    }
+    
     m_flags = 0;
     m_fmode = S_IRUSR | S_IWUSR;
 
@@ -125,8 +135,8 @@
     if (newFile) {
         initialise(); // write header and "unwritten" column tags
     } else {
-        size_t header[2];
-        if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
+        int header[2];
+        if (::read(m_fd, header, 2 * sizeof(int)) < 0) {
             ::perror("MatrixFile::MatrixFile: read failed");
             cerr << "ERROR: MatrixFile::MatrixFile: "
                       << "Failed to read header (fd " << m_fd << ", file \""
@@ -166,8 +176,6 @@
 
     QMutexLocker locker(&m_createMutex);
 
-    delete m_setColumns;
-
     if (m_fileName != "") {
 
         if (--m_refcount[m_fileName] == 0) {
@@ -198,12 +206,12 @@
 
     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;
 
 #ifdef DEBUG_MATRIX_FILE
-    cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): cell size " << m_cellSize << ", header size " << m_headerSize << ", resizing file" << endl;
+    cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): cell size " << m_cellSize << ", header size " << m_headerSize << ", resizing fd " << m_fd << " to " << off << endl;
 #endif
 
     if (::lseek(m_fd, off - 1, SEEK_SET) < 0) {
@@ -222,10 +230,10 @@
         throw FileOperationFailed(m_fileName, "lseek");
     }
 
-    size_t header[2];
+    int header[2];
     header[0] = m_width;
     header[1] = m_height;
-    if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) {
+    if (::write(m_fd, header, 2 * sizeof(int)) != 2 * sizeof(int)) {
         ::perror("ERROR: MatrixFile::initialise: Failed to write header");
         throw FileOperationFailed(m_fileName, "write");
     }
@@ -263,7 +271,7 @@
 }
 
 void
-MatrixFile::getColumnAt(size_t x, void *data)
+MatrixFile::getColumnAt(int x, void *data)
 {
     assert(m_mode == ReadOnly);
     
@@ -276,7 +284,7 @@
     ssize_t r = -1;
 
     if (m_readyToReadColumn < 0 ||
-        size_t(m_readyToReadColumn) != x) {
+        m_readyToReadColumn != x) {
 
         unsigned char set = 0;
         if (!seekTo(x)) {
@@ -303,14 +311,14 @@
 }
 
 bool
-MatrixFile::haveSetColumnAt(size_t x) const
+MatrixFile::haveSetColumnAt(int x) const
 {
     if (m_mode == WriteOnly) {
-        return m_setColumns->get(x);
+        return m_setColumns[x];
     }
 
     if (m_readyToReadColumn >= 0 &&
-        size_t(m_readyToReadColumn) == x) return true;
+        int(m_readyToReadColumn) == x) return true;
     
     Profiler profiler("MatrixFile::haveSetColumnAt");
 
@@ -338,7 +346,7 @@
 }
 
 void
-MatrixFile::setColumnAt(size_t x, const void *data)
+MatrixFile::setColumnAt(int x, const void *data)
 {
     assert(m_mode == WriteOnly);
     if (m_fd < 0) return; // closed
@@ -388,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
@@ -408,7 +417,7 @@
 }
 
 bool
-MatrixFile::seekTo(size_t x) const
+MatrixFile::seekTo(int x) const
 {
     if (m_fd < 0) {
         cerr << "ERROR: MatrixFile::seekTo: File not open" << endl;
--- a/data/fileio/MatrixFile.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/MatrixFile.h	Wed Apr 20 12:06:28 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"
 
@@ -58,15 +56,15 @@
      * MatrixFile has no built-in cache and is not thread-safe.  Use a
      * separate MatrixFile in each thread.
      */
-    MatrixFile(QString fileBase, Mode mode, size_t cellSize,
-               size_t width, size_t height);
+    MatrixFile(QString fileBase, Mode mode, int cellSize,
+               int width, int height);
     virtual ~MatrixFile();
 
     Mode getMode() const { return m_mode; }
 
-    size_t getWidth() const { return m_width; }
-    size_t getHeight() const { return m_height; }
-    size_t getCellSize() const { return m_cellSize; }
+    int getWidth() const { return m_width; }
+    int getHeight() const { return m_height; }
+    int getCellSize() const { return m_cellSize; }
 
     /**
      * If this is set true on a write-mode MatrixFile, then the file
@@ -76,22 +74,22 @@
 
     void close(); // does not decrement ref count; that happens in dtor
 
-    bool haveSetColumnAt(size_t x) const;
-    void getColumnAt(size_t x, void *data); // may throw FileReadFailed
-    void setColumnAt(size_t x, const void *data);
+    bool haveSetColumnAt(int x) const;
+    void getColumnAt(int x, void *data); // may throw FileReadFailed
+    void setColumnAt(int x, const void *data);
 
 protected:
     int     m_fd;
     Mode    m_mode;
     int     m_flags;
     mode_t  m_fmode;
-    size_t  m_cellSize;
-    size_t  m_width;
-    size_t  m_height;
-    size_t  m_headerSize;
+    int     m_cellSize;
+    int     m_width;
+    int     m_height;
+    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
@@ -102,7 +100,7 @@
     static QMutex m_createMutex;
 
     void initialise();
-    bool seekTo(size_t x) const;
+    bool seekTo(int col) const;
 };
 
 #endif
--- a/data/fileio/OggVorbisFileReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/OggVorbisFileReader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -34,9 +34,10 @@
 OggVorbisFileReader::OggVorbisFileReader(FileSource source,
                                          DecodeMode decodeMode,
                                          CacheMode mode,
-                                         size_t targetRate,
+                                         sv_samplerate_t targetRate,
+                                         bool normalised,
                                          ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate),
+    CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_reporter(reporter),
@@ -143,15 +144,15 @@
     OggVorbisFileReader *reader = (OggVorbisFileReader *)data;
     FishSound *fs = reader->m_fishSound;
 
-    fish_sound_prepare_truncation(fs, packet->granulepos, packet->e_o_s);
+    fish_sound_prepare_truncation(fs, packet->granulepos, int(packet->e_o_s));
     fish_sound_decode(fs, packet->packet, packet->bytes);
 
     reader->m_bytesRead += packet->bytes;
 
     // The number of bytes read by this function is smaller than
     // the file size because of the packet headers
-    int p = lrint(double(reader->m_bytesRead) * 114 /
-                  double(reader->m_fileSize));
+    int p = int(lrint(double(reader->m_bytesRead) * 114 /
+                      double(reader->m_fileSize)));
     if (p > 99) p = 99;
     reader->m_completion = p;
     reader->progress(p);
@@ -172,11 +173,11 @@
 
     if (!reader->m_commentsRead) {
         const FishSoundComment *comment;
-        comment = fish_sound_comment_first_byname(fs, "TITLE");
+        comment = fish_sound_comment_first_byname(fs, (char *)"TITLE");
         if (comment && comment->value) {
             reader->m_title = QString::fromUtf8(comment->value);
         }
-        comment = fish_sound_comment_first_byname(fs, "ARTIST");
+        comment = fish_sound_comment_first_byname(fs, (char *)"ARTIST");
         if (comment && comment->value) {
             reader->m_maker = QString::fromUtf8(comment->value);
         }
--- a/data/fileio/OggVorbisFileReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/OggVorbisFileReader.h	Wed Apr 20 12:06:28 2016 +0100
@@ -34,16 +34,12 @@
     Q_OBJECT
 
 public:
-    enum DecodeMode {
-        DecodeAtOnce, // decode the file on construction, with progress 
-        DecodeThreaded // decode in a background thread after construction
-    };
-
     OggVorbisFileReader(FileSource source,
                         DecodeMode decodeMode,
                         CacheMode cacheMode,
-                        size_t targetRate = 0,
-                        ProgressReporter *reporter = 0);
+                        sv_samplerate_t targetRate = 0,
+                        bool normalised = false,
+                        ProgressReporter *reporter = nullptr);
     virtual ~OggVorbisFileReader();
 
     virtual QString getError() const { return m_error; }
@@ -78,8 +74,8 @@
     OGGZ *m_oggz;
     FishSound *m_fishSound;
     ProgressReporter *m_reporter;
-    size_t m_fileSize;
-    size_t m_bytesRead;
+    sv_frame_t m_fileSize;
+    sv_frame_t m_bytesRead;
     bool m_commentsRead;
     bool m_cancelled;
     int m_completion;
--- a/data/fileio/QuickTimeFileReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/QuickTimeFileReader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -43,16 +43,17 @@
     OSErr                        err; 
     AudioStreamBasicDescription  asbd;
     Movie                        movie;
-    size_t                       blockSize;
+    int                          blockSize;
 };
 
 
 QuickTimeFileReader::QuickTimeFileReader(FileSource source,
                                          DecodeMode decodeMode,
                                          CacheMode mode,
-                                         size_t targetRate,
+                                         sv_samplerate_t targetRate,
+                                         bool normalised,
                                          ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate),
+    CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_d(new D),
--- a/data/fileio/QuickTimeFileReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/QuickTimeFileReader.h	Wed Apr 20 12:06:28 2016 +0100
@@ -42,7 +42,8 @@
     QuickTimeFileReader(FileSource source,
                         DecodeMode decodeMode,
                         CacheMode cacheMode,
-                        size_t targetRate = 0,
+                        sv_samplerate_t targetRate = 0,
+                        bool normalised = false,
                         ProgressReporter *reporter = 0);
     virtual ~QuickTimeFileReader();
 
--- a/data/fileio/WavFileReader.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/WavFileReader.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -20,13 +20,13 @@
 #include <QMutexLocker>
 #include <QFileInfo>
 
+using namespace std;
+
 WavFileReader::WavFileReader(FileSource source, bool fileUpdating) :
     m_file(0),
     m_source(source),
     m_path(source.getLocalFilename()),
     m_seekable(false),
-    m_buffer(0),
-    m_bufsiz(0),
     m_lastStart(0),
     m_lastCount(0),
     m_updating(fileUpdating)
@@ -62,26 +62,31 @@
 
         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()
 {
     if (m_file) sf_close(m_file);
-    delete[] m_buffer;
 }
 
 void
@@ -89,7 +94,7 @@
 {
     QMutexLocker locker(&m_mutex);
 
-    size_t prevCount = m_fileInfo.frames;
+    sv_frame_t prevCount = m_fileInfo.frames;
 
     if (m_file) {
         sf_close(m_file);
@@ -122,69 +127,56 @@
     m_updating = false;
 }
 
-void
-WavFileReader::getInterleavedFrames(size_t start, size_t count,
-				    SampleBlock &results) const
+vector<float>
+WavFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
 {
-    if (count == 0) return;
-    results.clear();
-    results.reserve(count * m_fileInfo.channels);
+    if (count == 0) return {};
 
     QMutexLocker locker(&m_mutex);
 
     if (!m_file || !m_channelCount) {
-        return;
+        return {};
     }
 
-    if ((long)start >= m_fileInfo.frames) {
+    if (start >= m_fileInfo.frames) {
 //        SVDEBUG << "WavFileReader::getInterleavedFrames: " << start
 //                  << " > " << m_fileInfo.frames << endl;
-	return;
+	return {};
     }
 
-    if (long(start + count) > m_fileInfo.frames) {
+    if (start + count > m_fileInfo.frames) {
 	count = m_fileInfo.frames - start;
     }
 
-    sf_count_t readCount = 0;
-
-    if (start != m_lastStart || count != m_lastCount) {
-
-	if (sf_seek(m_file, start, SEEK_SET) < 0) {
-//            cerr << "sf_seek failed" << endl;
-	    return;
-	}
-	
-	if (count * m_fileInfo.channels > m_bufsiz) {
-//	    cerr << "WavFileReader: Reallocating buffer for " << count
-//		      << " frames, " << m_fileInfo.channels << " channels: "
-//		      << m_bufsiz << " floats" << endl;
-	    m_bufsiz = count * m_fileInfo.channels;
-	    delete[] m_buffer;
-	    m_buffer = new float[m_bufsiz];
-	}
-	
-	if ((readCount = sf_readf_float(m_file, m_buffer, count)) < 0) {
-//            cerr << "sf_readf_float failed" << endl;
-	    return;
-	}
-
-	m_lastStart = start;
-	m_lastCount = readCount;
+    // Because WaveFileModel::getSummaries() is called separately for
+    // individual channels, it's quite common for us to be called
+    // repeatedly for the same data. So this is worth cacheing.
+    if (start == m_lastStart && count == m_lastCount) {
+        return m_buffer;
+    }
+    
+    if (sf_seek(m_file, start, SEEK_SET) < 0) {
+        return {};
     }
 
-    for (size_t i = 0; i < count * m_fileInfo.channels; ++i) {
-        if (i >= m_bufsiz) {
-            cerr << "INTERNAL ERROR: WavFileReader::getInterleavedFrames: " << i << " >= " << m_bufsiz << endl;
-        }
-	results.push_back(m_buffer[i]);
+    vector<float> data;
+    sv_frame_t n = count * m_fileInfo.channels;
+    data.resize(n);
+
+    m_lastStart = start;
+    m_lastCount = count;
+    
+    sf_count_t readCount = 0;
+    if ((readCount = sf_readf_float(m_file, data.data(), count)) < 0) {
+        return {};
     }
 
-    return;
+    m_buffer = data;
+    return data;
 }
 
 void
-WavFileReader::getSupportedExtensions(std::set<QString> &extensions)
+WavFileReader::getSupportedExtensions(set<QString> &extensions)
 {
     int count;
 
@@ -215,7 +207,7 @@
 bool
 WavFileReader::supportsExtension(QString extension)
 {
-    std::set<QString> extensions;
+    set<QString> extensions;
     getSupportedExtensions(extensions);
     return (extensions.find(extension.toLower()) != extensions.end());
 }
--- a/data/fileio/WavFileReader.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/WavFileReader.h	Wed Apr 20 12:06:28 2016 +0100
@@ -42,14 +42,15 @@
     virtual QString getLocation() const { return m_source.getLocation(); }
     virtual QString getError() const { return m_error; }
 
+    virtual QString getLocalFilename() const { return m_path; }
+    
     virtual bool isQuicklySeekable() const { return m_seekable; }
     
     /** 
      * Must be safe to call from multiple threads with different
      * arguments on the same object at the same time.
      */
-    virtual void getInterleavedFrames(size_t start, size_t count,
-				      SampleBlock &frames) const;
+    virtual std::vector<float> getInterleavedFrames(sv_frame_t start, sv_frame_t count) const;
     
     static void getSupportedExtensions(std::set<QString> &extensions);
     static bool supportsExtension(QString ext);
@@ -74,10 +75,9 @@
     bool m_seekable;
 
     mutable QMutex m_mutex;
-    mutable float *m_buffer;
-    mutable size_t m_bufsiz;
-    mutable size_t m_lastStart;
-    mutable size_t m_lastCount;
+    mutable std::vector<float> m_buffer;
+    mutable sv_frame_t m_lastStart;
+    mutable sv_frame_t m_lastCount;
 
     bool m_updating;
 };
--- a/data/fileio/WavFileWriter.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/WavFileWriter.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -23,10 +23,13 @@
 #include <QFileInfo>
 
 #include <iostream>
+#include <cmath>
+
+using namespace std;
 
 WavFileWriter::WavFileWriter(QString path,
-			     size_t sampleRate,
-                             size_t channels,
+			     sv_samplerate_t sampleRate,
+                             int channels,
                              FileWriteMode mode) :
     m_path(path),
     m_sampleRate(sampleRate),
@@ -35,7 +38,14 @@
     m_file(0)
 {
     SF_INFO fileInfo;
-    fileInfo.samplerate = m_sampleRate;
+
+    int fileRate = int(round(m_sampleRate));
+    if (m_sampleRate != sv_samplerate_t(fileRate)) {
+        cerr << "WavFileWriter: WARNING: Non-integer sample rate "
+             << m_sampleRate << " presented, rounding to " << fileRate
+             << endl;
+    }
+    fileInfo.samplerate = fileRate;
     fileInfo.channels = m_channels;
     fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
 
@@ -120,28 +130,27 @@
         ownSelection = true;
     }
 
-    size_t bs = 2048;
-    float *ub = new float[bs]; // uninterleaved buffer (one channel)
-    float *ib = new float[bs * m_channels]; // interleaved buffer
+    sv_frame_t bs = 2048;
 
     for (MultiSelection::SelectionList::iterator i =
 	     selection->getSelections().begin();
 	 i != selection->getSelections().end(); ++i) {
 	
-	size_t f0(i->getStartFrame()), f1(i->getEndFrame());
+	sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame());
 
-	for (size_t f = f0; f < f1; f += bs) {
+	for (sv_frame_t f = f0; f < f1; f += bs) {
 	    
-	    size_t n = std::min(bs, f1 - f);
+	    sv_frame_t n = min(bs, f1 - f);
+            vector<float> interleaved(n * m_channels, 0.f);
 
 	    for (int c = 0; c < int(m_channels); ++c) {
-		source->getData(c, f, n, ub);
-		for (size_t i = 0; i < n; ++i) {
-		    ib[i * m_channels + c] = ub[i];
+                vector<float> chanbuf = source->getData(c, f, n);
+		for (int i = 0; in_range_for(chanbuf, i); ++i) {
+		    interleaved[i * m_channels + c] = chanbuf[i];
 		}
 	    }	    
 
-	    sf_count_t written = sf_writef_float(m_file, ib, n);
+	    sf_count_t written = sf_writef_float(m_file, interleaved.data(), n);
 
 	    if (written < n) {
 		m_error = QString("Only wrote %1 of %2 frames at file frame %3")
@@ -151,15 +160,13 @@
 	}
     }
 
-    delete[] ub;
-    delete[] ib;
     if (ownSelection) delete selection;
 
     return isOK();
 }
 	
 bool
-WavFileWriter::writeSamples(float **samples, size_t count)
+WavFileWriter::writeSamples(float **samples, sv_frame_t count)
 {
     if (!m_file) {
         m_error = QString("Failed to write model to audio file '%1': File not open")
@@ -168,13 +175,13 @@
     }
 
     float *b = new float[count * m_channels];
-    for (size_t i = 0; i < count; ++i) {
-        for (size_t c = 0; c < m_channels; ++c) {
+    for (sv_frame_t i = 0; i < count; ++i) {
+        for (int c = 0; c < int(m_channels); ++c) {
             b[i * m_channels + c] = samples[c][i];
         }
     }
 
-    sf_count_t written = sf_writef_float(m_file, b, count);
+    sv_frame_t written = sf_writef_float(m_file, b, count);
 
     delete[] b;
 
--- a/data/fileio/WavFileWriter.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/WavFileWriter.h	Wed Apr 20 12:06:28 2016 +0100
@@ -20,6 +20,8 @@
 
 #include <sndfile.h>
 
+#include "base/BaseTypes.h"
+
 class DenseTimeValueModel;
 class MultiSelection;
 class TempWriteFile;
@@ -44,7 +46,7 @@
         WriteToTarget
     };
 
-    WavFileWriter(QString path, size_t sampleRate, size_t channels,
+    WavFileWriter(QString path, sv_samplerate_t sampleRate, int channels,
                   FileWriteMode mode);
     virtual ~WavFileWriter();
 
@@ -57,14 +59,14 @@
     bool writeModel(DenseTimeValueModel *source,
                     MultiSelection *selection = 0);
 
-    bool writeSamples(float **samples, size_t count); // count per channel
+    bool writeSamples(float **samples, sv_frame_t count); // count per channel
 
     bool close();
 
 protected:
     QString m_path;
-    size_t m_sampleRate;
-    size_t m_channels;
+    sv_samplerate_t m_sampleRate;
+    int m_channels;
     TempWriteFile *m_temp;
     SNDFILE *m_file;
     QString m_error;
--- a/data/fileio/test/AudioFileReaderTest.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/test/AudioFileReaderTest.h	Wed Apr 20 12:06:28 2016 +0100
@@ -63,7 +63,7 @@
     {
         QFETCH(QString, audiofile);
 
-        int readRate = 48000;
+        sv_samplerate_t readRate = 48000;
 
 	AudioFileReader *reader =
 	    AudioFileReaderFactory::createReader
@@ -72,7 +72,7 @@
         QStringList fileAndExt = audiofile.split(".");
         QStringList bits = fileAndExt[0].split("-");
         QString extension = fileAndExt[1];
-        int nominalRate = bits[0].toInt();
+        sv_samplerate_t nominalRate = bits[0].toInt();
         int nominalChannels = bits[1].toInt();
         int nominalDepth = 16;
         if (bits.length() > 2) nominalDepth = bits[2].toInt();
@@ -86,25 +86,22 @@
 	}
 
         QCOMPARE((int)reader->getChannelCount(), nominalChannels);
-        QCOMPARE((int)reader->getNativeRate(), nominalRate);
-        QCOMPARE((int)reader->getSampleRate(), readRate);
+        QCOMPARE(reader->getNativeRate(), nominalRate);
+        QCOMPARE(reader->getSampleRate(), readRate);
 
 	int channels = reader->getChannelCount();
 	AudioTestData tdata(readRate, channels);
 	
 	float *reference = tdata.getInterleavedData();
-        int refFrames = tdata.getFrameCount();
-	int refsize = refFrames * channels;
-	
-	vector<float> test;
+        sv_frame_t refFrames = tdata.getFrameCount();
 	
 	// The reader should give us exactly the expected number of
 	// frames, except for mp3/aac files. We ask for quite a lot
 	// more, though, so we can (a) check that we only get the
 	// expected number back (if this is not mp3/aac) or (b) take
 	// into account silence at beginning and end (if it is).
-	reader->getInterleavedFrames(0, refFrames + 5000, test);
-	int read = test.size() / channels;
+	vector<float> test = reader->getInterleavedFrames(0, refFrames + 5000);
+	sv_frame_t read = test.size() / channels;
 
         if (extension == "mp3" || extension == "aac" || extension == "m4a") {
             // mp3s and aacs can have silence at start and end
@@ -117,8 +114,8 @@
         // or resampler quality here, just whether the results are
         // plainly wrong (e.g. at wrong samplerate or with an offset)
 
-	float limit = 0.01;
-        float edgeLimit = limit * 10; // in first or final edgeSize frames
+	double limit = 0.01;
+        double edgeLimit = limit * 10; // in first or final edgeSize frames
         int edgeSize = 100; 
 
         if (nominalDepth < 16) {
@@ -131,23 +128,23 @@
         }
 
         // And we ignore completely the last few frames when upsampling
-        int discard = 1 + readRate / nominalRate;
+        int discard = 1 + int(round(readRate / nominalRate));
 
         int offset = 0;
 
         if (extension == "aac" || extension == "m4a") {
             // our m4a file appears to have a fixed offset of 1024 (at
             // file sample rate)
-            offset = (1024 / float(nominalRate)) * readRate;
+            offset = int(round((1024 / nominalRate) * readRate));
         }
 
         if (extension == "mp3") {
             // while mp3s appear to vary
             for (int i = 0; i < read; ++i) {
                 bool any = false;
-                float thresh = 0.01;
+                double thresh = 0.01;
                 for (int c = 0; c < channels; ++c) {
-                    if (fabsf(test[i * channels + c]) > thresh) {
+                    if (fabs(test[i * channels + c]) > thresh) {
                         any = true;
                         break;
                     }
@@ -170,7 +167,7 @@
 		totdiff += diff;
                 // in edge areas, record this only if it exceeds edgeLimit
                 if (i < edgeSize || i + edgeSize >= read - offset) {
-                    if (diff > edgeLimit) {
+                    if (diff > edgeLimit && diff > maxdiff) {
                         maxdiff = diff;
                         maxAt = i;
                     }
@@ -181,7 +178,7 @@
                     }
 		}
 	    }
-	    float meandiff = totdiff / read;
+	    float meandiff = totdiff / float(read);
 //	    cerr << "meandiff on channel " << c << ": " << meandiff << endl;
 //	    cerr << "maxdiff on channel " << c << ": " << maxdiff << " at " << maxAt << endl;
             if (meandiff >= limit) {
--- a/data/fileio/test/AudioTestData.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/test/AudioTestData.h	Wed Apr 20 12:06:28 2016 +0100
@@ -18,6 +18,8 @@
 
 #include <cmath>
 
+#include "base/BaseTypes.h"
+
 /**
  * Class that generates a single fixed test pattern to a given sample
  * rate and number of channels.
@@ -35,7 +37,7 @@
 class AudioTestData
 {
 public:
-    AudioTestData(float rate, int channels) :
+    AudioTestData(double rate, int channels) :
 	m_channelCount(channels),
 	m_duration(2.0),
 	m_sampleRate(rate),
@@ -54,24 +56,25 @@
 
     void generate() {
 
-	float hpw = m_pulseWidth / 2.0;
+	double hpw = m_pulseWidth / 2.0;
 
 	for (int i = 0; i < m_frameCount; ++i) {
 	    for (int c = 0; c < m_channelCount; ++c) {
 
-		float s = 0.f;
+		double s = 0.0;
 
 		if (c == 0) {
 
-		    float phase = (i * m_sinFreq * 2.f * M_PI) / m_sampleRate;
-		    s = sinf(phase);
+		    double phase = (i * m_sinFreq * 2.0 * M_PI) / m_sampleRate;
+		    s = sin(phase);
 
 		} else if (c == 1) {
 
 		    int pulseNo = int((i * m_pulseFreq) / m_sampleRate);
-		    int index = (i * m_pulseFreq) - (m_sampleRate * pulseNo);
+		    int index = int(round((i * m_pulseFreq) -
+                                          (m_sampleRate * pulseNo)));
 		    if (index < m_pulseWidth) {
-			s = 1.0 - fabsf(hpw - index) / hpw;
+			s = 1.0 - fabs(hpw - index) / hpw;
 			if (pulseNo % 2) s = -s;
 		    }
 
@@ -80,7 +83,7 @@
 		    s = c / 20.0;
 		}
 
-		m_data[i * m_channelCount + c] = s;
+		m_data[i * m_channelCount + c] = float(s);
 	    }
 	}
     }
@@ -89,7 +92,7 @@
 	return m_data;
     }
 
-    int getFrameCount() const { 
+    sv_frame_t getFrameCount() const { 
 	return m_frameCount;
     }
 
@@ -97,23 +100,23 @@
 	return m_channelCount;
     }
 
-    float getSampleRate () const {
+    sv_samplerate_t getSampleRate () const {
 	return m_sampleRate;
     }
 
-    float getDuration() const { // seconds
+    double getDuration() const { // seconds
 	return m_duration;
     }
 
 private:
     float *m_data;
-    int m_frameCount;
+    sv_frame_t m_frameCount;
     int m_channelCount;
-    float m_duration;
-    float m_sampleRate;
-    float m_sinFreq;
-    float m_pulseFreq;
-    float m_pulseWidth;
+    double m_duration;
+    sv_samplerate_t m_sampleRate;
+    double m_sinFreq;
+    double m_pulseFreq;
+    double m_pulseWidth;
 };
 
 #endif
--- a/data/fileio/test/main.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/test/main.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -1,5 +1,16 @@
 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-/* Copyright Chris Cannam - All Rights Reserved */
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2013 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.
+*/
 
 #include "AudioFileReaderTest.h"
 
--- a/data/fileio/test/test.pro	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/fileio/test/test.pro	Wed Apr 20 12:06:28 2016 +0100
@@ -7,23 +7,45 @@
     INCLUDEPATH += ../../../../sv-dependency-builds/win32-mingw/include
     LIBS += -L../../../../sv-dependency-builds/win32-mingw/lib
 }
+win32-msvc* {
+    INCLUDEPATH += ../../../../sv-dependency-builds/win32-msvc/include
+    LIBS += -L../../../../sv-dependency-builds/win32-msvc/lib
+}
+mac* {
+    INCLUDEPATH += ../../../../sv-dependency-builds/osx/include
+    LIBS += -L../../../../sv-dependency-builds/osx/lib
+}
 
 exists(../../../config.pri) {
     include(../../../config.pri)
 }
 
-win* {
-    !exists(../../../config.pri) {
-        DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_RUBBERBAND HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG HAVE_PORTAUDIO_2_0
-        LIBS += -lbz2 -lrubberband -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -llo -lz -lsord-0 -lserd-0 -lwinmm -lws2_32
+!exists(../../../config.pri) {
+
+    CONFIG += release
+    DEFINES += NDEBUG BUILD_RELEASE NO_TIMING
+
+    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 -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0
+
+    win* {
+        LIBS += -llo -lwinmm -lws2_32
+    }
+    macx* {
+        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
+CONFIG += qt thread warn_on stl rtti exceptions console c++11
 QT += network xml testlib
 QT -= gui
 
-TARGET = svcore-test
+TARGET = svcore-data-fileio-test
 
 DEPENDPATH += ../../..
 INCLUDEPATH += ../../..
--- a/data/midi/MIDIEvent.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/midi/MIDIEvent.h	Wed Apr 20 12:06:28 2016 +0100
@@ -25,6 +25,8 @@
 #include <QString>
 #include <string>
 #include <iostream>
+#include <stdexcept>
+
 #include "base/Debug.h"
 
 typedef unsigned char MIDIByte;
@@ -118,16 +120,22 @@
 {
 public:
     MIDIEvent(unsigned long deltaTime,
-              MIDIByte eventCode,
-              MIDIByte data1 = 0,
-              MIDIByte data2 = 0) :
+              int eventCode,
+              int data1 = 0,
+              int data2 = 0) :
 	m_deltaTime(deltaTime),
 	m_duration(0),
-	m_eventCode(eventCode),
-	m_data1(data1),
-	m_data2(data2),
 	m_metaEventCode(0)
-    { }
+    {
+        if (eventCode < 0 || eventCode > 0xff ||
+            data1 < 0 || data1 > 0xff ||
+            data2 < 0 || data2 > 0xff) {
+            throw std::domain_error("not all args within byte range");
+        }
+        m_eventCode = MIDIByte(eventCode);
+        m_data1 = MIDIByte(data1);
+        m_data2 = MIDIByte(data2);
+    }
 
     MIDIEvent(unsigned long deltaTime,
               MIDIByte eventCode,
--- a/data/midi/MIDIInput.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/midi/MIDIInput.h	Wed Apr 20 12:06:28 2016 +0100
@@ -36,7 +36,7 @@
     bool isOK() const { return m_rtmidi != 0; }
 
     bool isEmpty() const { return getEventsAvailable() == 0; }
-    size_t getEventsAvailable() const { return m_buffer.getReadSpace(); }
+    int getEventsAvailable() const { return m_buffer.getReadSpace(); }
     MIDIEvent readEvent();
 
 signals:
--- a/data/midi/rtmidi/RtMidi.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/midi/rtmidi/RtMidi.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -835,7 +835,7 @@
       if ( data->firstMessage == true )
         data->firstMessage = false;
       else
-        message.timeStamp = time * 0.000001;
+	  message.timeStamp = double(time) * 0.000001;
     }
 
     snd_seq_free_event(ev);
@@ -947,9 +947,9 @@
 
 
   snd_seq_addr_t sender, receiver;
-  sender.client = snd_seq_port_info_get_client( pinfo );
-  sender.port = snd_seq_port_info_get_port( pinfo );
-  receiver.client = snd_seq_client_id( data->seq );
+  sender.client = (unsigned char)snd_seq_port_info_get_client( pinfo );
+  sender.port = (unsigned char)snd_seq_port_info_get_port( pinfo );
+  receiver.client = (unsigned char)snd_seq_client_id( data->seq );
   if ( data->vport < 0 ) {
     snd_seq_port_info_set_client( pinfo, 0 );
     snd_seq_port_info_set_port( pinfo, 0 );
@@ -972,7 +972,7 @@
     }
   }
 
-  receiver.port = data->vport;
+  receiver.port = (unsigned char)data->vport;
 
   // Make subscription
   snd_seq_port_subscribe_malloc( &data->subscription );
@@ -1222,9 +1222,9 @@
   }
 
   snd_seq_addr_t sender, receiver;
-  receiver.client = snd_seq_port_info_get_client( pinfo );
-  receiver.port = snd_seq_port_info_get_port( pinfo );
-  sender.client = snd_seq_client_id( data->seq );
+  receiver.client = (unsigned char)snd_seq_port_info_get_client( pinfo );
+  receiver.port = (unsigned char)snd_seq_port_info_get_port( pinfo );
+  sender.client = (unsigned char)snd_seq_client_id( data->seq );
 
   if ( data->vport < 0 ) {
     data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
@@ -1236,7 +1236,7 @@
     }
   }
 
-  sender.port = data->vport;
+  sender.port = (unsigned char)data->vport;
 
   // Make subscription
   snd_seq_port_subscribe_malloc( &data->subscription );
@@ -1295,7 +1295,7 @@
 {
   int result;
   AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
-  unsigned int nBytes = message->size();
+  unsigned int nBytes = (unsigned int) message->size();
   if ( nBytes > data->bufferSize ) {
     data->bufferSize = nBytes;
     result = snd_midi_event_resize_buffer ( data->coder, nBytes);
@@ -1313,11 +1313,11 @@
 
   snd_seq_event_t ev;
   snd_seq_ev_clear(&ev);
-  snd_seq_ev_set_source(&ev, data->vport);
+  snd_seq_ev_set_source(&ev, (unsigned char) data->vport);
   snd_seq_ev_set_subs(&ev);
   snd_seq_ev_set_direct(&ev);
   for ( unsigned int i=0; i<nBytes; i++ ) data->buffer[i] = message->at(i);
-  result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev );
+  result = (int) snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev );
   if ( result < (int)nBytes ) {
     errorString_ = "RtMidiOut::sendMessage: event parsing error!";
     error( RtError::WARNING );
@@ -2263,5 +2263,5 @@
 {
 }
 
-#endif __RTMIDI_DUMMY_ONLY__
+#endif /* __RTMIDI_DUMMY_ONLY__ */
 
--- a/data/model/AggregateWaveModel.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/AggregateWaveModel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -19,6 +19,8 @@
 
 #include <QTextStream>
 
+using namespace std;
+
 PowerOfSqrtTwoZoomConstraint
 AggregateWaveModel::m_zoomConstraint;
 
@@ -65,161 +67,114 @@
     return ready;
 }
 
-size_t
+sv_frame_t
 AggregateWaveModel::getFrameCount() const
 {
-    size_t count = 0;
+    sv_frame_t count = 0;
 
     for (ChannelSpecList::const_iterator i = m_components.begin();
          i != m_components.end(); ++i) {
-        size_t thisCount = i->model->getEndFrame() - i->model->getStartFrame();
+        sv_frame_t thisCount = i->model->getEndFrame() - i->model->getStartFrame();
         if (thisCount > count) count = thisCount;
     }
 
     return count;
 }
 
-size_t
+int
 AggregateWaveModel::getChannelCount() const
 {
-    return m_components.size();
+    return int(m_components.size());
 }
 
-size_t
+sv_samplerate_t
 AggregateWaveModel::getSampleRate() const
 {
     if (m_components.empty()) return 0;
     return m_components.begin()->model->getSampleRate();
 }
 
-Model *
-AggregateWaveModel::clone() const
-{
-    return new AggregateWaveModel(m_components);
-}
-
-size_t
-AggregateWaveModel::getData(int channel, size_t start, size_t count,
-                            float *buffer) const
+vector<float>
+AggregateWaveModel::getData(int channel, sv_frame_t start, sv_frame_t count) const
 {
     int ch0 = channel, ch1 = channel;
-    bool mixing = false;
     if (channel == -1) {
         ch0 = 0;
         ch1 = getChannelCount()-1;
-        mixing = true;
     }
 
-    float *readbuf = buffer;
-    if (mixing) {
-        readbuf = new float[count];
-        for (size_t i = 0; i < count; ++i) {
-            buffer[i] = 0.f;
+    vector<float> result(count, 0.f);
+
+    sv_frame_t longest = 0;
+    
+    for (int c = ch0; c <= ch1; ++c) {
+
+        auto here = m_components[c].model->getData(m_components[c].channel,
+                                                   start, count);
+        if (sv_frame_t(here.size()) > longest) {
+            longest = sv_frame_t(here.size());
+        }
+        for (sv_frame_t i = 0; in_range_for(here, i); ++i) {
+            result[i] += here[i];
         }
     }
 
-    size_t sz = count;
+    result.resize(longest);
+    return result;
+}
 
-    for (int c = ch0; c <= ch1; ++c) {
-        size_t szHere = 
-            m_components[c].model->getData(m_components[c].channel,
-                                           start, count,
-                                           readbuf);
-        if (szHere < sz) sz = szHere;
-        if (mixing) {
-            for (size_t i = 0; i < count; ++i) {
-                buffer[i] += readbuf[i];
-            }
+vector<vector<float>>
+AggregateWaveModel::getMultiChannelData(int fromchannel, int tochannel,
+                                        sv_frame_t start, sv_frame_t count) const
+{
+    sv_frame_t min = count;
+
+    vector<vector<float>> result;
+
+    for (int c = fromchannel; c <= tochannel; ++c) {
+        auto here = getData(c, start, count);
+        if (sv_frame_t(here.size()) < min) {
+            min = sv_frame_t(here.size());
         }
+        result.push_back(here);
     }
 
-    if (mixing) delete[] readbuf;
-    return sz;
-}
-         
-size_t
-AggregateWaveModel::getData(int channel, size_t start, size_t count,
-                            double *buffer) const
-{
-    int ch0 = channel, ch1 = channel;
-    bool mixing = false;
-    if (channel == -1) {
-        ch0 = 0;
-        ch1 = getChannelCount()-1;
-        mixing = true;
-    }
-
-    double *readbuf = buffer;
-    if (mixing) {
-        readbuf = new double[count];
-        for (size_t i = 0; i < count; ++i) {
-            buffer[i] = 0.0;
-        }
-    }
-
-    size_t sz = count;
-    
-    for (int c = ch0; c <= ch1; ++c) {
-        size_t szHere = 
-            m_components[c].model->getData(m_components[c].channel,
-                                           start, count,
-                                           readbuf);
-        if (szHere < sz) sz = szHere;
-        if (mixing) {
-            for (size_t i = 0; i < count; ++i) {
-                buffer[i] += readbuf[i];
-            }
-        }
+    if (min < count) {
+        for (auto &v : result) v.resize(min);
     }
     
-    if (mixing) delete[] readbuf;
-    return sz;
+    return result;
 }
 
-size_t
-AggregateWaveModel::getData(size_t fromchannel, size_t tochannel,
-                            size_t start, size_t count,
-                            float **buffer) const
-{
-    size_t min = count;
-
-    for (size_t c = fromchannel; c <= tochannel; ++c) {
-        size_t here = getData(c, start, count, buffer[c - fromchannel]);
-        if (here < min) min = here;
-    }
-    
-    return min;
-}
-
-size_t
-AggregateWaveModel::getSummaryBlockSize(size_t desired) const
+int
+AggregateWaveModel::getSummaryBlockSize(int desired) const
 {
     //!!! complete
     return desired;
 }
         
 void
-AggregateWaveModel::getSummaries(size_t channel, size_t start, size_t count,
-                                 RangeBlock &ranges, size_t &blockSize) const
+AggregateWaveModel::getSummaries(int, sv_frame_t, sv_frame_t,
+                                 RangeBlock &, int &) const
 {
     //!!! complete
 }
 
 AggregateWaveModel::Range
-AggregateWaveModel::getSummary(size_t channel, size_t start, size_t count) const
+AggregateWaveModel::getSummary(int, sv_frame_t, sv_frame_t) const
 {
     //!!! complete
     return Range();
 }
         
-size_t
+int
 AggregateWaveModel::getComponentCount() const
 {
-    return m_components.size();
+    return int(m_components.size());
 }
 
 AggregateWaveModel::ModelChannelSpec
-AggregateWaveModel::getComponent(size_t c) const
+AggregateWaveModel::getComponent(int c) const
 {
     return m_components[c];
 }
@@ -231,9 +186,9 @@
 }
 
 void
-AggregateWaveModel::componentModelChanged(size_t start, size_t end)
+AggregateWaveModel::componentModelChangedWithin(sv_frame_t start, sv_frame_t end)
 {
-    emit modelChanged(start, end);
+    emit modelChangedWithin(start, end);
 }
 
 void
@@ -243,9 +198,9 @@
 }
 
 void
-AggregateWaveModel::toXml(QTextStream &out,
-                          QString indent,
-                          QString extraAttributes) const
+AggregateWaveModel::toXml(QTextStream &,
+                          QString ,
+                          QString ) const
 {
     //!!! complete
 }
--- a/data/model/AggregateWaveModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/AggregateWaveModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -44,40 +44,32 @@
 
     QString getTypeName() const { return tr("Aggregate Wave"); }
 
-    size_t getComponentCount() const;
-    ModelChannelSpec getComponent(size_t c) const;
+    int getComponentCount() const;
+    ModelChannelSpec getComponent(int c) const;
 
     const ZoomConstraint *getZoomConstraint() const { return &m_zoomConstraint; }
 
-    size_t getFrameCount() const;
-    size_t getChannelCount() const;
-    size_t getSampleRate() const;
-
-    virtual Model *clone() const;
+    sv_frame_t getFrameCount() const;
+    int getChannelCount() const;
+    sv_samplerate_t getSampleRate() const;
 
     float getValueMinimum() const { return -1.0f; }
     float getValueMaximum() const { return  1.0f; }
 
-    virtual size_t getStartFrame() const { return 0; }
-    virtual size_t getEndFrame() const { return getFrameCount(); }
+    virtual sv_frame_t getStartFrame() const { return 0; }
+    virtual sv_frame_t getEndFrame() const { return getFrameCount(); }
 
-    virtual size_t getData(int channel, size_t start, size_t count,
-                           float *buffer) const;
+    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const;
 
-    virtual size_t getData(int channel, size_t start, size_t count,
-                           double *buffer) const;
+    virtual std::vector<std::vector<float>> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const;
 
-    virtual size_t getData(size_t fromchannel, size_t tochannel,
-                           size_t start, size_t count,
-                           float **buffer) const;
+    virtual int getSummaryBlockSize(int desired) const;
 
-    virtual size_t getSummaryBlockSize(size_t desired) const;
+    virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count,
+                              RangeBlock &ranges,
+                              int &blockSize) const;
 
-    virtual void getSummaries(size_t channel, size_t start, size_t count,
-                              RangeBlock &ranges,
-                              size_t &blockSize) const;
-
-    virtual Range getSummary(size_t channel, size_t start, size_t count) const;
+    virtual Range getSummary(int channel, sv_frame_t start, sv_frame_t count) const;
 
     virtual void toXml(QTextStream &out,
                        QString indent = "",
@@ -85,12 +77,12 @@
 
 signals:
     void modelChanged();
-    void modelChanged(size_t, size_t);
+    void modelChangedWithin(sv_frame_t, sv_frame_t);
     void completionChanged();
 
 protected slots:
     void componentModelChanged();
-    void componentModelChanged(size_t, size_t);
+    void componentModelChangedWithin(sv_frame_t, sv_frame_t);
     void componentModelCompletionChanged();
 
 protected:
--- a/data/model/AlignmentModel.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/AlignmentModel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -37,8 +37,8 @@
         connect(m_rawPath, SIGNAL(modelChanged()),
                 this, SLOT(pathChanged()));
 
-        connect(m_rawPath, SIGNAL(modelChanged(size_t, size_t)),
-                this, SLOT(pathChanged(size_t, size_t)));
+        connect(m_rawPath, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+                this, SLOT(pathChangedWithin(sv_frame_t, sv_frame_t)));
         
         connect(m_rawPath, SIGNAL(completionChanged()),
                 this, SLOT(pathCompletionChanged()));
@@ -74,37 +74,28 @@
     else return true;
 }
 
-size_t
+sv_frame_t
 AlignmentModel::getStartFrame() const
 {
-    size_t a = m_reference->getStartFrame();
-    size_t b = m_aligned->getStartFrame();
+    sv_frame_t a = m_reference->getStartFrame();
+    sv_frame_t b = m_aligned->getStartFrame();
     return std::min(a, b);
 }
 
-size_t
+sv_frame_t
 AlignmentModel::getEndFrame() const
 {
-    size_t a = m_reference->getEndFrame();
-    size_t b = m_aligned->getEndFrame();
+    sv_frame_t a = m_reference->getEndFrame();
+    sv_frame_t b = m_aligned->getEndFrame();
     return std::max(a, b);
 }
 
-size_t
+sv_samplerate_t
 AlignmentModel::getSampleRate() const
 {
     return m_reference->getSampleRate();
 }
 
-Model *
-AlignmentModel::clone() const
-{
-    return new AlignmentModel
-        (m_reference, m_aligned,
-         m_inputModel ? m_inputModel->clone() : 0,
-         m_rawPath ? static_cast<SparseTimeValueModel *>(m_rawPath->clone()) : 0);
-}
-
 bool
 AlignmentModel::isReady(int *completion) const
 {
@@ -112,10 +103,17 @@
         if (completion) *completion = 0;
         return false;
     }
-    if (m_pathComplete || !m_rawPath) {
+    if (m_pathComplete) {
         if (completion) *completion = 100;
         return true;
     }
+    if (!m_rawPath) {
+        // lack of raw path could mean path is complete (in which case
+        // m_pathComplete true above) or else no alignment has been
+        // set at all yet (this case)
+        if (completion) *completion = 0;
+        return false;
+    }
     return m_rawPath->isReady(completion);
 }
 
@@ -137,11 +135,11 @@
     return m_aligned;
 }
 
-size_t
-AlignmentModel::toReference(size_t frame) const
+sv_frame_t
+AlignmentModel::toReference(sv_frame_t frame) const
 {
 #ifdef DEBUG_ALIGNMENT_MODEL
-    SVDEBUG << "AlignmentModel::toReference(" << frame << ")" << endl;
+    cerr << "AlignmentModel::toReference(" << frame << ")" << endl;
 #endif
     if (!m_path) {
         if (!m_rawPath) return frame;
@@ -150,11 +148,11 @@
     return align(m_path, frame);
 }
 
-size_t
-AlignmentModel::fromReference(size_t frame) const
+sv_frame_t
+AlignmentModel::fromReference(sv_frame_t frame) const
 {
 #ifdef DEBUG_ALIGNMENT_MODEL
-    SVDEBUG << "AlignmentModel::fromReference(" << frame << ")" << endl;
+    cerr << "AlignmentModel::fromReference(" << frame << ")" << endl;
 #endif
     if (!m_reversePath) {
         if (!m_rawPath) return frame;
@@ -175,7 +173,7 @@
 }
 
 void
-AlignmentModel::pathChanged(size_t, size_t)
+AlignmentModel::pathChangedWithin(sv_frame_t, sv_frame_t)
 {
     if (!m_pathComplete) return;
     constructPath();
@@ -194,7 +192,7 @@
         m_rawPath->isReady(&completion);
 
 #ifdef DEBUG_ALIGNMENT_MODEL
-        SVDEBUG << "AlignmentModel::pathCompletionChanged: completion = "
+        cerr << "AlignmentModel::pathCompletionChanged: completion = "
                   << completion << endl;
 #endif
 
@@ -235,14 +233,14 @@
         
     for (SparseTimeValueModel::PointList::const_iterator i = points.begin();
          i != points.end(); ++i) {
-        long frame = i->frame;
-        float value = i->value;
-        long rframe = lrintf(value * m_aligned->getSampleRate());
+        sv_frame_t frame = i->frame;
+        double value = i->value;
+        sv_frame_t rframe = lrint(value * m_aligned->getSampleRate());
         m_path->addPoint(PathPoint(frame, rframe));
     }
 
 #ifdef DEBUG_ALIGNMENT_MODEL
-    SVDEBUG << "AlignmentModel::constructPath: " << m_path->getPointCount() << " points, at least " << (2 * m_path->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl;
+    cerr << "AlignmentModel::constructPath: " << m_path->getPointCount() << " points, at least " << (2 * m_path->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl;
 #endif
 }
 
@@ -267,18 +265,18 @@
         
     for (PathModel::PointList::const_iterator i = points.begin();
          i != points.end(); ++i) {
-        long frame = i->frame;
-        long rframe = i->mapframe;
+        sv_frame_t frame = i->frame;
+        sv_frame_t rframe = i->mapframe;
         m_reversePath->addPoint(PathPoint(rframe, frame));
     }
 
 #ifdef DEBUG_ALIGNMENT_MODEL
-    SVDEBUG << "AlignmentModel::constructReversePath: " << m_reversePath->getPointCount() << " points, at least " << (2 * m_reversePath->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl;
+    cerr << "AlignmentModel::constructReversePath: " << m_reversePath->getPointCount() << " points, at least " << (2 * m_reversePath->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl;
 #endif
 }
 
-size_t
-AlignmentModel::align(PathModel *path, size_t frame) const
+sv_frame_t
+AlignmentModel::align(PathModel *path, sv_frame_t frame) const
 {
     if (!path) return frame;
 
@@ -291,13 +289,13 @@
 
     if (points.empty()) {
 #ifdef DEBUG_ALIGNMENT_MODEL
-        SVDEBUG << "AlignmentModel::align: No points" << endl;
+        cerr << "AlignmentModel::align: No points" << endl;
 #endif
         return frame;
     }        
 
 #ifdef DEBUG_ALIGNMENT_MODEL
-    SVDEBUG << "AlignmentModel::align: frame " << frame << " requested" << endl;
+    cerr << "AlignmentModel::align: frame " << frame << " requested" << endl;
 #endif
 
     PathModel::Point point(frame);
@@ -308,13 +306,13 @@
 #endif
         --i;
     }
-    while (i != points.begin() && i->frame > long(frame)) --i;
+    while (i != points.begin() && i->frame > frame) --i;
 
-    long foundFrame = i->frame;
-    long foundMapFrame = i->mapframe;
+    sv_frame_t foundFrame = i->frame;
+    sv_frame_t foundMapFrame = i->mapframe;
 
-    long followingFrame = foundFrame;
-    long followingMapFrame = foundMapFrame;
+    sv_frame_t followingFrame = foundFrame;
+    sv_frame_t followingMapFrame = foundMapFrame;
 
     if (++i != points.end()) {
 #ifdef DEBUG_ALIGNMENT_MODEL
@@ -328,36 +326,67 @@
 #endif
     }        
 
+#ifdef DEBUG_ALIGNMENT_MODEL
+    cerr << "foundFrame = " << foundFrame << ", foundMapFrame = " << foundMapFrame
+         << ", followingFrame = " << followingFrame << ", followingMapFrame = "
+         << followingMapFrame << endl;
+#endif
+    
     if (foundMapFrame < 0) return 0;
 
-    size_t resultFrame = foundMapFrame;
+    sv_frame_t resultFrame = foundMapFrame;
 
-    if (followingFrame != foundFrame && long(frame) > foundFrame) {
-        float interp =
-            float(frame - foundFrame) /
-            float(followingFrame - foundFrame);
-        resultFrame += lrintf((followingMapFrame - foundMapFrame) * interp);
+    if (followingFrame != foundFrame && frame > foundFrame) {
+        double interp =
+            double(frame - foundFrame) /
+            double(followingFrame - foundFrame);
+        resultFrame += lrint(double(followingMapFrame - foundMapFrame) * interp);
     }
 
 #ifdef DEBUG_ALIGNMENT_MODEL
-    SVDEBUG << "AlignmentModel::align: resultFrame = " << resultFrame << endl;
+    cerr << "AlignmentModel::align: resultFrame = " << resultFrame << endl;
 #endif
 
     return resultFrame;
 }
 
 void
+AlignmentModel::setPathFrom(SparseTimeValueModel *rawpath)
+{
+    if (m_rawPath) m_rawPath->aboutToDelete();
+    delete m_rawPath;
+
+    m_rawPath = rawpath;
+
+    connect(m_rawPath, SIGNAL(modelChanged()),
+            this, SLOT(pathChanged()));
+
+    connect(m_rawPath, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+            this, SLOT(pathChangedWithin(sv_frame_t, sv_frame_t)));
+        
+    connect(m_rawPath, SIGNAL(completionChanged()),
+            this, SLOT(pathCompletionChanged()));
+    
+    constructPath();
+    constructReversePath();
+
+    if (m_rawPath->isReady()) {
+        pathCompletionChanged();
+    }        
+}
+
+void
 AlignmentModel::setPath(PathModel *path)
 {
     if (m_path) m_path->aboutToDelete();
     delete m_path;
     m_path = path;
 #ifdef DEBUG_ALIGNMENT_MODEL
-    SVDEBUG << "AlignmentModel::setPath: path = " << m_path << endl;
+    cerr << "AlignmentModel::setPath: path = " << m_path << endl;
 #endif
     constructReversePath();
 #ifdef DEBUG_ALIGNMENT_MODEL
-    SVDEBUG << "AlignmentModel::setPath: after construction path = "
+    cerr << "AlignmentModel::setPath: after construction path = "
               << m_path << ", rpath = " << m_reversePath << endl;
 #endif
 }
--- a/data/model/AlignmentModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/AlignmentModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -32,15 +32,14 @@
 public:
     AlignmentModel(Model *reference,
                    Model *aligned,
-                   Model *inputModel, // probably an AggregateWaveModel; I take ownership
+                   Model *inputModel, // probably an AggregateWaveModel; may be null; I take ownership
                    SparseTimeValueModel *path); // I take ownership
     ~AlignmentModel();
 
     virtual bool isOK() const;
-    virtual size_t getStartFrame() const;
-    virtual size_t getEndFrame() const;
-    virtual size_t getSampleRate() const;
-    virtual Model *clone() const;
+    virtual sv_frame_t getStartFrame() const;
+    virtual sv_frame_t getEndFrame() const;
+    virtual sv_samplerate_t getSampleRate() const;
     virtual bool isReady(int *completion = 0) const;
     virtual const ZoomConstraint *getZoomConstraint() const;
 
@@ -49,9 +48,10 @@
     const Model *getReferenceModel() const;
     const Model *getAlignedModel() const;
 
-    size_t toReference(size_t frame) const;
-    size_t fromReference(size_t frame) const;
+    sv_frame_t toReference(sv_frame_t frame) const;
+    sv_frame_t fromReference(sv_frame_t frame) const;
 
+    void setPathFrom(SparseTimeValueModel *rawpath);
     void setPath(PathModel *path);
 
     virtual void toXml(QTextStream &stream,
@@ -60,12 +60,12 @@
 
 signals:
     void modelChanged();
-    void modelChanged(size_t startFrame, size_t endFrame);
+    void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
     void completionChanged();
 
 protected slots:
     void pathChanged();
-    void pathChanged(size_t startFrame, size_t endFrame);
+    void pathChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
     void pathCompletionChanged();
 
 protected:
@@ -83,7 +83,7 @@
     void constructPath() const;
     void constructReversePath() const;
 
-    size_t align(PathModel *path, size_t frame) const;
+    sv_frame_t align(PathModel *path, sv_frame_t frame) const;
 };
 
 #endif
--- a/data/model/Dense3DModelPeakCache.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/Dense3DModelPeakCache.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -18,12 +18,10 @@
 #include "base/Profiler.h"
 
 Dense3DModelPeakCache::Dense3DModelPeakCache(DenseThreeDimensionalModel *source,
-					     size_t columnsPerPeak) :
+					     int columnsPerPeak) :
     m_source(source),
     m_resolution(columnsPerPeak)
 {
-    m_coverage.resize(1); // otherwise it is simply invalid
-
     m_cache = new EditableDenseThreeDimensionalModel
         (source->getSampleRate(),
          getResolution(),
@@ -43,22 +41,8 @@
     delete m_cache;
 }
 
-bool
-Dense3DModelPeakCache::isColumnAvailable(size_t 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(size_t column) const
+Dense3DModelPeakCache::getColumn(int column) const
 {
     Profiler profiler("Dense3DModelPeakCache::getColumn");
     if (!m_source) return Column();
@@ -67,7 +51,7 @@
 }
 
 float
-Dense3DModelPeakCache::getValueAt(size_t column, size_t n) const
+Dense3DModelPeakCache::getValueAt(int column, int n) const
 {
     if (!m_source) return 0.f;
     if (!haveColumn(column)) fillColumn(column);
@@ -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
@@ -93,36 +77,39 @@
 }
 
 bool
-Dense3DModelPeakCache::haveColumn(size_t column) const
+Dense3DModelPeakCache::haveColumn(int column) const
 {
-    return column < m_coverage.size() && m_coverage.get(column);
+    return in_range_for(m_coverage, column) && m_coverage[column];
 }
 
 void
-Dense3DModelPeakCache::fillColumn(size_t column) const
+Dense3DModelPeakCache::fillColumn(int column) const
 {
     Profiler profiler("Dense3DModelPeakCache::fillColumn");
 
-    if (column >= m_coverage.size()) {
+    if (!in_range_for(m_coverage, column)) {
         // see note in sourceModelChanged
-        if (m_coverage.size() > 0) m_coverage.reset(m_coverage.size()-1);
-        m_coverage.resize(column + 1);
+        if (m_coverage.size() > 0) m_coverage[m_coverage.size()-1] = false;
+        m_coverage.resize(column + 1, false);
     }
 
     Column peak;
+    int n = 0;
     for (int i = 0; i < m_resolution; ++i) {
         Column here = m_source->getColumn(column * m_resolution + i);
         if (i == 0) {
             peak = here;
+            n = int(peak.size());
         } else {
-            for (int j = 0; j < peak.size() && j < 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 Jul 14 15:04:46 2015 +0100
+++ b/data/model/Dense3DModelPeakCache.h	Wed Apr 20 12:06:28 2016 +0100
@@ -18,7 +18,6 @@
 
 #include "DenseThreeDimensionalModel.h"
 #include "EditableDenseThreeDimensionalModel.h"
-#include "base/ResizeableBitset.h"
 
 class Dense3DModelPeakCache : public DenseThreeDimensionalModel
 {
@@ -26,38 +25,34 @@
 
 public:
     Dense3DModelPeakCache(DenseThreeDimensionalModel *source,
-                          size_t columnsPerPeak);
+                          int columnsPerPeak);
     ~Dense3DModelPeakCache();
 
     virtual bool isOK() const {
         return m_source && m_source->isOK(); 
     }
 
-    virtual size_t getSampleRate() const {
+    virtual sv_samplerate_t getSampleRate() const {
         return m_source->getSampleRate();
     }
 
-    virtual size_t getStartFrame() const {
+    virtual sv_frame_t getStartFrame() const {
         return m_source->getStartFrame();
     }
 
-    virtual size_t getEndFrame() const {
+    virtual sv_frame_t getEndFrame() const {
         return m_source->getEndFrame();
     }
 
-    virtual Model *clone() const {
-        return new Dense3DModelPeakCache(m_source, m_resolution);
-    }
-    
-    virtual size_t getResolution() const {
+    virtual int getResolution() const {
         return m_source->getResolution() * m_resolution;
     }
 
-    virtual size_t getWidth() const {
+    virtual int getWidth() const {
         return m_source->getWidth() / m_resolution + 1;
     }
 
-    virtual size_t getHeight() const {
+    virtual int getHeight() const {
         return m_source->getHeight();
     }
 
@@ -69,13 +64,11 @@
         return m_source->getMaximumLevel();
     }
 
-    virtual bool isColumnAvailable(size_t column) const;
+    virtual Column getColumn(int column) const;
 
-    virtual Column getColumn(size_t column) const;
+    virtual float getValueAt(int column, int n) const;
 
-    virtual float getValueAt(size_t column, size_t n) const;
-
-    virtual QString getBinName(size_t n) const {
+    virtual QString getBinName(int n) const {
         return m_source->getBinName(n);
     }
 
@@ -96,11 +89,12 @@
 private:
     DenseThreeDimensionalModel *m_source;
     mutable EditableDenseThreeDimensionalModel *m_cache;
-    mutable ResizeableBitset m_coverage;
-    size_t m_resolution;
+    mutable std::vector<bool> m_coverage; // must be bool, for space efficiency
+                                          // (vector of bool uses 1-bit elements)
+    int m_resolution;
 
-    bool haveColumn(size_t column) const;
-    void fillColumn(size_t column) const;
+    bool haveColumn(int column) const;
+    void fillColumn(int column) const;
 };
 
 
--- a/data/model/DenseThreeDimensionalModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/DenseThreeDimensionalModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -33,17 +33,17 @@
     /**
      * Return the number of sample frames covered by each column of bins.
      */
-    virtual size_t getResolution() const = 0;
+    virtual int getResolution() const = 0;
 
     /**
      * Return the number of columns of bins in the model.
      */
-    virtual size_t getWidth() const = 0;
+    virtual int getWidth() const = 0;
 
     /**
      * Return the number of bins in each column.
      */
-    virtual size_t getHeight() const = 0; 
+    virtual int getHeight() const = 0; 
 
     /**
      * Return the minimum permissible value in each bin.
@@ -55,32 +55,42 @@
      */
     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(size_t column) const = 0;
-
-    typedef QVector<float> Column;
+    typedef std::vector<float> Column;
 
     /**
      * Get data from the given column of bin values.
      */
-    virtual Column getColumn(size_t column) const = 0;
+    virtual Column getColumn(int column) const = 0;
 
     /**
      * Get the single data point from the n'th bin of the given column.
      */
-    virtual float getValueAt(size_t column, size_t n) const = 0;
+    virtual float getValueAt(int column, int n) const = 0;
 
     /**
      * Get the name of a given bin (i.e. a label to associate with
      * that bin across all columns).
      */
-    virtual QString getBinName(size_t n) const = 0;
+    virtual QString getBinName(int n) const = 0;
+
+    /**
+     * Return true if the bins have values as well as names. If this
+     * returns true, getBinValue() may be used to retrieve the values.
+     */
+    virtual bool hasBinValues() const { return false; }
+
+    /**
+     * Return the value of bin n, if any. This is a "vertical scale"
+     * value which does not vary from one column to the next. This is
+     * only meaningful if hasBinValues() returns true.
+     */
+    virtual float getBinValue(int n) const { return float(n); }
+
+    /**
+     * Obtain the name of the unit of the values returned from
+     * getBinValue(), if any.
+     */
+    virtual QString getBinValueUnit() const { return ""; }
 
     /**
      * Estimate whether a logarithmic scale might be appropriate for
@@ -92,7 +102,7 @@
      * Utility function to query whether a given bin is greater than
      * its (vertical) neighbours.
      */
-    bool isLocalPeak(size_t x, size_t y) {
+    bool isLocalPeak(int x, int y) {
         float value = getValueAt(x, y);
         if (y > 0 && value < getValueAt(x, y - 1)) return false;
         if (y < getHeight() - 1 && value < getValueAt(x, y + 1)) return false;
@@ -103,7 +113,7 @@
      * Utility function to query whether a given bin is greater than a
      * certain threshold.
      */
-    bool isOverThreshold(size_t x, size_t y, float threshold) {
+    bool isOverThreshold(int x, int y, float threshold) {
         return getValueAt(x, y) > threshold;
     }
 
@@ -134,12 +144,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);
         }
@@ -152,11 +162,11 @@
         return SortNumeric;
     }
 
-    virtual long getFrameForRow(int row) const {
-        return row * getSampleRate();
+    virtual sv_frame_t getFrameForRow(int row) const {
+        return sv_frame_t(row) * getResolution() + getStartFrame();
     }
-    virtual int getRowForFrame(long frame) const {
-        return frame / getSampleRate();
+    virtual int getRowForFrame(sv_frame_t frame) const {
+        return int((frame - getStartFrame()) / getResolution());
     }
 
 protected:
--- a/data/model/DenseTimeValueModel.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/DenseTimeValueModel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -29,35 +29,27 @@
 }
 	
 QString
-DenseTimeValueModel::toDelimitedDataString(QString delimiter, size_t f0, size_t f1) const
+DenseTimeValueModel::toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const
 {
-    size_t ch = getChannelCount();
+    int ch = getChannelCount();
 
     cerr << "f0 = " << f0 << ", f1 = " << f1 << endl;
 
     if (f1 <= f0) return "";
 
-    float **all = new float *[ch];
-    for (size_t c = 0; c < ch; ++c) {
-        all[c] = new float[f1 - f0];
-    }
+    auto data = getMultiChannelData(0, ch - 1, f0, f1 - f0);
 
-    size_t n = getData(0, ch - 1, f0, f1 - f0, all);
-
+    if (data.empty() || data[0].empty()) return "";
+    
     QStringList list;
-    for (size_t i = 0; i < n; ++i) {
+    for (sv_frame_t i = 0; in_range_for(data[0], i); ++i) {
         QStringList parts;
         parts << QString("%1").arg(f0 + i);
-        for (size_t c = 0; c < ch; ++c) {
-            parts << QString("%1").arg(all[c][i]);
+        for (int c = 0; in_range_for(data, c); ++c) {
+            parts << QString("%1").arg(data[c][i]);
         }
         list << parts.join(delimiter);
     }
 
-    for (size_t c = 0; c < ch; ++c) {
-        delete[] all[c];
-    }
-    delete[] all;
-
     return list.join("\n");
 }
--- a/data/model/DenseTimeValueModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/DenseTimeValueModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -22,7 +22,9 @@
 
 /**
  * Base class for models containing dense two-dimensional data (value
- * against time).  For example, audio waveform data.
+ * against time).  For example, audio waveform data.  Other time-value
+ * plot data, especially if editable, will normally go into a
+ * SparseTimeValueModel instead even if regularly sampled.
  */
 
 class DenseTimeValueModel : public Model
@@ -51,42 +53,31 @@
     /**
      * Return the number of distinct channels for this model.
      */
-    virtual size_t getChannelCount() const = 0;
+    virtual int getChannelCount() const = 0;
 
     /**
      * Get the specified set of samples from the given channel of the
-     * model in single-precision floating-point format.  Return the
-     * number of samples actually retrieved.
+     * model in single-precision floating-point format. Returned
+     * vector may have fewer samples than requested, if the end of
+     * file was reached.
+     *
      * If the channel is given as -1, mix all available channels and
      * return the result.
      */
-    virtual size_t getData(int channel, size_t start, size_t count,
-                           float *buffer) const = 0;
+    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const = 0;
 
     /**
-     * Get the specified set of samples from the given channel of the
-     * model in double-precision floating-point format.  Return the
-     * number of samples actually retrieved.
-     * If the channel is given as -1, mix all available channels and
-     * return the result.
+     * Get the specified set of samples from given contiguous range of
+     * channels of the model in single-precision floating-point
+     * format. Returned vector may have fewer samples than requested,
+     * if the end of file was reached.
      */
-    virtual size_t getData(int channel, size_t start, size_t count,
-                           double *buffer) const = 0;
-
-    /**
-     * Get the specified set of samples from given contiguous range
-     * of channels of the model in single-precision floating-point
-     * format.  Return the number of sample frames actually retrieved.
-     */
-    virtual size_t getData(size_t fromchannel, size_t tochannel,
-                           size_t start, size_t count,
-                           float **buffers) const = 0;
+    virtual std::vector<std::vector<float>> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const = 0;
 
     virtual bool canPlay() const { return true; }
-    virtual QString getDefaultPlayPluginId() const { return ""; }
-    virtual QString getDefaultPlayPluginConfiguration() const { return ""; }
+    virtual QString getDefaultPlayClipId() const { return ""; }
 
-    virtual QString toDelimitedDataString(QString delimiter, size_t f0, size_t f1) const;
+    virtual QString toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const;
 
     QString getTypeName() const { return tr("Dense Time-Value"); }
 };
--- a/data/model/EditableDenseThreeDimensionalModel.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/EditableDenseThreeDimensionalModel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -27,11 +27,13 @@
 #include <cmath>
 #include <cassert>
 
+using std::vector;
+
 #include "system/System.h"
 
-EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(size_t sampleRate,
-                                                                       size_t resolution,
-                                                                       size_t yBinCount,
+EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(sv_samplerate_t sampleRate,
+                                                                       int resolution,
+                                                                       int yBinCount,
                                                                        CompressionType compression,
                                                                        bool notifyOnAdd) :
     m_startFrame(0),
@@ -55,76 +57,56 @@
     return true;
 }
 
-size_t
+sv_samplerate_t
 EditableDenseThreeDimensionalModel::getSampleRate() const
 {
     return m_sampleRate;
 }
 
-size_t
+sv_frame_t
 EditableDenseThreeDimensionalModel::getStartFrame() const
 {
     return m_startFrame;
 }
 
 void
-EditableDenseThreeDimensionalModel::setStartFrame(size_t f)
+EditableDenseThreeDimensionalModel::setStartFrame(sv_frame_t f)
 {
     m_startFrame = f; 
 }
 
-size_t
+sv_frame_t
 EditableDenseThreeDimensionalModel::getEndFrame() const
 {
     return m_resolution * m_data.size() + (m_resolution - 1);
 }
 
-Model *
-EditableDenseThreeDimensionalModel::clone() const
-{
-    QReadLocker locker(&m_lock);
-
-    EditableDenseThreeDimensionalModel *model =
-        new EditableDenseThreeDimensionalModel
-	(m_sampleRate, m_resolution, m_yBinCount, m_compression);
-
-    model->m_minimum = m_minimum;
-    model->m_maximum = m_maximum;
-    model->m_haveExtents = m_haveExtents;
-
-    for (size_t i = 0; i < m_data.size(); ++i) {
-	model->setColumn(i, m_data.at(i));
-    }
-
-    return model;
-}
-
-size_t
+int
 EditableDenseThreeDimensionalModel::getResolution() const
 {
     return m_resolution;
 }
 
 void
-EditableDenseThreeDimensionalModel::setResolution(size_t sz)
+EditableDenseThreeDimensionalModel::setResolution(int sz)
 {
     m_resolution = sz;
 }
 
-size_t
+int
 EditableDenseThreeDimensionalModel::getWidth() const
 {
-    return m_data.size();
+    return int(m_data.size());
 }
 
-size_t
+int
 EditableDenseThreeDimensionalModel::getHeight() const
 {
     return m_yBinCount;
 }
 
 void
-EditableDenseThreeDimensionalModel::setHeight(size_t sz)
+EditableDenseThreeDimensionalModel::setHeight(int sz)
 {
     m_yBinCount = sz;
 }
@@ -154,28 +136,28 @@
 }
 
 EditableDenseThreeDimensionalModel::Column
-EditableDenseThreeDimensionalModel::getColumn(size_t index) const
+EditableDenseThreeDimensionalModel::getColumn(int index) const
 {
     QReadLocker locker(&m_lock);
-    if (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(size_t index, size_t n) const
+EditableDenseThreeDimensionalModel::getValueAt(int index, int n) const
 {
     Column c = getColumn(index);
-    if (n < c.size()) return c.at(n);
+    if (in_range_for(c, n)) return c.at(n);
     return m_minimum;
 }
 
 //static int given = 0, stored = 0;
 
 void
-EditableDenseThreeDimensionalModel::truncateAndStore(size_t index,
+EditableDenseThreeDimensionalModel::truncateAndStore(int index,
                                                      const Column &values)
 {
-    assert(index < m_data.size());
+    assert(in_range_for(m_data, index));
 
     //cout << "truncateAndStore(" << index << ", " << values.size() << ")" << endl;
 
@@ -187,7 +169,7 @@
     m_trunc[index] = 0;
     if (index == 0 ||
         m_compression == NoCompression ||
-        values.size() != m_yBinCount) {
+        int(values.size()) != m_yBinCount) {
 //        given += values.size();
 //        stored += values.size();
         m_data[index] = values;
@@ -224,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) {
@@ -255,7 +237,7 @@
                     tcol[i - bcount] = values.at(i);
                 }
                 m_data[index] = tcol;
-                m_trunc[index] = -tdist;
+                m_trunc[index] = (signed char)(-tdist);
                 return;
             } else {
                 // create a new column with h - tcount values from 0 up
@@ -266,7 +248,7 @@
                     tcol[i] = values.at(i);
                 }
                 m_data[index] = tcol;
-                m_trunc[index] = tdist;
+                m_trunc[index] = (signed char)(tdist);
                 return;
             }
         }
@@ -283,11 +265,11 @@
 }
 
 EditableDenseThreeDimensionalModel::Column
-EditableDenseThreeDimensionalModel::expandAndRetrieve(size_t index) const
+EditableDenseThreeDimensionalModel::expandAndRetrieve(int index) const
 {
     // See comment above m_trunc declaration in header
 
-    assert(index < m_data.size());
+    assert(index >= 0 && index < int(m_data.size()));
     Column c = m_data.at(index);
     if (index == 0) {
         return c;
@@ -300,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;
     }
@@ -309,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);
@@ -326,12 +304,12 @@
 }
 
 void
-EditableDenseThreeDimensionalModel::setColumn(size_t index,
+EditableDenseThreeDimensionalModel::setColumn(int index,
                                               const Column &values)
 {
     QWriteLocker locker(&m_lock);
 
-    while (index >= m_data.size()) {
+    while (index >= int(m_data.size())) {
 	m_data.push_back(Column());
         m_trunc.push_back(0);
     }
@@ -340,7 +318,7 @@
 
 //    if (values.size() > m_yBinCount) m_yBinCount = values.size();
 
-    for (size_t 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;
@@ -360,14 +338,14 @@
 
 //    assert(values == expandAndRetrieve(index));
 
-    long windowStart = index;
+    sv_frame_t windowStart = index;
     windowStart *= m_resolution;
 
     if (m_notifyOnAdd) {
 	if (allChange) {
 	    emit modelChanged();
 	} else {
-	    emit modelChanged(windowStart, windowStart + m_resolution);
+	    emit modelChangedWithin(windowStart, windowStart + m_resolution);
 	}
     } else {
 	if (allChange) {
@@ -388,16 +366,16 @@
 }
 
 QString
-EditableDenseThreeDimensionalModel::getBinName(size_t n) const
+EditableDenseThreeDimensionalModel::getBinName(int n) const
 {
-    if (m_binNames.size() > n) return m_binNames[n];
+    if (n >= 0 && (int)m_binNames.size() > n) return m_binNames[n];
     else return "";
 }
 
 void
-EditableDenseThreeDimensionalModel::setBinName(size_t n, QString name)
+EditableDenseThreeDimensionalModel::setBinName(int n, QString name)
 {
-    while (m_binNames.size() <= n) m_binNames.push_back("");
+    while ((int)m_binNames.size() <= n) m_binNames.push_back("");
     m_binNames[n] = name;
     emit modelChanged();
 }
@@ -410,22 +388,53 @@
 }
 
 bool
+EditableDenseThreeDimensionalModel::hasBinValues() const
+{
+    return !m_binValues.empty();
+}
+
+float
+EditableDenseThreeDimensionalModel::getBinValue(int n) const
+{
+    if (n < (int)m_binValues.size()) return m_binValues[n];
+    else return 0.f;
+}
+
+void
+EditableDenseThreeDimensionalModel::setBinValues(std::vector<float> values)
+{
+    m_binValues = values;
+}
+
+QString
+EditableDenseThreeDimensionalModel::getBinValueUnit() const
+{
+    return m_binValueUnit;
+}
+
+void
+EditableDenseThreeDimensionalModel::setBinValueUnit(QString unit)
+{
+    m_binValueUnit = unit;
+}
+
+bool
 EditableDenseThreeDimensionalModel::shouldUseLogValueScale() const
 {
     QReadLocker locker(&m_lock);
 
-    QVector<float> sample;
-    QVector<int> n;
+    vector<double> sample;
+    vector<int> n;
     
     for (int i = 0; i < 10; ++i) {
-        size_t index = i * 10;
-        if (index < m_data.size()) {
+        int index = i * 10;
+        if (in_range_for(m_data, index)) {
             const Column &c = m_data.at(index);
             while (c.size() > sample.size()) {
-                sample.push_back(0.f);
+                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];
             }
@@ -433,11 +442,11 @@
     }
 
     if (sample.empty()) return false;
-    for (int j = 0; j < sample.size(); ++j) {
+    for (decltype(sample)::size_type j = 0; j < sample.size(); ++j) {
         if (n[j]) sample[j] /= n[j];
     }
     
-    return LogRange::useLogScale(sample.toStdVector());
+    return LogRange::useLogScale(sample);
 }
 
 void
@@ -456,8 +465,8 @@
 	    if (update &&
                 m_sinceLastNotifyMin >= 0 &&
 		m_sinceLastNotifyMax >= 0) {
-		emit modelChanged(m_sinceLastNotifyMin,
-				  m_sinceLastNotifyMax + m_resolution);
+		emit modelChangedWithin(m_sinceLastNotifyMin,
+                                        m_sinceLastNotifyMax + m_resolution);
 		m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
 	    } else {
 		emit completionChanged();
@@ -473,9 +482,9 @@
 {
     QReadLocker locker(&m_lock);
     QString s;
-    for (size_t i = 0; i < m_data.size(); ++i) {
+    for (int i = 0; in_range_for(m_data, i); ++i) {
         QStringList list;
-	for (size_t 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";
@@ -484,15 +493,15 @@
 }
 
 QString
-EditableDenseThreeDimensionalModel::toDelimitedDataString(QString delimiter, size_t f0, size_t f1) const
+EditableDenseThreeDimensionalModel::toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const
 {
     QReadLocker locker(&m_lock);
     QString s;
-    for (size_t i = 0; i < m_data.size(); ++i) {
-        size_t fr = m_startFrame + i * m_resolution;
+    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 (size_t 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";
@@ -527,7 +536,7 @@
     out << QString("<dataset id=\"%1\" dimensions=\"3\" separator=\" \">\n")
 	.arg(getObjectExportId(&m_data));
 
-    for (size_t i = 0; i < m_binNames.size(); ++i) {
+    for (int i = 0; i < (int)m_binNames.size(); ++i) {
 	if (m_binNames[i] != "") {
 	    out << indent + "  ";
 	    out << QString("<bin number=\"%1\" name=\"%2\"/>\n")
@@ -535,10 +544,10 @@
 	}
     }
 
-    for (size_t i = 0; i < m_data.size(); ++i) {
+    for (int i = 0; i < (int)m_data.size(); ++i) {
 	out << indent + "  ";
 	out << QString("<row n=\"%1\">").arg(i);
-	for (size_t j = 0; j < m_data.at(i).size(); ++j) {
+	for (int j = 0; j < (int)m_data.at(i).size(); ++j) {
 	    if (j > 0) out << " ";
 	    out << m_data.at(i).at(j);
 	}
--- a/data/model/EditableDenseThreeDimensionalModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/EditableDenseThreeDimensionalModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -42,50 +42,47 @@
         BasicMultirateCompression
     };
 
-    EditableDenseThreeDimensionalModel(size_t sampleRate,
-				       size_t resolution,
-				       size_t yBinCount,
+    EditableDenseThreeDimensionalModel(sv_samplerate_t sampleRate,
+				       int resolution,
+				       int yBinCount,
                                        CompressionType compression,
 				       bool notifyOnAdd = true);
 
     virtual bool isOK() const;
 
-    virtual size_t getSampleRate() const;
-    virtual size_t getStartFrame() const;
-    virtual size_t getEndFrame() const;
-
-    virtual Model *clone() const;
-    
+    virtual sv_samplerate_t getSampleRate() const;
+    virtual sv_frame_t getStartFrame() const;
+    virtual sv_frame_t getEndFrame() const;
 
     /**
      * Set the frame offset of the first column.
      */
-    virtual void setStartFrame(size_t);
+    virtual void setStartFrame(sv_frame_t);
 
     /**
      * Return the number of sample frames covered by each set of bins.
      */
-    virtual size_t getResolution() const;
+    virtual int getResolution() const;
 
     /**
      * Set the number of sample frames covered by each set of bins.
      */
-    virtual void setResolution(size_t sz);
+    virtual void setResolution(int sz);
 
     /**
      * Return the number of columns.
      */
-    virtual size_t getWidth() const;
+    virtual int getWidth() const;
 
     /**
      * Return the number of bins in each set of bins.
      */
-    virtual size_t getHeight() const; 
+    virtual int getHeight() const; 
 
     /**
      * Set the number of bins in each set of bins.
      */
-    virtual void setHeight(size_t sz);
+    virtual void setHeight(int sz);
 
     /**
      * Return the minimum value of the value in each bin.
@@ -108,29 +105,75 @@
     virtual void setMaximumLevel(float sz);
 
     /**
-     * Return true if there are data available for the given column.
-     */
-    virtual bool isColumnAvailable(size_t x) const { return x < getWidth(); }
-
-    /**
      * Get the set of bin values at the given column.
      */
-    virtual Column getColumn(size_t x) const;
+    virtual Column getColumn(int x) const;
 
     /**
      * Get a single value, from the n'th bin of the given column.
      */
-    virtual float getValueAt(size_t x, size_t n) const;
+    virtual float getValueAt(int x, int n) const;
 
     /**
      * Set the entire set of bin values at the given column.
      */
-    virtual void setColumn(size_t x, const Column &values);
+    virtual void setColumn(int x, const Column &values);
 
-    virtual QString getBinName(size_t n) const;
-    virtual void setBinName(size_t n, QString);
+    /**
+     * Return the name of bin n. This is a single label per bin that
+     * does not vary from one column to the next.
+     */
+    virtual QString getBinName(int n) const;
+
+    /**
+     * Set the name of bin n.
+     */
+    virtual void setBinName(int n, QString);
+
+    /**
+     * Set the names of all bins.
+     */
     virtual void setBinNames(std::vector<QString> names);
 
+    /**
+     * Return true if the bins have values as well as names. (The
+     * values may have been derived from the names, e.g. by parsing
+     * numbers from them.) If this returns true, getBinValue() may be
+     * used to retrieve the values.
+     */
+    virtual bool hasBinValues() const;
+
+    /**
+     * Return the value of bin n, if any. This is a "vertical scale"
+     * value which does not vary from one column to the next. This is
+     * only meaningful if hasBinValues() returns true.
+     */
+    virtual float getBinValue(int n) const;
+
+    /**
+     * Set the values of all bins (separate from their labels). These
+     * are "vertical scale" values which do not vary from one column
+     * to the next.
+     */
+    virtual void setBinValues(std::vector<float> values);
+
+    /**
+     * Obtain the name of the unit of the values returned from
+     * getBinValue(), if any.
+     */
+    virtual QString getBinValueUnit() const;
+
+    /**
+     * Set the name of the unit of the values return from
+     * getBinValue() if any.
+     */
+    virtual void setBinValueUnit(QString unit);
+
+    /**
+     * Return true if the distribution of values in the bins is such
+     * as to suggest a log scale (mapping to colour etc) may be better
+     * than a linear one.
+     */
     bool shouldUseLogValueScale() const;
 
     virtual void setCompletion(int completion, bool update = true);
@@ -139,14 +182,14 @@
     QString getTypeName() const { return tr("Editable Dense 3-D"); }
 
     virtual QString toDelimitedDataString(QString delimiter) const;
-    virtual QString toDelimitedDataString(QString delimiter, size_t f0, size_t f1) const;
+    virtual QString toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const;
 
     virtual void toXml(QTextStream &out,
                        QString indent = "",
                        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
@@ -158,22 +201,24 @@
     // value).  If m_trunc[x] is 0 then the whole of column x is
     // stored.
     std::vector<signed char> m_trunc;
-    void truncateAndStore(size_t index, const Column & values);
-    Column expandAndRetrieve(size_t index) const;
+    void truncateAndStore(int index, const Column & values);
+    Column expandAndRetrieve(int index) const;
 
     std::vector<QString> m_binNames;
+    std::vector<float> m_binValues;
+    QString m_binValueUnit;
 
-    size_t m_startFrame;
-    size_t m_sampleRate;
-    size_t m_resolution;
-    size_t m_yBinCount;
+    sv_frame_t m_startFrame;
+    sv_samplerate_t m_sampleRate;
+    int m_resolution;
+    int m_yBinCount;
     CompressionType m_compression;
     float m_minimum;
     float m_maximum;
     bool m_haveExtents;
     bool m_notifyOnAdd;
-    long m_sinceLastNotifyMin;
-    long m_sinceLastNotifyMax;
+    sv_frame_t m_sinceLastNotifyMin;
+    sv_frame_t m_sinceLastNotifyMax;
     int m_completion;
 
     mutable QReadWriteLock m_lock;
--- a/data/model/FFTModel.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/FFTModel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -15,7 +15,6 @@
 
 #include "FFTModel.h"
 #include "DenseTimeValueModel.h"
-#include "AggregateWaveModel.h"
 
 #include "base/Profiler.h"
 #include "base/Pitch.h"
@@ -23,196 +22,300 @@
 #include <algorithm>
 
 #include <cassert>
+#include <deque>
 
 #ifndef __GNUC__
 #include <alloca.h>
 #endif
 
+using namespace std;
+
 FFTModel::FFTModel(const DenseTimeValueModel *model,
                    int channel,
                    WindowType windowType,
-                   size_t windowSize,
-                   size_t windowIncrement,
-                   size_t fftSize,
-                   bool polar,
-                   StorageAdviser::Criteria criteria,
-                   size_t fillFromColumn) :
-    //!!! ZoomConstraint!
-    m_server(0),
-    m_xshift(0),
-    m_yshift(0)
+                   int windowSize,
+                   int windowIncrement,
+                   int fftSize) :
+    m_model(model),
+    m_channel(channel),
+    m_windowType(windowType),
+    m_windowSize(windowSize),
+    m_windowIncrement(windowIncrement),
+    m_fftSize(fftSize),
+    m_windower(windowType, windowSize),
+    m_fft(fftSize),
+    m_cacheSize(3)
 {
-    setSourceModel(const_cast<DenseTimeValueModel *>(model)); //!!! hmm.
-
-    m_server = getServer(model,
-                         channel,
-                         windowType,
-                         windowSize,
-                         windowIncrement,
-                         fftSize,
-                         polar,
-                         criteria,
-                         fillFromColumn);
-
-    if (!m_server) return; // caller should check isOK()
-
-    size_t xratio = windowIncrement / m_server->getWindowIncrement();
-    size_t yratio = m_server->getFFTSize() / fftSize;
-
-    while (xratio > 1) {
-        if (xratio & 0x1) {
-            cerr << "ERROR: FFTModel: Window increment ratio "
-                      << windowIncrement << " / "
-                      << m_server->getWindowIncrement()
-                      << " must be a power of two" << endl;
-            assert(!(xratio & 0x1));
-        }
-        ++m_xshift;
-        xratio >>= 1;
+    if (m_windowSize > m_fftSize) {
+        cerr << "ERROR: FFTModel::FFTModel: window size (" << m_windowSize
+             << ") must be at least FFT size (" << m_fftSize << ")" << endl;
+        throw invalid_argument("FFTModel window size must be at least FFT size");
     }
 
-    while (yratio > 1) {
-        if (yratio & 0x1) {
-            cerr << "ERROR: FFTModel: FFT size ratio "
-                      << m_server->getFFTSize() << " / " << fftSize
-                      << " must be a power of two" << endl;
-            assert(!(yratio & 0x1));
-        }
-        ++m_yshift;
-        yratio >>= 1;
-    }
+    connect(model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
+    connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+            this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)));
 }
 
 FFTModel::~FFTModel()
 {
-    if (m_server) FFTDataServer::releaseInstance(m_server);
 }
 
 void
 FFTModel::sourceModelAboutToBeDeleted()
 {
-    if (m_sourceModel) {
-        cerr << "FFTModel[" << this << "]::sourceModelAboutToBeDeleted(" << m_sourceModel << ")" << endl;
-        if (m_server) {
-            FFTDataServer::releaseInstance(m_server);
-            m_server = 0;
-        }
-        FFTDataServer::modelAboutToBeDeleted(m_sourceModel);
+    if (m_model) {
+        cerr << "FFTModel[" << this << "]::sourceModelAboutToBeDeleted(" << m_model << ")" << endl;
+        m_model = 0;
     }
 }
 
-FFTDataServer *
-FFTModel::getServer(const DenseTimeValueModel *model,
-                    int channel,
-                    WindowType windowType,
-                    size_t windowSize,
-                    size_t windowIncrement,
-                    size_t fftSize,
-                    bool polar,
-                    StorageAdviser::Criteria criteria,
-                    size_t fillFromColumn)
+int
+FFTModel::getWidth() const
 {
-    // Obviously, an FFT model of channel C (where C != -1) of an
-    // aggregate model is the same as the FFT model of the appropriate
-    // channel of whichever model that aggregate channel is drawn
-    // from.  We should use that model here, in case we already have
-    // the data for it or will be wanting the same data again later.
-
-    // If the channel is -1 (i.e. mixture of all channels), then we
-    // can't do this shortcut unless the aggregate model only has one
-    // channel or contains exactly all of the channels of a single
-    // other model.  That isn't very likely -- if it were the case,
-    // why would we be using an aggregate model?
-
-    if (channel >= 0) {
-
-        const AggregateWaveModel *aggregate =
-            dynamic_cast<const AggregateWaveModel *>(model);
-
-        if (aggregate && channel < aggregate->getComponentCount()) {
-
-            AggregateWaveModel::ModelChannelSpec spec =
-                aggregate->getComponent(channel);
-
-            return getServer(spec.model,
-                             spec.channel,
-                             windowType,
-                             windowSize,
-                             windowIncrement,
-                             fftSize,
-                             polar,
-                             criteria,
-                             fillFromColumn);
-        }
-    }
-
-    // The normal case
-
-    return FFTDataServer::getFuzzyInstance(model,
-                                           channel,
-                                           windowType,
-                                           windowSize,
-                                           windowIncrement,
-                                           fftSize,
-                                           polar,
-                                           criteria,
-                                           fillFromColumn);
+    if (!m_model) return 0;
+    return int((m_model->getEndFrame() - m_model->getStartFrame())
+               / m_windowIncrement) + 1;
 }
 
-size_t
-FFTModel::getSampleRate() const
+int
+FFTModel::getHeight() const
 {
-    return isOK() ? m_server->getModel()->getSampleRate() : 0;
-}
-
-FFTModel::Column
-FFTModel::getColumn(size_t x) const
-{
-    Profiler profiler("FFTModel::getColumn", false);
-
-    Column result;
-
-    result.clear();
-    size_t h = getHeight();
-    result.reserve(h);
-
-#ifdef __GNUC__
-    float magnitudes[h];
-#else
-    float *magnitudes = (float *)alloca(h * sizeof(float));
-#endif
-
-    if (m_server->getMagnitudesAt(x << m_xshift, magnitudes)) {
-
-        for (size_t y = 0; y < h; ++y) {
-            result.push_back(magnitudes[y]);
-        }
-
-    } else {
-        for (size_t i = 0; i < h; ++i) result.push_back(0.f);
-    }
-
-    return result;
+    return m_fftSize / 2 + 1;
 }
 
 QString
-FFTModel::getBinName(size_t n) const
+FFTModel::getBinName(int n) const
 {
-    size_t sr = getSampleRate();
+    sv_samplerate_t sr = getSampleRate();
     if (!sr) return "";
     QString name = tr("%1 Hz").arg((n * sr) / ((getHeight()-1) * 2));
     return name;
 }
 
+FFTModel::Column
+FFTModel::getColumn(int x) const
+{
+    auto cplx = getFFTColumn(x);
+    Column col;
+    col.reserve(cplx.size());
+    for (auto c: cplx) col.push_back(abs(c));
+    return move(col);
+}
+
+float
+FFTModel::getMagnitudeAt(int x, int y) const
+{
+    if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return 0.f;
+    auto col = getFFTColumn(x);
+    return abs(col[y]);
+}
+
+float
+FFTModel::getMaximumMagnitudeAt(int x) const
+{
+    Column col(getColumn(x));
+    float max = 0.f;
+    int n = int(col.size());
+    for (int i = 0; i < n; ++i) {
+        if (col[i] > max) max = col[i];
+    }
+    return max;
+}
+
+float
+FFTModel::getPhaseAt(int x, int y) const
+{
+    if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return 0.f;
+    return arg(getFFTColumn(x)[y]);
+}
+
+void
+FFTModel::getValuesAt(int x, int y, float &re, float &im) const
+{
+    auto col = getFFTColumn(x);
+    re = col[y].real();
+    im = col[y].imag();
+}
+
 bool
-FFTModel::estimateStableFrequency(size_t x, size_t y, float &frequency)
+FFTModel::getMagnitudesAt(int x, float *values, int minbin, int count) const
+{
+    if (count == 0) count = getHeight();
+    auto col = getFFTColumn(x);
+    for (int i = 0; i < count; ++i) {
+        values[i] = abs(col[minbin + i]);
+    }
+    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
+{
+    if (count == 0) count = getHeight();
+    auto col = getFFTColumn(x);
+    for (int i = 0; i < count; ++i) {
+        values[i] = arg(col[minbin + i]);
+    }
+    return true;
+}
+
+bool
+FFTModel::getValuesAt(int x, float *reals, float *imags, int minbin, int count) const
+{
+    if (count == 0) count = getHeight();
+    auto col = getFFTColumn(x);
+    for (int i = 0; i < count; ++i) {
+        reals[i] = col[minbin + i].real();
+    }
+    for (int i = 0; i < count; ++i) {
+        imags[i] = col[minbin + i].imag();
+    }
+    return true;
+}
+
+vector<float>
+FFTModel::getSourceSamples(int column) const
+{
+    // m_fftSize may be greater than m_windowSize, but not the reverse
+
+//    cerr << "getSourceSamples(" << column << ")" << endl;
+    
+    auto range = getSourceSampleRange(column);
+    auto data = getSourceData(range);
+
+    int off = (m_fftSize - m_windowSize) / 2;
+
+    if (off == 0) {
+        return data;
+    } else {
+        vector<float> pad(off, 0.f);
+        vector<float> padded;
+        padded.reserve(m_fftSize);
+        padded.insert(padded.end(), pad.begin(), pad.end());
+        padded.insert(padded.end(), data.begin(), data.end());
+        padded.insert(padded.end(), pad.begin(), pad.end());
+        return padded;
+    }
+}
+
+vector<float>
+FFTModel::getSourceData(pair<sv_frame_t, sv_frame_t> range) const
+{
+//    cerr << "getSourceData(" << range.first << "," << range.second
+//         << "): saved range is (" << m_savedData.range.first
+//         << "," << m_savedData.range.second << ")" << endl;
+
+    if (m_savedData.range == range) {
+        return m_savedData.data;
+    }
+
+    if (range.first < m_savedData.range.second &&
+        range.first >= m_savedData.range.first &&
+        range.second > m_savedData.range.second) {
+
+        sv_frame_t discard = range.first - m_savedData.range.first;
+
+        vector<float> acc(m_savedData.data.begin() + discard,
+                          m_savedData.data.end());
+
+        vector<float> rest =
+            getSourceDataUncached({ m_savedData.range.second, range.second });
+
+        acc.insert(acc.end(), rest.begin(), rest.end());
+        
+        m_savedData = { range, acc };
+        return acc;
+
+    } else {
+
+        auto data = getSourceDataUncached(range);
+        m_savedData = { range, data };
+        return data;
+    }
+}
+
+vector<float>
+FFTModel::getSourceDataUncached(pair<sv_frame_t, sv_frame_t> range) const
+{
+    decltype(range.first) pfx = 0;
+    if (range.first < 0) {
+        pfx = -range.first;
+        range = { 0, range.second };
+    }
+
+    auto data = m_model->getData(m_channel,
+                                 range.first,
+                                 range.second - range.first);
+
+    // don't return a partial frame
+    data.resize(range.second - range.first, 0.f);
+
+    if (pfx > 0) {
+        vector<float> pad(pfx, 0.f);
+        data.insert(data.begin(), pad.begin(), pad.end());
+    }
+    
+    if (m_channel == -1) {
+	int channels = m_model->getChannelCount();
+	if (channels > 1) {
+            int n = int(data.size());
+            float factor = 1.f / float(channels);
+            // use mean instead of sum for fft model input
+	    for (int i = 0; i < n; ++i) {
+		data[i] *= factor;
+	    }
+	}
+    }
+    
+    return data;
+}
+
+vector<complex<float>>
+FFTModel::getFFTColumn(int n) const
+{
+    for (auto &incache : m_cached) {
+        if (incache.n == n) {
+            return incache.col;
+        }
+    }
+    
+    auto samples = getSourceSamples(n);
+    m_windower.cut(samples.data());
+    auto col = m_fft.process(samples);
+
+    SavedColumn sc { n, col };
+    if (m_cached.size() >= m_cacheSize) {
+        m_cached.pop_front();
+    }
+    m_cached.push_back(sc);
+
+    return move(col);
+}
+
+bool
+FFTModel::estimateStableFrequency(int x, int y, double &frequency)
 {
     if (!isOK()) return false;
 
-    size_t sampleRate = m_server->getModel()->getSampleRate();
-
-    size_t fftSize = m_server->getFFTSize() >> m_yshift;
-    frequency = (float(y) * sampleRate) / fftSize;
+    frequency = double(y * getSampleRate()) / m_fftSize;
 
     if (x+1 >= getWidth()) return false;
 
@@ -225,29 +328,27 @@
     //  = 2pi * ((h * b * sr) / (w * sr))
     //  = 2pi * (h * b) / w.
 
-    float oldPhase = getPhaseAt(x, y);
-    float newPhase = getPhaseAt(x+1, y);
+    double oldPhase = getPhaseAt(x, y);
+    double newPhase = getPhaseAt(x+1, y);
 
-    size_t incr = getResolution();
+    int incr = getResolution();
 
-    float expectedPhase = oldPhase + (2.0 * M_PI * y * incr) / fftSize;
+    double expectedPhase = oldPhase + (2.0 * M_PI * y * incr) / m_fftSize;
 
-    float phaseError = princargf(newPhase - expectedPhase);
-
-//    bool stable = (fabsf(phaseError) < (1.1f * (m_windowIncrement * M_PI) / m_fftSize));
+    double phaseError = princarg(newPhase - expectedPhase);
 
     // The new frequency estimate based on the phase error resulting
     // from assuming the "native" frequency of this bin
 
     frequency =
-        (sampleRate * (expectedPhase + phaseError - oldPhase)) /
-        (2 * M_PI * incr);
+        (getSampleRate() * (expectedPhase + phaseError - oldPhase)) /
+        (2.0 * M_PI * incr);
 
     return true;
 }
 
 FFTModel::PeakLocationSet
-FFTModel::getPeaks(PeakPickType type, size_t x, size_t ymin, size_t ymax)
+FFTModel::getPeaks(PeakPickType type, int x, int ymin, int ymax)
 {
     Profiler profiler("FFTModel::getPeaks");
 
@@ -270,7 +371,7 @@
         float *values = (float *)alloca(n * sizeof(float));
 #endif
         getMagnitudesAt(x, values, minbin, maxbin - minbin + 1);
-        for (size_t bin = ymin; bin <= ymax; ++bin) {
+        for (int bin = ymin; bin <= ymax; ++bin) {
             if (bin == minbin || bin == maxbin) continue;
             if (values[bin - minbin] > values[bin - minbin - 1] &&
                 values[bin - minbin] > values[bin - minbin + 1]) {
@@ -281,36 +382,37 @@
     }
 
     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 /= 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
     // exceed the median.  For pitch adaptivity, we adjust the window
     // size to a roughly constant pitch range (about four tones).
 
-    size_t sampleRate = getSampleRate();
+    sv_samplerate_t sampleRate = getSampleRate();
 
-    std::deque<float> window;
-    std::vector<size_t> inrange;
+    deque<float> window;
+    vector<int> inrange;
     float dist = 0.5;
 
-    size_t medianWinSize = getPeakPickWindowSize(type, sampleRate, ymin, dist);
-    size_t halfWin = medianWinSize/2;
+    int medianWinSize = getPeakPickWindowSize(type, sampleRate, ymin, dist);
+    int halfWin = medianWinSize/2;
 
-    size_t binmin;
+    int binmin;
     if (ymin > halfWin) binmin = ymin - halfWin;
     else binmin = 0;
 
-    size_t binmax;
-    if (ymax + halfWin < values.size()) binmax = ymax + halfWin;
-    else binmax = values.size()-1;
+    int binmax;
+    if (ymax + halfWin < nv) binmax = ymax + halfWin;
+    else binmax = nv - 1;
 
-    size_t prevcentre = 0;
+    int prevcentre = 0;
 
-    for (size_t bin = binmin; bin <= binmax; ++bin) {
+    for (int bin = binmin; bin <= binmax; ++bin) {
 
         float value = values[bin];
 
@@ -320,22 +422,22 @@
         medianWinSize = getPeakPickWindowSize(type, sampleRate, bin, dist);
         halfWin = medianWinSize/2;
 
-        while (window.size() > medianWinSize) {
+        while ((int)window.size() > medianWinSize) {
             window.pop_front();
         }
 
-        size_t actualSize = window.size();
+        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;
         }
 
-        std::deque<float> sorted(window);
-        std::sort(sorted.begin(), sorted.end());
-        float median = sorted[int(sorted.size() * dist)];
+        deque<float> sorted(window);
+        sort(sorted.begin(), sorted.end());
+        float median = sorted[int(float(sorted.size()) * dist)];
 
-        size_t centrebin = 0;
+        int centrebin = 0;
         if (bin > actualSize/2) centrebin = bin - actualSize/2;
         
         while (centrebin > prevcentre || bin == binmin) {
@@ -348,11 +450,11 @@
                 inrange.push_back(centrebin);
             }
 
-            if (centre <= median || centrebin+1 == values.size()) {
+            if (centre <= median || centrebin+1 == nv) {
                 if (!inrange.empty()) {
-                    size_t peakbin = 0;
+                    int peakbin = 0;
                     float peakval = 0.f;
-                    for (size_t i = 0; i < inrange.size(); ++i) {
+                    for (int i = 0; i < (int)inrange.size(); ++i) {
                         if (i == 0 || values[inrange[i]] > peakval) {
                             peakval = values[inrange[i]];
                             peakbin = inrange[i];
@@ -372,30 +474,29 @@
     return peaks;
 }
 
-size_t
-FFTModel::getPeakPickWindowSize(PeakPickType type, size_t sampleRate,
-                                size_t bin, float &percentile) const
+int
+FFTModel::getPeakPickWindowSize(PeakPickType type, sv_samplerate_t sampleRate,
+                                int bin, float &percentile) const
 {
     percentile = 0.5;
     if (type == MajorPeaks) return 10;
     if (bin == 0) return 3;
 
-    size_t fftSize = m_server->getFFTSize() >> m_yshift;
-    float binfreq = (sampleRate * bin) / fftSize;
-    float hifreq = Pitch::getFrequencyForPitch(73, 0, binfreq);
+    double binfreq = (sampleRate * bin) / m_fftSize;
+    double hifreq = Pitch::getFrequencyForPitch(73, 0, binfreq);
 
-    int hibin = lrintf((hifreq * fftSize) / sampleRate);
+    int hibin = int(lrint((hifreq * m_fftSize) / sampleRate));
     int medianWinSize = hibin - bin;
     if (medianWinSize < 3) medianWinSize = 3;
 
-    percentile = 0.5 + (binfreq / sampleRate);
+    percentile = 0.5f + float(binfreq / sampleRate);
 
     return medianWinSize;
 }
 
 FFTModel::PeakSet
-FFTModel::getPeakFrequencies(PeakPickType type, size_t x,
-                             size_t ymin, size_t ymax)
+FFTModel::getPeakFrequencies(PeakPickType type, int x,
+                             int ymin, int ymax)
 {
     Profiler profiler("FFTModel::getPeakFrequencies");
 
@@ -403,33 +504,30 @@
     if (!isOK()) return peaks;
     PeakLocationSet locations = getPeaks(type, x, ymin, ymax);
 
-    size_t sampleRate = getSampleRate();
-    size_t fftSize = m_server->getFFTSize() >> m_yshift;
-    size_t incr = getResolution();
+    sv_samplerate_t sampleRate = getSampleRate();
+    int incr = getResolution();
 
     // This duplicates some of the work of estimateStableFrequency to
     // allow us to retrieve the phases in two separate vertical
     // columns, instead of jumping back and forth between columns x and
     // x+1, which may be significantly slower if re-seeking is needed
 
-    std::vector<float> phases;
+    vector<float> phases;
     for (PeakLocationSet::iterator i = locations.begin();
          i != locations.end(); ++i) {
         phases.push_back(getPhaseAt(x, *i));
     }
 
-    size_t phaseIndex = 0;
+    int phaseIndex = 0;
     for (PeakLocationSet::iterator i = locations.begin();
          i != locations.end(); ++i) {
-        float oldPhase = phases[phaseIndex];
-        float newPhase = getPhaseAt(x+1, *i);
-        float expectedPhase = oldPhase + (2.0 * M_PI * *i * incr) / fftSize;
-        float phaseError = princargf(newPhase - expectedPhase);
-        float frequency =
+        double oldPhase = phases[phaseIndex];
+        double newPhase = getPhaseAt(x+1, *i);
+        double expectedPhase = oldPhase + (2.0 * M_PI * *i * incr) / m_fftSize;
+        double phaseError = princarg(newPhase - expectedPhase);
+        double frequency =
             (sampleRate * (expectedPhase + phaseError - oldPhase))
             / (2 * M_PI * incr);
-//        bool stable = (fabsf(phaseError) < (1.1f * (incr * M_PI) / fftSize));
-//        if (stable)
         peaks[*i] = frequency;
         ++phaseIndex;
     }
@@ -437,18 +535,3 @@
     return peaks;
 }
 
-Model *
-FFTModel::clone() const
-{
-    return new FFTModel(*this);
-}
-
-FFTModel::FFTModel(const FFTModel &model) :
-    DenseThreeDimensionalModel(),
-    m_server(model.m_server),
-    m_xshift(model.m_xshift),
-    m_yshift(model.m_yshift)
-{
-    FFTDataServer::claimInstance(m_server);
-}
-
--- a/data/model/FFTModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/FFTModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -13,27 +13,33 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _FFT_MODEL_H_
-#define _FFT_MODEL_H_
+#ifndef FFT_MODEL_H
+#define FFT_MODEL_H
 
-#include "data/fft/FFTDataServer.h"
 #include "DenseThreeDimensionalModel.h"
+#include "DenseTimeValueModel.h"
+
+#include "base/Window.h"
+
+#include "data/fft/FFTapi.h"
 
 #include <set>
-#include <map>
+#include <vector>
+#include <complex>
+#include <deque>
 
 /**
  * An implementation of DenseThreeDimensionalModel that makes FFT data
  * derived from a DenseTimeValueModel available as a generic data
- * grid.  The FFT data is acquired using FFTDataServer.  Note that any
- * of the accessor functions may throw AllocationFailed if a cache
- * resize fails.
+ * grid.
  */
-
 class FFTModel : public DenseThreeDimensionalModel
 {
     Q_OBJECT
 
+    //!!! threading requirements?
+    //!!! doubles? since we're not caching much
+
 public:
     /**
      * Construct an FFT model derived from the given
@@ -43,115 +49,68 @@
      * If the model has multiple channels use only the given channel,
      * unless the channel is -1 in which case merge all available
      * channels.
-     * 
-     * If polar is true, the data will normally be retrieved from the
-     * FFT model in magnitude/phase form; otherwise it will normally
-     * be retrieved in "cartesian" real/imaginary form.  The results
-     * should be the same either way, but a "polar" model addressed in
-     * "cartesian" form or vice versa may suffer a performance
-     * penalty.
-     *
-     * The fillFromColumn argument gives a hint that the FFT data
-     * server should aim to start calculating FFT data at that column
-     * number if possible, as that is likely to be requested first.
      */
     FFTModel(const DenseTimeValueModel *model,
              int channel,
              WindowType windowType,
-             size_t windowSize,
-             size_t windowIncrement,
-             size_t fftSize,
-             bool polar,
-             StorageAdviser::Criteria criteria = StorageAdviser::NoCriteria,
-             size_t fillFromColumn = 0);
+             int windowSize,
+             int windowIncrement,
+             int fftSize);
     ~FFTModel();
 
-    inline float getMagnitudeAt(size_t x, size_t y) {
-        return m_server->getMagnitudeAt(x << m_xshift, y << m_yshift);
-    }
-    inline float getNormalizedMagnitudeAt(size_t x, size_t y) {
-        return m_server->getNormalizedMagnitudeAt(x << m_xshift, y << m_yshift);
-    }
-    inline float getMaximumMagnitudeAt(size_t x) {
-        return m_server->getMaximumMagnitudeAt(x << m_xshift);
-    }
-    inline float getPhaseAt(size_t x, size_t y) {
-        return m_server->getPhaseAt(x << m_xshift, y << m_yshift);
-    }
-    inline void getValuesAt(size_t x, size_t y, float &real, float &imaginary) {
-        m_server->getValuesAt(x << m_xshift, y << m_yshift, real, imaginary);
-    }
-    inline bool isColumnAvailable(size_t x) const {
-        return m_server->isColumnReady(x << m_xshift);
-    }
-
-    inline bool getMagnitudesAt(size_t x, float *values, size_t minbin = 0, size_t count = 0) {
-        return m_server->getMagnitudesAt(x << m_xshift, values, minbin << m_yshift, count, getYRatio());
-    }
-    inline bool getNormalizedMagnitudesAt(size_t x, float *values, size_t minbin = 0, size_t count = 0) {
-        return m_server->getNormalizedMagnitudesAt(x << m_xshift, values, minbin << m_yshift, count, getYRatio());
-    }
-    inline bool getPhasesAt(size_t x, float *values, size_t minbin = 0, size_t count = 0) {
-        return m_server->getPhasesAt(x << m_xshift, values, minbin << m_yshift, count, getYRatio());
-    }
-    inline bool getValuesAt(size_t x, float *reals, float *imaginaries, size_t minbin = 0, size_t count = 0) {
-        return m_server->getValuesAt(x << m_xshift, reals, imaginaries, minbin << m_yshift, count, getYRatio());
-    }
-
-    inline size_t getFillExtent() const { return m_server->getFillExtent(); }
-
     // DenseThreeDimensionalModel and Model methods:
     //
-    inline virtual size_t getWidth() const {
-        return m_server->getWidth() >> m_xshift;
+    virtual int getWidth() const;
+    virtual int getHeight() const;
+    virtual float getValueAt(int x, int y) const { return getMagnitudeAt(x, y); }
+    virtual bool isOK() const { return m_model && m_model->isOK(); }
+    virtual sv_frame_t getStartFrame() const { return 0; }
+    virtual sv_frame_t getEndFrame() const {
+        return sv_frame_t(getWidth()) * getResolution() + getResolution();
     }
-    inline virtual size_t getHeight() const {
-        // If there is no y-shift, the server's height (based on its
-        // fftsize/2 + 1) is correct.  If there is a shift, then the
-        // server is using a larger fft size than we want, so we shift
-        // it right as many times as necessary, but then we need to
-        // re-add the "+1" part (because ((fftsize*2)/2 + 1) / 2 !=
-        // fftsize/2 + 1).
-        return (m_server->getHeight() >> m_yshift) + (m_yshift > 0 ? 1 : 0);
+    virtual sv_samplerate_t getSampleRate() const {
+        return isOK() ? m_model->getSampleRate() : 0;
     }
-    virtual float getValueAt(size_t x, size_t y) const {
-        return const_cast<FFTModel *>(this)->getMagnitudeAt(x, y);
+    virtual int getResolution() const { return m_windowIncrement; }
+    virtual int getYBinCount() const { return getHeight(); }
+    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 QString getBinName(int n) const;
+    virtual bool shouldUseLogValueScale() const { return true; }
+    virtual int getCompletion() const {
+        int c = 100;
+        if (m_model) {
+            if (m_model->isReady(&c)) return 100;
+        }
+        return c;
     }
-    virtual bool isOK() const {
-        return m_server && m_server->getModel();
-    }
-    virtual size_t getStartFrame() const {
-        return 0;
-    }
-    virtual size_t getEndFrame() const {
-        return getWidth() * getResolution() + getResolution();
-    }
-    virtual size_t getSampleRate() const;
-    virtual size_t getResolution() const {
-        return m_server->getWindowIncrement() << m_xshift;
-    }
-    virtual size_t getYBinCount() const {
-        return getHeight();
-    }
-    virtual float getMinimumLevel() const {
-        return 0.f; // Can't provide
-    }
-    virtual float getMaximumLevel() const {
-        return 1.f; // Can't provide
-    }
-    virtual Column getColumn(size_t x) const;
-    virtual QString getBinName(size_t n) const;
+    virtual QString getError() const { return ""; } //!!!???
+    virtual sv_frame_t getFillExtent() const { return getEndFrame(); }
 
-    virtual bool shouldUseLogValueScale() const {
-        return true; // Although obviously it's up to the user...
-    }
+    // FFTModel methods:
+    //
+    int getChannel() const { return m_channel; }
+    WindowType getWindowType() const { return m_windowType; }
+    int getWindowSize() const { return m_windowSize; }
+    int getWindowIncrement() const { return m_windowIncrement; }
+    int getFFTSize() const { return m_fftSize; }
+    
+    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 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;
 
     /**
      * Calculate an estimated frequency for a stable signal in this
      * bin, using phase unwrapping.  This will be completely wrong if
      * the signal is not stable here.
      */
-    virtual bool estimateStableFrequency(size_t x, size_t y, float &frequency);
+    virtual bool estimateStableFrequency(int x, int y, double &frequency);
 
     enum PeakPickType
     {
@@ -160,30 +119,21 @@
         MajorPitchAdaptivePeaks  /// Bigger window for higher frequencies
     };
 
-    typedef std::set<size_t> PeakLocationSet; // bin
-    typedef std::map<size_t, float> PeakSet; // bin -> freq
+    typedef std::set<int> PeakLocationSet; // bin
+    typedef std::map<int, double> PeakSet; // bin -> freq
 
     /**
      * Return locations of peak bins in the range [ymin,ymax].  If
      * ymax is zero, getHeight()-1 will be used.
      */
-    virtual PeakLocationSet getPeaks(PeakPickType type, size_t x,
-                                     size_t ymin = 0, size_t ymax = 0);
+    virtual PeakLocationSet getPeaks(PeakPickType type, int x,
+                                     int ymin = 0, int ymax = 0);
 
     /**
      * Return locations and estimated stable frequencies of peak bins.
      */
-    virtual PeakSet getPeakFrequencies(PeakPickType type, size_t x,
-                                       size_t ymin = 0, size_t ymax = 0);
-
-    virtual int getCompletion() const { return m_server->getFillCompletion(); }
-    virtual QString getError() const { return m_server->getError(); }
-
-    virtual Model *clone() const;
-
-    virtual void suspend() { m_server->suspend(); }
-    virtual void suspendWrites() { m_server->suspendWrites(); }
-    virtual void resume() { m_server->resume(); }
+    virtual PeakSet getPeakFrequencies(PeakPickType type, int x,
+                                       int ymin = 0, int ymax = 0);
 
     QString getTypeName() const { return tr("FFT"); }
 
@@ -194,23 +144,44 @@
     FFTModel(const FFTModel &); // not implemented
     FFTModel &operator=(const FFTModel &); // not implemented
 
-    FFTDataServer *m_server;
-    int m_xshift;
-    int m_yshift;
+    const DenseTimeValueModel *m_model;
+    int m_channel;
+    WindowType m_windowType;
+    int m_windowSize;
+    int m_windowIncrement;
+    int m_fftSize;
+    Window<float> m_windower;
+    FFTForward m_fft;
+    
+    int getPeakPickWindowSize(PeakPickType type, sv_samplerate_t sampleRate,
+                              int bin, float &percentile) const;
 
-    FFTDataServer *getServer(const DenseTimeValueModel *,
-                             int, WindowType, size_t, size_t, size_t,
-                             bool, StorageAdviser::Criteria, size_t);
+    std::pair<sv_frame_t, sv_frame_t> getSourceSampleRange(int column) const {
+        sv_frame_t startFrame = m_windowIncrement * sv_frame_t(column);
+        sv_frame_t endFrame = startFrame + m_windowSize;
+        // Cols are centred on the audio sample (e.g. col 0 is centred at sample 0)
+        startFrame -= m_windowSize / 2;
+        endFrame -= m_windowSize / 2;
+        return { startFrame, endFrame };
+    }
 
-    size_t getPeakPickWindowSize(PeakPickType type, size_t sampleRate,
-                                 size_t bin, float &percentile) const;
+    std::vector<std::complex<float> > getFFTColumn(int column) const;
+    std::vector<float> getSourceSamples(int column) const;
+    std::vector<float> getSourceData(std::pair<sv_frame_t, sv_frame_t>) const;
+    std::vector<float> getSourceDataUncached(std::pair<sv_frame_t, sv_frame_t>) const;
 
-    size_t getYRatio() {
-        size_t ys = m_yshift;
-        size_t r = 1;
-        while (ys) { --ys; r <<= 1; }
-        return r;
-    }
+    struct SavedSourceData {
+        std::pair<sv_frame_t, sv_frame_t> range;
+        std::vector<float> data;
+    };
+    mutable SavedSourceData m_savedData;
+    
+    struct SavedColumn {
+        int n;
+        std::vector<std::complex<float> > col;
+    };
+    mutable std::deque<SavedColumn> m_cached;
+    size_t m_cacheSize;
 };
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/FlexiNoteModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,268 @@
+/* -*- 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 _FLEXINOTE_MODEL_H_
+#define _FLEXINOTE_MODEL_H_
+
+#include "IntervalModel.h"
+#include "NoteData.h"
+#include "base/RealTime.h"
+#include "base/Pitch.h"
+#include "base/PlayParameterRepository.h"
+
+/**
+ * FlexiNoteModel -- a concrete IntervalModel for notes.
+ */
+
+/**
+ * Extension of the NoteModel for more flexible note interaction. 
+ * The original NoteModel rationale is given below, will need to be
+ * updated for FlexiNoteModel:
+ *
+ * Note type for use in a sparse model.  All we mean by a "note" is
+ * something that has an onset time, a single value, a duration, and a
+ * level.  Like other points, it can also have a label.  With this
+ * point type, the model can be thought of as representing a simple
+ * MIDI-type piano roll, except that the y coordinates (values) do not
+ * have to be discrete integers.
+ */
+
+struct FlexiNote
+{
+public:
+    FlexiNote(sv_frame_t _frame) : frame(_frame), value(0.0f), duration(0), level(1.f) { }
+    FlexiNote(sv_frame_t _frame, float _value, sv_frame_t _duration, float _level, QString _label) :
+	frame(_frame), value(_value), duration(_duration), level(_level), label(_label) { }
+
+    int getDimensions() const { return 3; }
+
+    sv_frame_t frame;
+    float value;
+    sv_frame_t duration;
+    float level;
+    QString label;
+
+    QString getLabel() const { return label; }
+    
+    void toXml(QTextStream &stream,
+               QString indent = "",
+               QString extraAttributes = "") const
+    {
+	stream <<
+            QString("%1<point frame=\"%2\" value=\"%3\" duration=\"%4\" level=\"%5\" label=\"%6\" %7/>\n")
+	    .arg(indent).arg(frame).arg(value).arg(duration).arg(level)
+            .arg(XmlExportable::encodeEntities(label)).arg(extraAttributes);
+    }
+
+    QString toDelimitedDataString(QString delimiter, DataExportOptions opts, sv_samplerate_t sampleRate) const
+    {
+        QStringList list;
+        list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
+        list << QString("%1").arg(value);
+        list << RealTime::frame2RealTime(duration, sampleRate).toString().c_str();
+        if (!(opts & DataExportOmitLevels)) {
+            list << QString("%1").arg(level);
+        }
+        if (label != "") list << label;
+        return list.join(delimiter);
+    }
+
+    struct Comparator {
+	bool operator()(const FlexiNote &p1,
+			const FlexiNote &p2) const {
+	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
+	    if (p1.value != p2.value) return p1.value < p2.value;
+	    if (p1.duration != p2.duration) return p1.duration < p2.duration;
+            if (p1.level != p2.level) return p1.level < p2.level;
+	    return p1.label < p2.label;
+	}
+    };
+    
+    struct OrderComparator {
+	bool operator()(const FlexiNote &p1,
+			const FlexiNote &p2) const {
+	    return p1.frame < p2.frame;
+	}
+    };
+};
+
+
+class FlexiNoteModel : public IntervalModel<FlexiNote>, public NoteExportable
+{
+    Q_OBJECT
+    
+public:
+    FlexiNoteModel(sv_samplerate_t sampleRate, int resolution,
+                   bool notifyOnAdd = true) :
+	IntervalModel<FlexiNote>(sampleRate, resolution, notifyOnAdd),
+	m_valueQuantization(0)
+    {
+	PlayParameterRepository::getInstance()->addPlayable(this);
+    }
+
+    FlexiNoteModel(sv_samplerate_t sampleRate, int resolution,
+	      float valueMinimum, float valueMaximum,
+	      bool notifyOnAdd = true) :
+	IntervalModel<FlexiNote>(sampleRate, resolution,
+                            valueMinimum, valueMaximum,
+                            notifyOnAdd),
+	m_valueQuantization(0)
+    {
+	PlayParameterRepository::getInstance()->addPlayable(this);
+    }
+
+    virtual ~FlexiNoteModel()
+    {
+        PlayParameterRepository::getInstance()->removePlayable(this);
+    }
+
+    float getValueQuantization() const { return m_valueQuantization; }
+    void setValueQuantization(float q) { m_valueQuantization = q; }
+    float getValueMinimum() const { return 33; }
+    float getValueMaximum() const { return 88; }
+
+    QString getTypeName() const { return tr("FlexiNote"); }
+
+    virtual bool canPlay() const { return true; }
+
+    virtual QString getDefaultPlayClipId() const
+    {
+        return "elecpiano";
+    }
+
+    virtual void toXml(QTextStream &out,
+                       QString indent = "",
+                       QString extraAttributes = "") const
+    {
+        std::cerr << "FlexiNoteModel::toXml: extraAttributes = \"" 
+                  << extraAttributes.toStdString() << std::endl;
+
+        IntervalModel<FlexiNote>::toXml
+	    (out,
+             indent,
+	     QString("%1 subtype=\"flexinote\" valueQuantization=\"%2\"")
+	     .arg(extraAttributes).arg(m_valueQuantization));
+    }
+
+    /**
+     * TabularModel methods.  
+     */
+    
+    virtual int getColumnCount() const
+    {
+        return 6;
+    }
+
+    virtual QString getHeading(int column) const
+    {
+        switch (column) {
+        case 0: return tr("Time");
+        case 1: return tr("Frame");
+        case 2: return tr("Pitch");
+        case 3: return tr("Duration");
+        case 4: return tr("Level");
+        case 5: return tr("Label");
+        default: return tr("Unknown");
+        }
+    }
+
+    virtual QVariant getData(int row, int column, int role) const
+    {
+        if (column < 4) {
+            return IntervalModel<FlexiNote>::getData(row, column, role);
+        }
+
+        PointListConstIterator i = getPointListIteratorForRow(row);
+        if (i == m_points.end()) return QVariant();
+
+        switch (column) {
+        case 4: return i->level;
+        case 5: return i->label;
+        default: return QVariant();
+        }
+    }
+
+    virtual Command *getSetDataCommand(int row, int column, const QVariant &value, int role)
+    {
+        if (column < 4) {
+            return IntervalModel<FlexiNote>::getSetDataCommand
+                (row, column, value, role);
+        }
+
+        if (role != Qt::EditRole) return 0;
+        PointListConstIterator i = getPointListIteratorForRow(row);
+        if (i == m_points.end()) return 0;
+        EditCommand *command = new EditCommand(this, tr("Edit Data"));
+
+        Point point(*i);
+        command->deletePoint(point);
+
+        switch (column) {
+        case 4: point.level = float(value.toDouble()); break;
+        case 5: point.label = value.toString(); break;
+        }
+
+        command->addPoint(point);
+        return command->finish();
+    }
+
+    virtual SortType getSortType(int column) const
+    {
+        if (column == 5) return SortAlphabetical;
+        return SortNumeric;
+    }
+
+    /**
+     * NoteExportable methods.
+     */
+
+    NoteList getNotes() const 
+    {
+        return getNotesWithin(getStartFrame(), getEndFrame());
+    }
+
+    NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const 
+    {    
+    	PointList points = getPoints(startFrame, endFrame);
+        NoteList notes;
+        for (PointList::iterator pli = points.begin(); pli != points.end(); ++pli) {
+    	    sv_frame_t duration = pli->duration;
+            if (duration == 0 || duration == 1) {
+                duration = sv_frame_t(getSampleRate() / 20);
+            }
+            int pitch = int(lrintf(pli->value));
+
+            int velocity = 100;
+            if (pli->level > 0.f && pli->level <= 1.f) {
+                velocity = int(lrintf(pli->level * 127));
+            }
+
+            NoteData note(pli->frame, duration, pitch, velocity);
+
+            if (getScaleUnits() == "Hz") {
+                note.frequency = pli->value;
+                note.midiPitch = Pitch::getPitchForFrequency(note.frequency);
+                note.isMidiPitchQuantized = false;
+            }
+            notes.push_back(note);
+        }
+        return notes;
+    }
+
+protected:
+    float m_valueQuantization;
+};
+
+#endif
--- a/data/model/ImageModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/ImageModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -31,13 +31,13 @@
 struct ImagePoint : public XmlExportable
 {
 public:
-    ImagePoint(long _frame) : frame(_frame) { }
-    ImagePoint(long _frame, QString _image, QString _label) :
+    ImagePoint(sv_frame_t _frame) : frame(_frame) { }
+    ImagePoint(sv_frame_t _frame, QString _image, QString _label) :
         frame(_frame), image(_image), label(_label) { }
 
     int getDimensions() const { return 1; }
     
-    long frame;
+    sv_frame_t frame;
     QString image;
     QString label;
 
@@ -55,7 +55,7 @@
             .arg(extraAttributes);
     }
 
-    QString toDelimitedDataString(QString delimiter, size_t sampleRate) const
+    QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const
     {
         QStringList list;
         list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
@@ -89,7 +89,7 @@
     Q_OBJECT
 
 public:
-    ImageModel(size_t sampleRate, size_t resolution, bool notifyOnAdd = true) :
+    ImageModel(sv_samplerate_t sampleRate, int resolution, bool notifyOnAdd = true) :
 	SparseModel<ImagePoint>(sampleRate, resolution, notifyOnAdd)
     { }
 
--- a/data/model/IntervalModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/IntervalModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -29,12 +29,12 @@
 class IntervalModel : public SparseValueModel<PointType>
 {
 public:
-    IntervalModel(size_t sampleRate, size_t resolution,
+    IntervalModel(sv_samplerate_t sampleRate, int resolution,
                   bool notifyOnAdd = true) :
 	SparseValueModel<PointType>(sampleRate, resolution, notifyOnAdd)
     { }
 
-    IntervalModel(size_t sampleRate, size_t resolution,
+    IntervalModel(sv_samplerate_t sampleRate, int resolution,
                   float valueMinimum, float valueMaximum,
                   bool notifyOnAdd = true) :
 	SparseValueModel<PointType>(sampleRate, resolution,
@@ -48,14 +48,14 @@
      * and after).  Consequently this can be very slow (optimised data
      * structures still to be done!).
      */
-    virtual typename SparseValueModel<PointType>::PointList getPoints(long start, long end) const;
+    virtual typename SparseValueModel<PointType>::PointList getPoints(sv_frame_t start, sv_frame_t end) const;
 
     /**
      * PointTypes have a duration, so this returns all points that span the
      * given frame.  Consequently this can be very slow (optimised
      * data structures still to be done!).
      */
-    virtual typename SparseValueModel<PointType>::PointList getPoints(long frame) const;
+    virtual typename SparseValueModel<PointType>::PointList getPoints(sv_frame_t frame) const;
 
     virtual const typename SparseModel<PointType>::PointList &getPoints() const {
         return SparseModel<PointType>::getPoints(); 
@@ -106,8 +106,8 @@
         command->deletePoint(point);
 
         switch (column) {
-        case 0: case 1: point.frame = value.toInt(); break; 
-        case 2: point.value = value.toDouble(); break;
+        // column cannot be 0 or 1, those cases were handled above
+        case 2: point.value = float(value.toDouble()); break;
         case 3: point.duration = value.toInt(); break;
         }
 
@@ -125,7 +125,7 @@
 
 template <typename PointType>
 typename SparseValueModel<PointType>::PointList
-IntervalModel<PointType>::getPoints(long start, long end) const
+IntervalModel<PointType>::getPoints(sv_frame_t start, sv_frame_t end) const
 {
     typedef IntervalModel<PointType> I;
 
@@ -146,7 +146,7 @@
     for (typename I::PointListConstIterator i = endItr; i != I::m_points.begin(); ) {
         --i;
         if (i->frame < start) {
-            if (i->frame + long(i->duration) >= start) {
+            if (i->frame + i->duration >= start) {
                 rv.insert(*i);
             }
         } else if (i->frame <= end) {
@@ -159,7 +159,7 @@
 
 template <typename PointType>
 typename SparseValueModel<PointType>::PointList
-IntervalModel<PointType>::getPoints(long frame) const
+IntervalModel<PointType>::getPoints(sv_frame_t frame) const
 {
     typedef IntervalModel<PointType> I;
 
@@ -168,8 +168,8 @@
 
     if (I::m_resolution == 0) return typename I::PointList();
 
-    long start = (frame / I::m_resolution) * I::m_resolution;
-    long end = start + I::m_resolution;
+    sv_frame_t start = (frame / I::m_resolution) * I::m_resolution;
+    sv_frame_t end = start + I::m_resolution;
 
     PointType endPoint(end);
     
@@ -180,7 +180,7 @@
     for (typename I::PointListConstIterator i = endItr; i != I::m_points.begin(); ) {
         --i;
         if (i->frame < start) {
-            if (i->frame + long(i->duration) >= start) {
+            if (i->frame + i->duration >= start) {
                 rv.insert(*i);
             }
         } else if (i->frame <= end) {
--- a/data/model/Labeller.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/Labeller.h	Wed Apr 20 12:06:28 2016 +0100
@@ -132,7 +132,7 @@
         if (m_counter > m_cycle) m_counter = 1;
     }
 
-    void setSampleRate(float rate) { m_rate = rate; }
+    void setSampleRate(sv_samplerate_t rate) { m_rate = rate; }
 
     void resetCounters() {
         m_counter = 1;
@@ -262,24 +262,24 @@
 
         case ValueFromSimpleCounter:
         case ValueFromCyclicalCounter:
-            value = m_counter;
+            value = float(m_counter);
             incrementCounter();
             break;
 
         case ValueFromTwoLevelCounter:
-            value = m_counter2 + double(m_counter) / double(m_dp);
+            value = float(m_counter2 + double(m_counter) / double(m_dp));
             incrementCounter();
             break;
 
         case ValueFromFrameNumber:
-            value = newPoint.frame;
+            value = float(newPoint.frame);
             break;
             
         case ValueFromRealTime: 
-            if (m_rate == 0.f) {
+            if (m_rate == 0.0) {
                 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
             } else {
-                value = float(newPoint.frame) / float(m_rate);
+                value = float(double(newPoint.frame) / m_rate);
             }
             break;
 
@@ -287,18 +287,18 @@
         case ValueFromTempoToNext:
         case ValueFromDurationFromPrevious:
         case ValueFromTempoFromPrevious:
-            if (m_rate == 0.f) {
+            if (m_rate == 0.0) {
                 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
             } else if (!prevPoint) {
                 std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl;
             } else {
-                size_t f0 = prevPoint->frame, f1 = newPoint.frame;
+                sv_frame_t f0 = prevPoint->frame, f1 = newPoint.frame;
                 if (m_type == ValueFromDurationToNext ||
                     m_type == ValueFromDurationFromPrevious) {
-                    value = float(f1 - f0) / m_rate;
+                    value = float(double(f1 - f0) / m_rate);
                 } else {
                     if (f1 > f0) {
-                        value = (60.f * m_rate) / (f1 - f0);
+                        value = float((60.0 * m_rate) / double(f1 - f0));
                     }
                 }
             }
@@ -313,7 +313,7 @@
         case ValueFromLabel:
             if (newPoint.label != "") {
                 // more forgiving than QString::toFloat()
-                value = atof(newPoint.label.toLocal8Bit());
+                value = float(atof(newPoint.label.toLocal8Bit()));
             } else {
                 value = 0.f;
             }
@@ -328,7 +328,7 @@
     int m_counter2;
     int m_cycle;
     int m_dp;
-    float m_rate;
+    sv_samplerate_t m_rate;
 };
 
 #endif
--- a/data/model/Model.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/Model.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -85,9 +85,13 @@
         m_alignment->aboutToDelete();
         delete m_alignment;
     }
+    
     m_alignment = alignment;
-    connect(m_alignment, SIGNAL(completionChanged()),
-            this, SIGNAL(alignmentCompletionChanged()));
+
+    if (m_alignment) {
+        connect(m_alignment, SIGNAL(completionChanged()),
+                this, SIGNAL(alignmentCompletionChanged()));
+    }
 }
 
 const AlignmentModel *
@@ -106,28 +110,32 @@
     return m_alignment->getReferenceModel();
 }
 
-size_t
-Model::alignToReference(size_t frame) const
+sv_frame_t
+Model::alignToReference(sv_frame_t frame) const
 {
+//    cerr << "Model(" << this << ")::alignToReference(" << frame << ")" << endl;
     if (!m_alignment) {
         if (m_sourceModel) return m_sourceModel->alignToReference(frame);
         else return frame;
     }
-    size_t refFrame = m_alignment->toReference(frame);
+    sv_frame_t refFrame = m_alignment->toReference(frame);
     const Model *m = m_alignment->getReferenceModel();
     if (m && refFrame > m->getEndFrame()) refFrame = m->getEndFrame();
+//    cerr << "have alignment, aligned is " << refFrame << endl;
     return refFrame;
 }
 
-size_t
-Model::alignFromReference(size_t refFrame) const
+sv_frame_t
+Model::alignFromReference(sv_frame_t refFrame) const
 {
+//    cerr << "Model(" << this << ")::alignFromReference(" << refFrame << ")" << endl;
     if (!m_alignment) {
         if (m_sourceModel) return m_sourceModel->alignFromReference(refFrame);
         else return refFrame;
     }
-    size_t frame = m_alignment->fromReference(refFrame);
+    sv_frame_t frame = m_alignment->fromReference(refFrame);
     if (frame > getEndFrame()) frame = getEndFrame();
+//    cerr << "have alignment, aligned is " << frame << endl;
     return frame;
 }
 
--- a/data/model/Model.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/Model.h	Wed Apr 20 12:06:28 2016 +0100
@@ -21,8 +21,8 @@
 
 #include "base/XmlExportable.h"
 #include "base/Playable.h"
-
-typedef std::vector<float> SampleBlock;
+#include "base/BaseTypes.h"
+#include "base/DataExportOptions.h"
 
 class ZoomConstraint;
 class AlignmentModel;
@@ -50,23 +50,23 @@
     /**
      * Return the first audio frame spanned by the model.
      */
-    virtual size_t getStartFrame() const = 0;
+    virtual sv_frame_t getStartFrame() const = 0;
 
     /**
      * Return the last audio frame spanned by the model.
      */
-    virtual size_t getEndFrame() const = 0;
+    virtual sv_frame_t getEndFrame() const = 0;
 
     /**
      * Return the frame rate in frames per second.
      */
-    virtual size_t getSampleRate() const = 0;
+    virtual sv_samplerate_t getSampleRate() const = 0;
 
     /**
      * Return the frame rate of the underlying material, if the model
      * itself has already been resampled.
      */
-    virtual size_t getNativeRate() const { return getSampleRate(); }
+    virtual sv_samplerate_t getNativeRate() const { return getSampleRate(); }
 
     /**
      * Return the "work title" of the model, if known.
@@ -91,21 +91,25 @@
     virtual QString getTypeName() const = 0;
 
     /**
-     * Return a copy of this model.
-     *
-     * If the model is not editable, this may be effectively a shallow
-     * copy.  If the model is editable, however, this operation must
-     * properly copy all of the model's editable data.
-     *
-     * In general this operation is not useful for non-editable dense
-     * models such as waveforms, because there may be no efficient
-     * copy operation implemented -- for such models it is better not
-     * to copy at all.
-     *
-     * Caller owns the returned value.
+     * Mark the model as abandoning. This means that the application
+     * no longer needs it, so it can stop doing any background
+     * calculations it may be involved in. Note that as far as the
+     * model API is concerned, this does nothing more than tell the
+     * model to return true from isAbandoning().  The actual response
+     * to this will depend on the model's context -- it's possible
+     * nothing at all will change.
      */
-    virtual Model *clone() const = 0;
-    
+    virtual void abandon() {
+        m_abandoning = true;
+    }
+
+    /**
+     * Query whether the model has been marked as abandoning.
+     */
+    virtual bool isAbandoning() const { 
+        return m_abandoning;
+    }
+
     /**
      * Return true if the model has finished loading or calculating
      * all its data, for a model that is capable of calculating in a
@@ -117,7 +121,8 @@
      * through it an estimated percentage value showing how far
      * through the background operation it thinks it is (for progress
      * reporting).  If it has no way to calculate progress, it may
-     * return the special value COMPLETION_UNKNOWN.
+     * return the special value COMPLETION_UNKNOWN.  See also
+     * getCompletion().
      */
     virtual bool isReady(int *completion = 0) const {
 	bool ok = isOK();
@@ -180,13 +185,13 @@
      * Return the frame number of the reference model that corresponds
      * to the given frame number in this model.
      */
-    virtual size_t alignToReference(size_t frame) const;
+    virtual sv_frame_t alignToReference(sv_frame_t frame) const;
 
     /**
      * Return the frame number in this model that corresponds to the
      * given frame number of the reference model.
      */
-    virtual size_t alignFromReference(size_t referenceFrame) const;
+    virtual sv_frame_t alignFromReference(sv_frame_t referenceFrame) const;
 
     /**
      * Return the completion percentage for the alignment model: 100
@@ -214,11 +219,20 @@
                        QString extraAttributes = "") const;
 
     virtual QString toDelimitedDataString(QString delimiter) const {
-        return toDelimitedDataString(delimiter, getStartFrame(), getEndFrame());
+        return toDelimitedDataStringSubset
+            (delimiter, getStartFrame(), getEndFrame() + 1);
     }
-    virtual QString toDelimitedDataString(QString, size_t f0, size_t f1) const {
+    virtual QString toDelimitedDataStringWithOptions(QString delimiter, DataExportOptions opts) const {
+        return toDelimitedDataStringSubsetWithOptions
+            (delimiter, opts, getStartFrame(), getEndFrame() + 1);
+    }
+    virtual QString toDelimitedDataStringSubset(QString, sv_frame_t /* f0 */, sv_frame_t /* f1 */) const {
         return "";
     }
+    virtual QString toDelimitedDataStringSubsetWithOptions(QString delimiter, DataExportOptions, sv_frame_t f0, sv_frame_t f1) const {
+        // Default implementation supports no options
+        return toDelimitedDataStringSubset(delimiter, f0, f1);
+    }
 
 public slots:
     void aboutToDelete();
@@ -235,7 +249,7 @@
      * Emitted when a model has been edited (or more data retrieved
      * from cache, in the case of a cached model that generates slowly)
      */
-    void modelChanged(size_t startFrame, size_t endFrame);
+    void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
 
     /**
      * Emitted when some internal processing has advanced a stage, but
@@ -268,7 +282,11 @@
     void aboutToBeDeleted();
 
 protected:
-    Model() : m_sourceModel(0), m_alignment(0), m_aboutToDelete(false) { }
+    Model() : 
+        m_sourceModel(0), 
+        m_alignment(0), 
+        m_abandoning(false), 
+        m_aboutToDelete(false) { }
 
     // Not provided.
     Model(const Model &);
@@ -277,6 +295,7 @@
     Model *m_sourceModel;
     AlignmentModel *m_alignment;
     QString m_typeUri;
+    bool m_abandoning;
     bool m_aboutToDelete;
 };
 
--- a/data/model/ModelDataTableModel.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/ModelDataTableModel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -31,8 +31,8 @@
     Model *baseModel = dynamic_cast<Model *>(m);
 
     connect(baseModel, SIGNAL(modelChanged()), this, SLOT(modelChanged()));
-    connect(baseModel, SIGNAL(modelChanged(size_t, size_t)),
-            this, SLOT(modelChanged(size_t, size_t)));
+    connect(baseModel, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+            this, SLOT(modelChangedWithin(sv_frame_t, sv_frame_t)));
     connect(baseModel, SIGNAL(aboutToBeDeleted()),
             this, SLOT(modelAboutToBeDeleted()));
 }
@@ -105,7 +105,7 @@
 }
 
 Qt::ItemFlags
-ModelDataTableModel::flags(const QModelIndex &index) const
+ModelDataTableModel::flags(const QModelIndex &) const
 {
     Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsEditable |
         Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsSelectable;
@@ -127,13 +127,13 @@
 }
 
 QModelIndex
-ModelDataTableModel::index(int row, int column, const QModelIndex &parent) const
+ModelDataTableModel::index(int row, int column, const QModelIndex &) const
 {
     return createIndex(row, column, (void *)0);
 }
 
 QModelIndex
-ModelDataTableModel::parent(const QModelIndex &index) const
+ModelDataTableModel::parent(const QModelIndex &) const
 {
     return QModelIndex();
 }
@@ -155,14 +155,14 @@
 }
 
 QModelIndex 
-ModelDataTableModel::getModelIndexForFrame(size_t frame) const
+ModelDataTableModel::getModelIndexForFrame(sv_frame_t frame) const
 {
     if (!m_model) return createIndex(0, 0);
     int row = m_model->getRowForFrame(frame);
     return createIndex(getSorted(row), 0, (void *)0);
 }
 
-size_t 
+sv_frame_t
 ModelDataTableModel::getFrameForModelIndex(const QModelIndex &index) const
 {
     if (!m_model) return 0;
@@ -219,7 +219,7 @@
 }
 
 void 
-ModelDataTableModel::modelChanged(size_t f0, size_t f1)
+ModelDataTableModel::modelChangedWithin(sv_frame_t, sv_frame_t)
 {
     //!!! inefficient
     clearSort();
@@ -250,7 +250,7 @@
         resort();
     }
     int result = 0;
-    if (row >= 0 && row < m_sort.size()) {
+    if (row >= 0 && row < (int)m_sort.size()) {
         result = m_sort[row];
     }
     if (m_sortOrdering == Qt::DescendingOrder) {
@@ -278,7 +278,7 @@
     }
 
     int result = 0;
-    if (row >= 0 && row < m_sort.size()) {
+    if (row >= 0 && row < (int)m_sort.size()) {
         if (m_sortOrdering == Qt::AscendingOrder) {
             result = m_rsort[row];
         } else {
@@ -309,7 +309,7 @@
 
     // rsort maps from sorted row number to original row number
 
-    for (int i = 0; i < m_rsort.size(); ++i) {
+    for (int i = 0; i < (int)m_rsort.size(); ++i) {
         tmp[m_rsort[i]] = i;
     }
 
--- a/data/model/ModelDataTableModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/ModelDataTableModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -20,6 +20,8 @@
 
 #include <vector>
 
+#include "base/BaseTypes.h"
+
 class TabularModel;
 class Command;
 
@@ -51,8 +53,8 @@
     int rowCount(const QModelIndex &parent = QModelIndex()) const;
     int columnCount(const QModelIndex &parent = QModelIndex()) const;
 
-    QModelIndex getModelIndexForFrame(size_t frame) const;
-    size_t getFrameForModelIndex(const QModelIndex &) const;
+    QModelIndex getModelIndexForFrame(sv_frame_t frame) const;
+    sv_frame_t getFrameForModelIndex(const QModelIndex &) const;
 
     void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
 
@@ -62,14 +64,14 @@
     int getCurrentRow() const;
 
 signals:
-    void frameSelected(size_t);
+    void frameSelected(int);
     void addCommand(Command *);
     void currentChanged(const QModelIndex &);
     void modelRemoved();
 
 protected slots:
     void modelChanged();
-    void modelChanged(size_t, size_t);
+    void modelChangedWithin(sv_frame_t, sv_frame_t);
     void modelAboutToBeDeleted();
 
 protected:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/NoteData.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,54 @@
+/* -*- 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 NOTE_DATA_H
+#define NOTE_DATA_H
+
+#include <vector>
+
+#include "base/Pitch.h"
+
+struct NoteData
+{
+    NoteData(sv_frame_t _start, sv_frame_t _dur, int _mp, int _vel) :
+	start(_start), duration(_dur), midiPitch(_mp), frequency(0),
+	isMidiPitchQuantized(true), velocity(_vel), channel(0) { };
+            
+    sv_frame_t start;       // audio sample frame
+    sv_frame_t duration;    // in audio sample frames
+    int midiPitch;   // 0-127
+    float frequency; // Hz, to be used if isMidiPitchQuantized false
+    bool isMidiPitchQuantized;
+    int velocity;    // MIDI-style 0-127
+    int channel;     // MIDI 0-15
+
+    float getFrequency() const {
+        if (isMidiPitchQuantized) {
+            return float(Pitch::getFrequencyForPitch(midiPitch));
+        } else {
+            return frequency;
+        }
+    }
+};
+
+typedef std::vector<NoteData> NoteList;
+
+class NoteExportable
+{
+public:
+    virtual NoteList getNotes() const = 0;
+    virtual NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const = 0;
+};
+
+#endif
--- a/data/model/NoteModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/NoteModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -17,8 +17,10 @@
 #define _NOTE_MODEL_H_
 
 #include "IntervalModel.h"
+#include "NoteData.h"
 #include "base/RealTime.h"
 #include "base/PlayParameterRepository.h"
+#include "base/Pitch.h"
 
 /**
  * NoteModel -- a concrete IntervalModel for notes.
@@ -36,15 +38,15 @@
 struct Note
 {
 public:
-    Note(long _frame) : frame(_frame), value(0.0f), duration(0), level(1.f) { }
-    Note(long _frame, float _value, size_t _duration, float _level, QString _label) :
+    Note(sv_frame_t _frame) : frame(_frame), value(0.0f), duration(0), level(1.f) { }
+    Note(sv_frame_t _frame, float _value, sv_frame_t _duration, float _level, QString _label) :
 	frame(_frame), value(_value), duration(_duration), level(_level), label(_label) { }
 
     int getDimensions() const { return 3; }
 
-    long frame;
+    sv_frame_t frame;
     float value;
-    size_t duration;
+    sv_frame_t duration;
     float level;
     QString label;
 
@@ -60,13 +62,14 @@
             .arg(XmlExportable::encodeEntities(label)).arg(extraAttributes);
     }
 
-    QString toDelimitedDataString(QString delimiter, size_t sampleRate) const
-    {
+    QString toDelimitedDataString(QString delimiter, DataExportOptions opts, sv_samplerate_t sampleRate) const {
         QStringList list;
         list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
         list << QString("%1").arg(value);
         list << RealTime::frame2RealTime(duration, sampleRate).toString().c_str();
-        list << QString("%1").arg(level);
+        if (!(opts & DataExportOmitLevels)) {
+            list << QString("%1").arg(level);
+        }
         if (label != "") list << label;
         return list.join(delimiter);
     }
@@ -91,12 +94,12 @@
 };
 
 
-class NoteModel : public IntervalModel<Note>
+class NoteModel : public IntervalModel<Note>, public NoteExportable
 {
     Q_OBJECT
     
 public:
-    NoteModel(size_t sampleRate, size_t resolution,
+    NoteModel(sv_samplerate_t sampleRate, int resolution,
 	      bool notifyOnAdd = true) :
 	IntervalModel<Note>(sampleRate, resolution, notifyOnAdd),
 	m_valueQuantization(0)
@@ -104,7 +107,7 @@
 	PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
-    NoteModel(size_t sampleRate, size_t resolution,
+    NoteModel(sv_samplerate_t sampleRate, int resolution,
 	      float valueMinimum, float valueMaximum,
 	      bool notifyOnAdd = true) :
 	IntervalModel<Note>(sampleRate, resolution,
@@ -127,14 +130,9 @@
 
     virtual bool canPlay() const { return true; }
 
-    virtual QString getDefaultPlayPluginId() const
+    virtual QString getDefaultPlayClipId() const
     {
-        return "dssi:_builtin:sample_player";
-    }
-
-    virtual QString getDefaultPlayPluginConfiguration() const
-    {
-        return "<plugin program=\"piano\"/>";
+        return "elecpiano";
     }
 
     virtual void toXml(QTextStream &out,
@@ -205,7 +203,7 @@
         command->deletePoint(point);
 
         switch (column) {
-        case 4: point.level = value.toDouble(); break;
+        case 4: point.level = float(value.toDouble()); break;
         case 5: point.label = value.toString(); break;
         }
 
@@ -219,6 +217,48 @@
         return SortNumeric;
     }
 
+    /**
+     * NoteExportable methods.
+     */
+
+    NoteList getNotes() const {
+        return getNotesWithin(getStartFrame(), getEndFrame());
+    }
+
+    NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const {
+        
+	PointList points = getPoints(startFrame, endFrame);
+        NoteList notes;
+
+        for (PointList::iterator pli =
+		 points.begin(); pli != points.end(); ++pli) {
+
+	    sv_frame_t duration = pli->duration;
+            if (duration == 0 || duration == 1) {
+                duration = sv_frame_t(getSampleRate() / 20);
+            }
+
+            int pitch = int(lrintf(pli->value));
+            
+            int velocity = 100;
+            if (pli->level > 0.f && pli->level <= 1.f) {
+                velocity = int(lrintf(pli->level * 127));
+            }
+
+            NoteData note(pli->frame, duration, pitch, velocity);
+
+            if (getScaleUnits() == "Hz") {
+                note.frequency = pli->value;
+                note.midiPitch = Pitch::getPitchForFrequency(note.frequency);
+                note.isMidiPitchQuantized = false;
+            }
+        
+            notes.push_back(note);
+        }
+        
+        return notes;
+    }
+
 protected:
     float m_valueQuantization;
 };
--- a/data/model/PathModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/PathModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -19,20 +19,21 @@
 #include "Model.h"
 #include "SparseModel.h"
 #include "base/RealTime.h"
+#include "base/BaseTypes.h"
 
 #include <QStringList>
 
 
 struct PathPoint
 {
-    PathPoint(long _frame) : frame(_frame), mapframe(_frame) { }
-    PathPoint(long _frame, long _mapframe) :
+    PathPoint(sv_frame_t _frame) : frame(_frame), mapframe(_frame) { }
+    PathPoint(sv_frame_t _frame, sv_frame_t _mapframe) :
         frame(_frame), mapframe(_mapframe) { }
 
     int getDimensions() const { return 2; }
 
-    long frame;
-    long mapframe;
+    sv_frame_t frame;
+    sv_frame_t mapframe;
 
     QString getLabel() const { return ""; }
 
@@ -42,8 +43,8 @@
             .arg(indent).arg(frame).arg(mapframe).arg(extraAttributes);
     }
         
-    QString toDelimitedDataString(QString delimiter,
-                                  size_t sampleRate) const {
+    QString toDelimitedDataString(QString delimiter, DataExportOptions,
+                                  sv_samplerate_t sampleRate) const {
         QStringList list;
         list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
         list << QString("%1").arg(mapframe);
@@ -67,7 +68,7 @@
 class PathModel : public SparseModel<PathPoint>
 {
 public:
-    PathModel(size_t sampleRate, size_t resolution, bool notify = true) :
+    PathModel(sv_samplerate_t sampleRate, int resolution, bool notify = true) :
         SparseModel<PathPoint>(sampleRate, resolution, notify) { }
 
     virtual void toXml(QTextStream &out,
--- a/data/model/PowerOfSqrtTwoZoomConstraint.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/PowerOfSqrtTwoZoomConstraint.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -19,54 +19,55 @@
 #include <cmath>
 
 
-size_t
-PowerOfSqrtTwoZoomConstraint::getNearestBlockSize(size_t blockSize,
+int
+PowerOfSqrtTwoZoomConstraint::getNearestBlockSize(int blockSize,
 						  RoundingDirection dir) const
 {
     int type, power;
-    size_t rv = getNearestBlockSize(blockSize, type, power, dir);
+    int rv = getNearestBlockSize(blockSize, type, power, dir);
     return rv;
 }
 
-size_t
-PowerOfSqrtTwoZoomConstraint::getNearestBlockSize(size_t blockSize,
+int
+PowerOfSqrtTwoZoomConstraint::getNearestBlockSize(int blockSize,
 						  int &type, 
 						  int &power,
 						  RoundingDirection dir) const
 {
 //    cerr << "given " << blockSize << endl;
 
-    size_t minCachePower = getMinCachePower();
+    int minCachePower = getMinCachePower();
 
-    if (blockSize < (1U << minCachePower)) {
+    if (blockSize < (1 << minCachePower)) {
 	type = -1;
 	power = 0;
 	float val = 1.0, prevVal = 1.0;
 	while (val + 0.01 < blockSize) {
 	    prevVal = val;
-	    val *= sqrt(2.f);
+	    val *= sqrtf(2.f);
 	}
-	size_t rval;
-	if (dir == RoundUp) rval = size_t(val + 0.01);
-	else if (dir == RoundDown) rval = size_t(prevVal + 0.01);
-	else if (val - blockSize < blockSize - prevVal) rval = size_t(val + 0.01);
-	else rval = size_t(prevVal + 0.01);
+	int rval;
+	if (dir == RoundUp) rval = int(val + 0.01f);
+	else if (dir == RoundDown) rval = int(prevVal + 0.01f);
+	else if (val - float(blockSize) <
+                 float(blockSize) - prevVal) rval = int(val + 0.01f);
+	else rval = int(prevVal + 0.01);
 //	SVDEBUG << "returning " << rval << endl;
 	return rval;
     }
 
-    unsigned int prevBase = (1 << minCachePower);
-    unsigned int prevPower = minCachePower;
-    unsigned int prevType = 0;
+    int prevBase = (1 << minCachePower);
+    int prevPower = minCachePower;
+    int prevType = 0;
 
-    size_t result = 0;
+    int result = 0;
 
     for (unsigned int i = 0; ; ++i) {
 
 	power = minCachePower + i/2;
 	type = i % 2;
 
-	unsigned int base;
+	int base;
 	if (type == 0) {
 	    base = (1 << power);
 	} else {
--- a/data/model/PowerOfSqrtTwoZoomConstraint.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/PowerOfSqrtTwoZoomConstraint.h	Wed Apr 20 12:06:28 2016 +0100
@@ -21,17 +21,17 @@
 class PowerOfSqrtTwoZoomConstraint : virtual public ZoomConstraint
 {
 public:
-    virtual size_t getNearestBlockSize(size_t requestedBlockSize,
+    virtual int getNearestBlockSize(int requestedBlockSize,
 				       RoundingDirection dir = RoundNearest)
 	const;
     
-    virtual size_t getNearestBlockSize(size_t requestedBlockSize,
+    virtual int getNearestBlockSize(int requestedBlockSize,
 				       int &type,
 				       int &power,
 				       RoundingDirection dir = RoundNearest)
 	const;
 	
-    virtual size_t getMinCachePower() const { return 6; }
+    virtual int getMinCachePower() const { return 6; }
 };
 
 #endif
--- a/data/model/PowerOfTwoZoomConstraint.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/PowerOfTwoZoomConstraint.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -15,13 +15,13 @@
 
 #include "PowerOfTwoZoomConstraint.h"
 
-size_t
-PowerOfTwoZoomConstraint::getNearestBlockSize(size_t req,
+int
+PowerOfTwoZoomConstraint::getNearestBlockSize(int req,
 					      RoundingDirection dir) const
 {
-    size_t result = 0;
+    int result = 0;
 
-    for (size_t bs = 1; ; bs *= 2) {
+    for (int bs = 1; ; bs *= 2) {
 	if (bs >= req) {
 	    if (dir == RoundNearest) {
 		if (bs - req < req - bs/2) {
--- a/data/model/PowerOfTwoZoomConstraint.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/PowerOfTwoZoomConstraint.h	Wed Apr 20 12:06:28 2016 +0100
@@ -21,7 +21,7 @@
 class PowerOfTwoZoomConstraint : virtual public ZoomConstraint
 {
 public:
-    virtual size_t getNearestBlockSize(size_t requestedBlockSize,
+    virtual int getNearestBlockSize(int requestedBlockSize,
 				       RoundingDirection dir = RoundNearest)
 	const;
 };
--- a/data/model/RangeSummarisableTimeValueModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/RangeSummarisableTimeValueModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -41,21 +41,33 @@
     {
     public:
         Range() : 
-            m_min(0.f), m_max(0.f), m_absmean(0.f) { }
+            m_new(true), m_min(0.f), m_max(0.f), m_absmean(0.f) { }
         Range(const Range &r) : 
-            m_min(r.m_min), m_max(r.m_max), m_absmean(r.m_absmean) { }
+            m_new(true), m_min(r.m_min), m_max(r.m_max), m_absmean(r.m_absmean) { }
         Range(float min, float max, float absmean) :
-            m_min(min), m_max(max), m_absmean(absmean) { }
+            m_new(true), m_min(min), m_max(max), m_absmean(absmean) { }
 
         float min() const { return m_min; }
         float max() const { return m_max; }
         float absmean() const { return m_absmean; }
 
-        void setMin(float min) { m_min = min; }
-        void setMax(float max) { m_max = max; }
+        void setMin(float min) { m_min = min; m_new = false; }
+        void setMax(float max) { m_max = max; m_new = false; }
         void setAbsmean(float absmean) { m_absmean = absmean; }
 
+        void sample(float s) {
+            if (m_new) {
+                m_min = s;
+                m_max = s;
+                m_new = false;
+            } else {
+                if (s < m_min) m_min = s;
+                if (s > m_max) m_max = s;
+            }
+        }
+        
     private:
+        bool m_new;
         float m_min;
         float m_max;
         float m_absmean;
@@ -74,18 +86,18 @@
      * parameter so as to return the block size that was actually
      * obtained.
      */
-    virtual void getSummaries(size_t channel, size_t start, size_t count,
+    virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count,
                               RangeBlock &ranges,
-                              size_t &blockSize) const = 0;
+                              int &blockSize) const = 0;
 
     /**
      * Return the range from the given start frame, corresponding to
      * the given number of underlying sample frames, summarised at a
      * block size equal to the distance between start and end frames.
      */
-    virtual Range getSummary(size_t channel, size_t start, size_t count) const = 0;
+    virtual Range getSummary(int channel, sv_frame_t start, sv_frame_t count) const = 0;
 
-    virtual size_t getSummaryBlockSize(size_t desired) const = 0;
+    virtual int getSummaryBlockSize(int desired) const = 0;
 
     QString getTypeName() const { return tr("Range-Summarisable Time-Value"); }
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/ReadOnlyWaveFileModel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,719 @@
+/* -*- 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.
+*/
+
+#include "ReadOnlyWaveFileModel.h"
+
+#include "fileio/AudioFileReader.h"
+#include "fileio/AudioFileReaderFactory.h"
+
+#include "system/System.h"
+
+#include "base/Preferences.h"
+
+#include <QFileInfo>
+#include <QTextStream>
+
+#include <iostream>
+#include <unistd.h>
+#include <cmath>
+#include <sndfile.h>
+
+#include <cassert>
+
+using namespace std;
+
+//#define DEBUG_WAVE_FILE_MODEL 1
+
+PowerOfSqrtTwoZoomConstraint
+ReadOnlyWaveFileModel::m_zoomConstraint;
+
+ReadOnlyWaveFileModel::ReadOnlyWaveFileModel(FileSource source, sv_samplerate_t targetRate) :
+    m_source(source),
+    m_path(source.getLocation()),
+    m_reader(0),
+    m_myReader(true),
+    m_startFrame(0),
+    m_fillThread(0),
+    m_updateTimer(0),
+    m_lastFillExtent(0),
+    m_exiting(false),
+    m_lastDirectReadStart(0),
+    m_lastDirectReadCount(0)
+{
+    m_source.waitForData();
+    if (m_source.isOK()) {
+        bool normalise = Preferences::getInstance()->getNormaliseAudio();
+        m_reader = AudioFileReaderFactory::createThreadingReader
+            (m_source, targetRate, normalise);
+        if (m_reader) {
+            SVDEBUG << "ReadOnlyWaveFileModel::ReadOnlyWaveFileModel: reader rate: "
+                      << m_reader->getSampleRate() << endl;
+        }
+    }
+    if (m_reader) setObjectName(m_reader->getTitle());
+    if (objectName() == "") setObjectName(QFileInfo(m_path).fileName());
+    if (isOK()) fillCache();
+}
+
+ReadOnlyWaveFileModel::ReadOnlyWaveFileModel(FileSource source, AudioFileReader *reader) :
+    m_source(source),
+    m_path(source.getLocation()),
+    m_reader(0),
+    m_myReader(false),
+    m_startFrame(0),
+    m_fillThread(0),
+    m_updateTimer(0),
+    m_lastFillExtent(0),
+    m_exiting(false)
+{
+    m_reader = reader;
+    if (m_reader) setObjectName(m_reader->getTitle());
+    if (objectName() == "") setObjectName(QFileInfo(m_path).fileName());
+    fillCache();
+}
+
+ReadOnlyWaveFileModel::~ReadOnlyWaveFileModel()
+{
+    m_exiting = true;
+    if (m_fillThread) m_fillThread->wait();
+    if (m_myReader) delete m_reader;
+    m_reader = 0;
+}
+
+bool
+ReadOnlyWaveFileModel::isOK() const
+{
+    return m_reader && m_reader->isOK();
+}
+
+bool
+ReadOnlyWaveFileModel::isReady(int *completion) const
+{
+    bool ready = (isOK() && (m_fillThread == 0));
+    double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame());
+    static int prevCompletion = 0;
+    if (completion) {
+        *completion = int(c * 100.0 + 0.01);
+        if (m_reader) {
+            int decodeCompletion = m_reader->getDecodeCompletion();
+            if (decodeCompletion < 90) *completion = decodeCompletion;
+            else *completion = min(*completion, decodeCompletion);
+        }
+        if (*completion != 0 &&
+            *completion != 100 &&
+            prevCompletion != 0 &&
+            prevCompletion > *completion) {
+            // just to avoid completion going backwards
+            *completion = prevCompletion;
+        }
+        prevCompletion = *completion;
+    }
+#ifdef DEBUG_WAVE_FILE_MODEL
+    SVDEBUG << "ReadOnlyWaveFileModel::isReady(): ready = " << ready << ", completion = " << (completion ? *completion : -1) << endl;
+#endif
+    return ready;
+}
+
+sv_frame_t
+ReadOnlyWaveFileModel::getFrameCount() const
+{
+    if (!m_reader) return 0;
+    return m_reader->getFrameCount();
+}
+
+int
+ReadOnlyWaveFileModel::getChannelCount() const
+{
+    if (!m_reader) return 0;
+    return m_reader->getChannelCount();
+}
+
+sv_samplerate_t
+ReadOnlyWaveFileModel::getSampleRate() const 
+{
+    if (!m_reader) return 0;
+    return m_reader->getSampleRate();
+}
+
+sv_samplerate_t
+ReadOnlyWaveFileModel::getNativeRate() const 
+{
+    if (!m_reader) return 0;
+    sv_samplerate_t rate = m_reader->getNativeRate();
+    if (rate == 0) rate = getSampleRate();
+    return rate;
+}
+
+QString
+ReadOnlyWaveFileModel::getTitle() const
+{
+    QString title;
+    if (m_reader) title = m_reader->getTitle();
+    if (title == "") title = objectName();
+    return title;
+}
+
+QString
+ReadOnlyWaveFileModel::getMaker() const
+{
+    if (m_reader) return m_reader->getMaker();
+    return "";
+}
+
+QString
+ReadOnlyWaveFileModel::getLocation() const
+{
+    if (m_reader) return m_reader->getLocation();
+    return "";
+}
+
+QString
+ReadOnlyWaveFileModel::getLocalFilename() const
+{
+    if (m_reader) return m_reader->getLocalFilename();
+    return "";
+}
+    
+vector<float>
+ReadOnlyWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const
+{
+    // Read directly from the file.  This is used for e.g. audio
+    // playback or input to transforms.
+
+#ifdef DEBUG_WAVE_FILE_MODEL
+    cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << endl;
+#endif
+
+    int channels = getChannelCount();
+
+    if (channel >= channels) {
+        cerr << "ERROR: WaveFileModel::getData: channel ("
+             << channel << ") >= channel count (" << channels << ")"
+             << endl;
+        return {};
+    }
+
+    if (!m_reader || !m_reader->isOK() || count == 0) {
+        return {};
+    }
+
+    if (start >= m_startFrame) {
+        start -= m_startFrame;
+    } else {
+        if (count <= m_startFrame - start) {
+            return {};
+        } else {
+            count -= (m_startFrame - start);
+            start = 0;
+        }
+    }
+
+    vector<float> interleaved = m_reader->getInterleavedFrames(start, count);
+    if (channels == 1) return interleaved;
+
+    sv_frame_t obtained = interleaved.size() / channels;
+    
+    vector<float> result(obtained, 0.f);
+    
+    if (channel != -1) {
+        // get a single channel
+        for (int i = 0; i < obtained; ++i) {
+            result[i] = interleaved[i * channels + channel];
+        }
+    } else {
+        // channel == -1, mix down all channels
+        for (int c = 0; c < channels; ++c) {
+            for (int i = 0; i < obtained; ++i) {
+                result[i] += interleaved[i * channels + c];
+            }
+        }
+    }
+
+    return result;
+}
+
+vector<vector<float>>
+ReadOnlyWaveFileModel::getMultiChannelData(int fromchannel, int tochannel,
+                                           sv_frame_t start, sv_frame_t count) const
+{
+    // Read directly from the file.  This is used for e.g. audio
+    // playback or input to transforms.
+
+#ifdef DEBUG_WAVE_FILE_MODEL
+    cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << endl;
+#endif
+
+    int channels = getChannelCount();
+
+    if (fromchannel > tochannel) {
+        cerr << "ERROR: ReadOnlyWaveFileModel::getData: fromchannel ("
+                  << fromchannel << ") > tochannel (" << tochannel << ")"
+                  << endl;
+        return {};
+    }
+
+    if (tochannel >= channels) {
+        cerr << "ERROR: ReadOnlyWaveFileModel::getData: tochannel ("
+                  << tochannel << ") >= channel count (" << channels << ")"
+                  << endl;
+        return {};
+    }
+
+    if (!m_reader || !m_reader->isOK() || count == 0) {
+        return {};
+    }
+
+    int reqchannels = (tochannel - fromchannel) + 1;
+
+    if (start >= m_startFrame) {
+        start -= m_startFrame;
+    } else {
+        if (count <= m_startFrame - start) {
+            return {};
+        } else {
+            count -= (m_startFrame - start);
+            start = 0;
+        }
+    }
+
+    vector<float> interleaved = m_reader->getInterleavedFrames(start, count);
+    if (channels == 1) return { interleaved };
+
+    sv_frame_t obtained = interleaved.size() / channels;
+    vector<vector<float>> result(reqchannels, vector<float>(obtained, 0.f));
+
+    for (int c = fromchannel; c <= tochannel; ++c) {
+        int destc = c - fromchannel;
+        for (int i = 0; i < obtained; ++i) {
+            result[destc][i] = interleaved[i * channels + c];
+        }
+    }
+    
+    return result;
+}
+
+int
+ReadOnlyWaveFileModel::getSummaryBlockSize(int desired) const
+{
+    int cacheType = 0;
+    int power = m_zoomConstraint.getMinCachePower();
+    int roundedBlockSize = m_zoomConstraint.getNearestBlockSize
+        (desired, cacheType, power, ZoomConstraint::RoundDown);
+    if (cacheType != 0 && cacheType != 1) {
+        // We will be reading directly from file, so can satisfy any
+        // blocksize requirement
+        return desired;
+    } else {
+        return roundedBlockSize;
+    }
+}    
+
+void
+ReadOnlyWaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count,
+                                    RangeBlock &ranges, int &blockSize) const
+{
+    ranges.clear();
+    if (!isOK()) return;
+    ranges.reserve((count / blockSize) + 1);
+
+    if (start > m_startFrame) start -= m_startFrame;
+    else if (count <= m_startFrame - start) return;
+    else {
+        count -= (m_startFrame - start);
+        start = 0;
+    }
+
+    int cacheType = 0;
+    int power = m_zoomConstraint.getMinCachePower();
+    int roundedBlockSize = m_zoomConstraint.getNearestBlockSize
+        (blockSize, cacheType, power, ZoomConstraint::RoundDown);
+
+    int channels = getChannelCount();
+
+    if (cacheType != 0 && cacheType != 1) {
+
+	// We need to read directly from the file.  We haven't got
+	// this cached.  Hope the requested area is small.  This is
+	// not optimal -- we'll end up reading the same frames twice
+	// for stereo files, in two separate calls to this method.
+	// We could fairly trivially handle this for most cases that
+	// matter by putting a single cache in getInterleavedFrames
+	// for short queries.
+
+        m_directReadMutex.lock();
+
+        if (m_lastDirectReadStart != start ||
+            m_lastDirectReadCount != count ||
+            m_directRead.empty()) {
+
+            m_directRead = m_reader->getInterleavedFrames(start, count);
+            m_lastDirectReadStart = start;
+            m_lastDirectReadCount = count;
+        }
+
+	float max = 0.0, min = 0.0, total = 0.0;
+	sv_frame_t i = 0, got = 0;
+
+	while (i < count) {
+
+	    sv_frame_t index = i * channels + channel;
+	    if (index >= (sv_frame_t)m_directRead.size()) break;
+            
+	    float sample = m_directRead[index];
+            if (sample > max || got == 0) max = sample;
+	    if (sample < min || got == 0) min = sample;
+            total += fabsf(sample);
+
+	    ++i;
+            ++got;
+            
+            if (got == blockSize) {
+                ranges.push_back(Range(min, max, total / float(got)));
+                min = max = total = 0.0f;
+                got = 0;
+	    }
+	}
+
+        m_directReadMutex.unlock();
+
+	if (got > 0) {
+            ranges.push_back(Range(min, max, total / float(got)));
+	}
+
+	return;
+
+    } else {
+
+	QMutexLocker locker(&m_mutex);
+    
+	const RangeBlock &cache = m_cache[cacheType];
+
+        blockSize = roundedBlockSize;
+
+	sv_frame_t cacheBlock, div;
+
+        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;
+
+	float max = 0.0, min = 0.0, total = 0.0;
+	sv_frame_t i = 0, got = 0;
+
+#ifdef DEBUG_WAVE_FILE_MODEL
+	cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", count " << count << " (frame count " << getFrameCount() << "), power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl;
+#endif
+
+	for (i = 0; i <= endIndex - startIndex; ) {
+        
+	    sv_frame_t index = (i + startIndex) * channels + channel;
+	    if (!in_range_for(cache, index)) break;
+            
+            const Range &range = cache[index];
+            if (range.max() > max || got == 0) max = range.max();
+            if (range.min() < min || got == 0) min = range.min();
+            total += range.absmean();
+            
+	    ++i;
+            ++got;
+            
+	    if (got == div) {
+		ranges.push_back(Range(min, max, total / float(got)));
+                min = max = total = 0.0f;
+                got = 0;
+	    }
+	}
+		
+	if (got > 0) {
+            ranges.push_back(Range(min, max, total / float(got)));
+	}
+    }
+
+#ifdef DEBUG_WAVE_FILE_MODEL
+    cerr << "returning " << ranges.size() << " ranges" << endl;
+#endif
+    return;
+}
+
+ReadOnlyWaveFileModel::Range
+ReadOnlyWaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const
+{
+    Range range;
+    if (!isOK()) return range;
+
+    if (start > m_startFrame) start -= m_startFrame;
+    else if (count <= m_startFrame - start) return range;
+    else {
+        count -= (m_startFrame - start);
+        start = 0;
+    }
+
+    int blockSize;
+    for (blockSize = 1; blockSize <= count; blockSize *= 2);
+    if (blockSize > 1) blockSize /= 2;
+
+    bool first = false;
+
+    sv_frame_t blockStart = (start / blockSize) * blockSize;
+    sv_frame_t blockEnd = ((start + count) / blockSize) * blockSize;
+
+    if (blockStart < start) blockStart += blockSize;
+        
+    if (blockEnd > blockStart) {
+        RangeBlock ranges;
+        getSummaries(channel, blockStart, blockEnd - blockStart, ranges, blockSize);
+        for (int i = 0; i < (int)ranges.size(); ++i) {
+            if (first || ranges[i].min() < range.min()) range.setMin(ranges[i].min());
+            if (first || ranges[i].max() > range.max()) range.setMax(ranges[i].max());
+            if (first || ranges[i].absmean() < range.absmean()) range.setAbsmean(ranges[i].absmean());
+            first = false;
+        }
+    }
+
+    if (blockStart > start) {
+        Range startRange = getSummary(channel, start, blockStart - start);
+        range.setMin(min(range.min(), startRange.min()));
+        range.setMax(max(range.max(), startRange.max()));
+        range.setAbsmean(min(range.absmean(), startRange.absmean()));
+    }
+
+    if (blockEnd < start + count) {
+        Range endRange = getSummary(channel, blockEnd, start + count - blockEnd);
+        range.setMin(min(range.min(), endRange.min()));
+        range.setMax(max(range.max(), endRange.max()));
+        range.setAbsmean(min(range.absmean(), endRange.absmean()));
+    }
+
+    return range;
+}
+
+void
+ReadOnlyWaveFileModel::fillCache()
+{
+    m_mutex.lock();
+
+    m_updateTimer = new QTimer(this);
+    connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut()));
+    m_updateTimer->start(100);
+
+    m_fillThread = new RangeCacheFillThread(*this);
+    connect(m_fillThread, SIGNAL(finished()), this, SLOT(cacheFilled()));
+
+    m_mutex.unlock();
+    m_fillThread->start();
+
+#ifdef DEBUG_WAVE_FILE_MODEL
+    SVDEBUG << "ReadOnlyWaveFileModel::fillCache: started fill thread" << endl;
+#endif
+}   
+
+void
+ReadOnlyWaveFileModel::fillTimerTimedOut()
+{
+    if (m_fillThread) {
+	sv_frame_t fillExtent = m_fillThread->getFillExtent();
+#ifdef DEBUG_WAVE_FILE_MODEL
+        SVDEBUG << "ReadOnlyWaveFileModel::fillTimerTimedOut: extent = " << fillExtent << endl;
+#endif
+	if (fillExtent > m_lastFillExtent) {
+	    emit modelChangedWithin(m_lastFillExtent, fillExtent);
+	    m_lastFillExtent = fillExtent;
+	}
+    } else {
+#ifdef DEBUG_WAVE_FILE_MODEL
+        SVDEBUG << "ReadOnlyWaveFileModel::fillTimerTimedOut: no thread" << endl;
+#endif
+	emit modelChanged();
+    }
+}
+
+void
+ReadOnlyWaveFileModel::cacheFilled()
+{
+    m_mutex.lock();
+    delete m_fillThread;
+    m_fillThread = 0;
+    delete m_updateTimer;
+    m_updateTimer = 0;
+    m_mutex.unlock();
+    if (getEndFrame() > m_lastFillExtent) {
+        emit modelChangedWithin(m_lastFillExtent, getEndFrame());
+    }
+    emit modelChanged();
+    emit ready();
+#ifdef DEBUG_WAVE_FILE_MODEL
+    SVDEBUG << "ReadOnlyWaveFileModel::cacheFilled" << endl;
+#endif
+}
+
+void
+ReadOnlyWaveFileModel::RangeCacheFillThread::run()
+{
+    int cacheBlockSize[2];
+    cacheBlockSize[0] = (1 << m_model.m_zoomConstraint.getMinCachePower());
+    cacheBlockSize[1] = (int((1 << m_model.m_zoomConstraint.getMinCachePower()) *
+                                        sqrt(2.) + 0.01));
+    
+    sv_frame_t frame = 0;
+    const sv_frame_t readBlockSize = 32768;
+    vector<float> block;
+
+    if (!m_model.isOK()) return;
+    
+    int channels = m_model.getChannelCount();
+    bool updating = m_model.m_reader->isUpdating();
+
+    if (updating) {
+        while (channels == 0 && !m_model.m_exiting) {
+#ifdef DEBUG_WAVE_FILE_MODEL
+            cerr << "ReadOnlyWaveFileModel::fill: Waiting for channels..." << endl;
+#endif
+            sleep(1);
+            channels = m_model.getChannelCount();
+        }
+    }
+
+    Range *range = new Range[2 * channels];
+    float *means = new float[2 * channels];
+    int count[2];
+    count[0] = count[1] = 0;
+    for (int i = 0; i < 2 * channels; ++i) {
+        means[i] = 0.f;
+    }
+
+    bool first = true;
+
+    while (first || updating) {
+
+        updating = m_model.m_reader->isUpdating();
+        m_frameCount = m_model.getFrameCount();
+
+        m_model.m_mutex.lock();
+
+        while (frame < m_frameCount) {
+
+            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);
+
+            sv_frame_t gotBlockSize = block.size() / channels;
+
+            m_model.m_mutex.lock();
+
+            for (sv_frame_t i = 0; i < gotBlockSize; ++i) {
+		
+                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) {
+                        sv_frame_t rangeIndex = ch * 2 + cacheType;
+                        range[rangeIndex].sample(sample);
+                        means[rangeIndex] += fabsf(sample);
+                    }
+                }
+
+                for (int cacheType = 0; cacheType < 2; ++cacheType) {
+
+                    if (++count[cacheType] == cacheBlockSize[cacheType]) {
+                        
+                        for (int ch = 0; ch < int(channels); ++ch) {
+                            int rangeIndex = ch * 2 + cacheType;
+                            means[rangeIndex] = means[rangeIndex] / float(count[cacheType]);
+                            range[rangeIndex].setAbsmean(means[rangeIndex]);
+                            m_model.m_cache[cacheType].push_back(range[rangeIndex]);
+                            range[rangeIndex] = Range();
+                            means[rangeIndex] = 0.f;
+                        }
+
+                        count[cacheType] = 0;
+                    }
+                }
+                
+                ++frame;
+            }
+
+            if (m_model.m_exiting) break;
+            m_fillExtent = frame;
+        }
+
+        m_model.m_mutex.unlock();
+            
+        first = false;
+        if (m_model.m_exiting) break;
+        if (updating) {
+            sleep(1);
+        }
+    }
+
+    if (!m_model.m_exiting) {
+
+        QMutexLocker locker(&m_model.m_mutex);
+
+        for (int cacheType = 0; cacheType < 2; ++cacheType) {
+
+            if (count[cacheType] > 0) {
+
+                for (int ch = 0; ch < int(channels); ++ch) {
+                    int rangeIndex = ch * 2 + cacheType;
+                    means[rangeIndex] = means[rangeIndex] / float(count[cacheType]);
+                    range[rangeIndex].setAbsmean(means[rangeIndex]);
+                    m_model.m_cache[cacheType].push_back(range[rangeIndex]);
+                    range[rangeIndex] = Range();
+                    means[rangeIndex] = 0.f;
+                }
+
+                count[cacheType] = 0;
+            }
+            
+            const Range &rr = *m_model.m_cache[cacheType].begin();
+            MUNLOCK(&rr, m_model.m_cache[cacheType].capacity() * sizeof(Range));
+        }
+    }
+    
+    delete[] means;
+    delete[] range;
+
+    m_fillExtent = m_frameCount;
+
+#ifdef DEBUG_WAVE_FILE_MODEL        
+    for (int cacheType = 0; cacheType < 2; ++cacheType) {
+        cerr << "Cache type " << cacheType << " now contains " << m_model.m_cache[cacheType].size() << " ranges" << endl;
+    }
+#endif
+}
+
+void
+ReadOnlyWaveFileModel::toXml(QTextStream &out,
+                     QString indent,
+                     QString extraAttributes) const
+{
+    Model::toXml(out, indent,
+                 QString("type=\"wavefile\" file=\"%1\" %2")
+                 .arg(encodeEntities(m_path)).arg(extraAttributes));
+}
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/ReadOnlyWaveFileModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,131 @@
+/* -*- 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 READ_ONLY_WAVE_FILE_MODEL_H
+#define READ_ONLY_WAVE_FILE_MODEL_H
+
+#include "WaveFileModel.h"
+
+#include "base/Thread.h"
+#include <QMutex>
+#include <QTimer>
+
+#include "data/fileio/FileSource.h"
+
+#include "RangeSummarisableTimeValueModel.h"
+#include "PowerOfSqrtTwoZoomConstraint.h"
+
+#include <stdlib.h>
+
+class AudioFileReader;
+
+class ReadOnlyWaveFileModel : public WaveFileModel
+{
+    Q_OBJECT
+
+public:
+    ReadOnlyWaveFileModel(FileSource source, sv_samplerate_t targetRate = 0);
+    ReadOnlyWaveFileModel(FileSource source, AudioFileReader *reader);
+    ~ReadOnlyWaveFileModel();
+
+    bool isOK() const;
+    bool isReady(int *) const;
+
+    const ZoomConstraint *getZoomConstraint() const { return &m_zoomConstraint; }
+
+    sv_frame_t getFrameCount() const;
+    int getChannelCount() const;
+    sv_samplerate_t getSampleRate() const;
+    sv_samplerate_t getNativeRate() const;
+
+    QString getTitle() const;
+    QString getMaker() const;
+    QString getLocation() const;
+
+    QString getLocalFilename() const;
+
+    float getValueMinimum() const { return -1.0f; }
+    float getValueMaximum() const { return  1.0f; }
+
+    virtual sv_frame_t getStartFrame() const { return m_startFrame; }
+    virtual sv_frame_t getEndFrame() const { return m_startFrame + getFrameCount(); }
+
+    void setStartFrame(sv_frame_t startFrame) { m_startFrame = startFrame; }
+
+    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const;
+
+    virtual std::vector<std::vector<float>> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const;
+
+    virtual int getSummaryBlockSize(int desired) const;
+
+    virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count,
+                              RangeBlock &ranges,
+                              int &blockSize) const;
+
+    virtual Range getSummary(int channel, sv_frame_t start, sv_frame_t count) const;
+
+    QString getTypeName() const { return tr("Wave File"); }
+
+    virtual void toXml(QTextStream &out,
+                       QString indent = "",
+                       QString extraAttributes = "") const;
+
+protected slots:
+    void fillTimerTimedOut();
+    void cacheFilled();
+    
+protected:
+    void initialize();
+
+    class RangeCacheFillThread : public Thread
+    {
+    public:
+        RangeCacheFillThread(ReadOnlyWaveFileModel &model) :
+	    m_model(model), m_fillExtent(0),
+            m_frameCount(model.getFrameCount()) { }
+    
+	sv_frame_t getFillExtent() const { return m_fillExtent; }
+        virtual void run();
+
+    protected:
+        ReadOnlyWaveFileModel &m_model;
+	sv_frame_t m_fillExtent;
+        sv_frame_t m_frameCount;
+    };
+         
+    void fillCache();
+
+    FileSource m_source;
+    QString m_path;
+    AudioFileReader *m_reader;
+    bool m_myReader;
+
+    sv_frame_t m_startFrame;
+
+    RangeBlock m_cache[2]; // interleaved at two base resolutions
+    mutable QMutex m_mutex;
+    RangeCacheFillThread *m_fillThread;
+    QTimer *m_updateTimer;
+    sv_frame_t m_lastFillExtent;
+    bool m_exiting;
+    static PowerOfSqrtTwoZoomConstraint m_zoomConstraint;
+
+    mutable std::vector<float> m_directRead;
+    mutable sv_frame_t m_lastDirectReadStart;
+    mutable sv_frame_t m_lastDirectReadCount;
+    mutable QMutex m_directReadMutex;
+};    
+
+#endif
--- a/data/model/RegionModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/RegionModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -37,15 +37,15 @@
 {
 public:
     RegionRec() : frame(0), value(0.f), duration(0) { }
-    RegionRec(long _frame) : frame(_frame), value(0.0f), duration(0) { }
-    RegionRec(long _frame, float _value, size_t _duration, QString _label) :
+    RegionRec(sv_frame_t _frame) : frame(_frame), value(0.0f), duration(0) { }
+    RegionRec(sv_frame_t _frame, float _value, sv_frame_t _duration, QString _label) :
 	frame(_frame), value(_value), duration(_duration), label(_label) { }
 
     int getDimensions() const { return 3; }
 
-    long frame;
+    sv_frame_t frame;
     float value;
-    size_t duration;
+    sv_frame_t duration;
     QString label;
 
     QString getLabel() const { return label; }
@@ -60,7 +60,7 @@
             .arg(XmlExportable::encodeEntities(label)).arg(extraAttributes);
     }
 
-    QString toDelimitedDataString(QString delimiter, size_t sampleRate) const
+    QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const
     {
         QStringList list;
         list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
@@ -94,7 +94,7 @@
     Q_OBJECT
     
 public:
-    RegionModel(size_t sampleRate, size_t resolution,
+    RegionModel(sv_samplerate_t sampleRate, int resolution,
                 bool notifyOnAdd = true) :
 	IntervalModel<RegionRec>(sampleRate, resolution, notifyOnAdd),
 	m_valueQuantization(0),
@@ -102,7 +102,7 @@
     {
     }
 
-    RegionModel(size_t sampleRate, size_t resolution,
+    RegionModel(sv_samplerate_t sampleRate, int resolution,
                 float valueMinimum, float valueMaximum,
                 bool notifyOnAdd = true) :
 	IntervalModel<RegionRec>(sampleRate, resolution,
--- a/data/model/SparseModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/SparseModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -43,27 +43,31 @@
                     public TabularModel
 {
 public:
-    SparseModel(size_t sampleRate, size_t resolution,
+    SparseModel(sv_samplerate_t sampleRate, int resolution,
 		bool notifyOnAdd = true);
     virtual ~SparseModel() { }
     
     virtual bool isOK() const { return true; }
-    virtual size_t getStartFrame() const;
-    virtual size_t getEndFrame() const;
-    virtual size_t getSampleRate() const { return m_sampleRate; }
-
-    virtual Model *clone() const;
+    virtual sv_frame_t getStartFrame() const;
+    virtual sv_frame_t getEndFrame() const;
+    virtual sv_samplerate_t getSampleRate() const { return m_sampleRate; }
 
     // Number of frames of the underlying sample rate that this model
     // is capable of resolving to.  For example, if m_resolution == 10
     // then every point in this model will be at a multiple of 10
     // sample frames and should be considered to cover a window ending
     // 10 sample frames later.
-    virtual size_t getResolution() const {
+    virtual int getResolution() const {
         return m_resolution ? m_resolution : 1;
     }
-    virtual void setResolution(size_t resolution);
+    virtual void setResolution(int resolution);
 
+    // Extend the end of the model. If this is set to something beyond
+    // the end of the final point in the model, then getEndFrame()
+    // will return this value. Otherwise getEndFrame() will return the
+    // end of the final point.
+    virtual void extendEndFrame(sv_frame_t to) { m_extendTo = to; }
+    
     typedef PointType Point;
     typedef std::multiset<PointType,
 			  typename PointType::OrderComparator> PointList;
@@ -78,7 +82,7 @@
     /**
      * Get the total number of points in the model.
      */
-    virtual size_t getPointCount() const;
+    virtual int getPointCount() const;
 
     /**
      * Get all points.
@@ -91,25 +95,25 @@
      * after the boundaries.  If you need exact boundaries, check the
      * point coordinates in the returned list.
      */
-    virtual PointList getPoints(long start, long end) const;
+    virtual PointList getPoints(sv_frame_t start, sv_frame_t end) const;
 
     /**
      * Get all points that cover the given frame number, taking the
      * resolution of the model into account.
      */
-    virtual PointList getPoints(long frame) const;
+    virtual PointList getPoints(sv_frame_t frame) const;
 
     /**
      * Return all points that share the nearest frame number prior to
      * the given one at which there are any points.
      */
-    virtual PointList getPreviousPoints(long frame) const;
+    virtual PointList getPreviousPoints(sv_frame_t frame) const;
 
     /**
      * Return all points that share the nearest frame number
      * subsequent to the given one at which there are any points.
      */
-    virtual PointList getNextPoints(long frame) const;
+    virtual PointList getNextPoints(sv_frame_t frame) const;
 
     /**
      * Remove all points.
@@ -129,6 +133,12 @@
      */
     virtual void deletePoint(const PointType &point);
 
+    /**
+     * Return true if the given point is found in this model, false
+     * otherwise.
+     */
+    virtual bool containsPoint(const PointType &point);
+    
     virtual bool isReady(int *completion = 0) const {
         bool ready = isOK() && (m_completion == 100);
         if (completion) *completion = m_completion;
@@ -148,24 +158,35 @@
                        QString indent = "",
                        QString extraAttributes = "") const;
 
-    virtual QString toDelimitedDataString(QString delimiter) const
-    { 
-        QString s;
-        for (PointListConstIterator i = m_points.begin(); i != m_points.end(); ++i) {
-            s += i->toDelimitedDataString(delimiter, m_sampleRate) + "\n";
-        }
-        return s;
+    virtual QString toDelimitedDataString(QString delimiter) const {
+        return toDelimitedDataStringWithOptions
+            (delimiter, DataExportDefaults);
     }
 
-    virtual QString toDelimitedDataString(QString delimiter, size_t f0, size_t f1) const
-    { 
-        QString s;
-        for (PointListConstIterator i = m_points.begin(); i != m_points.end(); ++i) {
-            if (i->frame >= (long)f0 && i->frame < (long)f1) {
-                s += i->toDelimitedDataString(delimiter, m_sampleRate) + "\n";
+    virtual QString toDelimitedDataStringWithOptions(QString delimiter,
+                                                     DataExportOptions opts) const {
+        return toDelimitedDataStringSubsetWithOptions
+            (delimiter, opts,
+             std::min(getStartFrame(), sv_frame_t(0)), getEndFrame() + 1);
+    }
+
+    virtual QString toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const {
+        return toDelimitedDataStringSubsetWithOptions
+            (delimiter, DataExportDefaults, f0, f1);
+    }
+
+    virtual QString toDelimitedDataStringSubsetWithOptions(QString delimiter, DataExportOptions opts, sv_frame_t f0, sv_frame_t f1) const {
+        if (opts & DataExportFillGaps) {
+            return toDelimitedDataStringSubsetFilled(delimiter, opts, f0, f1);
+        } else {
+            QString s;
+            for (PointListConstIterator i = m_points.begin(); i != m_points.end(); ++i) {
+                if (i->frame >= f0 && i->frame < f1) {
+                    s += i->toDelimitedDataString(delimiter, opts, m_sampleRate) + "\n";
+                }
             }
+            return s;
         }
-        return s;
     }
 
     /**
@@ -284,31 +305,26 @@
 
     virtual int getRowCount() const
     {
-        return m_points.size();
+        return int(m_points.size());
     }
 
-    virtual long getFrameForRow(int row) const
+    virtual sv_frame_t getFrameForRow(int row) const
     {
         PointListConstIterator i = getPointListIteratorForRow(row);
         if (i == m_points.end()) return 0;
         return i->frame;
     }
 
-    virtual int getRowForFrame(long frame) const
+    virtual int getRowForFrame(sv_frame_t frame) const
     {
         if (m_rows.empty()) rebuildRowVector();
-        std::vector<long>::iterator i =
+        std::vector<sv_frame_t>::iterator i =
             std::lower_bound(m_rows.begin(), m_rows.end(), frame);
-#if defined(__SUNPRO_CC) && defined(__STD_RW_ITERATOR__)
-        int row = 0;
-        std::distance(m_rows.begin(), i, row);
-#else
-        int row = std::distance(m_rows.begin(), i);
-#endif
+        ssize_t row = std::distance(m_rows.begin(), i);
         if (i != m_rows.begin() && (i == m_rows.end() || *i != frame)) {
             --row;
         }
-        return row;
+        return int(row);
     }
 
     virtual int getColumnCount() const { return 1; }
@@ -363,36 +379,37 @@
             
     virtual Command *getRemoveRowCommand(int row)
     {
-        EditCommand *command = new EditCommand(this, tr("Delete Data Point"));
         PointListIterator i = getPointListIteratorForRow(row);
         if (i == m_points.end()) return 0;
+        EditCommand *command = new EditCommand(this, tr("Delete Data Point"));
         command->deletePoint(*i);
         return command->finish();
     }
             
 protected:
-    size_t m_sampleRate;
-    size_t m_resolution;
+    sv_samplerate_t m_sampleRate;
+    int m_resolution;
+    sv_frame_t m_extendTo;
     bool m_notifyOnAdd;
-    long m_sinceLastNotifyMin;
-    long m_sinceLastNotifyMax;
+    sv_frame_t m_sinceLastNotifyMin;
+    sv_frame_t m_sinceLastNotifyMax;
     bool m_hasTextLabels;
 
     PointList m_points;
-    size_t m_pointCount;
+    int m_pointCount;
     mutable QMutex m_mutex;
     int m_completion;
 
-    void getPointIterators(long frame,
+    void getPointIterators(sv_frame_t frame,
                            PointListIterator &startItr,
                            PointListIterator &endItr);
-    void getPointIterators(long frame,
+    void getPointIterators(sv_frame_t frame,
                            PointListConstIterator &startItr,
                            PointListConstIterator &endItr) const;
 
     // This is only used if the model is called on to act in
     // TabularModel mode
-    mutable std::vector<long> m_rows; // map from row number to frame
+    mutable std::vector<sv_frame_t> m_rows; // map from row number to frame
     void rebuildRowVector() const
     {
         m_rows.clear();
@@ -407,7 +424,7 @@
         if (m_rows.empty()) rebuildRowVector();
         if (row < 0 || row + 1 > int(m_rows.size())) return m_points.end();
 
-        size_t frame = m_rows[row];
+        sv_frame_t frame = m_rows[row];
         int indexAtFrame = 0;
         int ri = row;
         while (ri > 0 && m_rows[ri-1] == m_rows[row]) { --ri; ++indexAtFrame; }
@@ -434,7 +451,7 @@
         if (m_rows.empty()) rebuildRowVector();
         if (row < 0 || row + 1 > int(m_rows.size())) return m_points.end();
 
-        size_t frame = m_rows[row];
+        sv_frame_t frame = m_rows[row];
         int indexAtFrame = 0;
         int ri = row;
         while (ri > 0 && m_rows[ri-1] == m_rows[row]) { --ri; ++indexAtFrame; }
@@ -461,15 +478,59 @@
         }
         return i;
     }
+
+    QString toDelimitedDataStringSubsetFilled(QString delimiter,
+                                              DataExportOptions opts,
+                                              sv_frame_t f0,
+                                              sv_frame_t f1) const {
+
+        QString s;
+        opts &= ~DataExportFillGaps;
+
+        // find frame time of first point in range (if any)
+        sv_frame_t first = f0;
+        for (auto &p: m_points) {
+            if (p.frame >= f0) {
+                first = p.frame;
+                break;
+            }
+        }
+
+        // project back to first frame time in range according to
+        // resolution.  e.g. if f0 = 2, first = 9, resolution = 4 then
+        // we start at 5 (because 1 is too early and we need to arrive
+        // at 9 to match the first actual point). This method is
+        // stupid but easy to understand:
+        sv_frame_t f = first;
+        while (f >= f0 + m_resolution) f -= m_resolution;
+        
+        // now progress, either writing the next point (if within
+        // distance) or a default point
+        PointListConstIterator itr = m_points.begin();
+
+        while (f < f1) {
+            if (itr != m_points.end() && itr->frame <= f) {
+                s += itr->toDelimitedDataString(delimiter, opts, m_sampleRate);
+                ++itr;
+            } else {
+                s += Point(f).toDelimitedDataString(delimiter, opts, m_sampleRate);
+            }
+            s += "\n";
+            f += m_resolution;
+        }
+
+        return s;
+    }
 };
 
 
 template <typename PointType>
-SparseModel<PointType>::SparseModel(size_t sampleRate,
-                                    size_t resolution,
+SparseModel<PointType>::SparseModel(sv_samplerate_t sampleRate,
+                                    int resolution,
                                     bool notifyOnAdd) :
     m_sampleRate(sampleRate),
     m_resolution(resolution),
+    m_extendTo(0),
     m_notifyOnAdd(notifyOnAdd),
     m_sinceLastNotifyMin(-1),
     m_sinceLastNotifyMax(-1),
@@ -480,11 +541,11 @@
 }
 
 template <typename PointType>
-size_t
+sv_frame_t
 SparseModel<PointType>::getStartFrame() const
 {
     QMutexLocker locker(&m_mutex);
-    size_t f = 0;
+    sv_frame_t f = 0;
     if (!m_points.empty()) {
 	f = m_points.begin()->frame;
     }
@@ -492,30 +553,17 @@
 }
 
 template <typename PointType>
-size_t
+sv_frame_t
 SparseModel<PointType>::getEndFrame() const
 {
     QMutexLocker locker(&m_mutex);
-    size_t f = 0;
+    sv_frame_t f = 0;
     if (!m_points.empty()) {
 	PointListConstIterator i(m_points.end());
 	f = (--i)->frame;
     }
-    return f;
-}
-
-template <typename PointType>
-Model *
-SparseModel<PointType>::clone() const
-{
-    return 0; //!!! is this ever used?
-/*
-    SparseModel<PointType> *model =
-	new SparseModel<PointType>(m_sampleRate, m_resolution, m_notifyOnAdd);
-    model->m_points = m_points;
-    model->m_pointCount = m_pointCount;
-    return model;
-*/
+    if (m_extendTo > f) return m_extendTo;
+    else return f;
 }
 
 template <typename PointType>
@@ -526,7 +574,7 @@
 }
 
 template <typename PointType>
-size_t
+int
 SparseModel<PointType>::getPointCount() const
 {
     return m_pointCount;
@@ -541,7 +589,7 @@
 
 template <typename PointType>
 typename SparseModel<PointType>::PointList
-SparseModel<PointType>::getPoints(long start, long end) const
+SparseModel<PointType>::getPoints(sv_frame_t start, sv_frame_t end) const
 {
     if (start > end) return PointList();
     QMutexLocker locker(&m_mutex);
@@ -567,7 +615,7 @@
 
 template <typename PointType>
 typename SparseModel<PointType>::PointList
-SparseModel<PointType>::getPoints(long frame) const
+SparseModel<PointType>::getPoints(sv_frame_t frame) const
 {
     PointListConstIterator startItr, endItr;
     getPointIterators(frame, startItr, endItr);
@@ -583,7 +631,7 @@
 
 template <typename PointType>
 void
-SparseModel<PointType>::getPointIterators(long frame,
+SparseModel<PointType>::getPointIterators(sv_frame_t frame,
                                           PointListIterator &startItr,
                                           PointListIterator &endItr)
 {
@@ -595,8 +643,8 @@
         return;
     }
 
-    long start = (frame / m_resolution) * m_resolution;
-    long end = start + m_resolution;
+    sv_frame_t start = (frame / m_resolution) * m_resolution;
+    sv_frame_t end = start + m_resolution;
 
     PointType startPoint(start), endPoint(end);
 
@@ -606,7 +654,7 @@
 
 template <typename PointType>
 void
-SparseModel<PointType>::getPointIterators(long frame,
+SparseModel<PointType>::getPointIterators(sv_frame_t frame,
                                           PointListConstIterator &startItr,
                                           PointListConstIterator &endItr) const
 {
@@ -619,8 +667,8 @@
         return;
     }
 
-    long start = (frame / m_resolution) * m_resolution;
-    long end = start + m_resolution;
+    sv_frame_t start = (frame / m_resolution) * m_resolution;
+    sv_frame_t end = start + m_resolution;
 
     PointType startPoint(start), endPoint(end);
     
@@ -632,7 +680,7 @@
 
 template <typename PointType>
 typename SparseModel<PointType>::PointList
-SparseModel<PointType>::getPreviousPoints(long originFrame) const
+SparseModel<PointType>::getPreviousPoints(sv_frame_t originFrame) const
 {
     QMutexLocker locker(&m_mutex);
 
@@ -643,7 +691,7 @@
     if (i == m_points.begin()) return rv;
 
     --i;
-    long frame = i->frame;
+    sv_frame_t frame = i->frame;
     while (i->frame == frame) {
 	rv.insert(*i);
 	if (i == m_points.begin()) break;
@@ -655,7 +703,7 @@
  
 template <typename PointType>
 typename SparseModel<PointType>::PointList
-SparseModel<PointType>::getNextPoints(long originFrame) const
+SparseModel<PointType>::getNextPoints(sv_frame_t originFrame) const
 {
     QMutexLocker locker(&m_mutex);
 
@@ -665,7 +713,7 @@
     PointListConstIterator i = m_points.upper_bound(lookupPoint);
     if (i == m_points.end()) return rv;
 
-    long frame = i->frame;
+    sv_frame_t frame = i->frame;
     while (i != m_points.end() && i->frame == frame) {
 	rv.insert(*i);
 	++i;
@@ -676,7 +724,7 @@
 
 template <typename PointType>
 void
-SparseModel<PointType>::setResolution(size_t resolution)
+SparseModel<PointType>::setResolution(int resolution)
 {
     {
 	QMutexLocker locker(&m_mutex);
@@ -718,7 +766,7 @@
 
     if (m_notifyOnAdd) {
         m_rows.clear(); //!!! inefficient
-	emit modelChanged(point.frame, point.frame + m_resolution);
+	emit modelChangedWithin(point.frame, point.frame + m_resolution);
     } else {
 	if (m_sinceLastNotifyMin == -1 ||
 	    point.frame < m_sinceLastNotifyMin) {
@@ -732,6 +780,27 @@
 }
 
 template <typename PointType>
+bool
+SparseModel<PointType>::containsPoint(const PointType &point)
+{
+    {
+	QMutexLocker locker(&m_mutex);
+
+	PointListIterator i = m_points.lower_bound(point);
+	typename PointType::Comparator comparator;
+	while (i != m_points.end()) {
+	    if (i->frame > point.frame) break;
+	    if (!comparator(*i, point) && !comparator(point, *i)) {
+                return true;
+	    }
+	    ++i;
+	}
+    }
+
+    return false;
+}
+
+template <typename PointType>
 void
 SparseModel<PointType>::deletePoint(const PointType &point)
 {
@@ -753,7 +822,7 @@
 //    std::cout << "SparseOneDimensionalModel: emit modelChanged("
 //	      << point.frame << ")" << std::endl;
     m_rows.clear(); //!!! inefficient
-    emit modelChanged(point.frame, point.frame + m_resolution);
+    emit modelChangedWithin(point.frame, point.frame + m_resolution);
 }
 
 template <typename PointType>
@@ -781,7 +850,7 @@
                 m_sinceLastNotifyMin >= 0 &&
 		m_sinceLastNotifyMax >= 0) {
                 m_rows.clear(); //!!! inefficient
-		emit modelChanged(m_sinceLastNotifyMin, m_sinceLastNotifyMax);
+		emit modelChangedWithin(m_sinceLastNotifyMin, m_sinceLastNotifyMax);
 		m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
 	    } else {
 		emit completionChanged();
--- a/data/model/SparseOneDimensionalModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/SparseOneDimensionalModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -17,6 +17,7 @@
 #define _SPARSE_ONE_DIMENSIONAL_MODEL_H_
 
 #include "SparseModel.h"
+#include "NoteData.h"
 #include "base/PlayParameterRepository.h"
 #include "base/RealTime.h"
 
@@ -25,12 +26,12 @@
 struct OneDimensionalPoint
 {
 public:
-    OneDimensionalPoint(long _frame) : frame(_frame) { }
-    OneDimensionalPoint(long _frame, QString _label) : frame(_frame), label(_label) { }
+    OneDimensionalPoint(sv_frame_t _frame) : frame(_frame) { }
+    OneDimensionalPoint(sv_frame_t _frame, QString _label) : frame(_frame), label(_label) { }
 
     int getDimensions() const { return 1; }
     
-    long frame;
+    sv_frame_t frame;
     QString label;
 
     QString getLabel() const { return label; }
@@ -44,7 +45,7 @@
             .arg(extraAttributes);
     }
 
-    QString toDelimitedDataString(QString delimiter, size_t sampleRate) const
+    QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const
     {
         QStringList list;
         list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
@@ -69,12 +70,13 @@
 };
 
 
-class SparseOneDimensionalModel : public SparseModel<OneDimensionalPoint>
+class SparseOneDimensionalModel : public SparseModel<OneDimensionalPoint>,
+                                  public NoteExportable
 {
     Q_OBJECT
     
 public:
-    SparseOneDimensionalModel(size_t sampleRate, size_t resolution,
+    SparseOneDimensionalModel(sv_samplerate_t sampleRate, int resolution,
 			      bool notifyOnAdd = true) :
 	SparseModel<OneDimensionalPoint>(sampleRate, resolution, notifyOnAdd)
     {
@@ -88,14 +90,9 @@
 
     virtual bool canPlay() const { return true; }
 
-    virtual QString getDefaultPlayPluginId() const
+    virtual QString getDefaultPlayClipId() const
     {
-        return "dssi:_builtin:sample_player";
-    }
-
-    virtual QString getDefaultPlayPluginConfiguration() const
-    {
-        return "<plugin program=\"tap\"/>";
+        return "tap";
     }
 
     int getIndexOf(const Point &point)
@@ -181,6 +178,32 @@
         if (column == 2) return SortAlphabetical;
         return SortNumeric;
     }
+
+    /**
+     * NoteExportable methods.
+     */
+
+    NoteList getNotes() const {
+        return getNotesWithin(getStartFrame(), getEndFrame());
+    }
+
+    NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const {
+        
+	PointList points = getPoints(startFrame, endFrame);
+        NoteList notes;
+
+	for (PointList::iterator pli =
+		 points.begin(); pli != points.end(); ++pli) {
+
+            notes.push_back
+                (NoteData(pli->frame,
+                          sv_frame_t(getSampleRate() / 6), // arbitrary short duration
+                          64,   // default pitch
+                          100)); // default velocity
+        }
+
+        return notes;
+    }
 };
 
 #endif
--- a/data/model/SparseTimeValueModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/SparseTimeValueModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -29,13 +29,13 @@
 struct TimeValuePoint
 {
 public:
-    TimeValuePoint(long _frame) : frame(_frame), value(0.0f) { }
-    TimeValuePoint(long _frame, float _value, QString _label) : 
+    TimeValuePoint(sv_frame_t _frame) : frame(_frame), value(0.0f) { }
+    TimeValuePoint(sv_frame_t _frame, float _value, QString _label) : 
 	frame(_frame), value(_value), label(_label) { }
 
     int getDimensions() const { return 2; }
     
-    long frame;
+    sv_frame_t frame;
     float value;
     QString label;
 
@@ -49,7 +49,7 @@
             .arg(extraAttributes);
     }
 
-    QString toDelimitedDataString(QString delimiter, size_t sampleRate) const
+    QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const
     {
         QStringList list;
         list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
@@ -81,26 +81,38 @@
     Q_OBJECT
     
 public:
-    SparseTimeValueModel(size_t sampleRate, size_t resolution,
+    SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution,
 			 bool notifyOnAdd = true) :
 	SparseValueModel<TimeValuePoint>(sampleRate, resolution,
 					 notifyOnAdd)
     {
-        // not yet playable
+        // Model is playable, but may not sound (if units not Hz or
+        // range unsuitable)
+	PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
-    SparseTimeValueModel(size_t sampleRate, size_t resolution,
+    SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution,
 			 float valueMinimum, float valueMaximum,
 			 bool notifyOnAdd = true) :
 	SparseValueModel<TimeValuePoint>(sampleRate, resolution,
 					 valueMinimum, valueMaximum,
 					 notifyOnAdd)
     {
-        // not yet playable
+        // Model is playable, but may not sound (if units not Hz or
+        // range unsuitable)
+	PlayParameterRepository::getInstance()->addPlayable(this);
+    }
+
+    virtual ~SparseTimeValueModel()
+    {
+	PlayParameterRepository::getInstance()->removePlayable(this);
     }
 
     QString getTypeName() const { return tr("Sparse Time-Value"); }
 
+    virtual bool canPlay() const { return true; }
+    virtual bool getDefaultPlayAudible() const { return false; } // user must unmute
+
     /**
      * TabularModel methods.  
      */
@@ -156,7 +168,7 @@
         command->deletePoint(point);
 
         switch (column) {
-        case 2: point.value = value.toDouble(); break;
+        case 2: point.value = float(value.toDouble()); break;
         case 3: point.label = value.toString(); break;
         }
 
--- a/data/model/SparseValueModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/SparseValueModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -31,7 +31,7 @@
 class SparseValueModel : public SparseModel<PointType>
 {
 public:
-    SparseValueModel(size_t sampleRate, size_t resolution,
+    SparseValueModel(sv_samplerate_t sampleRate, int resolution,
 		     bool notifyOnAdd = true) :
 	SparseModel<PointType>(sampleRate, resolution, notifyOnAdd),
 	m_valueMinimum(0.f),
@@ -39,7 +39,7 @@
         m_haveExtents(false)
     { }
 
-    SparseValueModel(size_t sampleRate, size_t resolution,
+    SparseValueModel(sv_samplerate_t sampleRate, int resolution,
 		     float valueMinimum, float valueMaximum,
 		     bool notifyOnAdd = true) :
 	SparseModel<PointType>(sampleRate, resolution, notifyOnAdd),
--- a/data/model/TabularModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/TabularModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -48,8 +48,8 @@
     virtual bool isColumnTimeValue(int col) const = 0;
     virtual SortType getSortType(int col) const = 0;
 
-    virtual long getFrameForRow(int row) const = 0;
-    virtual int getRowForFrame(long frame) const = 0;
+    virtual sv_frame_t getFrameForRow(int row) const = 0;
+    virtual int getRowForFrame(sv_frame_t frame) const = 0;
 
     virtual bool isEditable() const { return false; }
     virtual Command *getSetDataCommand(int /* row */, int /* column */, const QVariant &, int /* role */) { return 0; }
--- a/data/model/TextModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/TextModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -31,13 +31,13 @@
 struct TextPoint : public XmlExportable
 {
 public:
-    TextPoint(long _frame) : frame(_frame), height(0.0f) { }
-    TextPoint(long _frame, float _height, QString _label) : 
+    TextPoint(sv_frame_t _frame) : frame(_frame), height(0.0f) { }
+    TextPoint(sv_frame_t _frame, float _height, QString _label) : 
 	frame(_frame), height(_height), label(_label) { }
 
     int getDimensions() const { return 2; }
     
-    long frame;
+    sv_frame_t frame;
     float height;
     QString label;
 
@@ -51,7 +51,7 @@
             .arg(encodeEntities(label)).arg(extraAttributes);
     }
 
-    QString toDelimitedDataString(QString delimiter, size_t sampleRate) const
+    QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const
     {
         QStringList list;
         list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
@@ -85,7 +85,7 @@
     Q_OBJECT
     
 public:
-    TextModel(size_t sampleRate, size_t resolution, bool notifyOnAdd = true) :
+    TextModel(sv_samplerate_t sampleRate, int resolution, bool notifyOnAdd = true) :
 	SparseModel<TextPoint>(sampleRate, resolution, notifyOnAdd)
     { }
 
@@ -155,7 +155,7 @@
         command->deletePoint(point);
 
         switch (column) {
-        case 2: point.height = value.toDouble(); break;
+        case 2: point.height = float(value.toDouble()); break;
         case 3: point.label = value.toString(); break;
         }
 
--- a/data/model/WaveFileModel.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/WaveFileModel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -15,798 +15,7 @@
 
 #include "WaveFileModel.h"
 
-#include "fileio/AudioFileReader.h"
-#include "fileio/AudioFileReaderFactory.h"
-
-#include "system/System.h"
-
-#include <QFileInfo>
-#include <QTextStream>
-
-#include <iostream>
-#include <unistd.h>
-#include <cmath>
-#include <sndfile.h>
-
-#include <cassert>
-
-//#define DEBUG_WAVE_FILE_MODEL 1
-
-PowerOfSqrtTwoZoomConstraint
-WaveFileModel::m_zoomConstraint;
-
-WaveFileModel::WaveFileModel(FileSource source, size_t targetRate) :
-    m_source(source),
-    m_path(source.getLocation()),
-    m_myReader(true),
-    m_startFrame(0),
-    m_fillThread(0),
-    m_updateTimer(0),
-    m_lastFillExtent(0),
-    m_exiting(false),
-    m_lastDirectReadStart(0),
-    m_lastDirectReadCount(0)
+WaveFileModel::~WaveFileModel()
 {
-    m_source.waitForData();
-    if (m_source.isOK()) {
-        m_reader = AudioFileReaderFactory::createThreadingReader
-            (m_source, targetRate);
-        if (m_reader) {
-            SVDEBUG << "WaveFileModel::WaveFileModel: reader rate: "
-                      << m_reader->getSampleRate() << endl;
-        }
-    }
-    if (m_reader) setObjectName(m_reader->getTitle());
-    if (objectName() == "") setObjectName(QFileInfo(m_path).fileName());
-    if (isOK()) fillCache();
 }
 
-WaveFileModel::WaveFileModel(FileSource source, AudioFileReader *reader) :
-    m_source(source),
-    m_path(source.getLocation()),
-    m_myReader(false),
-    m_startFrame(0),
-    m_fillThread(0),
-    m_updateTimer(0),
-    m_lastFillExtent(0),
-    m_exiting(false)
-{
-    m_reader = reader;
-    if (m_reader) setObjectName(m_reader->getTitle());
-    if (objectName() == "") setObjectName(QFileInfo(m_path).fileName());
-    fillCache();
-}
-
-WaveFileModel::~WaveFileModel()
-{
-    m_exiting = true;
-    if (m_fillThread) m_fillThread->wait();
-    if (m_myReader) delete m_reader;
-    m_reader = 0;
-}
-
-bool
-WaveFileModel::isOK() const
-{
-    return m_reader && m_reader->isOK();
-}
-
-bool
-WaveFileModel::isReady(int *completion) const
-{
-    bool ready = (isOK() && (m_fillThread == 0));
-    double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame());
-    static int prevCompletion = 0;
-    if (completion) {
-        *completion = int(c * 100.0 + 0.01);
-        if (m_reader) {
-            int decodeCompletion = m_reader->getDecodeCompletion();
-            if (decodeCompletion < 90) *completion = decodeCompletion;
-            else *completion = std::min(*completion, decodeCompletion);
-        }
-        if (*completion != 0 &&
-            *completion != 100 &&
-            prevCompletion != 0 &&
-            prevCompletion > *completion) {
-            // just to avoid completion going backwards
-            *completion = prevCompletion;
-        }
-        prevCompletion = *completion;
-    }
-#ifdef DEBUG_WAVE_FILE_MODEL
-    SVDEBUG << "WaveFileModel::isReady(): ready = " << ready << ", completion = " << (completion ? *completion : -1) << endl;
-#endif
-    return ready;
-}
-
-Model *
-WaveFileModel::clone() const
-{
-    WaveFileModel *model = new WaveFileModel(m_source);
-    return model;
-}
-
-size_t
-WaveFileModel::getFrameCount() const
-{
-    if (!m_reader) return 0;
-    return m_reader->getFrameCount();
-}
-
-size_t
-WaveFileModel::getChannelCount() const
-{
-    if (!m_reader) return 0;
-    return m_reader->getChannelCount();
-}
-
-size_t
-WaveFileModel::getSampleRate() const 
-{
-    if (!m_reader) return 0;
-    return m_reader->getSampleRate();
-}
-
-size_t
-WaveFileModel::getNativeRate() const 
-{
-    if (!m_reader) return 0;
-    size_t rate = m_reader->getNativeRate();
-    if (rate == 0) rate = getSampleRate();
-    return rate;
-}
-
-QString
-WaveFileModel::getTitle() const
-{
-    QString title;
-    if (m_reader) title = m_reader->getTitle();
-    if (title == "") title = objectName();
-    return title;
-}
-
-QString
-WaveFileModel::getMaker() const
-{
-    if (m_reader) return m_reader->getMaker();
-    return "";
-}
-
-QString
-WaveFileModel::getLocation() const
-{
-    if (m_reader) return m_reader->getLocation();
-    return "";
-}
-    
-size_t
-WaveFileModel::getData(int channel, size_t start, size_t count,
-                       float *buffer) const
-{
-    // Always read these directly from the file. 
-    // This is used for e.g. audio playback.
-    // Could be much more efficient (although compiler optimisation will help)
-
-#ifdef DEBUG_WAVE_FILE_MODEL
-    cout << "WaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << ", " << buffer << endl;
-#endif
-
-    if (start >= m_startFrame) {
-        start -= m_startFrame;
-    } else {
-        for (size_t i = 0; i < count; ++i) buffer[i] = 0.f;
-        if (count <= m_startFrame - start) {
-            return 0;
-        } else {
-            count -= (m_startFrame - start);
-            start = 0;
-        }
-    }
-
-    if (!m_reader || !m_reader->isOK() || count == 0) {
-        for (size_t i = 0; i < count; ++i) buffer[i] = 0.f;
-        return 0;
-    }
-
-#ifdef DEBUG_WAVE_FILE_MODEL
-//    SVDEBUG << "WaveFileModel::getValues(" << channel << ", "
-//              << start << ", " << end << "): calling reader" << endl;
-#endif
-
-    int channels = getChannelCount();
-
-    SampleBlock frames(count * channels);
-    m_reader->getInterleavedFrames(start, count, frames);
-
-    size_t i = 0;
-
-    int ch0 = channel, ch1 = channel;
-    if (channel == -1) {
-	ch0 = 0;
-	ch1 = channels - 1;
-    }
-    
-    while (i < count) {
-
-	buffer[i] = 0.0;
-
-	for (int ch = ch0; ch <= ch1; ++ch) {
-
-	    size_t index = i * channels + ch;
-	    if (index >= frames.size()) break;
-            
-	    float sample = frames[index];
-	    buffer[i] += sample;
-	}
-
-	++i;
-    }
-
-    return i;
-}
-
-size_t
-WaveFileModel::getData(int channel, size_t start, size_t count,
-                       double *buffer) const
-{
-#ifdef DEBUG_WAVE_FILE_MODEL
-    cout << "WaveFileModel::getData(double)[" << this << "]: " << channel << ", " << start << ", " << count << ", " << buffer << endl;
-#endif
-
-    if (start > m_startFrame) {
-        start -= m_startFrame;
-    } else {
-        for (size_t i = 0; i < count; ++i) buffer[i] = 0.0;
-        if (count <= m_startFrame - start) {
-            return 0;
-        } else {
-            count -= (m_startFrame - start);
-            start = 0;
-        }
-    }
-
-    if (!m_reader || !m_reader->isOK() || count == 0) {
-        for (size_t i = 0; i < count; ++i) buffer[i] = 0.0;
-        return 0;
-    }
-
-    int channels = getChannelCount();
-
-    SampleBlock frames(count * channels);
-    m_reader->getInterleavedFrames(start, count, frames);
-
-    size_t i = 0;
-
-    int ch0 = channel, ch1 = channel;
-    if (channel == -1) {
-	ch0 = 0;
-	ch1 = channels - 1;
-    }
-
-    while (i < count) {
-
-	buffer[i] = 0.0;
-
-	for (int ch = ch0; ch <= ch1; ++ch) {
-
-	    size_t index = i * channels + ch;
-	    if (index >= frames.size()) break;
-            
-	    float sample = frames[index];
-	    buffer[i] += sample;
-	}
-
-	++i;
-    }
-
-    return i;
-}
-
-size_t
-WaveFileModel::getData(size_t fromchannel, size_t tochannel,
-                       size_t start, size_t count,
-                       float **buffer) const
-{
-#ifdef DEBUG_WAVE_FILE_MODEL
-    cout << "WaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << ", " << buffer << endl;
-#endif
-
-    size_t channels = getChannelCount();
-
-    if (fromchannel > tochannel) {
-        cerr << "ERROR: WaveFileModel::getData: fromchannel ("
-                  << fromchannel << ") > tochannel (" << tochannel << ")"
-                  << endl;
-        return 0;
-    }
-
-    if (tochannel >= channels) {
-        cerr << "ERROR: WaveFileModel::getData: tochannel ("
-                  << tochannel << ") >= channel count (" << channels << ")"
-                  << endl;
-        return 0;
-    }
-
-    if (fromchannel == tochannel) {
-        return getData(fromchannel, start, count, buffer[0]);
-    }
-
-    size_t reqchannels = (tochannel - fromchannel) + 1;
-
-    // Always read these directly from the file. 
-    // This is used for e.g. audio playback.
-    // Could be much more efficient (although compiler optimisation will help)
-
-    if (start >= m_startFrame) {
-        start -= m_startFrame;
-    } else {
-        for (size_t c = 0; c < reqchannels; ++c) {
-            for (size_t i = 0; i < count; ++i) buffer[c][i] = 0.f;
-        }
-        if (count <= m_startFrame - start) {
-            return 0;
-        } else {
-            count -= (m_startFrame - start);
-            start = 0;
-        }
-    }
-
-    if (!m_reader || !m_reader->isOK() || count == 0) {
-        for (size_t c = 0; c < reqchannels; ++c) {
-            for (size_t i = 0; i < count; ++i) buffer[c][i] = 0.f;
-        }
-        return 0;
-    }
-
-    SampleBlock frames(count * channels);
-    m_reader->getInterleavedFrames(start, count, frames);
-
-    size_t i = 0;
-
-    int ch0 = fromchannel, ch1 = tochannel;
-    
-    size_t index = 0, available = frames.size();
-
-    while (i < count) {
-
-        if (index >= available) break;
-
-        size_t destc = 0;
-
-        for (size_t c = 0; c < channels; ++c) {
-            
-            if (c >= fromchannel && c <= tochannel) {
-                buffer[destc][i] = frames[index];
-                ++destc;
-            }
-
-            ++index;
-        }
-
-        ++i;
-    }
-
-    return i;
-}
-
-size_t
-WaveFileModel::getSummaryBlockSize(size_t desired) const
-{
-    int cacheType = 0;
-    int power = m_zoomConstraint.getMinCachePower();
-    size_t roundedBlockSize = m_zoomConstraint.getNearestBlockSize
-        (desired, cacheType, power, ZoomConstraint::RoundDown);
-    if (cacheType != 0 && cacheType != 1) {
-        // We will be reading directly from file, so can satisfy any
-        // blocksize requirement
-        return desired;
-    } else {
-        return roundedBlockSize;
-    }
-}    
-
-void
-WaveFileModel::getSummaries(size_t channel, size_t start, size_t count,
-                            RangeBlock &ranges, size_t &blockSize) const
-{
-    ranges.clear();
-    if (!isOK()) return;
-    ranges.reserve((count / blockSize) + 1);
-
-    if (start > m_startFrame) start -= m_startFrame;
-    else if (count <= m_startFrame - start) return;
-    else {
-        count -= (m_startFrame - start);
-        start = 0;
-    }
-
-    int cacheType = 0;
-    int power = m_zoomConstraint.getMinCachePower();
-    size_t roundedBlockSize = m_zoomConstraint.getNearestBlockSize
-        (blockSize, cacheType, power, ZoomConstraint::RoundDown);
-
-    size_t channels = getChannelCount();
-
-    if (cacheType != 0 && cacheType != 1) {
-
-	// We need to read directly from the file.  We haven't got
-	// this cached.  Hope the requested area is small.  This is
-	// not optimal -- we'll end up reading the same frames twice
-	// for stereo files, in two separate calls to this method.
-	// We could fairly trivially handle this for most cases that
-	// matter by putting a single cache in getInterleavedFrames
-	// for short queries.
-
-        m_directReadMutex.lock();
-
-        if (m_lastDirectReadStart != start ||
-            m_lastDirectReadCount != count ||
-            m_directRead.empty()) {
-
-            m_reader->getInterleavedFrames(start, count, m_directRead);
-            m_lastDirectReadStart = start;
-            m_lastDirectReadCount = count;
-        }
-
-	float max = 0.0, min = 0.0, total = 0.0;
-	size_t i = 0, got = 0;
-
-	while (i < count) {
-
-	    size_t index = i * channels + channel;
-	    if (index >= m_directRead.size()) break;
-            
-	    float sample = m_directRead[index];
-            if (sample > max || got == 0) max = sample;
-	    if (sample < min || got == 0) min = sample;
-            total += fabsf(sample);
-
-	    ++i;
-            ++got;
-            
-            if (got == blockSize) {
-                ranges.push_back(Range(min, max, total / got));
-                min = max = total = 0.0f;
-                got = 0;
-	    }
-	}
-
-        m_directReadMutex.unlock();
-
-	if (got > 0) {
-            ranges.push_back(Range(min, max, total / got));
-	}
-
-	return;
-
-    } else {
-
-	QMutexLocker locker(&m_mutex);
-    
-	const RangeBlock &cache = m_cache[cacheType];
-
-        blockSize = roundedBlockSize;
-
-	size_t cacheBlock, div;
-        
-	if (cacheType == 0) {
-	    cacheBlock = (1 << m_zoomConstraint.getMinCachePower());
-            div = (1 << power) / cacheBlock;
-	} else {
-	    cacheBlock = ((unsigned int)((1 << m_zoomConstraint.getMinCachePower()) * sqrt(2.) + 0.01));
-            div = ((unsigned int)((1 << power) * sqrt(2.) + 0.01)) / cacheBlock;
-	}
-
-	size_t startIndex = start / cacheBlock;
-	size_t endIndex = (start + count) / cacheBlock;
-
-	float max = 0.0, min = 0.0, total = 0.0;
-	size_t i = 0, got = 0;
-
-#ifdef DEBUG_WAVE_FILE_MODEL
-	cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", count " << count << " (frame count " << getFrameCount() << "), power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl;
-#endif
-
-	for (i = 0; i <= endIndex - startIndex; ) {
-        
-	    size_t index = (i + startIndex) * channels + channel;
-	    if (index >= cache.size()) break;
-            
-            const Range &range = cache[index];
-            if (range.max() > max || got == 0) max = range.max();
-            if (range.min() < min || got == 0) min = range.min();
-            total += range.absmean();
-            
-	    ++i;
-            ++got;
-            
-	    if (got == div) {
-		ranges.push_back(Range(min, max, total / got));
-                min = max = total = 0.0f;
-                got = 0;
-	    }
-	}
-		
-	if (got > 0) {
-            ranges.push_back(Range(min, max, total / got));
-	}
-    }
-
-#ifdef DEBUG_WAVE_FILE_MODEL
-    SVDEBUG << "returning " << ranges.size() << " ranges" << endl;
-#endif
-    return;
-}
-
-WaveFileModel::Range
-WaveFileModel::getSummary(size_t channel, size_t start, size_t count) const
-{
-    Range range;
-    if (!isOK()) return range;
-
-    if (start > m_startFrame) start -= m_startFrame;
-    else if (count <= m_startFrame - start) return range;
-    else {
-        count -= (m_startFrame - start);
-        start = 0;
-    }
-
-    size_t blockSize;
-    for (blockSize = 1; blockSize <= count; blockSize *= 2);
-    if (blockSize > 1) blockSize /= 2;
-
-    bool first = false;
-
-    size_t blockStart = (start / blockSize) * blockSize;
-    size_t blockEnd = ((start + count) / blockSize) * blockSize;
-
-    if (blockStart < start) blockStart += blockSize;
-        
-    if (blockEnd > blockStart) {
-        RangeBlock ranges;
-        getSummaries(channel, blockStart, blockEnd - blockStart, ranges, blockSize);
-        for (size_t i = 0; i < ranges.size(); ++i) {
-            if (first || ranges[i].min() < range.min()) range.setMin(ranges[i].min());
-            if (first || ranges[i].max() > range.max()) range.setMax(ranges[i].max());
-            if (first || ranges[i].absmean() < range.absmean()) range.setAbsmean(ranges[i].absmean());
-            first = false;
-        }
-    }
-
-    if (blockStart > start) {
-        Range startRange = getSummary(channel, start, blockStart - start);
-        range.setMin(std::min(range.min(), startRange.min()));
-        range.setMax(std::max(range.max(), startRange.max()));
-        range.setAbsmean(std::min(range.absmean(), startRange.absmean()));
-    }
-
-    if (blockEnd < start + count) {
-        Range endRange = getSummary(channel, blockEnd, start + count - blockEnd);
-        range.setMin(std::min(range.min(), endRange.min()));
-        range.setMax(std::max(range.max(), endRange.max()));
-        range.setAbsmean(std::min(range.absmean(), endRange.absmean()));
-    }
-
-    return range;
-}
-
-void
-WaveFileModel::fillCache()
-{
-    m_mutex.lock();
-
-    m_updateTimer = new QTimer(this);
-    connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut()));
-    m_updateTimer->start(100);
-
-    m_fillThread = new RangeCacheFillThread(*this);
-    connect(m_fillThread, SIGNAL(finished()), this, SLOT(cacheFilled()));
-
-    m_mutex.unlock();
-    m_fillThread->start();
-
-#ifdef DEBUG_WAVE_FILE_MODEL
-    SVDEBUG << "WaveFileModel::fillCache: started fill thread" << endl;
-#endif
-}   
-
-void
-WaveFileModel::fillTimerTimedOut()
-{
-    if (m_fillThread) {
-	size_t fillExtent = m_fillThread->getFillExtent();
-#ifdef DEBUG_WAVE_FILE_MODEL
-        SVDEBUG << "WaveFileModel::fillTimerTimedOut: extent = " << fillExtent << endl;
-#endif
-	if (fillExtent > m_lastFillExtent) {
-	    emit modelChanged(m_lastFillExtent, fillExtent);
-	    m_lastFillExtent = fillExtent;
-	}
-    } else {
-#ifdef DEBUG_WAVE_FILE_MODEL
-        SVDEBUG << "WaveFileModel::fillTimerTimedOut: no thread" << endl;
-#endif
-	emit modelChanged();
-    }
-}
-
-void
-WaveFileModel::cacheFilled()
-{
-    m_mutex.lock();
-    delete m_fillThread;
-    m_fillThread = 0;
-    delete m_updateTimer;
-    m_updateTimer = 0;
-    m_mutex.unlock();
-    if (getEndFrame() > m_lastFillExtent) {
-        emit modelChanged(m_lastFillExtent, getEndFrame());
-    }
-    emit modelChanged();
-    emit ready();
-#ifdef DEBUG_WAVE_FILE_MODEL
-    SVDEBUG << "WaveFileModel::cacheFilled" << endl;
-#endif
-}
-
-void
-WaveFileModel::RangeCacheFillThread::run()
-{
-    size_t cacheBlockSize[2];
-    cacheBlockSize[0] = (1 << m_model.m_zoomConstraint.getMinCachePower());
-    cacheBlockSize[1] = ((unsigned int)((1 << m_model.m_zoomConstraint.getMinCachePower()) *
-                                        sqrt(2.) + 0.01));
-    
-    size_t frame = 0;
-    int readBlockSize = 16384;
-    SampleBlock block;
-
-    if (!m_model.isOK()) return;
-    
-    size_t channels = m_model.getChannelCount();
-    bool updating = m_model.m_reader->isUpdating();
-
-    if (updating) {
-        while (channels == 0 && !m_model.m_exiting) {
-//            SVDEBUG << "WaveFileModel::fill: Waiting for channels..." << endl;
-            sleep(1);
-            channels = m_model.getChannelCount();
-        }
-    }
-
-    Range *range = new Range[2 * channels];
-    float *means = new float[2 * channels];
-    size_t count[2];
-    count[0] = count[1] = 0;
-    for (int i = 0; i < 2 * channels; ++i) {
-        means[i] = 0.f;
-    }
-
-    bool first = true;
-
-    while (first || updating) {
-
-        updating = m_model.m_reader->isUpdating();
-        m_frameCount = m_model.getFrameCount();
-
-//        SVDEBUG << "WaveFileModel::fill: frame = " << frame << ", count = " << m_frameCount << endl;
-
-        while (frame < m_frameCount) {
-
-//            SVDEBUG << "WaveFileModel::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl;
-
-            if (updating && (frame + readBlockSize > m_frameCount)) break;
-
-            m_model.m_reader->getInterleavedFrames(frame, readBlockSize, block);
-
-//            cerr << "block is " << block.size() << endl;
-
-            for (int i = 0; i < readBlockSize; ++i) {
-		
-                if (channels * i + channels > block.size()) break;
-
-                for (int ch = 0; ch < channels; ++ch) {
-
-                    int index = channels * i + ch;
-                    float sample = block[index];
-                    
-                    for (int ct = 0; ct < 2; ++ct) { // cache type
-                        
-                        int rangeIndex = ch * 2 + ct;
-                        
-                        if (sample > range[rangeIndex].max() || count[ct] == 0) {
-                            range[rangeIndex].setMax(sample);
-                        }
-                        if (sample < range[rangeIndex].min() || count[ct] == 0) {
-                            range[rangeIndex].setMin(sample);
-                        }
-
-                        means[rangeIndex] += fabsf(sample);
-                    }
-                }
-                
-                QMutexLocker locker(&m_model.m_mutex);
-
-                for (size_t ct = 0; ct < 2; ++ct) {
-
-                    if (++count[ct] == cacheBlockSize[ct]) {
-                        
-                        for (size_t ch = 0; ch < size_t(channels); ++ch) {
-                            size_t rangeIndex = ch * 2 + ct;
-                            means[rangeIndex] /= count[ct];
-                            range[rangeIndex].setAbsmean(means[rangeIndex]);
-                            m_model.m_cache[ct].push_back(range[rangeIndex]);
-                            range[rangeIndex] = Range();
-                            means[rangeIndex] = 0.f;
-                        }
-
-                        count[ct] = 0;
-                    }
-                }
-                
-                ++frame;
-            }
-            
-            if (m_model.m_exiting) break;
-            
-            m_fillExtent = frame;
-        }
-
-//        cerr << "WaveFileModel: inner loop ended" << endl;
-
-        first = false;
-        if (m_model.m_exiting) break;
-        if (updating) {
-//            cerr << "sleeping..." << endl;
-            sleep(1);
-        }
-    }
-
-    if (!m_model.m_exiting) {
-
-        QMutexLocker locker(&m_model.m_mutex);
-
-        for (size_t ct = 0; ct < 2; ++ct) {
-
-            if (count[ct] > 0) {
-
-                for (size_t ch = 0; ch < size_t(channels); ++ch) {
-                    size_t rangeIndex = ch * 2 + ct;
-                    means[rangeIndex] /= count[ct];
-                    range[rangeIndex].setAbsmean(means[rangeIndex]);
-                    m_model.m_cache[ct].push_back(range[rangeIndex]);
-                    range[rangeIndex] = Range();
-                    means[rangeIndex] = 0.f;
-                }
-
-                count[ct] = 0;
-            }
-            
-            const Range &rr = *m_model.m_cache[ct].begin();
-            MUNLOCK(&rr, m_model.m_cache[ct].capacity() * sizeof(Range));
-        }
-    }
-    
-    delete[] means;
-    delete[] range;
-
-    m_fillExtent = m_frameCount;
-
-#ifdef DEBUG_WAVE_FILE_MODEL        
-    for (size_t ct = 0; ct < 2; ++ct) {
-        cerr << "Cache type " << ct << " now contains " << m_model.m_cache[ct].size() << " ranges" << endl;
-    }
-#endif
-}
-
-void
-WaveFileModel::toXml(QTextStream &out,
-                     QString indent,
-                     QString extraAttributes) const
-{
-    Model::toXml(out, indent,
-                 QString("type=\"wavefile\" file=\"%1\" %2")
-                 .arg(encodeEntities(m_path)).arg(extraAttributes));
-}
-
-    
--- a/data/model/WaveFileModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/WaveFileModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -13,123 +13,36 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _WAVE_FILE_MODEL_H_
-#define _WAVE_FILE_MODEL_H_
-
-#include "base/Thread.h"
-#include <QMutex>
-#include <QTimer>
-
-#include "data/fileio/FileSource.h"
+#ifndef WAVE_FILE_MODEL_H
+#define WAVE_FILE_MODEL_H
 
 #include "RangeSummarisableTimeValueModel.h"
-#include "PowerOfSqrtTwoZoomConstraint.h"
 
 #include <stdlib.h>
 
-class AudioFileReader;
-
 class WaveFileModel : public RangeSummarisableTimeValueModel
 {
     Q_OBJECT
 
 public:
-    WaveFileModel(FileSource source, size_t targetRate = 0);
-    WaveFileModel(FileSource source, AudioFileReader *reader);
-    ~WaveFileModel();
+    virtual ~WaveFileModel();
 
-    bool isOK() const;
-    bool isReady(int *) const;
+    virtual sv_frame_t getFrameCount() const = 0;
+    virtual int getChannelCount() const = 0;
+    virtual sv_samplerate_t getSampleRate() const = 0;
+    virtual sv_samplerate_t getNativeRate() const = 0;
 
-    const ZoomConstraint *getZoomConstraint() const { return &m_zoomConstraint; }
+    virtual QString getTitle() const = 0;
+    virtual QString getMaker() const = 0;
+    virtual QString getLocation() const = 0;
 
-    size_t getFrameCount() const;
-    size_t getChannelCount() const;
-    size_t getSampleRate() const;
-    size_t getNativeRate() const;
+    virtual sv_frame_t getStartFrame() const = 0;
+    virtual sv_frame_t getEndFrame() const = 0;
 
-    QString getTitle() const;
-    QString getMaker() const;
-    QString getLocation() const;
+    virtual void setStartFrame(sv_frame_t startFrame) = 0;
 
-    virtual Model *clone() const;
-
-    float getValueMinimum() const { return -1.0f; }
-    float getValueMaximum() const { return  1.0f; }
-
-    virtual size_t getStartFrame() const { return m_startFrame; }
-    virtual size_t getEndFrame() const { return m_startFrame + getFrameCount(); }
-
-    void setStartFrame(size_t startFrame) { m_startFrame = startFrame; }
-
-    virtual size_t getData(int channel, size_t start, size_t count,
-                           float *buffer) const;
-
-    virtual size_t getData(int channel, size_t start, size_t count,
-                           double *buffer) const;
-
-    virtual size_t getData(size_t fromchannel, size_t tochannel,
-                           size_t start, size_t count,
-                           float **buffers) const;
-
-    virtual size_t getSummaryBlockSize(size_t desired) const;
-
-    virtual void getSummaries(size_t channel, size_t start, size_t count,
-                              RangeBlock &ranges,
-                              size_t &blockSize) const;
-
-    virtual Range getSummary(size_t channel, size_t start, size_t count) const;
-
-    QString getTypeName() const { return tr("Wave File"); }
-
-    virtual void toXml(QTextStream &out,
-                       QString indent = "",
-                       QString extraAttributes = "") const;
-
-protected slots:
-    void fillTimerTimedOut();
-    void cacheFilled();
-    
 protected:
-    void initialize();
-
-    class RangeCacheFillThread : public Thread
-    {
-    public:
-        RangeCacheFillThread(WaveFileModel &model) :
-	    m_model(model), m_fillExtent(0),
-            m_frameCount(model.getFrameCount()) { }
-    
-	size_t getFillExtent() const { return m_fillExtent; }
-        virtual void run();
-
-    protected:
-        WaveFileModel &m_model;
-	size_t m_fillExtent;
-        size_t m_frameCount;
-    };
-         
-    void fillCache();
-
-    FileSource m_source;
-    QString m_path;
-    AudioFileReader *m_reader;
-    bool m_myReader;
-
-    size_t m_startFrame;
-
-    RangeBlock m_cache[2]; // interleaved at two base resolutions
-    mutable QMutex m_mutex;
-    RangeCacheFillThread *m_fillThread;
-    QTimer *m_updateTimer;
-    size_t m_lastFillExtent;
-    bool m_exiting;
-    static PowerOfSqrtTwoZoomConstraint m_zoomConstraint;
-
-    mutable SampleBlock m_directRead;
-    mutable size_t m_lastDirectReadStart;
-    mutable size_t m_lastDirectReadCount;
-    mutable QMutex m_directReadMutex;
+    WaveFileModel() { } // only accessible from subclasses
 };    
 
 #endif
--- a/data/model/WritableWaveFileModel.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/WritableWaveFileModel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -15,6 +15,8 @@
 
 #include "WritableWaveFileModel.h"
 
+#include "ReadOnlyWaveFileModel.h"
+
 #include "base/TempDirectory.h"
 #include "base/Exceptions.h"
 
@@ -28,10 +30,14 @@
 #include <iostream>
 #include <stdint.h>
 
+using namespace std;
+
+const int WritableWaveFileModel::PROPORTION_UNKNOWN = -1;
+
 //#define DEBUG_WRITABLE_WAVE_FILE_MODEL 1
 
-WritableWaveFileModel::WritableWaveFileModel(size_t sampleRate,
-					     size_t channels,
+WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate,
+					     int channels,
 					     QString path) :
     m_model(0),
     m_writer(0),
@@ -40,7 +46,7 @@
     m_channels(channels),
     m_frameCount(0),
     m_startFrame(0),
-    m_completion(0)
+    m_proportion(PROPORTION_UNKNOWN)
 {
     if (path.isEmpty()) {
         try {
@@ -74,7 +80,7 @@
         return;
     }
     
-    m_model = new WaveFileModel(source, m_reader);
+    m_model = new ReadOnlyWaveFileModel(source, m_reader);
     if (!m_model->isOK()) {
         cerr << "WritableWaveFileModel: Error in creating wave file model" << endl;
         delete m_model;
@@ -86,8 +92,8 @@
     m_model->setStartFrame(m_startFrame);
 
     connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
-    connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
-            this, SIGNAL(modelChanged(size_t, size_t)));
+    connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+            this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)));
 }
 
 WritableWaveFileModel::~WritableWaveFileModel()
@@ -98,14 +104,14 @@
 }
 
 void
-WritableWaveFileModel::setStartFrame(size_t startFrame)
+WritableWaveFileModel::setStartFrame(sv_frame_t startFrame)
 {
     m_startFrame = startFrame;
     if (m_model) m_model->setStartFrame(startFrame);
 }
 
 bool
-WritableWaveFileModel::addSamples(float **samples, size_t count)
+WritableWaveFileModel::addSamples(float **samples, sv_frame_t count)
 {
     if (!m_writer) return false;
 
@@ -149,69 +155,65 @@
 bool
 WritableWaveFileModel::isReady(int *completion) const
 {
-    if (completion) *completion = m_completion;
-    return (m_completion == 100);
+    int c = getCompletion();
+    if (completion) *completion = c;
+    if (!isOK()) return false;
+    return (c == 100);
 }
 
 void
-WritableWaveFileModel::setCompletion(int completion)
+WritableWaveFileModel::setWriteProportion(int proportion)
 {
-    m_completion = completion;
-    if (completion == 100) {
-        if (m_reader) m_reader->updateDone();
-    }
+    m_proportion = proportion;
 }
 
-size_t
+int
+WritableWaveFileModel::getWriteProportion() const
+{
+    return m_proportion;
+}
+
+void
+WritableWaveFileModel::writeComplete()
+{
+    if (m_reader) m_reader->updateDone();
+    m_proportion = 100;
+    emit modelChanged();
+}
+
+sv_frame_t
 WritableWaveFileModel::getFrameCount() const
 {
 //    SVDEBUG << "WritableWaveFileModel::getFrameCount: count = " << m_frameCount << endl;
     return m_frameCount;
 }
 
-Model *
-WritableWaveFileModel::clone() const
+vector<float>
+WritableWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const
 {
-    assert(0); //!!!
-    return 0;
+    if (!m_model || m_model->getChannelCount() == 0) return {};
+    return m_model->getData(channel, start, count);
 }
 
-size_t
-WritableWaveFileModel::getData(int channel, size_t start, size_t count,
-                               float *buffer) const
+vector<vector<float>>
+WritableWaveFileModel::getMultiChannelData(int fromchannel, int tochannel,
+                                           sv_frame_t start, sv_frame_t count) const
 {
-    if (!m_model || m_model->getChannelCount() == 0) return 0;
-    return m_model->getData(channel, start, count, buffer);
-}
-
-size_t
-WritableWaveFileModel::getData(int channel, size_t start, size_t count,
-                               double *buffer) const
-{
-    if (!m_model || m_model->getChannelCount() == 0) return 0;
-    return m_model->getData(channel, start, count, buffer);
-}
-
-size_t
-WritableWaveFileModel::getData(size_t fromchannel, size_t tochannel,
-                               size_t start, size_t count,
-                               float **buffers) const
-{
-    if (!m_model || m_model->getChannelCount() == 0) return 0;
-    return m_model->getData(fromchannel, tochannel, start, count, buffers);
+    if (!m_model || m_model->getChannelCount() == 0) return {};
+    return m_model->getMultiChannelData(fromchannel, tochannel, start, count);
 }    
 
-size_t
-WritableWaveFileModel::getSummaryBlockSize(size_t desired) const
+int
+WritableWaveFileModel::getSummaryBlockSize(int desired) const
 {
     if (!m_model) return desired;
     return m_model->getSummaryBlockSize(desired);
 }
 
 void
-WritableWaveFileModel::getSummaries(size_t channel, size_t start, size_t count,
+WritableWaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count,
                                     RangeBlock &ranges,
-                                    size_t &blockSize) const
+                                    int &blockSize) const
 {
     ranges.clear();
     if (!m_model || m_model->getChannelCount() == 0) return;
@@ -219,7 +221,7 @@
 }
 
 WritableWaveFileModel::Range
-WritableWaveFileModel::getSummary(size_t channel, size_t start, size_t count) const
+WritableWaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const
 {
     if (!m_model || m_model->getChannelCount() == 0) return Range();
     return m_model->getSummary(channel, start, count);
@@ -230,15 +232,16 @@
                              QString indent,
                              QString extraAttributes) const
 {
-    // We don't actually write the data to XML.  We just write a brief
-    // description of the model.  Any code that uses this class is
-    // going to need to be aware that it will have to make separate
-    // arrangements for the audio file itself.
+    // The assumption here is that the underlying wave file has
+    // already been saved somewhere (its location is available through
+    // getLocation()) and that the code that uses this class is
+    // dealing with the problem of making sure it remains available.
+    // We just write this out as if it were a normal wave file.
 
     Model::toXml
         (out, indent,
-         QString("type=\"writablewavefile\" file=\"%1\" channels=\"%2\" %3")
+         QString("type=\"wavefile\" file=\"%1\" subtype=\"writable\" %2")
          .arg(encodeEntities(m_writer->getPath()))
-         .arg(m_model->getChannelCount()).arg(extraAttributes));
+         .arg(extraAttributes));
 }
 
--- a/data/model/WritableWaveFileModel.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/model/WritableWaveFileModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -13,71 +13,113 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _WRITABLE_WAVE_FILE_MODEL_H_
-#define _WRITABLE_WAVE_FILE_MODEL_H_
+#ifndef WRITABLE_WAVE_FILE_MODEL_H
+#define WRITABLE_WAVE_FILE_MODEL_H
 
 #include "WaveFileModel.h"
+#include "ReadOnlyWaveFileModel.h"
+#include "PowerOfSqrtTwoZoomConstraint.h"
 
 class WavFileWriter;
 class WavFileReader;
 
-class WritableWaveFileModel : public RangeSummarisableTimeValueModel
+class WritableWaveFileModel : public WaveFileModel
 {
     Q_OBJECT
 
 public:
-    WritableWaveFileModel(size_t sampleRate, size_t channels, QString path = "");
+    WritableWaveFileModel(sv_samplerate_t sampleRate, int channels, QString path = "");
     ~WritableWaveFileModel();
 
     /**
      * Call addSamples to append a block of samples to the end of the
-     * file.  Caller should also call setCompletion to update the
-     * progress of this file, if it has a known end point, and should
-     * call setCompletion(100) when the file has been written.
+     * file.  Caller should also call setWriteProportion() to update
+     * the progress of this file, if it has a known end point, and
+     * should call writeComplete() when the file has been written.
      */
-    virtual bool addSamples(float **samples, size_t count);
+    virtual bool addSamples(float **samples, sv_frame_t count);
+
+    /**
+     * Set the proportion of the file which has been written so far,
+     * as a percentage. This may be used to indicate progress.
+     *
+     * Note that this differs from the "completion" percentage
+     * reported through isReady()/getCompletion(). That percentage is
+     * updated when "internal processing has advanced... but the model
+     * has not changed externally", i.e. it reports progress in
+     * calculating the initial state of a model. In contrast, an
+     * update to setWriteProportion corresponds to a change in the
+     * externally visible state of the model (i.e. it contains more
+     * data than before).
+     */
+    void setWriteProportion(int proportion);
+
+    /**
+     * Indicate that writing is complete. You should call this even if
+     * you have never called setWriteProportion().
+     */
+    void writeComplete();
+
+    static const int PROPORTION_UNKNOWN;
+    
+    /**
+     * Get the proportion of the file which has been written so far,
+     * as a percentage. Return PROPORTION_UNKNOWN if unknown.
+     */
+    int getWriteProportion() const;
     
     bool isOK() const;
     bool isReady(int *) const;
-
-    virtual void setCompletion(int completion); // percentage
-    virtual int getCompletion() const { return m_completion; }
+    
+    /**
+     * Return the generation completion percentage of this model. This
+     * is always 100, because the model is always in a complete state
+     * -- it just contains varying amounts of data depending on how
+     * much has been written.
+     */
+    virtual int getCompletion() const { return 100; }
 
     const ZoomConstraint *getZoomConstraint() const {
         static PowerOfSqrtTwoZoomConstraint zc;
         return &zc;
     }
 
-    size_t getFrameCount() const;
-    size_t getChannelCount() const { return m_channels; }
-    size_t getSampleRate() const { return m_sampleRate; }
+    sv_frame_t getFrameCount() const;
+    int getChannelCount() const { return m_channels; }
+    sv_samplerate_t getSampleRate() const { return m_sampleRate; }
+    sv_samplerate_t getNativeRate() const { return m_sampleRate; }
 
-    virtual Model *clone() const;
+    QString getTitle() const {
+        if (m_model) return m_model->getTitle();
+        else return "";
+    } 
+    QString getMaker() const {
+        if (m_model) return m_model->getMaker();
+        else return "";
+    }
+    QString getLocation() const {
+        if (m_model) return m_model->getLocation();
+        else return "";
+    }
 
     float getValueMinimum() const { return -1.0f; }
     float getValueMaximum() const { return  1.0f; }
 
-    virtual size_t getStartFrame() const { return m_startFrame; }
-    virtual size_t getEndFrame() const { return m_startFrame + getFrameCount(); }
+    virtual sv_frame_t getStartFrame() const { return m_startFrame; }
+    virtual sv_frame_t getEndFrame() const { return m_startFrame + getFrameCount(); }
 
-    void setStartFrame(size_t startFrame);
+    void setStartFrame(sv_frame_t startFrame);
 
-    virtual size_t getData(int channel, size_t start, size_t count,
-                           float *buffer) const;
+    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const;
 
-    virtual size_t getData(int channel, size_t start, size_t count,
-                           double *buffer) const;
+    virtual std::vector<std::vector<float>> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const;
 
-    virtual size_t getData(size_t fromchannel, size_t tochannel,
-                           size_t start, size_t count,
-                           float **buffer) const;
+    virtual int getSummaryBlockSize(int desired) const;
 
-    virtual size_t getSummaryBlockSize(size_t desired) const;
+    virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count,
+                              RangeBlock &ranges, int &blockSize) const;
 
-    virtual void getSummaries(size_t channel, size_t start, size_t count,
-                              RangeBlock &ranges, size_t &blockSize) const;
-
-    virtual Range getSummary(size_t channel, size_t start, size_t count) const;
+    virtual Range getSummary(int channel, sv_frame_t start, sv_frame_t count) const;
 
     QString getTypeName() const { return tr("Writable Wave File"); }
 
@@ -86,14 +128,14 @@
                        QString extraAttributes = "") const;
 
 protected:
-    WaveFileModel *m_model;
+    ReadOnlyWaveFileModel *m_model;
     WavFileWriter *m_writer;
     WavFileReader *m_reader;
-    size_t m_sampleRate;
-    size_t m_channels;
-    size_t m_frameCount;
-    size_t m_startFrame;
-    int m_completion;
+    sv_samplerate_t m_sampleRate;
+    int m_channels;
+    sv_frame_t m_frameCount;
+    sv_frame_t m_startFrame;
+    int m_proportion;
 };
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/test/Compares.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,52 @@
+
+#ifndef TEST_COMPARES_H
+#define TEST_COMPARES_H
+
+// These macros are used for comparing generated results, and they
+// aren't always going to be exact. Adding 0.1 to each value gives
+// us a little more fuzz in qFuzzyCompare (which ultimately does
+// the comparison).
+
+#define COMPARE_ZERO(a) \
+    QCOMPARE(a + 0.1, 0.1)
+
+#define COMPARE_ZERO_F(a) \
+    QCOMPARE(a + 0.1f, 0.1f)
+
+#define COMPARE_FUZZIER(a, b) \
+    QCOMPARE(a + 0.1, b + 0.1)
+
+#define COMPARE_FUZZIER_F(a, b) \
+    QCOMPARE(a + 0.1f, b + 0.1f)
+
+#define COMPARE_ALL_TO(a, n) \
+    for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \
+        COMPARE_FUZZIER(a[cmp_i], n); \
+    }
+
+#define COMPARE_ALL(a, b)						\
+    for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \
+        COMPARE_FUZZIER(a[cmp_i], b[cmp_i]); \
+    }
+
+#define COMPARE_SCALED(a, b, s)						\
+    for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \
+        COMPARE_FUZZIER(a[cmp_i] / s, b[cmp_i]); \
+    }
+
+#define COMPARE_ALL_TO_F(a, n) \
+    for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \
+        COMPARE_FUZZIER_F(a[cmp_i], n); \
+    }
+
+#define COMPARE_ALL_F(a, b)						\
+    for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \
+        COMPARE_FUZZIER_F(a[cmp_i], b[cmp_i]); \
+    }
+
+#define COMPARE_SCALED_F(a, b, s)						\
+    for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \
+        COMPARE_FUZZIER_F(a[cmp_i] / s, b[cmp_i]); \
+    }
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/test/MockWaveModel.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,94 @@
+/* -*- 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.
+*/
+
+#include "MockWaveModel.h"
+
+#include <cmath>
+
+using namespace std;
+
+MockWaveModel::MockWaveModel(vector<Sort> sorts, int length, int pad)
+{
+    for (auto sort: sorts) {
+	m_data.push_back(generate(sort, length, pad));
+    }
+}
+
+vector<float>
+MockWaveModel::getData(int channel, sv_frame_t start, sv_frame_t count) const
+{
+    sv_frame_t i = 0;
+
+//    cerr << "MockWaveModel::getData(" << channel << "," << start << "," << count << "): ";
+
+    vector<float> data;
+    
+    while (i < count) {
+	sv_frame_t idx = start + i;
+	if (!in_range_for(m_data[channel], idx)) break;
+	data.push_back(m_data[channel][idx]);
+//	cerr << data[i] << " ";
+	++i;
+    }
+
+//    cerr << endl;
+    
+    return data;
+}
+
+vector<vector<float>>
+MockWaveModel::getMultiChannelData(int fromchannel, int tochannel,
+				   sv_frame_t start, sv_frame_t count) const
+{
+    vector<vector<float>> data(tochannel - fromchannel + 1);
+    
+    for (int c = fromchannel; c <= tochannel; ++c) {
+        data.push_back(getData(c, start, count));
+    }
+
+    return data;
+}
+
+vector<float>
+MockWaveModel::generate(Sort sort, int length, int pad) const
+{
+    vector<float> data;
+
+    for (int i = 0; i < pad; ++i) {
+        data.push_back(0.f);
+    }
+    
+    for (int i = 0; i < length; ++i) {
+
+	double v = 0.0;
+	
+	switch (sort) {
+	case DC: v = 1.0; break;
+	case Sine: v = sin((2.0 * M_PI / 8.0) * i); break;
+	case Cosine: v = cos((2.0 * M_PI / 8.0) * i); break;
+	case Nyquist: v = (i % 2) * 2 - 1; break;
+	case Dirac: v = (i == 0) ? 1.0 : 0.0; break;
+	}
+
+	data.push_back(float(v));
+    }
+
+    for (int i = 0; i < pad; ++i) {
+        data.push_back(0.f);
+    }
+
+    return data;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/test/MockWaveModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,62 @@
+/* -*- 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 MOCK_WAVE_MODEL_H
+#define MOCK_WAVE_MODEL_H
+
+#include "../DenseTimeValueModel.h"
+
+#include <vector>
+
+enum Sort {
+    DC,
+    Sine,
+    Cosine,
+    Nyquist,
+    Dirac
+};
+
+class MockWaveModel : public DenseTimeValueModel
+{
+    Q_OBJECT
+
+public:
+    /** One Sort per channel! Length is in samples, and is in addition
+     * to "pad" number of zero samples at the start and end */
+    MockWaveModel(std::vector<Sort> sorts, int length, int pad);
+
+    virtual float getValueMinimum() const { return -1.f; }
+    virtual float getValueMaximum() const { return  1.f; }
+    virtual int getChannelCount() const { return int(m_data.size()); }
+    
+    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const;
+    virtual std::vector<std::vector<float>> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const;
+
+    virtual bool canPlay() const { return true; }
+    virtual QString getDefaultPlayClipId() const { return ""; }
+
+    virtual sv_frame_t getStartFrame() const { return 0; }
+    virtual sv_frame_t getEndFrame() const { return m_data[0].size(); }
+    virtual sv_samplerate_t getSampleRate() const { return 44100; }
+    virtual bool isOK() const { return true; }
+    
+    QString getTypeName() const { return tr("Mock Wave"); }
+
+private:
+    std::vector<std::vector<float> > m_data;
+    std::vector<float> generate(Sort sort, int length, int pad) const;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/test/TestFFTModel.h	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,255 @@
+/* -*- 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 TEST_FFT_MODEL_H
+#define TEST_FFT_MODEL_H
+
+#include "../FFTModel.h"
+
+#include "MockWaveModel.h"
+
+#include "Compares.h"
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+#include <complex>
+
+using namespace std;
+
+class TestFFTModel : public QObject
+{
+    Q_OBJECT
+
+private:
+    void test(DenseTimeValueModel *model,
+              WindowType window, int windowSize, int windowIncrement, int fftSize,
+              int columnNo, vector<vector<complex<float>>> expectedValues,
+              int expectedWidth) {
+        for (int ch = 0; in_range_for(expectedValues, ch); ++ch) {
+            FFTModel fftm(model, ch, window, windowSize, windowIncrement, fftSize);
+            QCOMPARE(fftm.getWidth(), expectedWidth);
+            int hs1 = fftSize/2 + 1;
+            QCOMPARE(fftm.getHeight(), hs1);
+            vector<float> reals(hs1 + 1, 0.f);
+            vector<float> imags(hs1 + 1, 0.f);
+            reals[hs1] = 999.f; // overrun guards
+            imags[hs1] = 999.f;
+            for (int stepThrough = 0; stepThrough <= 1; ++stepThrough) {
+                if (stepThrough) {
+                    // Read through the columns in order instead of
+                    // randomly accessing the one we want. This is to
+                    // exercise the case where the FFT model saves
+                    // part of each input frame and moves along by
+                    // only the non-overlapping distance
+                    for (int sc = 0; sc < columnNo; ++sc) {
+                        fftm.getValuesAt(sc, &reals[0], &imags[0]);
+                    }
+                }
+                fftm.getValuesAt(columnNo, &reals[0], &imags[0]);
+                for (int i = 0; i < hs1; ++i) {
+                    float eRe = expectedValues[ch][i].real();
+                    float eIm = expectedValues[ch][i].imag();
+                    float thresh = 1e-5f;
+                    if (abs(reals[i] - eRe) > thresh ||
+                        abs(imags[i] - eIm) > thresh) {
+                        cerr << "ERROR: output is not as expected for column "
+                             << i << " in channel " << ch << " (stepThrough = "
+                             << stepThrough << ")" << endl;
+                        cerr << "expected : ";
+                        for (int j = 0; j < hs1; ++j) {
+                            cerr << expectedValues[ch][j] << " ";
+                        }
+                        cerr << "\nactual   : ";
+                        for (int j = 0; j < hs1; ++j) {
+                            cerr << complex<float>(reals[j], imags[j]) << " ";
+                        }
+                        cerr << endl;
+                    }
+                    COMPARE_FUZZIER_F(reals[i], eRe);
+                    COMPARE_FUZZIER_F(imags[i], eIm);
+                }
+                QCOMPARE(reals[hs1], 999.f);
+                QCOMPARE(imags[hs1], 999.f);
+            }
+        }
+    }
+
+private slots:
+
+    // NB. FFTModel columns are centred on the sample frame, and in
+    // particular this means column 0 is centred at sample 0 (i.e. it
+    // contains only half the window-size worth of real samples, the
+    // others are 0-valued from before the origin).  Generally in
+    // these tests we are padding our signal with half a window of
+    // zeros, in order that the result for column 0 is all zeros
+    // (rather than something with a step in it that is harder to
+    // reason about the FFT of) and the results for subsequent columns
+    // are those of our expected signal.
+    
+    void dc_simple_rect() {
+	MockWaveModel mwm({ DC }, 16, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 0,
+             { { {}, {}, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 1,
+             { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 2,
+             { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 3,
+             { { {}, {}, {}, {}, {} } }, 4);
+    }
+
+    void dc_simple_hann() {
+        // The Hann window function is a simple sinusoid with period
+        // equal to twice the window size, and it halves the DC energy
+	MockWaveModel mwm({ DC }, 16, 4);
+        test(&mwm, HanningWindow, 8, 8, 8, 0,
+             { { {}, {}, {}, {}, {} } }, 4);
+        test(&mwm, HanningWindow, 8, 8, 8, 1,
+             { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4);
+        test(&mwm, HanningWindow, 8, 8, 8, 2,
+             { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4);
+        test(&mwm, HanningWindow, 8, 8, 8, 3,
+             { { {}, {}, {}, {}, {} } }, 4);
+    }
+    
+    void dc_simple_hann_halfoverlap() {
+	MockWaveModel mwm({ DC }, 16, 4);
+        test(&mwm, HanningWindow, 8, 4, 8, 0,
+             { { {}, {}, {}, {}, {} } }, 7);
+        test(&mwm, HanningWindow, 8, 4, 8, 2,
+             { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7);
+        test(&mwm, HanningWindow, 8, 4, 8, 3,
+             { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7);
+        test(&mwm, HanningWindow, 8, 4, 8, 6,
+             { { {}, {}, {}, {}, {} } }, 7);
+    }
+    
+    void sine_simple_rect() {
+	MockWaveModel mwm({ Sine }, 16, 4);
+        // Sine: output is purely imaginary. Note the sign is flipped
+        // (normally the first half of the output would have negative
+        // sign for a sine starting at 0) because the model does an
+        // FFT shift to centre the phase
+        test(&mwm, RectangularWindow, 8, 8, 8, 0,
+             { { {}, {}, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 1,
+             { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 2,
+             { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 3,
+             { { {}, {}, {}, {}, {} } }, 4);
+    }
+    
+    void cosine_simple_rect() {
+	MockWaveModel mwm({ Cosine }, 16, 4);
+        // Cosine: output is purely real. Note the sign is flipped
+        // because the model does an FFT shift to centre the phase
+        test(&mwm, RectangularWindow, 8, 8, 8, 0,
+             { { {}, {}, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 1,
+             { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 2,
+             { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 3,
+             { { {}, {}, {}, {}, {} } }, 4);
+    }
+    
+    void twochan_simple_rect() {
+	MockWaveModel mwm({ Sine, Cosine }, 16, 4);
+        // Test that the two channels are read and converted separately
+        test(&mwm, RectangularWindow, 8, 8, 8, 0,
+             {
+                 { {}, {}, {}, {}, {} },
+                 { {}, {}, {}, {}, {} }
+             }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 1,
+             {
+                 { {}, {  0.f, 2.f }, {}, {}, {} },
+                 { {}, { -2.f, 0.f }, {}, {}, {} }
+             }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 2,
+             {
+                 { {}, {  0.f, 2.f }, {}, {}, {} },
+                 { {}, { -2.f, 0.f }, {}, {}, {} }
+             }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 3,
+             {
+                 { {}, {}, {}, {}, {} },
+                 { {}, {}, {}, {}, {} }
+             }, 4);
+    }
+    
+    void nyquist_simple_rect() {
+	MockWaveModel mwm({ Nyquist }, 16, 4);
+        // Again, the sign is flipped. This has the same amount of
+        // energy as the DC example
+        test(&mwm, RectangularWindow, 8, 8, 8, 0,
+             { { {}, {}, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 1,
+             { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 2,
+             { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 3,
+             { { {}, {}, {}, {}, {} } }, 4);
+    }
+    
+    void dirac_simple_rect() {
+	MockWaveModel mwm({ Dirac }, 16, 4);
+        // The window scales by 0.5 and some signs are flipped. Only
+        // column 1 has any data (the single impulse).
+        test(&mwm, RectangularWindow, 8, 8, 8, 0,
+             { { {}, {}, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 1,
+             { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 2,
+             { { {}, {}, {}, {}, {} } }, 4);
+        test(&mwm, RectangularWindow, 8, 8, 8, 3,
+             { { {}, {}, {}, {}, {} } }, 4);
+    }
+    
+    void dirac_simple_rect_2() {
+	MockWaveModel mwm({ Dirac }, 16, 8);
+        // With 8 samples padding, the FFT shift places the first
+        // Dirac impulse at the start of column 1, thus giving all
+        // positive values
+        test(&mwm, RectangularWindow, 8, 8, 8, 0,
+             { { {}, {}, {}, {}, {} } }, 5);
+        test(&mwm, RectangularWindow, 8, 8, 8, 1,
+             { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 5);
+        test(&mwm, RectangularWindow, 8, 8, 8, 2,
+             { { {}, {}, {}, {}, {} } }, 5);
+        test(&mwm, RectangularWindow, 8, 8, 8, 3,
+             { { {}, {}, {}, {}, {} } }, 5);
+        test(&mwm, RectangularWindow, 8, 8, 8, 4,
+             { { {}, {}, {}, {}, {} } }, 5);
+    }
+
+    void dirac_simple_rect_halfoverlap() {
+	MockWaveModel mwm({ Dirac }, 16, 4);
+        test(&mwm, RectangularWindow, 8, 4, 8, 0,
+             { { {}, {}, {}, {}, {} } }, 7);
+        test(&mwm, RectangularWindow, 8, 4, 8, 1,
+             { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 7);
+        test(&mwm, RectangularWindow, 8, 4, 8, 2,
+             { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 7);
+        test(&mwm, RectangularWindow, 8, 4, 8, 3,
+             { { {}, {}, {}, {}, {} } }, 7);
+    }
+    
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/test/main.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,44 @@
+/* -*- 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 "TestFFTModel.h"
+
+#include <QtTest>
+
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char *argv[])
+{
+    int good = 0, bad = 0;
+
+    QCoreApplication app(argc, argv);
+    app.setOrganizationName("Sonic Visualiser");
+    app.setApplicationName("test-model");
+
+    {
+	TestFFTModel t;
+	if (QTest::qExec(&t, argc, argv) == 0) ++good;
+	else ++bad;
+    }
+
+    if (bad > 0) {
+	cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl;
+	return 1;
+    } else {
+	cerr << "All tests passed" << endl;
+	return 0;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/test/test.pro	Wed Apr 20 12:06:28 2016 +0100
@@ -0,0 +1,72 @@
+
+TEMPLATE = app
+
+LIBS += -L../../.. -L../../../../dataquay -L../../../release -L../../../../dataquay/release -lsvcore -ldataquay
+
+win32-g++ {
+    INCLUDEPATH += ../../../../sv-dependency-builds/win32-mingw/include
+    LIBS += -L../../../../sv-dependency-builds/win32-mingw/lib
+}
+win32-msvc* {
+    INCLUDEPATH += ../../../../sv-dependency-builds/win32-msvc/include
+    LIBS += -L../../../../sv-dependency-builds/win32-msvc/lib
+}
+mac* {
+    INCLUDEPATH += ../../../../sv-dependency-builds/osx/include
+    LIBS += -L../../../../sv-dependency-builds/osx/lib
+}
+
+exists(../../../config.pri) {
+    include(../../../config.pri)
+}
+
+!exists(../../../config.pri) {
+
+    CONFIG += release
+    DEFINES += NDEBUG BUILD_RELEASE NO_TIMING
+
+    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
+
+    win* {
+        LIBS += -llo -lwinmm -lws2_32
+    }
+    macx* {
+        DEFINES += HAVE_COREAUDIO
+        LIBS += -framework CoreAudio -framework CoreMidi -framework AudioUnit -framework AudioToolbox -framework CoreFoundation -framework CoreServices -framework Accelerate
+    }
+}
+
+CONFIG += qt thread warn_on stl rtti exceptions console c++11
+QT += network xml testlib
+QT -= gui
+
+TARGET = svcore-data-model-test
+
+DEPENDPATH += ../../..
+INCLUDEPATH += ../../..
+OBJECTS_DIR = o
+MOC_DIR = o
+
+HEADERS += Compares.h MockWaveModel.h TestFFTModel.h
+SOURCES += MockWaveModel.cpp main.cpp
+
+win* {
+//PRE_TARGETDEPS += ../../../svcore.lib
+}
+!win* {
+PRE_TARGETDEPS += ../../../libsvcore.a
+}
+
+!win32 {
+    !macx* {
+        QMAKE_POST_LINK=./$${TARGET}
+    }
+    macx* {
+        QMAKE_POST_LINK=./$${TARGET}.app/Contents/MacOS/$${TARGET}
+    }
+}
+
+win32:QMAKE_POST_LINK=./release/$${TARGET}.exe
+
--- a/data/osc/OSCMessage.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/osc/OSCMessage.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -38,14 +38,14 @@
     m_args.push_back(arg);
 }
 
-size_t
+int
 OSCMessage::getArgCount() const
 {
-    return m_args.size();
+    return int(m_args.size());
 }
 
 const QVariant &
-OSCMessage::getArg(size_t i) const
+OSCMessage::getArg(int i) const
 {
     return m_args[i];
 }
--- a/data/osc/OSCMessage.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/osc/OSCMessage.h	Wed Apr 20 12:06:28 2016 +0100
@@ -32,7 +32,7 @@
 class OSCMessage
 {
 public:
-    OSCMessage() { }
+    OSCMessage() : m_target(0), m_targetData(0) { }
     ~OSCMessage();
 
     void setTarget(const int &target) { m_target = target; }
@@ -47,8 +47,8 @@
     void clearArgs();
     void addArg(QVariant arg);
 
-    size_t getArgCount() const;
-    const QVariant &getArg(size_t i) const;
+    int getArgCount() const;
+    const QVariant &getArg(int i) const;
 
 private:
     int m_target;
--- a/data/osc/OSCQueue.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/osc/OSCQueue.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -142,7 +142,7 @@
     return url;
 }
 
-size_t
+int
 OSCQueue::getMessagesAvailable() const
 {
     return m_buffer.getReadSpace();
--- a/data/osc/OSCQueue.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/data/osc/OSCQueue.h	Wed Apr 20 12:06:28 2016 +0100
@@ -42,7 +42,7 @@
     bool isOK() const;
 
     bool isEmpty() const { return getMessagesAvailable() == 0; }
-    size_t getMessagesAvailable() const;
+    int getMessagesAvailable() const;
     OSCMessage readMessage();
 
     QString getOSCURL() const;
--- a/data/sv.pch	Tue Jul 14 15:04:46 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-../sv.pch
\ No newline at end of file
--- a/plugin/DSSIPluginFactory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/DSSIPluginFactory.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -82,7 +82,7 @@
 	list.push_back(m_taxonomy[*i]);
 	list.push_back(QString("%1").arg(descriptor->PortCount));
 
-	for (unsigned long p = 0; p < descriptor->PortCount; ++p) {
+	for (int p = 0; p < (int)descriptor->PortCount; ++p) {
 
 	    int type = 0;
 	    if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) {
@@ -113,9 +113,9 @@
 DSSIPluginFactory::instantiatePlugin(QString identifier,
 				     int instrument,
 				     int position,
-				     unsigned int sampleRate,
-				     unsigned int blockSize,
-				     unsigned int channels)
+				     sv_samplerate_t sampleRate,
+				     int blockSize,
+				     int channels)
 {
     Profiler profiler("DSSIPluginFactory::instantiatePlugin");
 
@@ -274,6 +274,9 @@
 #else
     baseUri = "http://dssi.sourceforge.net/ontology#";
 #endif
+#else
+    // avoid unused parameter
+    baseUri = "";
 #endif
 
     return lrdfPaths;
@@ -281,7 +284,7 @@
 
 
 void
-DSSIPluginFactory::discoverPlugins(QString soname)
+DSSIPluginFactory::discoverPluginsFrom(QString soname)
 {
     Profiler profiler("DSSIPluginFactory::discoverPlugins");
 
@@ -343,8 +346,8 @@
             category = m_taxonomy[identifier];
         }
 
-	if (category == "" && ladspaDescriptor->Name != 0) {
-	    std::string name = ladspaDescriptor->Name;
+	if (category == "") {
+	    std::string name = rtd->name;
 	    if (name.length() > 4 &&
 		name.substr(name.length() - 4) == " VST") {
 		if (descriptor->run_synth || descriptor->run_multiple_synths) {
@@ -372,13 +375,13 @@
 	
 	unsigned int controlPortNumber = 1;
 	
-	for (unsigned long i = 0; i < ladspaDescriptor->PortCount; i++) {
+	for (int i = 0; i < (int)ladspaDescriptor->PortCount; i++) {
 	    
 	    if (LADSPA_IS_PORT_CONTROL(ladspaDescriptor->PortDescriptors[i])) {
 		
 		if (def_uri && defs) {
 		    
-		    for (unsigned int j = 0; j < defs->count; j++) {
+		    for (int j = 0; j < (int)defs->count; j++) {
 			if (defs->items[j].pid == controlPortNumber) {
 //			    cerr << "Default for this port (" << defs->items[j].pid << ", " << defs->items[j].label << ") is " << defs->items[j].value << "; applying this to port number " << i << " with name " << ladspaDescriptor->PortNames[i] << endl;
 			    m_portDefaults[ladspaDescriptor->UniqueID][i] =
--- a/plugin/DSSIPluginFactory.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/DSSIPluginFactory.h	Wed Apr 20 12:06:28 2016 +0100
@@ -40,19 +40,23 @@
     virtual RealTimePluginInstance *instantiatePlugin(QString identifier,
 						      int clientId,
 						      int position,
-						      unsigned int sampleRate,
-						      unsigned int blockSize,
-						      unsigned int channels);
+						      sv_samplerate_t sampleRate,
+						      int blockSize,
+						      int channels);
 
 protected:
     DSSIPluginFactory();
     friend class RealTimePluginFactory;
 
+    virtual PluginScan::PluginType getPluginType() const {
+        return PluginScan::DSSIPlugin;
+    }
+
     virtual std::vector<QString> getPluginPath();
 
     virtual std::vector<QString> getLRDFPath(QString &baseUri);
 
-    virtual void discoverPlugins(QString soName);
+    virtual void discoverPluginsFrom(QString soName);
 
     virtual const LADSPA_Descriptor *getLADSPADescriptor(QString identifier);
     virtual const DSSI_Descriptor *getDSSIDescriptor(QString identifier);
--- a/plugin/DSSIPluginInstance.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/DSSIPluginInstance.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -34,7 +34,7 @@
 #endif
 
 //#define DEBUG_DSSI 1
-//#define DEBUG_DSSI_PROCESS 1
+#define DEBUG_DSSI_PROCESS 1
 
 #define EVENT_BUFFER_SIZE 1023
 
@@ -57,13 +57,14 @@
 				       int clientId,
 				       QString identifier,
 				       int position,
-				       unsigned long sampleRate,
-				       size_t blockSize,
+				       sv_samplerate_t sampleRate,
+				       int blockSize,
 				       int idealChannelCount,
 				       const DSSI_Descriptor* descriptor) :
     RealTimePluginInstance(factory, identifier),
     m_client(clientId),
     m_position(position),
+    m_instanceHandle(0),
     m_descriptor(descriptor),
     m_programCacheValid(false),
     m_eventBuffer(EVENT_BUFFER_SIZE),
@@ -89,7 +90,7 @@
     for (size_t i = 0; i < m_audioPortsIn.size(); ++i) {
 	m_inputBuffers[i] = new sample_t[blockSize];
     }
-    for (size_t i = 0; i < m_outputBufferCount; ++i) {
+    for (int i = 0; i < m_outputBufferCount; ++i) {
 	m_outputBuffers[i] = new sample_t[blockSize];
     }
 
@@ -146,11 +147,12 @@
 {
     ParameterList list;
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
+    if (!f) return list;
     
-    for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+    for (int i = 0; in_range_for(m_controlPortsIn, i); ++i) {
         
         ParameterDescriptor pd;
-        unsigned int pn = m_controlPortsIn[i].first;
+        int pn = (int)m_controlPortsIn[i].first;
 
         pd.identifier = m_descriptor->LADSPA_Plugin->PortNames[pn];
         pd.name = pd.identifier;
@@ -179,7 +181,7 @@
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::getParameter(" << id << ")" << endl;
 #endif
-    for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+    for (int i = 0; in_range_for(m_controlPortsIn, i); ++i) {
         if (id == m_descriptor->LADSPA_Plugin->PortNames[m_controlPortsIn[i].first]) {
 #ifdef DEBUG_DSSI
             cerr << "Matches port " << i << endl;
@@ -202,7 +204,7 @@
     SVDEBUG << "DSSIPluginInstance::setParameter(" << id << ", " << value << ")" << endl;
 #endif
 
-    for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+    for (int i = 0; in_range_for(m_controlPortsIn, i); ++i) {
         if (id == m_descriptor->LADSPA_Plugin->PortNames[m_controlPortsIn[i].first]) {
             setParameterValue(i, value);
             break;
@@ -221,7 +223,7 @@
     //
     const LADSPA_Descriptor *descriptor = m_descriptor->LADSPA_Plugin;
 
-    for (unsigned long i = 0; i < descriptor->PortCount; ++i)
+    for (int i = 0; i < (int)descriptor->PortCount; ++i)
     {
         if (LADSPA_IS_PORT_AUDIO(descriptor->PortDescriptors[i]))
         {
@@ -238,7 +240,7 @@
 
 		LADSPA_Data *data = new LADSPA_Data(0.0);
 
-		m_controlPortsIn.push_back(std::pair<unsigned long, LADSPA_Data*>
+		m_controlPortsIn.push_back(std::pair<long, LADSPA_Data*>
 					   (i, data));
 
 		m_backupControlPortsIn.push_back(0.0);
@@ -246,7 +248,7 @@
 	    } else {
 		LADSPA_Data *data = new LADSPA_Data(0.0);
 		m_controlPortsOut.push_back(
-                    std::pair<unsigned long, LADSPA_Data*>(i, data));
+                    std::pair<long, LADSPA_Data*>(i, data));
 		if (!strcmp(descriptor->PortNames[i], "latency") ||
 		    !strcmp(descriptor->PortNames[i], "_latency")) {
 #ifdef DEBUG_DSSI
@@ -263,13 +265,14 @@
 #endif
     }
 
-    m_outputBufferCount = std::max(m_idealChannelCount, m_audioPortsOut.size());
+    m_outputBufferCount = std::max(m_idealChannelCount,
+                                   (int)m_audioPortsOut.size());
 }
 
-size_t
+sv_frame_t
 DSSIPluginInstance::getLatency()
 {
-    size_t latency = 0;
+    sv_frame_t latency = 0;
 
 #ifdef DEBUG_DSSI_PROCESS
     SVDEBUG << "DSSIPluginInstance::getLatency(): m_latencyPort " << m_latencyPort << ", m_run " << m_run << endl;
@@ -277,14 +280,14 @@
 
     if (m_latencyPort) {
 	if (!m_run) {
-            for (size_t i = 0; i < getAudioInputCount(); ++i) {
-                for (size_t j = 0; j < m_blockSize; ++j) {
+            for (int i = 0; i < getAudioInputCount(); ++i) {
+                for (int j = 0; j < m_blockSize; ++j) {
                     m_inputBuffers[i][j] = 0.f;
                 }
             }
             run(Vamp::RealTime::zeroTime);
         }
-	latency = (size_t)(*m_latencyPort + 0.1);
+	latency = (sv_frame_t)(*m_latencyPort + 0.1);
     }
     
 #ifdef DEBUG_DSSI_PROCESS
@@ -310,7 +313,7 @@
 }
 
 void
-DSSIPluginInstance::setIdealChannelCount(size_t channels)
+DSSIPluginInstance::setIdealChannelCount(int channels)
 {
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::setIdealChannelCount: channel count "
@@ -330,7 +333,7 @@
 
     if (channels > m_outputBufferCount) {
 
-	for (size_t i = 0; i < m_outputBufferCount; ++i) {
+	for (int i = 0; i < m_outputBufferCount; ++i) {
 	    delete[] m_outputBuffers[i];
 	}
 
@@ -340,7 +343,7 @@
 
 	m_outputBuffers = new sample_t*[m_outputBufferCount];
 
-	for (size_t i = 0; i < m_outputBufferCount; ++i) {
+	for (int i = 0; i < m_outputBufferCount; ++i) {
 	    m_outputBuffers[i] = new sample_t[m_blockSize];
 	}
 
@@ -426,20 +429,20 @@
 
     cleanup();
 
-    for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i)
+    for (int i = 0; in_range_for(m_controlPortsIn, i); ++i)
         delete m_controlPortsIn[i].second;
 
-    for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i)
+    for (int i = 0; in_range_for(m_controlPortsOut, i); ++i)
         delete m_controlPortsOut[i].second;
 
     m_controlPortsIn.clear();
     m_controlPortsOut.clear();
 
     if (m_ownBuffers) {
-	for (size_t i = 0; i < m_audioPortsIn.size(); ++i) {
+	for (int i = 0; i < getAudioInputCount(); ++i) {
 	    delete[] m_inputBuffers[i];
 	}
-	for (size_t i = 0; i < m_outputBufferCount; ++i) {
+	for (int i = 0; i < m_outputBufferCount; ++i) {
 	    delete[] m_outputBuffers[i];
 	}
 
@@ -453,7 +456,7 @@
 
 
 void
-DSSIPluginInstance::instantiate(unsigned long sampleRate)
+DSSIPluginInstance::instantiate(sv_samplerate_t sampleRate)
 {
     if (!m_descriptor) return;
 
@@ -470,13 +473,19 @@
 	return;
     }
 
-    m_instanceHandle = descriptor->instantiate(descriptor, sampleRate);
+    unsigned long pluginRate = (unsigned long)(sampleRate);
+    if (sampleRate != sv_samplerate_t(pluginRate)) {
+        cerr << "DSSIPluginInstance: WARNING: Non-integer sample rate "
+             << sampleRate << " presented, rounding to " << pluginRate
+             << endl;
+    }
+    m_instanceHandle = descriptor->instantiate(descriptor, pluginRate);
 
     if (m_instanceHandle) {
 
 	if (m_descriptor->get_midi_controller_for_port) {
 
-	    for (unsigned long i = 0; i < descriptor->PortCount; ++i) {
+	    for (int i = 0; i < (int)descriptor->PortCount; ++i) {
 
 		if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i]) &&
 		    LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
@@ -510,13 +519,13 @@
 	return;
     }
 
-    unsigned long index = 0;
+    int index = 0;
     const DSSI_Program_Descriptor *programDescriptor;
     while ((programDescriptor = m_descriptor->get_program(m_instanceHandle, index))) {
 	++index;
 	ProgramDescriptor d;
-	d.bank = programDescriptor->Bank;
-	d.program = programDescriptor->Program;
+	d.bank = (int)programDescriptor->Bank;
+	d.program = (int)programDescriptor->Program;
 	d.name = programDescriptor->Name;
 	m_cachedPrograms.push_back(d);
     }
@@ -568,7 +577,7 @@
     return std::string();
 }
 
-unsigned long
+int
 DSSIPluginInstance::getProgram(std::string name) const
 {
 #ifdef DEBUG_DSSI
@@ -579,7 +588,7 @@
 
     checkProgramCache();
 
-    unsigned long rv;
+    int rv;
 
     for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
 	 i != m_cachedPrograms.end(); ++i) {
@@ -619,7 +628,7 @@
     if (!m_descriptor->select_program) return;
 
     bool found = false;
-    unsigned long bankNo = 0, programNo = 0;
+    int bankNo = 0, programNo = 0;
 
     for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
 	 i != m_cachedPrograms.end(); ++i) {
@@ -687,7 +696,7 @@
 {
     if (!m_descriptor || !m_descriptor->LADSPA_Plugin->connect_port) return;
 #ifdef DEBUG_DSSI
-    SVDEBUG << "DSSIPluginInstance::connectPorts: " << m_audioPortsIn.size() 
+    SVDEBUG << "DSSIPluginInstance::connectPorts: " << getAudioInputCount() 
 	      << " audio ports in, " << m_audioPortsOut.size() << " out, "
 	      << m_outputBufferCount << " output buffers" << endl;
 #endif
@@ -698,7 +707,7 @@
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     int inbuf = 0, outbuf = 0;
 
-    for (unsigned int i = 0; i < m_audioPortsIn.size(); ++i) {
+    for (int i = 0; i < getAudioInputCount(); ++i) {
 	m_descriptor->LADSPA_Plugin->connect_port
 	    (m_instanceHandle,
 	     m_audioPortsIn[i],
@@ -706,7 +715,7 @@
 	++inbuf;
     }
 
-    for (unsigned int i = 0; i < m_audioPortsOut.size(); ++i) {
+    for (size_t i = 0; i < m_audioPortsOut.size(); ++i) {
 	m_descriptor->LADSPA_Plugin->connect_port
 	    (m_instanceHandle,
 	     m_audioPortsOut[i],
@@ -714,7 +723,7 @@
 	++outbuf;
     }
 
-    for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+    for (size_t i = 0; i < m_controlPortsIn.size(); ++i) {
 	m_descriptor->LADSPA_Plugin->connect_port
 	    (m_instanceHandle,
 	     m_controlPortsIn[i].first,
@@ -722,7 +731,7 @@
 
         if (f) {
             float defaultValue = f->getPortDefault
-                (m_descriptor->LADSPA_Plugin, m_controlPortsIn[i].first);
+                (m_descriptor->LADSPA_Plugin, (int)m_controlPortsIn[i].first);
             *m_controlPortsIn[i].second = defaultValue;
             m_backupControlPortsIn[i] = defaultValue;
 #ifdef DEBUG_DSSI
@@ -731,7 +740,7 @@
         }
     }
 
-    for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i) {
+    for (size_t i = 0; i < m_controlPortsOut.size(); ++i) {
 	m_descriptor->LADSPA_Plugin->connect_port
 	    (m_instanceHandle,
 	     m_controlPortsOut[i].first,
@@ -739,21 +748,21 @@
     }
 }
 
-unsigned int
+int
 DSSIPluginInstance::getParameterCount() const
 {
-    return m_controlPortsIn.size();
+    return (int)m_controlPortsIn.size();
 }
 
 void
-DSSIPluginInstance::setParameterValue(unsigned int parameter, float value)
+DSSIPluginInstance::setParameterValue(int parameter, float value)
 {
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::setParameterValue(" << parameter << ") to " << value << endl;
 #endif
-    if (parameter >= m_controlPortsIn.size()) return;
+    if (!in_range_for(m_controlPortsIn, parameter)) return;
 
-    unsigned int portNumber = m_controlPortsIn[parameter].first;
+    int portNumber = m_controlPortsIn[parameter].first;
 
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     if (f) {
@@ -770,7 +779,7 @@
 }
 
 void
-DSSIPluginInstance::setPortValueFromController(unsigned int port, int cv)
+DSSIPluginInstance::setPortValueFromController(int port, int cv)
 {
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::setPortValueFromController(" << port << ") to " << cv << endl;
@@ -801,7 +810,7 @@
 	}
     }
 
-    for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+    for (int i = 0; in_range_for(m_controlPortsIn, i); ++i) {
 	if (m_controlPortsIn[i].first == port) {
 	    setParameterValue(i, value);
 	}
@@ -809,26 +818,26 @@
 }
 
 float
-DSSIPluginInstance::getControlOutputValue(size_t output) const
+DSSIPluginInstance::getControlOutputValue(int output) const
 {
-    if (output > m_controlPortsOut.size()) return 0.0;
+    if (!in_range_for(m_controlPortsOut, output)) return 0.0;
     return (*m_controlPortsOut[output].second);
 }
 
 float
-DSSIPluginInstance::getParameterValue(unsigned int parameter) const
+DSSIPluginInstance::getParameterValue(int parameter) const
 {
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::getParameterValue(" << parameter << ")" << endl;
 #endif
-    if (parameter >= m_controlPortsIn.size()) return 0.0;
+    if (!in_range_for(m_controlPortsIn, parameter)) return 0.0;
     return (*m_controlPortsIn[parameter].second);
 }
 
 float
-DSSIPluginInstance::getParameterDefault(unsigned int parameter) const
+DSSIPluginInstance::getParameterDefault(int parameter) const
 {
-    if (parameter >= m_controlPortsIn.size()) return 0.0;
+    if (!in_range_for(m_controlPortsIn, parameter)) return 0.0;
 
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     if (f) {
@@ -840,9 +849,9 @@
 }
 
 int
-DSSIPluginInstance::getParameterDisplayHint(unsigned int parameter) const
+DSSIPluginInstance::getParameterDisplayHint(int parameter) const
 {
-    if (parameter >= m_controlPortsIn.size()) return 0.0;
+    if (!in_range_for(m_controlPortsIn, parameter)) return 0.0;
 
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     if (f) {
@@ -904,7 +913,7 @@
 }
 
 void
-DSSIPluginInstance::sendEvent(const Vamp::RealTime &eventTime,
+DSSIPluginInstance::sendEvent(const RealTime &eventTime,
 			      const void *e)
 {
 #ifdef DEBUG_DSSI_PROCESS
@@ -980,7 +989,7 @@
 }
 
 void
-DSSIPluginInstance::run(const Vamp::RealTime &blockTime, size_t count)
+DSSIPluginInstance::run(const RealTime &blockTime, int count)
 {
     static snd_seq_event_t localEventBuffer[EVENT_BUFFER_SIZE];
     int evCount = 0;
@@ -988,7 +997,7 @@
     if (count == 0) count = m_blockSize;
 
     bool needLock = false;
-    if (m_descriptor->select_program) needLock = true;
+    if (m_descriptor && m_descriptor->select_program) needLock = true;
 
     if (needLock) {
 	if (!m_processLock.tryLock()) {
@@ -1007,7 +1016,7 @@
     if (!m_descriptor || !m_descriptor->run_synth) {
 	m_eventBuffer.skip(m_eventBuffer.getReadSpace());
 	m_haveLastEventSendTime = false;
-	if (m_descriptor->LADSPA_Plugin->run) {
+	if (m_descriptor && m_descriptor->LADSPA_Plugin->run) {
 	    m_descriptor->LADSPA_Plugin->run(m_instanceHandle, count);
 	} else {
 	    for (size_t ch = 0; ch < m_audioPortsOut.size(); ++ch) {
@@ -1036,11 +1045,11 @@
 	*ev = m_eventBuffer.peekOne();
 	bool accept = true;
 
-	Vamp::RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
+	RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
 
-	int frameOffset = 0;
+        sv_frame_t frameOffset = 0;
 	if (evTime > blockTime) {
-	    frameOffset = Vamp::RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
+	    frameOffset = RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
 	}
 
 #ifdef DEBUG_DSSI_PROCESS
@@ -1049,7 +1058,7 @@
 	cerr << "Type: " << int(ev->type) << ", pitch: " << int(ev->data.note.note) << ", velocity: " << int(ev->data.note.velocity) << endl;
 #endif
 
-	if (frameOffset >= int(count)) break;
+	if (frameOffset >= (long)count) break;
 	if (frameOffset < 0) {
 	    frameOffset = 0;
 	    if (ev->type == SND_SEQ_EVENT_NOTEON) {
@@ -1058,7 +1067,7 @@
 	    }
 	}
 
-	ev->time.tick = frameOffset;
+	ev->time.tick = (snd_seq_tick_time_t)frameOffset;
 	m_eventBuffer.skip(1);
 
 	if (ev->type == SND_SEQ_EVENT_CONTROLLER) {
@@ -1108,28 +1117,30 @@
  done:
     if (needLock) m_processLock.unlock();
 
-    if (m_audioPortsOut.size() == 0) {
+    int numAudioOuts = int(m_audioPortsOut.size());
+    
+    if (numAudioOuts == 0) {
 	// copy inputs to outputs
-	for (size_t ch = 0; ch < m_idealChannelCount; ++ch) {
-	    size_t sch = ch % m_audioPortsIn.size();
-	    for (size_t i = 0; i < m_blockSize; ++i) {
+	for (int ch = 0; ch < m_idealChannelCount; ++ch) {
+	    int sch = ch % getAudioInputCount();
+	    for (int i = 0; i < m_blockSize; ++i) {
 		m_outputBuffers[ch][i] = m_inputBuffers[sch][i];
 	    }
 	}
-    } else if (m_idealChannelCount < m_audioPortsOut.size()) {
+    } else if (m_idealChannelCount < numAudioOuts) {
 	if (m_idealChannelCount == 1) {
 	    // mix down to mono
-	    for (size_t ch = 1; ch < m_audioPortsOut.size(); ++ch) {
-		for (size_t i = 0; i < m_blockSize; ++i) {
+	    for (int ch = 1; ch < numAudioOuts; ++ch) {
+		for (int i = 0; i < m_blockSize; ++i) {
 		    m_outputBuffers[0][i] += m_outputBuffers[ch][i];
 		}
 	    }
 	}
-    } else if (m_idealChannelCount > m_audioPortsOut.size()) {
+    } else if (m_idealChannelCount > numAudioOuts) {
 	// duplicate
-	for (size_t ch = m_audioPortsOut.size(); ch < m_idealChannelCount; ++ch) {
-	    size_t sch = (ch - m_audioPortsOut.size()) % m_audioPortsOut.size();
-	    for (size_t i = 0; i < m_blockSize; ++i) {
+	for (int ch = numAudioOuts; ch < m_idealChannelCount; ++ch) {
+	    int sch = (ch - numAudioOuts) % numAudioOuts;
+	    for (int i = 0; i < m_blockSize; ++i) {
 		m_outputBuffers[ch][i] = m_outputBuffers[sch][i];
 	    }
 	}
@@ -1140,7 +1151,7 @@
 }
 
 void
-DSSIPluginInstance::runGrouped(const Vamp::RealTime &blockTime)
+DSSIPluginInstance::runGrouped(const RealTime &blockTime)
 {
     // If something else in our group has just been called for this
     // block time (but we haven't) then we should just write out the
@@ -1212,11 +1223,11 @@
 	    *ev = instance->m_eventBuffer.peekOne();
 	    bool accept = true;
 
-	    Vamp::RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
+	    RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
 
-	    int frameOffset = 0;
+	    sv_frame_t frameOffset = 0;
 	    if (evTime > blockTime) {
-		frameOffset = Vamp::RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
+		frameOffset = RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
 	    }
 
 #ifdef DEBUG_DSSI_PROCESS
@@ -1227,7 +1238,7 @@
 	    if (frameOffset >= int(m_blockSize)) break;
 	    if (frameOffset < 0) frameOffset = 0;
 
-	    ev->time.tick = frameOffset;
+	    ev->time.tick = snd_seq_tick_time_t(frameOffset);
 	    instance->m_eventBuffer.skip(1);
 
 	    if (ev->type == SND_SEQ_EVENT_CONTROLLER) {
--- a/plugin/DSSIPluginInstance.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/DSSIPluginInstance.h	Wed Apr 20 12:06:28 2016 +0100
@@ -54,46 +54,46 @@
     virtual int getPluginVersion() const;
     virtual std::string getCopyright() const;
 
-    virtual void run(const Vamp::RealTime &, size_t count = 0);
+    virtual void run(const RealTime &, int count = 0);
 
-    virtual unsigned int getParameterCount() const;
-    virtual void setParameterValue(unsigned int parameter, float value);
-    virtual float getParameterValue(unsigned int parameter) const;
-    virtual float getParameterDefault(unsigned int parameter) const;
-    virtual int getParameterDisplayHint(unsigned int parameter) const;
+    virtual int getParameterCount() const;
+    virtual void setParameterValue(int parameter, float value);
+    virtual float getParameterValue(int parameter) const;
+    virtual float getParameterDefault(int parameter) const;
+    virtual int getParameterDisplayHint(int parameter) const;
 
     virtual ParameterList getParameterDescriptors() const;
     virtual float getParameter(std::string) const;
     virtual void setParameter(std::string, float);
 
     virtual std::string configure(std::string key, std::string value);
-    virtual void sendEvent(const Vamp::RealTime &eventTime,
+    virtual void sendEvent(const RealTime &eventTime,
 			   const void *event);
     virtual void clearEvents();
 
-    virtual size_t getBufferSize() const { return m_blockSize; }
-    virtual size_t getAudioInputCount() const { return m_audioPortsIn.size(); }
-    virtual size_t getAudioOutputCount() const { return m_idealChannelCount; }
+    virtual int getBufferSize() const { return m_blockSize; }
+    virtual int getAudioInputCount() const { return (int)m_audioPortsIn.size(); }
+    virtual int getAudioOutputCount() const { return m_idealChannelCount; }
     virtual sample_t **getAudioInputBuffers() { return m_inputBuffers; }
     virtual sample_t **getAudioOutputBuffers() { return m_outputBuffers; }
 
-    virtual size_t getControlOutputCount() const { return m_controlPortsOut.size(); }
-    virtual float getControlOutputValue(size_t n) const;
+    virtual int getControlOutputCount() const { return (int)m_controlPortsOut.size(); }
+    virtual float getControlOutputValue(int n) const;
 
     virtual ProgramList getPrograms() const;
     virtual std::string getCurrentProgram() const;
     virtual std::string getProgram(int bank, int program) const;
-    virtual unsigned long getProgram(std::string name) const;
+    virtual int getProgram(std::string name) const;
     virtual void selectProgram(std::string program);
 
     virtual bool isBypassed() const { return m_bypassed; }
     virtual void setBypassed(bool bypassed) { m_bypassed = bypassed; }
 
-    virtual size_t getLatency();
+    virtual sv_frame_t getLatency();
 
     virtual void silence();
     virtual void discardEvents();
-    virtual void setIdealChannelCount(size_t channels); // may re-instantiate
+    virtual void setIdealChannelCount(int channels); // may re-instantiate
 
     virtual bool isInGroup() const { return m_grouped; }
     virtual void detachFromGroup();
@@ -110,25 +110,25 @@
 		       int client,
 		       QString identifier,
 		       int position,
-		       unsigned long sampleRate,
-		       size_t blockSize,
+		       sv_samplerate_t sampleRate,
+		       int blockSize,
 		       int idealChannelCount,
 		       const DSSI_Descriptor* descriptor);
     
     void init();
-    void instantiate(unsigned long sampleRate);
+    void instantiate(sv_samplerate_t sampleRate);
     void cleanup();
     void activate();
     void deactivate();
     void connectPorts();
 
     bool handleController(snd_seq_event_t *ev);
-    void setPortValueFromController(unsigned int portNumber, int controlValue);
+    void setPortValueFromController(int portNumber, int controlValue);
     void selectProgramAux(std::string program, bool backupPortValues);
     void checkProgramCache() const;
 
     void initialiseGroupMembership();
-    void runGrouped(const Vamp::RealTime &);
+    void runGrouped(const RealTime &);
 
     // For use in DSSIPluginFactory (set in the DSSI_Host_Descriptor):
     static int requestMidiSend(LADSPA_Handle instance,
@@ -145,8 +145,8 @@
     LADSPA_Handle              m_instanceHandle;
     const DSSI_Descriptor     *m_descriptor;
 
-    std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsIn;
-    std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsOut;
+    std::vector<std::pair<int, LADSPA_Data*> > m_controlPortsIn;
+    std::vector<std::pair<int, LADSPA_Data*> > m_controlPortsOut;
 
     std::vector<LADSPA_Data>  m_backupControlPortsIn;
 
@@ -172,22 +172,22 @@
 
     RingBuffer<snd_seq_event_t> m_eventBuffer;
 
-    size_t                    m_blockSize;
+    int                       m_blockSize;
     sample_t                **m_inputBuffers;
     sample_t                **m_outputBuffers;
     bool                      m_ownBuffers;
-    size_t                    m_idealChannelCount;
-    size_t                    m_outputBufferCount;
-    size_t                    m_sampleRate;
+    int                       m_idealChannelCount;
+    int                       m_outputBufferCount;
+    sv_samplerate_t           m_sampleRate;
     float                    *m_latencyPort;
     bool                      m_run;
     
     bool                      m_bypassed;
     std::string               m_program;
     bool                      m_grouped;
-    Vamp::RealTime            m_lastRunTime;
+    RealTime                  m_lastRunTime;
 
-    Vamp::RealTime            m_lastEventSendTime;
+    RealTime                  m_lastEventSendTime;
     bool                      m_haveLastEventSendTime;
 
     QMutex                    m_processLock;
--- a/plugin/FeatureExtractionPluginFactory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/FeatureExtractionPluginFactory.cpp	Wed Apr 20 12:06:28 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 {
@@ -48,6 +52,8 @@
     Vamp::Plugin *p = m_plugin;
     delete m_plugin;
     m_plugin = 0;
+    // acceptable use after free here, as pluginDeleted uses p only as
+    // pointer key and does not deref it
     if (m_factory) m_factory->pluginDeleted(p);
 }
 
@@ -75,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]);
@@ -106,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();
@@ -216,7 +203,7 @@
     QString file = "";
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-    SVDEBUG << "FeatureExtractionPluginFactory::findPluginFile(\""
+    cerr << "FeatureExtractionPluginFactory::findPluginFile(\""
               << soname << "\", \"" << inDir << "\")"
               << endl;
 #endif
@@ -233,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
 
@@ -245,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
 
@@ -254,7 +241,7 @@
         }
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        SVDEBUG << "FeatureExtractionPluginFactory::findPluginFile (with dir): "
+        cerr << "FeatureExtractionPluginFactory::findPluginFile (with dir): "
                   << "not found" << endl;
 #endif
 
@@ -266,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;
@@ -277,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);
@@ -287,7 +274,7 @@
         }
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        SVDEBUG << "FeatureExtractionPluginFactory::findPluginFile: "
+        cerr << "FeatureExtractionPluginFactory::findPluginFile: "
                   << "not found" << endl;
 #endif
 
@@ -297,7 +284,7 @@
 
 Vamp::Plugin *
 FeatureExtractionPluginFactory::instantiatePlugin(QString identifier,
-						  float inputSampleRate)
+						  sv_samplerate_t inputSampleRate)
 {
     Profiler profiler("FeatureExtractionPluginFactory::instantiatePlugin");
 
@@ -310,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;
     }
 
@@ -322,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
 
@@ -341,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;
     }
 
@@ -355,7 +344,7 @@
         goto done;
     }
 
-    plugin = new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
+    plugin = new Vamp::PluginHostAdapter(descriptor, float(inputSampleRate));
 
     if (plugin) {
         m_handleMap[plugin] = libraryHandle;
@@ -373,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;
 }
@@ -383,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);
@@ -398,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 Jul 14 15:04:46 2015 +0100
+++ b/plugin/FeatureExtractionPluginFactory.h	Wed Apr 20 12:06:28 2016 +0100
@@ -23,6 +23,7 @@
 #include <vamp-hostsdk/Plugin.h>
 
 #include "base/Debug.h"
+#include "base/BaseTypes.h"
 
 class FeatureExtractionPluginFactory
 {
@@ -36,13 +37,13 @@
     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
     // negotiated and handled via initialize() on the plugin
     virtual Vamp::Plugin *instantiatePlugin(QString identifier,
-                                            float inputSampleRate);
+                                            sv_samplerate_t inputSampleRate);
 
     /**
      * Get category metadata about a plugin (without instantiating it).
@@ -56,7 +57,7 @@
     friend class PluginDeletionNotifyAdapter;
     void pluginDeleted(Vamp::Plugin *);
     std::map<Vamp::Plugin *, void *> m_handleMap;
-
+    
     void generateTaxonomy();
 };
 
--- a/plugin/LADSPAPluginFactory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/LADSPAPluginFactory.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -104,7 +104,7 @@
 
 	list.push_back(QString("%1").arg(descriptor->PortCount));
 
-	for (unsigned long p = 0; p < descriptor->PortCount; ++p) {
+	for (int p = 0; p < (int)descriptor->PortCount; ++p) {
 
 	    int type = 0;
 	    if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) {
@@ -150,18 +150,18 @@
     LADSPA_PortRangeHintDescriptor d =
 	descriptor->PortRangeHints[port].HintDescriptor;
 
-    float minimum = 0.0;
+    float minimum = 0.f;
 		
     if (LADSPA_IS_HINT_BOUNDED_BELOW(d)) {
 	float lb = descriptor->PortRangeHints[port].LowerBound;
 	minimum = lb;
     } else if (LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
 	float ub = descriptor->PortRangeHints[port].UpperBound;
-	minimum = std::min(0.0, ub - 1.0);
+	minimum = std::min(0.f, ub - 1.f);
     }
     
     if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
-	minimum *= m_sampleRate;
+	minimum = float(minimum * m_sampleRate);
     }
 
     if (LADSPA_IS_HINT_LOGARITHMIC(d)) {
@@ -177,18 +177,18 @@
     LADSPA_PortRangeHintDescriptor d =
 	descriptor->PortRangeHints[port].HintDescriptor;
 
-    float maximum = 1.0;
+    float maximum = 1.f;
     
     if (LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
 	float ub = descriptor->PortRangeHints[port].UpperBound;
 	maximum = ub;
     } else {
 	float lb = descriptor->PortRangeHints[port].LowerBound;
-	maximum = lb + 1.0;
+	maximum = lb + 1.f;
     }
     
     if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
-	maximum *= m_sampleRate;
+	maximum = float(maximum * m_sampleRate);
     }
 
     return maximum;
@@ -240,25 +240,25 @@
     } else if (LADSPA_IS_HINT_DEFAULT_LOW(d)) {
 	
 	if (logarithmic) {
-	    deft = powf(10, logmin * 0.75 + logmax * 0.25);
+	    deft = powf(10, logmin * 0.75f + logmax * 0.25f);
 	} else {
-	    deft = minimum * 0.75 + maximum * 0.25;
+	    deft = minimum * 0.75f + maximum * 0.25f;
 	}
 	
     } else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(d)) {
 	
 	if (logarithmic) {
-	    deft = powf(10, logmin * 0.5 + logmax * 0.5);
+	    deft = powf(10, logmin * 0.5f + logmax * 0.5f);
 	} else {
-	    deft = minimum * 0.5 + maximum * 0.5;
+	    deft = minimum * 0.5f + maximum * 0.5f;
 	}
 	
     } else if (LADSPA_IS_HINT_DEFAULT_HIGH(d)) {
 	
 	if (logarithmic) {
-	    deft = powf(10, logmin * 0.25 + logmax * 0.75);
+	    deft = powf(10, logmin * 0.25f + logmax * 0.75f);
 	} else {
-	    deft = minimum * 0.25 + maximum * 0.75;
+	    deft = minimum * 0.25f + maximum * 0.75f;
 	}
 	
     } else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(d)) {
@@ -280,7 +280,7 @@
     } else if (LADSPA_IS_HINT_DEFAULT_440(d)) {
 	
 //	deft = 440.0;
-        deft = Preferences::getInstance()->getTuningFrequency();
+        deft = (float)Preferences::getInstance()->getTuningFrequency();
 	
     } else {
 	
@@ -303,8 +303,8 @@
 {
     int displayHint = getPortDisplayHint(descriptor, port);
     if (displayHint & PortHint::Toggled) {
-        return lrintf(getPortMaximum(descriptor, port)) - 
-            lrintf(getPortMinimum(descriptor, port));
+        return float(lrintf(getPortMaximum(descriptor, port)) - 
+                     lrintf(getPortMinimum(descriptor, port)));
     }
     if (displayHint & PortHint::Integer) {
         return 1.0;
@@ -331,9 +331,9 @@
 LADSPAPluginFactory::instantiatePlugin(QString identifier,
 				       int instrument,
 				       int position,
-				       unsigned int sampleRate,
-				       unsigned int blockSize,
-				       unsigned int channels)
+				       sv_samplerate_t sampleRate,
+				       int blockSize,
+				       int channels)
 {
     Profiler profiler("LADSPAPluginFactory::instantiatePlugin");
 
@@ -621,6 +621,8 @@
     }
 
     baseUri = LADSPA_BASE;
+#else
+    baseUri = "";
 #endif
 
     return lrdfPaths;
@@ -666,19 +668,16 @@
 
     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) {
-	    discoverPlugins(QString("%1/%2").arg(*i).arg(pluginDir[j]));
-	}
+    for (QString c: candidates) {
+        discoverPluginsFrom(c);
     }
 }
 
 void
-LADSPAPluginFactory::discoverPlugins(QString soname)
+LADSPAPluginFactory::discoverPluginsFrom(QString soname)
 {
     void *libraryHandle = DLOPEN(soname, RTLD_LAZY);
 
@@ -728,8 +727,8 @@
 
 	QString category = m_taxonomy[identifier];
 	
-	if (category == "" && descriptor->Name != 0) {
-	    std::string name = descriptor->Name;
+	if (category == "") {
+	    std::string name = rtd->name;
 	    if (name.length() > 4 &&
 		name.substr(name.length() - 4) == " VST") {
 		category = "VST effects";
@@ -752,7 +751,7 @@
 
 	unsigned int controlPortNumber = 1;
 	
-	for (unsigned long i = 0; i < descriptor->PortCount; i++) {
+	for (int i = 0; i < (int)descriptor->PortCount; i++) {
 	    
 	    if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i])) {
 		
@@ -772,7 +771,7 @@
 	}
 #endif // HAVE_LRDF
 
-	for (unsigned long i = 0; i < descriptor->PortCount; i++) {
+	for (int i = 0; i < (int)descriptor->PortCount; i++) {
 	    if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i])) {
                 if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
                     ++rtd->parameterCount;
@@ -877,6 +876,10 @@
 	}
 	lrdf_free_uris(uris);
     }
+#else
+    // avoid unused parameter
+    (void)uri;
+    (void)base;
 #endif
 }
     
--- a/plugin/LADSPAPluginFactory.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/LADSPAPluginFactory.h	Wed Apr 20 12:06:28 2016 +0100
@@ -24,6 +24,8 @@
 #include "RealTimePluginFactory.h"
 #include "api/ladspa.h"
 
+#include "PluginScan.h"
+
 #include <vector>
 #include <map>
 #include <set>
@@ -47,9 +49,9 @@
     virtual RealTimePluginInstance *instantiatePlugin(QString identifier,
 						      int clientId,
 						      int position,
-						      unsigned int sampleRate,
-						      unsigned int blockSize,
-						      unsigned int channels);
+						      sv_samplerate_t sampleRate,
+						      int blockSize,
+						      int channels);
 
     virtual QString getPluginCategory(QString identifier);
 
@@ -63,11 +65,15 @@
     LADSPAPluginFactory();
     friend class RealTimePluginFactory;
 
+    virtual PluginScan::PluginType getPluginType() const {
+        return PluginScan::LADSPAPlugin;
+    }
+
     virtual std::vector<QString> getPluginPath();
 
     virtual std::vector<QString> getLRDFPath(QString &baseUri);
 
-    virtual void discoverPlugins(QString soName);
+    virtual void discoverPluginsFrom(QString soName);
     virtual void generateTaxonomy(QString uri, QString base);
     virtual void generateFallbackCategories();
 
--- a/plugin/LADSPAPluginInstance.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/LADSPAPluginInstance.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -37,8 +37,8 @@
 					   int clientId,
 					   QString identifier,
                                            int position,
-					   unsigned long sampleRate,
-					   size_t blockSize,
+					   sv_samplerate_t sampleRate,
+					   int blockSize,
 					   int idealChannelCount,
                                            const LADSPA_Descriptor* descriptor) :
     RealTimePluginInstance(factory, identifier),
@@ -123,6 +123,7 @@
 {
     ParameterList list;
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
+    if (!f) return list;
     
     for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
         
@@ -155,12 +156,12 @@
             if (defaults) {
                 if (defaults->count > 0) {
                     std::map<int, std::string> values;
-                    size_t v = 0;
+                    int v = 0;
                     for (size_t i = 0; i < defaults->count; ++i) {
-                        v = size_t(lrintf(fabsf(defaults->items[i].value)));
+                        v = int(lrintf(fabsf(defaults->items[i].value)));
                         values[v] = defaults->items[i].label;
                     }
-                    for (size_t i = 0; i <= v; ++i) {
+                    for (int i = 0; i <= v; ++i) {
                         pd.valueNames.push_back(values[i]);
                     }
                     haveLabels = true;
@@ -226,7 +227,7 @@
 
     // Discover ports numbers and identities
     //
-    for (unsigned long i = 0; i < m_descriptor->PortCount; ++i) {
+    for (int i = 0; i < (int)m_descriptor->PortCount; ++i) {
 
         if (LADSPA_IS_PORT_AUDIO(m_descriptor->PortDescriptors[i])) {
 
@@ -288,19 +289,19 @@
     }
 }
 
-size_t
+sv_frame_t
 LADSPAPluginInstance::getLatency()
 {
     if (m_latencyPort) {
 	if (!m_run) {
-            for (size_t i = 0; i < getAudioInputCount(); ++i) {
-                for (size_t j = 0; j < m_blockSize; ++j) {
+            for (int i = 0; i < getAudioInputCount(); ++i) {
+                for (int j = 0; j < m_blockSize; ++j) {
                     m_inputBuffers[i][j] = 0.f;
                 }
             }
             run(Vamp::RealTime::zeroTime);
         }
-	if (*m_latencyPort > 0) return (size_t)*m_latencyPort;
+	if (*m_latencyPort > 0) return (sv_frame_t)*m_latencyPort;
     }
     return 0;
 }
@@ -315,7 +316,7 @@
 }
 
 void
-LADSPAPluginInstance::setIdealChannelCount(size_t channels)
+LADSPAPluginInstance::setIdealChannelCount(int channels)
 {
     if (m_audioPortsIn.size() != 1 || channels == m_instanceCount) {
 	silence();
@@ -377,7 +378,7 @@
 
 
 void
-LADSPAPluginInstance::instantiate(unsigned long sampleRate)
+LADSPAPluginInstance::instantiate(sv_samplerate_t sampleRate)
 {
     if (!m_descriptor) return;
 
@@ -393,9 +394,16 @@
 	return;
     }
 
-    for (size_t i = 0; i < m_instanceCount; ++i) {
+    unsigned long pluginRate = (unsigned long)(sampleRate);
+    if (sampleRate != sv_samplerate_t(pluginRate)) {
+        cerr << "LADSPAPluginInstance: WARNING: Non-integer sample rate "
+             << sampleRate << " presented, rounding to " << pluginRate
+             << endl;
+    }
+    
+    for (int i = 0; i < m_instanceCount; ++i) {
 	m_instanceHandles.push_back
-	    (m_descriptor->instantiate(m_descriptor, sampleRate));
+	    (m_descriptor->instantiate(m_descriptor, pluginRate));
     }
 }
 
@@ -463,16 +471,16 @@
     }
 }
 
-unsigned int
+int
 LADSPAPluginInstance::getParameterCount() const
 {
-    return m_controlPortsIn.size();
+    return (int)m_controlPortsIn.size();
 }
 
 void
-LADSPAPluginInstance::setParameterValue(unsigned int parameter, float value)
+LADSPAPluginInstance::setParameterValue(int parameter, float value)
 {
-    if (parameter >= m_controlPortsIn.size()) return;
+    if (!in_range_for(m_controlPortsIn, parameter)) return;
 
     unsigned int portNumber = m_controlPortsIn[parameter].first;
 
@@ -490,23 +498,23 @@
 }
 
 float
-LADSPAPluginInstance::getControlOutputValue(size_t output) const
+LADSPAPluginInstance::getControlOutputValue(int output) const
 {
-    if (output > m_controlPortsOut.size()) return 0.0;
+    if (!in_range_for(m_controlPortsOut, output)) return 0.0;
     return (*m_controlPortsOut[output].second);
 }
 
 float
-LADSPAPluginInstance::getParameterValue(unsigned int parameter) const
+LADSPAPluginInstance::getParameterValue(int parameter) const
 {
-    if (parameter >= m_controlPortsIn.size()) return 0.0;
+    if (!in_range_for(m_controlPortsIn, parameter)) return 0.0;
     return (*m_controlPortsIn[parameter].second);
 }
 
 float
-LADSPAPluginInstance::getParameterDefault(unsigned int parameter) const
+LADSPAPluginInstance::getParameterDefault(int parameter) const
 {
-    if (parameter >= m_controlPortsIn.size()) return 0.0;
+    if (!in_range_for(m_controlPortsIn, parameter)) return 0.0;
 
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     if (f) {
@@ -517,9 +525,9 @@
 }
 
 int
-LADSPAPluginInstance::getParameterDisplayHint(unsigned int parameter) const
+LADSPAPluginInstance::getParameterDisplayHint(int parameter) const
 {
-    if (parameter >= m_controlPortsIn.size()) return 0.0;
+    if (!in_range_for(m_controlPortsIn, parameter)) return 0.0;
 
     LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
     if (f) {
@@ -530,7 +538,7 @@
 }
 
 void
-LADSPAPluginInstance::run(const Vamp::RealTime &, size_t count)
+LADSPAPluginInstance::run(const RealTime &, int count)
 {
     if (!m_descriptor || !m_descriptor->run) return;
 
--- a/plugin/LADSPAPluginInstance.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/LADSPAPluginInstance.h	Wed Apr 20 12:06:28 2016 +0100
@@ -27,6 +27,7 @@
 
 #include "api/ladspa.h"
 #include "RealTimePluginInstance.h"
+#include "base/BaseTypes.h"
 
 // LADSPA plugin instance.  LADSPA is a variable block size API, but
 // for one reason and another it's more convenient to use a fixed
@@ -50,34 +51,34 @@
     virtual int getPluginVersion() const;
     virtual std::string getCopyright() const;
 
-    virtual void run(const Vamp::RealTime &rt, size_t count = 0);
+    virtual void run(const RealTime &rt, int count = 0);
 
-    virtual unsigned int getParameterCount() const;
-    virtual void setParameterValue(unsigned int parameter, float value);
-    virtual float getParameterValue(unsigned int parameter) const;
-    virtual float getParameterDefault(unsigned int parameter) const;
-    virtual int getParameterDisplayHint(unsigned int parameter) const;
+    virtual int getParameterCount() const;
+    virtual void setParameterValue(int parameter, float value);
+    virtual float getParameterValue(int parameter) const;
+    virtual float getParameterDefault(int parameter) const;
+    virtual int getParameterDisplayHint(int parameter) const;
     
     virtual ParameterList getParameterDescriptors() const;
     virtual float getParameter(std::string) const;
     virtual void setParameter(std::string, float);
 
-    virtual size_t getBufferSize() const { return m_blockSize; }
-    virtual size_t getAudioInputCount() const { return m_instanceCount * m_audioPortsIn.size(); }
-    virtual size_t getAudioOutputCount() const { return m_instanceCount * m_audioPortsOut.size(); }
+    virtual int getBufferSize() const { return m_blockSize; }
+    virtual int getAudioInputCount() const { return int(m_instanceCount * m_audioPortsIn.size()); }
+    virtual int getAudioOutputCount() const { return int(m_instanceCount * m_audioPortsOut.size()); }
     virtual sample_t **getAudioInputBuffers() { return m_inputBuffers; }
     virtual sample_t **getAudioOutputBuffers() { return m_outputBuffers; }
 
-    virtual size_t getControlOutputCount() const { return m_controlPortsOut.size(); }
-    virtual float getControlOutputValue(size_t n) const;
+    virtual int getControlOutputCount() const { return int(m_controlPortsOut.size()); }
+    virtual float getControlOutputValue(int n) const;
 
     virtual bool isBypassed() const { return m_bypassed; }
     virtual void setBypassed(bool bypassed) { m_bypassed = bypassed; }
 
-    virtual size_t getLatency();
+    virtual sv_frame_t getLatency();
 
     virtual void silence();
-    virtual void setIdealChannelCount(size_t channels); // may re-instantiate
+    virtual void setIdealChannelCount(int channels); // may re-instantiate
 
     virtual std::string getType() const { return "LADSPA Real-Time Plugin"; }
 
@@ -91,13 +92,13 @@
 			 int client,
 			 QString identifier,
                          int position,
-			 unsigned long sampleRate,
-			 size_t blockSize,
+			 sv_samplerate_t sampleRate,
+			 int blockSize,
 			 int idealChannelCount,
                          const LADSPA_Descriptor* descriptor);
 
     void init(int idealChannelCount = 0);
-    void instantiate(unsigned long sampleRate);
+    void instantiate(sv_samplerate_t sampleRate);
     void cleanup();
     void activate();
     void deactivate();
@@ -109,20 +110,20 @@
     int                        m_client;
     int                        m_position;
     std::vector<LADSPA_Handle> m_instanceHandles;
-    size_t                     m_instanceCount;
+    int                        m_instanceCount;
     const LADSPA_Descriptor   *m_descriptor;
 
-    std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsIn;
-    std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsOut;
+    std::vector<std::pair<int, LADSPA_Data*> > m_controlPortsIn;
+    std::vector<std::pair<int, LADSPA_Data*> > m_controlPortsOut;
 
     std::vector<int>          m_audioPortsIn;
     std::vector<int>          m_audioPortsOut;
 
-    size_t                    m_blockSize;
+    int                       m_blockSize;
     sample_t                **m_inputBuffers;
     sample_t                **m_outputBuffers;
     bool                      m_ownBuffers;
-    size_t                    m_sampleRate;
+    sv_samplerate_t           m_sampleRate;
     float                    *m_latencyPort;
     bool                      m_run;
     
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/PluginScan.cpp	Wed Apr 20 12:06:28 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	Wed Apr 20 12:06:28 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/PluginXml.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/PluginXml.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -214,7 +214,7 @@
     QDomNamedNodeMap attrNodes = pluginElt.attributes();
     QXmlAttributes attrs;
 
-    for (unsigned int i = 0; i < attrNodes.length(); ++i) {
+    for (int i = 0; i < attrNodes.length(); ++i) {
         QDomAttr attr = attrNodes.item(i).toAttr();
         if (attr.isNull()) continue;
 //        SVDEBUG << "PluginXml::setParametersFromXml: Adding attribute \"" << attr.name()//                  << "\" with value \"" << attr.value() << "\"" << endl;
--- a/plugin/RealTimePluginFactory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/RealTimePluginFactory.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -30,7 +30,7 @@
 
 #include <iostream>
 
-int RealTimePluginFactory::m_sampleRate = 48000;
+sv_samplerate_t RealTimePluginFactory::m_sampleRate = 48000;
 
 static LADSPAPluginFactory *_ladspaInstance = 0;
 static LADSPAPluginFactory *_dssiInstance = 0;
--- a/plugin/RealTimePluginFactory.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/RealTimePluginFactory.h	Wed Apr 20 12:06:28 2016 +0100
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include "base/Debug.h"
+#include "base/BaseTypes.h"
 
 class RealTimePluginInstance;
 
@@ -55,7 +56,7 @@
     static std::vector<QString> getAllPluginIdentifiers();
     static void enumerateAllPlugins(std::vector<QString> &);
 
-    static void setSampleRate(int sampleRate) { m_sampleRate = sampleRate; }
+    static void setSampleRate(sv_samplerate_t sampleRate) { m_sampleRate = sampleRate; }
 
     /**
      * Look up the plugin path and find the plugins in it.  Called 
@@ -87,9 +88,9 @@
     virtual RealTimePluginInstance *instantiatePlugin(QString identifier,
 						      int clientId,
 						      int position,
-						      unsigned int sampleRate,
-						      unsigned int blockSize,
-						      unsigned int channels) = 0;
+						      sv_samplerate_t sampleRate,
+						      int blockSize,
+						      int channels) = 0;
 
     /**
      * Get category metadata about a plugin (without instantiating it).
@@ -103,7 +104,7 @@
     virtual void releasePlugin(RealTimePluginInstance *, QString identifier) = 0;
     friend class RealTimePluginInstance;
 
-    static int m_sampleRate;
+    static sv_samplerate_t m_sampleRate;
 };
 
 #endif
--- a/plugin/RealTimePluginInstance.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/RealTimePluginInstance.h	Wed Apr 20 12:06:28 2016 +0100
@@ -22,7 +22,9 @@
 #define _REALTIME_PLUGIN_INSTANCE_H_
 
 #include <vamp-hostsdk/PluginBase.h>
-#include <vamp-hostsdk/RealTime.h>
+
+#include "base/RealTime.h"
+#include "base/AudioPlaySource.h"
 
 #include <QString>
 #include <QStringList>
@@ -30,8 +32,6 @@
 #include <string>
 #include <map>
 
-#include "base/AudioPlaySource.h"
-
 class RealTimePluginFactory;
 	
 /**
@@ -91,36 +91,36 @@
      * waiting.  Other plugins can ignore it.  The count, if zero,
      * defaults to our fixed buffer size.
      */
-    virtual void run(const Vamp::RealTime &blockStartTime,
-                     size_t count = 0) = 0;
+    virtual void run(const RealTime &blockStartTime,
+                     int count = 0) = 0;
     
-    virtual size_t getBufferSize() const = 0;
+    virtual int getBufferSize() const = 0;
 
-    virtual size_t getAudioInputCount() const = 0;
-    virtual size_t getAudioOutputCount() const = 0;
+    virtual int getAudioInputCount() const = 0;
+    virtual int getAudioOutputCount() const = 0;
 
     virtual sample_t **getAudioInputBuffers() = 0;
     virtual sample_t **getAudioOutputBuffers() = 0;
 
     // Control inputs are known as parameters here
-    virtual size_t getControlOutputCount() const = 0;
-    virtual float getControlOutputValue(size_t n) const = 0;
+    virtual int getControlOutputCount() const = 0;
+    virtual float getControlOutputValue(int n) const = 0;
 
 //     virtual QStringList getPrograms() const { return QStringList(); }
 //     virtual QString getCurrentProgram() const { return QString(); }
     virtual std::string getProgram(int /* bank */, int /* program */) const { return std::string(); }
-//     virtual unsigned long getProgram(QString /* name */) const { return 0; } // bank << 16 + program
+//     virtual int getProgram(QString /* name */) const { return 0; } // bank << 16 + program
 //     virtual void selectProgram(QString) { }
 
-    virtual unsigned int getParameterCount() const = 0;
-    virtual void setParameterValue(unsigned int parameter, float value) = 0;
-    virtual float getParameterValue(unsigned int parameter) const = 0;
-    virtual float getParameterDefault(unsigned int parameter) const = 0;
-    virtual int getParameterDisplayHint(unsigned int parameter) const = 0;
+    virtual int getParameterCount() const = 0;
+    virtual void setParameterValue(int parameter, float value) = 0;
+    virtual float getParameterValue(int parameter) const = 0;
+    virtual float getParameterDefault(int parameter) const = 0;
+    virtual int getParameterDisplayHint(int parameter) const = 0;
 
     virtual std::string configure(std::string /* key */, std::string /* value */) { return std::string(); }
 
-    virtual void sendEvent(const Vamp::RealTime & /* eventTime */,
+    virtual void sendEvent(const RealTime & /* eventTime */,
 			   const void * /* event */) { }
     virtual void clearEvents() { }
 
@@ -128,11 +128,11 @@
     virtual void setBypassed(bool value) = 0;
 
     // This should be called after setup, but while not actually playing.
-    virtual size_t getLatency() = 0;
+    virtual sv_frame_t getLatency() = 0;
 
     virtual void silence() = 0;
     virtual void discardEvents() { }
-    virtual void setIdealChannelCount(size_t channels) = 0; // must also silence(); may also re-instantiate
+    virtual void setIdealChannelCount(int channels) = 0; // must also silence(); may also re-instantiate
 
     void setFactory(RealTimePluginFactory *f) { m_factory = f; } // ew
 
--- a/plugin/plugins/SamplePlayer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/plugin/plugins/SamplePlayer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -69,7 +69,7 @@
     { LADSPA_HINT_DEFAULT_MINIMUM | LADSPA_HINT_INTEGER |
       LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 1 },
     { LADSPA_HINT_DEFAULT_MINIMUM | LADSPA_HINT_LOGARITHMIC |
-      LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0.001, 2.0 }
+      LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0.001f, 2.0f }
 };
 
 const LADSPA_Properties
@@ -156,7 +156,8 @@
 	return 0;
     }
 
-    SamplePlayer *player = new SamplePlayer(rate);
+    SamplePlayer *player = new SamplePlayer(int(rate));
+	// std::cerr << "Instantiated sample player " << std::endl;
 
     if (hostDescriptor->request_non_rt_thread(player, workThreadCallback)) {
 	SVDEBUG << "SamplePlayer::instantiate: Host rejected request_non_rt_thread call, not instantiating" << endl;
@@ -281,7 +282,7 @@
 			    unsigned long program)
 {
     SamplePlayer *player = (SamplePlayer *)handle;
-    player->m_pendingProgramChange = program;
+    player->m_pendingProgramChange = (int)program;
 }
 
 int
@@ -414,7 +415,7 @@
     if (info.samplerate != m_sampleRate) {
 	
 	double ratio = (double)m_sampleRate / (double)info.samplerate;
-	size_t target = (size_t)(info.frames * ratio);
+	size_t target = (size_t)(double(info.frames) * ratio);
 	SRC_DATA data;
 
 	tmpResamples = (float *)malloc(target * info.channels * sizeof(float));
@@ -571,7 +572,7 @@
             ratio *= *m_concertA / 440.f;
         }
 	if (m_basePitch && n != *m_basePitch) {
-	    ratio *= powf(1.059463094f, n - *m_basePitch);
+	    ratio *= powf(1.059463094f, float(n) - *m_basePitch);
 	}
     }
 
@@ -584,8 +585,8 @@
 	 ++i, ++s) {
 
 	float         lgain = gain;
-	float         rs = s * ratio;
-	unsigned long rsi = lrintf(floor(rs));
+	float         rs = float(s) * ratio;
+	unsigned long rsi = lrintf(floorf(rs));
 
 	if (rsi >= m_sampleCount) {
 #ifdef DEBUG_SAMPLE_PLAYER
@@ -603,7 +604,7 @@
 
 	    unsigned long releaseFrames = 200;
 	    if (m_release) {
-		releaseFrames = long(*m_release * m_sampleRate + 0.0001);
+		releaseFrames = long(*m_release * float(m_sampleRate) + 0.0001f);
 	    }
 
 	    if (dist > releaseFrames) {
--- a/rdf/RDFExporter.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/rdf/RDFExporter.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -78,7 +78,7 @@
     Vamp::Plugin::FeatureList features;
     features.push_back(Vamp::Plugin::Feature());
     Vamp::Plugin::Feature &f = features[0];
-    int sr = m_model->getSampleRate();
+    sv_samplerate_t sr = m_model->getSampleRate();
 
     {
         RegionModel *m = dynamic_cast<RegionModel *>(m_model);
@@ -88,8 +88,8 @@
             const RegionModel::PointList &pl(m->getPoints());
             for (RegionModel::PointList::const_iterator i = pl.begin(); 
                  i != pl.end(); ++i) {
-                f.timestamp = Vamp::RealTime::frame2RealTime(i->frame, sr);
-                f.duration = Vamp::RealTime::frame2RealTime(i->duration, sr);
+                f.timestamp = RealTime::frame2RealTime(i->frame, sr).toVampRealTime();
+                f.duration = RealTime::frame2RealTime(i->duration, sr).toVampRealTime();
                 f.values.clear();
                 f.values.push_back(i->value);
                 f.label = i->label.toStdString();
@@ -106,8 +106,8 @@
             const NoteModel::PointList &pl(m->getPoints());
             for (NoteModel::PointList::const_iterator i = pl.begin(); 
                  i != pl.end(); ++i) {
-                f.timestamp = Vamp::RealTime::frame2RealTime(i->frame, sr);
-                f.duration = Vamp::RealTime::frame2RealTime(i->duration, sr);
+                f.timestamp = RealTime::frame2RealTime(i->frame, sr).toVampRealTime();
+                f.duration = RealTime::frame2RealTime(i->duration, sr).toVampRealTime();
                 f.values.clear();
                 f.values.push_back(i->value);
                 f.values.push_back(i->level);
@@ -125,7 +125,7 @@
             const SparseOneDimensionalModel::PointList &pl(m->getPoints());
             for (SparseOneDimensionalModel::PointList::const_iterator i = pl.begin(); 
                  i != pl.end(); ++i) {
-                f.timestamp = Vamp::RealTime::frame2RealTime(i->frame, sr);
+                f.timestamp = RealTime::frame2RealTime(i->frame, sr).toVampRealTime();
                 f.values.clear();
                 f.label = i->label.toStdString();
                 m_fw->write(trackId, transform, output, features, summaryType);
@@ -141,7 +141,7 @@
             const SparseTimeValueModel::PointList &pl(m->getPoints());
             for (SparseTimeValueModel::PointList::const_iterator i = pl.begin(); 
                  i != pl.end(); ++i) {
-                f.timestamp = Vamp::RealTime::frame2RealTime(i->frame, sr);
+                f.timestamp = RealTime::frame2RealTime(i->frame, sr).toVampRealTime();
                 f.values.clear();
                 f.values.push_back(i->value);
                 f.label = i->label.toStdString();
@@ -159,7 +159,7 @@
             m_fw->setFixedEventTypeURI("af:Text");
             for (TextModel::PointList::const_iterator i = pl.begin(); 
                  i != pl.end(); ++i) {
-                f.timestamp = Vamp::RealTime::frame2RealTime(i->frame, sr);
+                f.timestamp = RealTime::frame2RealTime(i->frame, sr).toVampRealTime();
                 f.values.clear();
                 f.values.push_back(i->height);
                 f.label = i->label.toStdString();
--- a/rdf/RDFFeatureWriter.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/rdf/RDFFeatureWriter.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -15,9 +15,6 @@
 
 #include <fstream>
 
-#include "vamp-hostsdk/PluginHostAdapter.h"
-#include "vamp-hostsdk/PluginLoader.h"
-
 #include "base/Exceptions.h"
 
 #include "RDFFeatureWriter.h"
@@ -25,6 +22,7 @@
 #include "PluginRDFIndexer.h"
 
 #include <QTextStream>
+#include <QTextCodec>
 #include <QUrl>
 #include <QFileInfo>
 #include <QRegExp>
@@ -36,7 +34,8 @@
 RDFFeatureWriter::RDFFeatureWriter() :
     FileFeatureWriter(SupportOneFilePerTrackTransform |
                       SupportOneFilePerTrack |
-                      SupportOneFileTotal,
+                      SupportOneFileTotal |
+                      SupportStdOut,
                       "n3"),
     m_plain(false),
     m_network(false),
@@ -49,6 +48,12 @@
 {
 }
 
+string
+RDFFeatureWriter::getDescription() const
+{
+    return "Write output in Audio Features Ontology RDF/Turtle format.";
+}
+
 RDFFeatureWriter::ParameterList
 RDFFeatureWriter::getSupportedParameters() const
 {
@@ -156,7 +161,8 @@
     // Need to select appropriate output file for our track/transform
     // combination
 
-    QTextStream *stream = getOutputStream(trackId, transform.getIdentifier());
+    QTextStream *stream = getOutputStream(trackId, transform.getIdentifier(),
+                                          QTextCodec::codecForName("UTF-8"));
     if (!stream) {
         throw FailedToOpenOutputStream(trackId, transform.getIdentifier());
     }
@@ -346,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;
@@ -361,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 ";
@@ -534,7 +540,7 @@
 
     // iterate through FeatureLists
         
-    for (int i = 0; i < featureList.size(); ++i) {
+    for (int i = 0; i < (int)featureList.size(); ++i) {
 
         const Plugin::Feature &feature = featureList[i];
         unsigned long featureNumber = m_count++;
@@ -597,7 +603,7 @@
             stream << ";\n";
             //!!! named bins?
             stream << "    af:feature \"" << feature.values[0];
-            for (int j = 1; j < feature.values.size(); ++j) {
+            for (int j = 1; j < (int)feature.values.size(); ++j) {
                 stream << " " << feature.values[j];
             }
             stream << "\" ";
@@ -609,7 +615,7 @@
 
 void
 RDFFeatureWriter::writeTrackLevelRDF(QTextStream *sptr,
-                                     const Transform &transform,
+                                     const Transform &,
                                      const Plugin::OutputDescriptor& od,
                                      const Plugin::FeatureList& featureList,
                                      PluginRDFDescription &desc,
@@ -618,7 +624,7 @@
     if (featureList.empty()) return;
     QTextStream &stream = *sptr;
         
-    bool plain = (m_plain || !desc.haveDescription());
+//    bool plain = (m_plain || !desc.haveDescription());
 
     QString outputId = od.identifier.c_str();
     QString featureUri = desc.getOutputFeatureAttributeURI(outputId);
@@ -628,7 +634,7 @@
         return;
     }
 
-    for (int i = 0; i < featureList.size(); ++i) {
+    for (int i = 0; i < (int)featureList.size(); ++i) {
 
         const Plugin::Feature &feature = featureList[i];
 
@@ -679,29 +685,45 @@
 
         stream << "\n:feature_timeline_" << featureNumber << " a tl:DiscreteTimeLine .\n\n";
 
-        size_t stepSize = transform.getStepSize();
-        if (stepSize == 0) {
-            cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the step size properly!" << endl;
-            return;
-        }
+        sv_samplerate_t sampleRate;
+        int stepSize, blockSize;
 
-        size_t blockSize = transform.getBlockSize();
-        if (blockSize == 0) {
-            cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the block size properly!" << endl;
-            return;
-        }
+        // If the output is FixedSampleRate, we need to draw the
+        // sample rate and step size from the output descriptor;
+        // otherwise they come from the transform
 
-        float sampleRate = transform.getSampleRate();
-        if (sampleRate == 0.f) {
-            cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the sample rate properly!" << endl;
-            return;
+        if (od.sampleType == Plugin::OutputDescriptor::FixedSampleRate) {
+
+            sampleRate = od.sampleRate;
+            stepSize = 1;
+            blockSize = 1;
+
+        } else {
+
+            sampleRate = transform.getSampleRate();
+            if (sampleRate == 0.f) {
+                cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the sample rate properly!" << endl;
+                return;
+            }
+
+            stepSize = transform.getStepSize();
+            if (stepSize == 0) {
+                cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the step size properly!" << endl;
+                return;
+            }
+
+            blockSize = transform.getBlockSize();
+            if (blockSize == 0) {
+                cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the block size properly!" << endl;
+                return;
+            }
         }
 
         stream << ":feature_timeline_map_" << featureNumber
                << " a tl:UniformSamplingWindowingMap ;\n"
                << "    tl:rangeTimeLine :feature_timeline_" << featureNumber << " ;\n"
                << "    tl:domainTimeLine " << timelineURI << " ;\n"
-               << "    tl:sampleRate \"" << int(sampleRate) << "\"^^xsd:int ;\n"
+               << "    tl:sampleRate \"" << sampleRate << "\"^^xsd:float ;\n"
                << "    tl:windowLength \"" << blockSize << "\"^^xsd:int ;\n"
                << "    tl:hopSize \"" << stepSize << "\"^^xsd:int .\n\n";
 
@@ -729,8 +751,10 @@
         RealTime startrt = transform.getStartTime();
         RealTime durationrt = transform.getDuration();
 
-        int start = RealTime::realTime2Frame(startrt, sampleRate) / stepSize;
-        int duration = RealTime::realTime2Frame(durationrt, sampleRate) / stepSize;
+        sv_frame_t start = RealTime::realTime2Frame
+            (startrt, sampleRate) / stepSize;
+        sv_frame_t duration = RealTime::realTime2Frame
+            (durationrt, sampleRate) / stepSize;
 
         if (start != 0) {
             stream << "\n        tl:start \"" << start << "\"^^xsd:int ;";
@@ -756,11 +780,11 @@
     QString &str = m_openDenseFeatures[sp].second;
     QTextStream stream(&str);
 
-    for (int i = 0; i < featureList.size(); ++i) {
+    for (int i = 0; i < (int)featureList.size(); ++i) {
 
         const Plugin::Feature &feature = featureList[i];
 
-        for (int j = 0; j < feature.values.size(); ++j) {
+        for (int j = 0; j < (int)feature.values.size(); ++j) {
             stream << feature.values[j] << " ";
         }
     }
--- a/rdf/RDFFeatureWriter.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/rdf/RDFFeatureWriter.h	Wed Apr 20 12:06:28 2016 +0100
@@ -44,6 +44,8 @@
     RDFFeatureWriter();
     virtual ~RDFFeatureWriter();
 
+    virtual string getDescription() const;
+
     virtual ParameterList getSupportedParameters() const;
     virtual void setParameters(map<string, string> &params);
 
@@ -68,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);
@@ -126,7 +141,7 @@
     bool m_network;
     bool m_networkRetrieved;
 
-    unsigned long m_count;
+    long m_count;
 };
 
 #endif
--- a/rdf/RDFImporter.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/rdf/RDFImporter.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -30,7 +30,7 @@
 #include "data/model/NoteModel.h"
 #include "data/model/TextModel.h"
 #include "data/model/RegionModel.h"
-#include "data/model/WaveFileModel.h"
+#include "data/model/ReadOnlyWaveFileModel.h"
 
 #include "data/fileio/FileSource.h"
 #include "data/fileio/CachedFile.h"
@@ -50,10 +50,10 @@
 class RDFImporterImpl
 {
 public:
-    RDFImporterImpl(QString url, int sampleRate);
+    RDFImporterImpl(QString url, sv_samplerate_t sampleRate);
     virtual ~RDFImporterImpl();
 
-    void setSampleRate(int sampleRate) { m_sampleRate = sampleRate; }
+    void setSampleRate(sv_samplerate_t sampleRate) { m_sampleRate = sampleRate; }
     
     bool isOK();
     QString getErrorString() const;
@@ -67,7 +67,7 @@
     QString m_uristring;
     QString m_errorString;
     std::map<QString, Model *> m_audioModelMap;
-    int m_sampleRate;
+    sv_samplerate_t m_sampleRate;
 
     std::map<Model *, std::map<QString, float> > m_labelValueMap;
 
@@ -78,10 +78,11 @@
     void getDenseModelTitle(Model *, QString, QString);
 
     void getDenseFeatureProperties(QString featureUri,
-                                   int &sampleRate, int &windowLength,
+                                   sv_samplerate_t &sampleRate, int &windowLength,
                                    int &hopSize, int &width, int &height);
 
-    void fillModel(Model *, long, long, bool, std::vector<float> &, QString);
+    void fillModel(Model *, sv_frame_t, sv_frame_t,
+                   bool, std::vector<float> &, QString);
 };
 
 QString
@@ -90,7 +91,7 @@
     return "*.rdf *.n3 *.ttl";
 }
 
-RDFImporter::RDFImporter(QString url, int sampleRate) :
+RDFImporter::RDFImporter(QString url, sv_samplerate_t sampleRate) :
     m_d(new RDFImporterImpl(url, sampleRate)) 
 {
 }
@@ -101,7 +102,7 @@
 }
 
 void
-RDFImporter::setSampleRate(int sampleRate)
+RDFImporter::setSampleRate(sv_samplerate_t sampleRate)
 {
     m_d->setSampleRate(sampleRate);
 }
@@ -124,7 +125,7 @@
     return m_d->getDataModels(r);
 }
 
-RDFImporterImpl::RDFImporterImpl(QString uri, int sampleRate) :
+RDFImporterImpl::RDFImporterImpl(QString uri, sv_samplerate_t sampleRate) :
     m_store(new BasicStore),
     m_uristring(uri),
     m_sampleRate(sampleRate)
@@ -269,7 +270,7 @@
             reporter->setMessage(RDFImporter::tr("Importing audio referenced in RDF..."));
         }
         fs->waitForData();
-        WaveFileModel *newModel = new WaveFileModel(*fs, m_sampleRate);
+        ReadOnlyWaveFileModel *newModel = new ReadOnlyWaveFileModel(*fs, m_sampleRate);
         if (newModel->isOK()) {
             cerr << "Successfully created wave file model from source at \"" << source << "\"" << endl;
             models.push_back(newModel);
@@ -309,7 +310,7 @@
         
         if (type == "" || value == "") continue;
 
-        int sampleRate = 0;
+        sv_samplerate_t sampleRate = 0;
         int windowLength = 0;
         int hopSize = 0;
         int width = 0;
@@ -416,7 +417,7 @@
 
 void
 RDFImporterImpl::getDenseFeatureProperties(QString featureUri,
-                                           int &sampleRate, int &windowLength,
+                                           sv_samplerate_t &sampleRate, int &windowLength,
                                            int &hopSize, int &width, int &height)
 {
     Node dim = m_store->complete
@@ -467,7 +468,7 @@
     PropertyObject po(m_store, "tl:", map);
 
     if (po.hasProperty("sampleRate")) {
-        sampleRate = po.getProperty("sampleRate").toInt();
+        sampleRate = po.getProperty("sampleRate").toDouble();
     }
     if (po.hasProperty("hopSize")) {
         hopSize = po.getProperty("hopSize").toInt();
@@ -576,14 +577,14 @@
                 RealTime time;
                 RealTime duration;
 
-                bool haveTime = false;
+//                bool haveTime = false;
                 bool haveDuration = false;
 
                 Node at = m_store->complete(Triple(tn, expand("tl:at"), Node()));
 
                 if (at != Node()) {
                     time = RealTime::fromXsdDuration(at.value.toStdString());
-                    haveTime = true;
+//                    haveTime = true;
                 } else {
     //!!! NB we're using rather old terminology for these things, apparently:
     // beginsAt -> start
@@ -596,7 +597,7 @@
                             (start.value.toStdString());
                         duration = RealTime::fromXsdDuration
                             (dur.value.toStdString());
-                        haveTime = haveDuration = true;
+//                        haveTime = haveDuration = true;
                     }
                 }
 
@@ -686,8 +687,8 @@
                 model = modelMap[timeline][type][dimensions][haveDuration];
 
                 if (model) {
-                    long ftime = RealTime::realTime2Frame(time, m_sampleRate);
-                    long fduration = RealTime::realTime2Frame(duration, m_sampleRate);
+                    sv_frame_t ftime = RealTime::realTime2Frame(time, m_sampleRate);
+                    sv_frame_t fduration = RealTime::realTime2Frame(duration, m_sampleRate);
                     fillModel(model, ftime, fduration, haveDuration, values, label);
                 }
             }
@@ -697,8 +698,8 @@
 
 void
 RDFImporterImpl::fillModel(Model *model,
-                           long ftime,
-                           long fduration,
+                           sv_frame_t ftime,
+                           sv_frame_t fduration,
                            bool haveDuration,
                            std::vector<float> &values,
                            QString label)
@@ -757,7 +758,8 @@
                     }
                 }
             }
-            NoteModel::Point point(ftime, value, duration, level, label);
+            NoteModel::Point point(ftime, value, sv_frame_t(lrintf(duration)),
+                                   level, label);
             nm->addPoint(point);
         }
         return;
@@ -789,7 +791,8 @@
                     duration = values[1];
                 }
             }
-            RegionModel::Point point(ftime, value, duration, label);
+            RegionModel::Point point(ftime, value,
+                                     sv_frame_t(lrintf(duration)), label);
             rm->addPoint(point);
         }
         return;
--- a/rdf/RDFImporter.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/rdf/RDFImporter.h	Wed Apr 20 12:06:28 2016 +0100
@@ -21,6 +21,8 @@
 
 #include <vector>
 
+#include "base/BaseTypes.h"
+
 class Model;
 class RDFImporterImpl;
 class ProgressReporter;
@@ -37,10 +39,10 @@
      */
     static QString getKnownExtensions();
 
-    RDFImporter(QString url, int sampleRate = 0);
+    RDFImporter(QString url, sv_samplerate_t sampleRate = 0);
     virtual ~RDFImporter();
 
-    void setSampleRate(int sampleRate);
+    void setSampleRate(sv_samplerate_t sampleRate);
 
     bool isOK();
     QString getErrorString() const;
--- a/rdf/RDFTransformFactory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/rdf/RDFTransformFactory.cpp	Wed Apr 20 12:06:28 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
@@ -156,10 +161,12 @@
 }
 
 std::vector<Transform>
-RDFTransformFactoryImpl::getTransforms(ProgressReporter *reporter)
+RDFTransformFactoryImpl::getTransforms(ProgressReporter *)
 {
     std::vector<Transform> transforms;
 
+    if (!m_isRDF) return transforms;
+    
     std::map<QString, Transform> uriTransformMap;
 
     Nodes tnodes = m_store->match
@@ -212,10 +219,11 @@
             "window_type",
             "sample_rate",
             "start", 
-            "duration"
+            "duration",
+            "plugin_version"
         };
         
-        for (int j = 0; j < sizeof(optionals)/sizeof(optionals[0]); ++j) {
+        for (int j = 0; j < int(sizeof(optionals)/sizeof(optionals[0])); ++j) {
 
             QString optional = optionals[j];
 
@@ -241,11 +249,16 @@
             } else if (optional == "sample_rate") {
                 transform.setSampleRate(onode.value.toFloat());
             } else if (optional == "start") {
-                transform.setStartTime
-                    (RealTime::fromXsdDuration(onode.value.toStdString()));
+                RealTime start = RealTime::fromXsdDuration(onode.value.toStdString());
+                transform.setStartTime(start);
             } else if (optional == "duration") {
-                transform.setDuration
-                    (RealTime::fromXsdDuration(onode.value.toStdString()));
+                RealTime duration = RealTime::fromXsdDuration(onode.value.toStdString());
+                transform.setDuration(duration);
+                if (duration == RealTime::zeroTime) {
+                    cerr << "\nRDFTransformFactory: WARNING: Duration is specified as \"" << onode.value << "\" in RDF file,\n    but this evaluates to zero when parsed as an xsd:duration datatype.\n    The duration property will therefore be ignored.\n    To specify start time and duration use the xsd:duration format,\n    for example \"PT2.5S\"^^xsd:duration (for 2.5 seconds).\n\n";
+                }
+            } else if (optional == "plugin_version") {
+                transform.setPluginVersion(onode.value);
             } else {
                 cerr << "RDFTransformFactory: ERROR: Inconsistent optionals lists (unexpected optional \"" << optional << "\"" << endl;
             }
@@ -394,6 +407,11 @@
     if (transform.getBlockSize() != 0) {
         s << "    vamp:block_size \"" << transform.getBlockSize() << "\"^^xsd:int ; " << endl;
     }
+    if (transform.getWindowType() != HanningWindow) {
+        s << "    vamp:window_type \"" <<
+            Window<float>::getNameForType(transform.getWindowType()).c_str()
+          << "\" ; " << endl;
+    }
     if (transform.getStartTime() != RealTime::zeroTime) {
         s << "    vamp:start \"" << transform.getStartTime().toXsdDuration().c_str() << "\"^^xsd:duration ; " << endl;
     }
@@ -403,6 +421,9 @@
     if (transform.getSampleRate() != 0) {
         s << "    vamp:sample_rate \"" << transform.getSampleRate() << "\"^^xsd:float ; " << endl;
     }
+    if (transform.getPluginVersion() != "") {
+        s << "    vamp:plugin_version \"\"\"" << transform.getPluginVersion() << "\"\"\" ; " << endl;
+    }
     
     QString program = transform.getProgram();
     if (program != "") {
--- a/rdf/RDFTransformFactory.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/rdf/RDFTransformFactory.h	Wed Apr 20 12:06:28 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 Jul 14 15:04:46 2015 +0100
+++ b/svcore.pro	Wed Apr 20 12:06:28 2016 +0100
@@ -1,33 +1,48 @@
 
 TEMPLATE = lib
 
+INCLUDEPATH += ../vamp-plugin-sdk
+DEFINES += HAVE_VAMP HAVE_VAMPHOSTSDK
+
 exists(config.pri) {
     include(config.pri)
 }
-win* {
-    !exists(config.pri) {
-        DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_RUBBERBAND HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG
+!exists(config.pri) {
+
+    CONFIG += release
+    DEFINES += NDEBUG BUILD_RELEASE NO_TIMING
+
+    win32-g++ {
+        INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include
+        LIBS += -L../sv-dependency-builds/win32-mingw/lib
+    }
+    win32-msvc* {
+        INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include
+        LIBS += -L../sv-dependency-builds/win32-msvc/lib
+    }
+    macx* {
+        INCLUDEPATH += ../sv-dependency-builds/osx/include
+        LIBS += -L../sv-dependency-builds/osx/lib
+    }
+
+    DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_LIBLO HAVE_MAD HAVE_ID3TAG 
+
+    macx* {
+        DEFINES += HAVE_COREAUDIO
     }
 }
 
-CONFIG += staticlib qt thread warn_on stl rtti exceptions
+CONFIG += staticlib qt thread warn_on stl rtti exceptions c++11
 QT += network xml
 QT -= gui
 
 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
 
-win32-g++ {
-    INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include
-}
-win32-msvc* {
-    INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include
-}
-
 # Doesn't work with this library, which contains C99 as well as C++
 PRECOMPILED_HEADER =
 
@@ -39,6 +54,7 @@
 
 HEADERS += base/AudioLevel.h \
            base/AudioPlaySource.h \
+           base/BaseTypes.h \
            base/Clipboard.h \
            base/Command.h \
            base/Debug.h \
@@ -57,7 +73,6 @@
            base/RealTime.h \
            base/RecentFiles.h \
            base/Resampler.h \
-           base/ResizeableBitset.h \
            base/ResourceFinder.h \
            base/RingBuffer.h \
            base/Scavenger.h \
@@ -106,15 +121,9 @@
            base/XmlExportable.cpp
 
 HEADERS += data/fft/FFTapi.h \
-           data/fft/FFTCacheReader.h \
-           data/fft/FFTCacheStorageType.h \
-           data/fft/FFTCacheWriter.h \
-           data/fft/FFTDataServer.h \
-           data/fft/FFTFileCacheReader.h \
-           data/fft/FFTFileCacheWriter.h \
-           data/fft/FFTMemoryCache.h \
            data/fileio/AudioFileReader.h \
            data/fileio/AudioFileReaderFactory.h \
+           data/fileio/AudioFileSizeEstimator.h \
            data/fileio/BZipFileDevice.h \
            data/fileio/CachedFile.h \
            data/fileio/CodedAudioFileReader.h \
@@ -126,7 +135,6 @@
            data/fileio/FileFinder.h \
            data/fileio/FileReadThread.h \
            data/fileio/FileSource.h \
-           data/fileio/MatchFileReader.h \
            data/fileio/MatrixFile.h \
            data/fileio/MIDIFileReader.h \
            data/fileio/MIDIFileWriter.h \
@@ -155,6 +163,7 @@
            data/model/Model.h \
            data/model/ModelDataTableModel.h \
            data/model/NoteModel.h \
+           data/model/FlexiNoteModel.h \
            data/model/PathModel.h \
            data/model/PowerOfSqrtTwoZoomConstraint.h \
            data/model/PowerOfTwoZoomConstraint.h \
@@ -167,16 +176,14 @@
            data/model/TabularModel.h \
            data/model/TextModel.h \
            data/model/WaveFileModel.h \
+           data/model/ReadOnlyWaveFileModel.h \
            data/model/WritableWaveFileModel.h \
            data/osc/OSCMessage.h \
            data/osc/OSCQueue.h 
 SOURCES += data/fft/FFTapi.cpp \
-           data/fft/FFTDataServer.cpp \
-           data/fft/FFTFileCacheReader.cpp \
-           data/fft/FFTFileCacheWriter.cpp \
-           data/fft/FFTMemoryCache.cpp \
            data/fileio/AudioFileReader.cpp \
            data/fileio/AudioFileReaderFactory.cpp \
+           data/fileio/AudioFileSizeEstimator.cpp \
            data/fileio/BZipFileDevice.cpp \
            data/fileio/CachedFile.cpp \
            data/fileio/CodedAudioFileReader.cpp \
@@ -186,7 +193,6 @@
            data/fileio/DataFileReaderFactory.cpp \
            data/fileio/FileReadThread.cpp \
            data/fileio/FileSource.cpp \
-           data/fileio/MatchFileReader.cpp \
            data/fileio/MatrixFile.cpp \
            data/fileio/MIDIFileReader.cpp \
            data/fileio/MIDIFileWriter.cpp \
@@ -212,11 +218,13 @@
            data/model/PowerOfTwoZoomConstraint.cpp \
            data/model/RangeSummarisableTimeValueModel.cpp \
            data/model/WaveFileModel.cpp \
+           data/model/ReadOnlyWaveFileModel.cpp \
            data/model/WritableWaveFileModel.cpp \
            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 \
@@ -236,7 +244,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 Jul 14 15:04:46 2015 +0100
+++ b/system/System.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -123,7 +123,7 @@
 #endif
 
 void
-GetRealMemoryMBAvailable(int &available, int &total)
+GetRealMemoryMBAvailable(ssize_t &available, ssize_t &total)
 {
     available = -1;
     total = -1;
@@ -175,11 +175,11 @@
 
     DWORDLONG size = wavail / 1048576;
     if (size > INT_MAX) size = INT_MAX;
-    available = int(size);
+    available = ssize_t(size);
 
     size = wtotal / 1048576;
     if (size > INT_MAX) size = INT_MAX;
-    total = int(size);
+    total = ssize_t(size);
 
     return;
 
@@ -243,7 +243,7 @@
 #endif
 }
 
-int
+ssize_t
 GetDiscSpaceMBAvailable(const char *path)
 {
 #ifdef _WIN32
@@ -252,7 +252,7 @@
 	  __int64 a = available.QuadPart;
         a /= 1048576;
         if (a > INT_MAX) a = INT_MAX;
-        return int(a);
+        return ssize_t(a);
     } else {
         cerr << "WARNING: GetDiskFreeSpaceEx failed: error code "
                   << GetLastError() << endl;
@@ -266,7 +266,7 @@
 //        cerr << "statvfs(" << path << ") says available: " << buf.f_bavail << ", block size: " << buf.f_bsize << endl;
         uint64_t available = ((buf.f_bavail / 1024) * buf.f_bsize) / 1024;
         if (available > INT_MAX) available = INT_MAX;
-        return int(available);
+        return ssize_t(available);
     } else {
         perror("statvfs failed");
         return -1;
@@ -323,5 +323,5 @@
 float modf(float x, float y) { return x - (y * floorf(x / y)); }
 
 double princarg(double a) { return mod(a + M_PI, -2 * M_PI) + M_PI; }
-float princargf(float a) { return modf(a + M_PI, -2 * M_PI) + M_PI; }
+float princargf(float a) { return float(princarg(a)); }
 
--- a/system/System.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/system/System.h	Wed Apr 20 12:06:28 2016 +0100
@@ -143,18 +143,23 @@
 extern ProcessStatus GetProcessStatus(int pid);
 
 // Return a vague approximation to the number of free megabytes of real memory.
-// Return -1 if unknown.
-extern void GetRealMemoryMBAvailable(int &available, int &total);
+// Return -1 if unknown. (Hence signed args)
+extern void GetRealMemoryMBAvailable(ssize_t &available, ssize_t &total);
 
-// Return a vague approximation to the number of free megabytes of disc space
-// on the partition containing the given path.  Return -1 if unknown.
-extern int GetDiscSpaceMBAvailable(const char *path);
+// Return a vague approximation to the number of free megabytes of
+// disc space on the partition containing the given path.  Return -1
+// if unknown. (Hence signed return type)
+extern ssize_t GetDiscSpaceMBAvailable(const char *path);
 
 extern void StoreStartupLocale();
 extern void RestoreStartupLocale();
 
 #include <cmath>
 
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
 extern double mod(double x, double y);
 extern float modf(float x, float y);
 
--- a/transform/CSVFeatureWriter.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/CSVFeatureWriter.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -23,16 +23,22 @@
 
 #include <QRegExp>
 #include <QTextStream>
+#include <QTextCodec>
 
 using namespace std;
 using namespace Vamp;
 
 CSVFeatureWriter::CSVFeatureWriter() :
     FileFeatureWriter(SupportOneFilePerTrackTransform |
-                      SupportOneFileTotal,
+                      SupportOneFileTotal |
+                      SupportStdOut,
                       "csv"),
     m_separator(","),
-    m_sampleTiming(false)
+    m_sampleTiming(false),
+    m_endTimes(false),
+    m_forceEnd(false),
+    m_omitFilename(false),
+    m_digits(6)
 {
 }
 
@@ -40,6 +46,12 @@
 {
 }
 
+string
+CSVFeatureWriter::getDescription() const
+{
+    return "Write features in comma-separated (CSV) format. If transforms are being written to a single file or to stdout, the first column in the output will contain the input audio filename, or an empty string if the feature hails from the same audio file as its predecessor. If transforms are being written to multiple files, the audio filename column will be omitted. Subsequent columns will contain the feature timestamp, then any or all of duration, values, and label.";
+}
+
 CSVFeatureWriter::ParameterList
 CSVFeatureWriter::getSupportedParameters() const
 {
@@ -51,10 +63,30 @@
     p.hasArg = true;
     pl.push_back(p);
     
+    p.name = "omit-filename";
+    p.description = "Omit the filename column. May result in confusion if sending more than one audio file's features to the same CSV output.";
+    p.hasArg = false;
+    pl.push_back(p);
+    
     p.name = "sample-timing";
     p.description = "Show timings as sample frame counts instead of in seconds.";
     p.hasArg = false;
     pl.push_back(p);
+    
+    p.name = "end-times";
+    p.description = "Show start and end time instead of start and duration, for features with duration.";
+    p.hasArg = false;
+    pl.push_back(p);
+
+    p.name = "fill-ends";
+    p.description = "Include durations (or end times) even for features without duration, by using the gap to the next feature instead.";
+    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;
 }
@@ -67,11 +99,29 @@
     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();
+            SVDEBUG << "m_separator = " << m_separator << endl;
+            if (m_separator == "\\t") {
+                m_separator = QChar::Tabulation;
+            }
         } else if (i->first == "sample-timing") {
             m_sampleTiming = true;
+        } else if (i->first == "end-times") {
+            m_endTimes = true;
+        } else if (i->first == "fill-ends") {
+            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;
+            }
         }
     }
 }
@@ -79,22 +129,91 @@
 void
 CSVFeatureWriter::write(QString trackId,
                         const Transform &transform,
-                        const Plugin::OutputDescriptor& output,
+                        const Plugin::OutputDescriptor& ,
                         const Plugin::FeatureList& features,
                         std::string summaryType)
 {
+    TransformId transformId = transform.getIdentifier();
+
     // Select appropriate output file for our track/transform
     // combination
 
-    QTextStream *sptr = getOutputStream(trackId, transform.getIdentifier());
+    QTextStream *sptr = getOutputStream(trackId,
+                                        transformId,
+                                        QTextCodec::codecForName("UTF-8"));
     if (!sptr) {
-        throw FailedToOpenOutputStream(trackId, transform.getIdentifier());
+        throw FailedToOpenOutputStream(trackId, transformId);
     }
 
     QTextStream &stream = *sptr;
 
-    for (unsigned int i = 0; i < features.size(); ++i) {
+    int n = (int)features.size();
 
+    if (n == 0) return;
+
+    DataId tt(trackId, transform);
+
+    if (m_pending.find(tt) != m_pending.end()) {
+        writeFeature(tt,
+                     stream,
+                     m_pending[tt],
+                     &features[0],
+                     m_pendingSummaryTypes[tt]);
+        m_pending.erase(tt);
+        m_pendingSummaryTypes.erase(tt);
+    }
+
+    if (m_forceEnd) {
+        // can't write final feature until we know its end time
+        --n;
+        m_pending[tt] = features[n];
+        m_pendingSummaryTypes[tt] = summaryType;
+    }
+
+    for (int i = 0; i < n; ++i) {
+        writeFeature(tt, 
+                     stream,
+                     features[i], 
+                     m_forceEnd ? &features[i+1] : 0,
+                     summaryType);
+    }
+}
+
+void
+CSVFeatureWriter::finish()
+{
+    for (PendingFeatures::const_iterator i = m_pending.begin();
+         i != m_pending.end(); ++i) {
+        DataId tt = i->first;
+        Plugin::Feature f = i->second;
+        QTextStream *sptr = getOutputStream(tt.first,
+                                            tt.second.getIdentifier(),
+                                            QTextCodec::codecForName("UTF-8"));
+        if (!sptr) {
+            throw FailedToOpenOutputStream(tt.first, tt.second.getIdentifier());
+        }
+        QTextStream &stream = *sptr;
+        // final feature has its own time as end time (we can't
+        // reliably determine the end of audio file, and because of
+        // the nature of block processing, the feature could even
+        // start beyond that anyway)
+        writeFeature(tt, stream, f, &f, m_pendingSummaryTypes[tt]);
+    }
+
+    m_pending.clear();
+}
+
+void
+CSVFeatureWriter::writeFeature(DataId tt,
+                               QTextStream &stream,
+                               const Plugin::Feature &f,
+                               const Plugin::Feature *optionalNextFeature,
+                               std::string summaryType)
+{
+    QString trackId = tt.first;
+    Transform transform = tt.second;
+
+    if (!m_omitFilename) {
         if (m_stdout || m_singleFileName != "") {
             if (trackId != m_prevPrintedTrackId) {
                 stream << "\"" << trackId << "\"" << m_separator;
@@ -103,45 +222,68 @@
                 stream << m_separator;
             }
         }
+    }
 
-        if (m_sampleTiming) {
+    ::RealTime duration;
+    bool haveDuration = true;
+    
+    if (f.hasDuration) {
+        duration = f.duration;
+    } else if (optionalNextFeature) {
+        duration = optionalNextFeature->timestamp - f.timestamp;
+    } else {
+        haveDuration = false;
+    }
 
-            stream << Vamp::RealTime::realTime2Frame
-                (features[i].timestamp, transform.getSampleRate());
+    if (m_sampleTiming) {
 
-            if (features[i].hasDuration) {
-                stream << m_separator;
-                stream << Vamp::RealTime::realTime2Frame
-                    (features[i].duration, transform.getSampleRate());
+        sv_samplerate_t rate = transform.getSampleRate();
+
+        stream << ::RealTime::realTime2Frame(f.timestamp, rate);
+
+        if (haveDuration) {
+            stream << m_separator;
+            if (m_endTimes) {
+                stream << ::RealTime::realTime2Frame
+                    (::RealTime(f.timestamp) + duration, rate);
+            } else {
+                stream << ::RealTime::realTime2Frame(duration, rate);
             }
-
-        } else {
-
-            QString timestamp = features[i].timestamp.toString().c_str();
-            timestamp.replace(QRegExp("^ +"), "");
-            stream << timestamp;
-
-            if (features[i].hasDuration) {
-                QString duration = features[i].duration.toString().c_str();
-                duration.replace(QRegExp("^ +"), "");
-                stream << m_separator << duration;
-            }            
         }
 
-        if (summaryType != "") {
-            stream << m_separator << summaryType.c_str();
-        }
+    } else {
 
-        for (unsigned int j = 0; j < features[i].values.size(); ++j) {
-            stream << m_separator << features[i].values[j];
-        }
+        QString timestamp = f.timestamp.toString().c_str();
+        timestamp.replace(QRegExp("^ +"), "");
+        stream << timestamp;
 
-        if (features[i].label != "") {
-            stream << m_separator << "\"" << features[i].label.c_str() << "\"";
-        }
+        if (haveDuration) {
+            if (m_endTimes) {
+                QString endtime =
+                    (::RealTime(f.timestamp) + duration).toString().c_str();
+                endtime.replace(QRegExp("^ +"), "");
+                stream << m_separator << endtime;
+            } else {
+                QString d = ::RealTime(duration).toString().c_str();
+                d.replace(QRegExp("^ +"), "");
+                stream << m_separator << d;
+            }
+        }            
+    }
 
-        stream << "\n";
+    if (summaryType != "") {
+        stream << m_separator << summaryType.c_str();
     }
+    
+    for (unsigned int j = 0; j < f.values.size(); ++j) {
+        stream << m_separator << QString("%1").arg(f.values[j], 0, 'g', m_digits);
+    }
+    
+    if (f.label != "") {
+        stream << m_separator << "\"" << f.label.c_str() << "\"";
+    }
+    
+    stream << "\n";
 }
 
 
--- a/transform/CSVFeatureWriter.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/CSVFeatureWriter.h	Wed Apr 20 12:06:28 2016 +0100
@@ -40,6 +40,8 @@
     CSVFeatureWriter();
     virtual ~CSVFeatureWriter();
 
+    virtual string getDescription() const;
+
     virtual ParameterList getSupportedParameters() const;
     virtual void setParameters(map<string, string> &params);
 
@@ -49,12 +51,31 @@
                        const Vamp::Plugin::FeatureList &features,
                        std::string summaryType = "");
 
+    virtual void finish();
+
     virtual QString getWriterTag() const { return "csv"; }
 
 private:
     QString m_separator;
     bool m_sampleTiming;
+    bool m_endTimes;
+    bool m_forceEnd;
+    bool m_omitFilename;
     QString m_prevPrintedTrackId;
+
+    typedef pair<QString, Transform> DataId; // track id, transform
+    typedef map<DataId, Vamp::Plugin::Feature> PendingFeatures;
+    typedef map<DataId, std::string> PendingSummaryTypes;
+    PendingFeatures m_pending;
+    PendingSummaryTypes m_pendingSummaryTypes;
+
+    void writeFeature(DataId,
+                      QTextStream &,
+                      const Vamp::Plugin::Feature &f,
+                      const Vamp::Plugin::Feature *optionalNextFeature,
+                      std::string summaryType);
+
+    int m_digits;
 };
 
 #endif
--- a/transform/FeatureExtractionModelTransformer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/FeatureExtractionModelTransformer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -27,6 +27,7 @@
 #include "data/model/EditableDenseThreeDimensionalModel.h"
 #include "data/model/DenseTimeValueModel.h"
 #include "data/model/NoteModel.h"
+#include "data/model/FlexiNoteModel.h"
 #include "data/model/RegionModel.h"
 #include "data/model/FFTModel.h"
 #include "data/model/WaveFileModel.h"
@@ -36,82 +37,124 @@
 
 #include <iostream>
 
+#include <QSettings>
+
 FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in,
                                                                      const Transform &transform) :
     ModelTransformer(in, transform),
-    m_plugin(0),
-    m_descriptor(0),
-    m_outputNo(0),
-    m_fixedRateFeatureNo(-1) // we increment before use
+    m_plugin(0)
 {
-//    SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << pluginId << ", outputName " << m_transform.getOutput() << endl;
+    SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl;
 
-    QString pluginId = transform.getPluginIdentifier();
+    initialise();
+}
+
+FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in,
+                                                                     const Transforms &transforms) :
+    ModelTransformer(in, transforms),
+    m_plugin(0)
+{
+    if (m_transforms.empty()) {
+        SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s)" << endl;
+    } else {
+        SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s), first has plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl;
+    }
+    
+    initialise();
+}
+
+static bool
+areTransformsSimilar(const Transform &t1, const Transform &t2)
+{
+    Transform t2o(t2);
+    t2o.setOutput(t1.getOutput());
+    return t1 == t2o;
+}
+
+bool
+FeatureExtractionModelTransformer::initialise()
+{
+    // All transforms must use the same plugin, parameters, and
+    // inputs: they can differ only in choice of plugin output. So we
+    // initialise based purely on the first transform in the list (but
+    // first check that they are actually similar as promised)
+
+    for (int j = 1; j < (int)m_transforms.size(); ++j) {
+        if (!areTransformsSimilar(m_transforms[0], m_transforms[j])) {
+            m_message = tr("Transforms supplied to a single FeatureExtractionModelTransformer instance must be similar in every respect except plugin output");
+            return false;
+        }
+    }
+
+    Transform primaryTransform = m_transforms[0];
+
+    QString pluginId = primaryTransform.getPluginIdentifier();
 
     FeatureExtractionPluginFactory *factory =
 	FeatureExtractionPluginFactory::instanceFor(pluginId);
 
     if (!factory) {
         m_message = tr("No factory available for feature extraction plugin id \"%1\" (unknown plugin type, or internal error?)").arg(pluginId);
-	return;
+	return false;
     }
 
     DenseTimeValueModel *input = getConformingInput();
     if (!input) {
         m_message = tr("Input model for feature extraction plugin \"%1\" is of wrong type (internal error?)").arg(pluginId);
-        return;
+        return false;
     }
 
     m_plugin = factory->instantiatePlugin(pluginId, input->getSampleRate());
     if (!m_plugin) {
         m_message = tr("Failed to instantiate plugin \"%1\"").arg(pluginId);
-	return;
+	return false;
     }
 
     TransformFactory::getInstance()->makeContextConsistentWithPlugin
-        (m_transform, m_plugin);
+        (primaryTransform, m_plugin);
 
     TransformFactory::getInstance()->setPluginParameters
-        (m_transform, m_plugin);
+        (primaryTransform, m_plugin);
 
-    size_t channelCount = input->getChannelCount();
-    if (m_plugin->getMaxChannelCount() < channelCount) {
+    int channelCount = input->getChannelCount();
+    if ((int)m_plugin->getMaxChannelCount() < channelCount) {
 	channelCount = 1;
     }
-    if (m_plugin->getMinChannelCount() > channelCount) {
+    if ((int)m_plugin->getMinChannelCount() > channelCount) {
         m_message = tr("Cannot provide enough channels to feature extraction plugin \"%1\" (plugin min is %2, max %3; input model has %4)")
             .arg(pluginId)
             .arg(m_plugin->getMinChannelCount())
             .arg(m_plugin->getMaxChannelCount())
             .arg(input->getChannelCount());
-	return;
+	return false;
     }
 
     SVDEBUG << "Initialising feature extraction plugin with channels = "
-              << channelCount << ", step = " << m_transform.getStepSize()
-              << ", block = " << m_transform.getBlockSize() << endl;
+              << channelCount << ", step = " << primaryTransform.getStepSize()
+              << ", block = " << primaryTransform.getBlockSize() << endl;
 
     if (!m_plugin->initialise(channelCount,
-                              m_transform.getStepSize(),
-                              m_transform.getBlockSize())) {
+                              primaryTransform.getStepSize(),
+                              primaryTransform.getBlockSize())) {
 
-        size_t pstep = m_transform.getStepSize();
-        size_t pblock = m_transform.getBlockSize();
+        int pstep = primaryTransform.getStepSize();
+        int pblock = primaryTransform.getBlockSize();
 
-        m_transform.setStepSize(0);
-        m_transform.setBlockSize(0);
+///!!! hang on, this isn't right -- we're modifying a copy
+        primaryTransform.setStepSize(0);
+        primaryTransform.setBlockSize(0);
         TransformFactory::getInstance()->makeContextConsistentWithPlugin
-            (m_transform, m_plugin);
+            (primaryTransform, m_plugin);
 
-        if (m_transform.getStepSize() != pstep ||
-            m_transform.getBlockSize() != pblock) {
+        if (primaryTransform.getStepSize() != pstep ||
+            primaryTransform.getBlockSize() != pblock) {
             
             if (!m_plugin->initialise(channelCount,
-                                      m_transform.getStepSize(),
-                                      m_transform.getBlockSize())) {
+                                      primaryTransform.getStepSize(),
+                                      primaryTransform.getBlockSize())) {
 
                 m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
-                return;
+                return false;
 
             } else {
 
@@ -119,22 +162,22 @@
                     .arg(pluginId)
                     .arg(pstep)
                     .arg(pblock)
-                    .arg(m_transform.getStepSize())
-                    .arg(m_transform.getBlockSize());
+                    .arg(primaryTransform.getStepSize())
+                    .arg(primaryTransform.getBlockSize());
             }
 
         } else {
 
             m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
-            return;
+            return false;
         }
     }
 
-    if (m_transform.getPluginVersion() != "") {
+    if (primaryTransform.getPluginVersion() != "") {
         QString pv = QString("%1").arg(m_plugin->getPluginVersion());
-        if (pv != m_transform.getPluginVersion()) {
+        if (pv != primaryTransform.getPluginVersion()) {
             QString vm = tr("Transform was configured for version %1 of plugin \"%2\", but the plugin being used is version %3")
-                .arg(m_transform.getPluginVersion())
+                .arg(primaryTransform.getPluginVersion())
                 .arg(pluginId)
                 .arg(pv);
             if (m_message != "") {
@@ -149,77 +192,88 @@
 
     if (outputs.empty()) {
         m_message = tr("Plugin \"%1\" has no outputs").arg(pluginId);
-	return;
-    }
-    
-    for (size_t i = 0; i < outputs.size(); ++i) {
-//        SVDEBUG << "comparing output " << i << " name \"" << outputs[i].identifier << "\" with expected \"" << m_transform.getOutput() << "\"" << endl;
-	if (m_transform.getOutput() == "" ||
-            outputs[i].identifier == m_transform.getOutput().toStdString()) {
-	    m_outputNo = i;
-	    m_descriptor = new Vamp::Plugin::OutputDescriptor(outputs[i]);
-	    break;
-	}
+	return false;
     }
 
-    if (!m_descriptor) {
-        m_message = tr("Plugin \"%1\" has no output named \"%2\"")
-            .arg(pluginId)
-            .arg(m_transform.getOutput());
-	return;
+    for (int j = 0; j < (int)m_transforms.size(); ++j) {
+
+        for (int i = 0; i < (int)outputs.size(); ++i) {
+//        SVDEBUG << "comparing output " << i << " name \"" << outputs[i].identifier << "\" with expected \"" << m_transform.getOutput() << "\"" << endl;
+            if (m_transforms[j].getOutput() == "" ||
+                outputs[i].identifier == m_transforms[j].getOutput().toStdString()) {
+                m_outputNos.push_back(i);
+                m_descriptors.push_back(new Vamp::Plugin::OutputDescriptor(outputs[i]));
+                m_fixedRateFeatureNos.push_back(-1); // we increment before use
+                break;
+            }
+        }
+
+        if ((int)m_descriptors.size() <= j) {
+            m_message = tr("Plugin \"%1\" has no output named \"%2\"")
+                .arg(pluginId)
+                .arg(m_transforms[j].getOutput());
+            return false;
+        }
     }
 
-    createOutputModel();
+    for (int j = 0; j < (int)m_transforms.size(); ++j) {
+        createOutputModels(j);
+    }
+
+    return true;
 }
 
 void
-FeatureExtractionModelTransformer::createOutputModel()
+FeatureExtractionModelTransformer::createOutputModels(int n)
 {
     DenseTimeValueModel *input = getConformingInput();
 
 //    cerr << "FeatureExtractionModelTransformer::createOutputModel: sample type " << m_descriptor->sampleType << ", rate " << m_descriptor->sampleRate << endl;
     
-    PluginRDFDescription description(m_transform.getPluginIdentifier());
-    QString outputId = m_transform.getOutput();
+    PluginRDFDescription description(m_transforms[n].getPluginIdentifier());
+    QString outputId = m_transforms[n].getOutput();
 
     int binCount = 1;
     float minValue = 0.0, maxValue = 0.0;
     bool haveExtents = false;
-    
-    if (m_descriptor->hasFixedBinCount) {
-	binCount = m_descriptor->binCount;
+    bool haveBinCount = m_descriptors[n]->hasFixedBinCount;
+
+    if (haveBinCount) {
+	binCount = (int)m_descriptors[n]->binCount;
     }
 
+    m_needAdditionalModels[n] = false;
+
 //    cerr << "FeatureExtractionModelTransformer: output bin count "
 //	      << binCount << endl;
 
-    if (binCount > 0 && m_descriptor->hasKnownExtents) {
-	minValue = m_descriptor->minValue;
-	maxValue = m_descriptor->maxValue;
+    if (binCount > 0 && m_descriptors[n]->hasKnownExtents) {
+	minValue = m_descriptors[n]->minValue;
+	maxValue = m_descriptors[n]->maxValue;
         haveExtents = true;
     }
 
-    size_t modelRate = input->getSampleRate();
-    size_t modelResolution = 1;
+    sv_samplerate_t modelRate = input->getSampleRate();
+    int modelResolution = 1;
 
-    if (m_descriptor->sampleType != 
+    if (m_descriptors[n]->sampleType != 
         Vamp::Plugin::OutputDescriptor::OneSamplePerStep) {
-        if (m_descriptor->sampleRate > input->getSampleRate()) {
+        if (m_descriptors[n]->sampleRate > input->getSampleRate()) {
             cerr << "WARNING: plugin reports output sample rate as "
-                      << m_descriptor->sampleRate << " (can't display features with finer resolution than the input rate of " << input->getSampleRate() << ")" << endl;
+                      << m_descriptors[n]->sampleRate << " (can't display features with finer resolution than the input rate of " << input->getSampleRate() << ")" << endl;
         }
     }
 
-    switch (m_descriptor->sampleType) {
+    switch (m_descriptors[n]->sampleType) {
 
     case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
-	if (m_descriptor->sampleRate != 0.0) {
-	    modelResolution = size_t(modelRate / m_descriptor->sampleRate + 0.001);
+	if (m_descriptors[n]->sampleRate != 0.0) {
+	    modelResolution = int(round(modelRate / m_descriptors[n]->sampleRate));
 	}
 	break;
 
     case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
-	modelResolution = m_transform.getStepSize();
+	modelResolution = m_transforms[n].getStepSize();
 	break;
 
     case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
@@ -228,33 +282,34 @@
         //!!! the model rate to be the input model's rate, and adjust
         //!!! the resolution appropriately.  We can't properly display
         //!!! data with a higher resolution than the base model at all
-//	modelRate = size_t(m_descriptor->sampleRate + 0.001);
-        if (m_descriptor->sampleRate > input->getSampleRate()) {
+        if (m_descriptors[n]->sampleRate > input->getSampleRate()) {
+            modelResolution = 1;
+        } else if (m_descriptors[n]->sampleRate <= 0.0) {
+            cerr << "WARNING: Fixed sample-rate plugin reports invalid sample rate " << m_descriptors[n]->sampleRate << "; defaulting to input rate of " << input->getSampleRate() << endl;
             modelResolution = 1;
         } else {
-            modelResolution = size_t(input->getSampleRate() /
-                                     m_descriptor->sampleRate);
+            modelResolution = int(round(modelRate / m_descriptors[n]->sampleRate));
         }
 	break;
     }
 
     bool preDurationPlugin = (m_plugin->getVampApiVersion() < 2);
 
+    Model *out = 0;
+
     if (binCount == 0 &&
-        (preDurationPlugin || !m_descriptor->hasDuration)) {
+        (preDurationPlugin || !m_descriptors[n]->hasDuration)) {
 
         // Anything with no value and no duration is an instant
 
-	m_output = new SparseOneDimensionalModel(modelRate, modelResolution,
-						 false);
-
+        out = new SparseOneDimensionalModel(modelRate, modelResolution, false);
         QString outputEventTypeURI = description.getOutputEventTypeURI(outputId);
-        m_output->setRDFTypeURI(outputEventTypeURI);
+        out->setRDFTypeURI(outputEventTypeURI);
 
     } else if ((preDurationPlugin && binCount > 1 &&
-                (m_descriptor->sampleType ==
+                (m_descriptors[n]->sampleType ==
                  Vamp::Plugin::OutputDescriptor::VariableSampleRate)) ||
-               (!preDurationPlugin && m_descriptor->hasDuration)) {
+               (!preDurationPlugin && m_descriptors[n]->hasDuration)) {
 
         // For plugins using the old v1 API without explicit duration,
         // we treat anything that has multiple bins (i.e. that has the
@@ -285,9 +340,9 @@
 
         // Regions do not have units of Hz or MIDI things (a sweeping
         // assumption!)
-        if (m_descriptor->unit == "Hz" ||
-            m_descriptor->unit.find("MIDI") != std::string::npos ||
-            m_descriptor->unit.find("midi") != std::string::npos) {
+        if (m_descriptors[n]->unit == "Hz" ||
+            m_descriptors[n]->unit.find("MIDI") != std::string::npos ||
+            m_descriptors[n]->unit.find("midi") != std::string::npos) {
             isNoteModel = true;
         }
 
@@ -295,7 +350,14 @@
         // problem of determining whether to use that here (if bin
         // count > 1).  But we don't.
 
-        if (isNoteModel) {
+        QSettings settings;
+        settings.beginGroup("Transformer");
+        bool flexi = settings.value("use-flexi-note-model", false).toBool();
+        settings.endGroup();
+
+        cerr << "flexi = " << flexi << endl;
+
+        if (isNoteModel && !flexi) {
 
             NoteModel *model;
             if (haveExtents) {
@@ -305,8 +367,21 @@
                 model = new NoteModel
                     (modelRate, modelResolution, false);
             }
-            model->setScaleUnits(m_descriptor->unit.c_str());
-            m_output = model;
+            model->setScaleUnits(m_descriptors[n]->unit.c_str());
+            out = model;
+
+        } else if (isNoteModel && flexi) {
+
+            FlexiNoteModel *model;
+            if (haveExtents) {
+                model = new FlexiNoteModel
+                    (modelRate, modelResolution, minValue, maxValue, false);
+            } else {
+                model = new FlexiNoteModel
+                    (modelRate, modelResolution, false);
+            }
+            model->setScaleUnits(m_descriptors[n]->unit.c_str());
+            out = model;
 
         } else {
 
@@ -318,15 +393,15 @@
                 model = new RegionModel
                     (modelRate, modelResolution, false);
             }
-            model->setScaleUnits(m_descriptor->unit.c_str());
-            m_output = model;
+            model->setScaleUnits(m_descriptors[n]->unit.c_str());
+            out = model;
         }
 
         QString outputEventTypeURI = description.getOutputEventTypeURI(outputId);
-        m_output->setRDFTypeURI(outputEventTypeURI);
+        out->setRDFTypeURI(outputEventTypeURI);
 
     } else if (binCount == 1 ||
-               (m_descriptor->sampleType == 
+               (m_descriptors[n]->sampleType == 
                 Vamp::Plugin::OutputDescriptor::VariableSampleRate)) {
 
         // Anything that is not a 1D, note, or interval model and that
@@ -334,10 +409,27 @@
         // model.
 
         // Anything that is not a 1D, note, or interval model and that
-        // has a variable sample rate is also treated as a sparse time
-        // value model regardless of its bin count, because we lack a
+        // has a variable sample rate is treated as a set of sparse
+        // time value models, one per output bin, because we lack a
         // sparse 3D model.
 
+        // Anything that is not a 1D, note, or interval model and that
+        // has a fixed sample rate but an unknown number of values per
+        // result is also treated as a set of sparse time value models.
+
+        // For sets of sparse time value models, we create a single
+        // model first as the "standard" output and then create models
+        // for bins 1+ in the additional model map (mapping the output
+        // descriptor to a list of models indexed by bin-1). But we
+        // don't create the additional models yet, as this case has to
+        // work even if the number of bins is unknown at this point --
+        // we create an additional model (copying its parameters from
+        // the default one) each time a new bin is encountered.
+
+        if (!haveBinCount || binCount > 1) {
+            m_needAdditionalModels[n] = true;
+        }
+
         SparseTimeValueModel *model;
         if (haveExtents) {
             model = new SparseTimeValueModel
@@ -348,12 +440,12 @@
         }
 
         Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors();
-        model->setScaleUnits(outputs[m_outputNo].unit.c_str());
+        model->setScaleUnits(outputs[m_outputNos[n]].unit.c_str());
 
-        m_output = model;
+        out = model;
 
         QString outputEventTypeURI = description.getOutputEventTypeURI(outputId);
-        m_output->setRDFTypeURI(outputEventTypeURI);
+        out->setRDFTypeURI(outputEventTypeURI);
 
     } else {
 
@@ -367,28 +459,95 @@
              EditableDenseThreeDimensionalModel::BasicMultirateCompression,
              false);
 
-	if (!m_descriptor->binNames.empty()) {
+	if (!m_descriptors[n]->binNames.empty()) {
 	    std::vector<QString> names;
-	    for (size_t i = 0; i < m_descriptor->binNames.size(); ++i) {
-		names.push_back(m_descriptor->binNames[i].c_str());
+	    for (int i = 0; i < (int)m_descriptors[n]->binNames.size(); ++i) {
+		names.push_back(m_descriptors[n]->binNames[i].c_str());
 	    }
 	    model->setBinNames(names);
 	}
         
-        m_output = model;
+        out = model;
 
         QString outputSignalTypeURI = description.getOutputSignalTypeURI(outputId);
-        m_output->setRDFTypeURI(outputSignalTypeURI);
+        out->setRDFTypeURI(outputSignalTypeURI);
     }
 
-    if (m_output) m_output->setSourceModel(input);
+    if (out) {
+        out->setSourceModel(input);
+        m_outputs.push_back(out);
+    }
 }
 
 FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()
 {
 //    SVDEBUG << "FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()" << endl;
     delete m_plugin;
-    delete m_descriptor;
+    for (int j = 0; j < (int)m_descriptors.size(); ++j) {
+        delete m_descriptors[j];
+    }
+}
+
+FeatureExtractionModelTransformer::Models
+FeatureExtractionModelTransformer::getAdditionalOutputModels()
+{
+    Models mm;
+    for (AdditionalModelMap::iterator i = m_additionalModels.begin();
+         i != m_additionalModels.end(); ++i) {
+        for (std::map<int, SparseTimeValueModel *>::iterator j =
+                 i->second.begin();
+             j != i->second.end(); ++j) {
+            SparseTimeValueModel *m = j->second;
+            if (m) mm.push_back(m);
+        }
+    }
+    return mm;
+}
+
+bool
+FeatureExtractionModelTransformer::willHaveAdditionalOutputModels()
+{
+    for (std::map<int, bool>::const_iterator i =
+             m_needAdditionalModels.begin(); 
+         i != m_needAdditionalModels.end(); ++i) {
+        if (i->second) return true;
+    }
+    return false;
+}
+
+SparseTimeValueModel *
+FeatureExtractionModelTransformer::getAdditionalModel(int n, int binNo)
+{
+//    std::cerr << "getAdditionalModel(" << n << ", " << binNo << ")" << std::endl;
+
+    if (binNo == 0) {
+        std::cerr << "Internal error: binNo == 0 in getAdditionalModel (should be using primary model)" << std::endl;
+        return 0;
+    }
+
+    if (!m_needAdditionalModels[n]) return 0;
+    if (!isOutput<SparseTimeValueModel>(n)) return 0;
+    if (m_additionalModels[n][binNo]) return m_additionalModels[n][binNo];
+
+    std::cerr << "getAdditionalModel(" << n << ", " << binNo << "): creating" << std::endl;
+
+    SparseTimeValueModel *baseModel = getConformingOutput<SparseTimeValueModel>(n);
+    if (!baseModel) return 0;
+
+    std::cerr << "getAdditionalModel(" << n << ", " << binNo << "): (from " << baseModel << ")" << std::endl;
+
+    SparseTimeValueModel *additional =
+        new SparseTimeValueModel(baseModel->getSampleRate(),
+                                 baseModel->getResolution(),
+                                 baseModel->getValueMinimum(),
+                                 baseModel->getValueMaximum(),
+                                 false);
+
+    additional->setScaleUnits(baseModel->getScaleUnits());
+    additional->setRDFTypeURI(baseModel->getRDFTypeURI());
+
+    m_additionalModels[n][binNo] = additional;
+    return additional;
 }
 
 DenseTimeValueModel *
@@ -410,65 +569,68 @@
     DenseTimeValueModel *input = getConformingInput();
     if (!input) return;
 
-    if (!m_output) return;
+    if (m_outputs.empty()) return;
+
+    Transform primaryTransform = m_transforms[0];
 
     while (!input->isReady() && !m_abandoned) {
-        SVDEBUG << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << endl;
+        cerr << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << endl;
         usleep(500000);
     }
     if (m_abandoned) return;
 
-    size_t sampleRate = input->getSampleRate();
+    sv_samplerate_t sampleRate = input->getSampleRate();
 
-    size_t channelCount = input->getChannelCount();
-    if (m_plugin->getMaxChannelCount() < channelCount) {
+    int channelCount = input->getChannelCount();
+    if ((int)m_plugin->getMaxChannelCount() < channelCount) {
 	channelCount = 1;
     }
 
     float **buffers = new float*[channelCount];
-    for (size_t ch = 0; ch < channelCount; ++ch) {
-	buffers[ch] = new float[m_transform.getBlockSize() + 2];
+    for (int ch = 0; ch < channelCount; ++ch) {
+	buffers[ch] = new float[primaryTransform.getBlockSize() + 2];
     }
 
-    size_t stepSize = m_transform.getStepSize();
-    size_t blockSize = m_transform.getBlockSize();
+    int stepSize = primaryTransform.getStepSize();
+    int blockSize = primaryTransform.getBlockSize();
 
     bool frequencyDomain = (m_plugin->getInputDomain() ==
                             Vamp::Plugin::FrequencyDomain);
     std::vector<FFTModel *> fftModels;
 
     if (frequencyDomain) {
-        for (size_t ch = 0; ch < channelCount; ++ch) {
+        for (int ch = 0; ch < channelCount; ++ch) {
             FFTModel *model = new FFTModel
                                   (getConformingInput(),
                                    channelCount == 1 ? m_input.getChannel() : ch,
-                                   m_transform.getWindowType(),
+                                   primaryTransform.getWindowType(),
                                    blockSize,
                                    stepSize,
-                                   blockSize,
-                                   false,
-                                   StorageAdviser::PrecisionCritical);
-            if (!model->isOK()) {
+                                   blockSize);
+            if (!model->isOK() || model->getError() != "") {
+                QString err = model->getError();
                 delete model;
-                setCompletion(100);
+                for (int j = 0; j < (int)m_outputNos.size(); ++j) {
+                    setCompletion(j, 100);
+                }
                 //!!! need a better way to handle this -- previously we were using a QMessageBox but that isn't an appropriate thing to do here either
-                throw AllocationFailed("Failed to create the FFT model for this feature extraction model transformer");
+                throw AllocationFailed("Failed to create the FFT model for this feature extraction model transformer: error is: " + err);
             }
-            model->resume();
             fftModels.push_back(model);
+            cerr << "created model for channel " << ch << endl;
         }
     }
 
-    long startFrame = m_input.getModel()->getStartFrame();
-    long   endFrame = m_input.getModel()->getEndFrame();
+    sv_frame_t startFrame = m_input.getModel()->getStartFrame();
+    sv_frame_t endFrame = m_input.getModel()->getEndFrame();
 
-    RealTime contextStartRT = m_transform.getStartTime();
-    RealTime contextDurationRT = m_transform.getDuration();
+    RealTime contextStartRT = primaryTransform.getStartTime();
+    RealTime contextDurationRT = primaryTransform.getDuration();
 
-    long contextStart =
+    sv_frame_t contextStart =
         RealTime::realTime2Frame(contextStartRT, sampleRate);
 
-    long contextDuration =
+    sv_frame_t contextDuration =
         RealTime::realTime2Frame(contextDurationRT, sampleRate);
 
     if (contextStart == 0 || contextStart < startFrame) {
@@ -482,11 +644,13 @@
         contextDuration = endFrame - contextStart;
     }
 
-    long blockFrame = contextStart;
+    sv_frame_t blockFrame = contextStart;
 
     long prevCompletion = 0;
 
-    setCompletion(0);
+    for (int j = 0; j < (int)m_outputNos.size(); ++j) {
+        setCompletion(j, 0);
+    }
 
     float *reals = 0;
     float *imaginaries = 0;
@@ -511,25 +675,32 @@
 //		  << blockFrame << ", endFrame " << endFrame << ", blockSize "
 //                  << blockSize << endl;
 
-	long completion =
-	    (((blockFrame - contextStart) / stepSize) * 99) /
-	    (contextDuration / stepSize + 1);
+	int completion = int
+	    ((((blockFrame - contextStart) / stepSize) * 99) /
+             (contextDuration / stepSize + 1));
 
 	// channelCount is either m_input.getModel()->channelCount or 1
 
         if (frequencyDomain) {
-            for (size_t ch = 0; ch < channelCount; ++ch) {
-                int column = (blockFrame - startFrame) / stepSize;
-                fftModels[ch]->getValuesAt(column, reals, imaginaries);
-                for (size_t i = 0; i <= blockSize/2; ++i) {
-                    buffers[ch][i*2] = reals[i];
-                    buffers[ch][i*2+1] = imaginaries[i];
-                }
+            for (int ch = 0; ch < channelCount; ++ch) {
+                int column = int((blockFrame - startFrame) / stepSize);
+                if (fftModels[ch]->getValuesAt(column, reals, imaginaries)) {
+                    for (int i = 0; i <= blockSize/2; ++i) {
+                        buffers[ch][i*2] = reals[i];
+                        buffers[ch][i*2+1] = imaginaries[i];
+                    }
+                } else {
+                    for (int i = 0; i <= blockSize/2; ++i) {
+                        buffers[ch][i*2] = 0.f;
+                        buffers[ch][i*2+1] = 0.f;
+                    }
+                }                    
                 error = fftModels[ch]->getError();
                 if (error != "") {
                     cerr << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << endl;
                     m_abandoned = true;
                     m_message = error;
+                    break;
                 }
             }
         } else {
@@ -539,17 +710,21 @@
         if (m_abandoned) break;
 
 	Vamp::Plugin::FeatureSet features = m_plugin->process
-	    (buffers, Vamp::RealTime::frame2RealTime(blockFrame, sampleRate));
+	    (buffers, RealTime::frame2RealTime(blockFrame, sampleRate).toVampRealTime());
 
         if (m_abandoned) break;
 
-	for (size_t fi = 0; fi < features[m_outputNo].size(); ++fi) {
-	    Vamp::Plugin::Feature feature = features[m_outputNo][fi];
-	    addFeature(blockFrame, feature);
-	}
+        for (int j = 0; j < (int)m_outputNos.size(); ++j) {
+            for (int fi = 0; fi < (int)features[m_outputNos[j]].size(); ++fi) {
+                Vamp::Plugin::Feature feature = features[m_outputNos[j]][fi];
+                addFeature(j, blockFrame, feature);
+            }
+        }
 
 	if (blockFrame == contextStart || completion > prevCompletion) {
-	    setCompletion(completion);
+            for (int j = 0; j < (int)m_outputNos.size(); ++j) {
+                setCompletion(j, completion);
+            }
 	    prevCompletion = completion;
 	}
 
@@ -559,33 +734,43 @@
     if (!m_abandoned) {
         Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures();
 
-        for (size_t fi = 0; fi < features[m_outputNo].size(); ++fi) {
-            Vamp::Plugin::Feature feature = features[m_outputNo][fi];
-            addFeature(blockFrame, feature);
+        for (int j = 0; j < (int)m_outputNos.size(); ++j) {
+            for (int fi = 0; fi < (int)features[m_outputNos[j]].size(); ++fi) {
+                Vamp::Plugin::Feature feature = features[m_outputNos[j]][fi];
+                addFeature(j, blockFrame, feature);
+            }
         }
     }
 
-    setCompletion(100);
+    for (int j = 0; j < (int)m_outputNos.size(); ++j) {
+        setCompletion(j, 100);
+    }
 
     if (frequencyDomain) {
-        for (size_t ch = 0; ch < channelCount; ++ch) {
+        for (int ch = 0; ch < channelCount; ++ch) {
             delete fftModels[ch];
         }
         delete[] reals;
         delete[] imaginaries;
     }
+
+    for (int ch = 0; ch < channelCount; ++ch) {
+        delete[] buffers[ch];
+    }
+    delete[] buffers;
 }
 
 void
 FeatureExtractionModelTransformer::getFrames(int channelCount,
-                                             long startFrame, long size,
+                                             sv_frame_t startFrame,
+                                             sv_frame_t size,
                                              float **buffers)
 {
-    long offset = 0;
+    sv_frame_t offset = 0;
 
     if (startFrame < 0) {
         for (int c = 0; c < channelCount; ++c) {
-            for (int i = 0; i < size && startFrame + i < 0; ++i) {
+            for (sv_frame_t i = 0; i < size && startFrame + i < 0; ++i) {
                 buffers[c][i] = 0.0f;
             }
         }
@@ -598,34 +783,32 @@
     DenseTimeValueModel *input = getConformingInput();
     if (!input) return;
     
-    long got = 0;
+    sv_frame_t got = 0;
 
     if (channelCount == 1) {
 
-        got = input->getData(m_input.getChannel(), startFrame, size,
-                             buffers[0] + offset);
+        auto data = input->getData(m_input.getChannel(), startFrame, size);
+        got = data.size();
+
+        copy(data.begin(), data.end(), buffers[0] + offset);
 
         if (m_input.getChannel() == -1 && input->getChannelCount() > 1) {
             // use mean instead of sum, as plugin input
             float cc = float(input->getChannelCount());
-            for (long i = 0; i < size; ++i) {
+            for (sv_frame_t i = 0; i < got; ++i) {
                 buffers[0][i + offset] /= cc;
             }
         }
 
     } else {
 
-        float **writebuf = buffers;
-        if (offset > 0) {
-            writebuf = new float *[channelCount];
-            for (int i = 0; i < channelCount; ++i) {
-                writebuf[i] = buffers[i] + offset;
+        auto data = input->getMultiChannelData(0, channelCount-1, startFrame, size);
+        if (!data.empty()) {
+            got = data[0].size();
+            for (int c = 0; in_range_for(data, c); ++c) {
+                copy(data[c].begin(), data[c].end(), buffers[c] + offset);
             }
         }
-
-        got = input->getData(0, channelCount-1, startFrame, size, writebuf);
-
-        if (writebuf != buffers) delete[] writebuf;
     }
 
     while (got < size) {
@@ -637,10 +820,11 @@
 }
 
 void
-FeatureExtractionModelTransformer::addFeature(size_t blockFrame,
+FeatureExtractionModelTransformer::addFeature(int n,
+                                              sv_frame_t blockFrame,
                                               const Vamp::Plugin::Feature &feature)
 {
-    size_t inputRate = m_input.getModel()->getSampleRate();
+    sv_samplerate_t inputRate = m_input.getModel()->getSampleRate();
 
 //    cerr << "FeatureExtractionModelTransformer::addFeature: blockFrame = "
 //              << blockFrame << ", hasTimestamp = " << feature.hasTimestamp
@@ -648,14 +832,9 @@
 //              << feature.hasDuration << ", duration = " << feature.duration
 //              << endl;
 
-    int binCount = 1;
-    if (m_descriptor->hasFixedBinCount) {
-	binCount = m_descriptor->binCount;
-    }
+    sv_frame_t frame = blockFrame;
 
-    int frame = blockFrame;
-
-    if (m_descriptor->sampleType ==
+    if (m_descriptors[n]->sampleType ==
 	Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
 
 	if (!feature.hasTimestamp) {
@@ -665,28 +844,32 @@
 		<< endl;
 	    return;
 	} else {
-	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp, inputRate);
+	    frame = RealTime::realTime2Frame(feature.timestamp, inputRate);
 	}
 
-    } else if (m_descriptor->sampleType ==
+//        cerr << "variable sample rate: timestamp = " << feature.timestamp
+//             << " at input rate " << inputRate << " -> " << frame << endl;
+        
+    } else if (m_descriptors[n]->sampleType ==
 	       Vamp::Plugin::OutputDescriptor::FixedSampleRate) {
 
+        sv_samplerate_t rate = m_descriptors[n]->sampleRate;
+        if (rate <= 0.0) {
+            rate = inputRate;
+        }
+        
         if (!feature.hasTimestamp) {
-            ++m_fixedRateFeatureNo;
+            ++m_fixedRateFeatureNos[n];
         } else {
             RealTime ts(feature.timestamp.sec, feature.timestamp.nsec);
-            m_fixedRateFeatureNo =
-                lrint(ts.toDouble() * m_descriptor->sampleRate);
+            m_fixedRateFeatureNos[n] = (int)lrint(ts.toDouble() * rate);
         }
 
-//        cerr << "m_fixedRateFeatureNo = " << m_fixedRateFeatureNo 
-//             << ", m_descriptor->sampleRate = " << m_descriptor->sampleRate
+//        cerr << "m_fixedRateFeatureNo = " << m_fixedRateFeatureNos[n]
+//             << ", m_descriptor->sampleRate = " << m_descriptors[n]->sampleRate
 //             << ", inputRate = " << inputRate
 //             << " giving frame = ";
- 
-        frame = lrintf((m_fixedRateFeatureNo / m_descriptor->sampleRate)
-                       * int(inputRate));
-
+        frame = lrint((double(m_fixedRateFeatureNos[n]) / rate) * inputRate);
 //        cerr << frame << endl;
     }
 
@@ -705,22 +888,22 @@
     // to, we instead test what sort of model the constructor decided
     // to create.
 
-    if (isOutput<SparseOneDimensionalModel>()) {
+    if (isOutput<SparseOneDimensionalModel>(n)) {
 
         SparseOneDimensionalModel *model =
-            getConformingOutput<SparseOneDimensionalModel>();
+            getConformingOutput<SparseOneDimensionalModel>(n);
 	if (!model) return;
 
         model->addPoint(SparseOneDimensionalModel::Point
                        (frame, feature.label.c_str()));
 	
-    } else if (isOutput<SparseTimeValueModel>()) {
+    } else if (isOutput<SparseTimeValueModel>(n)) {
 
 	SparseTimeValueModel *model =
-            getConformingOutput<SparseTimeValueModel>();
+            getConformingOutput<SparseTimeValueModel>(n);
 	if (!model) return;
 
-        for (int i = 0; i < feature.values.size(); ++i) {
+        for (int i = 0; i < (int)feature.values.size(); ++i) {
 
             float value = feature.values[i];
 
@@ -729,49 +912,77 @@
                 label = QString("[%1] %2").arg(i+1).arg(label);
             }
 
-            model->addPoint(SparseTimeValueModel::Point(frame, value, label));
+            SparseTimeValueModel *targetModel = model;
+
+            if (m_needAdditionalModels[n] && i > 0) {
+                targetModel = getAdditionalModel(n, i);
+                if (!targetModel) targetModel = model;
+//                std::cerr << "adding point to model " << targetModel
+//                          << " for output " << n << " bin " << i << std::endl;
+            }
+
+            targetModel->addPoint
+                (SparseTimeValueModel::Point(frame, value, label));
         }
 
-    } else if (isOutput<NoteModel>() || isOutput<RegionModel>()) {
+    } else if (isOutput<FlexiNoteModel>(n) || isOutput<NoteModel>(n) || isOutput<RegionModel>(n)) { //GF: Added Note Model
 
         int index = 0;
 
         float value = 0.0;
-        if (feature.values.size() > index) {
+        if ((int)feature.values.size() > index) {
             value = feature.values[index++];
         }
 
-        float duration = 1;
+        sv_frame_t duration = 1;
         if (feature.hasDuration) {
-            duration = Vamp::RealTime::realTime2Frame(feature.duration, inputRate);
+            duration = RealTime::realTime2Frame(feature.duration, inputRate);
         } else {
-            if (feature.values.size() > index) {
-                duration = feature.values[index++];
+            if (in_range_for(feature.values, index)) {
+                duration = lrintf(feature.values[index++]);
             }
         }
-        
-        if (isOutput<NoteModel>()) {
+
+        if (isOutput<FlexiNoteModel>(n)) { // GF: added for flexi note model
 
             float velocity = 100;
-            if (feature.values.size() > index) {
+            if ((int)feature.values.size() > index) {
                 velocity = feature.values[index++];
             }
             if (velocity < 0) velocity = 127;
             if (velocity > 127) velocity = 127;
 
-            NoteModel *model = getConformingOutput<NoteModel>();
+            FlexiNoteModel *model = getConformingOutput<FlexiNoteModel>(n);
+            if (!model) return;
+            model->addPoint(FlexiNoteModel::Point(frame,
+                                                  value, // value is pitch
+                                                  duration,
+                                                  velocity / 127.f,
+                                                  feature.label.c_str()));
+			// GF: end -- added for flexi note model
+        } else  if (isOutput<NoteModel>(n)) {
+
+            float velocity = 100;
+            if ((int)feature.values.size() > index) {
+                velocity = feature.values[index++];
+            }
+            if (velocity < 0) velocity = 127;
+            if (velocity > 127) velocity = 127;
+
+            NoteModel *model = getConformingOutput<NoteModel>(n);
             if (!model) return;
             model->addPoint(NoteModel::Point(frame, value, // value is pitch
-                                             lrintf(duration),
+                                             duration,
                                              velocity / 127.f,
                                              feature.label.c_str()));
         } else {
-            RegionModel *model = getConformingOutput<RegionModel>();
+
+            RegionModel *model = getConformingOutput<RegionModel>(n);
             if (!model) return;
 
             if (feature.hasDuration && !feature.values.empty()) {
 
-                for (int i = 0; i < feature.values.size(); ++i) {
+                for (int i = 0; i < (int)feature.values.size(); ++i) {
 
                     float value = feature.values[i];
 
@@ -780,28 +991,36 @@
                         label = QString("[%1] %2").arg(i+1).arg(label);
                     }
 
-                    model->addPoint(RegionModel::Point(frame, value,
-                                                       lrintf(duration),
+                    model->addPoint(RegionModel::Point(frame,
+                                                       value,
+                                                       duration,
                                                        label));
                 }
             } else {
             
-                model->addPoint(RegionModel::Point(frame, value,
-                                                   lrintf(duration),
+                model->addPoint(RegionModel::Point(frame,
+                                                   value,
+                                                   duration,
                                                    feature.label.c_str()));
             }
         }
 	
-    } else if (isOutput<EditableDenseThreeDimensionalModel>()) {
+    } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) {
 	
-	DenseThreeDimensionalModel::Column values =
-            DenseThreeDimensionalModel::Column::fromStdVector(feature.values);
+	DenseThreeDimensionalModel::Column values = feature.values;
 	
 	EditableDenseThreeDimensionalModel *model =
-            getConformingOutput<EditableDenseThreeDimensionalModel>();
+            getConformingOutput<EditableDenseThreeDimensionalModel>(n);
 	if (!model) return;
 
-	model->setColumn(frame / model->getResolution(), values);
+//        cerr << "(note: model resolution = " << model->getResolution() << ")"
+//             << endl;
+
+        if (!feature.hasTimestamp && m_fixedRateFeatureNos[n] >= 0) {
+            model->setColumn(m_fixedRateFeatureNos[n], values);
+        } else {
+            model->setColumn(int(frame / model->getResolution()), values);
+        }
 
     } else {
         SVDEBUG << "FeatureExtractionModelTransformer::addFeature: Unknown output model type!" << endl;
@@ -809,47 +1028,54 @@
 }
 
 void
-FeatureExtractionModelTransformer::setCompletion(int completion)
+FeatureExtractionModelTransformer::setCompletion(int n, int completion)
 {
-    int binCount = 1;
-    if (m_descriptor->hasFixedBinCount) {
-	binCount = m_descriptor->binCount;
-    }
-
 //    SVDEBUG << "FeatureExtractionModelTransformer::setCompletion("
 //              << completion << ")" << endl;
 
-    if (isOutput<SparseOneDimensionalModel>()) {
+    if (isOutput<SparseOneDimensionalModel>(n)) {
 
 	SparseOneDimensionalModel *model =
-            getConformingOutput<SparseOneDimensionalModel>();
+            getConformingOutput<SparseOneDimensionalModel>(n);
 	if (!model) return;
+        if (model->isAbandoning()) abandon();
 	model->setCompletion(completion, true);
 
-    } else if (isOutput<SparseTimeValueModel>()) {
+    } else if (isOutput<SparseTimeValueModel>(n)) {
 
 	SparseTimeValueModel *model =
-            getConformingOutput<SparseTimeValueModel>();
+            getConformingOutput<SparseTimeValueModel>(n);
 	if (!model) return;
+        if (model->isAbandoning()) abandon();
 	model->setCompletion(completion, true);
 
-    } else if (isOutput<NoteModel>()) {
+    } else if (isOutput<NoteModel>(n)) {
 
-	NoteModel *model = getConformingOutput<NoteModel>();
+	NoteModel *model = getConformingOutput<NoteModel>(n);
 	if (!model) return;
+        if (model->isAbandoning()) abandon();
+	model->setCompletion(completion, true);
+	
+    } else if (isOutput<FlexiNoteModel>(n)) {
+
+	FlexiNoteModel *model = getConformingOutput<FlexiNoteModel>(n);
+	if (!model) return;
+        if (model->isAbandoning()) abandon();
 	model->setCompletion(completion, true);
 
-    } else if (isOutput<RegionModel>()) {
+    } else if (isOutput<RegionModel>(n)) {
 
-	RegionModel *model = getConformingOutput<RegionModel>();
+	RegionModel *model = getConformingOutput<RegionModel>(n);
 	if (!model) return;
+        if (model->isAbandoning()) abandon();
 	model->setCompletion(completion, true);
 
-    } else if (isOutput<EditableDenseThreeDimensionalModel>()) {
+    } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) {
 
 	EditableDenseThreeDimensionalModel *model =
-            getConformingOutput<EditableDenseThreeDimensionalModel>();
+            getConformingOutput<EditableDenseThreeDimensionalModel>(n);
 	if (!model) return;
+        if (model->isAbandoning()) abandon();
 	model->setCompletion(completion, true); //!!!m_context.updates);
     }
 }
--- a/transform/FeatureExtractionModelTransformer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/FeatureExtractionModelTransformer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _FEATURE_EXTRACTION_PLUGIN_TRANSFORMER_H_
-#define _FEATURE_EXTRACTION_PLUGIN_TRANSFORMER_H_
+#ifndef _FEATURE_EXTRACTION_MODEL_TRANSFORMER_H_
+#define _FEATURE_EXTRACTION_MODEL_TRANSFORMER_H_
 
 #include "ModelTransformer.h"
 
@@ -23,8 +23,10 @@
 #include <vamp-hostsdk/Plugin.h>
 
 #include <iostream>
+#include <map>
 
 class DenseTimeValueModel;
+class SparseTimeValueModel;
 
 class FeatureExtractionModelTransformer : public ModelTransformer
 {
@@ -33,40 +35,64 @@
 public:
     FeatureExtractionModelTransformer(Input input,
                                       const Transform &transform);
+
+    // Obtain outputs for a set of transforms that all use the same
+    // plugin and input (but with different outputs). i.e. run the
+    // plugin once only and collect more than one output from it.
+    FeatureExtractionModelTransformer(Input input,
+                                      const Transforms &relatedTransforms);
+
     virtual ~FeatureExtractionModelTransformer();
 
+    // ModelTransformer method, retrieve the additional models
+    Models getAdditionalOutputModels();
+    bool willHaveAdditionalOutputModels();
+
 protected:
+    bool initialise();
+
     virtual void run();
 
     Vamp::Plugin *m_plugin;
-    Vamp::Plugin::OutputDescriptor *m_descriptor;
-    int m_fixedRateFeatureNo; // to assign times to FixedSampleRate features
-    int m_outputNo;
+    std::vector<Vamp::Plugin::OutputDescriptor *> m_descriptors; // per transform
+    std::vector<int> m_fixedRateFeatureNos; // to assign times to FixedSampleRate features
+    std::vector<int> m_outputNos; // list of plugin output indexes required for this group of transforms
 
-    void createOutputModel();
+    void createOutputModels(int n);
 
-    void addFeature(size_t blockFrame,
+    std::map<int, bool> m_needAdditionalModels; // transformNo -> necessity
+    typedef std::map<int, std::map<int, SparseTimeValueModel *> > AdditionalModelMap;
+    AdditionalModelMap m_additionalModels;
+    SparseTimeValueModel *getAdditionalModel(int transformNo, int binNo);
+
+    void addFeature(int n,
+                    sv_frame_t blockFrame,
 		    const Vamp::Plugin::Feature &feature);
 
-    void setCompletion(int);
+    void setCompletion(int, int);
 
-    void getFrames(int channelCount, long startFrame, long size,
+    void getFrames(int channelCount, sv_frame_t startFrame, sv_frame_t size,
                    float **buffer);
 
     // just casts
 
     DenseTimeValueModel *getConformingInput();
 
-    template <typename ModelClass> bool isOutput() {
-        return dynamic_cast<ModelClass *>(m_output) != 0;
+    template <typename ModelClass> bool isOutput(int n) {
+        return dynamic_cast<ModelClass *>(m_outputs[n]) != 0;
     }
 
-    template <typename ModelClass> ModelClass *getConformingOutput() {
-	ModelClass *mc = dynamic_cast<ModelClass *>(m_output);
-	if (!mc) {
-	    std::cerr << "FeatureExtractionModelTransformer::getOutput: Output model not conformable" << std::endl;
-	}
-	return mc;
+    template <typename ModelClass> ModelClass *getConformingOutput(int n) {
+        if ((int)m_outputs.size() > n) {
+            ModelClass *mc = dynamic_cast<ModelClass *>(m_outputs[n]);
+            if (!mc) {
+                std::cerr << "FeatureExtractionModelTransformer::getOutput: Output model not conformable" << std::endl;
+            }
+            return mc;
+        } else {
+            std::cerr << "FeatureExtractionModelTransformer::getOutput: No such output number " << n << std::endl;
+            return 0;
+        }
     }
 };
 
--- a/transform/FeatureWriter.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/FeatureWriter.h	Wed Apr 20 12:06:28 2016 +0100
@@ -39,25 +39,30 @@
 public:
     virtual ~FeatureWriter() { }
 
+    virtual string getDescription() const = 0;
+
     struct Parameter { // parameter of the writer, not the plugin
+        Parameter() : hasArg(false), mandatory(false) { }
         string name;
         string description;
         bool hasArg;
+        bool mandatory;
     };
     typedef vector<Parameter> ParameterList;
     virtual ParameterList getSupportedParameters() const {
         return ParameterList();
     }
 
-    virtual void setParameters(map<string, string> &params) {
+    virtual void setParameters(map<string, string> &) {
         return;
     }
 
     struct TrackMetadata {
         QString title;
         QString maker;
+        RealTime duration;
     };
-    virtual void setTrackMetadata(QString trackid, TrackMetadata metadata) { }
+    virtual void setTrackMetadata(QString /* trackid */, TrackMetadata) { }
 
     class FailedToOpenOutputStream : virtual public std::exception
     {
@@ -77,8 +82,15 @@
         QString m_transformId;
     };
 
+    /**
+     * Notify the writer that we are about to start extraction for
+     * input file N of M (where N is 1..M). May be useful when writing
+     * multiple outputs into a single file where some syntactic
+     * element is needed to connect them.
+     */
+    virtual void setNofM(int /* N */, int /* M */) { }
+
     // may throw FailedToOpenFile or other exceptions
-
     virtual void write(QString trackid,
                        const Transform &transform,
                        const Vamp::Plugin::OutputDescriptor &output,
@@ -93,7 +105,7 @@
      * really an optimisation to ensure that easy-to-recognise failure
      * cases fail early.
      */
-    virtual void testOutputFile(QString trackId, TransformId transformId) { }
+    virtual void testOutputFile(QString /* trackId */, TransformId) { }
 
     virtual void flush() { } // whatever the last stream was
 
--- a/transform/FileFeatureWriter.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/FileFeatureWriter.cpp	Wed Apr 20 12:06:28 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;
         }
     }
 }
@@ -75,14 +75,14 @@
     Parameter p;
 
     p.name = "basedir";
-    p.description = "Base output directory path.  (The default is the same directory as the input file.)";
+    p.description = "Base output directory path. (The default is the same directory as the input file.) The directory must exist already.";
     p.hasArg = true;
     pl.push_back(p);
 
     if (m_support & SupportOneFilePerTrackTransform &&
         m_support & SupportOneFilePerTrack) {
         p.name = "many-files";
-        p.description = "Create a separate output file for every combination of input file and transform.  The output file names will be based on the input file names.  (The default is to create one output file per input audio file, and write all transform results for that input into it.)";
+        p.description = "Create a separate output file for every combination of input file and transform. The output file names will be based on the input file names. (The default is to create one output file per input audio file, and write all transform results for that input into it.)";
         p.hasArg = false;
         pl.push_back(p);
     }
@@ -91,13 +91,15 @@
         if (m_support & ~SupportOneFileTotal) { // not only option
             p.name = "one-file";
             if (m_support & SupportOneFilePerTrack) {
-                p.description = "Write all transform results for all input files into the single named output file.  (The default is to create one output file per input audio file, and write all transform results for that input into it.)";
+                p.description = "Write all transform results for all input files into the single named output file. (The default is to create one output file per input audio file, and write all transform results for that input into it.)";
             } else {
-                p.description = "Write all transform results for all input files into the single named output file.  (The default is to create a separate output file for each combination of input audio file and transform.)";
+                p.description = "Write all transform results for all input files into the single named output file. (The default is to create a separate output file for each combination of input audio file and transform.)";
             }                
             p.hasArg = true;
             pl.push_back(p);
         }
+    }
+    if (m_support & SupportStdOut) {
         p.name = "stdout";
         p.description = "Write all transform results directly to standard output.";
         p.hasArg = false;
@@ -128,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;
                 }
@@ -142,16 +144,16 @@
                     // 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();
 //                    }
                 }
             }
         } else if (i->first == "stdout") {
-            if (m_support & SupportOneFileTotal) {
+            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;
                 }
@@ -165,19 +167,21 @@
 }
 
 QString
-FileFeatureWriter::getOutputFilename(QString trackId,
-                                     TransformId transformId)
+FileFeatureWriter::createOutputFilename(QString trackId,
+                                        TransformId transformId)
 {
     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;
     }
 
-    if (m_stdout) return "";
+    if (m_stdout) {
+        return "";
+    }
     
     QUrl url(trackId, QUrl::StrictMode);
     QString scheme = url.scheme().toLower();
@@ -216,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 "";
     }
     
@@ -235,29 +239,49 @@
     // leave it to it
     if (m_stdout || m_singleFileName != "") return;
 
-    QString filename = getOutputFilename(trackId, transformId);
+    QString filename = createOutputFilename(trackId, transformId);
     if (filename == "") {
         throw FailedToOpenOutputStream(trackId, transformId);
     }
 }
 
+FileFeatureWriter::TrackTransformPair
+FileFeatureWriter::getFilenameKey(QString trackId,
+                                  TransformId transformId)
+{
+    TrackTransformPair key;
+
+    if (m_singleFileName != "") {
+        key = TrackTransformPair("", "");
+    } else if (m_manyFiles) {
+        key = TrackTransformPair(trackId, transformId);
+    } else {
+        key = TrackTransformPair(trackId, "");
+    }
+
+    return key;
+}    
+
+QString
+FileFeatureWriter::getOutputFilename(QString trackId,
+                                     TransformId transformId)
+{
+    TrackTransformPair key = getFilenameKey(trackId, transformId);
+    if (m_filenames.find(key) == m_filenames.end()) {
+        m_filenames[key] = createOutputFilename(trackId, transformId);
+    }
+    return m_filenames[key];
+}
+
 QFile *
 FileFeatureWriter::getOutputFile(QString trackId,
                                  TransformId transformId)
 {
-    pair<QString, TransformId> key;
-
-    if (m_singleFileName != "") {
-        key = pair<QString, TransformId>("", "");
-    } else if (m_manyFiles) {
-        key = pair<QString, TransformId>(trackId, transformId);
-    } else {
-        key = pair<QString, TransformId>(trackId, "");
-    }
+    TrackTransformPair key = getFilenameKey(trackId, transformId);
 
     if (m_files.find(key) == m_files.end()) {
 
-        QString filename = getOutputFilename(trackId, transformId);
+        QString filename = createOutputFilename(trackId, transformId);
 
         if (filename == "") { // stdout or failure
             return 0;
@@ -291,7 +315,8 @@
 
 
 QTextStream *FileFeatureWriter::getOutputStream(QString trackId,
-                                               TransformId transformId)
+                                                TransformId transformId,
+                                                QTextCodec *codec)
 {
     QFile *file = getOutputFile(trackId, transformId);
     if (!file && !m_stdout) {
@@ -304,6 +329,7 @@
         } else {
             m_streams[file] = new QTextStream(file);
         }
+        m_streams[file]->setCodec(codec);
     }
 
     QTextStream *stream = m_streams[file];
--- a/transform/FileFeatureWriter.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/FileFeatureWriter.h	Wed Apr 20 12:06:28 2016 +0100
@@ -32,6 +32,7 @@
 using std::pair;
 
 class QTextStream;
+class QTextCodec;
 class QFile;
 
 class FileFeatureWriter : public FeatureWriter
@@ -50,24 +51,38 @@
     enum FileWriteSupport {
         SupportOneFilePerTrackTransform = 1,
         SupportOneFilePerTrack = 2,
-        SupportOneFileTotal = 4
+        SupportOneFileTotal = 4,
+        SupportStdOut = 8
     };
 
     FileFeatureWriter(int support, QString extension);
-    QTextStream *getOutputStream(QString, TransformId);
+    QTextStream *getOutputStream(QString, TransformId, QTextCodec *);
 
     typedef pair<QString, TransformId> TrackTransformPair;
+    typedef map<TrackTransformPair, QString> FileNameMap;
     typedef map<TrackTransformPair, QFile *> FileMap;
     typedef map<QFile *, QTextStream *> FileStreamMap;
     FileMap m_files;
+    FileNameMap m_filenames;
     FileStreamMap m_streams;
     QTextStream *m_prevstream;
 
+    TrackTransformPair getFilenameKey(QString, TransformId);
+
+    // Come up with a suitable output filename for the given track ID - 
+    // transform ID combo. Fail if it already exists, etc.
+    QString createOutputFilename(QString, TransformId);
+
+    // Look up and return the output filename for the given track ID -
+    // transform ID combo.
     QString getOutputFilename(QString, TransformId);
+
+    // Look up and return the output file handle for the given track
+    // ID - transform ID combo. Return 0 if it could not be opened.
     QFile *getOutputFile(QString, TransformId);
     
     // subclass can implement this to be called before file is opened for append
-    virtual void reviewFileForAppending(QString filename) { }
+    virtual void reviewFileForAppending(QString) { }
 
     int m_support;
     QString m_extension;
--- a/transform/ModelTransformer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/ModelTransformer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -16,10 +16,19 @@
 #include "ModelTransformer.h"
 
 ModelTransformer::ModelTransformer(Input input, const Transform &transform) :
-    m_transform(transform),
     m_input(input),
-    m_output(0),
     m_detached(false),
+    m_detachedAdd(false),
+    m_abandoned(false)
+{
+    m_transforms.push_back(transform);
+}
+
+ModelTransformer::ModelTransformer(Input input, const Transforms &transforms) :
+    m_transforms(transforms),
+    m_input(input),
+    m_detached(false),
+    m_detachedAdd(false),
     m_abandoned(false)
 {
 }
@@ -28,6 +37,13 @@
 {
     m_abandoned = true;
     wait();
-    if (!m_detached) delete m_output;
+    if (!m_detached) {
+        Models mine = getOutputModels();
+        foreach (Model *m, mine) delete m;
+    }
+    if (!m_detachedAdd) {
+        Models mine = getAdditionalOutputModels();
+        foreach (Model *m, mine) delete m;
+    }
 }
 
--- a/transform/ModelTransformer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/ModelTransformer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -40,6 +40,8 @@
 public:
     virtual ~ModelTransformer();
 
+    typedef std::vector<Model *> Models;
+
     class Input {
     public:
         Input(Model *m) : m_model(m), m_channel(-1) { }
@@ -66,6 +68,12 @@
     void abandon() { m_abandoned = true; }
 
     /**
+     * Return true if the processing thread is being or has been
+     * abandoned, i.e. if abandon() has been called.
+     */
+    bool isAbandoned() const { return m_abandoned; }
+    
+    /**
      * Return the input model for the transform.
      */
     Model *getInputModel()  { return m_input.getModel(); }
@@ -76,18 +84,47 @@
     int getInputChannel() { return m_input.getChannel(); }
 
     /**
-     * Return the output model created by the transform.  Returns a
-     * null model if the transform could not be initialised; an error
-     * message may be available via getMessage() in this situation.
+     * Return the set of output models created by the transform or
+     * transforms.  Returns an empty list if any transform could not
+     * be initialised; an error message may be available via
+     * getMessage() in this situation.
      */
-    Model *getOutputModel() { return m_output; }
+    Models getOutputModels() { return m_outputs; }
 
     /**
-     * Return the output model, also detaching it from the transformer
-     * so that it will not be deleted when the transformer is.  The
-     * caller takes ownership of the model.
+     * Return the set of output models, also detaching them from the
+     * transformer so that they will not be deleted when the
+     * transformer is.  The caller takes ownership of the models.
      */
-    Model *detachOutputModel() { m_detached = true; return m_output; }
+    Models detachOutputModels() { 
+        m_detached = true; 
+        return getOutputModels(); 
+    }
+
+    /**
+     * Return any additional models that were created during
+     * processing. This might happen if, for example, a transform was
+     * configured to split a multi-bin output into separate single-bin
+     * models as it processed. These should not be queried until after
+     * the transform has completed.
+     */
+    virtual Models getAdditionalOutputModels() { return Models(); }
+
+    /**
+     * Return true if the current transform is one that may produce
+     * additional models (to be retrieved through
+     * getAdditionalOutputModels above).
+     */
+    virtual bool willHaveAdditionalOutputModels() { return false; }
+
+    /**
+     * Return the set of additional models, also detaching them from
+     * the transformer.  The caller takes ownership of the models.
+     */
+    virtual Models detachAdditionalOutputModels() { 
+        m_detachedAdd = true;
+        return getAdditionalOutputModels();
+    }
 
     /**
      * Return a warning or error message.  If getOutputModel returned
@@ -99,11 +136,13 @@
 
 protected:
     ModelTransformer(Input input, const Transform &transform);
+    ModelTransformer(Input input, const Transforms &transforms);
 
-    Transform m_transform;
+    Transforms m_transforms;
     Input m_input; // I don't own the model in this
-    Model *m_output; // I own this, unless...
+    Models m_outputs; // I own this, unless...
     bool m_detached; // ... this is true.
+    bool m_detachedAdd;
     bool m_abandoned;
     QString m_message;
 };
--- a/transform/ModelTransformerFactory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/ModelTransformerFactory.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -35,6 +35,8 @@
 
 #include <QRegExp>
 
+using std::vector;
+
 ModelTransformerFactory *
 ModelTransformerFactory::m_instance = new ModelTransformerFactory;
 
@@ -53,8 +55,8 @@
                                                       const std::vector<Model *> &candidateInputModels,
                                                       Model *defaultInputModel,
                                                       AudioPlaySource *source,
-                                                      size_t startFrame,
-                                                      size_t duration,
+                                                      sv_frame_t startFrame,
+                                                      sv_frame_t duration,
                                                       UserConfigurator *configurator)
 {
     ModelTransformer::Input input(0);
@@ -68,7 +70,7 @@
     QStringList candidateModelNames;
     QString defaultModelName;
     QMap<QString, Model *> modelMap;
-    for (size_t i = 0; i < candidateInputModels.size(); ++i) {
+    for (int i = 0; i < (int)candidateInputModels.size(); ++i) {
         QString modelName = candidateInputModels[i]->objectName();
         QString origModelName = modelName;
         int dupcount = 1;
@@ -97,18 +99,17 @@
 
         Vamp::Plugin *vp =
             FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
-            (id, inputModel->getSampleRate());
+            (id, float(inputModel->getSampleRate()));
 
         plugin = vp;
 
     } else if (RealTimePluginFactory::instanceFor(id)) {
 
         RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id);
-        const RealTimePluginDescriptor *desc = factory->getPluginDescriptor(id);
 
-        size_t sampleRate = inputModel->getSampleRate();
-        size_t blockSize = 1024;
-        size_t channels = 1;
+        sv_samplerate_t sampleRate = inputModel->getSampleRate();
+        int blockSize = 1024;
+        int channels = 1;
         if (source) {
             sampleRate = source->getTargetSampleRate();
             blockSize = source->getTargetBlockSize();
@@ -163,63 +164,85 @@
 }
 
 ModelTransformer *
-ModelTransformerFactory::createTransformer(const Transform &transform,
+ModelTransformerFactory::createTransformer(const Transforms &transforms,
                                            const ModelTransformer::Input &input)
 {
     ModelTransformer *transformer = 0;
 
-    QString id = transform.getPluginIdentifier();
+    QString id = transforms[0].getPluginIdentifier();
 
     if (FeatureExtractionPluginFactory::instanceFor(id)) {
 
         transformer =
-            new FeatureExtractionModelTransformer(input, transform);
+            new FeatureExtractionModelTransformer(input, transforms);
 
     } else if (RealTimePluginFactory::instanceFor(id)) {
 
         transformer =
-            new RealTimeEffectModelTransformer(input, transform);
+            new RealTimeEffectModelTransformer(input, transforms[0]);
 
     } else {
         SVDEBUG << "ModelTransformerFactory::createTransformer: Unknown transform \""
-                  << transform.getIdentifier() << "\"" << endl;
+                  << transforms[0].getIdentifier() << "\"" << endl;
         return transformer;
     }
 
-    if (transformer) transformer->setObjectName(transform.getIdentifier());
+    if (transformer) transformer->setObjectName(transforms[0].getIdentifier());
     return transformer;
 }
 
 Model *
 ModelTransformerFactory::transform(const Transform &transform,
                                    const ModelTransformer::Input &input,
-                                   QString &message)
+                                   QString &message,
+                                   AdditionalModelHandler *handler) 
 {
     SVDEBUG << "ModelTransformerFactory::transform: Constructing transformer with input model " << input.getModel() << endl;
 
-    ModelTransformer *t = createTransformer(transform, input);
-    if (!t) return 0;
+    Transforms transforms;
+    transforms.push_back(transform);
+    vector<Model *> mm = transformMultiple(transforms, input, message, handler);
+    if (mm.empty()) return 0;
+    else return mm[0];
+}
+
+vector<Model *>
+ModelTransformerFactory::transformMultiple(const Transforms &transforms,
+                                           const ModelTransformer::Input &input,
+                                           QString &message,
+                                           AdditionalModelHandler *handler) 
+{
+    SVDEBUG << "ModelTransformerFactory::transformMultiple: Constructing transformer with input model " << input.getModel() << endl;
+    
+    ModelTransformer *t = createTransformer(transforms, input);
+    if (!t) return vector<Model *>();
+
+    if (handler) {
+        m_handlers[t] = handler;
+    }
+
+    m_runningTransformers.insert(t);
 
     connect(t, SIGNAL(finished()), this, SLOT(transformerFinished()));
 
-    m_runningTransformers.insert(t);
+    t->start();
+    vector<Model *> models = t->detachOutputModels();
 
-    t->start();
-    Model *model = t->detachOutputModel();
-
-    if (model) {
+    if (!models.empty()) {
         QString imn = input.getModel()->objectName();
         QString trn =
             TransformFactory::getInstance()->getTransformFriendlyName
-            (transform.getIdentifier());
-        if (imn != "") {
-            if (trn != "") {
-                model->setObjectName(tr("%1: %2").arg(imn).arg(trn));
-            } else {
-                model->setObjectName(imn);
+            (transforms[0].getIdentifier());
+        for (int i = 0; i < (int)models.size(); ++i) {
+            if (imn != "") {
+                if (trn != "") {
+                    models[i]->setObjectName(tr("%1: %2").arg(imn).arg(trn));
+                } else {
+                    models[i]->setObjectName(imn);
+                }
+            } else if (trn != "") {
+                models[i]->setObjectName(trn);
             }
-        } else if (trn != "") {
-            model->setObjectName(trn);
         }
     } else {
         t->wait();
@@ -227,7 +250,7 @@
 
     message = t->getMessage();
 
-    return model;
+    return models;
 }
 
 void
@@ -252,6 +275,22 @@
 
     m_runningTransformers.erase(transformer);
 
+    if (m_handlers.find(transformer) != m_handlers.end()) {
+        if (transformer->willHaveAdditionalOutputModels()) {
+            vector<Model *> mm = transformer->detachAdditionalOutputModels();
+            m_handlers[transformer]->moreModelsAvailable(mm);
+        } else {
+            m_handlers[transformer]->noMoreModelsAvailable();
+        }
+        m_handlers.erase(transformer);
+    }
+
+    if (transformer->isAbandoned()) {
+        if (transformer->getMessage() != "") {
+            emit transformFailed("", transformer->getMessage());
+        }
+    }
+    
     transformer->wait(); // unnecessary but reassuring
     delete transformer;
 }
@@ -266,8 +305,13 @@
 
         ModelTransformer *t = *i;
 
-        if (t->getInputModel() == m || t->getOutputModel() == m) {
+        if (t->getInputModel() == m) {
             affected.insert(t);
+        } else {
+            vector<Model *> mm = t->getOutputModels();
+            for (int i = 0; i < (int)mm.size(); ++i) {
+                if (mm[i] == m) affected.insert(t);
+            }
         }
     }
 
--- a/transform/ModelTransformerFactory.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/ModelTransformerFactory.h	Wed Apr 20 12:06:28 2016 +0100
@@ -18,6 +18,7 @@
 
 #include "Transform.h"
 #include "TransformDescription.h"
+#include "FeatureExtractionModelTransformer.h"
 
 #include "ModelTransformer.h"
 
@@ -26,6 +27,7 @@
 #include <QMap>
 #include <map>
 #include <set>
+#include <vector>
 
 class AudioPlaySource;
 
@@ -45,8 +47,8 @@
                                Vamp::PluginBase *plugin,
                                Model *&inputModel,
                                AudioPlaySource *source,
-                               size_t startFrame,
-                               size_t duration,
+                               sv_frame_t startFrame,
+                               sv_frame_t duration,
                                const QMap<QString, Model *> &modelMap,
                                QStringList candidateModelNames,
                                QString defaultModelName) = 0;
@@ -65,35 +67,94 @@
                                  const std::vector<Model *> &candidateInputModels,
                                  Model *defaultInputModel,
                                  AudioPlaySource *source = 0,
-                                 size_t startFrame = 0,
-                                 size_t duration = 0,
+                                 sv_frame_t startFrame = 0,
+                                 sv_frame_t duration = 0,
                                  UserConfigurator *configurator = 0);
+
+    class AdditionalModelHandler {
+    public:
+        virtual ~AdditionalModelHandler() { }
+
+        // Exactly one of these functions will be called
+        virtual void moreModelsAvailable(std::vector<Model *> models) = 0;
+        virtual void noMoreModelsAvailable() = 0;
+    };
     
     /**
      * Return the output model resulting from applying the named
      * transform to the given input model.  The transform may still be
      * working in the background when the model is returned; check the
-     * output model's isReady completion status for more details.
+     * output model's isReady completion status for more details. To
+     * cancel a background transform, call abandon() on its model.
      *
      * If the transform is unknown or the input model is not an
      * appropriate type for the given transform, or if some other
      * problem occurs, return 0.  Set message if there is any error or
      * warning to report.
      * 
+     * Some transforms may return additional models at the end of
+     * processing. (For example, a transform that splits an output
+     * into multiple one-per-bin models.) If an additionalModelHandler
+     * is provided here, its moreModelsAvailable method will be called
+     * when those models become available, and ownership of those
+     * models will be transferred to the handler. Otherwise (if the
+     * handler is null) any such models will be discarded.
+     *
      * The returned model is owned by the caller and must be deleted
      * when no longer needed.
      */
     Model *transform(const Transform &transform,
                      const ModelTransformer::Input &input,
-                     QString &message);
+                     QString &message,
+                     AdditionalModelHandler *handler = 0);
 
+    /**
+     * Return the multiple output models resulting from applying the
+     * named transforms to the given input model.  The transforms may
+     * differ only in output identifier for the plugin: they must all
+     * use the same plugin, parameters, and programs. The plugin will
+     * be run once only, but more than one output will be harvested
+     * (as appropriate). Models will be returned in the same order as
+     * the transforms were given. The plugin may still be working in
+     * the background when the model is returned; check the output
+     * models' isReady completion statuses for more details. To cancel
+     * a background transform, call abandon() on its model.
+     *
+     * If a transform is unknown or the transforms are insufficiently
+     * closely related or the input model is not an appropriate type
+     * for the given transform, or if some other problem occurs,
+     * return 0.  Set message if there is any error or warning to
+     * report.
+     *
+     * Some transforms may return additional models at the end of
+     * processing. (For example, a transform that splits an output
+     * into multiple one-per-bin models.) If an additionalModelHandler
+     * is provided here, its moreModelsAvailable method will be called
+     * when those models become available, and ownership of those
+     * models will be transferred to the handler. Otherwise (if the
+     * handler is null) any such models will be discarded. Note that
+     * calling abandon() on any one of the models returned by
+     * transformMultiple is sufficient to cancel all background
+     * transform activity associated with these output models.
+     *
+     * The returned models are owned by the caller and must be deleted
+     * when no longer needed.
+     */
+    std::vector<Model *> transformMultiple(const Transforms &transform,
+                                           const ModelTransformer::Input &input,
+                                           QString &message,
+                                           AdditionalModelHandler *handler = 0);
+
+signals:
+    void transformFailed(QString transformName, QString message);
+                                                                               
 protected slots:
     void transformerFinished();
 
     void modelAboutToBeDeleted(Model *);
 
 protected:
-    ModelTransformer *createTransformer(const Transform &transform,
+    ModelTransformer *createTransformer(const Transforms &transforms,
                                         const ModelTransformer::Input &input);
 
     typedef std::map<TransformId, QString> TransformerConfigurationMap;
@@ -102,6 +163,9 @@
     typedef std::set<ModelTransformer *> TransformerSet;
     TransformerSet m_runningTransformers;
 
+    typedef std::map<ModelTransformer *, AdditionalModelHandler *> HandlerMap;
+    HandlerMap m_handlers;
+
     static ModelTransformerFactory *m_instance;
 };
 
--- a/transform/RealTimeEffectModelTransformer.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/RealTimeEffectModelTransformer.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -30,10 +30,16 @@
 #include <iostream>
 
 RealTimeEffectModelTransformer::RealTimeEffectModelTransformer(Input in,
-                                                               const Transform &transform) :
-    ModelTransformer(in, transform),
+                                                               const Transform &t) :
+    ModelTransformer(in, t),
     m_plugin(0)
 {
+    Transform transform(t);
+    if (!transform.getBlockSize()) {
+        transform.setBlockSize(1024);
+        m_transforms[0] = transform;
+    }
+
     m_units = TransformFactory::getInstance()->getTransformUnits
         (transform.getIdentifier());
     m_outputNo =
@@ -41,8 +47,6 @@
 
     QString pluginId = transform.getPluginIdentifier();
 
-    if (!m_transform.getBlockSize()) m_transform.setBlockSize(1024);
-
 //    SVDEBUG << "RealTimeEffectModelTransformer::RealTimeEffectModelTransformer: plugin " << pluginId << ", output " << output << endl;
 
     RealTimePluginFactory *factory =
@@ -59,16 +63,16 @@
 
     m_plugin = factory->instantiatePlugin(pluginId, 0, 0,
                                           input->getSampleRate(),
-                                          m_transform.getBlockSize(),
+                                          transform.getBlockSize(),
                                           input->getChannelCount());
 
     if (!m_plugin) {
 	cerr << "RealTimeEffectModelTransformer: Failed to instantiate plugin \""
-		  << pluginId << "\"" << endl;
+             << pluginId << "\"" << endl;
 	return;
     }
 
-    TransformFactory::getInstance()->setPluginParameters(m_transform, m_plugin);
+    TransformFactory::getInstance()->setPluginParameters(transform, m_plugin);
 
     if (m_outputNo >= 0 &&
         m_outputNo >= int(m_plugin->getControlOutputCount())) {
@@ -78,7 +82,7 @@
 
     if (m_outputNo == -1) {
 
-        size_t outputChannels = m_plugin->getAudioOutputCount();
+        int outputChannels = (int)m_plugin->getAudioOutputCount();
         if (outputChannels > input->getChannelCount()) {
             outputChannels = input->getChannelCount();
         }
@@ -86,16 +90,16 @@
         WritableWaveFileModel *model = new WritableWaveFileModel
             (input->getSampleRate(), outputChannels);
 
-        m_output = model;
+        m_outputs.push_back(model);
 
     } else {
 	
         SparseTimeValueModel *model = new SparseTimeValueModel
-            (input->getSampleRate(), m_transform.getBlockSize(), 0.0, 0.0, false);
+            (input->getSampleRate(), transform.getBlockSize(), 0.0, 0.0, false);
 
         if (m_units != "") model->setScaleUnits(m_units);
 
-        m_output = model;
+        m_outputs.push_back(model);
     }
 }
 
@@ -127,30 +131,32 @@
     }
     if (m_abandoned) return;
 
-    SparseTimeValueModel *stvm = dynamic_cast<SparseTimeValueModel *>(m_output);
-    WritableWaveFileModel *wwfm = dynamic_cast<WritableWaveFileModel *>(m_output);
+    SparseTimeValueModel *stvm = dynamic_cast<SparseTimeValueModel *>(m_outputs[0]);
+    WritableWaveFileModel *wwfm = dynamic_cast<WritableWaveFileModel *>(m_outputs[0]);
     if (!stvm && !wwfm) return;
 
     if (stvm && (m_outputNo >= int(m_plugin->getControlOutputCount()))) return;
 
-    size_t sampleRate = input->getSampleRate();
-    size_t channelCount = input->getChannelCount();
+    sv_samplerate_t sampleRate = input->getSampleRate();
+    int channelCount = input->getChannelCount();
     if (!wwfm && m_input.getChannel() != -1) channelCount = 1;
 
-    long blockSize = m_plugin->getBufferSize();
+    sv_frame_t blockSize = m_plugin->getBufferSize();
 
     float **inbufs = m_plugin->getAudioInputBuffers();
 
-    long startFrame = m_input.getModel()->getStartFrame();
-    long   endFrame = m_input.getModel()->getEndFrame();
+    sv_frame_t startFrame = m_input.getModel()->getStartFrame();
+    sv_frame_t endFrame = m_input.getModel()->getEndFrame();
+
+    Transform transform = m_transforms[0];
     
-    RealTime contextStartRT = m_transform.getStartTime();
-    RealTime contextDurationRT = m_transform.getDuration();
+    RealTime contextStartRT = transform.getStartTime();
+    RealTime contextDurationRT = transform.getDuration();
 
-    long contextStart =
+    sv_frame_t contextStart =
         RealTime::realTime2Frame(contextStartRT, sampleRate);
 
-    long contextDuration =
+    sv_frame_t contextDuration =
         RealTime::realTime2Frame(contextDurationRT, sampleRate);
 
     if (contextStart == 0 || contextStart < startFrame) {
@@ -168,49 +174,58 @@
         wwfm->setStartFrame(contextStart);
     }
 
-    long blockFrame = contextStart;
+    sv_frame_t blockFrame = contextStart;
 
-    long prevCompletion = 0;
+    int prevCompletion = 0;
 
-    long latency = m_plugin->getLatency();
+    sv_frame_t latency = m_plugin->getLatency();
 
     while (blockFrame < contextStart + contextDuration + latency &&
            !m_abandoned) {
 
-	long completion =
-	    (((blockFrame - contextStart) / blockSize) * 99) /
-	    (1 + ((contextDuration) / blockSize));
+	int completion = int
+	    ((((blockFrame - contextStart) / blockSize) * 99) /
+             (1 + ((contextDuration) / blockSize)));
 
-	long got = 0;
+	sv_frame_t got = 0;
 
 	if (channelCount == 1) {
             if (inbufs && inbufs[0]) {
-                got = input->getData
-                    (m_input.getChannel(), blockFrame, blockSize, inbufs[0]);
+                auto data = input->getData
+                    (m_input.getChannel(), blockFrame, blockSize);
+                got = data.size();
+                for (sv_frame_t i = 0; i < got; ++i) {
+                    inbufs[0][i] = data[i];
+                }
                 while (got < blockSize) {
-                    inbufs[0][got++] = 0.0;
+                    inbufs[0][got++] = 0.f;
                 }          
-            }
-            for (size_t ch = 1; ch < m_plugin->getAudioInputCount(); ++ch) {
-                for (long i = 0; i < blockSize; ++i) {
-                    inbufs[ch][i] = inbufs[0][i];
+                for (int ch = 1; ch < (int)m_plugin->getAudioInputCount(); ++ch) {
+                    for (sv_frame_t i = 0; i < blockSize; ++i) {
+                        inbufs[ch][i] = inbufs[0][i];
+                    }
                 }
             }
 	} else {
             if (inbufs && inbufs[0]) {
-                got = input->getData(0, channelCount - 1,
-                                     blockFrame, blockSize,
-                                     inbufs);
+                auto data = input->getMultiChannelData
+                    (0, channelCount - 1, blockFrame, blockSize);
+                if (!data.empty()) got = data[0].size();
+                for (int ch = 0; ch < channelCount; ++ch) {
+                    for (sv_frame_t i = 0; i < got; ++i) {
+                        inbufs[ch][i] = data[ch][i];
+                    }
+                }
                 while (got < blockSize) {
-                    for (size_t ch = 0; ch < channelCount; ++ch) {
+                    for (int ch = 0; ch < channelCount; ++ch) {
                         inbufs[ch][got] = 0.0;
                     }
                     ++got;
                 }
-            }
-            for (size_t ch = channelCount; ch < m_plugin->getAudioInputCount(); ++ch) {
-                for (long i = 0; i < blockSize; ++i) {
-                    inbufs[ch][i] = inbufs[ch % channelCount][i];
+                for (int ch = channelCount; ch < (int)m_plugin->getAudioInputCount(); ++ch) {
+                    for (sv_frame_t i = 0; i < blockSize; ++i) {
+                        inbufs[ch][i] = inbufs[ch % channelCount][i];
+                    }
                 }
             }
 	}
@@ -218,9 +233,9 @@
 /*
         cerr << "Input for plugin: " << m_plugin->getAudioInputCount() << " channels "<< endl;
 
-        for (size_t ch = 0; ch < m_plugin->getAudioInputCount(); ++ch) {
+        for (int ch = 0; ch < m_plugin->getAudioInputCount(); ++ch) {
             cerr << "Input channel " << ch << endl;
-            for (size_t i = 0; i < 100; ++i) {
+            for (int i = 0; i < 100; ++i) {
                 cerr << inbufs[ch][i] << " ";
                 if (isnan(inbufs[ch][i])) {
                     cerr << "\n\nWARNING: NaN in audio input" << endl;
@@ -229,13 +244,13 @@
         }
 */
 
-        m_plugin->run(Vamp::RealTime::frame2RealTime(blockFrame, sampleRate));
+        m_plugin->run(RealTime::frame2RealTime(blockFrame, sampleRate));
 
         if (stvm) {
 
             float value = m_plugin->getControlOutputValue(m_outputNo);
 
-            long pointFrame = blockFrame;
+            sv_frame_t pointFrame = blockFrame;
             if (pointFrame > latency) pointFrame -= latency;
             else pointFrame = 0;
 
@@ -249,15 +264,15 @@
             if (outbufs) {
 
                 if (blockFrame >= latency) {
-                    long writeSize = std::min
+                    sv_frame_t writeSize = std::min
                         (blockSize,
                          contextStart + contextDuration + latency - blockFrame);
                     wwfm->addSamples(outbufs, writeSize);
                 } else if (blockFrame + blockSize >= latency) {
-                    long offset = latency - blockFrame;
-                    long count = blockSize - offset;
+                    sv_frame_t offset = latency - blockFrame;
+                    sv_frame_t count = blockSize - offset;
                     float **tmp = new float *[channelCount];
-                    for (size_t c = 0; c < channelCount; ++c) {
+                    for (int c = 0; c < channelCount; ++c) {
                         tmp[c] = outbufs[c] + offset;
                     }
                     wwfm->addSamples(tmp, count);
@@ -267,8 +282,10 @@
         }
 
 	if (blockFrame == contextStart || completion > prevCompletion) {
+            // This setCompletion is probably misusing the completion
+            // terminology, just as it was for WritableWaveFileModel
 	    if (stvm) stvm->setCompletion(completion);
-	    if (wwfm) wwfm->setCompletion(completion);
+	    if (wwfm) wwfm->setWriteProportion(completion);
 	    prevCompletion = completion;
 	}
         
@@ -278,6 +295,6 @@
     if (m_abandoned) return;
     
     if (stvm) stvm->setCompletion(100);
-    if (wwfm) wwfm->setCompletion(100);
+    if (wwfm) wwfm->writeComplete();
 }
 
--- a/transform/RealTimeEffectModelTransformer.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/RealTimeEffectModelTransformer.h	Wed Apr 20 12:06:28 2016 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _REAL_TIME_PLUGIN_TRANSFORMER_H_
-#define _REAL_TIME_PLUGIN_TRANSFORMER_H_
+#ifndef _REAL_TIME_EFFECT_TRANSFORMER_H_
+#define _REAL_TIME_EFFECT_TRANSFORMER_H_
 
 #include "ModelTransformer.h"
 #include "plugin/RealTimePluginInstance.h"
--- a/transform/Transform.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/Transform.cpp	Wed Apr 20 12:06:28 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;
     }
     
@@ -66,7 +63,7 @@
     QDomNamedNodeMap attrNodes = transformElt.attributes();
     QXmlAttributes attrs;
 
-    for (unsigned int i = 0; i < attrNodes.length(); ++i) {
+    for (int i = 0; i < attrNodes.length(); ++i) {
         QDomAttr attr = attrNodes.item(i).toAttr();
         if (!attr.isNull()) attrs.append(attr.name(), "", "", attr.value());
     }
@@ -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;
     }
 }
 
@@ -319,26 +316,26 @@
     m_summaryType = type;
 }
     
-size_t
+int
 Transform::getStepSize() const
 {
     return m_stepSize;
 }
 
 void
-Transform::setStepSize(size_t s)
+Transform::setStepSize(int s)
 {
     m_stepSize = s;
 }
     
-size_t
+int
 Transform::getBlockSize() const
 {
     return m_blockSize;
 }
 
 void
-Transform::setBlockSize(size_t s)
+Transform::setBlockSize(int s)
 {
     m_blockSize = s;
 }
@@ -379,14 +376,14 @@
     m_duration = d;
 }
     
-float
+sv_samplerate_t
 Transform::getSampleRate() const
 {
     return m_sampleRate;
 }
 
 void
-Transform::setSampleRate(float rate)
+Transform::setSampleRate(sv_samplerate_t rate)
 {
     m_sampleRate = rate;
 }
@@ -520,7 +517,7 @@
     }
 
     if (attrs.value("duration") != "") {
-        setStartTime(RealTime::fromString(attrs.value("duration").toStdString()));
+        setDuration(RealTime::fromString(attrs.value("duration").toStdString()));
     }
     
     if (attrs.value("sampleRate") != "") {
--- a/transform/Transform.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/Transform.h	Wed Apr 20 12:06:28 2016 +0100
@@ -25,6 +25,7 @@
 #include <QString>
 
 #include <map>
+#include <vector>
 
 typedef QString TransformId;
 
@@ -45,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);
 
@@ -66,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;
@@ -119,11 +121,11 @@
     QString getProgram() const;
     void setProgram(QString program);
     
-    size_t getStepSize() const;
-    void setStepSize(size_t s);
+    int getStepSize() const;
+    void setStepSize(int s);
     
-    size_t getBlockSize() const;
-    void setBlockSize(size_t s);
+    int getBlockSize() const;
+    void setBlockSize(int s);
     
     WindowType getWindowType() const;
     void setWindowType(WindowType type);
@@ -134,8 +136,8 @@
     RealTime getDuration() const; // 0 -> all
     void setDuration(RealTime d);
     
-    float getSampleRate() const; // 0 -> as input
-    void setSampleRate(float rate);
+    sv_samplerate_t getSampleRate() const; // 0 -> as input
+    void setSampleRate(sv_samplerate_t rate);
 
     void toXml(QTextStream &stream, QString indent = "",
                QString extraAttributes = "") const;
@@ -155,6 +157,8 @@
      */
     void setFromXmlAttributes(const QXmlAttributes &);
 
+    QString getErrorString() const { return m_errorString; }
+    
     static SummaryType stringToSummaryType(QString);
     static QString summaryTypeToString(SummaryType);
 
@@ -188,13 +192,16 @@
     SummaryType m_summaryType;
     QString m_pluginVersion;
     QString m_program;
-    size_t m_stepSize;
-    size_t m_blockSize;
+    int m_stepSize;
+    int m_blockSize;
     WindowType m_windowType;
     RealTime m_startTime;
     RealTime m_duration;
-    float m_sampleRate;
+    sv_samplerate_t m_sampleRate;
+    QString m_errorString;
 };
 
+typedef std::vector<Transform> Transforms;
+
 #endif
 
--- a/transform/TransformDescription.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/TransformDescription.h	Wed Apr 20 12:06:28 2016 +0100
@@ -53,7 +53,8 @@
         UnknownType
     };
 
-    TransformDescription() { }
+    TransformDescription() : 
+        type(UnknownType), configurable(false) { }
     TransformDescription(Type _type, QString _category,
                          TransformId _identifier, QString _name,
                          QString _friendlyName, QString _description,
--- a/transform/TransformFactory.cpp	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/TransformFactory.cpp	Wed Apr 20 12:06:28 2016 +0100
@@ -60,7 +60,8 @@
     m_transformsPopulated(false),
     m_uninstalledTransformsPopulated(false),
     m_thread(0),
-    m_exiting(false)
+    m_exiting(false),
+    m_populatingSlowly(false)
 {
 }
 
@@ -405,7 +406,7 @@
 	FeatureExtractionPluginFactory::getAllPluginIdentifiers();
     if (m_exiting) return;
 
-    for (size_t i = 0; i < plugs.size(); ++i) {
+    for (int i = 0; i < (int)plugs.size(); ++i) {
 
 	QString pluginId = plugs[i];
 
@@ -431,7 +432,7 @@
 	Vamp::Plugin::OutputList outputs =
 	    plugin->getOutputDescriptors();
 
-	for (size_t j = 0; j < outputs.size(); ++j) {
+	for (int j = 0; j < (int)outputs.size(); ++j) {
 
 	    QString transformId = QString("%1:%2")
 		    .arg(pluginId).arg(outputs[j].identifier.c_str());
@@ -506,7 +507,7 @@
 
     static QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$");
 
-    for (size_t i = 0; i < plugs.size(); ++i) {
+    for (int i = 0; i < (int)plugs.size(); ++i) {
         
 	QString pluginId = plugs[i];
 
@@ -539,14 +540,14 @@
 
         if (descriptor->audioInputPortCount > 0) {
 
-            for (size_t j = 0; j < descriptor->controlOutputPortCount; ++j) {
+            for (int j = 0; j < (int)descriptor->controlOutputPortCount; ++j) {
 
                 QString transformId = QString("%1:%2").arg(pluginId).arg(j);
                 QString userName;
                 QString units;
                 QString portName;
 
-                if (j < descriptor->controlOutputPortNames.size() &&
+                if (j < (int)descriptor->controlOutputPortNames.size() &&
                     descriptor->controlOutputPortNames[j] != "") {
 
                     portName = descriptor->controlOutputPortNames[j].c_str();
@@ -749,7 +750,7 @@
 }
 
 Transform
-TransformFactory::getDefaultTransformFor(TransformId id, size_t rate)
+TransformFactory::getDefaultTransformFor(TransformId id, sv_samplerate_t rate)
 {
     Transform t;
     t.setIdentifier(id);
@@ -781,17 +782,21 @@
 }
 
 Vamp::PluginBase *
-TransformFactory::instantiateDefaultPluginFor(TransformId identifier, size_t rate)
+TransformFactory::instantiateDefaultPluginFor(TransformId identifier,
+                                              sv_samplerate_t rate)
 {
     Transform t;
     t.setIdentifier(identifier);
-    if (rate == 0) rate = 44100;
+    if (rate == 0) rate = 44100.0;
     QString pluginId = t.getPluginIdentifier();
 
     Vamp::PluginBase *plugin = 0;
 
     if (t.getType() == Transform::FeatureExtraction) {
 
+//        cerr << "TransformFactory::instantiateDefaultPluginFor: identifier \""
+//             << identifier << "\" is a feature extraction transform" << endl;
+        
         FeatureExtractionPluginFactory *factory = 
             FeatureExtractionPluginFactory::instanceFor(pluginId);
 
@@ -799,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);
@@ -807,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;
@@ -912,8 +924,8 @@
             instantiatePlugin(id, 44100);
         if (!plugin) return false;
 
-        min = plugin->getMinChannelCount();
-        max = plugin->getMaxChannelCount();
+        min = (int)plugin->getMinChannelCount();
+        max = (int)plugin->getMaxChannelCount();
         delete plugin;
 
         return true;
@@ -1039,10 +1051,10 @@
     } else {
         Vamp::Plugin::InputDomain domain = vp->getInputDomain();
         if (!transform.getStepSize()) {
-            transform.setStepSize(vp->getPreferredStepSize());
+            transform.setStepSize((int)vp->getPreferredStepSize());
         }
         if (!transform.getBlockSize()) {
-            transform.setBlockSize(vp->getPreferredBlockSize());
+            transform.setBlockSize((int)vp->getPreferredBlockSize());
         }
         if (!transform.getBlockSize()) {
             transform.setBlockSize(1024);
--- a/transform/TransformFactory.h	Tue Jul 14 15:04:46 2015 +0100
+++ b/transform/TransformFactory.h	Wed Apr 20 12:06:28 2016 +0100
@@ -88,7 +88,7 @@
      * with different parameters and execution context settings.
      * Return the default one for the given transform.
      */
-    Transform getDefaultTransformFor(TransformId identifier, size_t rate = 0);
+    Transform getDefaultTransformFor(TransformId identifier, sv_samplerate_t rate = 0);
 
     /**
      * Full name of a transform, suitable for putting on a menu.
@@ -195,7 +195,7 @@
      */
     void setParametersFromPluginConfigurationXml(Transform &transform,
                                                  QString xml);
-
+    
 protected:
     typedef std::map<TransformId, TransformDescription> TransformDescriptionMap;
 
@@ -210,7 +210,7 @@
     void populateFeatureExtractionPlugins(TransformDescriptionMap &);
     void populateRealTimePlugins(TransformDescriptionMap &);
 
-    Vamp::PluginBase *instantiateDefaultPluginFor(TransformId id, size_t rate);
+    Vamp::PluginBase *instantiateDefaultPluginFor(TransformId id, sv_samplerate_t rate);
     QMutex m_transformsMutex;
     QMutex m_uninstalledTransformsMutex;