changeset 1106:ee0aff1d0743 3.0-integration

Merge from branch normalize_hybrid_option
author Chris Cannam
date Fri, 26 Jun 2015 14:08:05 +0100
parents a27b1ce86e4f (diff) a54016762f77 (current diff)
children a65328f0c450
files
diffstat 186 files changed, 4993 insertions(+), 6231 deletions(-) [+]
line wrap: on
line diff
--- a/acinclude.m4	Fri Sep 12 11:38:55 2014 +0100
+++ b/acinclude.m4	Fri Jun 26 14:08:05 2015 +0100
@@ -112,3 +112,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/AudioLevel.cpp	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/AudioLevel.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/AudioPlaySource.h	Fri Jun 26 14:08:05 2015 +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(int 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 int 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 int 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 int getTargetSampleRate() const = 0;
+    virtual sv_samplerate_t getTargetSampleRate() const = 0;
 
     /**
      * Get the block size of the target audio device.  This may be an
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/BaseTypes.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Clipboard.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -15,7 +15,7 @@
 
 #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),
@@ -31,7 +31,7 @@
 {
 }
 
-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),
@@ -47,7 +47,7 @@
 {
 }
 
-Clipboard::Point::Point(long frame, float value, int 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),
@@ -63,7 +63,7 @@
 {
 }
 
-Clipboard::Point::Point(long frame, float value, int 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),
@@ -120,14 +120,14 @@
     return m_haveFrame;
 }
 
-long
+sv_frame_t
 Clipboard::Point::getFrame() const
 {
     return m_frame;
 }
 
 Clipboard::Point
-Clipboard::Point::withFrame(long frame) const
+Clipboard::Point::withFrame(sv_frame_t frame) const
 {
     Point p(*this);
     p.m_haveFrame = true;
@@ -162,14 +162,14 @@
     return m_haveDuration;
 }
 
-int
+sv_frame_t
 Clipboard::Point::getDuration() const
 {
     return m_duration;
 }
 
 Clipboard::Point
-Clipboard::Point::withDuration(int duration) const
+Clipboard::Point::withDuration(sv_frame_t duration) const
 {
     Point p(*this);
     p.m_haveDuration = true;
@@ -231,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Clipboard.h	Fri Jun 26 14:08:05 2015 +0100
@@ -19,30 +19,32 @@
 #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, int duration, QString label);
-        Point(long frame, float value, int 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;
-        Point withFrame(long frame) 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;
-        int getDuration() const;
-        Point withDuration(int duration) const;
+        sv_frame_t getDuration() const;
+        Point withDuration(sv_frame_t duration) const;
         
         bool haveLabel() const;
         QString getLabel() const;
@@ -55,22 +57,22 @@
         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;
-        int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Command.cpp	Fri Jun 26 14:08:05 2015 +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	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Debug.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -16,68 +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 <QThreadStorage>
 
-#include <cstdio>
+#ifndef NDEBUG
 
-static QThreadStorage<QDebug *> debugs;
+static SVDebug *debug = 0;
 static QMutex mutex;
-static char *prefix = 0;
 
-QDebug &
-getSVDebug()
+SVDebug &getSVDebug() {
+    mutex.lock();
+    if (!debug) {
+        debug = new SVDebug();
+    }
+    mutex.unlock();
+    return *debug;
+}
+
+SVDebug::SVDebug() :
+    m_prefix(0),
+    m_ok(false),
+    m_eol(false)
 {
-    mutex.lock();
-
-    QDebug *debug = 0;
-
     QString pfx = ResourceFinder().getUserResourcePrefix();
     QDir logdir(QString("%1/%2").arg(pfx).arg("log"));
 
-    if (!prefix) {
-        prefix = new char[20];
-        sprintf(prefix, "[%lu]", (unsigned long)QCoreApplication::applicationPid());
-        //!!! what to do if mkpath fails?
-	if (!logdir.exists()) logdir.mkpath(logdir.path());
+    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;
     }
+}
 
-    if (!debugs.hasLocalData()) {
-        QFile *logFile = new QFile(logdir.path() + "/sv-debug.log");
-        if (logFile->open(QIODevice::WriteOnly | QIODevice::Append)) {
-            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";
-            delete logFile;
-            logFile = 0;
-            debug = new QDebug(QtDebugMsg);
-        }
-        debugs.setLocalData(debug);
-        *debug << endl << (const char *)prefix << "Log started at "
-               << QDateTime::currentDateTime().toString();
-    } else {
-        debug = debugs.localData();
-    }
-
-    mutex.unlock();
-
-    QDebug &dref = *debug;
-    dref << endl << (const char *)prefix;
-
-    return dref;
+SVDebug::~SVDebug()
+{
+    m_stream.close();
 }
 
 QDebug &
@@ -87,6 +76,8 @@
     return dbg;
 }
 
+#endif
+
 std::ostream &
 operator<<(std::ostream &target, const QString &str)
 {
--- a/base/Debug.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Debug.h	Fri Jun 26 14:08:05 2015 +0100
@@ -13,16 +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 <vamp-hostsdk/RealTime.h>
+#include "RealTime.h"
 
 #include <string>
 #include <iostream>
+#include <fstream>
 
 class QString;
 class QUrl;
@@ -37,24 +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()
 
-inline QDebug &operator<<(QDebug &d, const Vamp::RealTime &rt) {
-    d << rt.toString();
-    return d;
-}
-
-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/Exceptions.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Exceptions.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -91,8 +91,8 @@
 }
 
 InsufficientDiscSpace::InsufficientDiscSpace(QString directory,
-                                             int required,
-                                             int available) throw() :
+                                             size_t required,
+                                             size_t available) throw() :
     m_directory(directory),
     m_required(required),
     m_available(available)
--- a/base/Exceptions.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Exceptions.h	Fri Jun 26 14:08:05 2015 +0100
@@ -82,19 +82,19 @@
 {
 public:
     InsufficientDiscSpace(QString directory,
-                          int required, int available) throw();
+                          size_t required, size_t available) throw();
     InsufficientDiscSpace(QString directory) throw();
     virtual ~InsufficientDiscSpace() throw() { }
     virtual const char *what() const throw();
 
     QString getDirectory() const { return m_directory; }
-    int getRequired() const { return m_required; }
-    int getAvailable() const { return m_available; }
+    size_t getRequired() const { return m_required; }
+    size_t getAvailable() const { return m_available; }
 
 protected:
     QString m_directory;
-    int m_required;
-    int m_available;
+    size_t m_required;
+    size_t m_available;
 };
 
 class AllocationFailed : virtual public std::exception
--- a/base/FrameTimer.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/FrameTimer.h	Fri Jun 26 14:08:05 2015 +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 int getFrame() const = 0;
+    virtual sv_frame_t getFrame() const = 0;
 };
 
 #endif
--- a/base/LogRange.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/LogRange.cpp	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/LogRange.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Pitch.cpp	Fri Jun 26 14:08:05 2015 +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,13 +100,19 @@
     "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 baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote();
-    int octave = baseOctave;
+    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
@@ -118,44 +128,55 @@
 	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) {
@@ -181,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Pitch.h	Fri Jun 26 14:08:05 2015 +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,10 +77,41 @@
      * 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
@@ -83,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.
@@ -97,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
@@ -118,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/PlayParameterRepository.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -35,14 +35,14 @@
 void
 PlayParameterRepository::addPlayable(const Playable *playable)
 {
-    cerr << "PlayParameterRepository:addPlayable playable = " << 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:addPlayable: Adding play parameters for " << playable << endl;
+//        cerr << "PlayParameterRepository:addPlayable: Adding play parameters for " << playable << endl;
 
         PlayParameters *params = new PlayParameters;
         m_playParameters[playable] = params;
@@ -59,8 +59,8 @@
         connect(params, SIGNAL(playClipIdChanged(QString)),
                 this, SLOT(playClipIdChanged(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;
     }
 }    
 
--- a/base/Preferences.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Preferences.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -49,6 +49,7 @@
     m_viewFontSize(10),
     m_backgroundMode(BackgroundFromTheme),
     m_timeToTextMode(TimeToTextMs),
+    m_showHMS(true),
     m_octave(4),
     m_showSplash(true)
 {
@@ -58,19 +59,20 @@
         (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).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();
@@ -102,6 +104,7 @@
     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");
@@ -148,7 +151,10 @@
         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");
@@ -205,6 +211,9 @@
     if (name == "Time To Text Mode") {
         return ValueProperty;
     }
+    if (name == "Show Hours And Minutes") {
+        return ToggleProperty;
+    }
     if (name == "Octave Numbering System") {
         return ValueProperty;
     }
@@ -259,6 +268,7 @@
 
     if (name == "Omit Temporaries from Recent Files") {
         if (deflt) *deflt = 1;
+        return m_omitRecentTemps ? 1 : 0;
     }
 
     if (name == "Background Mode") {
@@ -275,6 +285,11 @@
         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
@@ -294,6 +309,7 @@
 
     if (name == "Show Splash Screen") {
         if (deflt) *deflt = 1;
+        return m_showSplash ? 1 : 0;
     }
 
     return 0;
@@ -404,6 +420,8 @@
         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)));
@@ -449,7 +467,7 @@
 }
 
 void
-Preferences::setTuningFrequency(float freq)
+Preferences::setTuningFrequency(double freq)
 {
     if (m_tuningFrequency != freq) {
         m_tuningFrequency = freq;
@@ -543,7 +561,7 @@
 }
 
 void
-Preferences::setFixedSampleRate(int rate)
+Preferences::setFixedSampleRate(sv_samplerate_t rate)
 {
     if (m_fixedSampleRate != rate) {
         m_fixedSampleRate = rate;
@@ -599,6 +617,21 @@
 }
 
 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) {
--- a/base/Preferences.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Preferences.h	Fri Jun 26 14:08:05 2015 +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; }
 
@@ -67,7 +67,7 @@
     QString getTemporaryDirectoryRoot() const { return m_tempDirRoot; }
 
     /// If we should always resample audio to the same rate, return it; otherwise (the normal case) return 0
-    int getFixedSampleRate() const { return m_fixedSampleRate; }
+    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; }    
@@ -93,6 +93,8 @@
     };
     TimeToTextMode getTimeToTextMode() const { return m_timeToTextMode; }
 
+    bool getShowHMS() const { return m_showHMS; }
+    
     int getOctaveOfMiddleC() const {
         // weed out unsupported octaves
         return getOctaveOfMiddleCInSystem(getSystemWithMiddleCInOctave(m_octave));
@@ -108,17 +110,18 @@
 
     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(int);
+    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);
@@ -144,18 +147,19 @@
 
     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;
-    int m_fixedSampleRate;
+    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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Profiler.cpp	Fri Jun 26 14:08:05 2015 +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/RangeMapper.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/RangeMapper.cpp	Fri Jun 26 14:08:05 2015 +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,7 +36,7 @@
 }
 
 int
-LinearRangeMapper::getPositionForValue(float value) const
+LinearRangeMapper::getPositionForValue(double value) const
 {
     int position = getPositionForValueUnclamped(value);
     if (position < m_minpos) position = m_minpos;
@@ -45,36 +45,36 @@
 }
 
 int
-LinearRangeMapper::getPositionForValueUnclamped(float value) const
+LinearRangeMapper::getPositionForValueUnclamped(double value) const
 {
     int position = m_minpos +
-        lrintf(((value - m_minval) / (m_maxval - m_minval))
-               * (m_maxpos - m_minpos));
+        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 (position < m_minpos) position = m_minpos;
     if (position > m_maxpos) position = m_maxpos;
-    float value = getValueForPositionUnclamped(position);
+    double value = getValueForPositionUnclamped(position);
     return value;
 }
 
-float
+double
 LinearRangeMapper::getValueForPositionUnclamped(int position) const
 {
     if (m_inverted) position = m_maxpos - (position - m_minpos);
-    float value = m_minval +
-        ((float(position - m_minpos) / float(m_maxpos - m_minpos))
+    double value = m_minval +
+        ((double(position - m_minpos) / double(m_maxpos - m_minpos))
          * (m_maxval - m_minval));
     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),
@@ -97,26 +97,26 @@
 
 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 = getPositionForValueUnclamped(value);
     if (position < m_minpos) position = m_minpos;
@@ -125,29 +125,29 @@
 }
 
 int
-LogRangeMapper::getPositionForValueUnclamped(float value) const
+LogRangeMapper::getPositionForValueUnclamped(double value) const
 {
-    static float thresh = powf(10, -10);
+    static double thresh = pow(10, -10);
     if (value < thresh) value = thresh;
-    int position = lrintf((log10(value) - m_minlog) * m_ratio) + m_minpos;
+    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 (position < m_minpos) position = m_minpos;
     if (position > m_maxpos) position = m_maxpos;
-    float value = getValueForPositionUnclamped(position);
+    double value = getValueForPositionUnclamped(position);
     return value;
 }
 
-float
+double
 LogRangeMapper::getValueForPositionUnclamped(int position) const
 {
     if (m_inverted) position = m_maxpos - (position - m_minpos);
-    float value = powf(10, (position - m_minpos) / m_ratio + m_minlog);
+    double value = pow(10, (position - m_minpos) / m_ratio + m_minlog);
     return value;
 }
 
@@ -163,7 +163,7 @@
 }
 
 int
-InterpolatingRangeMapper::getPositionForValue(float value) const
+InterpolatingRangeMapper::getPositionForValue(double value) const
 {
     int pos = getPositionForValueUnclamped(value);
     CoordMap::const_iterator i = m_mappings.begin();
@@ -174,16 +174,16 @@
 }
 
 int
-InterpolatingRangeMapper::getPositionForValueUnclamped(float value) const
+InterpolatingRangeMapper::getPositionForValueUnclamped(double value) const
 {
-    float p = interpolate(&m_mappings, value);
-    return lrintf(p);
+    double p = interpolate(&m_mappings, value);
+    return int(lrint(p));
 }
 
-float
+double
 InterpolatingRangeMapper::getValueForPosition(int position) const
 {
-    float val = getValueForPositionUnclamped(position);
+    double val = getValueForPositionUnclamped(position);
     CoordMap::const_iterator i = m_mappings.begin();
     if (val < i->first) val = i->first;
     i = m_mappings.end(); --i;
@@ -191,18 +191,19 @@
     return val;
 }
 
-float
+double
 InterpolatingRangeMapper::getValueForPositionUnclamped(int position) const
 {
     return interpolate(&m_reverse, position);
 }
 
 template <typename T>
-float
-InterpolatingRangeMapper::interpolate(T *mapping, float value) const
+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(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
@@ -219,7 +220,7 @@
     typename T::const_iterator j = i;
     --j;
 
-    float gradient = float(i->second - j->second) / float(i->first - j->first);
+    double gradient = double(i->second - j->second) / double(i->first - j->first);
 
     return j->second + (value - j->first) * gradient;
 }
@@ -316,24 +317,24 @@
 }
 
 int
-AutoRangeMapper::getPositionForValue(float value) const
+AutoRangeMapper::getPositionForValue(double value) const
 {
     return m_mapper->getPositionForValue(value);
 }
 
-float
+double
 AutoRangeMapper::getValueForPosition(int position) const
 {
     return m_mapper->getValueForPosition(position);
 }
 
 int
-AutoRangeMapper::getPositionForValueUnclamped(float value) const
+AutoRangeMapper::getPositionForValueUnclamped(double value) const
 {
     return m_mapper->getPositionForValueUnclamped(value);
 }
 
-float
+double
 AutoRangeMapper::getValueForPositionUnclamped(int position) const
 {
     return m_mapper->getValueForPositionUnclamped(position);
--- a/base/RangeMapper.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/RangeMapper.h	Fri Jun 26 14:08:05 2015 +0100
@@ -31,7 +31,7 @@
      * the nearest position and clamping to the minimum and maximum
      * extents of the mapper's positional range.
      */
-    virtual int getPositionForValue(float value) const = 0;
+    virtual int getPositionForValue(double value) const = 0;
 
     /**
      * Return the position that maps to the given value, rounding to
@@ -41,13 +41,13 @@
      * range. (The mapping outside that range is not guaranteed to be
      * exact, except if the mapper is a linear one.)
      */
-    virtual int getPositionForValueUnclamped(float value) const = 0;
+    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 float getValueForPosition(int position) const = 0;
+    virtual double getValueForPosition(int position) const = 0;
 
     /**
      * Return the value mapped from the given positionq, without
@@ -56,7 +56,7 @@
      * mapper's value range. (The mapping outside that range is not
      * guaranteed to be exact, except if the mapper is a linear one.)
      */
-    virtual float getValueForPositionUnclamped(int position) const = 0;
+    virtual double getValueForPositionUnclamped(int position) const = 0;
 
     /**
      * Get the unit of the mapper's value range.
@@ -75,22 +75,22 @@
      * 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 int getPositionForValueUnclamped(float value) const;
+    virtual int getPositionForValue(double value) const;
+    virtual int getPositionForValueUnclamped(double value) const;
 
-    virtual float getValueForPosition(int position) const;
-    virtual float getValueForPositionUnclamped(int position) 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;
 };
@@ -107,31 +107,31 @@
      * 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 int getPositionForValueUnclamped(float value) const;
+    virtual int getPositionForValue(double value) const;
+    virtual int getPositionForValueUnclamped(double value) const;
 
-    virtual float getValueForPosition(int position) const;
-    virtual float getValueForPositionUnclamped(int position) 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;
 };
@@ -139,7 +139,7 @@
 class InterpolatingRangeMapper : public RangeMapper
 {
 public:
-    typedef std::map<float, int> CoordMap;
+    typedef std::map<double, int> CoordMap;
 
     /**
      * Given a series of (value, position) coordinate mappings,
@@ -162,21 +162,21 @@
     InterpolatingRangeMapper(CoordMap pointMappings,
                              QString unit);
 
-    virtual int getPositionForValue(float value) const;
-    virtual int getPositionForValueUnclamped(float value) const;
+    virtual int getPositionForValue(double value) const;
+    virtual int getPositionForValueUnclamped(double value) const;
 
-    virtual float getValueForPosition(int position) const;
-    virtual float getValueForPositionUnclamped(int position) 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, float> m_reverse;
+    std::map<int, double> m_reverse;
     QString m_unit;
 
     template <typename T>
-    float interpolate(T *mapping, float v) const;
+    double interpolate(T *mapping, double v) const;
 };
 
 class AutoRangeMapper : public RangeMapper
@@ -188,7 +188,7 @@
         Logarithmic,
     };
 
-    typedef std::map<float, int> CoordMap;
+    typedef std::map<double, int> CoordMap;
 
     /**
      * Given a series of (value, position) coordinate mappings,
@@ -235,11 +235,11 @@
      */
     MappingType getType() const { return m_type; }
 
-    virtual int getPositionForValue(float value) const;
-    virtual int getPositionForValueUnclamped(float value) const;
+    virtual int getPositionForValue(double value) const;
+    virtual int getPositionForValueUnclamped(double value) const;
 
-    virtual float getValueForPosition(int position) const;
-    virtual float getValueForPositionUnclamped(int position) const;
+    virtual double getValueForPosition(int position) const;
+    virtual double getValueForPositionUnclamped(int position) const;
 
     virtual QString getUnit() const { return m_unit; }
 
--- a/base/RealTime.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/RealTime.cpp	Fri Jun 26 14:08:05 2015 +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,7 +89,7 @@
     int i = 0;
 
     const char *s = xsdd.c_str();
-    int len = xsdd.length();
+    int len = int(xsdd.length());
 
     bool negative = false, afterT = false;
 
@@ -102,7 +106,7 @@
 
         if (isdigit(s[i]) || s[i] == '.') {
             value = strtod(&s[i], &eptr);
-            i = eptr - s;
+            i = int(eptr - s);
         }
 
         if (i == len) break;
@@ -254,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;
@@ -265,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();
 
@@ -311,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;
@@ -363,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";
@@ -438,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/RealTime.h	Fri Jun 26 14:08:05 2015 +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/Resampler.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Resampler.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -31,16 +31,16 @@
 class Resampler::D
 {
 public:
-    D(Quality quality, int channels, int chunkSize);
+    D(Quality quality, int channels, sv_frame_t chunkSize);
     ~D();
 
-    int resample(float **in, float **out,
-                    int incount, float ratio,
-                    bool final);
+    sv_frame_t resample(float **in, float **out,
+                 sv_frame_t incount, double ratio,
+                 bool final);
 
-    int resampleInterleaved(float *in, float *out,
-                               int incount, float ratio,
-                               bool final);
+    sv_frame_t resampleInterleaved(float *in, float *out,
+                            sv_frame_t incount, double ratio,
+                            bool final);
 
     void reset();
 
@@ -49,11 +49,11 @@
     float *m_iin;
     float *m_iout;
     int m_channels;
-    int m_iinsize;
-    int m_ioutsize;
+    sv_frame_t m_iinsize;
+    sv_frame_t m_ioutsize;
 };
 
-Resampler::D::D(Quality quality, int channels, int 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 @@
     }
 }
 
-int
+sv_frame_t
 Resampler::D::resample(float **in, float **out,
-                       int incount, float ratio,
+                       sv_frame_t incount, double ratio,
                        bool final)
 {
     if (m_channels == 1) {
         return resampleInterleaved(*in, *out, incount, ratio, final);
     }
 
-    int 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,15 +108,15 @@
         m_ioutsize = outcount * m_channels;
         m_iout = (float *)realloc(m_iout, m_ioutsize * sizeof(float));
     }
-    for (int i = 0; i < incount; ++i) {
+    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];
         }
     }
     
-    int gen = resampleInterleaved(m_iin, m_iout, incount, ratio, final);
+    sv_frame_t gen = resampleInterleaved(m_iin, m_iout, incount, ratio, final);
 
-    for (int i = 0; i < gen; ++i) {
+    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;
 }
 
-int
+sv_frame_t
 Resampler::D::resampleInterleaved(float *in, float *out,
-                                  int incount, float ratio,
+                                  sv_frame_t incount, double ratio,
                                   bool final)
 {
     SRC_DATA data;
 
-    int outcount = lrintf(ceilf(incount * ratio));
+    sv_frame_t outcount = lrint(ceil(double(incount) * ratio));
 
     data.data_in = in;
     data.data_out = out;
@@ -149,7 +149,7 @@
         return 0;
     }
 
-    if (data.input_frames_used != (int)incount) {
+    if (data.input_frames_used != incount) {
         cerr << "Resampler: NOTE: input_frames_used == " << data.input_frames_used << " (while incount = " << incount << ")" << endl;
     }
 
@@ -162,7 +162,7 @@
     src_reset(m_src);
 }
 
-Resampler::Resampler(Quality quality, int channels, int chunkSize)
+Resampler::Resampler(Quality quality, int channels, sv_frame_t chunkSize)
 {
     m_d = new D(quality, channels, chunkSize);
 }
@@ -172,18 +172,18 @@
     delete m_d;
 }
 
-int 
+sv_frame_t 
 Resampler::resample(float **in, float **out,
-                    int incount, float ratio,
+                    sv_frame_t incount, double ratio,
                     bool final)
 {
     return m_d->resample(in, out, incount, ratio, final);
 }
 
-int 
+sv_frame_t 
 Resampler::resampleInterleaved(float *in, float *out,
-                    int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Resampler.h	Fri Jun 26 14:08:05 2015 +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, int channels, int chunkSize = 0);
+    Resampler(Quality quality, int channels, sv_frame_t chunkSize = 0);
     ~Resampler();
 
-    int resample(float **in, float **out,
-                    int incount, float ratio,
-                    bool final = false);
+    sv_frame_t resample(float **in, float **out,
+                        sv_frame_t incount, double ratio,
+                        bool final = false);
 
-    int resampleInterleaved(float *in, float *out,
-                               int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/ResizeableBitset.h	Fri Jun 26 14:08:05 2015 +0100
@@ -62,11 +62,14 @@
     }
     
     void set(size_t column) {
-        ((*m_bits)[column >> 3]) |=  (uint8_t(1) << (column & 0x07));
+        size_t ix = (column >> 3);
+        uint8_t prior = (*m_bits)[ix];
+        uint8_t extra = ((1u << (column & 0x07)) & 0xff);
+        (*m_bits)[ix] = uint8_t(prior | extra);
     }
 
     void reset(size_t column) {
-        ((*m_bits)[column >> 3]) &= ~(uint8_t(1) << (column & 0x07));
+        ((*m_bits)[column >> 3]) &= uint8_t((~(1u << (column & 0x07))) & 0xff);
     }
 
     void copy(size_t source, size_t dest) {
--- a/base/Scavenger.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Scavenger.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Selection.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -22,12 +22,12 @@
 {
 }
 
-Selection::Selection(int startFrame, int endFrame) :
+Selection::Selection(sv_frame_t startFrame, sv_frame_t endFrame) :
     m_startFrame(startFrame),
     m_endFrame(endFrame)
 {
     if (m_startFrame > m_endFrame) {
-	int 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;
 }
 
-int
+sv_frame_t
 Selection::getStartFrame() const
 {
     return m_startFrame;
 }
 
-int
+sv_frame_t
 Selection::getEndFrame() const
 {
     return m_endFrame;
 }
 
 bool
-Selection::contains(int frame) const
+Selection::contains(sv_frame_t frame) const
 {
     return (frame >= m_startFrame) && (frame < m_endFrame);
 }
@@ -174,7 +174,7 @@
 }
 
 void
-MultiSelection::getExtents(int &startFrame, int &endFrame) const
+MultiSelection::getExtents(sv_frame_t &startFrame, sv_frame_t &endFrame) const
 {
     startFrame = 0;
     endFrame = 0;
@@ -193,7 +193,7 @@
 }
 
 Selection
-MultiSelection::getContainingSelection(int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Selection.h	Fri Jun 26 14:08:05 2015 +0100
@@ -20,6 +20,7 @@
 #include <set>
 
 #include "XmlExportable.h"
+#include "BaseTypes.h"
 
 /**
  * A selection object simply represents a range in time, via start and
@@ -40,22 +41,22 @@
 {
 public:
     Selection();
-    Selection(int startFrame, int endFrame);
+    Selection(sv_frame_t startFrame, sv_frame_t endFrame);
     Selection(const Selection &);
     Selection &operator=(const Selection &);
     virtual ~Selection();
 
     bool isEmpty() const;
-    int getStartFrame() const;
-    int getEndFrame() const;
-    bool contains(int 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:
-    int m_startFrame;
-    int m_endFrame;
+    sv_frame_t m_startFrame;
+    sv_frame_t m_endFrame;
 };
 
 class MultiSelection : public XmlExportable
@@ -72,7 +73,7 @@
     void removeSelection(const Selection &selection);
     void clearSelections();
 
-    void getExtents(int &startFrame, int &endFrame) const;
+    void getExtents(sv_frame_t &startFrame, sv_frame_t &endFrame) const;
 
     /**
      * Return the selection that contains a given frame.
@@ -80,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(int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/StorageAdviser.cpp	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/StorageAdviser.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/StringBits.cpp	Fri Jun 26 14:08:05 2015 +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())) {
@@ -117,86 +126,19 @@
 	}
     }
 
-    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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/TempDirectory.cpp	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/ViewManagerBase.h	Fri Jun 26 14:08:05 2015 +0100
@@ -37,15 +37,15 @@
 
     virtual void setAudioPlaySource(AudioPlaySource *source) = 0;
 
-    virtual int alignPlaybackFrameToReference(int) const = 0;
-    virtual int alignReferenceToPlaybackFrame(int) 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 int constrainFrameToSelection(int frame) const = 0;
+    virtual sv_frame_t constrainFrameToSelection(sv_frame_t frame) const = 0;
 
     virtual Selection getContainingSelection
-    (int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/Window.h	Fri Jun 26 14:08:05 2015 +0100
@@ -22,7 +22,7 @@
 #include <map>
 #include <cstdlib>
 
-#include <cstdlib>
+#include "system/System.h"
 
 enum WindowType {
     RectangularWindow,
@@ -82,7 +82,7 @@
     T m_area;
     
     void encache();
-    void cosinewin(T *, T, T, T, T);
+    void cosinewin(T *, double, double, double, double);
 };
 
 template <typename T>
@@ -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)
 {
     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	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/ZoomConstraint.h	Fri Jun 26 14:08:05 2015 +0100
@@ -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 int getMaxZoomLevel() const { return 262144; }
+    virtual int getMaxZoomLevel() const { return 4194304; } // 2^22, arbitrarily
 };
 
 #endif
--- a/base/test/TestPitch.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/test/TestPitch.h	Fri Jun 26 14:08:05 2015 +0100
@@ -72,18 +72,29 @@
 	QCOMPARE(Pitch::getPitchLabelForFrequency(261.63, 440, false), QString("C4"));
     }
 
-#define MIDDLE_C 261.6255653f
+#define MIDDLE_C 261.6255653005986
 
     void frequencyForPitch()
     {
 	QCOMPARE(Pitch::getFrequencyForPitch(60, 0), MIDDLE_C);
-	QCOMPARE(Pitch::getFrequencyForPitch(69, 0), 440.f);
-	QCOMPARE(Pitch::getFrequencyForPitch(60, 0, 220), MIDDLE_C / 2.f);
-	QCOMPARE(Pitch::getFrequencyForPitch(69, 0, 220), 220.f);
+	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);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/TestRealTime.h	Fri Jun 26 14:08:05 2015 +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	Fri Jun 26 14:08:05 2015 +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
+*/
--- a/base/test/main.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/test/main.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -13,6 +13,8 @@
 
 #include "TestRangeMapper.h"
 #include "TestPitch.h"
+#include "TestRealTime.h"
+#include "TestStringBits.h"
 
 #include <QtTest>
 
@@ -36,6 +38,16 @@
 	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;
--- a/base/test/test.pro	Fri Sep 12 11:38:55 2014 +0100
+++ b/base/test/test.pro	Fri Jun 26 14:08:05 2015 +0100
@@ -38,7 +38,7 @@
     }
 }
 
-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
 
@@ -49,7 +49,7 @@
 OBJECTS_DIR = o
 MOC_DIR = o
 
-HEADERS += TestRangeMapper.h TestPitch.h
+HEADERS += TestRangeMapper.h TestPitch.h TestRealTime.h TestStringBits.h
 SOURCES += main.cpp
 
 win* {
--- a/configure	Fri Sep 12 11:38:55 2014 +0100
+++ b/configure	Fri Jun 26 14:08:05 2015 +0100
@@ -673,6 +673,7 @@
 EGREP
 GREP
 CXXCPP
+HAVE_CXX11
 MKDIR_P
 INSTALL_DATA
 INSTALL_SCRIPT
@@ -3449,6 +3450,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'
@@ -4202,9 +4343,10 @@
 CXXFLAGS_MINIMAL="$AUTOCONF_CXXFLAGS"
 
 if test "x$GCC" = "xyes"; then
-        CXXFLAGS_DEBUG="-Wall -Wextra -Werror -Woverloaded-virtual -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"
--- a/configure.ac	Fri Sep 12 11:38:55 2014 +0100
+++ b/configure.ac	Fri Jun 26 14:08:05 2015 +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 -Wextra -Werror -Woverloaded-virtual -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"
--- a/data/fft/FFTCacheReader.h	Fri Sep 12 11:38:55 2014 +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 int getWidth() const = 0;
-    virtual int getHeight() const = 0;
-	
-    virtual float getMagnitudeAt(int x, int y) const = 0;
-    virtual float getNormalizedMagnitudeAt(int x, int y) const = 0;
-    virtual float getMaximumMagnitudeAt(int x) const = 0;
-    virtual float getPhaseAt(int x, int y) const = 0;
-
-    virtual void getValuesAt(int x, int y, float &real, float &imag) const = 0;
-    virtual void getMagnitudesAt(int x, float *values, int minbin, int count, int step) const = 0;
-
-    virtual bool haveSetColumnAt(int x) const = 0;
-
-    virtual FFTCache::StorageType getStorageType() const = 0;
-};
-
-#endif
--- a/data/fft/FFTCacheStorageType.h	Fri Sep 12 11:38:55 2014 +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	Fri Sep 12 11:38:55 2014 +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 int getWidth() const = 0;
-    virtual int getHeight() const = 0;
-
-    virtual void setColumnAt(int x, float *mags, float *phases, float factor) = 0;
-    virtual void setColumnAt(int x, float *reals, float *imags) = 0;
-
-    virtual bool haveSetColumnAt(int x) const = 0;
-
-    virtual void allColumnsWritten() = 0; // notify cache to close
-
-    virtual FFTCache::StorageType getStorageType() const = 0;
-};
-
-#endif
-
--- a/data/fft/FFTDataServer.cpp	Fri Sep 12 11:38:55 2014 +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,
-                           int windowSize,
-                           int windowIncrement,
-                           int fftSize,
-                           bool polar,
-                           StorageAdviser::Criteria criteria,
-                           int 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,
-                                int windowSize,
-                                int windowIncrement,
-                                int fftSize,
-                                bool polar,
-                                StorageAdviser::Criteria criteria,
-                                int 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
-                std::cerr << "FFTDataServer::getFuzzyInstance: Distance for server " << server << " is " << distance << ", best is " << bestdist << std::endl;
-#endif
-                
-                if (bestdist == -1 || distance < bestdist) {
-                    bestdist = distance;
-                    best = i;
-                }
-            }
-        }
-
-        if (bestdist >= 0) {
-            FFTDataServer *server = best->second.first;
-#ifdef DEBUG_FFT_SERVER
-            std::cerr << "FFTDataServer::getFuzzyInstance: We like server " << server << " (with distance " << bestdist << ")" << std::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
-    std::cerr << "FFTDataServer::findServer(\"" << n << "\")" << std::endl;
-#endif
-
-    if (m_servers.find(n) != m_servers.end()) {
-
-        FFTDataServer *server = m_servers[n].first;
-
-#ifdef DEBUG_FFT_SERVER
-        std::cerr << "FFTDataServer::findServer(\"" << n << "\"): found " << server << std::endl;
-#endif
-
-        claimInstance(server, false);
-
-        return server;
-    }
-
-#ifdef DEBUG_FFT_SERVER
-        std::cerr << "FFTDataServer::findServer(\"" << n << "\"): not found" << std::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
-    std::cerr << "FFTDataServer::claimInstance(" << server << ")" << std::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
-    std::cerr << "FFTDataServer::claimInstance: found in released server list, removing from it" << std::endl;
-#endif
-                    m_releasedServers.erase(j);
-                    break;
-                }
-            }
-
-            ++i->second.second;
-
-#ifdef DEBUG_FFT_SERVER
-            std::cerr << "FFTDataServer::claimInstance: new refcount is " << i->second.second << std::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
-    std::cerr << "FFTDataServer::releaseInstance(" << server << ")" << std::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
-                    std::cerr << "FFTDataServer::releaseInstance: instance "
-                              << server << " has never been used, erasing"
-                              << std::endl;
-#endif
-                    delete server;
-                    m_servers.erase(i);
-                } else {
-*/
-#ifdef DEBUG_FFT_SERVER
-                    std::cerr << "FFTDataServer::releaseInstance: instance "
-                              << server << " no longer in use, marking for possible collection"
-                              << std::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
-                    std::cerr << "FFTDataServer::releaseInstance: instance "
-                              << server << " now has refcount " << i->second.second
-                              << std::endl;
-#endif
-            }
-            return;
-        }
-    }
-
-    cerr << "ERROR: FFTDataServer::releaseInstance(" << server << "): "
-              << "instance not found" << endl;
-}
-
-void
-FFTDataServer::purgeLimbo(int maxSize)
-{
-#ifdef DEBUG_FFT_SERVER
-    std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): "
-              << m_releasedServers.size() << " candidates" << std::endl;
-#endif
-
-    while (int(m_releasedServers.size()) > maxSize) {
-
-        FFTDataServer *server = *m_releasedServers.begin();
-
-        bool found = false;
-
-#ifdef DEBUG_FFT_SERVER
-        std::cerr << "FFTDataServer::purgeLimbo: considering candidate "
-                  << server << std::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
-                std::cerr << "FFTDataServer::purgeLimbo: looks OK, erasing it"
-                          << std::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
-    std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): "
-              << m_releasedServers.size() << " remain" << std::endl;
-#endif
-
-}
-
-void
-FFTDataServer::modelAboutToBeDeleted(Model *model)
-{
-    MutexLocker locker(&m_serverMapMutex,
-                       "FFTDataServer::modelAboutToBeDeleted::m_serverMapMutex");
-
-#ifdef DEBUG_FFT_SERVER
-    std::cerr << "FFTDataServer::modelAboutToBeDeleted(" << model << ")"
-              << std::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
-            std::cerr << "FFTDataServer::modelAboutToBeDeleted: server is "
-                      << server << std::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
-                    std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing from released servers" << std::endl;
-#endif
-                    m_releasedServers.erase(j);
-                    break;
-                }
-            }
-#ifdef DEBUG_FFT_SERVER
-            std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing server" << std::endl;
-#endif
-            m_servers.erase(i);
-            delete server;
-            return;
-        }
-    }
-}
-
-FFTDataServer::FFTDataServer(QString fileBaseName,
-                             const DenseTimeValueModel *model,
-                             int channel,
-			     WindowType windowType,
-			     int windowSize,
-			     int windowIncrement,
-			     int fftSize,
-                             bool polar,
-                             StorageAdviser::Criteria criteria,
-                             int 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???
-
-    int start = m_model->getStartFrame();
-    int 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
-
-    int maxCacheSize = 20 * 1024 * 1024;
-    int 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 (int 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(int w, int 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);
-
-    int 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
-    std::cerr << "FFTDataServer::makeCacheReader(" << c << ")" << std::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 >= (int)m_caches.size()) {
-        return true;
-    }
-
-    cb = m_caches.at(deleteCandidate);
-    if (cb && cb->fileCacheReader.find(me) != cb->fileCacheReader.end()) {
-#ifdef DEBUG_FFT_SERVER
-        std::cerr << "FFTDataServer::makeCacheReader: Deleting probably unpopular reader " << deleteCandidate << " for this thread (as I create reader " << c << ")" << std::endl;
-#endif
-        delete cb->fileCacheReader[me];
-        cb->fileCacheReader.erase(me);
-    }
-            
-    return true;
-}
-       
-float
-FFTDataServer::getMagnitudeAt(int x, int y)
-{
-    Profiler profiler("FFTDataServer::getMagnitudeAt", false);
-
-    if (x >= m_width || y >= m_height) return 0;
-
-    float val = 0;
-
-    try {
-        int col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return 0;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getMagnitudeAt: filling");
-#ifdef DEBUG_FFT_SERVER
-            std::cerr << "FFTDataServer::getMagnitudeAt: calling fillColumn("
-                  << x << ")" << std::endl;
-#endif
-            fillColumn(x);
-        }
-
-        val = cache->getMagnitudeAt(col, y);
-
-    } catch (std::exception &e) {
-        m_error = e.what();
-    }
-
-    return val;
-}
-
-bool
-FFTDataServer::getMagnitudesAt(int x, float *values, int minbin, int count, int 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 {
-        int 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(int x, int y)
-{
-    Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt", false);
-
-    if (x >= m_width || y >= m_height) return 0;
-
-    float val = 0;
-
-    try {
-
-        int 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(int x, float *values, int minbin, int count, int 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 {
-
-        int col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return false;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getNormalizedMagnitudesAt: filling");
-            fillColumn(x);
-        }
-        
-        for (int 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(int x)
-{
-    Profiler profiler("FFTDataServer::getMaximumMagnitudeAt", false);
-
-    if (x >= m_width) return 0;
-
-    float val = 0;
-
-    try {
-
-        int 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(int x, int y)
-{
-    Profiler profiler("FFTDataServer::getPhaseAt", false);
-
-    if (x >= m_width || y >= m_height) return 0;
-
-    float val = 0;
-
-    try {
-
-        int 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(int x, float *values, int minbin, int count, int 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 {
-
-        int col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return false;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getPhasesAt: filling");
-            fillColumn(x);
-        }
-        
-        for (int 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(int x, int y, float &real, float &imaginary)
-{
-    Profiler profiler("FFTDataServer::getValuesAt", false);
-
-    if (x >= m_width || y >= m_height) {
-        real = 0;
-        imaginary = 0;
-        return;
-    }
-
-    try {
-
-        int 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
-            std::cerr << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << std::endl;
-#endif
-            fillColumn(x);
-        }        
-
-        cache->getValuesAt(col, y, real, imaginary);
-
-    } catch (std::exception &e) {
-        m_error = e.what();
-    }
-}
-
-bool
-FFTDataServer::getValuesAt(int x, float *reals, float *imaginaries, int minbin, int count, int 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 {
-
-        int col;
-        FFTCacheReader *cache = getCacheReader(x, col);
-        if (!cache) return false;
-
-        if (!cache->haveSetColumnAt(col)) {
-            Profiler profiler("FFTDataServer::getValuesAt: filling");
-            fillColumn(x);
-        }
-
-        for (int 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(int x)
-{
-    Profiler profiler("FFTDataServer::isColumnReady", false);
-
-    if (x >= m_width) return true;
-
-    if (!haveCache(x)) {
-/*!!!
-        if (m_lastUsedCache == -1) {
-            if (m_suspended) {
-                std::cerr << "FFTDataServer::isColumnReady(" << x << "): no cache, calling resume" << std::endl;
-                resume();
-            }
-            m_fillThread->start();
-        }
-*/
-        return false;
-    }
-
-    try {
-
-        int 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(int 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;
-    }
-
-    int 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
-    std::cerr << "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 << std::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) {
-//        std::cerr << "FFTDataServer::fillColumn(" << x << "): calling resume" << std::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 "";
-}
-
-int
-FFTDataServer::getFillCompletion() const 
-{
-    if (m_fillThread) return m_fillThread->getCompletion();
-    else return 100;
-}
-
-int
-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,
-                                    int windowSize,
-                                    int windowIncrement,
-                                    int 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
-    std::cerr << "FFTDataServer::FillThread::run()" << std::endl;
-#endif
-    
-    m_extent = 0;
-    m_completion = 0;
-    
-    while (!m_server.m_model->isReady() && !m_server.m_exiting) {
-#ifdef DEBUG_FFT_SERVER_FILL
-        std::cerr << "FFTDataServer::FillThread::run(): waiting for model " << m_server.m_model << " to be ready" << std::endl;
-#endif
-        sleep(1);
-    }
-    if (m_server.m_exiting) return;
-
-    int start = m_server.m_model->getStartFrame();
-    int end = m_server.m_model->getEndFrame();
-    int 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 (int 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) {
-                std::cerr << "FFTDataServer::FillThread::run: exception: " << e.what() << std::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 = int(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;
-    }
-
-    int baseCompletion = m_completion;
-
-    for (int f = start; f < remainingEnd; f += m_server.m_windowIncrement) {
-
-        try {
-            m_server.fillColumn(int((f - start) / m_server.m_windowIncrement));
-        } catch (std::exception &e) {
-            std::cerr << "FFTDataServer::FillThread::run: exception: " << e.what() << std::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 +
-                int(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
-    std::cerr << "FFTDataServer::FillThread::run exiting" << std::endl;
-#endif
-}
-
--- a/data/fft/FFTDataServer.h	Fri Sep 12 11:38:55 2014 +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,
-                                      int windowSize,
-                                      int windowIncrement,
-                                      int fftSize,
-                                      bool polar,
-                                      StorageAdviser::Criteria criteria =
-                                          StorageAdviser::NoCriteria,
-                                      int fillFromColumn = 0);
-
-    static FFTDataServer *getFuzzyInstance(const DenseTimeValueModel *model,
-                                           int channel,
-                                           WindowType windowType,
-                                           int windowSize,
-                                           int windowIncrement,
-                                           int fftSize,
-                                           bool polar,
-                                           StorageAdviser::Criteria criteria =
-                                               StorageAdviser::NoCriteria,
-                                           int 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(); }
-    int     getWindowSize() const { return m_windowSize; }
-    int     getWindowIncrement() const { return m_windowIncrement; }
-    int     getFFTSize() const { return m_fftSize; }
-    bool       getPolar() const { return m_polar; }
-
-    int     getWidth() const  { return m_width;  }
-    int     getHeight() const { return m_height; }
-
-    float      getMagnitudeAt(int x, int y);
-    float      getNormalizedMagnitudeAt(int x, int y);
-    float      getMaximumMagnitudeAt(int x);
-    float      getPhaseAt(int x, int y);
-    void       getValuesAt(int x, int y, float &real, float &imaginary);
-    bool       isColumnReady(int x);
-
-    bool       getMagnitudesAt(int x, float *values, int minbin = 0, int count = 0, int step = 1);
-    bool       getNormalizedMagnitudesAt(int x, float *values, int minbin = 0, int count = 0, int step = 1);
-    bool       getPhasesAt(int x, float *values, int minbin = 0, int count = 0, int step = 1);
-    bool       getValuesAt(int x, float *reals, float *imaginaries, int minbin = 0, int count = 0, int step = 1);
-
-    void       suspend();
-    void       suspendWrites();
-    void       resume(); // also happens automatically if new data needed
-
-    // Convenience functions:
-
-    bool isLocalPeak(int x, int 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(int x, int y, float threshold) {
-        return getMagnitudeAt(x, y) > threshold;
-    }
-
-    QString getError() const;
-    int getFillCompletion() const;
-    int getFillExtent() const;
-
-private:
-    FFTDataServer(QString fileBaseName,
-                  const DenseTimeValueModel *model,
-                  int channel,
-                  WindowType windowType,
-                  int windowSize,
-                  int windowIncrement,
-                  int fftSize,
-                  bool polar,
-                  StorageAdviser::Criteria criteria,
-                  int 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;
-
-    int m_windowSize;
-    int m_windowIncrement;
-    int m_fftSize;
-    bool m_polar;
-
-    int m_width;
-    int m_height;
-    int m_cacheWidth;
-    int m_cacheWidthPower;
-    int 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(int x, int &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(int x, int &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(int 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(int w, int 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, int fillFromColumn) :
-            m_server(server), m_extent(0), m_completion(0),
-            m_fillFrom(fillFromColumn) { }
-
-        int getExtent() const { return m_extent; }
-        int getCompletion() const { return m_completion ? m_completion : 1; }
-        QString getError() const { return m_error; }
-        virtual void run();
-
-    protected:
-        FFTDataServer &m_server;
-        int m_extent;
-        int m_completion;
-        int m_fillFrom;
-        QString m_error;
-    };
-
-    bool m_exiting;
-    bool m_suspended;
-    FillThread *m_fillThread;
-    QString m_error;
-
-    void deleteProcessingData();
-    void fillColumn(int x);
-    void fillComplete();
-
-    QString generateFileBasename() const;
-    static QString generateFileBasename(const DenseTimeValueModel *model,
-                                        int channel,
-                                        WindowType windowType,
-                                        int windowSize,
-                                        int windowIncrement,
-                                        int 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	Fri Sep 12 11:38:55 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,278 +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;
-}
-
-int
-FFTFileCacheReader::getWidth() const
-{
-    return m_mfc->getWidth();
-}
-
-int
-FFTFileCacheReader::getHeight() const
-{
-    int mh = m_mfc->getHeight();
-    if (mh > m_factorSize) return (mh - m_factorSize) / 2;
-    else return 0;
-}
-
-float
-FFTFileCacheReader::getMagnitudeAt(int x, int 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(int x, int y) const
-{
-    float value = 0.f;
-
-    switch (m_storageType) {
-
-    case FFTCache::Compact:
-        value = getFromReadBufCompactUnsigned(x, y * 2) / 65535.0;
-        break;
-
-    case FFTCache::Rectangular:
-    case FFTCache::Polar:
-    {
-        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(int x) const
-{
-    return getNormalizationFactor(x);
-}
-
-float
-FFTFileCacheReader::getPhaseAt(int x, int 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(int x, int 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;
-
-    case FFTCache::Compact:
-    case FFTCache::Polar:
-        float mag = getMagnitudeAt(x, y);
-        float phase = getPhaseAt(x, y);
-        real = mag * cosf(phase);
-        imag = mag * sinf(phase);
-        return;
-    }
-}
-
-void
-FFTFileCacheReader::getMagnitudesAt(int x, float *values, int minbin, int count, int step) const
-{
-    Profiler profiler("FFTFileCacheReader::getMagnitudesAt");
-
-    switch (m_storageType) {
-
-    case FFTCache::Compact:
-        for (int i = 0; i < count; ++i) {
-            int y = minbin + i * step;
-            values[i] = (getFromReadBufCompactUnsigned(x, y * 2) / 65535.0)
-                * getNormalizationFactor(x);
-        }
-        break;
-
-    case FFTCache::Rectangular:
-    {
-        float real, imag;
-        for (int i = 0; i < count; ++i) {
-            int 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 (int i = 0; i < count; ++i) {
-            int y = minbin + i * step;
-            values[i] = getFromReadBufStandard(x, y * 2);
-        }
-        break;
-    }
-}
-
-bool
-FFTFileCacheReader::haveSetColumnAt(int 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);
-}
-
-int
-FFTFileCacheReader::getCacheSize(int width, int height,
-                                 FFTCache::StorageType type)
-{
-    return (height * 2 + (type == FFTCache::Compact ? 2 : 1)) * width *
-        (type == FFTCache::Compact ? sizeof(uint16_t) : sizeof(float)) +
-        2 * sizeof(int); // matrix file header size
-}
-
-void
-FFTFileCacheReader::populateReadBuf(int 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	Fri Sep 12 11:38:55 2014 +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();
-
-    int getWidth() const;
-    int getHeight() const;
-	
-    float getMagnitudeAt(int x, int y) const;
-    float getNormalizedMagnitudeAt(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 &imag) const;
-    void getMagnitudesAt(int x, float *values, int minbin, int count, int step) const;
-
-    bool haveSetColumnAt(int x) const;
-
-    static int getCacheSize(int width, int height,
-                               FFTCache::StorageType type);
-
-    FFTCache::StorageType getStorageType() const { return m_storageType; }
-
-protected:
-    mutable char *m_readbuf;
-    mutable int m_readbufCol;
-    mutable int m_readbufWidth;
-    mutable bool m_readbufGood;
-
-    float getFromReadBufStandard(int x, int 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(int x, int 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(int x, int 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(int x) const;
-
-    float getNormalizationFactor(int col) const {
-        int 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);
-            }
-            int 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;
-    int m_factorSize;
-    MatrixFile *m_mfc;
-};
-
-#endif
--- a/data/fft/FFTFileCacheWriter.cpp	Fri Sep 12 11:38:55 2014 +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,
-                                       int width, int 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;
-}
-
-int
-FFTFileCacheWriter::getWidth() const
-{
-    return m_mfc->getWidth();
-}
-
-int
-FFTFileCacheWriter::getHeight() const
-{
-    int mh = m_mfc->getHeight();
-    if (mh > m_factorSize) return (mh - m_factorSize) / 2;
-    else return 0;
-}
-
-bool
-FFTFileCacheWriter::haveSetColumnAt(int x) const
-{
-    return m_mfc->haveSetColumnAt(x);
-}
-
-void
-FFTFileCacheWriter::setColumnAt(int x, float *mags, float *phases, float factor)
-{
-    int h = getHeight();
-
-    switch (m_storageType) {
-
-    case FFTCache::Compact:
-        for (int 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 (int 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 (int 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(int x, float *real, float *imag)
-{
-    int h = getHeight();
-
-    float factor = 0.0f;
-
-    switch (m_storageType) {
-
-    case FFTCache::Compact:
-        for (int y = 0; y < h; ++y) {
-            float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]);
-            if (mag > factor) factor = mag;
-        }
-        for (int 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 (int 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 (int 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);
-}
-
-int
-FFTFileCacheWriter::getCacheSize(int width, int height,
-                                 FFTCache::StorageType type)
-{
-    return (height * 2 + (type == FFTCache::Compact ? 2 : 1)) * width *
-        (type == FFTCache::Compact ? sizeof(uint16_t) : sizeof(float)) +
-        2 * sizeof(int); // 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	Fri Sep 12 11:38:55 2014 +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,
-                       int width, int height);
-    ~FFTFileCacheWriter();
-
-    int getWidth() const;
-    int getHeight() const;
-
-    void setColumnAt(int x, float *mags, float *phases, float factor);
-    void setColumnAt(int x, float *reals, float *imags);
-
-    static int getCacheSize(int width, int height,
-                               FFTCache::StorageType type);
-
-    bool haveSetColumnAt(int x) const;
-
-    void allColumnsWritten();
-
-    QString getFileBase() const;
-    FFTCache::StorageType getStorageType() const { return m_storageType; }
-
-protected:
-    char *m_writebuf;
-
-    void setNormalizationFactorToWritebuf(float newfactor) {
-        int 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;
-    int m_factorSize;
-    MatrixFile *m_mfc;
-};
-
-#endif
--- a/data/fft/FFTMemoryCache.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,218 +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,
-                               int width, int 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 (int 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");
-
-    int 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 (int 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 (int 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(int 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 (int 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 (int 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(int x, float *reals, float *imags)
-{
-    Profiler profiler("FFTMemoryCache::setColumnAt: from cart");
-
-    float max = 0.0;
-
-    switch (m_storageType) {
-
-    case FFTCache::Rectangular:
-        for (int 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 (int 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);
-    }
-}
-
-int
-FFTMemoryCache::getCacheSize(int width, int height, FFTCache::StorageType type)
-{
-    int sz = 0;
-
-    switch (type) {
-
-    case FFTCache::Compact:
-        sz = (height * 2 + 1) * width * sizeof(uint16_t);
-        break;
-
-    case FFTCache::Polar:
-    case FFTCache::Rectangular:
-        sz = (height * 2 + 1) * width * sizeof(float);
-        break;
-    }
-
-    return sz;
-}
-
--- a/data/fft/FFTMemoryCache.h	Fri Sep 12 11:38:55 2014 +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,
-                   int width, int height);
-    ~FFTMemoryCache();
-	
-    int getWidth() const { return m_width; }
-    int getHeight() const { return m_height; }
-	
-    float getMagnitudeAt(int x, int 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(int x, int 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(int x) const {
-        return m_factor[x];
-    }
-    
-    float getPhaseAt(int x, int 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(int x, int 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(int x, float *values, int minbin, int count, int step) const
-    {
-        if (m_storageType == FFTCache::Rectangular) {
-            for (int i = 0; i < count; ++i) {
-                int 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 (int i = 0; i < count; ++i) {
-                int y = i * step + minbin;
-                values[i] = m_fmagnitude[x][y] * m_factor[x];
-            }
-        } else {
-            for (int i = 0; i < count; ++i) {
-                int y = i * step + minbin;
-                values[i] = (float(m_magnitude[x][y]) * m_factor[x]) / 65535.0;
-            }
-        }
-    }
-
-    bool haveSetColumnAt(int x) const {
-        m_colsetLock.lockForRead();
-        bool have = m_colset.get(x);
-        m_colsetLock.unlock();
-        return have;
-    }
-
-    void setColumnAt(int x, float *mags, float *phases, float factor);
-
-    void setColumnAt(int x, float *reals, float *imags);
-
-    void allColumnsWritten() { } 
-
-    static int getCacheSize(int width, int height,
-                               FFTCache::StorageType type);
-
-    FFTCache::StorageType getStorageType() const { return m_storageType; }
-
-private:
-    int m_width;
-    int 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(int x, float factor) {
-        if (x < m_width) m_factor[x] = factor;
-    }
-    
-    void setMagnitudeAt(int x, int y, float mag) {
-         // norm factor must already be set
-        setNormalizedMagnitudeAt(x, y, mag / m_factor[x]);
-    }
-    
-    void setNormalizedMagnitudeAt(int x, int 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(int x, int 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.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fft/FFTapi.h	Fri Jun 26 14:08:05 2015 +0100
@@ -50,5 +50,48 @@
 
 #endif
 
+#include <vector>
+#include <complex>
+
+class FFTForward // with fft shift but not window
+{
+public:
+    FFTForward(int size) :
+        m_size(size),
+        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_MEASURE))
+    { }
+
+    ~FFTForward() {
+        fftf_destroy_plan(m_plan);
+        fftf_free(m_input);
+        fftf_free(m_output);
+    }
+
+    std::vector<std::complex<float> > process(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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/AudioFileReader.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -15,26 +15,26 @@
 
 #include "AudioFileReader.h"
 
-void
-AudioFileReader::getDeInterleavedFrames(int start, int 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);
     
     int channels = getChannelCount();
-    int rc = interleaved.size() / channels;
+    if (channels == 1) return { interleaved };
+    
+    sv_frame_t rc = interleaved.size() / channels;
 
-    frames.clear();
-
+    vector<vector<float>> frames(channels, vector<float>(rc, 0.f));
+    
     for (int c = 0; c < channels; ++c) {
-        frames.push_back(SampleBlock());
+        for (sv_frame_t i = 0; i < rc; ++i) {
+            frames[c][i] = interleaved[i * channels + c];
+        }
     }
 
-    for (int i = 0; i < rc; ++i) {
-        for (int c = 0; c < channels; ++c) {
-            frames[c].push_back(interleaved[i * channels + c]);
-        }
-    }
+    return frames;
 }
 
--- a/data/fileio/AudioFileReader.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/AudioFileReader.h	Fri Jun 26 14:08:05 2015 +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 ""; }
 
-    int getFrameCount() const { return m_frameCount; }
+    sv_frame_t getFrameCount() const { return m_frameCount; }
     int getChannelCount() const { return m_channelCount; }
-    int getSampleRate() const { return m_sampleRate; }
+    sv_samplerate_t getSampleRate() const { return m_sampleRate; }
 
-    virtual int 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(int start, int 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(int start, int 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:
-    int m_frameCount;
+    sv_frame_t m_frameCount;
     int m_channelCount;
-    int m_sampleRate;
+    sv_samplerate_t m_sampleRate;
 };
 
 #endif
--- a/data/fileio/AudioFileReaderFactory.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/AudioFileReaderFactory.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -21,6 +21,9 @@
 #include "MP3FileReader.h"
 #include "QuickTimeFileReader.h"
 #include "CoreAudioFileReader.h"
+#include "AudioFileSizeEstimator.h"
+
+#include "base/StorageAdviser.h"
 
 #include <QString>
 #include <QFileInfo>
@@ -59,7 +62,7 @@
 
 AudioFileReader *
 AudioFileReaderFactory::createReader(FileSource source, 
-                                     int targetRate,
+                                     sv_samplerate_t targetRate,
                                      bool normalised,
                                      ProgressReporter *reporter)
 {
@@ -68,7 +71,7 @@
 
 AudioFileReader *
 AudioFileReaderFactory::createThreadingReader(FileSource source, 
-                                              int targetRate,
+                                              sv_samplerate_t targetRate,
                                               bool normalised,
                                               ProgressReporter *reporter)
 {
@@ -77,7 +80,7 @@
 
 AudioFileReader *
 AudioFileReaderFactory::create(FileSource source, 
-                               int targetRate, 
+                               sv_samplerate_t targetRate, 
                                bool normalised,
                                bool threading,
                                ProgressReporter *reporter)
@@ -98,175 +101,132 @@
 
     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() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl;
+            SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", in memory " << (cacheMode == CodedAudioFileReader::CacheInMemory) << ", creating decoding reader" << endl;
 
             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,
-                 normalised,
-                 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,
-                 normalised,
-                 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,
-                 normalised,
-                 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,
-                 normalised,
-                 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() ||
-             normalised ||
-             (targetRate != 0 && fileRate != targetRate))) {
+        SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", in memory " << (cacheMode == CodedAudioFileReader::CacheInMemory) << ", creating decoding reader" << endl;
 
-            SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", 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,
-                 normalised,
-                 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
@@ -274,76 +234,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/AudioFileReaderFactory.h	Fri Jun 26 14:08:05 2015 +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;
@@ -53,7 +54,7 @@
      * Caller owns the returned object and must delete it after use.
      */
     static AudioFileReader *createReader(FileSource source,
-                                         int targetRate = 0,
+                                         sv_samplerate_t targetRate = 0,
                                          bool normalised = false,
                                          ProgressReporter *reporter = 0);
 
@@ -82,13 +83,13 @@
      * Caller owns the returned object and must delete it after use.
      */
     static AudioFileReader *createThreadingReader(FileSource source,
-                                                  int targetRate = 0,
+                                                  sv_samplerate_t targetRate = 0,
                                                   bool normalised = false,
                                                   ProgressReporter *reporter = 0);
 
 protected:
     static AudioFileReader *create(FileSource source,
-                                   int 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	Fri Jun 26 14:08:05 2015 +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	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/BZipFileDevice.cpp	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/CSVFileReader.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -26,6 +26,7 @@
 #include "DataFileReaderFactory.h"
 
 #include <QFile>
+#include <QFileInfo>
 #include <QString>
 #include <QRegExp>
 #include <QStringList>
@@ -35,44 +36,57 @@
 #include <map>
 
 CSVFileReader::CSVFileReader(QString path, CSVFormat format,
-                             int 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
@@ -81,8 +95,9 @@
     return m_error;
 }
 
-int
-CSVFileReader::convertTimeValue(QString s, int lineno, int sampleRate,
+sv_frame_t
+CSVFileReader::convertTimeValue(QString s, int lineno,
+                                sv_samplerate_t sampleRate,
                                 int windowSize) const
 {
     QRegExp nonNumericRx("[^0-9eE.,+-]");
@@ -90,7 +105,7 @@
 
     CSVFormat::TimeUnits timeUnits = m_format.getTimeUnits();
 
-    int calculatedFrame = 0;
+    sv_frame_t calculatedFrame = 0;
 
     bool ok = false;
     QString numeric = s;
@@ -100,7 +115,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 {
         
@@ -130,12 +151,12 @@
 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();
-    int sampleRate = m_format.getSampleRate();
+    sv_samplerate_t sampleRate = m_format.getSampleRate();
     int windowSize = m_format.getWindowSize();
     QChar separator = m_format.getSeparator();
     bool allowQuoting = m_format.getAllowQuoting();
@@ -149,7 +170,8 @@
         } else {
             windowSize = 1;
         }
-	if (timeUnits == CSVFormat::TimeSeconds) {
+	if (timeUnits == CSVFormat::TimeSeconds ||
+            timeUnits == CSVFormat::TimeMilliseconds) {
 	    sampleRate = m_mainModelSampleRate;
 	}
     }
@@ -161,23 +183,22 @@
     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;
 
-    int frameNo = 0;
-    int duration = 0;
-    int 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;
 
-    int 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;
@@ -208,7 +229,7 @@
         for (int li = 0; li < lines.size(); ++li) {
 
             QString line = lines[li];
-
+            
             if (line.startsWith("#")) continue;
 
             QStringList list = StringBits::split(line, separator, allowQuoting);
@@ -245,6 +266,12 @@
                     model = model3;
                     break;
                 }
+
+                if (model) {
+                    if (m_filename != "") {
+                        model->setObjectName(m_filename);
+                    }
+                }
             }
 
             float value = 0.f;
@@ -347,7 +374,7 @@
                         model3->setStartFrame(startFrame);
                     } else if (lineno == 1 &&
                                timingType == CSVFormat::ExplicitTiming) {
-                        model3->setResolution(frameNo - startFrame);
+                        model3->setResolution(int(frameNo - startFrame));
                     }
                     
                     firstEverValue = false;
--- a/data/fileio/CSVFileReader.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/CSVFileReader.h	Fri Jun 26 14:08:05 2015 +0100
@@ -20,6 +20,8 @@
 
 #include "CSVFormat.h"
 
+#include "base/BaseTypes.h"
+
 #include <QList>
 #include <QStringList>
 
@@ -28,22 +30,38 @@
 class CSVFileReader : public DataFileReader
 {
 public:
-    CSVFileReader(QString path, CSVFormat format, int 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;
-    int m_mainModelSampleRate;
+    sv_samplerate_t m_mainModelSampleRate;
 
-    int convertTimeValue(QString, int lineno, int sampleRate,
-                            int windowSize) const;
+    sv_frame_t convertTimeValue(QString, int lineno, sv_samplerate_t sampleRate,
+                                int windowSize) const;
 };
 
 
--- a/data/fileio/CSVFileWriter.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/CSVFileWriter.cpp	Fri Jun 26 14:08:05 2015 +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) {
 	
-            int f0(i->getStartFrame()), f1(i->getEndFrame());
-            out << m_model->toDelimitedDataStringSubset(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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/CSVFileWriter.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/CSVFormat.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/CSVFormat.h	Fri Jun 26 14:08:05 2015 +0100
@@ -19,6 +19,8 @@
 #include <QString>
 #include <QStringList>
 
+#include "base/BaseTypes.h"
+
 class CSVFormat
 {
 public:
@@ -37,8 +39,9 @@
 
     enum TimeUnits {
 	TimeSeconds,
+        TimeMilliseconds,
 	TimeAudioFrames,
-	TimeWindows
+	TimeWindows,
     };
 
     enum ColumnPurpose {
@@ -52,10 +55,11 @@
     };
 
     enum ColumnQuality {
-        ColumnNumeric    = 0x1,
-        ColumnIntegral   = 0x2,
-        ColumnIncreasing = 0x4,
-        ColumnLarge      = 0x8
+        ColumnNumeric    = 1,
+        ColumnIntegral   = 2,
+        ColumnIncreasing = 4,
+        ColumnLarge      = 8,
+        ColumnNearEmpty  = 16,
     };
     typedef unsigned int ColumnQualities;
 
@@ -86,7 +90,7 @@
     ModelType    getModelType()     const { return m_modelType;     }
     TimingType   getTimingType()    const { return m_timingType;    }
     TimeUnits    getTimeUnits()     const { return m_timeUnits;     }
-    int          getSampleRate()    const { return m_sampleRate;    }
+    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;  }
@@ -99,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(int r)          { m_sampleRate   = r; }
-    void setWindowSize(int 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; }
 
@@ -123,7 +127,7 @@
     TimingType   m_timingType;
     TimeUnits    m_timeUnits;
     QString      m_separator;
-    int          m_sampleRate;
+    sv_samplerate_t m_sampleRate;
     int          m_windowSize;
 
     int          m_columnCount;
--- a/data/fileio/CodedAudioFileReader.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/CodedAudioFileReader.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -21,14 +21,17 @@
 #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,
-                                           int targetRate,
+                                           sv_samplerate_t targetRate,
                                            bool normalised) :
     m_cacheMode(cacheMode),
     m_initialised(false),
@@ -57,7 +60,7 @@
     QMutexLocker locker(&m_cacheMutex);
 
     endSerialised();
-
+    
     if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr);
 
     SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file reader" << endl;
@@ -73,6 +76,12 @@
 
     delete m_resampler;
     delete[] m_resampleBuffer;
+
+    if (!m_data.empty()) {
+        StorageAdviser::notifyDoneAllocation
+            (StorageAdviser::MemoryAllocation,
+             (m_data.size() * sizeof(float)) / 1024);
+    }
 }
 
 void
@@ -113,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];
@@ -129,9 +138,15 @@
                                            .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.
@@ -176,13 +191,13 @@
 }
 
 void
-CodedAudioFileReader::addSamplesToDecodeCache(float **samples, int nframes)
+CodedAudioFileReader::addSamplesToDecodeCache(float **samples, sv_frame_t nframes)
 {
     QMutexLocker locker(&m_cacheMutex);
 
     if (!m_initialised) return;
 
-    for (int i = 0; i < nframes; ++i) {
+    for (sv_frame_t i = 0; i < nframes; ++i) {
         
         for (int c = 0; c < m_channelCount; ++c) {
 
@@ -206,13 +221,13 @@
 }
 
 void
-CodedAudioFileReader::addSamplesToDecodeCache(float *samples, int nframes)
+CodedAudioFileReader::addSamplesToDecodeCache(float *samples, sv_frame_t nframes)
 {
     QMutexLocker locker(&m_cacheMutex);
 
     if (!m_initialised) return;
 
-    for (int i = 0; i < nframes; ++i) {
+    for (sv_frame_t i = 0; i < nframes; ++i) {
         
         for (int c = 0; c < m_channelCount; ++c) {
 
@@ -236,15 +251,13 @@
 }
 
 void
-CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples)
+CodedAudioFileReader::addSamplesToDecodeCache(const vector<float> &samples)
 {
     QMutexLocker locker(&m_cacheMutex);
 
     if (!m_initialised) return;
 
-    for (int i = 0; i < (int)samples.size(); ++i) {
-
-        float sample = samples[i];
+    for (float sample: samples) {
         
         m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
 
@@ -288,23 +301,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, int 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);
@@ -312,13 +332,13 @@
 }
 
 void
-CodedAudioFileReader::pushBufferNonResampling(float *buffer, int sz)
+CodedAudioFileReader::pushBufferNonResampling(float *buffer, sv_frame_t sz)
 {
     float clip = 1.0;
-    int count = sz * m_channelCount;
+    sv_frame_t count = sz * m_channelCount;
 
     if (m_normalised) {
-        for (int i = 0; i < count; ++i) {
+        for (sv_frame_t i = 0; i < count; ++i) {
             float v = fabsf(buffer[i]);
             if (v > m_max) {
                 m_max = v;
@@ -326,10 +346,10 @@
             }
         }
     } else {
-        for (int i = 0; i < count; ++i) {
+        for (sv_frame_t i = 0; i < count; ++i) {
             if (buffer[i] >  clip) buffer[i] =  clip;
         }
-        for (int i = 0; i < count; ++i) {
+        for (sv_frame_t i = 0; i < count; ++i) {
             if (buffer[i] < -clip) buffer[i] = -clip;
         }
     }
@@ -339,7 +359,7 @@
     switch (m_cacheMode) {
 
     case CacheInTemporaryFile:
-        if (sf_writef_float(m_cacheFileWritePtr, buffer, sz) < (int)sz) {
+        if (sf_writef_float(m_cacheFileWritePtr, buffer, sz) < sz) {
             sf_close(m_cacheFileWritePtr);
             m_cacheFileWritePtr = 0;
             throw InsufficientDiscSpace(TempDirectory::getInstance()->getPath());
@@ -347,25 +367,22 @@
         break;
 
     case CacheInMemory:
-        m_dataLock.lockForWrite();
-        for (int 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, int 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) {
 
-        int out = m_resampler->resampleInterleaved
+        sv_frame_t out = m_resampler->resampleInterleaved
             (buffer,
              m_resampleBuffer,
              sz,
@@ -377,27 +394,27 @@
 
     if (final) {
 
-        int 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;
         }
 
-        int 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;
 
-        int out = m_resampler->resampleInterleaved
+        sv_frame_t out = m_resampler->resampleInterleaved
             (padding,
              m_resampleBuffer,
              padFrames,
              ratio,
              true);
 
-        if (int(m_frameCount + out) > int(m_fileFrameCount * ratio)) {
-            out = int(m_fileFrameCount * ratio) - int(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);
@@ -405,9 +422,8 @@
     }
 }
 
-void
-CodedAudioFileReader::getInterleavedFrames(int start, int 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
@@ -415,40 +431,43 @@
 
     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 {};
 
-        int idx = start * m_channelCount;
-        int 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 < (int)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 (int i = 0; i < (int)(count * m_channelCount); ++i) {
-            frames[i] *= m_gain;
-        }
+        for (auto &f: frames) f *= m_gain;
     }
+
+    return frames;
 }
 
--- a/data/fileio/CodedAudioFileReader.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/CodedAudioFileReader.h	Fri Jun 26 14:08:05 2015 +0100
@@ -38,11 +38,17 @@
         CacheInMemory
     };
 
-    virtual void getInterleavedFrames(int start, int count,
-				      SampleBlock &frames) const;
+    enum DecodeMode {
+        DecodeAtOnce, // decode the file on construction, with progress 
+        DecodeThreaded // decode in a background thread after construction
+    };
 
-    virtual int 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; }
 
@@ -51,15 +57,15 @@
 
 protected:
     CodedAudioFileReader(CacheMode cacheMode, 
-                         int targetRate,
+                         sv_samplerate_t targetRate,
                          bool normalised);
 
     void initialiseDecodeCache(); // samplerate, channels must have been set
 
     // may throw InsufficientDiscSpace:
-    void addSamplesToDecodeCache(float **samples, int nframes);
-    void addSamplesToDecodeCache(float *samplesInterleaved, int 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();
@@ -70,29 +76,29 @@
     void endSerialised();
 
 private:
-    void pushBuffer(float *interleaved, int sz, bool final);
-    void pushBufferResampling(float *interleaved, int sz, float ratio, bool final);
-    void pushBufferNonResampling(float *interleaved, int 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;
-    int m_fileRate;
+    sv_samplerate_t m_fileRate;
 
     QString m_cacheFileName;
     SNDFILE *m_cacheFileWritePtr;
     WavFileReader *m_cacheFileReader;
     float *m_cacheWriteBuffer;
-    int m_cacheWriteBufferIndex;
-    int m_cacheWriteBufferSize; // frames
+    sv_frame_t m_cacheWriteBufferIndex;
+    sv_frame_t m_cacheWriteBufferSize; // frames
 
     Resampler *m_resampler;
     float *m_resampleBuffer;
-    int m_fileFrameCount;
+    sv_frame_t m_fileFrameCount;
 
     bool m_normalised;
     float m_max;
--- a/data/fileio/CoreAudioFileReader.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/CoreAudioFileReader.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -59,7 +59,7 @@
 CoreAudioFileReader::CoreAudioFileReader(FileSource source,
                                          DecodeMode decodeMode,
                                          CacheMode mode,
-                                         int targetRate,
+                                         sv_samplerate_t targetRate,
                                          bool normalised,
                                          ProgressReporter *reporter) :
     CodedAudioFileReader(mode, targetRate, normalised),
--- a/data/fileio/CoreAudioFileReader.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/CoreAudioFileReader.h	Fri Jun 26 14:08:05 2015 +0100
@@ -31,17 +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,
-                        int targetRate = 0,
+                        sv_samplerate_t targetRate = 0,
                         bool normalised = false,
-                        ProgressReporter *reporter = 0);
+                        ProgressReporter *reporter = nullptr);
     virtual ~CoreAudioFileReader();
 
     virtual QString getError() const { return m_error; }
--- a/data/fileio/DataFileReaderFactory.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/DataFileReaderFactory.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -32,7 +32,7 @@
                                     bool csv,
                                     MIDIFileImportPreferenceAcquirer *acquirer,
                                     CSVFormat format,
-                                    int mainModelSampleRate)
+                                    sv_samplerate_t mainModelSampleRate)
 {
     QString err;
 
@@ -58,7 +58,7 @@
 DataFileReader *
 DataFileReaderFactory::createReader(QString path,
                                     MIDIFileImportPreferenceAcquirer *acquirer,
-                                    int mainModelSampleRate)
+                                    sv_samplerate_t mainModelSampleRate)
 {
     DataFileReader *reader = createReader
         (path, false, acquirer, CSVFormat(), mainModelSampleRate);
@@ -74,7 +74,7 @@
 Model *
 DataFileReaderFactory::load(QString path,
                             MIDIFileImportPreferenceAcquirer *acquirer,
-                            int mainModelSampleRate)
+                            sv_samplerate_t mainModelSampleRate)
 {
     DataFileReader *reader = createReader(path,
                                           acquirer,
@@ -94,7 +94,7 @@
 Model *
 DataFileReaderFactory::loadNonCSV(QString path,
                                   MIDIFileImportPreferenceAcquirer *acquirer,
-                                  int mainModelSampleRate)
+                                  sv_samplerate_t mainModelSampleRate)
 {
     DataFileReader *reader = createReader(path, false,
                                           acquirer,
@@ -114,7 +114,7 @@
 
 Model *
 DataFileReaderFactory::loadCSV(QString path, CSVFormat format,
-                               int mainModelSampleRate)
+                               sv_samplerate_t mainModelSampleRate)
 {
     DataFileReader *reader = createReader(path, true, 0, format,
                                           mainModelSampleRate);
--- a/data/fileio/DataFileReaderFactory.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/DataFileReaderFactory.h	Fri Jun 26 14:08:05 2015 +0100
@@ -48,7 +48,7 @@
      */
     static DataFileReader *createReader(QString path,
                                         MIDIFileImportPreferenceAcquirer *,
-					int 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,
-                       int 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,
-                             int 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,
-                          int mainModelSampleRate);
+                          sv_samplerate_t mainModelSampleRate);
 
 protected:
     static DataFileReader *createReader(QString path, bool csv,
                                         MIDIFileImportPreferenceAcquirer *,
                                         CSVFormat format,
-					int mainModelSampleRate);
+					sv_samplerate_t mainModelSampleRate);
 };
 
 #endif
--- a/data/fileio/DecodingWavFileReader.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/DecodingWavFileReader.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -21,10 +21,12 @@
 
 #include <QFileInfo>
 
+using namespace std;
+
 DecodingWavFileReader::DecodingWavFileReader(FileSource source,
-                                             ResampleMode resampleMode,
+                                             DecodeMode decodeMode,
                                              CacheMode mode,
-                                             int targetRate,
+                                             sv_samplerate_t targetRate,
                                              bool normalised,
                                              ProgressReporter *reporter) :
     CodedAudioFileReader(mode, targetRate, normalised),
@@ -56,7 +58,7 @@
 
     initialiseDecodeCache();
 
-    if (resampleMode == ResampleAtOnce) {
+    if (decodeMode == DecodeAtOnce) {
 
         if (m_reporter) {
             connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
@@ -64,17 +66,17 @@
                 (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
         }
 
-        int blockSize = 16384;
-        int total = m_original->getFrameCount();
+        sv_frame_t blockSize = 16384;
+        sv_frame_t total = m_original->getFrameCount();
 
-        SampleBlock block;
+        vector<float> block;
 
-        for (int i = 0; i < total; i += blockSize) {
+        for (sv_frame_t i = 0; i < total; i += blockSize) {
 
-            int 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;
@@ -121,17 +123,17 @@
         m_reader->startSerialised("DecodingWavFileReader::Decode");
     }
 
-    int blockSize = 16384;
-    int 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 (int i = 0; i < total; i += blockSize) {
+    for (sv_frame_t i = 0; i < total; i += blockSize) {
         
-        int 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;
@@ -147,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;
@@ -167,7 +169,7 @@
 }
 
 void
-DecodingWavFileReader::getSupportedExtensions(std::set<QString> &extensions)
+DecodingWavFileReader::getSupportedExtensions(set<QString> &extensions)
 {
     WavFileReader::getSupportedExtensions(extensions);
 }
--- a/data/fileio/DecodingWavFileReader.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/DecodingWavFileReader.h	Fri Jun 26 14:08:05 2015 +0100
@@ -29,15 +29,10 @@
 {
     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,
+                          DecodeMode decodeMode, // determines when to resample
                           CacheMode cacheMode,
-                          int targetRate = 0,
+                          sv_samplerate_t targetRate = 0,
                           bool normalised = false,
                           ProgressReporter *reporter = 0);
     virtual ~DecodingWavFileReader();
@@ -63,13 +58,13 @@
     QString m_path;
     QString m_error;
     bool m_cancelled;
-    int 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	Fri Sep 12 11:38:55 2014 +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,
-				 int windowSize,
-				 int windowIncrement,
-				 int fftSize,
-				 bool polar,
-				 int fillFromColumn) :
-    m_server(0),
-    m_xshift(0),
-    m_yshift(0)
-{
-    m_server = FFTDataServer::getFuzzyInstance(model,
-                                               channel,
-                                               windowType,
-                                               windowSize,
-                                               windowIncrement,
-                                               fftSize,
-                                               polar,
-                                               fillFromColumn);
-
-    int xratio = windowIncrement / m_server->getWindowIncrement();
-    int 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	Fri Sep 12 11:38:55 2014 +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,
-                    int windowSize,
-                    int windowIncrement,
-                    int fftSize,
-                    bool polar,
-                    int fillFromColumn = 0);
-    ~FFTFuzzyAdapter();
-
-    int getWidth() const {
-        return m_server->getWidth() >> m_xshift;
-    }
-    int getHeight() const {
-        return m_server->getHeight() >> m_yshift;
-    }
-    float getMagnitudeAt(int x, int y) {
-        return m_server->getMagnitudeAt(x << m_xshift, y << m_yshift);
-    }
-    float getNormalizedMagnitudeAt(int x, int y) {
-        return m_server->getNormalizedMagnitudeAt(x << m_xshift, y << m_yshift);
-    }
-    float getMaximumMagnitudeAt(int x) {
-        return m_server->getMaximumMagnitudeAt(x << m_xshift);
-    }
-    float getPhaseAt(int x, int y) {
-        return m_server->getPhaseAt(x << m_xshift, y << m_yshift);
-    }
-    void getValuesAt(int x, int y, float &real, float &imaginary) {
-        m_server->getValuesAt(x << m_xshift, y << m_yshift, real, imaginary);
-    }
-    bool isColumnReady(int x) {
-        return m_server->isColumnReady(x << m_xshift);
-    }
-    bool isLocalPeak(int x, int 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(int x, int y, float threshold) {
-        return getMagnitudeAt(x, y) > threshold;
-    }
-
-    int getFillCompletion() const { return m_server->getFillCompletion(); }
-    int 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/FileSource.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/FileSource.cpp	Fri Jun 26 14:08:05 2015 +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;
@@ -268,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;
@@ -463,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()),
@@ -487,6 +509,7 @@
     m_done = true;
     if (m_reply) {
         QNetworkReply *r = m_reply;
+        disconnect(r, 0, this, 0);
         m_reply = 0;
         // Can only call abort() when there are no errors.
         if (r->error() == QNetworkReply::NoError) {
--- a/data/fileio/FileSource.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/FileSource.h	Fri Jun 26 14:08:05 2015 +0100
@@ -167,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;
 
@@ -195,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
--- a/data/fileio/MIDIFileReader.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/MIDIFileReader.cpp	Fri Jun 26 14:08:05 2015 +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,
-			       int 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
@@ -889,7 +892,7 @@
 
     if (tracksToLoad.empty()) return 0;
 
-    int n = tracksToLoad.size(), count = 0;
+    int n = int(tracksToLoad.size()), count = 0;
     Model *model = 0;
 
     for (std::set<unsigned int>::iterator i = tracksToLoad.begin();
@@ -932,11 +935,12 @@
     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;
 
-    int totalEvents = track.size();
+    int totalEvents = int(track.size());
     int count = 0;
 
     bool sharpKey = true;
--- a/data/fileio/MIDIFileReader.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/MIDIFileReader.h	Fri Jun 26 14:08:05 2015 +0100
@@ -62,7 +62,7 @@
 public:
     MIDIFileReader(QString path,
                    MIDIFileImportPreferenceAcquirer *pref,
-                   int mainModelSampleRate);
+                   sv_samplerate_t mainModelSampleRate);
     virtual ~MIDIFileReader();
 
     virtual bool isOK() const;
@@ -126,9 +126,9 @@
 
     QString                m_path;
     std::ifstream         *m_midiFile;
-    int                    m_fileSize;
+    size_t                 m_fileSize;
     QString                m_error;
-    int                    m_mainModelSampleRate;
+    sv_samplerate_t        m_mainModelSampleRate;
 
     MIDIFileImportPreferenceAcquirer *m_acquirer;
 };
--- a/data/fileio/MIDIFileWriter.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/MIDIFileWriter.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -37,7 +37,7 @@
 using namespace MIDIConstants;
 
 MIDIFileWriter::MIDIFileWriter(QString path, const NoteExportable *exportable,
-                               int sampleRate, float tempo) :
+                               sv_samplerate_t sampleRate, float tempo) :
     m_path(path),
     m_exportable(exportable),
     m_sampleRate(sampleRate),
@@ -89,8 +89,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;
@@ -106,10 +106,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;
@@ -317,7 +317,6 @@
     m_numberOfTracks = 1;
 
     int track = 0;
-    int midiChannel = 0;
 
     MIDIEvent *event;
 
@@ -345,22 +344,26 @@
 
     for (NoteList::const_iterator i = notes.begin(); i != notes.end(); ++i) {
 
-        int frame = i->start;
-        int duration = i->duration;
+        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_sampleRate);
+        double seconds = double(frame) / m_sampleRate;
         double quarters = (seconds * m_tempo) / 60.0;
         unsigned long midiTime = int(quarters * m_timingDivision + 0.5);
 
         // Get the sounding time for the matching NOTE_OFF
-        seconds = double(frame + duration) / double(m_sampleRate);
+        seconds = double(frame + duration) / m_sampleRate;
         quarters = (seconds * m_tempo) / 60.0;
         unsigned long endTime = int(quarters * m_timingDivision + 0.5);
 
@@ -370,13 +373,13 @@
         // 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
 
--- a/data/fileio/MIDIFileWriter.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/MIDIFileWriter.h	Fri Jun 26 14:08:05 2015 +0100
@@ -24,6 +24,7 @@
 #define _MIDI_FILE_WRITER_H_
 
 #include "base/RealTime.h"
+#include "base/BaseTypes.h"
 
 #include <QString>
 
@@ -45,7 +46,7 @@
 public:
     MIDIFileWriter(QString path, 
                    const NoteExportable *exportable, 
-                   int sampleRate, // used to convert exportable sample timings
+                   sv_samplerate_t sampleRate, // used to convert exportable sample timings
                    float tempo = 120.f);
     virtual ~MIDIFileWriter();
 
@@ -79,7 +80,7 @@
 
     QString               m_path;
     const NoteExportable *m_exportable;
-    int                   m_sampleRate;
+    sv_samplerate_t       m_sampleRate;
     float                 m_tempo;
     int                   m_timingDivision;   // pulses per quarter note
     MIDIFileFormatType    m_format;
--- a/data/fileio/MP3FileReader.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/MP3FileReader.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -37,7 +37,7 @@
 #include <QFileInfo>
 
 MP3FileReader::MP3FileReader(FileSource source, DecodeMode decodeMode, 
-                             CacheMode mode, int targetRate,
+                             CacheMode mode, sv_samplerate_t targetRate,
                              bool normalised,
                              ProgressReporter *reporter) :
     CodedAudioFileReader(mode, targetRate, normalised),
@@ -86,7 +86,7 @@
     }
     
     ssize_t sz = 0;
-    int offset = 0;
+    ssize_t offset = 0;
     while (offset < m_fileSize) {
         sz = ::read(fd, m_filebuffer + offset, m_fileSize - offset);
         if (sz < 0) {
@@ -291,7 +291,7 @@
 } 
 
 bool
-MP3FileReader::decode(void *mm, int sz)
+MP3FileReader::decode(void *mm, sv_frame_t sz)
 {
     DecoderData data;
     struct mad_decoder decoder;
@@ -320,7 +320,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;
@@ -352,7 +352,7 @@
     int frames = pcm->length;
 
     if (header) {
-        m_bitrateNum += header->bitrate;
+        m_bitrateNum = m_bitrateNum + double(header->bitrate);
         m_bitrateDenom ++;
     }
 
--- a/data/fileio/MP3FileReader.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/MP3FileReader.h	Fri Jun 26 14:08:05 2015 +0100
@@ -32,15 +32,10 @@
     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,
-                  int targetRate = 0,
+                  sv_samplerate_t targetRate = 0,
                   bool normalised = false,
                   ProgressReporter *reporter = 0);
     virtual ~MP3FileReader();
@@ -73,7 +68,7 @@
     QString m_title;
     QString m_maker;
     TagMap m_tags;
-    int m_fileSize;
+    sv_frame_t m_fileSize;
     double m_bitrateNum;
     int m_bitrateDenom;
     int m_completion;
@@ -93,7 +88,7 @@
 	MP3FileReader *reader;
     };
 
-    bool decode(void *mm, int 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	Fri Sep 12 11:38:55 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,187 +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),
-    m_mainModel(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 && int(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	Fri Sep 12 11:38:55 2014 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/MatrixFile.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -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;
 
@@ -203,7 +213,7 @@
     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) {
--- a/data/fileio/OggVorbisFileReader.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/OggVorbisFileReader.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -34,7 +34,7 @@
 OggVorbisFileReader::OggVorbisFileReader(FileSource source,
                                          DecodeMode decodeMode,
                                          CacheMode mode,
-                                         int targetRate,
+                                         sv_samplerate_t targetRate,
                                          bool normalised,
                                          ProgressReporter *reporter) :
     CodedAudioFileReader(mode, targetRate, normalised),
@@ -144,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);
@@ -173,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/OggVorbisFileReader.h	Fri Jun 26 14:08:05 2015 +0100
@@ -34,17 +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,
-                        int targetRate = 0,
+                        sv_samplerate_t targetRate = 0,
                         bool normalised = false,
-                        ProgressReporter *reporter = 0);
+                        ProgressReporter *reporter = nullptr);
     virtual ~OggVorbisFileReader();
 
     virtual QString getError() const { return m_error; }
@@ -79,8 +74,8 @@
     OGGZ *m_oggz;
     FishSound *m_fishSound;
     ProgressReporter *m_reporter;
-    int m_fileSize;
-    int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/QuickTimeFileReader.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -50,7 +50,7 @@
 QuickTimeFileReader::QuickTimeFileReader(FileSource source,
                                          DecodeMode decodeMode,
                                          CacheMode mode,
-                                         int targetRate,
+                                         sv_samplerate_t targetRate,
                                          bool normalised,
                                          ProgressReporter *reporter) :
     CodedAudioFileReader(mode, targetRate, normalised),
--- a/data/fileio/QuickTimeFileReader.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/QuickTimeFileReader.h	Fri Jun 26 14:08:05 2015 +0100
@@ -42,7 +42,7 @@
     QuickTimeFileReader(FileSource source,
                         DecodeMode decodeMode,
                         CacheMode cacheMode,
-                        int targetRate = 0,
+                        sv_samplerate_t targetRate = 0,
                         bool normalised = false,
                         ProgressReporter *reporter = 0);
     virtual ~QuickTimeFileReader();
--- a/data/fileio/WavFileReader.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/WavFileReader.cpp	Fri Jun 26 14:08:05 2015 +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)
@@ -81,7 +81,6 @@
 WavFileReader::~WavFileReader()
 {
     if (m_file) sf_close(m_file);
-    delete[] m_buffer;
 }
 
 void
@@ -89,7 +88,7 @@
 {
     QMutexLocker locker(&m_mutex);
 
-    int prevCount = m_fileInfo.frames;
+    sv_frame_t prevCount = m_fileInfo.frames;
 
     if (m_file) {
         sf_close(m_file);
@@ -122,69 +121,56 @@
     m_updating = false;
 }
 
-void
-WavFileReader::getInterleavedFrames(int start, int 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 (int 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 +201,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/WavFileReader.h	Fri Jun 26 14:08:05 2015 +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(int start, int 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 int m_bufsiz;
-    mutable int m_lastStart;
-    mutable int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/WavFileWriter.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -23,9 +23,12 @@
 #include <QFileInfo>
 
 #include <iostream>
+#include <cmath>
+
+using namespace std;
 
 WavFileWriter::WavFileWriter(QString path,
-			     int sampleRate,
+			     sv_samplerate_t sampleRate,
                              int channels,
                              FileWriteMode mode) :
     m_path(path),
@@ -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;
     }
 
-    int 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) {
 	
-	int f0(i->getStartFrame()), f1(i->getEndFrame());
+	sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame());
 
-	for (int f = f0; f < f1; f += bs) {
+	for (sv_frame_t f = f0; f < f1; f += bs) {
 	    
-	    int 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 (int 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, int 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,17 +175,17 @@
     }
 
     float *b = new float[count * m_channels];
-    for (int i = 0; i < int(count); ++i) {
+    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;
 
-    if (written < int(count)) {
+    if (written < count) {
         m_error = QString("Only wrote %1 of %2 frames")
             .arg(written).arg(count);
     }
--- a/data/fileio/WavFileWriter.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/WavFileWriter.h	Fri Jun 26 14:08:05 2015 +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, int sampleRate, int channels,
+    WavFileWriter(QString path, sv_samplerate_t sampleRate, int channels,
                   FileWriteMode mode);
     virtual ~WavFileWriter();
 
@@ -57,13 +59,13 @@
     bool writeModel(DenseTimeValueModel *source,
                     MultiSelection *selection = 0);
 
-    bool writeSamples(float **samples, int count); // count per channel
+    bool writeSamples(float **samples, sv_frame_t count); // count per channel
 
     bool close();
 
 protected:
     QString m_path;
-    int m_sampleRate;
+    sv_samplerate_t m_sampleRate;
     int m_channels;
     TempWriteFile *m_temp;
     SNDFILE *m_file;
--- a/data/fileio/test/AudioFileReaderTest.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/test/AudioFileReaderTest.h	Fri Jun 26 14:08:05 2015 +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,24 +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();
-	
-	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
@@ -116,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) {
@@ -130,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;
                     }
@@ -180,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/test/AudioTestData.h	Fri Jun 26 14:08:05 2015 +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/test.pro	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/fileio/test/test.pro	Fri Jun 26 14:08:05 2015 +0100
@@ -38,7 +38,7 @@
     }
 }
 
-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
 
--- a/data/midi/MIDIEvent.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/midi/MIDIEvent.h	Fri Jun 26 14:08:05 2015 +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/rtmidi/RtMidi.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/midi/rtmidi/RtMidi.cpp	Fri Jun 26 14:08:05 2015 +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 );
--- a/data/model/AggregateWaveModel.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/AggregateWaveModel.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -19,6 +19,8 @@
 
 #include <QTextStream>
 
+using namespace std;
+
 PowerOfSqrtTwoZoomConstraint
 AggregateWaveModel::m_zoomConstraint;
 
@@ -65,14 +67,14 @@
     return ready;
 }
 
-int
+sv_frame_t
 AggregateWaveModel::getFrameCount() const
 {
-    int count = 0;
+    sv_frame_t count = 0;
 
     for (ChannelSpecList::const_iterator i = m_components.begin();
          i != m_components.end(); ++i) {
-        int thisCount = i->model->getEndFrame() - i->model->getStartFrame();
+        sv_frame_t thisCount = i->model->getEndFrame() - i->model->getStartFrame();
         if (thisCount > count) count = thisCount;
     }
 
@@ -82,113 +84,66 @@
 int
 AggregateWaveModel::getChannelCount() const
 {
-    return m_components.size();
+    return int(m_components.size());
 }
 
-int
+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);
-}
-
-int
-AggregateWaveModel::getData(int channel, int start, int 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 (int 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];
         }
     }
 
-    int sz = count;
+    result.resize(longest);
+    return result;
+}
 
-    for (int c = ch0; c <= ch1; ++c) {
-        int szHere = 
-            m_components[c].model->getData(m_components[c].channel,
-                                           start, count,
-                                           readbuf);
-        if (szHere < sz) sz = szHere;
-        if (mixing) {
-            for (int 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;
-}
-         
-int
-AggregateWaveModel::getData(int channel, int start, int 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 (int i = 0; i < count; ++i) {
-            buffer[i] = 0.0;
-        }
-    }
-
-    int sz = count;
-    
-    for (int c = ch0; c <= ch1; ++c) {
-        int szHere = 
-            m_components[c].model->getData(m_components[c].channel,
-                                           start, count,
-                                           readbuf);
-        if (szHere < sz) sz = szHere;
-        if (mixing) {
-            for (int 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;
-}
-
-int
-AggregateWaveModel::getData(int fromchannel, int tochannel,
-                            int start, int count,
-                            float **buffer) const
-{
-    int min = count;
-
-    for (int c = fromchannel; c <= tochannel; ++c) {
-        int here = getData(c, start, count, buffer[c - fromchannel]);
-        if (here < min) min = here;
-    }
-    
-    return min;
+    return result;
 }
 
 int
@@ -199,14 +154,14 @@
 }
         
 void
-AggregateWaveModel::getSummaries(int, int, int,
+AggregateWaveModel::getSummaries(int, sv_frame_t, sv_frame_t,
                                  RangeBlock &, int &) const
 {
     //!!! complete
 }
 
 AggregateWaveModel::Range
-AggregateWaveModel::getSummary(int, int, int) const
+AggregateWaveModel::getSummary(int, sv_frame_t, sv_frame_t) const
 {
     //!!! complete
     return Range();
@@ -215,7 +170,7 @@
 int
 AggregateWaveModel::getComponentCount() const
 {
-    return m_components.size();
+    return int(m_components.size());
 }
 
 AggregateWaveModel::ModelChannelSpec
@@ -231,7 +186,7 @@
 }
 
 void
-AggregateWaveModel::componentModelChangedWithin(int start, int end)
+AggregateWaveModel::componentModelChangedWithin(sv_frame_t start, sv_frame_t end)
 {
     emit modelChangedWithin(start, end);
 }
--- a/data/model/AggregateWaveModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/AggregateWaveModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -49,35 +49,27 @@
 
     const ZoomConstraint *getZoomConstraint() const { return &m_zoomConstraint; }
 
-    int getFrameCount() const;
+    sv_frame_t getFrameCount() const;
     int getChannelCount() const;
-    int getSampleRate() const;
-
-    virtual Model *clone() const;
+    sv_samplerate_t getSampleRate() const;
 
     float getValueMinimum() const { return -1.0f; }
     float getValueMaximum() const { return  1.0f; }
 
-    virtual int getStartFrame() const { return 0; }
-    virtual int getEndFrame() const { return getFrameCount(); }
+    virtual sv_frame_t getStartFrame() const { return 0; }
+    virtual sv_frame_t getEndFrame() const { return getFrameCount(); }
 
-    virtual int getData(int channel, int start, int count,
-                           float *buffer) const;
+    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const;
 
-    virtual int getData(int channel, int start, int count,
-                           double *buffer) const;
-
-    virtual int getData(int fromchannel, int tochannel,
-                           int start, int count,
-                           float **buffer) 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, int start, int count,
+    virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count,
                               RangeBlock &ranges,
                               int &blockSize) const;
 
-    virtual Range getSummary(int channel, int start, int 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 modelChangedWithin(int, int);
+    void modelChangedWithin(sv_frame_t, sv_frame_t);
     void completionChanged();
 
 protected slots:
     void componentModelChanged();
-    void componentModelChangedWithin(int, int);
+    void componentModelChangedWithin(sv_frame_t, sv_frame_t);
     void componentModelCompletionChanged();
 
 protected:
--- a/data/model/AlignmentModel.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/AlignmentModel.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -37,8 +37,8 @@
         connect(m_rawPath, SIGNAL(modelChanged()),
                 this, SLOT(pathChanged()));
 
-        connect(m_rawPath, SIGNAL(modelChangedWithin(int, int)),
-                this, SLOT(pathChangedWithin(int, int)));
+        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;
 }
 
-int
+sv_frame_t
 AlignmentModel::getStartFrame() const
 {
-    int a = m_reference->getStartFrame();
-    int b = m_aligned->getStartFrame();
+    sv_frame_t a = m_reference->getStartFrame();
+    sv_frame_t b = m_aligned->getStartFrame();
     return std::min(a, b);
 }
 
-int
+sv_frame_t
 AlignmentModel::getEndFrame() const
 {
-    int a = m_reference->getEndFrame();
-    int b = m_aligned->getEndFrame();
+    sv_frame_t a = m_reference->getEndFrame();
+    sv_frame_t b = m_aligned->getEndFrame();
     return std::max(a, b);
 }
 
-int
+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;
 }
 
-int
-AlignmentModel::toReference(int 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);
 }
 
-int
-AlignmentModel::fromReference(int 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::pathChangedWithin(int, int)
+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
 }
 
-int
-AlignmentModel::align(PathModel *path, int 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;
 
-    int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/AlignmentModel.h	Fri Jun 26 14:08:05 2015 +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 int getStartFrame() const;
-    virtual int getEndFrame() const;
-    virtual int 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;
 
-    int toReference(int frame) const;
-    int fromReference(int 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 modelChangedWithin(int startFrame, int endFrame);
+    void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
     void completionChanged();
 
 protected slots:
     void pathChanged();
-    void pathChangedWithin(int startFrame, int endFrame);
+    void pathChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
     void pathCompletionChanged();
 
 protected:
@@ -83,7 +83,7 @@
     void constructPath() const;
     void constructReversePath() const;
 
-    int align(PathModel *path, int frame) const;
+    sv_frame_t align(PathModel *path, sv_frame_t frame) const;
 };
 
 #endif
--- a/data/model/Dense3DModelPeakCache.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/Dense3DModelPeakCache.h	Fri Jun 26 14:08:05 2015 +0100
@@ -33,22 +33,18 @@
         return m_source && m_source->isOK(); 
     }
 
-    virtual int getSampleRate() const {
+    virtual sv_samplerate_t getSampleRate() const {
         return m_source->getSampleRate();
     }
 
-    virtual int getStartFrame() const {
+    virtual sv_frame_t getStartFrame() const {
         return m_source->getStartFrame();
     }
 
-    virtual int getEndFrame() const {
+    virtual sv_frame_t getEndFrame() const {
         return m_source->getEndFrame();
     }
 
-    virtual Model *clone() const {
-        return new Dense3DModelPeakCache(m_source, m_resolution);
-    }
-    
     virtual int getResolution() const {
         return m_source->getResolution() * m_resolution;
     }
--- a/data/model/DenseThreeDimensionalModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/DenseThreeDimensionalModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -93,7 +93,7 @@
      * 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 n; }
+    virtual float getBinValue(int n) const { return float(n); }
 
     /**
      * Obtain the name of the unit of the values returned from
@@ -171,11 +171,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();
     }
-    virtual int getRowForFrame(long frame) const {
-        return frame / getSampleRate();
+    virtual int getRowForFrame(sv_frame_t frame) const {
+        return int(frame / getResolution());
     }
 
 protected:
--- a/data/model/DenseTimeValueModel.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/DenseTimeValueModel.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -29,7 +29,7 @@
 }
 	
 QString
-DenseTimeValueModel::toDelimitedDataStringSubset(QString delimiter, int f0, int f1) const
+DenseTimeValueModel::toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const
 {
     int ch = getChannelCount();
 
@@ -37,27 +37,19 @@
 
     if (f1 <= f0) return "";
 
-    float **all = new float *[ch];
-    for (int c = 0; c < ch; ++c) {
-        all[c] = new float[f1 - f0];
-    }
+    auto data = getMultiChannelData(0, ch - 1, f0, f1 - f0);
 
-    int n = getData(0, ch - 1, f0, f1 - f0, all);
-
+    if (data.empty() || data[0].empty()) return "";
+    
     QStringList list;
-    for (int 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 (int 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 (int c = 0; c < ch; ++c) {
-        delete[] all[c];
-    }
-    delete[] all;
-
     return list.join("\n");
 }
--- a/data/model/DenseTimeValueModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/DenseTimeValueModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -57,37 +57,27 @@
 
     /**
      * 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 int getData(int channel, int start, int 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 int getData(int channel, int start, int 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 int getData(int fromchannel, int tochannel,
-                           int start, int 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 getDefaultPlayClipId() const { return ""; }
 
-    virtual QString toDelimitedDataStringSubset(QString delimiter, int f0, int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/EditableDenseThreeDimensionalModel.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -27,9 +27,11 @@
 #include <cmath>
 #include <cassert>
 
+using std::vector;
+
 #include "system/System.h"
 
-EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(int sampleRate,
+EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(sv_samplerate_t sampleRate,
                                                                        int resolution,
                                                                        int yBinCount,
                                                                        CompressionType compression,
@@ -55,50 +57,30 @@
     return true;
 }
 
-int
+sv_samplerate_t
 EditableDenseThreeDimensionalModel::getSampleRate() const
 {
     return m_sampleRate;
 }
 
-int
+sv_frame_t
 EditableDenseThreeDimensionalModel::getStartFrame() const
 {
     return m_startFrame;
 }
 
 void
-EditableDenseThreeDimensionalModel::setStartFrame(int f)
+EditableDenseThreeDimensionalModel::setStartFrame(sv_frame_t f)
 {
     m_startFrame = f; 
 }
 
-int
+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 (int i = 0; i < m_data.size(); ++i) {
-	model->setColumn(i, m_data.at(i));
-    }
-
-    return model;
-}
-
 int
 EditableDenseThreeDimensionalModel::getResolution() const
 {
@@ -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;
             }
         }
@@ -445,15 +427,15 @@
 {
     QReadLocker locker(&m_lock);
 
-    QVector<float> sample;
-    QVector<int> n;
+    vector<double> sample;
+    vector<int> n;
     
     for (int i = 0; i < 10; ++i) {
         int index = i * 10;
         if (index < m_data.size()) {
             const Column &c = m_data.at(index);
-            while (c.size() > sample.size()) {
-                sample.push_back(0.f);
+            while (c.size() > int(sample.size())) {
+                sample.push_back(0.0);
                 n.push_back(0);
             }
             for (int j = 0; j < c.size(); ++j) {
@@ -464,11 +446,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
@@ -515,13 +497,13 @@
 }
 
 QString
-EditableDenseThreeDimensionalModel::toDelimitedDataStringSubset(QString delimiter, int f0, int f1) const
+EditableDenseThreeDimensionalModel::toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const
 {
     QReadLocker locker(&m_lock);
     QString s;
     for (int i = 0; i < m_data.size(); ++i) {
-        int fr = m_startFrame + i * m_resolution;
-        if (fr >= int(f0) && fr < int(f1)) {
+        sv_frame_t fr = m_startFrame + i * m_resolution;
+        if (fr >= f0 && fr < f1) {
             QStringList list;
             for (int j = 0; j < m_data.at(i).size(); ++j) {
                 list << QString("%1").arg(m_data.at(i).at(j));
--- a/data/model/EditableDenseThreeDimensionalModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/EditableDenseThreeDimensionalModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -42,7 +42,7 @@
         BasicMultirateCompression
     };
 
-    EditableDenseThreeDimensionalModel(int sampleRate,
+    EditableDenseThreeDimensionalModel(sv_samplerate_t sampleRate,
 				       int resolution,
 				       int yBinCount,
                                        CompressionType compression,
@@ -50,17 +50,14 @@
 
     virtual bool isOK() const;
 
-    virtual int getSampleRate() const;
-    virtual int getStartFrame() const;
-    virtual int 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(int);
+    virtual void setStartFrame(sv_frame_t);
 
     /**
      * Return the number of sample frames covered by each set of bins.
@@ -190,7 +187,7 @@
     QString getTypeName() const { return tr("Editable Dense 3-D"); }
 
     virtual QString toDelimitedDataString(QString delimiter) const;
-    virtual QString toDelimitedDataStringSubset(QString delimiter, int f0, int f1) const;
+    virtual QString toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const;
 
     virtual void toXml(QTextStream &out,
                        QString indent = "",
@@ -216,8 +213,8 @@
     std::vector<float> m_binValues;
     QString m_binValueUnit;
 
-    int m_startFrame;
-    int m_sampleRate;
+    sv_frame_t m_startFrame;
+    sv_samplerate_t m_sampleRate;
     int m_resolution;
     int m_yBinCount;
     CompressionType m_compression;
--- a/data/model/FFTModel.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/FFTModel.cpp	Fri Jun 26 14:08:05 2015 +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,302 @@
 #include <algorithm>
 
 #include <cassert>
+#include <deque>
 
 #ifndef __GNUC__
 #include <alloca.h>
 #endif
 
+using namespace std;
+
 FFTModel::FFTModel(const DenseTimeValueModel *model,
                    int channel,
                    WindowType windowType,
                    int windowSize,
                    int windowIncrement,
-                   int fftSize,
-                   bool polar,
-                   StorageAdviser::Criteria criteria,
-                   int fillFromColumn) :
-    //!!! ZoomConstraint!
-    m_server(0),
-    m_xshift(0),
-    m_yshift(0)
+                   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()
-
-    int xratio = windowIncrement / m_server->getWindowIncrement();
-    int 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;
-    }
-
-    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;
+    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");
     }
 }
 
 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,
-                    int windowSize,
-                    int windowIncrement,
-                    int fftSize,
-                    bool polar,
-                    StorageAdviser::Criteria criteria,
-                    int 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;
 }
 
 int
-FFTModel::getSampleRate() const
+FFTModel::getHeight() const
 {
-    return isOK() ? m_server->getModel()->getSampleRate() : 0;
+    return m_fftSize / 2 + 1;
+}
+
+QString
+FFTModel::getBinName(int n) const
+{
+    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
 {
-    Profiler profiler("FFTModel::getColumn", false);
-
-    Column result;
-
-    result.clear();
-    int 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 (int y = 0; y < h; ++y) {
-            result.push_back(magnitudes[y]);
-        }
-
-    } else {
-        for (int i = 0; i < h; ++i) result.push_back(0.f);
-    }
-
-    return result;
+    auto cplx = getFFTColumn(x);
+    Column col;
+    col.reserve(int(cplx.size()));
+    for (auto c: cplx) col.push_back(abs(c));
+    return col;
 }
 
-QString
-FFTModel::getBinName(int n) const
+float
+FFTModel::getMagnitudeAt(int x, int y) const
 {
-    int sr = getSampleRate();
-    if (!sr) return "";
-    QString name = tr("%1 Hz").arg((n * sr) / ((getHeight()-1) * 2));
-    return name;
+    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;
+    for (int i = 0; i < col.size(); ++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(int x, int y, float &frequency)
+FFTModel::isColumnAvailable(int) const
+{
+    //!!!
+    return true;
+}
+
+bool
+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;
+}
+
+bool
+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 true;
+}
+
+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 col;
+}
+
+bool
+FFTModel::estimateStableFrequency(int x, int y, double &frequency)
 {
     if (!isOK()) return false;
 
-    int sampleRate = m_server->getModel()->getSampleRate();
-
-    int fftSize = m_server->getFFTSize() >> m_yshift;
-    frequency = (float(y) * sampleRate) / fftSize;
+    frequency = double(y * getSampleRate()) / m_fftSize;
 
     if (x+1 >= getWidth()) return false;
 
@@ -225,23 +330,21 @@
     //  = 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);
 
     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;
 }
@@ -284,17 +387,17 @@
 
     float mean = 0.f;
     for (int i = 0; i < values.size(); ++i) mean += values[i];
-    if (values.size() >0) mean /= values.size();
-
+    if (values.size() > 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).
 
-    int sampleRate = getSampleRate();
+    sv_samplerate_t sampleRate = getSampleRate();
 
-    std::deque<float> window;
-    std::vector<int> inrange;
+    deque<float> window;
+    vector<int> inrange;
     float dist = 0.5;
 
     int medianWinSize = getPeakPickWindowSize(type, sampleRate, ymin, dist);
@@ -324,16 +427,16 @@
             window.pop_front();
         }
 
-        int actualSize = window.size();
+        int actualSize = int(window.size());
 
         if (type == MajorPitchAdaptivePeaks) {
             if (ymax + halfWin < values.size()) binmax = ymax + halfWin;
             else binmax = values.size()-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)];
 
         int centrebin = 0;
         if (bin > actualSize/2) centrebin = bin - actualSize/2;
@@ -373,22 +476,21 @@
 }
 
 int
-FFTModel::getPeakPickWindowSize(PeakPickType type, int sampleRate,
+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;
 
-    int fftSize = m_server->getFFTSize() >> m_yshift;
-    float binfreq = (float(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;
 }
@@ -403,8 +505,7 @@
     if (!isOK()) return peaks;
     PeakLocationSet locations = getPeaks(type, x, ymin, ymax);
 
-    int sampleRate = getSampleRate();
-    int fftSize = m_server->getFFTSize() >> m_yshift;
+    sv_samplerate_t sampleRate = getSampleRate();
     int incr = getResolution();
 
     // This duplicates some of the work of estimateStableFrequency to
@@ -412,7 +513,7 @@
     // 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));
@@ -421,15 +522,13 @@
     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 +536,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/FFTModel.h	Fri Jun 26 14:08:05 2015 +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,69 @@
      * 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,
              int windowSize,
              int windowIncrement,
-             int fftSize,
-             bool polar,
-             StorageAdviser::Criteria criteria = StorageAdviser::NoCriteria,
-             int fillFromColumn = 0);
+             int fftSize);
     ~FFTModel();
 
-    inline float getMagnitudeAt(int x, int y) {
-        return m_server->getMagnitudeAt(x << m_xshift, y << m_yshift);
-    }
-    inline float getNormalizedMagnitudeAt(int x, int y) {
-        return m_server->getNormalizedMagnitudeAt(x << m_xshift, y << m_yshift);
-    }
-    inline float getMaximumMagnitudeAt(int x) {
-        return m_server->getMaximumMagnitudeAt(x << m_xshift);
-    }
-    inline float getPhaseAt(int x, int y) {
-        return m_server->getPhaseAt(x << m_xshift, y << m_yshift);
-    }
-    inline void getValuesAt(int x, int y, float &real, float &imaginary) {
-        m_server->getValuesAt(x << m_xshift, y << m_yshift, real, imaginary);
-    }
-    inline bool isColumnAvailable(int x) const {
-        return m_server->isColumnReady(x << m_xshift);
-    }
-
-    inline bool getMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) {
-        return m_server->getMagnitudesAt(x << m_xshift, values, minbin << m_yshift, count, getYRatio());
-    }
-    inline bool getNormalizedMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) {
-        return m_server->getNormalizedMagnitudesAt(x << m_xshift, values, minbin << m_yshift, count, getYRatio());
-    }
-    inline bool getPhasesAt(int x, float *values, int minbin = 0, int count = 0) {
-        return m_server->getPhasesAt(x << m_xshift, values, minbin << m_yshift, count, getYRatio());
-    }
-    inline bool getValuesAt(int x, float *reals, float *imaginaries, int minbin = 0, int count = 0) {
-        return m_server->getValuesAt(x << m_xshift, reals, imaginaries, minbin << m_yshift, count, getYRatio());
-    }
-
-    inline int getFillExtent() const { return m_server->getFillExtent(); }
-
     // DenseThreeDimensionalModel and Model methods:
     //
-    inline virtual int 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 int 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(int x, int 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 int getStartFrame() const {
-        return 0;
-    }
-    virtual int getEndFrame() const {
-        return getWidth() * getResolution() + getResolution();
-    }
-    virtual int getSampleRate() const;
-    virtual int getResolution() const {
-        return m_server->getWindowIncrement() << m_xshift;
-    }
-    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;
-    virtual QString getBinName(int 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 isColumnAvailable(int x) const;
+    bool getMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) const;
+    bool getNormalizedMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) const;
+    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(int x, int y, float &frequency);
+    virtual bool estimateStableFrequency(int x, int y, double &frequency);
 
     enum PeakPickType
     {
@@ -161,7 +121,7 @@
     };
 
     typedef std::set<int> PeakLocationSet; // bin
-    typedef std::map<int, float> PeakSet; // bin -> freq
+    typedef std::map<int, double> PeakSet; // bin -> freq
 
     /**
      * Return locations of peak bins in the range [ymin,ymax].  If
@@ -176,15 +136,6 @@
     virtual PeakSet getPeakFrequencies(PeakPickType type, int x,
                                        int ymin = 0, int 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(); }
-
     QString getTypeName() const { return tr("FFT"); }
 
 public slots:
@@ -194,23 +145,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, int, int, int,
-                             bool, StorageAdviser::Criteria, int);
+    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 };
+    }
 
-    int getPeakPickWindowSize(PeakPickType type, int sampleRate,
-                                 int 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;
 
-    int getYRatio() {
-        int ys = m_yshift;
-        int 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
--- a/data/model/FlexiNoteModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/FlexiNoteModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -42,15 +42,15 @@
 struct FlexiNote
 {
 public:
-    FlexiNote(long _frame) : frame(_frame), value(0.0f), duration(0), level(1.f) { }
-    FlexiNote(long _frame, float _value, int _duration, float _level, QString _label) :
+    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; }
 
-    long frame;
+    sv_frame_t frame;
     float value;
-    int duration;
+    sv_frame_t duration;
     float level;
     QString label;
 
@@ -66,13 +66,15 @@
             .arg(XmlExportable::encodeEntities(label)).arg(extraAttributes);
     }
 
-    QString toDelimitedDataString(QString delimiter, int 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);
     }
@@ -102,15 +104,15 @@
     Q_OBJECT
     
 public:
-    FlexiNoteModel(int sampleRate, int resolution,
-	      bool notifyOnAdd = true) :
+    FlexiNoteModel(sv_samplerate_t sampleRate, int resolution,
+                   bool notifyOnAdd = true) :
 	IntervalModel<FlexiNote>(sampleRate, resolution, notifyOnAdd),
 	m_valueQuantization(0)
     {
 	PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
-    FlexiNoteModel(int sampleRate, int resolution,
+    FlexiNoteModel(sv_samplerate_t sampleRate, int resolution,
 	      float valueMinimum, float valueMaximum,
 	      bool notifyOnAdd = true) :
 	IntervalModel<FlexiNote>(sampleRate, resolution,
@@ -208,7 +210,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;
         }
 
@@ -231,20 +233,20 @@
         return getNotesWithin(getStartFrame(), getEndFrame());
     }
 
-    NoteList getNotesWithin(int startFrame, int endFrame) const 
+    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) {
-    	    int duration = pli->duration;
+    	    sv_frame_t duration = pli->duration;
             if (duration == 0 || duration == 1) {
-                duration = getSampleRate() / 20;
+                duration = sv_frame_t(getSampleRate() / 20);
             }
-            int pitch = lrintf(pli->value);
+            int pitch = int(lrintf(pli->value));
 
             int velocity = 100;
             if (pli->level > 0.f && pli->level <= 1.f) {
-                velocity = lrintf(pli->level * 127);
+                velocity = int(lrintf(pli->level * 127));
             }
 
             NoteData note(pli->frame, duration, pitch, velocity);
--- a/data/model/ImageModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/ImageModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -55,7 +55,7 @@
             .arg(extraAttributes);
     }
 
-    QString toDelimitedDataString(QString delimiter, int 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(int sampleRate, int resolution, bool notifyOnAdd = true) :
+    ImageModel(sv_samplerate_t sampleRate, int resolution, bool notifyOnAdd = true) :
 	SparseModel<ImagePoint>(sampleRate, resolution, notifyOnAdd)
     { }
 
--- a/data/model/IntervalModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/IntervalModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -29,12 +29,12 @@
 class IntervalModel : public SparseValueModel<PointType>
 {
 public:
-    IntervalModel(int sampleRate, int resolution,
+    IntervalModel(sv_samplerate_t sampleRate, int resolution,
                   bool notifyOnAdd = true) :
 	SparseValueModel<PointType>(sampleRate, resolution, notifyOnAdd)
     { }
 
-    IntervalModel(int sampleRate, int 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(); 
@@ -107,7 +107,7 @@
 
         switch (column) {
         // column cannot be 0 or 1, those cases were handled above
-        case 2: point.value = value.toDouble(); break;
+        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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/Labeller.h	Fri Jun 26 14:08:05 2015 +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 {
-                int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/Model.cpp	Fri Jun 26 14:08:05 2015 +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,30 +110,30 @@
     return m_alignment->getReferenceModel();
 }
 
-int
-Model::alignToReference(int 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;
     }
-    int 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;
 }
 
-int
-Model::alignFromReference(int 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;
     }
-    int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/Model.h	Fri Jun 26 14:08:05 2015 +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 int getStartFrame() const = 0;
+    virtual sv_frame_t getStartFrame() const = 0;
 
     /**
      * Return the last audio frame spanned by the model.
      */
-    virtual int getEndFrame() const = 0;
+    virtual sv_frame_t getEndFrame() const = 0;
 
     /**
      * Return the frame rate in frames per second.
      */
-    virtual int 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 int getNativeRate() const { return getSampleRate(); }
+    virtual sv_samplerate_t getNativeRate() const { return getSampleRate(); }
 
     /**
      * Return the "work title" of the model, if known.
@@ -91,22 +91,6 @@
     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.
-     */
-    virtual Model *clone() const = 0;
-
-    /**
      * 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
@@ -200,13 +184,13 @@
      * Return the frame number of the reference model that corresponds
      * to the given frame number in this model.
      */
-    virtual int alignToReference(int 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 int alignFromReference(int referenceFrame) const;
+    virtual sv_frame_t alignFromReference(sv_frame_t referenceFrame) const;
 
     /**
      * Return the completion percentage for the alignment model: 100
@@ -234,11 +218,20 @@
                        QString extraAttributes = "") const;
 
     virtual QString toDelimitedDataString(QString delimiter) const {
-        return toDelimitedDataStringSubset(delimiter, getStartFrame(), getEndFrame());
+        return toDelimitedDataStringSubset
+            (delimiter, getStartFrame(), getEndFrame() + 1);
     }
-    virtual QString toDelimitedDataStringSubset(QString, int /* f0 */, int /* 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();
@@ -255,7 +248,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 modelChangedWithin(int startFrame, int endFrame);
+    void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
 
     /**
      * Emitted when some internal processing has advanced a stage, but
--- a/data/model/ModelDataTableModel.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/ModelDataTableModel.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -31,8 +31,8 @@
     Model *baseModel = dynamic_cast<Model *>(m);
 
     connect(baseModel, SIGNAL(modelChanged()), this, SLOT(modelChanged()));
-    connect(baseModel, SIGNAL(modelChangedWithin(int, int)),
-            this, SLOT(modelChangedWithin(int, int)));
+    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()));
 }
@@ -155,14 +155,14 @@
 }
 
 QModelIndex 
-ModelDataTableModel::getModelIndexForFrame(int 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);
 }
 
-int 
+sv_frame_t
 ModelDataTableModel::getFrameForModelIndex(const QModelIndex &index) const
 {
     if (!m_model) return 0;
@@ -219,7 +219,7 @@
 }
 
 void 
-ModelDataTableModel::modelChangedWithin(int, int)
+ModelDataTableModel::modelChangedWithin(sv_frame_t, sv_frame_t)
 {
     //!!! inefficient
     clearSort();
--- a/data/model/ModelDataTableModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/ModelDataTableModel.h	Fri Jun 26 14:08:05 2015 +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(int frame) const;
-    int 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);
 
@@ -69,7 +71,7 @@
 
 protected slots:
     void modelChanged();
-    void modelChangedWithin(int, int);
+    void modelChangedWithin(sv_frame_t, sv_frame_t);
     void modelAboutToBeDeleted();
 
 protected:
--- a/data/model/NoteData.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/NoteData.h	Fri Jun 26 14:08:05 2015 +0100
@@ -21,20 +21,21 @@
 
 struct NoteData
 {
-    NoteData(int _start, int _dur, int _mp, int _vel) :
+    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) { };
+	isMidiPitchQuantized(true), velocity(_vel), channel(0) { };
             
-    int start;     // audio sample frame
-    int duration;  // in audio sample frames
-    int midiPitch; // 0-127
+    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 velocity;    // MIDI-style 0-127
+    int channel;     // MIDI 0-15
 
     float getFrequency() const {
         if (isMidiPitchQuantized) {
-            return Pitch::getFrequencyForPitch(midiPitch);
+            return float(Pitch::getFrequencyForPitch(midiPitch));
         } else {
             return frequency;
         }
@@ -47,7 +48,7 @@
 {
 public:
     virtual NoteList getNotes() const = 0;
-    virtual NoteList getNotesWithin(int startFrame, int endFrame) const = 0;
+    virtual NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const = 0;
 };
 
 #endif
--- a/data/model/NoteModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/NoteModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -38,15 +38,15 @@
 struct Note
 {
 public:
-    Note(long _frame) : frame(_frame), value(0.0f), duration(0), level(1.f) { }
-    Note(long _frame, float _value, int _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;
-    int duration;
+    sv_frame_t duration;
     float level;
     QString label;
 
@@ -62,13 +62,14 @@
             .arg(XmlExportable::encodeEntities(label)).arg(extraAttributes);
     }
 
-    QString toDelimitedDataString(QString delimiter, int 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);
     }
@@ -98,7 +99,7 @@
     Q_OBJECT
     
 public:
-    NoteModel(int sampleRate, int resolution,
+    NoteModel(sv_samplerate_t sampleRate, int resolution,
 	      bool notifyOnAdd = true) :
 	IntervalModel<Note>(sampleRate, resolution, notifyOnAdd),
 	m_valueQuantization(0)
@@ -106,7 +107,7 @@
 	PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
-    NoteModel(int sampleRate, int resolution,
+    NoteModel(sv_samplerate_t sampleRate, int resolution,
 	      float valueMinimum, float valueMaximum,
 	      bool notifyOnAdd = true) :
 	IntervalModel<Note>(sampleRate, resolution,
@@ -202,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;
         }
 
@@ -224,7 +225,7 @@
         return getNotesWithin(getStartFrame(), getEndFrame());
     }
 
-    NoteList getNotesWithin(int startFrame, int endFrame) const {
+    NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const {
         
 	PointList points = getPoints(startFrame, endFrame);
         NoteList notes;
@@ -232,16 +233,16 @@
         for (PointList::iterator pli =
 		 points.begin(); pli != points.end(); ++pli) {
 
-	    int duration = pli->duration;
+	    sv_frame_t duration = pli->duration;
             if (duration == 0 || duration == 1) {
-                duration = getSampleRate() / 20;
+                duration = sv_frame_t(getSampleRate() / 20);
             }
 
-            int pitch = lrintf(pli->value);
+            int pitch = int(lrintf(pli->value));
             
             int velocity = 100;
             if (pli->level > 0.f && pli->level <= 1.f) {
-                velocity = lrintf(pli->level * 127);
+                velocity = int(lrintf(pli->level * 127));
             }
 
             NoteData note(pli->frame, duration, pitch, velocity);
--- a/data/model/PathModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/PathModel.h	Fri Jun 26 14:08:05 2015 +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,
-                                  int 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(int sampleRate, int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/PowerOfSqrtTwoZoomConstraint.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -44,12 +44,13 @@
 	float val = 1.0, prevVal = 1.0;
 	while (val + 0.01 < blockSize) {
 	    prevVal = val;
-	    val *= sqrt(2.f);
+	    val *= sqrtf(2.f);
 	}
 	int rval;
-	if (dir == RoundUp) rval = int(val + 0.01);
-	else if (dir == RoundDown) rval = int(prevVal + 0.01);
-	else if (val - blockSize < blockSize - prevVal) rval = int(val + 0.01);
+	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;
--- a/data/model/RangeSummarisableTimeValueModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/RangeSummarisableTimeValueModel.h	Fri Jun 26 14:08:05 2015 +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,7 +86,7 @@
      * parameter so as to return the block size that was actually
      * obtained.
      */
-    virtual void getSummaries(int channel, int start, int count,
+    virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count,
                               RangeBlock &ranges,
                               int &blockSize) const = 0;
 
@@ -83,7 +95,7 @@
      * the given number of underlying sample frames, summarised at a
      * block size equal to the distance between start and end frames.
      */
-    virtual Range getSummary(int channel, int start, int count) const = 0;
+    virtual Range getSummary(int channel, sv_frame_t start, sv_frame_t count) const = 0;
 
     virtual int getSummaryBlockSize(int desired) const = 0;
 
--- a/data/model/RegionModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/RegionModel.h	Fri Jun 26 14:08:05 2015 +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, int _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;
-    int 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, int 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(int sampleRate, int resolution,
+    RegionModel(sv_samplerate_t sampleRate, int resolution,
                 bool notifyOnAdd = true) :
 	IntervalModel<RegionRec>(sampleRate, resolution, notifyOnAdd),
 	m_valueQuantization(0),
@@ -102,7 +102,7 @@
     {
     }
 
-    RegionModel(int sampleRate, int resolution,
+    RegionModel(sv_samplerate_t sampleRate, int resolution,
                 float valueMinimum, float valueMaximum,
                 bool notifyOnAdd = true) :
 	IntervalModel<RegionRec>(sampleRate, resolution,
--- a/data/model/SparseModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/SparseModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -43,16 +43,14 @@
                     public TabularModel
 {
 public:
-    SparseModel(int sampleRate, int resolution,
+    SparseModel(sv_samplerate_t sampleRate, int resolution,
 		bool notifyOnAdd = true);
     virtual ~SparseModel() { }
     
     virtual bool isOK() const { return true; }
-    virtual int getStartFrame() const;
-    virtual int getEndFrame() const;
-    virtual int 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
@@ -64,6 +62,12 @@
     }
     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;
@@ -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.
@@ -148,24 +152,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 toDelimitedDataStringSubset(QString delimiter, int f0, int 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 +299,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; }
@@ -371,11 +381,12 @@
     }
             
 protected:
-    int m_sampleRate;
+    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;
@@ -383,16 +394,16 @@
     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 +418,7 @@
         if (m_rows.empty()) rebuildRowVector();
         if (row < 0 || row + 1 > int(m_rows.size())) return m_points.end();
 
-        int 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 +445,7 @@
         if (m_rows.empty()) rebuildRowVector();
         if (row < 0 || row + 1 > int(m_rows.size())) return m_points.end();
 
-        int 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 +472,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(int sampleRate,
+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 +535,11 @@
 }
 
 template <typename PointType>
-int
+sv_frame_t
 SparseModel<PointType>::getStartFrame() const
 {
     QMutexLocker locker(&m_mutex);
-    int f = 0;
+    sv_frame_t f = 0;
     if (!m_points.empty()) {
 	f = m_points.begin()->frame;
     }
@@ -492,30 +547,17 @@
 }
 
 template <typename PointType>
-int
+sv_frame_t
 SparseModel<PointType>::getEndFrame() const
 {
     QMutexLocker locker(&m_mutex);
-    int 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>
@@ -541,7 +583,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 +609,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 +625,7 @@
 
 template <typename PointType>
 void
-SparseModel<PointType>::getPointIterators(long frame,
+SparseModel<PointType>::getPointIterators(sv_frame_t frame,
                                           PointListIterator &startItr,
                                           PointListIterator &endItr)
 {
@@ -595,8 +637,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 +648,7 @@
 
 template <typename PointType>
 void
-SparseModel<PointType>::getPointIterators(long frame,
+SparseModel<PointType>::getPointIterators(sv_frame_t frame,
                                           PointListConstIterator &startItr,
                                           PointListConstIterator &endItr) const
 {
@@ -619,8 +661,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 +674,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 +685,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 +697,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 +707,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;
--- a/data/model/SparseOneDimensionalModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/SparseOneDimensionalModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -26,12 +26,12 @@
 struct OneDimensionalPoint
 {
 public:
-    OneDimensionalPoint(int _frame) : frame(_frame) { }
-    OneDimensionalPoint(int _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; }
     
-    int frame;
+    sv_frame_t frame;
     QString label;
 
     QString getLabel() const { return label; }
@@ -45,7 +45,7 @@
             .arg(extraAttributes);
     }
 
-    QString toDelimitedDataString(QString delimiter, int sampleRate) const
+    QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const
     {
         QStringList list;
         list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
@@ -76,7 +76,7 @@
     Q_OBJECT
     
 public:
-    SparseOneDimensionalModel(int sampleRate, int resolution,
+    SparseOneDimensionalModel(sv_samplerate_t sampleRate, int resolution,
 			      bool notifyOnAdd = true) :
 	SparseModel<OneDimensionalPoint>(sampleRate, resolution, notifyOnAdd)
     {
@@ -187,7 +187,7 @@
         return getNotesWithin(getStartFrame(), getEndFrame());
     }
 
-    NoteList getNotesWithin(int startFrame, int endFrame) const {
+    NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const {
         
 	PointList points = getPoints(startFrame, endFrame);
         NoteList notes;
@@ -197,7 +197,7 @@
 
             notes.push_back
                 (NoteData(pli->frame,
-                          getSampleRate() / 6, // arbitrary short duration
+                          sv_frame_t(getSampleRate() / 6), // arbitrary short duration
                           64,   // default pitch
                           100)); // default velocity
         }
--- a/data/model/SparseTimeValueModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/SparseTimeValueModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -49,7 +49,7 @@
             .arg(extraAttributes);
     }
 
-    QString toDelimitedDataString(QString delimiter, int sampleRate) const
+    QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const
     {
         QStringList list;
         list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
@@ -81,7 +81,7 @@
     Q_OBJECT
     
 public:
-    SparseTimeValueModel(int sampleRate, int resolution,
+    SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution,
 			 bool notifyOnAdd = true) :
 	SparseValueModel<TimeValuePoint>(sampleRate, resolution,
 					 notifyOnAdd)
@@ -91,7 +91,7 @@
 	PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
-    SparseTimeValueModel(int sampleRate, int resolution,
+    SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution,
 			 float valueMinimum, float valueMaximum,
 			 bool notifyOnAdd = true) :
 	SparseValueModel<TimeValuePoint>(sampleRate, resolution,
@@ -168,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/SparseValueModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -31,7 +31,7 @@
 class SparseValueModel : public SparseModel<PointType>
 {
 public:
-    SparseValueModel(int sampleRate, int 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(int sampleRate, int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/TabularModel.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/TextModel.h	Fri Jun 26 14:08:05 2015 +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, int 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(int sampleRate, int 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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/WaveFileModel.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -32,12 +32,14 @@
 
 #include <cassert>
 
+using namespace std;
+
 //#define DEBUG_WAVE_FILE_MODEL 1
 
 PowerOfSqrtTwoZoomConstraint
 WaveFileModel::m_zoomConstraint;
 
-WaveFileModel::WaveFileModel(FileSource source, int targetRate) :
+WaveFileModel::WaveFileModel(FileSource source, sv_samplerate_t targetRate) :
     m_source(source),
     m_path(source.getLocation()),
     m_reader(0),
@@ -107,7 +109,7 @@
         if (m_reader) {
             int decodeCompletion = m_reader->getDecodeCompletion();
             if (decodeCompletion < 90) *completion = decodeCompletion;
-            else *completion = std::min(*completion, decodeCompletion);
+            else *completion = min(*completion, decodeCompletion);
         }
         if (*completion != 0 &&
             *completion != 100 &&
@@ -124,14 +126,7 @@
     return ready;
 }
 
-Model *
-WaveFileModel::clone() const
-{
-    WaveFileModel *model = new WaveFileModel(m_source);
-    return model;
-}
-
-int
+sv_frame_t
 WaveFileModel::getFrameCount() const
 {
     if (!m_reader) return 0;
@@ -145,18 +140,18 @@
     return m_reader->getChannelCount();
 }
 
-int
+sv_samplerate_t
 WaveFileModel::getSampleRate() const 
 {
     if (!m_reader) return 0;
     return m_reader->getSampleRate();
 }
 
-int
+sv_samplerate_t
 WaveFileModel::getNativeRate() const 
 {
     if (!m_reader) return 0;
-    int rate = m_reader->getNativeRate();
+    sv_samplerate_t rate = m_reader->getNativeRate();
     if (rate == 0) rate = getSampleRate();
     return rate;
 }
@@ -183,137 +178,79 @@
     if (m_reader) return m_reader->getLocation();
     return "";
 }
+
+QString
+WaveFileModel::getLocalFilename() const
+{
+    if (m_reader) return m_reader->getLocalFilename();
+    return "";
+}
     
-int
-WaveFileModel::getData(int channel, int start, int count,
-                       float *buffer) const
+vector<float>
+WaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) 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)
+    // Read directly from the file.  This is used for e.g. audio
+    // playback or input to transforms.
 
 #ifdef DEBUG_WAVE_FILE_MODEL
     cout << "WaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << ", " << buffer << 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 {
-        for (int i = 0; i < count; ++i) {
-            buffer[i] = 0.f;
-        }
         if (count <= m_startFrame - start) {
-            return 0;
+            return {};
         } else {
             count -= (m_startFrame - start);
             start = 0;
         }
     }
 
-    if (!m_reader || !m_reader->isOK() || count == 0) {
-        for (int i = 0; i < count; ++i) buffer[i] = 0.f;
-        return 0;
-    }
+    vector<float> interleaved = m_reader->getInterleavedFrames(start, count);
+    if (channels == 1) return interleaved;
 
-#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);
-
-    int i = 0;
-
-    int ch0 = channel, ch1 = channel;
-    if (channel == -1) {
-	ch0 = 0;
-	ch1 = channels - 1;
-    }
+    sv_frame_t obtained = interleaved.size() / channels;
     
-    while (i < count) {
-
-	buffer[i] = 0.0;
-
-	for (int ch = ch0; ch <= ch1; ++ch) {
-
-	    int index = i * channels + ch;
-	    if (index >= (int)frames.size()) break;
-            
-	    float sample = frames[index];
-	    buffer[i] += sample;
-	}
-
-	++i;
-    }
-
-    return i;
-}
-
-int
-WaveFileModel::getData(int channel, int start, int 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;
+    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 {
-        for (int i = 0; i < count; ++i) buffer[i] = 0.0;
-        if (count <= m_startFrame - start) {
-            return 0;
-        } else {
-            count -= (m_startFrame - start);
-            start = 0;
+        // 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];
+            }
         }
     }
 
-    if (!m_reader || !m_reader->isOK() || count == 0) {
-        for (int i = 0; i < count; ++i) buffer[i] = 0.0;
-        return 0;
-    }
-
-    int channels = getChannelCount();
-
-    SampleBlock frames(count * channels);
-    m_reader->getInterleavedFrames(start, count, frames);
-
-    int 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) {
-
-	    int index = i * channels + ch;
-	    if (index >= (int)frames.size()) break;
-            
-	    float sample = frames[index];
-	    buffer[i] += sample;
-	}
-
-	++i;
-    }
-
-    return i;
+    return result;
 }
 
-int
-WaveFileModel::getData(int fromchannel, int tochannel,
-                       int start, int count,
-                       float **buffer) const
+vector<vector<float>>
+WaveFileModel::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 << "WaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << ", " << buffer << endl;
 #endif
@@ -324,74 +261,47 @@
         cerr << "ERROR: WaveFileModel::getData: fromchannel ("
                   << fromchannel << ") > tochannel (" << tochannel << ")"
                   << endl;
-        return 0;
+        return {};
     }
 
     if (tochannel >= channels) {
         cerr << "ERROR: WaveFileModel::getData: tochannel ("
                   << tochannel << ") >= channel count (" << channels << ")"
                   << endl;
-        return 0;
+        return {};
     }
 
-    if (fromchannel == tochannel) {
-        return getData(fromchannel, start, count, buffer[0]);
+    if (!m_reader || !m_reader->isOK() || count == 0) {
+        return {};
     }
 
     int 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 (int c = 0; c < reqchannels; ++c) {
-            for (int i = 0; i < count; ++i) buffer[c][i] = 0.f;
-        }
         if (count <= m_startFrame - start) {
-            return 0;
+            return {};
         } else {
             count -= (m_startFrame - start);
             start = 0;
         }
     }
 
-    if (!m_reader || !m_reader->isOK() || count == 0) {
-        for (int c = 0; c < reqchannels; ++c) {
-            for (int i = 0; i < count; ++i) buffer[c][i] = 0.f;
+    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 0;
     }
-
-    SampleBlock frames(count * channels);
-    m_reader->getInterleavedFrames(start, count, frames);
-
-    int i = 0;
-
-    int index = 0, available = frames.size();
-
-    while (i < count) {
-
-        if (index >= available) break;
-
-        int destc = 0;
-
-        for (int c = 0; c < channels; ++c) {
-            
-            if (c >= fromchannel && c <= tochannel) {
-                buffer[destc][i] = frames[index];
-                ++destc;
-            }
-
-            ++index;
-        }
-
-        ++i;
-    }
-
-    return i;
+    
+    return result;
 }
 
 int
@@ -411,7 +321,7 @@
 }    
 
 void
-WaveFileModel::getSummaries(int channel, int start, int count,
+WaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count,
                             RangeBlock &ranges, int &blockSize) const
 {
     ranges.clear();
@@ -448,18 +358,18 @@
             m_lastDirectReadCount != count ||
             m_directRead.empty()) {
 
-            m_reader->getInterleavedFrames(start, count, m_directRead);
+            m_directRead = m_reader->getInterleavedFrames(start, count);
             m_lastDirectReadStart = start;
             m_lastDirectReadCount = count;
         }
 
 	float max = 0.0, min = 0.0, total = 0.0;
-	int i = 0, got = 0;
+	sv_frame_t i = 0, got = 0;
 
 	while (i < count) {
 
-	    int index = i * channels + channel;
-	    if (index >= (int)m_directRead.size()) break;
+	    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;
@@ -470,7 +380,7 @@
             ++got;
             
             if (got == blockSize) {
-                ranges.push_back(Range(min, max, total / got));
+                ranges.push_back(Range(min, max, total / float(got)));
                 min = max = total = 0.0f;
                 got = 0;
 	    }
@@ -479,7 +389,7 @@
         m_directReadMutex.unlock();
 
 	if (got > 0) {
-            ranges.push_back(Range(min, max, total / got));
+            ranges.push_back(Range(min, max, total / float(got)));
 	}
 
 	return;
@@ -492,21 +402,21 @@
 
         blockSize = roundedBlockSize;
 
-	int cacheBlock, div;
+	sv_frame_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;
+	    cacheBlock = sv_frame_t((1 << m_zoomConstraint.getMinCachePower()) * sqrt(2.) + 0.01);
+            div = sv_frame_t(((1 << power) * sqrt(2.) + 0.01) / double(cacheBlock));
 	}
 
-	int startIndex = start / cacheBlock;
-	int endIndex = (start + count) / cacheBlock;
+	sv_frame_t startIndex = start / cacheBlock;
+	sv_frame_t endIndex = (start + count) / cacheBlock;
 
 	float max = 0.0, min = 0.0, total = 0.0;
-	int i = 0, got = 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;
@@ -514,8 +424,8 @@
 
 	for (i = 0; i <= endIndex - startIndex; ) {
         
-	    int index = (i + startIndex) * channels + channel;
-	    if (index >= (int)cache.size()) break;
+	    sv_frame_t index = (i + startIndex) * channels + channel;
+	    if (index >= (sv_frame_t)cache.size()) break;
             
             const Range &range = cache[index];
             if (range.max() > max || got == 0) max = range.max();
@@ -526,14 +436,14 @@
             ++got;
             
 	    if (got == div) {
-		ranges.push_back(Range(min, max, total / got));
+		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 / got));
+            ranges.push_back(Range(min, max, total / float(got)));
 	}
     }
 
@@ -544,7 +454,7 @@
 }
 
 WaveFileModel::Range
-WaveFileModel::getSummary(int channel, int start, int count) const
+WaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const
 {
     Range range;
     if (!isOK()) return range;
@@ -562,8 +472,8 @@
 
     bool first = false;
 
-    int blockStart = (start / blockSize) * blockSize;
-    int blockEnd = ((start + count) / blockSize) * blockSize;
+    sv_frame_t blockStart = (start / blockSize) * blockSize;
+    sv_frame_t blockEnd = ((start + count) / blockSize) * blockSize;
 
     if (blockStart < start) blockStart += blockSize;
         
@@ -580,16 +490,16 @@
 
     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()));
+        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(std::min(range.min(), endRange.min()));
-        range.setMax(std::max(range.max(), endRange.max()));
-        range.setAbsmean(std::min(range.absmean(), endRange.absmean()));
+        range.setMin(min(range.min(), endRange.min()));
+        range.setMax(max(range.max(), endRange.max()));
+        range.setAbsmean(min(range.absmean(), endRange.absmean()));
     }
 
     return range;
@@ -619,7 +529,7 @@
 WaveFileModel::fillTimerTimedOut()
 {
     if (m_fillThread) {
-	int fillExtent = m_fillThread->getFillExtent();
+	sv_frame_t fillExtent = m_fillThread->getFillExtent();
 #ifdef DEBUG_WAVE_FILE_MODEL
         SVDEBUG << "WaveFileModel::fillTimerTimedOut: extent = " << fillExtent << endl;
 #endif
@@ -659,12 +569,12 @@
 {
     int cacheBlockSize[2];
     cacheBlockSize[0] = (1 << m_model.m_zoomConstraint.getMinCachePower());
-    cacheBlockSize[1] = ((unsigned int)((1 << m_model.m_zoomConstraint.getMinCachePower()) *
+    cacheBlockSize[1] = (int((1 << m_model.m_zoomConstraint.getMinCachePower()) *
                                         sqrt(2.) + 0.01));
     
-    int frame = 0;
-    int readBlockSize = 16384;
-    SampleBlock block;
+    sv_frame_t frame = 0;
+    const sv_frame_t readBlockSize = 16384;
+    vector<float> block;
 
     if (!m_model.isOK()) return;
     
@@ -702,50 +612,44 @@
 
             if (updating && (frame + readBlockSize > m_frameCount)) break;
 
-            m_model.m_reader->getInterleavedFrames(frame, readBlockSize, block);
+            block = m_model.m_reader->getInterleavedFrames(frame, readBlockSize);
 
 //            cerr << "block is " << block.size() << endl;
 
-            for (int i = 0; i < readBlockSize; ++i) {
+            for (sv_frame_t i = 0; i < readBlockSize; ++i) {
 		
                 if (channels * i + channels > (int)block.size()) break;
 
                 for (int ch = 0; ch < channels; ++ch) {
 
-                    int index = channels * i + ch;
+                    sv_frame_t index = channels * i + ch;
                     float sample = block[index];
                     
-                    for (int ct = 0; ct < 2; ++ct) { // cache type
+                    for (int cacheType = 0; cacheType < 2; ++cacheType) { // 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);
-                        }
-
+                        sv_frame_t rangeIndex = ch * 2 + cacheType;
+                        range[rangeIndex].sample(sample);
                         means[rangeIndex] += fabsf(sample);
                     }
                 }
-                
+
+                //!!! this looks like a ludicrous way to do synchronisation
                 QMutexLocker locker(&m_model.m_mutex);
 
-                for (int ct = 0; ct < 2; ++ct) {
+                for (int cacheType = 0; cacheType < 2; ++cacheType) {
 
-                    if (++count[ct] == cacheBlockSize[ct]) {
+                    if (++count[cacheType] == cacheBlockSize[cacheType]) {
                         
                         for (int ch = 0; ch < int(channels); ++ch) {
-                            int rangeIndex = ch * 2 + ct;
-                            means[rangeIndex] /= count[ct];
+                            int rangeIndex = ch * 2 + cacheType;
+                            means[rangeIndex] = means[rangeIndex] / float(count[cacheType]);
                             range[rangeIndex].setAbsmean(means[rangeIndex]);
-                            m_model.m_cache[ct].push_back(range[rangeIndex]);
+                            m_model.m_cache[cacheType].push_back(range[rangeIndex]);
                             range[rangeIndex] = Range();
                             means[rangeIndex] = 0.f;
                         }
 
-                        count[ct] = 0;
+                        count[cacheType] = 0;
                     }
                 }
                 
@@ -771,24 +675,24 @@
 
         QMutexLocker locker(&m_model.m_mutex);
 
-        for (int ct = 0; ct < 2; ++ct) {
+        for (int cacheType = 0; cacheType < 2; ++cacheType) {
 
-            if (count[ct] > 0) {
+            if (count[cacheType] > 0) {
 
                 for (int ch = 0; ch < int(channels); ++ch) {
-                    int rangeIndex = ch * 2 + ct;
-                    means[rangeIndex] /= count[ct];
+                    int rangeIndex = ch * 2 + cacheType;
+                    means[rangeIndex] = means[rangeIndex] / float(count[cacheType]);
                     range[rangeIndex].setAbsmean(means[rangeIndex]);
-                    m_model.m_cache[ct].push_back(range[rangeIndex]);
+                    m_model.m_cache[cacheType].push_back(range[rangeIndex]);
                     range[rangeIndex] = Range();
                     means[rangeIndex] = 0.f;
                 }
 
-                count[ct] = 0;
+                count[cacheType] = 0;
             }
             
-            const Range &rr = *m_model.m_cache[ct].begin();
-            MUNLOCK(&rr, m_model.m_cache[ct].capacity() * sizeof(Range));
+            const Range &rr = *m_model.m_cache[cacheType].begin();
+            MUNLOCK(&rr, m_model.m_cache[cacheType].capacity() * sizeof(Range));
         }
     }
     
@@ -798,8 +702,8 @@
     m_fillExtent = m_frameCount;
 
 #ifdef DEBUG_WAVE_FILE_MODEL        
-    for (int ct = 0; ct < 2; ++ct) {
-        cerr << "Cache type " << ct << " now contains " << m_model.m_cache[ct].size() << " ranges" << endl;
+    for (int cacheType = 0; cacheType < 2; ++cacheType) {
+        cerr << "Cache type " << cacheType << " now contains " << m_model.m_cache[cacheType].size() << " ranges" << endl;
     }
 #endif
 }
--- a/data/model/WaveFileModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/WaveFileModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -34,7 +34,7 @@
     Q_OBJECT
 
 public:
-    WaveFileModel(FileSource source, int targetRate = 0);
+    WaveFileModel(FileSource source, sv_samplerate_t targetRate = 0);
     WaveFileModel(FileSource source, AudioFileReader *reader);
     ~WaveFileModel();
 
@@ -43,42 +43,36 @@
 
     const ZoomConstraint *getZoomConstraint() const { return &m_zoomConstraint; }
 
-    int getFrameCount() const;
+    sv_frame_t getFrameCount() const;
     int getChannelCount() const;
-    int getSampleRate() const;
-    int getNativeRate() const;
+    sv_samplerate_t getSampleRate() const;
+    sv_samplerate_t getNativeRate() const;
 
     QString getTitle() const;
     QString getMaker() const;
     QString getLocation() const;
 
-    virtual Model *clone() const;
+    QString getLocalFilename() const;
 
     float getValueMinimum() const { return -1.0f; }
     float getValueMaximum() const { return  1.0f; }
 
-    virtual int getStartFrame() const { return m_startFrame; }
-    virtual int 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(int startFrame) { m_startFrame = startFrame; }
+    void setStartFrame(sv_frame_t startFrame) { m_startFrame = startFrame; }
 
-    virtual int getData(int channel, int start, int count,
-                        float *buffer) const;
+    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const;
 
-    virtual int getData(int channel, int start, int count,
-                        double *buffer) const;
-
-    virtual int getData(int fromchannel, int tochannel,
-                        int start, int count,
-                        float **buffers) 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, int start, int count,
+    virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count,
                               RangeBlock &ranges,
                               int &blockSize) const;
 
-    virtual Range getSummary(int channel, int start, int count) const;
+    virtual Range getSummary(int channel, sv_frame_t start, sv_frame_t count) const;
 
     QString getTypeName() const { return tr("Wave File"); }
 
@@ -100,13 +94,13 @@
 	    m_model(model), m_fillExtent(0),
             m_frameCount(model.getFrameCount()) { }
     
-	int getFillExtent() const { return m_fillExtent; }
+	sv_frame_t getFillExtent() const { return m_fillExtent; }
         virtual void run();
 
     protected:
         WaveFileModel &m_model;
-	int m_fillExtent;
-        int m_frameCount;
+	sv_frame_t m_fillExtent;
+        sv_frame_t m_frameCount;
     };
          
     void fillCache();
@@ -116,19 +110,19 @@
     AudioFileReader *m_reader;
     bool m_myReader;
 
-    int m_startFrame;
+    sv_frame_t m_startFrame;
 
     RangeBlock m_cache[2]; // interleaved at two base resolutions
     mutable QMutex m_mutex;
     RangeCacheFillThread *m_fillThread;
     QTimer *m_updateTimer;
-    int m_lastFillExtent;
+    sv_frame_t m_lastFillExtent;
     bool m_exiting;
     static PowerOfSqrtTwoZoomConstraint m_zoomConstraint;
 
-    mutable SampleBlock m_directRead;
-    mutable int m_lastDirectReadStart;
-    mutable int m_lastDirectReadCount;
+    mutable std::vector<float> m_directRead;
+    mutable sv_frame_t m_lastDirectReadStart;
+    mutable sv_frame_t m_lastDirectReadCount;
     mutable QMutex m_directReadMutex;
 };    
 
--- a/data/model/WritableWaveFileModel.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/WritableWaveFileModel.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -28,9 +28,11 @@
 #include <iostream>
 #include <stdint.h>
 
+using namespace std;
+
 //#define DEBUG_WRITABLE_WAVE_FILE_MODEL 1
 
-WritableWaveFileModel::WritableWaveFileModel(int sampleRate,
+WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate,
 					     int channels,
 					     QString path) :
     m_model(0),
@@ -86,8 +88,8 @@
     m_model->setStartFrame(m_startFrame);
 
     connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
-    connect(m_model, SIGNAL(modelChangedWithin(int, int)),
-            this, SIGNAL(modelChangedWithin(int, int)));
+    connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+            this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)));
 }
 
 WritableWaveFileModel::~WritableWaveFileModel()
@@ -98,14 +100,14 @@
 }
 
 void
-WritableWaveFileModel::setStartFrame(int startFrame)
+WritableWaveFileModel::setStartFrame(sv_frame_t startFrame)
 {
     m_startFrame = startFrame;
     if (m_model) m_model->setStartFrame(startFrame);
 }
 
 bool
-WritableWaveFileModel::addSamples(float **samples, int count)
+WritableWaveFileModel::addSamples(float **samples, sv_frame_t count)
 {
     if (!m_writer) return false;
 
@@ -162,43 +164,26 @@
     }
 }
 
-int
+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);
 }
 
-int
-WritableWaveFileModel::getData(int channel, int start, int 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);
-}
-
-int
-WritableWaveFileModel::getData(int channel, int start, int count,
-                               double *buffer) const
-{
-    if (!m_model || m_model->getChannelCount() == 0) return 0;
-    return m_model->getData(channel, start, count, buffer);
-}
-
-int
-WritableWaveFileModel::getData(int fromchannel, int tochannel,
-                               int start, int 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);
 }    
 
 int
@@ -209,7 +194,7 @@
 }
 
 void
-WritableWaveFileModel::getSummaries(int channel, int start, int count,
+WritableWaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count,
                                     RangeBlock &ranges,
                                     int &blockSize) const
 {
@@ -219,7 +204,7 @@
 }
 
 WritableWaveFileModel::Range
-WritableWaveFileModel::getSummary(int channel, int start, int 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);
--- a/data/model/WritableWaveFileModel.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/model/WritableWaveFileModel.h	Fri Jun 26 14:08:05 2015 +0100
@@ -26,7 +26,7 @@
     Q_OBJECT
 
 public:
-    WritableWaveFileModel(int sampleRate, int channels, QString path = "");
+    WritableWaveFileModel(sv_samplerate_t sampleRate, int channels, QString path = "");
     ~WritableWaveFileModel();
 
     /**
@@ -35,7 +35,7 @@
      * progress of this file, if it has a known end point, and should
      * call setCompletion(100) when the file has been written.
      */
-    virtual bool addSamples(float **samples, int count);
+    virtual bool addSamples(float **samples, sv_frame_t count);
     
     bool isOK() const;
     bool isReady(int *) const;
@@ -48,36 +48,28 @@
         return &zc;
     }
 
-    int getFrameCount() const;
+    sv_frame_t getFrameCount() const;
     int getChannelCount() const { return m_channels; }
-    int getSampleRate() const { return m_sampleRate; }
-
-    virtual Model *clone() const;
+    sv_samplerate_t getSampleRate() const { return m_sampleRate; }
 
     float getValueMinimum() const { return -1.0f; }
     float getValueMaximum() const { return  1.0f; }
 
-    virtual int getStartFrame() const { return m_startFrame; }
-    virtual int 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(int startFrame);
+    void setStartFrame(sv_frame_t startFrame);
 
-    virtual int getData(int channel, int start, int count,
-                           float *buffer) const;
+    virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const;
 
-    virtual int getData(int channel, int start, int count,
-                           double *buffer) const;
-
-    virtual int getData(int fromchannel, int tochannel,
-                           int start, int count,
-                           float **buffer) 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, int start, int count,
+    virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count,
                               RangeBlock &ranges, int &blockSize) const;
 
-    virtual Range getSummary(int channel, int start, int count) const;
+    virtual Range getSummary(int channel, sv_frame_t start, sv_frame_t count) const;
 
     QString getTypeName() const { return tr("Writable Wave File"); }
 
@@ -89,10 +81,10 @@
     WaveFileModel *m_model;
     WavFileWriter *m_writer;
     WavFileReader *m_reader;
-    int m_sampleRate;
+    sv_samplerate_t m_sampleRate;
     int m_channels;
-    int m_frameCount;
-    int m_startFrame;
+    sv_frame_t m_frameCount;
+    sv_frame_t m_startFrame;
     int m_completion;
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/test/Compares.h	Fri Jun 26 14:08:05 2015 +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	Fri Jun 26 14:08:05 2015 +0100
@@ -0,0 +1,92 @@
+/* -*- 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"
+
+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	Fri Jun 26 14:08:05 2015 +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	Fri Jun 26 14:08:05 2015 +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	Fri Jun 26 14:08:05 2015 +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	Fri Jun 26 14:08:05 2015 +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_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 -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	Fri Sep 12 11:38:55 2014 +0100
+++ b/data/osc/OSCMessage.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -41,7 +41,7 @@
 int
 OSCMessage::getArgCount() const
 {
-    return m_args.size();
+    return int(m_args.size());
 }
 
 const QVariant &
--- a/plugin/DSSIPluginFactory.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/DSSIPluginFactory.cpp	Fri Jun 26 14:08:05 2015 +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");
 
@@ -375,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/DSSIPluginFactory.h	Fri Jun 26 14:08:05 2015 +0100
@@ -40,9 +40,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);
 
 protected:
     DSSIPluginFactory();
--- a/plugin/DSSIPluginInstance.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/DSSIPluginInstance.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -57,8 +57,8 @@
 				       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),
@@ -90,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];
     }
 
@@ -149,10 +149,10 @@
     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;
@@ -181,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;
@@ -204,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;
@@ -223,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]))
         {
@@ -240,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);
@@ -248,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
@@ -265,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;
@@ -279,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
@@ -312,7 +313,7 @@
 }
 
 void
-DSSIPluginInstance::setIdealChannelCount(size_t channels)
+DSSIPluginInstance::setIdealChannelCount(int channels)
 {
 #ifdef DEBUG_DSSI
     SVDEBUG << "DSSIPluginInstance::setIdealChannelCount: channel count "
@@ -332,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];
 	}
 
@@ -342,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];
 	}
 
@@ -428,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];
 	}
 
@@ -455,7 +456,7 @@
 
 
 void
-DSSIPluginInstance::instantiate(unsigned long sampleRate)
+DSSIPluginInstance::instantiate(sv_samplerate_t sampleRate)
 {
     if (!m_descriptor) return;
 
@@ -472,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])) {
@@ -512,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);
     }
@@ -570,7 +577,7 @@
     return std::string();
 }
 
-unsigned long
+int
 DSSIPluginInstance::getProgram(std::string name) const
 {
 #ifdef DEBUG_DSSI
@@ -581,7 +588,7 @@
 
     checkProgramCache();
 
-    unsigned long rv;
+    int rv;
 
     for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
 	 i != m_cachedPrograms.end(); ++i) {
@@ -621,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) {
@@ -689,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
@@ -700,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],
@@ -708,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],
@@ -716,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,
@@ -724,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
@@ -733,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,
@@ -741,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) {
@@ -772,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;
@@ -803,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);
 	}
@@ -811,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) {
@@ -842,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) {
@@ -906,7 +913,7 @@
 }
 
 void
-DSSIPluginInstance::sendEvent(const Vamp::RealTime &eventTime,
+DSSIPluginInstance::sendEvent(const RealTime &eventTime,
 			      const void *e)
 {
 #ifdef DEBUG_DSSI_PROCESS
@@ -982,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;
@@ -1038,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
@@ -1051,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) {
@@ -1060,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) {
@@ -1110,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];
 	    }
 	}
@@ -1142,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
@@ -1214,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
@@ -1229,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/DSSIPluginInstance.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/FeatureExtractionPluginFactory.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -299,7 +299,7 @@
 
 Vamp::Plugin *
 FeatureExtractionPluginFactory::instantiatePlugin(QString identifier,
-						  float inputSampleRate)
+						  sv_samplerate_t inputSampleRate)
 {
     Profiler profiler("FeatureExtractionPluginFactory::instantiatePlugin");
 
@@ -357,7 +357,7 @@
         goto done;
     }
 
-    plugin = new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
+    plugin = new Vamp::PluginHostAdapter(descriptor, float(inputSampleRate));
 
     if (plugin) {
         m_handleMap[plugin] = libraryHandle;
--- a/plugin/FeatureExtractionPluginFactory.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/FeatureExtractionPluginFactory.h	Fri Jun 26 14:08:05 2015 +0100
@@ -23,6 +23,7 @@
 #include <vamp-hostsdk/Plugin.h>
 
 #include "base/Debug.h"
+#include "base/BaseTypes.h"
 
 class FeatureExtractionPluginFactory
 {
@@ -42,7 +43,7 @@
     // 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).
--- a/plugin/LADSPAPluginFactory.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/LADSPAPluginFactory.cpp	Fri Jun 26 14:08:05 2015 +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");
 
@@ -754,7 +754,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])) {
 		
@@ -774,7 +774,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;
--- a/plugin/LADSPAPluginFactory.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/LADSPAPluginFactory.h	Fri Jun 26 14:08:05 2015 +0100
@@ -47,9 +47,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);
 
--- a/plugin/LADSPAPluginInstance.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/LADSPAPluginInstance.cpp	Fri Jun 26 14:08:05 2015 +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),
@@ -156,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;
@@ -227,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])) {
 
@@ -289,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;
 }
@@ -316,7 +316,7 @@
 }
 
 void
-LADSPAPluginInstance::setIdealChannelCount(size_t channels)
+LADSPAPluginInstance::setIdealChannelCount(int channels)
 {
     if (m_audioPortsIn.size() != 1 || channels == m_instanceCount) {
 	silence();
@@ -378,7 +378,7 @@
 
 
 void
-LADSPAPluginInstance::instantiate(unsigned long sampleRate)
+LADSPAPluginInstance::instantiate(sv_samplerate_t sampleRate)
 {
     if (!m_descriptor) return;
 
@@ -394,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));
     }
 }
 
@@ -464,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;
 
@@ -491,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) {
@@ -518,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) {
@@ -531,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/LADSPAPluginInstance.h	Fri Jun 26 14:08:05 2015 +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;
     
--- a/plugin/RealTimePluginFactory.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/RealTimePluginFactory.cpp	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/RealTimePluginFactory.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/RealTimePluginInstance.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/plugin/plugins/SamplePlayer.cpp	Fri Jun 26 14:08:05 2015 +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,7 @@
 	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)) {
@@ -282,7 +282,7 @@
 			    unsigned long program)
 {
     SamplePlayer *player = (SamplePlayer *)handle;
-    player->m_pendingProgramChange = program;
+    player->m_pendingProgramChange = (int)program;
 }
 
 int
@@ -415,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));
@@ -572,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);
 	}
     }
 
@@ -585,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
@@ -604,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/rdf/RDFExporter.cpp	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/rdf/RDFFeatureWriter.cpp	Fri Jun 26 14:08:05 2015 +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());
     }
@@ -679,29 +685,45 @@
 
         stream << "\n:feature_timeline_" << featureNumber << " a tl:DiscreteTimeLine .\n\n";
 
-        int 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;
 
-        int 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 ;";
--- a/rdf/RDFFeatureWriter.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/rdf/RDFFeatureWriter.h	Fri Jun 26 14:08:05 2015 +0100
@@ -44,6 +44,8 @@
     RDFFeatureWriter();
     virtual ~RDFFeatureWriter();
 
+    virtual string getDescription() const;
+
     virtual ParameterList getSupportedParameters() const;
     virtual void setParameters(map<string, string> &params);
 
--- a/rdf/RDFImporter.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/rdf/RDFImporter.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -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)
@@ -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();
@@ -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	Fri Sep 12 11:38:55 2014 +0100
+++ b/rdf/RDFImporter.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/rdf/RDFTransformFactory.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -212,7 +212,8 @@
             "window_type",
             "sample_rate",
             "start", 
-            "duration"
+            "duration",
+            "plugin_version"
         };
         
         for (int j = 0; j < int(sizeof(optionals)/sizeof(optionals[0])); ++j) {
@@ -241,11 +242,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 +400,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 +414,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/svcore.pro	Fri Sep 12 11:38:55 2014 +0100
+++ b/svcore.pro	Fri Jun 26 14:08:05 2015 +0100
@@ -29,7 +29,7 @@
     }
 }
 
-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
 
@@ -51,6 +51,7 @@
 
 HEADERS += base/AudioLevel.h \
            base/AudioPlaySource.h \
+           base/BaseTypes.h \
            base/Clipboard.h \
            base/Command.h \
            base/Debug.h \
@@ -118,15 +119,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 \
@@ -138,7 +133,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 \
@@ -184,12 +178,9 @@
            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 \
@@ -199,7 +190,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 \
--- a/system/System.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/system/System.cpp	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/system/System.h	Fri Jun 26 14:08:05 2015 +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	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/CSVFeatureWriter.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -23,16 +23,21 @@
 
 #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)
 {
 }
 
@@ -40,6 +45,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 +62,25 @@
     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);
 
     return pl;
 }
@@ -70,8 +96,18 @@
         cerr << i->first << " -> " << i->second << endl;
         if (i->first == "separator") {
             m_separator = i->second.c_str();
+            cerr << "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;
         }
     }
 }
@@ -83,18 +119,87 @@
                         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 +208,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 << f.values[j];
+    }
+    
+    if (f.label != "") {
+        stream << m_separator << "\"" << f.label.c_str() << "\"";
+    }
+    
+    stream << "\n";
 }
 
 
--- a/transform/CSVFeatureWriter.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/CSVFeatureWriter.h	Fri Jun 26 14:08:05 2015 +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,29 @@
                        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);
 };
 
 #endif
--- a/transform/FeatureExtractionModelTransformer.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/FeatureExtractionModelTransformer.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -44,7 +44,7 @@
     ModelTransformer(in, transform),
     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;
 
     initialise();
 }
@@ -54,8 +54,12 @@
     ModelTransformer(in, transforms),
     m_plugin(0)
 {
-//    SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << pluginId << ", outputName " << m_transform.getOutput() << endl;
-
+    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();
 }
 
@@ -235,7 +239,7 @@
     bool haveBinCount = m_descriptors[n]->hasFixedBinCount;
 
     if (haveBinCount) {
-	binCount = m_descriptors[n]->binCount;
+	binCount = (int)m_descriptors[n]->binCount;
     }
 
     m_needAdditionalModels[n] = false;
@@ -249,7 +253,7 @@
         haveExtents = true;
     }
 
-    int modelRate = input->getSampleRate();
+    sv_samplerate_t modelRate = input->getSampleRate();
     int modelResolution = 1;
 
     if (m_descriptors[n]->sampleType != 
@@ -264,7 +268,7 @@
 
     case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
 	if (m_descriptors[n]->sampleRate != 0.0) {
-	    modelResolution = int(modelRate / m_descriptors[n]->sampleRate + 0.001);
+	    modelResolution = int(round(modelRate / m_descriptors[n]->sampleRate));
 	}
 	break;
 
@@ -280,9 +284,11 @@
         //!!! data with a higher resolution than the base model at all
         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 = int(round(input->getSampleRate() /
-                                           m_descriptors[n]->sampleRate));
+            modelResolution = int(round(modelRate / m_descriptors[n]->sampleRate));
         }
 	break;
     }
@@ -573,7 +579,7 @@
     }
     if (m_abandoned) return;
 
-    int sampleRate = input->getSampleRate();
+    sv_samplerate_t sampleRate = input->getSampleRate();
 
     int channelCount = input->getChannelCount();
     if ((int)m_plugin->getMaxChannelCount() < channelCount) {
@@ -600,32 +606,31 @@
                                    primaryTransform.getWindowType(),
                                    blockSize,
                                    stepSize,
-                                   blockSize,
-                                   false,
-                                   StorageAdviser::PrecisionCritical);
-            if (!model->isOK()) {
+                                   blockSize);
+            if (!model->isOK() || model->getError() != "") {
+                QString err = model->getError();
                 delete model;
                 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 = 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) {
@@ -639,7 +644,7 @@
         contextDuration = endFrame - contextStart;
     }
 
-    long blockFrame = contextStart;
+    sv_frame_t blockFrame = contextStart;
 
     long prevCompletion = 0;
 
@@ -670,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 (int ch = 0; ch < channelCount; ++ch) {
-                int column = (blockFrame - startFrame) / stepSize;
-                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];
-                }
+                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 {
@@ -698,7 +710,7 @@
         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;
 
@@ -750,14 +762,15 @@
 
 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;
             }
         }
@@ -770,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) {
@@ -810,10 +821,10 @@
 
 void
 FeatureExtractionModelTransformer::addFeature(int n,
-                                              int blockFrame,
+                                              sv_frame_t blockFrame,
                                               const Vamp::Plugin::Feature &feature)
 {
-    int inputRate = m_input.getModel()->getSampleRate();
+    sv_samplerate_t inputRate = m_input.getModel()->getSampleRate();
 
 //    cerr << "FeatureExtractionModelTransformer::addFeature: blockFrame = "
 //              << blockFrame << ", hasTimestamp = " << feature.hasTimestamp
@@ -821,7 +832,7 @@
 //              << feature.hasDuration << ", duration = " << feature.duration
 //              << endl;
 
-    int frame = blockFrame;
+    sv_frame_t frame = blockFrame;
 
     if (m_descriptors[n]->sampleType ==
 	Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
@@ -833,26 +844,33 @@
 		<< endl;
 	    return;
 	} else {
-	    frame = Vamp::RealTime::realTime2Frame(feature.timestamp, inputRate);
+	    frame = RealTime::realTime2Frame(feature.timestamp, inputRate);
 	}
 
+//        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_fixedRateFeatureNos[n];
         } else {
             RealTime ts(feature.timestamp.sec, feature.timestamp.nsec);
-            m_fixedRateFeatureNos[n] =
-                lrint(ts.toDouble() * m_descriptors[n]->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_fixedRateFeatureNos[n] / m_descriptors[n]->sampleRate)
-                       * int(inputRate));
+        frame = lrint((double(m_fixedRateFeatureNos[n]) / rate) * inputRate);
+//        cerr << frame << endl;
     }
 
     if (frame < 0) {
@@ -916,12 +934,12 @@
             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 ((int)feature.values.size() > index) {
-                duration = feature.values[index++];
+            if (in_range_for(feature.values, index)) {
+                duration = lrintf(feature.values[index++]);
             }
         }
 
@@ -936,10 +954,11 @@
 
             FlexiNoteModel *model = getConformingOutput<FlexiNoteModel>(n);
             if (!model) return;
-            model->addPoint(FlexiNoteModel::Point(frame, value, // value is pitch
-                                             lrintf(duration),
-                                             velocity / 127.f,
-                                             feature.label.c_str()));
+            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)) {
 
@@ -953,7 +972,7 @@
             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 {
@@ -972,14 +991,16 @@
                         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()));
             }
         }
@@ -999,7 +1020,7 @@
         if (!feature.hasTimestamp && m_fixedRateFeatureNos[n] >= 0) {
             model->setColumn(m_fixedRateFeatureNos[n], values);
         } else {
-            model->setColumn(frame / model->getResolution(), values);
+            model->setColumn(int(frame / model->getResolution()), values);
         }
 
     } else {
--- a/transform/FeatureExtractionModelTransformer.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/FeatureExtractionModelTransformer.h	Fri Jun 26 14:08:05 2015 +0100
@@ -66,12 +66,12 @@
     SparseTimeValueModel *getAdditionalModel(int transformNo, int binNo);
 
     void addFeature(int n,
-                    int blockFrame,
+                    sv_frame_t blockFrame,
 		    const Vamp::Plugin::Feature &feature);
 
     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
--- a/transform/FeatureWriter.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/FeatureWriter.h	Fri Jun 26 14:08:05 2015 +0100
@@ -39,6 +39,8 @@
 public:
     virtual ~FeatureWriter() { }
 
+    virtual string getDescription() const = 0;
+
     struct Parameter { // parameter of the writer, not the plugin
         string name;
         string description;
@@ -77,8 +79,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,
--- a/transform/FileFeatureWriter.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/FileFeatureWriter.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -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;
@@ -149,7 +151,7 @@
                 }
             }
         } 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;
                 } else {
@@ -165,8 +167,8 @@
 }
 
 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)) {
@@ -177,7 +179,9 @@
         return m_singleFileName;
     }
 
-    if (m_stdout) return "";
+    if (m_stdout) {
+        return "";
+    }
     
     QUrl url(trackId, QUrl::StrictMode);
     QString scheme = url.scheme().toLower();
@@ -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	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/FileFeatureWriter.h	Fri Jun 26 14:08:05 2015 +0100
@@ -32,6 +32,7 @@
 using std::pair;
 
 class QTextStream;
+class QTextCodec;
 class QFile;
 
 class FileFeatureWriter : public FeatureWriter
@@ -50,20 +51,34 @@
     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
--- a/transform/ModelTransformer.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/ModelTransformer.h	Fri Jun 26 14:08:05 2015 +0100
@@ -68,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(); }
--- a/transform/ModelTransformerFactory.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/ModelTransformerFactory.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -55,8 +55,8 @@
                                                       const std::vector<Model *> &candidateInputModels,
                                                       Model *defaultInputModel,
                                                       AudioPlaySource *source,
-                                                      int startFrame,
-                                                      int duration,
+                                                      sv_frame_t startFrame,
+                                                      sv_frame_t duration,
                                                       UserConfigurator *configurator)
 {
     ModelTransformer::Input input(0);
@@ -99,7 +99,7 @@
 
         Vamp::Plugin *vp =
             FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
-            (id, inputModel->getSampleRate());
+            (id, float(inputModel->getSampleRate()));
 
         plugin = vp;
 
@@ -107,7 +107,7 @@
 
         RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id);
 
-        int sampleRate = inputModel->getSampleRate();
+        sv_samplerate_t sampleRate = inputModel->getSampleRate();
         int blockSize = 1024;
         int channels = 1;
         if (source) {
@@ -285,6 +285,12 @@
         m_handlers.erase(transformer);
     }
 
+    if (transformer->isAbandoned()) {
+        if (transformer->getMessage() != "") {
+            emit transformFailed("", transformer->getMessage());
+        }
+    }
+    
     transformer->wait(); // unnecessary but reassuring
     delete transformer;
 }
--- a/transform/ModelTransformerFactory.h	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/ModelTransformerFactory.h	Fri Jun 26 14:08:05 2015 +0100
@@ -47,8 +47,8 @@
                                Vamp::PluginBase *plugin,
                                Model *&inputModel,
                                AudioPlaySource *source,
-                               int startFrame,
-                               int duration,
+                               sv_frame_t startFrame,
+                               sv_frame_t duration,
                                const QMap<QString, Model *> &modelMap,
                                QStringList candidateModelNames,
                                QString defaultModelName) = 0;
@@ -67,8 +67,8 @@
                                  const std::vector<Model *> &candidateInputModels,
                                  Model *defaultInputModel,
                                  AudioPlaySource *source = 0,
-                                 int startFrame = 0,
-                                 int duration = 0,
+                                 sv_frame_t startFrame = 0,
+                                 sv_frame_t duration = 0,
                                  UserConfigurator *configurator = 0);
 
     class AdditionalModelHandler {
@@ -145,6 +145,9 @@
                                            QString &message,
                                            AdditionalModelHandler *handler = 0);
 
+signals:
+    void transformFailed(QString transformName, QString message);
+                                                                               
 protected slots:
     void transformerFinished();
 
--- a/transform/RealTimeEffectModelTransformer.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/RealTimeEffectModelTransformer.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -82,7 +82,7 @@
 
     if (m_outputNo == -1) {
 
-        int outputChannels = m_plugin->getAudioOutputCount();
+        int outputChannels = (int)m_plugin->getAudioOutputCount();
         if (outputChannels > input->getChannelCount()) {
             outputChannels = input->getChannelCount();
         }
@@ -137,26 +137,26 @@
 
     if (stvm && (m_outputNo >= int(m_plugin->getControlOutputCount()))) return;
 
-    int sampleRate = input->getSampleRate();
+    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 = 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) {
@@ -174,39 +174,48 @@
         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 (int ch = 1; ch < (int)m_plugin->getAudioInputCount(); ++ch) {
-                    for (long i = 0; i < blockSize; ++i) {
+                    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 (int ch = 0; ch < channelCount; ++ch) {
                         inbufs[ch][got] = 0.0;
@@ -214,7 +223,7 @@
                     ++got;
                 }
                 for (int ch = channelCount; ch < (int)m_plugin->getAudioInputCount(); ++ch) {
-                    for (long i = 0; i < blockSize; ++i) {
+                    for (sv_frame_t i = 0; i < blockSize; ++i) {
                         inbufs[ch][i] = inbufs[ch % channelCount][i];
                     }
                 }
@@ -235,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;
 
@@ -255,13 +264,13 @@
             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 (int c = 0; c < channelCount; ++c) {
                         tmp[c] = outbufs[c] + offset;
--- a/transform/Transform.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/Transform.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -379,14 +379,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 +520,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/Transform.h	Fri Jun 26 14:08:05 2015 +0100
@@ -135,8 +135,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;
@@ -194,7 +194,7 @@
     WindowType m_windowType;
     RealTime m_startTime;
     RealTime m_duration;
-    float m_sampleRate;
+    sv_samplerate_t m_sampleRate;
 };
 
 typedef std::vector<Transform> Transforms;
--- a/transform/TransformFactory.cpp	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/TransformFactory.cpp	Fri Jun 26 14:08:05 2015 +0100
@@ -750,7 +750,7 @@
 }
 
 Transform
-TransformFactory::getDefaultTransformFor(TransformId id, int rate)
+TransformFactory::getDefaultTransformFor(TransformId id, sv_samplerate_t rate)
 {
     Transform t;
     t.setIdentifier(id);
@@ -782,11 +782,12 @@
 }
 
 Vamp::PluginBase *
-TransformFactory::instantiateDefaultPluginFor(TransformId identifier, int 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;
@@ -913,8 +914,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;
@@ -1040,10 +1041,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	Fri Sep 12 11:38:55 2014 +0100
+++ b/transform/TransformFactory.h	Fri Jun 26 14:08:05 2015 +0100
@@ -88,7 +88,7 @@
      * with different parameters and execution context settings.
      * Return the default one for the given transform.
      */
-    Transform getDefaultTransformFor(TransformId identifier, int rate = 0);
+    Transform getDefaultTransformFor(TransformId identifier, sv_samplerate_t rate = 0);
 
     /**
      * Full name of a transform, suitable for putting on a menu.
@@ -210,7 +210,7 @@
     void populateFeatureExtractionPlugins(TransformDescriptionMap &);
     void populateRealTimePlugins(TransformDescriptionMap &);
 
-    Vamp::PluginBase *instantiateDefaultPluginFor(TransformId id, int rate);
+    Vamp::PluginBase *instantiateDefaultPluginFor(TransformId id, sv_samplerate_t rate);
     QMutex m_transformsMutex;
     QMutex m_uninstalledTransformsMutex;